ncc/src/ncc/Classes/NccExtension/PackageCompiler.php
Netkas 729c9cb217
- Added a new interface class SerializableObjectInterface to implement serializable objects, BytecodeObjectInterface
extends this interface to allow for serialization of compiled assets
 - Updated class `\ncc\Objects\ComposerJson > Author` to use method calls rather than direct property access and
   implemented `SerializableObjectInterface`
 - Updated class `\ncc\Objects\ComposerJson > Autoloader` to use method calls rather than direct property access and
   implemented `SerializableObjectInterface`
 - Updated class `\ncc\Objects\ComposerJson > Funding` to use method calls rather than direct property access and
   implemented `SerializableObjectInterface`
 - Updated class `\ncc\Objects\ComposerJson > NamespacePointer` to use method calls rather than direct property access
   and implemented `SerializableObjectInterface`
 - Updated class `\ncc\Objects\ComposerJson > PackageLink` to use method calls rather than direct property access and
   implemented `SerializableObjectInterface`
 - Updated class `\ncc\Objects\ComposerJson > Suggestion` to use method calls rather than direct property access and
   implemented `SerializableObjectInterface`
 - Updated class `\ncc\Objects\ComposerJson > Support` to use method calls rather than direct property access and
   implemented `SerializableObjectInterface`
 - Updated class `\ncc\Objects\ExecutionPointers > ExecutionPointer` to use method calls rather than direct property
   access and implemented `BytecodeObjectInterface`
 - Updated class `\ncc\Objects\NccVersionInformation > Component` to use method calls rather than direct property access
   and implemented `SerializableObjectInterface`
 - Updated class `\ncc\Objects\Package > Component` to use method calls rather than direct property access and
   implemented `BytecodeObjectInterface`
 - Updated class `\ncc\Objects\Package > ExecutionUnit` to use method calls rather than direct property access and
   implemented `BytecodeObjectInterface`
 - Updated class `\ncc\Objects\Package > Header` to use method calls rather than direct property access and implemented
   `BytecodeObjectInterface`
 - Updated class `\ncc\Objects\Package > Installer` to use method calls rather than direct property access and implemented
   `BytecodeObjectInterface`
 - Updated class `\ncc\Objects\Package > MagicBytes` to use method calls rather than direct property access and implemented
   `SerializableObjectInterface`
 - Updated class `\ncc\Objects\Package > Resource` to use method calls rather than direct property access and implemented
   `BytecodeObjectInterface`
 -  Updated class `\ncc\Objects\PackageLock > DependencyEntry` to use method calls rather than direct property access
    and implemented `BytecodeObjectInterface`
 - Updated class `\ncc\Objects\PackageLock > PackageEntry` to use method calls rather than direct property access
   and implemented `BytecodeObjectInterface`
 - Updated class `\ncc\Objects\PackageLock > VersionEntry` to use method calls rather than direct property access
   and implemented `BytecodeObjectInterface`
2023-08-28 18:27:09 -04:00

405 lines
No EOL
16 KiB
PHP

<?php
/*
* Copyright (c) Nosial 2022-2023, all rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/
namespace ncc\Classes\NccExtension;
use Exception;
use ncc\Enums\CompilerExtensions;
use ncc\Enums\ConstantReferences;
use ncc\Enums\LogLevel;
use ncc\Enums\Options\BuildConfigurationValues;
use ncc\Enums\ProjectType;
use ncc\Classes\ComposerExtension\ComposerSourceBuiltin;
use ncc\Classes\PhpExtension\PhpCompiler;
use ncc\CLI\Main;
use ncc\Exceptions\BuildException;
use ncc\Exceptions\ConfigurationException;
use ncc\Exceptions\IOException;
use ncc\Exceptions\NotSupportedException;
use ncc\Exceptions\PathNotFoundException;
use ncc\Interfaces\CompilerInterface;
use ncc\Managers\ProjectManager;
use ncc\ncc;
use ncc\Objects\Package;
use ncc\Objects\ProjectConfiguration;
use ncc\Objects\ProjectConfiguration\Assembly;
use ncc\ThirdParty\Symfony\Filesystem\Filesystem;
use ncc\Utilities\Console;
use ncc\Utilities\Functions;
use ncc\Utilities\Resolver;
class PackageCompiler
{
/**
* Compiles the project into a package
*
* @param ProjectManager $manager
* @param string $build_configuration
* @return string
* @throws BuildException
* @throws ConfigurationException
* @throws IOException
* @throws NotSupportedException
* @throws PathNotFoundException
*/
public static function compile(ProjectManager $manager, string $build_configuration=BuildConfigurationValues::DEFAULT): string
{
$configuration = $manager->getProjectConfiguration();
if(Main::getLogLevel() !== null && Resolver::checkLogLevel(LogLevel::DEBUG, Main::getLogLevel()))
{
foreach($configuration->assembly->toArray() as $prop => $value)
{
Console::outDebug(sprintf('assembly.%s: %s', $prop, ($value ?? 'n/a')));
}
foreach($configuration->project->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->project->getCompiler()->getExtension()) === CompilerExtensions::PHP)
{
/** @var CompilerInterface $Compiler */
$Compiler = new PhpCompiler($configuration, $manager->getProjectPath());
}
else
{
throw new NotSupportedException('The compiler extension \'' . $configuration->project->getCompiler()->getExtension() . '\' is not supported');
}
$build_configuration = $configuration->build->getBuildConfiguration($build_configuration)->getName();
Console::out(sprintf('Building %s=%s', $configuration->assembly->getPackage(), $configuration->assembly->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->ProjectType === ProjectType::COMPOSER)
{
$project_path = ComposerSourceBuiltin::fromLocal($project_type->ProjectPath);
}
elseif($project_type->ProjectType === ProjectType::NCC)
{
$project_manager = new ProjectManager($project_type->ProjectPath);
$project_manager->getProjectConfiguration()->assembly->setVersion($version);
$project_path = $project_manager->build();
}
else
{
throw new NotSupportedException(sprintf('Failed to compile %s, project type %s is not supported', $project_type->ProjectPath, $project_type->ProjectType));
}
if($version !== null)
{
$package = Package::load($project_path);
$package->assembly->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->execution_policies) === 0)
{
return [];
}
Console::out('Compiling Execution Policies');
$total_items = count($configuration->execution_policies);
$execution_units = [];
$processed_items = 1;
/** @var ProjectConfiguration\ExecutionPolicy $policy */
foreach($configuration->execution_policies 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->build->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->assembly->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->assembly !== null)
{
$assembly = [];
foreach($package->assembly->toArray() as $key => $value)
{
Console::outDebug(sprintf('compiling constant Assembly.%s (%s)', $key, implode(', ', array_keys($refs))));
$assembly[$key] = self::compileConstants($value, $refs);
}
$package->assembly = Assembly::fromArray($assembly);
unset($assembly);
}
if($package->execution_units !== null && count($package->execution_units) > 0)
{
$units = [];
foreach($package->execution_units as $executionUnit)
{
Console::outDebug(sprintf('compiling execution unit constant %s (%s)', $executionUnit->getExecutionPolicy()->getName(), implode(', ', array_keys($refs))));
$units[] = self::compileExecutionUnitConstants($executionUnit, $refs);
}
$package->execution_units = $units;
unset($units);
}
$compiled_constants = [];
foreach($package->header->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->header->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->header->setOptions($options);
$package->header->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;
}
}