diff --git a/.idea/php.xml b/.idea/php.xml index dec4919..097e372 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -17,6 +17,11 @@ + + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b6e48a..a5d911b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -226,6 +226,7 @@ features and reduced the number of exceptions down to 15 exceptions. ExitHandler, Repository, Assembly, Build, Dependency, ExecutionPolicy, Installer, Project, UpdateSource) I'm not going to list them all here, but you can find them in the commit history. - Implemented a template engine and refactored the CLI menu for the Project Manager and added a new `template` command + - Refactored the entire package structure to ncc package structure 2.0 for memory efficiency and performance ### Removed diff --git a/src/ncc/CLI/Commands/BuildCommand.php b/src/ncc/CLI/Commands/BuildCommand.php index 41db29a..5e8dbfe 100644 --- a/src/ncc/CLI/Commands/BuildCommand.php +++ b/src/ncc/CLI/Commands/BuildCommand.php @@ -1,26 +1,26 @@ load(); + $project_manager = new ProjectManager($project_path); } catch (Exception $e) { - Console::outException('Failed to load Project Configuration (project.json)', $e, 1); + Console::outException('There was an error loading the project', $e, 1); return; } @@ -97,10 +92,7 @@ namespace ncc\CLI\Commands; $build_configuration = $args['config']; } - $output = $ProjectManager->build($build_configuration); - - Console::out('Successfully built ' . $output); - exit(0); + $output = $project_manager->build($build_configuration); } catch (Exception $e) { @@ -108,6 +100,7 @@ namespace ncc\CLI\Commands; return; } + Console::out($output); } /** diff --git a/src/ncc/CLI/Management/PackageManagerMenu.php b/src/ncc/CLI/Management/PackageManagerMenu.php index 0c1731f..c280459 100644 --- a/src/ncc/CLI/Management/PackageManagerMenu.php +++ b/src/ncc/CLI/Management/PackageManagerMenu.php @@ -178,7 +178,7 @@ try { Console::out('magic_bytes: ' . json_encode(($package->getMagicBytes()?->toArray() ?? []), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); - Console::out('header: ' . json_encode(($package->getHeader()?->toArray() ?? []), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + Console::out('header: ' . json_encode(($package->getMetadata()?->toArray() ?? []), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); Console::out('assembly: ' . json_encode(($package->getAssembly()?->toArray() ?? []), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); Console::out('installer: ' . json_encode(($package->getInstaller()?->toArray() ?? []), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); } @@ -559,15 +559,15 @@ } Console::out(sprintf('Extension: %s', - Console::formatColor($package->getHeader()->getCompilerExtension()->getExtension(), ConsoleColors::GREEN) + Console::formatColor($package->getMetadata()->getCompilerExtension()->getExtension(), ConsoleColors::GREEN) )); Console::out(sprintf('Maximum Version: %s', - Console::formatColor($package->getHeader()->getCompilerExtension()->getMinimumVersion(), ConsoleColors::LIGHT_MAGENTA) + Console::formatColor($package->getMetadata()->getCompilerExtension()->getMinimumVersion(), ConsoleColors::LIGHT_MAGENTA) )); Console::out(sprintf('Minimum Version: %s', - Console::formatColor($package->getHeader()->getCompilerExtension()->getMinimumVersion(), ConsoleColors::LIGHT_MAGENTA) + Console::formatColor($package->getMetadata()->getCompilerExtension()->getMinimumVersion(), ConsoleColors::LIGHT_MAGENTA) )); if(!$user_confirmation) diff --git a/src/ncc/Classes/NccExtension/NccCompiler.php b/src/ncc/Classes/NccExtension/NccCompiler.php new file mode 100644 index 0000000..9c310a2 --- /dev/null +++ b/src/ncc/Classes/NccExtension/NccCompiler.php @@ -0,0 +1,188 @@ +project_manager = $project_manager; + } + + /** + * @param string $build_configuration + * @return string + * @throws ConfigurationException + * @throws IOException + * @throws NotSupportedException + * @throws PathNotFoundException + */ + public function build(string $build_configuration=BuildConfigurationValues::DEFAULT): string + { + $configuration = $this->project_manager->getProjectConfiguration()->getBuild()->getBuildConfiguration($build_configuration); + $package_path = $configuration->getOutputPath() . DIRECTORY_SEPARATOR . $this->project_manager->getProjectConfiguration()->getAssembly()->getPackage() . '.ncc'; + $package_writer = new PackageWriter($package_path); + + Console::out(sprintf('Building project \'%s\'', $this->project_manager->getProjectConfiguration()->getAssembly()->getName())); + + // Debugging information + if(Resolver::checkLogLevel(LogLevel::DEBUG, Main::getLogLevel())) + { + foreach($this->project_manager->getProjectConfiguration()->getAssembly()->toArray() as $prop => $value) + { + Console::outDebug(sprintf('assembly.%s: %s', $prop, ($value ?? 'n/a'))); + } + + foreach($this->project_manager->getProjectConfiguration()->getProject()->getCompiler()->toArray() as $prop => $value) + { + Console::outDebug(sprintf('compiler.%s: %s', $prop, ($value ?? 'n/a'))); + } + } + + Console::outVerbose('Building package header...'); + $package_writer->setMetadata($this->buildMetadata($build_configuration)); + + Console::outVerbose('Adding assembly information...'); + $package_writer->setAssembly($this->project_manager->getProjectConfiguration()->getAssembly()); + + if($this->project_manager->getProjectConfiguration()->getInstaller() !== null) + { + Console::outVerbose('Adding installer information...'); + /** @noinspection NullPointerExceptionInspection */ + $package_writer->setInstaller($this->project_manager->getProjectConfiguration()->getInstaller()); + } + + // Process execution policies + if(count($this->project_manager->getProjectConfiguration()->getExecutionPolicies()) > 0) + { + Console::out('Processing execution policies...'); + $execution_units = $this->project_manager->getExecutionUnits($build_configuration); + + if(count($execution_units) === 0) + { + Console::outWarning('The project contains execution policies but none of them are used'); + } + + foreach($execution_units as $unit) + { + $package_writer->addExecutionUnit($unit); + } + } + + // Compile package components + foreach($this->project_manager->getComponents($build_configuration) as $component) + { + Console::outVerbose(sprintf('Compiling \'%s\'', $component)); + $package_writer->addComponent($this->buildComponent($component)); + } + + // Compile package resources + foreach($this->project_manager->getResources($build_configuration) as $resource) + { + Console::outVerbose(sprintf('Processing \'%s\'', $resource)); + $package_writer->addResource($this->buildResource($resource)); + } + + $package_writer->close(); + return $package_path; + } + + /** + * Compiles a single component as a base64 encoded string + * + * @param string $file_path + * @return Package\Component + * @throws IOException + * @throws PathNotFoundException + */ + public function buildComponent(string $file_path): Package\Component + { + return new Package\Component( + Functions::removeBasename($file_path), + Base64::encode(IO::fread($file_path)), ComponentDataType::BASE64_ENCODED + ); + } + + /** + * @param string $file_path + * @return Resource + * @throws IOException + * @throws PathNotFoundException + */ + public function buildResource(string $file_path): Package\Resource + { + return new Package\Resource( + basename($file_path), IO::fread($file_path) + ); + } + + /** + * Builds the package header + * + * @param ProjectManager $project_manager + * @param string $build_configuration + * @return Package\Metadata + * @throws ConfigurationException + */ + public function buildMetadata(string $build_configuration=BuildConfigurationValues::DEFAULT): Package\Metadata + { + $header = new Package\Metadata($this->project_manager->getProjectConfiguration()->getProject()->getCompiler()); + + $header->setRuntimeConstants($this->project_manager->getRuntimeConstants($build_configuration)); + $header->setOptions($this->project_manager->getCompilerOptions($build_configuration)); + $header->setUpdateSource($this->project_manager->getProjectConfiguration()->getProject()->getUpdateSource()); + $header->setMainExecutionPolicy($this->project_manager->getProjectConfiguration()->getBuild()->getMain()); + $header->setInstaller($this->project_manager->getProjectConfiguration()->getInstaller()); + + return $header; + } + } \ No newline at end of file diff --git a/src/ncc/Classes/NccExtension/PackageCompiler.php b/src/ncc/Classes/NccExtension/PackageCompiler.php deleted file mode 100644 index 0b9089a..0000000 --- a/src/ncc/Classes/NccExtension/PackageCompiler.php +++ /dev/null @@ -1,405 +0,0 @@ -getProjectConfiguration(); - - if(Resolver::checkLogLevel(LogLevel::DEBUG, Main::getLogLevel())) - { - foreach($configuration->getAssembly()->toArray() as $prop => $value) - { - Console::outDebug(sprintf('assembly.%s: %s', $prop, ($value ?? 'n/a'))); - } - foreach($configuration->getProject()->getCompiler()->toArray() as $prop => $value) - { - Console::outDebug(sprintf('compiler.%s: %s', $prop, ($value ?? 'n/a'))); - } - } - - // Select the correct compiler for the specified extension - if (strtolower($configuration->getProject()->getCompiler()->getExtension()) === CompilerExtensions::PHP) - { - /** @var CompilerInterface $Compiler */ - $Compiler = new PhpCompiler($configuration, $manager->getProjectPath()); - } - else - { - throw new NotSupportedException('The compiler extension \'' . $configuration->getProject()->getCompiler()->getExtension() . '\' is not supported'); - } - - $build_configuration = $configuration->getBuild()->getBuildConfiguration($build_configuration)->getName(); - Console::out(sprintf('Building %s=%s', $configuration->getAssembly()->getPackage(), $configuration->getAssembly()->getVersion())); - $Compiler->prepare($build_configuration); - $Compiler->build(); - - return self::writePackage( - $manager->getProjectPath(), $Compiler->getPackage(), $configuration, $build_configuration - ); - } - - /** - * Attempts to detect the project type and convert it accordingly before compiling - * Returns the compiled package path - * - * @param string $path - * @param string|null $version - * @return string - * @throws BuildException - */ - public static function tryCompile(string $path, ?string $version=null): string - { - $project_type = Resolver::detectProjectType($path); - - try - { - if($project_type->getProjectType() === ProjectType::COMPOSER) - { - $project_path = ComposerSourceBuiltin::fromLocal($project_type->getProjectPath()); - } - elseif($project_type->getProjectType() === ProjectType::NCC) - { - $project_manager = new ProjectManager($project_type->getProjectPath()); - $project_manager->getProjectConfiguration()->getAssembly()->setVersion($version); - $project_path = $project_manager->build(); - } - else - { - throw new NotSupportedException(sprintf('Failed to compile %s, project type %s is not supported', $project_type->getProjectPath(), $project_type->getProjectType())); - } - - if($version !== null) - { - $package = Package::load($project_path); - $package->getAssembly()->setVersion(Functions::convertToSemVer($version)); - $package->save($project_path); - } - - return $project_path; - } - catch(Exception $e) - { - throw new BuildException('Failed to build project', $e); - } - } - - - /** - * Compiles the execution policies of the package - * - * @param string $path - * @param ProjectConfiguration $configuration - * @return array - * @throws IOException - * @throws NotSupportedException - * @throws PathNotFoundException - */ - public static function compileExecutionPolicies(string $path, ProjectConfiguration $configuration): array - { - if(count($configuration->getExecutionPolicies()) === 0) - { - return []; - } - - Console::out('Compiling Execution Policies'); - $total_items = count($configuration->getExecutionPolicies()); - $execution_units = []; - $processed_items = 1; - - /** @var ProjectConfiguration\ExecutionPolicy $policy */ - foreach($configuration->getExecutionPolicies() as $policy) - { - Console::outVerbose(sprintf('Compiling Execution Policy %s', $policy->getName())); - - /** @noinspection DisconnectedForeachInstructionInspection */ - if($total_items > 5) - { - Console::inlineProgressBar($processed_items, $total_items); - } - - $unit_path = Functions::correctDirectorySeparator($path . $policy->getExecute()->getTarget()); - $execution_units[] = Functions::compileRunner($unit_path, $policy); - } - - if($total_items > 5 && ncc::cliMode()) - { - print(PHP_EOL); - } - - return $execution_units; - } - - /** - * Writes the finished package to disk, returns the output path - * - * @param string $path - * @param Package $package - * @param ProjectConfiguration $configuration - * @param string $build_configuration - * @return string - * @throws IOException - * @throws ConfigurationException - */ - public static function writePackage(string $path, Package $package, ProjectConfiguration $configuration, string $build_configuration=BuildConfigurationValues::DEFAULT): string - { - Console::outVerbose(sprintf('Writing package to %s', $path)); - - // Write the package to disk - $FileSystem = new Filesystem(); - $BuildConfiguration = $configuration->getBuild()->getBuildConfiguration($build_configuration); - if(!$FileSystem->exists($path . $BuildConfiguration->getOutputPath())) - { - Console::outDebug(sprintf('creating output directory %s', $path . $BuildConfiguration->getOutputPath())); - $FileSystem->mkdir($path . $BuildConfiguration->getOutputPath()); - } - - // Finally write the package to the disk - $FileSystem->mkdir($path . $BuildConfiguration->getOutputPath()); - $output_file = $path . $BuildConfiguration->getOutputPath() . DIRECTORY_SEPARATOR . $package->getAssembly()->getPackage() . '.ncc'; - if($FileSystem->exists($output_file)) - { - Console::outDebug(sprintf('removing existing package %s', $output_file)); - $FileSystem->remove($output_file); - } - $FileSystem->touch($output_file); - - try - { - $package->save($output_file); - } - catch(Exception $e) - { - throw new IOException('Cannot write to output file', $e); - } - - return $output_file; - } - - /** - * Compiles the constants in the package object - * - * @param Package $package - * @param array $refs - * @return void - */ - public static function compilePackageConstants(Package $package, array $refs): void - { - if($package->getAssembly() !== null) - { - $assembly = []; - - foreach($package->getAssembly()->toArray() as $key => $value) - { - Console::outDebug(sprintf('compiling constant Assembly.%s (%s)', $key, implode(', ', array_keys($refs)))); - $assembly[$key] = self::compileConstants($value, $refs); - } - $package->setAssembly(Assembly::fromArray($assembly)); - - unset($assembly); - } - - if(count($package->getExecutionUnits()) > 0) - { - $units = []; - foreach($package->ExecutionUnits() as $executionUnit) - { - Console::outDebug(sprintf('compiling execution unit constant %s (%s)', $executionUnit->getExecutionPolicy()->getName(), implode(', ', array_keys($refs)))); - $units[] = self::compileExecutionUnitConstants($executionUnit, $refs); - } - $package->setExecutionUnits($units); - unset($units); - } - - $compiled_constants = []; - foreach($package->getHeader()->getRuntimeConstants() as $name => $value) - { - Console::outDebug(sprintf('compiling runtime constant %s (%s)', $name, implode(', ', array_keys($refs)))); - $compiled_constants[$name] = self::compileConstants($value, $refs); - } - - $options = []; - foreach($package->getHeader()->getOptions() as $name => $value) - { - if(is_array($value)) - { - $options[$name] = []; - foreach($value as $key => $val) - { - if(!is_string($val)) - { - continue; - } - - Console::outDebug(sprintf('compiling option %s.%s (%s)', $name, $key, implode(', ', array_keys($refs)))); - $options[$name][$key] = self::compileConstants($val, $refs); - } - } - else - { - Console::outDebug(sprintf('compiling option %s (%s)', $name, implode(', ', array_keys($refs)))); - $options[$name] = self::compileConstants((string)$value, $refs); - } - } - - $package->getHeader()->setOptions($options); - $package->getHeader()->setRuntimeConstants($compiled_constants); - } - - /** - * Compiles the constants in a given execution unit - * - * @param Package\ExecutionUnit $unit - * @param array $refs - * @return Package\ExecutionUnit - */ - public static function compileExecutionUnitConstants(Package\ExecutionUnit $unit, array $refs): Package\ExecutionUnit - { - $unit->getExecutionPolicy()->setMessage(self::compileConstants($unit->getExecutionPolicy()->getMessage(), $refs)); - - if($unit->getExecutionPolicy()->getExitHandlers() !== null) - { - if($unit->getExecutionPolicy()->getExitHandlers()->getSuccess()?->getMessage() !== null) - { - $unit->getExecutionPolicy()->getExitHandlers()->getSuccess()?->setMessage( - self::compileConstants($unit->getExecutionPolicy()->getExitHandlers()->getSuccess()->getMessage(), $refs) - ); - } - - if($unit->getExecutionPolicy()->getExitHandlers()->getError()?->getMessage() !== null) - { - $unit->getExecutionPolicy()->getExitHandlers()->getError()?->setMessage( - self::compileConstants($unit->getExecutionPolicy()->getExitHandlers()->getError()->getMessage(), $refs) - ); - } - - if($unit->getExecutionPolicy()->getExitHandlers()->getWarning()?->getMessage() !== null) - { - $unit->getExecutionPolicy()->getExitHandlers()->getWarning()?->setMessage( - self::compileConstants($unit->getExecutionPolicy()->getExitHandlers()->getWarning()->getMessage(), $refs) - ); - } - - } - - if($unit->getExecutionPolicy()->getExecute() !== null) - { - $unit->getExecutionPolicy()->getExecute()->setTarget(self::compileConstants($unit->getExecutionPolicy()->getExecute()->getTarget(), $refs)); - $unit->getExecutionPolicy()->getExecute()->setWorkingDirectory(self::compileConstants($unit->getExecutionPolicy()->getExecute()->getWorkingDirectory(), $refs)); - - if(count($unit->getExecutionPolicy()->getExecute()->getOptions()) > 0) - { - $options = []; - foreach($unit->getExecutionPolicy()->getExecute()->getOptions() as $key=> $value) - { - $options[self::compileConstants($key, $refs)] = self::compileConstants($value, $refs); - } - - $unit->getExecutionPolicy()->getExecute()->setOptions($options); - } - } - - return $unit; - } - - /** - * Compiles multiple types of constants - * - * @param string|null $value - * @param array $refs - * @return string|null - */ - public static function compileConstants(?string $value, array $refs): ?string - { - if($value === null) - { - return null; - } - - if(isset($refs[ConstantReferences::ASSEMBLY])) - { - $value = ConstantCompiler::compileAssemblyConstants($value, $refs[ConstantReferences::ASSEMBLY]); - } - - if(isset($refs[ConstantReferences::BUILD])) - { - $value = ConstantCompiler::compileBuildConstants($value); - } - - if(isset($refs[ConstantReferences::DATE_TIME])) - { - $value = ConstantCompiler::compileDateTimeConstants($value, $refs[ConstantReferences::DATE_TIME]); - } - - if(isset($refs[ConstantReferences::INSTALL])) - { - $value = ConstantCompiler::compileInstallConstants($value, $refs[ConstantReferences::INSTALL]); - } - - if(isset($refs[ConstantReferences::RUNTIME])) - { - $value = ConstantCompiler::compileRuntimeConstants($value); - } - - return $value; - } - } \ No newline at end of file diff --git a/src/ncc/Classes/PackageReader.php b/src/ncc/Classes/PackageReader.php new file mode 100644 index 0000000..9cdd533 --- /dev/null +++ b/src/ncc/Classes/PackageReader.php @@ -0,0 +1,388 @@ +package_file = fopen($file_path, 'rb'); + if($this->package_file === false) + { + throw new IOException(sprintf('Failed to open file \'%s\'', $file_path)); + } + + $magic_bytes = fread($this->package_file, 7); + $header = ''; + $diameter_hit = false; + $this->header_length = 7; + + // Check for the magic bytes "ncc_pkg" + if($magic_bytes !== 'ncc_pkg') + { + throw new IOException(sprintf('File \'%s\' is not a valid package file (invalid magic bytes)', $file_path)); + } + + // Read everything after "ncc_pkg" up until the delimiter (0x1F 0x1F) + while(!feof($this->package_file)) + { + $this->header_length++; + $header .= fread($this->package_file, 1); + + if(str_ends_with($header, "\x1F\x1F")) + { + $diameter_hit = true; + $header = substr($header, 0, -2); + break; + } + + // Stop at 100MB + if($this->header_length >= 100000000) + { + throw new IOException(sprintf('File \'%s\' is not a valid package file (header is too large)', $file_path)); + } + } + + if(!$diameter_hit) + { + throw new IOException(sprintf('File \'%s\' is not a valid package file (invalid header)', $file_path)); + } + + $this->headers = ZiProto::decode($header); + } + + /** + * Returns the package headers + * + * @return array + */ + public function getHeaders(): array + { + return $this->headers; + } + + /** + * Returns the package file version + * + * @return string + */ + public function getFileVersion(): string + { + return $this->headers[PackageStructure::FILE_VERSION]; + } + + /** + * Returns an array of flags from the package + * + * @return array + */ + public function getFlags(): array + { + return $this->headers[PackageStructure::FLAGS]; + } + + /** + * Returns a flag from the package + * + * @param string $name + * @return bool + */ + public function getFlag(string $name): bool + { + return in_array($name, $this->headers[PackageStructure::FLAGS], true); + } + + /** + * Returns the directory of the package + * + * @return array + */ + public function getDirectory(): array + { + return $this->headers[PackageStructure::DIRECTORY]; + } + + /** + * Gets a resource from the package + * + * @param string $name + * @return string + */ + public function get(string $name): string + { + if(!isset($this->headers[PackageStructure::DIRECTORY][$name])) + { + throw new RuntimeException(sprintf('File \'%s\' not found in package', $name)); + } + + $location = explode(':', $this->headers[PackageStructure::DIRECTORY][$name]); + fseek($this->package_file, ($this->header_length + (int)$location[0])); + return fread($this->package_file, (int)$location[1]); + } + + /** + * Returns the package's assembly + * + * @return Assembly + * @throws ConfigurationException + */ + public function getAssembly(): Assembly + { + if(!isset($this->headers[PackageStructure::DIRECTORY]['@assembly'])) + { + throw new ConfigurationException('Package does not contain an assembly'); + } + + return Assembly::fromArray(ZiProto::decode($this->get('@assembly'))); + } + + /** + * Returns the package's metadata + * + * @return Metadata + * @throws ConfigurationException + */ + public function getMetadata(): Metadata + { + if(!isset($this->headers[PackageStructure::DIRECTORY]['@metadata'])) + { + throw new ConfigurationException('Package does not contain metadata'); + } + + return Metadata::fromArray(ZiProto::decode($this->get('@metadata'))); + } + + /** + * Optional. Returns the package's installer + * + * @return Installer|null + */ + public function getInstaller(): ?Installer + { + if(!isset($this->headers[PackageStructure::DIRECTORY]['@installer'])) + { + return null; + } + + return Installer::fromArray(ZiProto::decode($this->get('@installer'))); + } + + /** + * Returns the package's dependencies + * + * @return array + */ + public function getDependencies(): array + { + $dependencies = []; + foreach($this->headers[PackageStructure::DIRECTORY] as $name => $location) + { + if(str_starts_with($name, '@dependencies:')) + { + $dependencies[] = str_replace('@dependencies:', '', $name); + } + } + + return $dependencies; + } + + /** + * Returns a dependency from the package + * + * @param string $name + * @return array + * @throws ConfigurationException + */ + public function getDependency(string $name): array + { + $dependency_name = sprintf('@dependencies:%s', $name); + if(!isset($this->headers[PackageStructure::DIRECTORY][$dependency_name])) + { + throw new ConfigurationException(sprintf('Dependency \'%s\' not found in package', $name)); + } + + return ZiProto::decode($this->get('@dependencies:' . $name)); + } + + /** + * Returns an array of execution units from the package + * + * @return array + */ + public function getExecutionUnits(): array + { + $execution_units = []; + foreach($this->headers[PackageStructure::DIRECTORY] as $name => $location) + { + if(str_starts_with($name, '@execution_units:')) + { + $execution_units[] = str_replace('@execution_units:', '', $name); + } + } + + return $execution_units; + } + + /** + * Returns an execution unit from the package + * + * @param string $name + * @return ExecutionUnit + * @throws ConfigurationException + */ + public function getExecutionUnit(string $name): ExecutionUnit + { + $execution_unit_name = sprintf('@execution_units:%s', $name); + if(!isset($this->headers[PackageStructure::DIRECTORY][$execution_unit_name])) + { + throw new ConfigurationException(sprintf('Execution unit \'%s\' not found in package', $name)); + } + + return ExecutionUnit::fromArray(ZiProto::decode($this->get($execution_unit_name))); + } + + /** + * Returns the package's components + * + * @return array + */ + public function getComponents(): array + { + $components = []; + foreach($this->headers[PackageStructure::DIRECTORY] as $name => $location) + { + if(str_starts_with($name, '@components:')) + { + $components[] = str_replace('@components:', '', $name); + } + } + + return $components; + } + + /** + * Returns a component from the package + * + * @param string $name + * @return Component + * @throws ConfigurationException + */ + public function getComponent(string $name): Component + { + $component_name = sprintf('@components:%s', $name); + if(!isset($this->headers[PackageStructure::DIRECTORY][$component_name])) + { + throw new ConfigurationException(sprintf('Component \'%s\' not found in package', $name)); + } + + return Component::fromArray(ZiProto::decode($this->get('@components:' . $name))); + } + + /** + * Returns an array of resources from the package + * + * @return array + */ + public function getResources(): array + { + $resources = []; + foreach($this->headers[PackageStructure::DIRECTORY] as $name => $location) + { + if(str_starts_with($name, '@resources:')) + { + $resources[] = str_replace('@resources:', '', $name); + } + } + + return $resources; + } + + /** + * Returns a resource from the package + * + * @param string $name + * @return string + * @throws ConfigurationException + */ + public function getResource(string $name): string + { + $resource_name = sprintf('@resources:%s', $name); + if(!isset($this->headers[PackageStructure::DIRECTORY][$resource_name])) + { + throw new ConfigurationException(sprintf('Resource \'%s\' not found in package', $name)); + } + + return $this->get('@resources:' . $name); + } + + /** + * PackageReader destructor. + */ + public function __destruct() + { + if(is_resource($this->package_file)) + { + fclose($this->package_file); + } + } + } \ No newline at end of file diff --git a/src/ncc/Classes/PackageWriter.php b/src/ncc/Classes/PackageWriter.php new file mode 100644 index 0000000..93ece92 --- /dev/null +++ b/src/ncc/Classes/PackageWriter.php @@ -0,0 +1,331 @@ +temporary_path = $file_path . '.tmp'; + $this->temp_file = @fopen($this->temporary_path, 'wb'); // Create a temporary data file + $this->package_file = @fopen($file_path, 'wb'); + $this->headers = [ + PackageStructure::FILE_VERSION => PackageStructureVersions::_2_0, + PackageStructure::FLAGS => [], + PackageStructure::DIRECTORY => [] + ]; + + if($this->temp_file === false || $this->package_file === false) + { + throw new IOException(sprintf('Failed to open file \'%s\'', $file_path)); + } + } + + /** + * Returns the package file version + * + * @return string + */ + public function getFileVersion(): string + { + return (string)$this->headers[PackageStructure::FILE_VERSION]; + } + + /** + * Sets the package file version + * + * @param string $version + * @return void + */ + public function setFileVersion(string $version): void + { + $this->headers[PackageStructure::FILE_VERSION] = $version; + } + + /** + * Returns the package flags + * + * @return array + */ + public function getFlags(): array + { + return (array)$this->headers[PackageStructure::FLAGS]; + } + + /** + * Sets the package flags + * + * @param array $flags + * @return void + */ + public function setFlags(array $flags): void + { + $this->headers[PackageStructure::FLAGS] = $flags; + } + + /** + * Adds a flag to the package + * + * @param string $flag + * @return void + */ + public function addFlag(string $flag): void + { + if(!in_array($flag, $this->headers[PackageStructure::FLAGS], true)) + { + $this->headers[PackageStructure::FLAGS][] = $flag; + } + } + + /** + * Removes a flag from the package + * + * @param string $flag + * @return void + */ + public function removeFlag(string $flag): void + { + $this->headers[PackageStructure::FLAGS] = array_diff($this->headers[PackageStructure::FLAGS], [$flag]); + } + + /** + * Adds a file to the package by writing to the temporary data file + * + * @param string $name + * @param string $data + * @return void + * @throws IOException + */ + public function add(string $name, string $data): void + { + if(isset($this->headers[PackageStructure::DIRECTORY][$name])) + { + throw new IOException(sprintf('Resource \'%s\' already exists in package', $name)); + } + + $this->headers[PackageStructure::DIRECTORY][$name] = sprintf("%d:%d", ftell($this->temp_file), strlen($data)); + fwrite($this->temp_file, $data); + } + + /** + * Sets the assembly of the package + * + * @param Assembly $assembly + * @return void + * @throws IOException + */ + public function setAssembly(Assembly $assembly): void + { + $this->add('@assembly', ZiProto::encode($assembly->toArray())); + } + + /** + * Adds the metadata to the package + * + * @param Metadata $metadata + * @return void + * @throws IOException + */ + public function setMetadata(Metadata $metadata): void + { + $this->add('@metadata', ZiProto::encode($metadata->toArray())); + } + + /** + * Sets the installer information of the package + * + * @param Installer $installer + * @return void + * @throws IOException + */ + public function setInstaller(Installer $installer): void + { + $this->add('@installer', ZiProto::encode($installer->toArray())); + } + + /** + * Adds a dependency configuration to the package + * + * @param Dependency $dependency + * @return void + * @throws IOException + */ + public function addDependencyConfiguration(Dependency $dependency): void + { + $this->add(sprintf('@dependencies:%s', $dependency->getName()), ZiProto::encode($dependency->toArray())); + } + + /** + * Adds an execution unit to the package + * + * @param ExecutionUnit $unit + * @return void + * @throws IOException + */ + public function addExecutionUnit(ExecutionUnit $unit): void + { + $this->add(sprintf('@execution_units:%s', $unit->getExecutionPolicy()->getName()), ZiProto::encode($unit->toArray())); + } + + /** + * Adds a component to the package + * + * @param Component $component + * @return void + * @throws IOException + */ + public function addComponent(Component $component): void + { + $this->add(sprintf('@components:%s', $component->getName()), ZiProto::encode($component->toArray())); + } + + /** + * Adds a resource to the package + * + * @param Resource $resource + * @return void + * @throws IOException + */ + public function addResource(Resource $resource): void + { + $this->add(sprintf('@resources:%s', $resource->getName()), $resource->getData()); + } + + /** + * Finalizes the package by writing the magic bytes, header length, delimiter, headers, and data to the file + * + * @return void + * @throws IOException + */ + public function close(): void + { + if(!is_resource($this->package_file) || !is_resource($this->temp_file)) + { + throw new IOException('Package is already closed'); + } + + // Close the temporary data file + fclose($this->temp_file); + + // Write the magic bytes "ncc_pkg" to the package and the header + fwrite($this->package_file, 'ncc_pkg'); + fwrite($this->package_file, ZiProto::encode($this->headers)); + fwrite($this->package_file, chr(0x1F) . chr(0x1F)); + + // Copy the temporary data file to the package + $temp_file = fopen($this->temporary_path, 'rb'); + stream_copy_to_stream($temp_file, $this->package_file); + + // Close the file handles + fclose($this->package_file); + fclose($temp_file); + + unlink($this->temporary_path); + + $this->package_file = null; + $this->temp_file = null; + } + + /** + * Closes the package when the object is destroyed + */ + public function __destruct() + { + try + { + $this->close(); + } + catch(IOException $e) + { + // Ignore + } + } + } \ No newline at end of file diff --git a/src/ncc/Classes/PhpExtension/NccCompiler.php b/src/ncc/Classes/PhpExtension/NccCompiler.php new file mode 100644 index 0000000..185e5c4 --- /dev/null +++ b/src/ncc/Classes/PhpExtension/NccCompiler.php @@ -0,0 +1,65 @@ +create(ParserFactory::PREFER_PHP7); + + try + { + $encoded = json_encode($parser->parse(IO::fread($file_path)), JSON_THROW_ON_ERROR); + return new Component(Functions::removeBasename($file_path), ZiProto::encode(json_decode($encoded, true, 512, JSON_THROW_ON_ERROR)), ComponentDataType::AST); + } + catch(Exception $e) + { + Console::outWarning(sprintf('Failed to compile file "%s" with error "%s"', $file_path, $e->getMessage())); + } + + return new Component( + Functions::removeBasename($file_path), + Base64::encode(IO::fread($file_path)), ComponentDataType::BASE64_ENCODED + ); + } + + } \ No newline at end of file diff --git a/src/ncc/Classes/PhpExtension/PhpCompiler.php b/src/ncc/Classes/PhpExtension/PhpCompiler.php deleted file mode 100644 index 482753d..0000000 --- a/src/ncc/Classes/PhpExtension/PhpCompiler.php +++ /dev/null @@ -1,488 +0,0 @@ -project_configuration = $project; - $this->path = $path; - } - - /** - * Prepares the PHP package by generating the Autoloader and detecting all components & resources - * This function must be called before calling the build function, otherwise the operation will fail - * - * @param string $build_configuration - * @return void - * @throws ConfigurationException - * @throws OperationException - * @throws PackageException - */ - public function prepare(string $build_configuration=BuildConfigurationValues::DEFAULT): void - { - try - { - /** @noinspection PhpRedundantOptionalArgumentInspection */ - $this->project_configuration->validate(True); - } - catch (Exception $e) - { - throw new PackageException($e->getMessage(), $e); - } - - // Select the build configuration - $selected_build_configuration = $this->project_configuration->getBuild()->getBuildConfiguration($build_configuration); - - // Create the package object - $this->package = new Package(); - $this->package->setAssembly($this->project_configuration->getAssembly()); - $this->package->setDependencies($this->project_configuration->getBuild()->getDependencies()); - $this->package->setMainExecutionPolicy($this->project_configuration->getBuild()->getMain()); - - // Add the option to create a symbolic link to the package - if(isset($this->project_configuration->getProject()->getOptions()['create_symlink']) && $this->project_configuration->getProject()->getOptions()['create_symlink'] === True) - { - $this->package->getHeader()->setOption('create_symlink', true); - } - - // Add both the defined constants from the build configuration and the global constants. - // Global constants are overridden - $this->package->getHeader()->setRuntimeConstants(array_merge( - $selected_build_configuration->getDefineConstants(), - ($this->project_configuration->getBuild()->getDefineConstants()), - ($this->package->getHeader()->getRuntimeConstants() ?? []) - )); - - $this->package->getHeader()->setCompilerExtension($this->project_configuration->getProject()->getCompiler()); - $this->package->getHeader()->setCompilerVersion(NCC_VERSION_NUMBER); - $this->package->getHeader()->setOptions($this->project_configuration->getProject()->getOptions()); - - if($this->project_configuration->getProject()->getUpdateSource() !== null) - { - $this->package->getHeader()->setUpdateSource($this->project_configuration->getProject()->getUpdateSource()); - } - - Console::outDebug('scanning project files'); - Console::outDebug('theseer\DirectoryScanner - Copyright (c) 2009-2014 Arne Blankerts All rights reserved.'); - - // First scan the project files and create a file struct. - $directory_scanner = new DirectoryScanner(); - - try - { - $directory_scanner->unsetFlag(FilesystemIterator::FOLLOW_SYMLINKS); - } - catch (Exception $e) - { - throw new PackageException('Cannot unset flag \'FOLLOW_SYMLINKS\' in DirectoryScanner, ' . $e->getMessage(), $e); - } - - // Include file components that can be compiled - $directory_scanner->setIncludes(ComponentFileExtensions::PHP); - - if(count($selected_build_configuration->getExcludeFiles()) > 0) - { - $directory_scanner->setExcludes($selected_build_configuration->getExcludeFiles()); - } - - $source_path = $this->path . $this->project_configuration->getBuild()->getSourcePath(); - - // TODO: Re-implement the scanning process outside the compiler, as this is will be redundant - // Scan for components first. - if(file_exists($source_path)) - { - Console::outVerbose('Scanning for components... '); - /** @var SplFileInfo $item */ - /** @noinspection PhpRedundantOptionalArgumentInspection */ - foreach($directory_scanner($source_path, True) as $item) - { - // Ignore directories, they're not important. :-) - if(is_dir($item->getPathName())) - { - continue; - } - - $component = new Package\Component(); - $component->setName(Functions::removeBasename($item->getPathname(), $this->path)); - $this->package->addComponent($component); - - Console::outVerbose(sprintf('Found component %s', $component->getName())); - } - - if(count($this->package->getComponents()) > 0) - { - Console::outVerbose(count($this->package->getComponents()) . ' component(s) found'); - } - else - { - Console::outVerbose('No components found'); - } - - // Clear previously excludes and includes - $directory_scanner->setExcludes(); - $directory_scanner->setIncludes(); - - // Ignore component files - if(count($selected_build_configuration->getExcludeFiles()) > 0) - { - $directory_scanner->setExcludes(array_merge($selected_build_configuration->getExcludeFiles(), ComponentFileExtensions::PHP)); - } - else - { - $directory_scanner->setExcludes(ComponentFileExtensions::PHP); - } - - Console::outVerbose('Scanning for resources... '); - /** @var SplFileInfo $item */ - foreach($directory_scanner($source_path) as $item) - { - // Ignore directories, they're not important. :-) - if(is_dir($item->getPathName())) - { - continue; - } - - $resource = new Package\Resource(); - $resource->setName(Functions::removeBasename($item->getPathname(), $this->path)); - $this->package->addResource($resource); - - Console::outVerbose(sprintf('found resource %s', $resource->getName())); - } - - if(count($this->package->getResources()) > 0) - { - Console::outVerbose(count($this->package->getResources()) . ' resources(s) found'); - } - else - { - Console::outVerbose('No resources found'); - } - } - else - { - Console::outWarning('Source path does not exist, skipping resource and component scanning'); - } - - $selected_dependencies = []; - - if(count($this->project_configuration->getBuild()->getDependencies()) > 0) - { - $selected_dependencies = array_merge($selected_dependencies, $this->project_configuration->getBuild()->getDependencies()); - } - - if(count($selected_build_configuration->getDependencies()) > 0) - { - $selected_dependencies = array_merge($selected_dependencies, $selected_build_configuration->getDependencies()); - } - - // Process the dependencies - if(count($selected_dependencies) > 0) - { - $package_lock_manager = new PackageLockManager(); - $filesystem = new Filesystem(); - - $lib_path = $selected_build_configuration->getOutputPath() . DIRECTORY_SEPARATOR . 'libs'; - if($filesystem->exists($lib_path)) - { - $filesystem->remove($lib_path); - } - - Console::outVerbose('Scanning for dependencies... '); - /** @var Dependency $dependency */ - foreach($selected_dependencies as $dependency) - { - Console::outVerbose(sprintf('processing dependency %s', $dependency->getName())); - switch($dependency->getSourceType()) - { - case DependencySourceType::STATIC: - - try - { - $out_path = $lib_path . DIRECTORY_SEPARATOR . sprintf('%s=%s.lib', $dependency->getName(), $dependency->getVersion()); - - $package = $package_lock_manager->getPackageLock()?->getPackage($dependency->getName()); - if($package === null) - { - throw new IOException('Cannot find package lock for dependency ' . $dependency->getName()); - } - - $version = $package->getVersion($dependency->getVersion()); - if($version === null) - { - throw new OperationException('Cannot find version ' . $dependency->getVersion() . ' for dependency ' . $dependency->getName()); - } - - Console::outDebug(sprintf('copying shadow package %s=%s to %s', $dependency->getName(), $dependency->getVersion(), $out_path)); - - if(!$filesystem->exists($lib_path)) - { - $filesystem->mkdir($lib_path); - } - - $filesystem->copy($version->location, $out_path); - $dependency->Source = 'libs' . DIRECTORY_SEPARATOR . sprintf('%s=%s.lib', $dependency->getName(), $dependency->getVersion()); - - } - catch (IOException $e) - { - throw new PackageException('Static linking not possible, cannot find package lock for dependency ' . $dependency->getName(), $e); - } - - break; - - default: - case DependencySourceType::REMOTE: - break; - } - - $this->package->addDependency($dependency); - } - - if(count($this->package->getDependencies()) > 0) - { - Console::outVerbose(count($this->package->getDependencies()) . ' dependency(ies) found'); - } - else - { - Console::outVerbose('No dependencies found'); - } - } - - } - - /** - * Executes the compile process in the correct order and returns the finalized Package object - * - * @return Package|null - * @throws BuildException - * @throws IOException - * @throws NotSupportedException - * @throws PathNotFoundException - */ - public function build(): ?Package - { - $this->compileExecutionPolicies(); - $this->compileComponents(); - $this->compileResources(); - - PackageCompiler::compilePackageConstants($this->package, [ - ConstantReferences::ASSEMBLY => $this->project_configuration->getAssembly(), - ConstantReferences::BUILD => null, - ConstantReferences::DATE_TIME => time() - ]); - - return $this->getPackage(); - } - - /** - * Compiles the resources of the package - * - * @return void - * @throws BuildException - * @throws IOException - * @throws PathNotFoundException - */ - public function compileResources(): void - { - if($this->package === null) - { - throw new BuildException('The prepare() method must be called before building the package'); - } - - if(count($this->package->getResources()) === 0) - { - return; - } - - // Process the resources - $total_items = count($this->package->getResources()); - $processed_items = 1; - $resources = []; - - if($total_items > 5) - { - Console::out('Processing resources'); - } - - foreach($this->package->getResources() as $resource) - { - /** @noinspection DisconnectedForeachInstructionInspection */ - if($total_items > 5) - { - Console::inlineProgressBar($processed_items, $total_items); - } - - // Get the data and - $resource->setData(Base64::encode(IO::fread(Functions::correctDirectorySeparator($this->path . $resource->getName())))); - $resource->setName(str_replace($this->project_configuration->getBuild()->getSourcePath(), (string)null, $resource->getName())); - $resource->updateChecksum(); - $resources[] = $resource; - - Console::outDebug(sprintf('processed resource %s', $resource->getName())); - } - - // Update the resources - $this->package->setResources($resources); - } - - /** - * Compiles the components of the package - * - * @return void - * @throws BuildException - * @throws IOException - * @throws PathNotFoundException - */ - public function compileComponents(): void - { - if($this->package === null) - { - throw new BuildException('The prepare() method must be called before building the package'); - } - - if(count($this->package->getComponents()) === 0) - { - return; - } - - $total_items = count($this->package->getComponents()); - $processed_items = 1; - $components = []; - - if($total_items > 5) - { - Console::out('Compiling components'); - } - - // Process the components and attempt to create an AST representation of the source - foreach($this->package->getComponents() as $component) - { - if($total_items > 5) - { - Console::inlineProgressBar($processed_items, $total_items); - } - - $content = IO::fread(Functions::correctDirectorySeparator($this->path . $component->getName())); - $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7); - - try - { - $stmts = $parser->parse($content); - $encoded = json_encode($stmts, JSON_THROW_ON_ERROR); - unset($stmts); - $component->setDataType(ComponentDataType::AST); - $component->setData(json_decode($encoded, true, 512, JSON_THROW_ON_ERROR)); - } - catch(Exception $e) - { - $component->setDataType(ComponentDataType::BASE64_ENCODED); - $component->setData(Base64::encode($content)); - unset($e); - } - - unset($parser); - - $component->setName(str_replace($this->project_configuration->getBuild()->getSourcePath(), (string)null, $component->getName())); - $component->updateChecksum(); - $components[] = $component; - ++$processed_items; - - Console::outDebug(sprintf('processed component %s (%s)', $component->getName(), $component->getDataType())); - } - - // Update the components - $this->package->setComponents($components); - } - - /** - * @return void - * @throws IOException - * @throws NotSupportedException - * @throws PathNotFoundException - */ - public function compileExecutionPolicies(): void - { - $this->package->setExecutionUnits(PackageCompiler::compileExecutionPolicies($this->path, $this->project_configuration)); - } - - /** - * @inheritDoc - */ - public function getPackage(): ?Package - { - return $this->package; - } - - } \ No newline at end of file diff --git a/src/ncc/Classes/PhpExtension/PhpInstaller.php b/src/ncc/Classes/PhpExtension/PhpInstaller.php index 1f6b115..22017b7 100644 --- a/src/ncc/Classes/PhpExtension/PhpInstaller.php +++ b/src/ncc/Classes/PhpExtension/PhpInstaller.php @@ -84,7 +84,7 @@ return null; } - if(!$component->validate_checksum()) + if(!$component->validateChecksum()) { throw new IntegrityException(sprintf('Checksum validation failed for component: %s', $component->getName())); } diff --git a/src/ncc/Enums/BuildOutputType.php b/src/ncc/Enums/BuildOutputType.php new file mode 100644 index 0000000..46f5e1e --- /dev/null +++ b/src/ncc/Enums/BuildOutputType.php @@ -0,0 +1,28 @@ +getAssembly()->getPackage(); } - $extension = $package->getHeader()->getCompilerExtension()->getExtension(); + $extension = $package->getMetadata()->getCompilerExtension()->getExtension(); $installation_paths = new InstallationPaths($this->packages_path . DIRECTORY_SEPARATOR . $package->getAssembly()->getPackage() . '=' . $package->getAssembly()->getVersion()); $installer = match ($extension) @@ -191,7 +191,7 @@ Console::outDebug(sprintf('assembly.%s: %s', $prop, ($value ?? 'n/a'))); } - foreach($package->getHeader()->getCompilerExtension()->toArray() as $prop => $value) + foreach($package->getMetadata()->getCompilerExtension()->toArray() as $prop => $value) { Console::outDebug(sprintf('header.compiler.%s: %s', $prop, ($value ?? 'n/a'))); } @@ -361,7 +361,7 @@ } // After execution units are installed, create a symlink if needed - if(!is_null($package->getHeader()->getOption('create_symlink')) && $package->getHeader()->getOption('create_symlink')) + if(!is_null($package->getMetadata()->getOption('create_symlink')) && $package->getMetadata()->getOption('create_symlink')) { if($package->getMainExecutionPolicy() === null) { @@ -415,18 +415,18 @@ Console::outDebug('no post-installation units to execute'); } - if($package->getHeader()->getUpdateSource()?->getRepository() !== null) + if($package->getMetadata()->getUpdateSource()?->getRepository() !== null) { $sources_manager = new RemoteSourcesManager(); - if($sources_manager->getRemoteSource($package->getHeader()->getUpdateSource()->getRepository()->getName()) === null) + if($sources_manager->getRemoteSource($package->getMetadata()->getUpdateSource()->getRepository()->getName()) === null) { - Console::outVerbose('Adding remote source ' . $package->getHeader()->getUpdateSource()->getRepository()->getName()); + Console::outVerbose('Adding remote source ' . $package->getMetadata()->getUpdateSource()->getRepository()->getName()); $defined_remote_source = new DefinedRemoteSource(); - $defined_remote_source->setName($package->getHeader()->getUpdateSource()?->getRepository()?->getName()); - $defined_remote_source->setHost($package->getHeader()->getUpdateSource()?->getRepository()?->getHost()); - $defined_remote_source->setType($package->getHeader()->getUpdateSource()?->getRepository()?->getType()); - $defined_remote_source->setSsl($package->getHeader()->getUpdateSource()?->getRepository()?->isSsl()); + $defined_remote_source->setName($package->getMetadata()->getUpdateSource()?->getRepository()?->getName()); + $defined_remote_source->setHost($package->getMetadata()->getUpdateSource()?->getRepository()?->getHost()); + $defined_remote_source->setType($package->getMetadata()->getUpdateSource()?->getRepository()?->getType()); + $defined_remote_source->setSsl($package->getMetadata()->getUpdateSource()?->getRepository()?->isSsl()); $sources_manager->addRemoteSource($defined_remote_source); } @@ -995,8 +995,8 @@ $data_files = [ $paths->getDataPath() . DIRECTORY_SEPARATOR . 'assembly' => ZiProto::encode($package->getAssembly()->toArray(true)), - $paths->getDataPath() . DIRECTORY_SEPARATOR . 'ext' => ZiProto::encode($package->getHeader()->getCompilerExtension()->toArray()), - $paths->getDataPath() . DIRECTORY_SEPARATOR . 'const' => ZiProto::encode($package->getHeader()->getRuntimeConstants()), + $paths->getDataPath() . DIRECTORY_SEPARATOR . 'ext' => ZiProto::encode($package->getMetadata()->getCompilerExtension()->toArray()), + $paths->getDataPath() . DIRECTORY_SEPARATOR . 'const' => ZiProto::encode($package->getMetadata()->getRuntimeConstants()), $paths->getDataPath() . DIRECTORY_SEPARATOR . 'dependencies' => ZiProto::encode($dependencies), ]; diff --git a/src/ncc/Managers/ProjectManager.php b/src/ncc/Managers/ProjectManager.php index de9859f..9abf604 100644 --- a/src/ncc/Managers/ProjectManager.php +++ b/src/ncc/Managers/ProjectManager.php @@ -25,23 +25,26 @@ namespace ncc\Managers; - use JetBrains\PhpStorm\NoReturn; + use ncc\Classes\PhpExtension\NccCompiler; use ncc\Classes\PhpExtension\PhpCliTemplate; use ncc\Classes\PhpExtension\PhpLibraryTemplate; + use ncc\Enums\BuildOutputType; + use ncc\Enums\CompilerExtensions; + use ncc\Enums\ComponentFileExtensions; use ncc\Enums\Options\BuildConfigurationValues; use ncc\Enums\Options\InitializeProjectOptions; - use ncc\Classes\NccExtension\PackageCompiler; use ncc\Enums\ProjectTemplates; use ncc\Exceptions\BuildException; use ncc\Exceptions\ConfigurationException; use ncc\Exceptions\IOException; use ncc\Exceptions\NotSupportedException; use ncc\Exceptions\PathNotFoundException; + use ncc\Objects\Package\ExecutionUnit; use ncc\Objects\ProjectConfiguration; use ncc\Objects\ProjectConfiguration\Compiler; use ncc\Utilities\Console; use ncc\Utilities\Functions; - use ncc\Utilities\Validate; + use ncc\Utilities\IO; class ProjectManager { @@ -148,7 +151,16 @@ */ public function build(string $build_configuration=BuildConfigurationValues::DEFAULT): string { - return PackageCompiler::compile($this, $build_configuration); + $configuration = $this->project_configuration->getBuild()->getBuildConfiguration($build_configuration); + + return match (strtolower($this->project_configuration->getProject()->getCompiler()->getExtension())) + { + CompilerExtensions::PHP => match (strtolower($configuration->getBuildType())) { + BuildOutputType::NCC_PACKAGE => (new NccCompiler($this))->build($build_configuration), + default => throw new BuildException(sprintf('php cannot produce the build type \'%s\'', $configuration->getBuildType())), + }, + default => throw new NotSupportedException(sprintf('The compiler extension \'%s\' is not supported', $this->project_configuration->getProject()->getCompiler()->getExtension())), + }; } /** @@ -178,6 +190,124 @@ } } + /** + * Returns an array of file extensions for the components that are part of this project + * + * @return array + * @throws NotSupportedException + */ + public function getComponentFileExtensions(): array + { + return match ($this->getProjectConfiguration()->getProject()->getCompiler()->getExtension()) + { + CompilerExtensions::PHP => ComponentFileExtensions::PHP, + default => throw new NotSupportedException( + sprintf('The compiler extension \'%s\' is not supported', $this->getProjectConfiguration()->getProject()->getCompiler()->getExtension()) + ), + }; + } + + /** + * Returns an array of ExecutionUnits associated with the project by selecting all required execution units + * from the project configuration and reading the contents of the files + * + * @param string $build_configuration + * @return ExecutionUnit[] + * @throws ConfigurationException + * @throws IOException + * @throws PathNotFoundException + */ + public function getExecutionUnits(string $build_configuration=BuildConfigurationValues::DEFAULT): array + { + $execution_units = []; + + foreach($this->project_configuration->getRequiredExecutionPolicies($build_configuration) as $policy) + { + $execution_policy = $this->project_configuration->getExecutionPolicy($policy); + $execution_file = $this->getProjectPath() . DIRECTORY_SEPARATOR . $execution_policy->getExecute()->getTarget(); + if(!is_file($execution_file)) + { + throw new IOException(sprintf('The execution policy %s points to a non-existent file \'%s\'', $execution_policy->getName(), $execution_file)); + } + + $execution_units[] = new ExecutionUnit($execution_policy, IO::fread($execution_file)); + } + + return $execution_units; + } + + /** + * Returns an array of file paths for the components that are part of this project + * + * @param string $build_configuration + * @return array + * @throws ConfigurationException + * @throws NotSupportedException + */ + public function getComponents(string $build_configuration=BuildConfigurationValues::DEFAULT): array + { + $configuration = $this->project_configuration->getBuild()->getBuildConfiguration($build_configuration); + + return array_map(static function ($file) { + return $file; + }, Functions::scanDirectory($this->getProjectSourcePath(), $this->getComponentFileExtensions(), $configuration->getExcludeFiles())); + } + + /** + * Returns an array of file paths for the resources that are part of this project + * + * @param string $build_configuration + * @return array + * @throws ConfigurationException + * @throws NotSupportedException + */ + public function getResources(string $build_configuration=BuildConfigurationValues::DEFAULT): array + { + $configuration = $this->project_configuration->getBuild()->getBuildConfiguration($build_configuration); + + return array_map(static function ($file) { + return $file; + }, Functions::scanDirectory($this->getProjectSourcePath(), [], array_merge( + $configuration->getExcludeFiles(), $this->getComponentFileExtensions() + ))); + } + + /** + * Returns an array of runtime constants for the project & build configuration + * + * @param string $build_configuration + * @return array + * @throws ConfigurationException + */ + public function getRuntimeConstants(string $build_configuration=BuildConfigurationValues::DEFAULT): array + { + $configuration = $this->project_configuration->getBuild()->getBuildConfiguration($build_configuration); + + /** @noinspection ArrayMergeMissUseInspection */ + return array_merge( + $configuration->getDefineConstants(), + $this->project_configuration->getBuild()->getDefineConstants() + ); + } + + /** + * Returns an array of compiler options associated with the build configuration + * + * @param string $build_configuration + * @return array + * @throws ConfigurationException + */ + public function getCompilerOptions(string $build_configuration=BuildConfigurationValues::DEFAULT): array + { + $configuration = $this->project_configuration->getBuild()->getBuildConfiguration($build_configuration); + + /** @noinspection ArrayMergeMissUseInspection */ + return array_merge( + $configuration->getOptions(), + $this->project_configuration->getBuild()->getOptions() + ); + } + /** * Initializes the project structure * diff --git a/src/ncc/Objects/Package.php b/src/ncc/Objects/Package.php index 1cddcab..5ecb0b1 100644 --- a/src/ncc/Objects/Package.php +++ b/src/ncc/Objects/Package.php @@ -34,11 +34,12 @@ use ncc\Interfaces\BytecodeObjectInterface; use ncc\Objects\Package\Component; use ncc\Objects\Package\ExecutionUnit; - use ncc\Objects\Package\Header; + use ncc\Objects\Package\Metadata; use ncc\Objects\Package\Installer; use ncc\Objects\Package\MagicBytes; use ncc\Objects\Package\Resource; use ncc\Objects\ProjectConfiguration\Assembly; + use ncc\Objects\ProjectConfiguration\Compiler; use ncc\Objects\ProjectConfiguration\Dependency; use ncc\Utilities\Functions; use ncc\Utilities\IO; @@ -56,9 +57,9 @@ /** * The true header of the package * - * @var Header + * @var Metadata */ - private $header; + private $metadata; /** * The assembly object of the package @@ -74,13 +75,6 @@ */ private $dependencies; - /** - * The Main Execution Policy object for the package if the package is an executable package. - * - * @var string|null - */ - private $main_execution_policy; - /** * The installer object that is used to install the package if the package is install-able * @@ -112,11 +106,11 @@ /** * Public Constructor */ - public function __construct() + public function __construct(Assembly $assembly, Compiler $compiler) { $this->magic_bytes = new MagicBytes(); - $this->header = new Header(); - $this->assembly = new Assembly(); + $this->metadata = new Metadata($compiler); + $this->assembly = $assembly; $this->execution_units = []; $this->components = []; $this->dependencies = []; @@ -178,19 +172,19 @@ } /** - * @return Header + * @return Metadata */ - public function getHeader(): Header + public function getMetadata(): Metadata { - return $this->header; + return $this->metadata; } /** - * @param Header $header + * @param Metadata $metadata */ - public function setHeader(Header $header): void + public function setMetadata(Metadata $metadata): void { - $this->header = $header; + $this->metadata = $metadata; } /** @@ -273,6 +267,24 @@ $this->execution_units = $execution_units; } + /** + * @param ExecutionUnit $unit + * @return void + */ + public function addExecutionUnit(ExecutionUnit $unit): void + { + foreach($this->execution_units as $exec_unit) + { + if($exec_unit->getId() === $unit->getId()) + { + $this->removeExecutionUnit($exec_unit->getId()); + break; + } + } + + $this->execution_units[] = $unit; + } + /** * @return array|Resource[] */ @@ -590,7 +602,7 @@ } return [ - ($bytecode ? Functions::cbc('header') : 'header') => $this?->header?->toArray($bytecode), + ($bytecode ? Functions::cbc('header') : 'header') => $this?->metadata?->toArray($bytecode), ($bytecode ? Functions::cbc('assembly') : 'assembly') => $this?->assembly?->toArray($bytecode), ($bytecode ? Functions::cbc('dependencies') : 'dependencies') => $_dependencies, ($bytecode ? Functions::cbc('main_execution_policy') : 'main_execution_policy') => $this?->main_execution_policy, @@ -608,10 +620,10 @@ { $object = new self(); - $object->header = Functions::array_bc($data, 'header'); - if($object->header !== null) + $object->metadata = Functions::array_bc($data, 'header'); + if($object->metadata !== null) { - $object->header = Header::fromArray($object->header); + $object->metadata = Metadata::fromArray($object->metadata); } $object->assembly = Functions::array_bc($data, 'assembly'); diff --git a/src/ncc/Objects/Package/Component.php b/src/ncc/Objects/Package/Component.php index 194da05..36e5f11 100644 --- a/src/ncc/Objects/Package/Component.php +++ b/src/ncc/Objects/Package/Component.php @@ -24,6 +24,8 @@ namespace ncc\Objects\Package; + use ncc\Enums\ComponentDataType; + use ncc\Exceptions\ConfigurationException; use ncc\Interfaces\BytecodeObjectInterface; use ncc\Utilities\Functions; @@ -61,46 +63,35 @@ /** * The raw data of the component, this is to be processed by the compiler extension * - * @var mixed + * @var string */ private $data; + /** + * @param string $name + * @param string $data + * @param string $data_type + */ + public function __construct(string $name, string $data, string $data_type=ComponentDataType::PLAIN) + { + $this->name = $name; + $this->flags = []; + $this->data_type = $data_type; + $this->data = $data; + $this->checksum = hash('sha1', $data, true); + } + /** * Validates the checksum of the component, returns false if the checksum or data is invalid or if the checksum * failed. * * @return bool */ - public function validate_checksum(): bool + public function validateChecksum(): bool { - if($this->checksum === null) - { - return true; // Return true if the checksum is empty - } - - if($this->data === null) - { - return true; // Return true if the data is null - } - return hash_equals($this->checksum, hash('sha1', $this->data, true)); } - /** - * Updates the checksum of the resource - * - * @return void - */ - public function updateChecksum(): void - { - $this->checksum = null; - - if(is_string($this->data)) - { - $this->checksum = hash('sha1', $this->data, true); - } - } - /** * @return string */ @@ -148,7 +139,8 @@ */ public function removeFlag(string $flag): void { - $this->flags = array_filter($this->flags, static function($f) use ($flag) { + $this->flags = array_filter($this->flags, static function($f) use ($flag) + { return $f !== $flag; }); } @@ -161,14 +153,6 @@ return $this->data_type; } - /** - * @param string $data_type - */ - public function setDataType(string $data_type): void - { - $this->data_type = $data_type; - } - /** * @return string */ @@ -178,27 +162,22 @@ } /** - * @param string $checksum + * @return string */ - public function setChecksum(string $checksum): void - { - $this->checksum = $checksum; - } - - /** - * @return mixed - */ - public function getData(): mixed + public function getData(): string { return $this->data; } /** * @param mixed $data + * @param string $data_type */ - public function setData(mixed $data): void + public function setData(mixed $data, string $data_type=ComponentDataType::PLAIN): void { $this->data = $data; + $this->data_type = $data_type; + $this->checksum = hash('sha1', $data, true); } /** @@ -223,16 +202,28 @@ * * @param array $data * @return Component + * @throws ConfigurationException */ public static function fromArray(array $data): Component { - $object = new self(); + $name = Functions::array_bc($data, 'name'); + $component_data = Functions::array_bc($data, 'data'); + $data_type = Functions::array_bc($data, 'data_type') ?? ComponentDataType::PLAIN; + + if($name === null) + { + throw new ConfigurationException('The component name is missing'); + } + + if($component_data === null) + { + throw new ConfigurationException('The component data is missing'); + } + + $object = new self($name, $component_data, $data_type); - $object->name = Functions::array_bc($data, 'name'); $object->flags = Functions::array_bc($data, 'flags'); - $object->data_type = Functions::array_bc($data, 'data_type'); $object->checksum = Functions::array_bc($data, 'checksum'); - $object->data = Functions::array_bc($data, 'data'); return $object; } diff --git a/src/ncc/Objects/Package/ExecutionUnit.php b/src/ncc/Objects/Package/ExecutionUnit.php index 8a40d68..2d6ceeb 100644 --- a/src/ncc/Objects/Package/ExecutionUnit.php +++ b/src/ncc/Objects/Package/ExecutionUnit.php @@ -24,8 +24,10 @@ namespace ncc\Objects\Package; + use ncc\Exceptions\ConfigurationException; use ncc\Interfaces\BytecodeObjectInterface; use ncc\Objects\ProjectConfiguration\ExecutionPolicy; + use ncc\Utilities\Base64; use ncc\Utilities\Functions; class ExecutionUnit implements BytecodeObjectInterface @@ -49,16 +51,22 @@ */ private $data; + /** + * @param ExecutionPolicy $execution_policy + * @param string $data + */ + public function __construct(ExecutionPolicy $execution_policy, string $data) + { + $this->execution_policy = $execution_policy; + $this->id = hash('sha1', $this->execution_policy->getName()); + $this->data = $data; + } + /** * @return string */ public function getId(): string { - if($this->id === null) - { - $this->id = hash('sha1', $this->execution_policy->getName()); - } - return $this->id; } @@ -70,14 +78,6 @@ return $this->execution_policy; } - /** - * @param ExecutionPolicy $execution_policy - */ - public function setExecutionPolicy(ExecutionPolicy $execution_policy): void - { - $this->execution_policy = $execution_policy; - } - /** * @return string */ @@ -86,14 +86,6 @@ return $this->data; } - /** - * @param string $data - */ - public function setData(string $data): void - { - $this->data = $data; - } - /** * @inheritDoc */ @@ -101,7 +93,7 @@ { return [ ($bytecode ? Functions::cbc('execution_policy') : 'execution_policy') => $this->execution_policy->toArray($bytecode), - ($bytecode ? Functions::cbc('data') : 'data') => $this->data, + ($bytecode ? Functions::cbc('data') : 'data') => Base64::encode($this->data), ]; } @@ -110,16 +102,19 @@ */ public static function fromArray(array $data): ExecutionUnit { - $object = new self(); + $execution_policy = Functions::array_bc($data, 'execution_policy'); + $execution_data = Functions::array_bc($data, 'data'); - $object->execution_policy = Functions::array_bc($data, 'execution_policy'); - $object->data = Functions::array_bc($data, 'data'); - - if($object->execution_policy !== null) + if($execution_policy === null) { - $object->execution_policy = ExecutionPolicy::fromArray($object->execution_policy); + throw new ConfigurationException('Missing execution policy for execution unit'); } - return $object; + if($execution_data === null) + { + throw new ConfigurationException('Missing execution data for execution unit'); + } + + return new self(ExecutionPolicy::fromArray($execution_policy), Base64::decode($execution_data)); } } \ No newline at end of file diff --git a/src/ncc/Objects/Package/Installer.php b/src/ncc/Objects/Package/Installer.php deleted file mode 100644 index 64234de..0000000 --- a/src/ncc/Objects/Package/Installer.php +++ /dev/null @@ -1,210 +0,0 @@ -pre_install; - } - - /** - * @param string[]|null $pre_install - */ - public function setPreInstall(?array $pre_install): void - { - $this->pre_install = $pre_install; - } - - /** - * @return string[]|null - */ - public function getPostInstall(): ?array - { - return $this->post_install; - } - - /** - * @param string[]|null $post_install - */ - public function setPostInstall(?array $post_install): void - { - $this->post_install = $post_install; - } - - /** - * @return string[]|null - */ - public function getPreUninstall(): ?array - { - return $this->pre_uninstall; - } - - /** - * @param string[]|null $pre_uninstall - */ - public function setPreUninstall(?array $pre_uninstall): void - { - $this->pre_uninstall = $pre_uninstall; - } - - /** - * @return string[]|null - */ - public function getPostUninstall(): ?array - { - return $this->post_uninstall; - } - - /** - * @param string[]|null $post_uninstall - */ - public function setPostUninstall(?array $post_uninstall): void - { - $this->post_uninstall = $post_uninstall; - } - - /** - * @return string[]|null - */ - public function getPreUpdate(): ?array - { - return $this->pre_update; - } - - /** - * @param string[]|null $pre_update - */ - public function setPreUpdate(?array $pre_update): void - { - $this->pre_update = $pre_update; - } - - /** - * @return string[]|null - */ - public function getPostUpdate(): ?array - { - return $this->post_update; - } - - /** - * @param string[]|null $post_update - */ - public function setPostUpdate(?array $post_update): void - { - $this->post_update = $post_update; - } - - /** - * @inheritDoc - */ - public function toArray(bool $bytecode=false): array - { - if( - $this->pre_install === null && $this->post_install === null && - $this->pre_uninstall === null && $this->post_uninstall === null && - $this->pre_update === null && $this->post_update === null - ) - { - return []; - } - - return [ - ($bytecode ? Functions::cbc('pre_install') : 'pre_install') => $this->pre_install, - ($bytecode ? Functions::cbc('post_install') : 'post_install') => $this->post_install, - ($bytecode ? Functions::cbc('pre_uninstall') : 'pre_uninstall') => $this->pre_uninstall, - ($bytecode? Functions::cbc('post_uninstall') : 'post_uninstall') => $this->post_uninstall, - ($bytecode? Functions::cbc('pre_update') : 'pre_update') => $this->pre_update, - ($bytecode? Functions::cbc('post_update') : 'post_update') => $this->post_update - ]; - } - - /** - * @inheritDoc - */ - public static function fromArray(array $data): Installer - { - $object = new self(); - - $object->pre_install = Functions::array_bc($data, 'pre_install'); - $object->post_install = Functions::array_bc($data, 'post_install'); - $object->pre_uninstall = Functions::array_bc($data, 'pre_uninstall'); - $object->post_uninstall = Functions::array_bc($data, 'post_uninstall'); - $object->pre_update = Functions::array_bc($data, 'pre_update'); - $object->post_update = Functions::array_bc($data, 'post_update'); - - return $object; - } - } \ No newline at end of file diff --git a/src/ncc/Objects/Package/Header.php b/src/ncc/Objects/Package/Metadata.php similarity index 79% rename from src/ncc/Objects/Package/Header.php rename to src/ncc/Objects/Package/Metadata.php index a164ba4..65ac6b6 100644 --- a/src/ncc/Objects/Package/Header.php +++ b/src/ncc/Objects/Package/Metadata.php @@ -24,12 +24,13 @@ namespace ncc\Objects\Package; + use ncc\Exceptions\ConfigurationException; use ncc\Interfaces\BytecodeObjectInterface; use ncc\Objects\ProjectConfiguration\Compiler; use ncc\Objects\ProjectConfiguration\UpdateSource; use ncc\Utilities\Functions; - class Header implements BytecodeObjectInterface + class Metadata implements BytecodeObjectInterface { /** * The compiler extension information that was used to build the package @@ -66,12 +67,23 @@ */ private $update_source; + /** + * @var string|null + */ + private $main_execution_policy; + + /** + * @var Installer|null + */ + private $installer; + /** * Public Constructor */ - public function __construct() + public function __construct(Compiler $compiler) { - $this->compiler_extension = new Compiler(); + $this->compiler_extension = $compiler; + $this->compiler_version = NCC_VERSION_NUMBER; $this->runtime_constants = []; $this->options = []; } @@ -184,6 +196,38 @@ $this->update_source = $update_source; } + /** + * @return string|null + */ + public function getMainExecutionPolicy(): ?string + { + return $this->main_execution_policy; + } + + /** + * @param string|null $main_execution_policy + */ + public function setMainExecutionPolicy(?string $main_execution_policy): void + { + $this->main_execution_policy = $main_execution_policy; + } + + /** + * @return Installer|null + */ + public function getInstaller(): ?Installer + { + return $this->installer; + } + + /** + * @param Installer|null $installer + */ + public function setInstaller(?Installer $installer): void + { + $this->installer = $installer; + } + /** * @inheritDoc */ @@ -201,21 +245,21 @@ /** * @inheritDoc */ - public static function fromArray(array $data): Header + public static function fromArray(array $data): Metadata { - $object = new self(); + $compiler_extension = Functions::array_bc($data, 'compiler_extension'); + if($compiler_extension === null) + { + throw new ConfigurationException('The compiler extension information is not specified in the package header'); + } + + $object = new self(Compiler::fromArray($compiler_extension)); - $object->compiler_extension = Functions::array_bc($data, 'compiler_extension'); $object->runtime_constants = Functions::array_bc($data, 'runtime_constants'); $object->compiler_version = Functions::array_bc($data, 'compiler_version'); $object->update_source = Functions::array_bc($data, 'update_source'); $object->options = Functions::array_bc($data, 'options'); - if($object->compiler_extension !== null) - { - $object->compiler_extension = Compiler::fromArray($object->compiler_extension); - } - if($object->update_source !== null) { $object->update_source = UpdateSource::fromArray($object->update_source); diff --git a/src/ncc/Objects/Package/Resource.php b/src/ncc/Objects/Package/Resource.php index 3e08a38..bc31eaf 100644 --- a/src/ncc/Objects/Package/Resource.php +++ b/src/ncc/Objects/Package/Resource.php @@ -24,7 +24,9 @@ namespace ncc\Objects\Package; + use ncc\Exceptions\ConfigurationException; use ncc\Interfaces\BytecodeObjectInterface; + use ncc\Utilities\Base64; use ncc\Utilities\Functions; class Resource implements BytecodeObjectInterface @@ -51,6 +53,13 @@ */ private $data; + public function __construct(string $name, mixed $data) + { + $this->name = $name; + $this->data = $data; + $this->checksum = hash('sha1', $this->data, true); + } + /** * Validates the checksum of the resource, returns false if the checksum or data is invalid or if the checksum * failed. @@ -59,34 +68,9 @@ */ public function validateChecksum(): bool { - if($this->checksum === null) - { - return false; - } - - if($this->data === null) - { - return false; - } - return hash_equals($this->checksum, hash('sha1', $this->data, true)); } - /** - * Updates the checksum of the resource - * - * @return void - */ - public function updateChecksum(): void - { - $this->checksum = null; - - if(is_string($this->data)) - { - $this->checksum = hash('sha1', $this->data, true); - } - } - /** * @return string */ @@ -111,14 +95,6 @@ return $this->checksum; } - /** - * @param string $checksum - */ - public function setChecksum(string $checksum): void - { - $this->checksum = $checksum; - } - /** * @return string */ @@ -132,7 +108,8 @@ */ public function setData(string $data): void { - $this->data = $data; + $this->data = Base64::encode($data); + $this->checksum = hash('sha1', $this->data, true); } /** @@ -149,14 +126,25 @@ /** * @inheritDoc + * @throws ConfigurationException */ public static function fromArray(array $data): self { - $object = new self(); + $name = Functions::array_bc($data, 'name'); + $resource_data = Functions::array_bc($data, 'data'); - $object->name = Functions::array_bc($data, 'name'); + if($name === null) + { + throw new ConfigurationException('Resource name is not defined'); + } + + if($resource_data === null) + { + throw new ConfigurationException('Resource data is not defined'); + } + + $object = new self($name, $resource_data); $object->checksum = Functions::array_bc($data, 'checksum'); - $object->data = Functions::array_bc($data, 'data'); return $object; } diff --git a/src/ncc/Objects/PackageLock.php b/src/ncc/Objects/PackageLock.php index 4886bdc..4170d44 100644 --- a/src/ncc/Objects/PackageLock.php +++ b/src/ncc/Objects/PackageLock.php @@ -90,14 +90,14 @@ $package_entry = new PackageEntry(); $package_entry->addVersion($package, $install_path, true); $package_entry->setName($package->getAssembly()->getPackage()); - $package_entry->setUpdateSource($package->getHeader()->getUpdateSource()); + $package_entry->setUpdateSource($package->getMetadata()->getUpdateSource()); $this->packages[$package->getAssembly()->getPackage()] = $package_entry; $this->update(); return; } - $this->packages[$package->getAssembly()->getPackage()]->setUpdateSource($package->getHeader()->getUpdateSource()); + $this->packages[$package->getAssembly()->getPackage()]->setUpdateSource($package->getMetadata()->getUpdateSource()); $this->packages[$package->getAssembly()->getPackage()]->addVersion($package, $install_path, true); $this->update(); } diff --git a/src/ncc/Objects/PackageLock/PackageEntry.php b/src/ncc/Objects/PackageLock/PackageEntry.php index baee6b5..63f2465 100644 --- a/src/ncc/Objects/PackageLock/PackageEntry.php +++ b/src/ncc/Objects/PackageLock/PackageEntry.php @@ -171,7 +171,7 @@ $version = new VersionEntry(); $version->setVersion($package->getAssembly()->getVersion()); - $version->setCompiler($package->getHeader()->getCompilerExtension()); + $version->setCompiler($package->getMetadata()->getCompilerExtension()); $version->setExecutionUnits($package->getExecutionUnits()); $version->main_execution_policy = $package->getMainExecutionPolicy(); $version->location = $install_path; diff --git a/src/ncc/Objects/ProjectConfiguration.php b/src/ncc/Objects/ProjectConfiguration.php index 92e995b..bced995 100644 --- a/src/ncc/Objects/ProjectConfiguration.php +++ b/src/ncc/Objects/ProjectConfiguration.php @@ -25,6 +25,7 @@ namespace ncc\Objects; use Exception; + use InvalidArgumentException; use ncc\Enums\Options\BuildConfigurationValues; use ncc\Exceptions\ConfigurationException; use ncc\Exceptions\IOException; @@ -142,9 +143,9 @@ /** * @param string $name - * @return ExecutionPolicy|null + * @return ExecutionPolicy */ - private function getExecutionPolicy(string $name): ?ExecutionPolicy + public function getExecutionPolicy(string $name): ExecutionPolicy { foreach($this->execution_policies as $executionPolicy) { @@ -154,7 +155,7 @@ } } - return null; + throw new InvalidArgumentException('Execution policy \'' . $name . '\' does not exist'); } /** @@ -203,12 +204,12 @@ * Runs a check on the project configuration and determines what policies are required * * @param string $build_configuration - * @return array + * @return string[] * @throws ConfigurationException */ public function getRequiredExecutionPolicies(string $build_configuration=BuildConfigurationValues::DEFAULT): array { - if($this->execution_policies === null || count($this->execution_policies) === 0) + if(count($this->execution_policies) === 0) { return []; } @@ -220,7 +221,6 @@ foreach($this->execution_policies as $execution_policy) { $defined_polices[] = $execution_policy->getName(); - //$execution_policy->validate(); } // Check the installer by batch @@ -249,57 +249,50 @@ } } - if(count($this->build->getPostBuild()) > 0) + foreach($this->build->getPostBuild() as $unit) { - foreach($this->build->getPostBuild() as $unit) + if(!in_array($unit, $defined_polices, true)) { - if(!in_array($unit, $defined_polices, true)) - { - throw new ConfigurationException('The property \'build.pre_build\' in the project configuration calls for an undefined execution policy \'' . $unit . '\''); - } + throw new ConfigurationException('The property \'build.pre_build\' in the project configuration calls for an undefined execution policy \'' . $unit . '\''); + } - if(!in_array($unit, $required_policies, true)) - { - $required_policies[] = $unit; - } + if(!in_array($unit, $required_policies, true)) + { + $required_policies[] = $unit; } } - if(count($this->build->getPreBuild()) > 0) + foreach($this->build->getPreBuild() as $unit) { - foreach($this->build->getPreBuild() as $unit) + if(!in_array($unit, $defined_polices, true)) { - if(!in_array($unit, $defined_polices, true)) - { - throw new ConfigurationException('The property \'build.pre_build\' in the project configuration calls for an undefined execution policy \'' . $unit . '\''); - } + throw new ConfigurationException('The property \'build.pre_build\' in the project configuration calls for an undefined execution policy \'' . $unit . '\''); + } - if(!in_array($unit, $required_policies, true)) - { - $required_policies[] = $unit; - } + if(!in_array($unit, $required_policies, true)) + { + $required_policies[] = $unit; } } - /** @noinspection DegradedSwitchInspection */ - switch($build_configuration) + if($this->build->getMain() !== null) { - case BuildConfigurationValues::ALL: - /** @var BuildConfiguration $configuration */ - foreach($this->build->getBuildConfigurations() as $configuration) - { - foreach($this->processBuildPolicies($configuration, $defined_polices) as $policy) - { - if(!in_array($policy, $required_policies, true)) - { - $required_policies[] = $policy; - } - } - } - break; + if(!in_array($this->build->getMain(), $defined_polices, true)) + { + throw new ConfigurationException('The property \'build.main\' in the project configuration calls for an undefined execution policy \'' . $this->build->getMain() . '\''); + } - default: - $configuration = $this->build->getBuildConfiguration($build_configuration); + if(!in_array($this->build->getMain(), $required_policies, true)) + { + $required_policies[] = $this->build->getMain(); + } + } + + if($build_configuration === BuildConfigurationValues::ALL) + { + /** @var BuildConfiguration $configuration */ + foreach($this->build->getBuildConfigurations() as $configuration) + { foreach($this->processBuildPolicies($configuration, $defined_polices) as $policy) { if(!in_array($policy, $required_policies, true)) @@ -307,52 +300,62 @@ $required_policies[] = $policy; } } - break; + } + } + else + { + $configuration = $this->build->getBuildConfiguration($build_configuration); + foreach($this->processBuildPolicies($configuration, $defined_polices) as $policy) + { + if(!in_array($policy, $required_policies, true)) + { + $required_policies[] = $policy; + } + } } foreach($required_policies as $policy) { $execution_policy = $this->getExecutionPolicy($policy); - if($execution_policy?->getExitHandlers()->getSuccess()?->getRun() !== null) + if($execution_policy?->getExitHandlers()?->getSuccess()?->getRun() !== null) { - if(!in_array($execution_policy?->getExitHandlers()->getSuccess()?->getRun(), $defined_polices, true)) + if(!in_array($execution_policy?->getExitHandlers()?->getSuccess()?->getRun(), $defined_polices, true)) { - throw new ConfigurationException('The execution policy \'' . $execution_policy?->getName() . '\' Success exit handler points to a undefined execution policy \'' . $execution_policy?->getExitHandlers()->getSuccess()?->getRun() . '\''); + throw new ConfigurationException('The execution policy \'' . $execution_policy?->getName() . '\' Success exit handler points to a undefined execution policy \'' . $execution_policy?->getExitHandlers()?->getSuccess()?->getRun() . '\''); } - if(!in_array($execution_policy?->getExitHandlers()->getSuccess()?->getRun(), $required_policies, true)) + if(!in_array($execution_policy?->getExitHandlers()?->getSuccess()?->getRun(), $required_policies, true)) { - $required_policies[] = $execution_policy?->getExitHandlers()->getSuccess()?->getRun(); + $required_policies[] = $execution_policy?->getExitHandlers()?->getSuccess()?->getRun(); } } - if($execution_policy?->getExitHandlers()->getWarning()?->getRun() !== null) + if($execution_policy?->getExitHandlers()?->getWarning()?->getRun() !== null) { - if(!in_array($execution_policy?->getExitHandlers()->getWarning()?->getRun(), $defined_polices, true)) + if(!in_array($execution_policy?->getExitHandlers()?->getWarning()?->getRun(), $defined_polices, true)) { - throw new ConfigurationException('The execution policy \'' . $execution_policy?->getName() . '\' Warning exit handler points to a undefined execution policy \'' . $execution_policy?->getExitHandlers()->getWarning()?->getRun() . '\''); + throw new ConfigurationException('The execution policy \'' . $execution_policy?->getName() . '\' Warning exit handler points to a undefined execution policy \'' . $execution_policy?->getExitHandlers()?->getWarning()?->getRun() . '\''); } - if(!in_array($execution_policy?->getExitHandlers()->getWarning()?->getRun(), $required_policies, true)) + if(!in_array($execution_policy?->getExitHandlers()?->getWarning()?->getRun(), $required_policies, true)) { - $required_policies[] = $execution_policy?->getExitHandlers()->getWarning()?->getRun(); + $required_policies[] = $execution_policy?->getExitHandlers()?->getWarning()?->getRun(); } } - if($execution_policy?->getExitHandlers()->getError()?->getRun() !== null) + if($execution_policy?->getExitHandlers()?->getError()?->getRun() !== null) { - if(!in_array($execution_policy?->getExitHandlers()->getError()?->getRun(), $defined_polices, true)) + if(!in_array($execution_policy?->getExitHandlers()?->getError()?->getRun(), $defined_polices, true)) { - throw new ConfigurationException('The execution policy \'' . $execution_policy?->getName() . '\' Error exit handler points to a undefined execution policy \'' . $execution_policy?->getExitHandlers()->getError()?->getRun() . '\''); + throw new ConfigurationException('The execution policy \'' . $execution_policy?->getName() . '\' Error exit handler points to a undefined execution policy \'' . $execution_policy?->getExitHandlers()?->getError()?->getRun() . '\''); } - if(!in_array($execution_policy?->getExitHandlers()->getError()?->getRun(), $required_policies, true)) + if(!in_array($execution_policy?->getExitHandlers()?->getError()?->getRun(), $required_policies, true)) { - $required_policies[] = $execution_policy?->getExitHandlers()->getError()?->getRun(); + $required_policies[] = $execution_policy?->getExitHandlers()?->getError()?->getRun(); } } - } return $required_policies; diff --git a/src/ncc/Objects/ProjectConfiguration/Build/BuildConfiguration.php b/src/ncc/Objects/ProjectConfiguration/Build/BuildConfiguration.php index f9048dc..9f59689 100644 --- a/src/ncc/Objects/ProjectConfiguration/Build/BuildConfiguration.php +++ b/src/ncc/Objects/ProjectConfiguration/Build/BuildConfiguration.php @@ -24,6 +24,7 @@ namespace ncc\Objects\ProjectConfiguration\Build; + use ncc\Enums\BuildOutputType; use ncc\Exceptions\ConfigurationException; use ncc\Interfaces\BytecodeObjectInterface; use ncc\Objects\ProjectConfiguration\Dependency; @@ -43,6 +44,11 @@ */ private $name; + /** + * @var string + */ + private $build_type; + /** * Options to pass onto the extension compiler * @@ -99,6 +105,7 @@ public function __construct(string $name, string $output_path) { $this->name = $name; + $this->build_type = BuildOutputType::NCC_PACKAGE; $this->output_path = $output_path; $this->options = []; $this->define_constants = []; @@ -177,6 +184,22 @@ $this->name = $name; } + /** + * @return string + */ + public function getBuildType(): string + { + return $this->build_type; + } + + /** + * @param string $build_type + */ + public function setBuildType(string $build_type): void + { + $this->build_type = $build_type; + } + /** * @return array */ @@ -370,6 +393,7 @@ $results = []; $results[($bytecode ? Functions::cbc('name') : 'name')] = $this->name; + $results[($bytecode ? Functions::cbc('build_type') : 'build_type')] = $this->build_type; $results[($bytecode ? Functions::cbc('output_path') : 'output_path')] = $this->output_path; if(count($this->options) > 0) @@ -412,6 +436,7 @@ public static function fromArray(array $data): BuildConfiguration { $name = Functions::array_bc($data, 'name'); + $build_type = Functions::array_bc($data, 'build_type'); $output_path = Functions::array_bc($data, 'output_path'); if($name === null) diff --git a/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy.php b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy.php index 17de121..dec7227 100644 --- a/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy.php +++ b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy.php @@ -146,17 +146,17 @@ } /** - * @return ExitHandlers + * @return ?ExitHandlers */ - public function getExitHandlers(): ExitHandlers + public function getExitHandlers(): ?ExitHandlers { return $this->exit_handlers; } /** - * @param ExitHandlers $exit_handlers + * @param ExitHandlers|null $exit_handlers */ - public function setExitHandlers(ExitHandlers $exit_handlers): void + public function setExitHandlers(?ExitHandlers $exit_handlers): void { $this->exit_handlers = $exit_handlers; } diff --git a/src/ncc/Utilities/Functions.php b/src/ncc/Utilities/Functions.php index be88b89..dbff10c 100644 --- a/src/ncc/Utilities/Functions.php +++ b/src/ncc/Utilities/Functions.php @@ -23,6 +23,7 @@ namespace ncc\Utilities; use Exception; + use FilesystemIterator; use JsonException; use ncc\Enums\AuthenticationType; use ncc\Enums\DefinedRemoteSourceType; @@ -63,6 +64,7 @@ use ncc\ThirdParty\Symfony\Filesystem\Filesystem; use ncc\ThirdParty\Symfony\Process\ExecutableFinder; use ncc\ThirdParty\Symfony\Process\Process; + use ncc\ThirdParty\theseer\DirectoryScanner\DirectoryScanner; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use RuntimeException; @@ -242,8 +244,7 @@ $banner_version = str_pad($version, 21); $banner_copyright = str_pad($copyright, 30); - $banner = str_ireplace('%A', $banner_version, $banner); - return str_ireplace('%B', $banner_copyright, $banner); + return str_ireplace(array('%A', '%B'), array($banner_version, $banner_copyright), $banner); } /** @@ -263,7 +264,7 @@ // Append the trailing slash if it's not already there // "/etc/foo" becomes "/etc/foo/" - if(substr($base_name, -1) !== DIRECTORY_SEPARATOR) + if(!str_ends_with($base_name, DIRECTORY_SEPARATOR)) { $base_name .= DIRECTORY_SEPARATOR; } @@ -280,8 +281,7 @@ */ public static function correctDirectorySeparator($path): string { - $path = str_ireplace('/', DIRECTORY_SEPARATOR, $path); - return str_ireplace('\\', DIRECTORY_SEPARATOR, $path); + return str_ireplace(array('/', '\\'), DIRECTORY_SEPARATOR, $path); } /** @@ -805,9 +805,9 @@ // If the specified version is a release, download the source code if($release_results !== null) { - $results->setReleaseName($release_results->getReleaseName() ?? null); - $results->setReleaseDescription($release_results->getReleaseDescription() ?? null); - $results->setFiles(self::mergeFilesResults($release_results->getFiles(), ($results->getFiles() ?? null))); + $results->setReleaseName($release_results->getReleaseName()); + $results->setReleaseDescription($release_results->getReleaseDescription()); + $results->setFiles(self::mergeFilesResults($release_results->getFiles(), ($results->getFiles()))); if($release_results->getVersion() !== null) { @@ -829,7 +829,7 @@ { if($results->getReleaseName() === null) { - $results->setReleaseName($git_results->getReleaseName() ?? null); + $results->setReleaseName($git_results->getReleaseName()); } elseif($git_results->getReleaseName() !== null) { @@ -841,7 +841,7 @@ if($results->getReleaseDescription() === null) { - $results->setReleaseDescription($git_results->getReleaseDescription() ?? null); + $results->setReleaseDescription($git_results->getReleaseDescription()); } elseif($git_results->getReleaseDescription() !== null) { @@ -853,7 +853,7 @@ if($results->getVersion() === null) { - $results->setVersion($git_results->getVersion() ?? null); + $results->setVersion($git_results->getVersion()); } elseif($git_results->getVersion() !== null) { @@ -864,7 +864,7 @@ } } - $results->setFiles(self::mergeFilesResults($git_results->getFiles(), ($results->getFiles() ?? null))); + $results->setFiles(self::mergeFilesResults($git_results->getFiles(), ($results->getFiles()))); } try @@ -881,7 +881,7 @@ { if($results->getReleaseName() === null) { - $results->setReleaseName($ncc_package_results->getReleaseName() ?? null); + $results->setReleaseName($ncc_package_results->getReleaseName()); } elseif($ncc_package_results->getReleaseName() !== null) { @@ -893,7 +893,7 @@ if($results->getReleaseDescription() === null) { - $results->setReleaseDescription($ncc_package_results->getReleaseDescription() ?? null); + $results->setReleaseDescription($ncc_package_results->getReleaseDescription()); } elseif($ncc_package_results->getReleaseDescription() !== null) { @@ -905,7 +905,7 @@ if($results->getVersion() === null) { - $results->setVersion($ncc_package_results->getVersion() ?? null); + $results->setVersion($ncc_package_results->getVersion()); } elseif($ncc_package_results->getVersion() !== null) { @@ -916,7 +916,7 @@ } } - $results->setFiles(self::mergeFilesResults($ncc_package_results->getFiles(), ($results->getFiles() ?? null))); + $results->setFiles(self::mergeFilesResults($ncc_package_results->getFiles(), ($results->getFiles()))); } return $results; @@ -1050,7 +1050,7 @@ return RuntimeCache::get('posix_isatty'); } - if(function_exists('posix_isatty') === false) + if(!function_exists('posix_isatty')) { return false; } @@ -1077,4 +1077,51 @@ return $input; } + /** + * Scans the given directory for files and returns the found file with the given patterns + * + * @param string $path + * @param array $include + * @param array $exclude + * @return array + */ + public static function scanDirectory(string $path, array $include=[], array $exclude=[]): array + { + $directory_scanner = new DirectoryScanner(); + + try + { + $directory_scanner->unsetFlag(FilesystemIterator::FOLLOW_SYMLINKS); + } + catch (\ncc\ThirdParty\theseer\DirectoryScanner\Exception $e) + { + throw new RuntimeException('Cannot scan directory, unable to remove the FOLLOW_SYMLINKS flag from the iterator: ' . $e->getMessage(), $e->getCode(), $e); + } + + if(count($include) > 0) + { + $directory_scanner->setIncludes($include); + } + + if(count($exclude) > 0) + { + $directory_scanner->setExcludes($exclude); + } + + $results = []; + foreach($directory_scanner($path) as $item) + { + // Ignore directories, they're not important. + if(is_dir($item->getPathName())) + { + continue; + } + + $results[] = $item->getPathName(); + Console::outVerbose(sprintf('Selected file %s', $item->getPathName())); + } + + return $results; + } + } \ No newline at end of file