diff --git a/.idea/scopes/NCC_Source_files.xml b/.idea/scopes/NCC_Source_files.xml
new file mode 100644
index 0000000..6e38ebb
--- /dev/null
+++ b/.idea/scopes/NCC_Source_files.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/scopes/Third_Party_Source_Files.xml b/.idea/scopes/Third_Party_Source_Files.xml
new file mode 100644
index 0000000..73f6682
--- /dev/null
+++ b/.idea/scopes/Third_Party_Source_Files.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/assets/pkg_struct_1.0.png b/assets/pkg_struct_1.0.png
new file mode 100644
index 0000000..31b102a
Binary files /dev/null and b/assets/pkg_struct_1.0.png differ
diff --git a/docs/project_configuration/execution_policy.md b/docs/project_configuration/execution_policy.md
new file mode 100644
index 0000000..f5b851b
--- /dev/null
+++ b/docs/project_configuration/execution_policy.md
@@ -0,0 +1,151 @@
+# Execution Policies
+
+**Updated on Sunday, November 20, 2022**
+
+An execution policy is a policy defined in the Project
+configuration file (`project.json`) that can be used
+to execute a script or program in any stage of the package
+
+For instance, you can have a script that is executed before
+the build process starts, or in different installation stages
+when the user is installing your package you can have a unit
+run before or after the installation/uninstallation process
+starts.#
+
+Use cases such as this allows you to properly implement
+and control your program's files & assets that are not
+handled by NCC's compiler extensions.
+
+## Table of Contents
+
+
+* [Execution Policies](#execution-policies)
+ * [Table of Contents](#table-of-contents)
+ * [JSON Example](#json-example)
+ * [ExecutionPolicy Object](#executionpolicy-object)
+ * [Object Properties](#object-properties)
+ * [JSON Example](#json-example)
+ * [ExecutionConfiguration Object](#executionconfiguration-object)
+ * [Object Properties](#object-properties)
+ * [JSON Example](#json-example)
+ * [ExitHandler Object](#exithandler-object)
+ * [Object Properties](#object-properties)
+ * [JSON Example](#json-example)
+
+
+
+## JSON Example
+
+```json
+{
+ "execution_policies": {
+ "main": {
+ "runner": "php",
+ "message": "Running main %ASSEMBLY.PACKAGE%",
+ "exec": {
+ "target": "scripts/main.php",
+ "working_directory": "%INSTALL_PATH.SRC%",
+ "silent": false
+ }
+ },
+ "hello_world": {
+ "runner": "shell",
+ "message": "Running HTOP",
+ "options": {
+ "htop": null
+ },
+ "exec": {
+ "tty": true
+ }
+ }
+ }
+}
+```
+
+------------------------------------------------------------
+
+## ExecutionPolicy Object
+
+Execution Policies for your project **must** have unique
+names, because they way you tell NCC to execute these
+policies is by referencing their name in the configuration.
+
+Invalid names/undefined policies will raise errors when
+building the project
+
+### Object Properties
+
+| Property Name | Value Type | Example Value | Description |
+|-----------------|---------------------------------|----------------------|--------------------------------------------------------------------------------------------|
+| `runner` | string | bash | The name of a supported runner instance, see runners in this document |
+| `message` | string, null | Starting foo_bar ... | *Optional* the message to display before running the execution policy |
+| `exec` | ExecutionConfiguration | N/A | The configuration object that tells how the runner should execute the process |
+| `exit_handlers` | ExitHandlersConfiguration, null | N/A | *Optional* Exit Handler Configurations that tells NCC how to handle exits from the process |
+
+### JSON Example
+
+```json
+{
+ "name": "foo_bar",
+ "runner": "bash",
+ "message": "Running foo_bar ...",
+ "exec": null,
+ "exit_handlers": null
+}
+```
+
+------------------------------------------------------------
+
+## ExecutionConfiguration Object
+
+### Object Properties
+
+| Property Name | Value Type | Example Value | Description |
+|---------------------|-------------------|---------------------------------|------------------------------------------------------------------------|
+| `target` | `string` | scripts/foo_bar.bash | The target file to execute |
+| `working_directory` | `string`, `null` | %INSTALL_PATH.SRC% | *optional* The working directory to execute the process in |
+| `options` | `array`, `null` | {"run": null, "log": "verbose"} | Commandline Parameters to pass on to the target or process |
+| `silent` | `boolean`, `null` | False | Indicates if the target should run silently, by default this is false. |
+| `tty` | `boolean`, `null` | False | Indicates if the target should run in TTY mode |
+| `timeout` | `integer`, `null` | 60 | The amount of seconds to wait before the process is killed |
+
+### JSON Example
+
+```json
+{
+ "target": "scripts/foo_bar.bash",
+ "working_directory": "%INSTALL_PATH.SRC%",
+ "options": {"run": null, "log": "verbose"},
+ "silent": false,
+ "tty": false,
+ "timeout": 10
+}
+```
+
+
+------------------------------------------------------------
+
+## ExitHandler Object
+
+An exit handler is executed once the specified exit code is
+returned or the process exits with an error or normally, if
+an exit handler is specified it will be executed.
+
+### Object Properties
+
+| Property Name | Value Type | Example Value | Description |
+|---------------|--------------------|---------------|------------------------------------------------------------------------------|
+| `message` | `string` | Hello World! | The message to display when the exit handler is triggered |
+| `end_process` | `boolean`, `null` | False | *optional* Kills the process after this exit handler is triggered |
+| `run` | `string`, `null` | `null` | *optional* A execution policy to execute once this exit handler is triggered |
+| `exit_code` | `int`, `null` | 1 | The exit code that triggers this exit handler |
+### JSON Example
+
+```json
+{
+ "message": "Hello World",
+ "end_process": false,
+ "run": null,
+ "exit_code": 1
+}
+```
\ No newline at end of file
diff --git a/src/installer/installer b/src/installer/installer
index 2121c4b..26732b9 100644
--- a/src/installer/installer
+++ b/src/installer/installer
@@ -12,12 +12,8 @@
exists($path))
{
Console::outError('Missing file \'' . $path . '\', installation failed.', true, 1);
exit(1);
@@ -202,7 +199,7 @@
// Preform the checksum validation
if(!$NCC_BYPASS_CHECKSUM)
{
- if(!file_exists($NCC_CHECKSUM))
+ if(!$NCC_FILESYSTEM->exists($NCC_CHECKSUM))
{
Console::outWarning('The file \'checksum.bin\' was not found, the contents of the program cannot be verified to be safe');
}
@@ -210,7 +207,16 @@
{
Console::out('Running checksum');
- $checksum = ZiProto::decode(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'checksum.bin'));
+ try
+ {
+ $checksum = ZiProto::decode(IO::fread(__DIR__ . DIRECTORY_SEPARATOR . 'checksum.bin'));
+ }
+ catch(Exception $e)
+ {
+ Console::outError($e->getMessage(), true, 1);
+ return;
+ }
+
$checksum_failed = false;
foreach($checksum as $path => $hash)
@@ -239,24 +245,33 @@
}
}
- // Check for required extensions
- $curl_available = true;
- foreach(Validate::requiredExtensions() as $ext => $installed)
+ // Check the installed extensions and report
+ Console::out('Checking installed extensions...');
+ $extensions = Validate::requiredExtensions();
+ foreach($extensions as $ext => $installed)
{
- if(!$installed)
+ if($installed)
{
- switch($ext)
- {
- case 'curl':
- Console::outWarning('This installer requires the \'curl\' extension to install composer');
- $curl_available = false;
- break;
-
- default:
- Console::outWarning('The extension \'' . $ext . '\' is not installed, compatibility without it is not guaranteed');
- break;
- }
+ Console::out("$ext ... " . Console::formatColor("installed", ConsoleColors::LightGreen));
}
+ else
+ {
+ Console::out("$ext ... " . Console::formatColor("missing", ConsoleColors::LightRed));
+ }
+ }
+
+ // Check for curl if the installer requires it
+ $curl_available = true;
+ if(!$extensions['curl'])
+ {
+ if(getParameter($NCC_ARGS, 'install-composer') !== null)
+ {
+ Console::outError('This installer requires the \'curl\' extension to install composer', true, 1);
+ return;
+ }
+
+ $curl_available = false;
+ Console::outWarning('The extension \'curl\' is not installed, the installer will not be able to install composer');
}
// Attempt to load version information
@@ -283,14 +298,16 @@
try
{
- Console::out($full_name . ' Version: ' . $component->getVersion());
+ Console::out(Console::formatColor($full_name, ConsoleColors::Green) . ' Version: ' . Console::formatColor($component->getVersion(), ConsoleColors::LightMagenta));
}
- catch (ComponentVersionNotFoundException $e)
+ catch (Exception $e)
{
- Console::outWarning('Cannot determine component version of ' . $full_name);
+ Console::outWarning('Cannot determine component version of ' . Console::formatColor($full_name, ConsoleColors::Green));
}
}
+ Console::out('Starting installation');
+
// Determine the installation path
$skip_prompt = false;
$install_dir_arg = getParameter($NCC_ARGS, 'install-dir');
@@ -304,7 +321,7 @@
exit(1);
}
- if(file_exists($install_dir_arg . DIRECTORY_SEPARATOR . 'ncc'))
+ if($NCC_FILESYSTEM->exists($install_dir_arg . DIRECTORY_SEPARATOR . 'ncc'))
{
Console::out('NCC Seems to already be installed, the installer will repair/upgrade your current install');
$NCC_INSTALL_PATH = $install_dir_arg;
@@ -323,9 +340,9 @@
{
$user_input = null;
$user_input = Console::getInput("Installation Path (Default: $NCC_INSTALL_PATH): ");
- if(strlen($user_input) > 0 && file_exists($user_input) && Validate::unixFilepath($user_input))
+ if(strlen($user_input) > 0 && $NCC_FILESYSTEM->exists($user_input) && Validate::unixFilepath($user_input))
{
- if(file_exists($user_input . DIRECTORY_SEPARATOR . 'ncc'))
+ if($NCC_FILESYSTEM->exists($user_input . DIRECTORY_SEPARATOR . 'ncc'))
{
$NCC_INSTALL_PATH = $user_input;
break;
@@ -346,7 +363,6 @@
}
}
-
// Determine the data path
$skip_prompt = false;
$data_dir_arg = getParameter($NCC_ARGS, 'data-dir');
@@ -360,7 +376,7 @@
exit(1);
}
- if(file_exists($data_dir_arg . DIRECTORY_SEPARATOR . 'package.lck'))
+ if($NCC_FILESYSTEM->exists($data_dir_arg . DIRECTORY_SEPARATOR . 'package.lck'))
{
$NCC_DATA_PATH = $data_dir_arg;
$skip_prompt = true;
@@ -379,9 +395,9 @@
{
$user_input = null;
$user_input = Console::getInput("Data Path (Default: $NCC_DATA_PATH): ");
- if(strlen($user_input) > 0 && file_exists($user_input) && Validate::unixFilepath($user_input))
+ if(strlen($user_input) > 0 && $NCC_FILESYSTEM->exists($user_input) && Validate::unixFilepath($user_input))
{
- if(file_exists($user_input . DIRECTORY_SEPARATOR . 'package.lck'))
+ if($NCC_FILESYSTEM->exists($user_input . DIRECTORY_SEPARATOR . 'package.lck'))
{
$NCC_DATA_PATH = $user_input;
break;
@@ -409,14 +425,17 @@
{
$update_composer = true;
}
- elseif(getParameter($NCC_ARGS, 'install-composer') !== null)
- {
- $update_composer = true;
- }
else
{
- Console::out("Note: This doesn't affect your current install of composer (if you have composer installed)");
- $update_composer = Console::getBooleanInput('Do you want to install composer for NCC? (Recommended)');
+ if(!$NCC_AUTO_MODE)
+ {
+ Console::out("Note: This doesn't affect your current install of composer (if you have composer installed)");
+ $update_composer = Console::getBooleanInput('Do you want to install composer for NCC? (Recommended)');
+ }
+ else
+ {
+ $update_composer = false;
+ }
}
}
else
@@ -424,6 +443,7 @@
$update_composer = false;
}
+
if(!$NCC_AUTO_MODE)
{
if(!Console::getBooleanInput('Do you want install NCC?'))
@@ -435,14 +455,22 @@
// Backup the configuration file
$config_backup = null;
- if(file_exists($NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'ncc.yaml'))
+ if($NCC_FILESYSTEM->exists($NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'ncc.yaml'))
{
Console::out('ncc.yaml will be updated');
- $config_backup = file_get_contents($NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'ncc.yaml');
+ try
+ {
+ $config_backup = IO::fread($NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'ncc.yaml');
+ }
+ catch(Exception $e)
+ {
+ Console::outError($e->getMessage(), true, 1);
+ return;
+ }
}
// Prepare installation
- if(file_exists($NCC_INSTALL_PATH))
+ if($NCC_FILESYSTEM->exists($NCC_INSTALL_PATH))
{
try
{
@@ -455,35 +483,20 @@
}
}
- // Create the required directories
- $required_dirs = [
- $NCC_INSTALL_PATH,
- $NCC_DATA_PATH,
- $NCC_DATA_PATH . DIRECTORY_SEPARATOR . 'packages',
- $NCC_DATA_PATH . DIRECTORY_SEPARATOR . 'cache',
- $NCC_DATA_PATH . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'repos',
- $NCC_DATA_PATH . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'downloads',
- $NCC_DATA_PATH . DIRECTORY_SEPARATOR . 'config',
- $NCC_DATA_PATH . DIRECTORY_SEPARATOR . 'data',
- $NCC_DATA_PATH . DIRECTORY_SEPARATOR . 'ext',
- ];
+ $NCC_FILESYSTEM->mkdir($NCC_INSTALL_PATH, 0755);
- $NCC_FILESYSTEM->mkdir($required_dirs, 0755);
- $NCC_FILESYSTEM->chmod([$NCC_DATA_PATH . DIRECTORY_SEPARATOR . 'config'], 0777);
- $NCC_FILESYSTEM->chmod([$NCC_DATA_PATH . DIRECTORY_SEPARATOR . 'cache'], 0777);
-
- // Verify directories exist
- foreach($required_dirs as $dir)
+ try
{
- if(!file_exists($dir))
- {
- Console::outError("Cannot create directory '$dir', please verify if you have write permissions to the directory.");
- exit(1);
- }
+ Functions::initializeFiles();
+ }
+ catch(Exception $e)
+ {
+ Console::outError('Cannot initialize NCC files, ' . $e->getMessage());
+ exit(1);
}
// Install composer
- if($curl_available && $update_composer)
+ if($update_composer)
{
Console::out('Installing composer for NCC');
@@ -501,11 +514,10 @@
fclose($fp);
Console::out('Running composer installer');
- // TODO: Unescaped shell arguments are a security issue
$Process = Process::fromShellCommandline(implode(' ', [
$NCC_PHP_EXECUTABLE,
- $NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'composer-setup.php',
- '--install-dir=' . $NCC_INSTALL_PATH,
+ escapeshellcmd($NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'composer-setup.php'),
+ '--install-dir=' . escapeshellcmd($NCC_INSTALL_PATH),
'--filename=composer.phar'
]));
$Process->setWorkingDirectory($NCC_INSTALL_PATH);
@@ -536,7 +548,15 @@
// Install NCC
Console::out('Copying files to \'' . $NCC_INSTALL_PATH . '\'');
- $build_files = explode("\n", file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'build_files'));
+ try
+ {
+ $build_files = explode("\n", IO::fread(__DIR__ . DIRECTORY_SEPARATOR . 'build_files'));
+ }
+ catch(Exception $e)
+ {
+ Console::outError($e->getMessage(), true, 1);
+ return;
+ }
$total_items = count($build_files);
$processed_items = 0;
@@ -561,7 +581,7 @@
$NCC_FILESYSTEM->copy(__DIR__ . DIRECTORY_SEPARATOR . $file, $NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . $file);
$NCC_FILESYSTEM->chmod([$NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . $file], 0755);
- if(!file_exists($NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . $file))
+ if(!$NCC_FILESYSTEM->exists($NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . $file))
{
Console::outError('Cannot create file \'' . $NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . $file . '\', installation failed.');
exit(1);
@@ -573,32 +593,19 @@
Console::inlineProgressBar($processed_items, $total_items);
}
- // Create credential store if needed
- Console::out('Processing Credential Store');
- $credential_manager = new CredentialManager();
-
- try
- {
- $credential_manager->constructStore();
- }
- catch (AccessDeniedException|\ncc\Exceptions\RuntimeException $e)
- {
- Console::outError('Cannot construct credential store, ' . $e->getMessage() . ' (Error Code: ' . $e->getCode() . ')');
- }
-
- try
- {
- $NCC_FILESYSTEM->touch([PathFinder::getPackageLock(Scopes::System)]);
- }
- catch (InvalidScopeException $e)
- {
- Console::outError('Cannot create package lock, ' . $e->getMessage());
- exit(0);
- }
-
// Generate executable shortcut
Console::out('Creating shortcut');
- $executable_shortcut = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'ncc.sh');
+
+ try
+ {
+ $executable_shortcut = IO::fread(__DIR__ . DIRECTORY_SEPARATOR . 'ncc.sh');
+ }
+ catch(Exception $e)
+ {
+ Console::outError($e->getMessage(), true, 1);
+ return;
+ }
+
$executable_shortcut = str_ireplace('%php_exec', $NCC_PHP_EXECUTABLE, $executable_shortcut);
$executable_shortcut = str_ireplace('%ncc_exec', $NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'ncc', $executable_shortcut);
@@ -611,41 +618,65 @@
foreach($bin_paths as $path)
{
// Delete old versions of the executable shortcuts.
- if(file_exists($path . DIRECTORY_SEPARATOR . 'ncc'))
+ if($NCC_FILESYSTEM->exists($path . DIRECTORY_SEPARATOR . 'ncc'))
{
$NCC_FILESYSTEM->remove($path . DIRECTORY_SEPARATOR . 'ncc');
}
- if($NCC_FILESYSTEM->exists([$path]))
+ if($NCC_FILESYSTEM->exists($path))
{
- file_put_contents($path . DIRECTORY_SEPARATOR . 'ncc', $executable_shortcut);
- $NCC_FILESYSTEM->chmod([$path . DIRECTORY_SEPARATOR . 'ncc'], 0755);
+ try
+ {
+ IO::fwrite($path . DIRECTORY_SEPARATOR . 'ncc', $executable_shortcut);
+ break;
+ }
+ catch (Exception $e)
+ {
+ Console::outException($e->getMessage(), $e, 1);
+ return;
+ }
}
}
// Register the ncc extension
Console::out('Registering extension');
- $extension_shortcut = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'extension');
+ try
+ {
+ $extension_shortcut = IO::fread(__DIR__ . DIRECTORY_SEPARATOR . 'extension');
+ }
+ catch(Exception $e)
+ {
+ Console::outError($e->getMessage(), true, 1);
+ return;
+ }
$extension_shortcut = str_ireplace('%ncc_install', $NCC_INSTALL_PATH, $extension_shortcut);
// Remove all the old extensions first.
/**
* @param string $path
- * @param Filesystem $NCC_FILESYSTEM
+ * @param Filesystem $filesystem
* @param string $extension_shortcut
* @return bool
*/
- function install_extension(string $path, Filesystem $NCC_FILESYSTEM, string $extension_shortcut): bool
+ function install_extension(string $path, Filesystem $filesystem, string $extension_shortcut): bool
{
- if (file_exists($path . DIRECTORY_SEPARATOR . 'ncc'))
+ if ($filesystem->exists($path . DIRECTORY_SEPARATOR . 'ncc'))
{
- $NCC_FILESYSTEM->remove($path . DIRECTORY_SEPARATOR . 'ncc');
+ $filesystem->remove($path . DIRECTORY_SEPARATOR . 'ncc');
}
- file_put_contents($path . DIRECTORY_SEPARATOR . 'ncc', $extension_shortcut);
- $NCC_FILESYSTEM->chmod([$path . DIRECTORY_SEPARATOR . 'ncc'], 0755);
+ try
+ {
+ IO::fwrite($path . DIRECTORY_SEPARATOR . 'ncc', $extension_shortcut);
+ }
+ catch (\ncc\Exceptions\IOException $e)
+ {
+ Console::outException($e->getMessage(), $e, 1);
+ return false;
+ }
- if (file_exists($path . DIRECTORY_SEPARATOR . 'ncc')) {
+ if ($filesystem->exists($path . DIRECTORY_SEPARATOR . 'ncc'))
+ {
return true;
}
@@ -745,7 +776,15 @@
Console::out('Updating ncc.yaml');
}
- file_put_contents($NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'ncc.yaml', Yaml::dump($config_obj));
+ try
+ {
+ IO::fwrite($NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'ncc.yaml', Yaml::dump($config_obj));
+ }
+ catch (\ncc\Exceptions\IOException $e)
+ {
+ Console::outException($e->getMessage(), $e, 1);
+ return;
+ }
Console::out('NCC version: ' . NCC_VERSION_NUMBER . ' has been successfully installed');
Console::out('For licensing information see \'' . $NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'LICENSE\' or run \'ncc help --license\'');
diff --git a/src/ncc/Abstracts/ConstantReferences.php b/src/ncc/Abstracts/ConstantReferences.php
new file mode 100644
index 0000000..43cf7ae
--- /dev/null
+++ b/src/ncc/Abstracts/ConstantReferences.php
@@ -0,0 +1,14 @@
+""|]*[%])|([a-zA-Z][:])|(\\\\))((\\\\{1})|((\\\\{1})[^\\\\]([^\/:*?<>""|]*))+)$/m';
const ConstantName = '/^([^\x00-\x7F]|[\w_\ \.\+\-]){2,64}$/';
+
+ const ExecutionPolicyName = '/^[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*$/m';
}
\ No newline at end of file
diff --git a/src/ncc/Abstracts/Runners.php b/src/ncc/Abstracts/Runners.php
new file mode 100644
index 0000000..5a0e052
--- /dev/null
+++ b/src/ncc/Abstracts/Runners.php
@@ -0,0 +1,8 @@
+Project->Compiler->Extension))
- {
- case CompilerExtensions::PHP:
- /** @var CompilerInterface $Compiler */
- $Compiler = new Compiler($ProjectConfiguration);
- break;
-
- default:
- Console::outError('The extension '. $ProjectConfiguration->Project->Compiler->Extension . ' is not supported', true, 1);
- return;
- }
-
- $build_configuration = BuildConfigurationValues::DefaultConfiguration;
-
- if(isset($args['config']))
- {
- $build_configuration = $args['config'];
- }
-
- // Auto-resolve to the default configuration if `default` is used or not specified
- if($build_configuration == BuildConfigurationValues::DefaultConfiguration)
- {
- $build_configuration = $ProjectConfiguration->Build->DefaultConfiguration;
- }
-
- try
- {
- $ProjectConfiguration->Build->getBuildConfiguration($build_configuration);
- }
- catch (BuildConfigurationNotFoundException $e)
- {
- Console::outException('The build configuration \'' . $build_configuration . '\' does not exist in the project configuration', $e, 1);
- return;
- }
-
- Console::out(
- ' ===== BUILD INFO ===== ' . PHP_EOL .
- ' Package Name: ' . $ProjectConfiguration->Assembly->Package . PHP_EOL .
- ' Version: ' . $ProjectConfiguration->Assembly->Version . PHP_EOL .
- ' Compiler Extension: ' . $ProjectConfiguration->Project->Compiler->Extension . PHP_EOL .
- ' Compiler Version: ' . $ProjectConfiguration->Project->Compiler->MaximumVersion . ' - ' . $ProjectConfiguration->Project->Compiler->MinimumVersion . PHP_EOL .
- ' Build Configuration: ' . $build_configuration . PHP_EOL
- );
-
- Console::out('Preparing package');
-
- try
- {
- $Compiler->prepare($project_path, $build_configuration);
+ $ProjectManager = new ProjectManager($project_path);
+ $ProjectManager->load();
}
catch (Exception $e)
{
- Console::outException('The package preparation process failed', $e, 1);
+ Console::outException('Failed to load Project Configuration (project.json)', $e, 1);
return;
}
- Console::out('Compiling package');
-
+ // Build the project
try
{
- $Compiler->build($project_path);
+ $build_configuration = BuildConfigurationValues::DefaultConfiguration;
+ if(isset($args['config']))
+ {
+ $build_configuration = $args['config'];
+ }
+
+ $output = $ProjectManager->build($build_configuration);
+
+ Console::out('Successfully built ' . $output);
+ exit(0);
}
catch (Exception $e)
{
- Console::outException('Build Failed', $e, 1);
+ Console::outException('Failed to build project', $e, 1);
return;
}
- exit(0);
}
/**
diff --git a/src/ncc/CLI/Functions.php b/src/ncc/CLI/Functions.php
deleted file mode 100644
index dceff6e..0000000
--- a/src/ncc/CLI/Functions.php
+++ /dev/null
@@ -1,31 +0,0 @@
- 0)
- {
- $out = str_pad($out, $padding, $pad_string, $pad_type);
- }
-
- if($eol)
- {
- $out = $out . PHP_EOL;
- }
-
- print($out);
- }
- }
\ No newline at end of file
diff --git a/src/ncc/CLI/Main.php b/src/ncc/CLI/Main.php
index c8fe242..a2a269f 100644
--- a/src/ncc/CLI/Main.php
+++ b/src/ncc/CLI/Main.php
@@ -1,8 +1,11 @@
getMessage() . ' (Code: ' . $e->getCode() . ')', $e, 1);
+ Console::outException($e->getMessage() . ' (Code: ' . $e->getCode() . ')', $e, 1);
exit(1);
}
}
}
+ /**
+ * @return mixed
+ */
+ public static function getArgs()
+ {
+ return self::$args;
+ }
+
+ /**
+ * @return string
+ */
+ public static function getLogLevel(): string
+ {
+ if(self::$log_level == null)
+ self::$log_level = LogLevel::Info;
+ return self::$log_level;
+ }
+
}
\ No newline at end of file
diff --git a/src/ncc/CLI/PackageManagerMenu.php b/src/ncc/CLI/PackageManagerMenu.php
new file mode 100644
index 0000000..319eac9
--- /dev/null
+++ b/src/ncc/CLI/PackageManagerMenu.php
@@ -0,0 +1,345 @@
+getInstalledPackages();
+ }
+ catch (Exception $e)
+ {
+ unset($e);
+ Console::out('No packages installed');
+ exit(0);
+ }
+
+ foreach($installed_packages as $package => $versions)
+ {
+ if(count($versions) == 0)
+ {
+ continue;
+ }
+
+ foreach($versions as $version)
+ {
+ try
+ {
+ $package_version = $package_manager->getPackageVersion($package, $version);
+ if($package_version == null)
+ throw new Exception();
+
+ Console::out(sprintf('%s==%s (%s)',
+ Console::formatColor($package, ConsoleColors::LightGreen),
+ Console::formatColor($version, ConsoleColors::LightMagenta),
+ $package_manager->getPackageVersion($package, $version)->Compiler->Extension
+ ));
+ }
+ catch(Exception $e)
+ {
+ unset($e);
+ Console::out(sprintf('%s==%s',
+ Console::formatColor($package, ConsoleColors::LightGreen),
+ Console::formatColor($version, ConsoleColors::LightMagenta)
+ ));
+ }
+ }
+ }
+ }
+
+ /**
+ * @param $args
+ * @return void
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ */
+ private static function installPackage($args): void
+ {
+ $path = ($args['path'] ?? $args['p']);
+ $package_manager = new PackageManager();
+
+ if(Resolver::resolveScope() !== Scopes::System)
+ throw new AccessDeniedException('Insufficient permission to install packages');
+
+ if(!file_exists($path) || !is_file($path) || !is_readable($path))
+ throw new FileNotFoundException('The specified file \'' . $path .' \' does not exist or is not readable.');
+
+ $user_confirmation = false;
+ if(isset($args['y']) || isset($args['Y']))
+ {
+ $user_confirmation = (bool)($args['y'] ?? $args['Y']);
+ }
+
+ try
+ {
+ $package = Package::load($path);
+ }
+ catch(Exception $e)
+ {
+ Console::outException('Error while loading package', $e, 1);
+ return;
+ }
+
+ Console::out('Package installation details' . PHP_EOL);
+ if(!is_null($package->Assembly->UUID))
+ Console::out(' UUID: ' . Console::formatColor($package->Assembly->UUID, ConsoleColors::LightGreen));
+ if(!is_null($package->Assembly->Package))
+ Console::out(' Package: ' . Console::formatColor($package->Assembly->Package, ConsoleColors::LightGreen));
+ if(!is_null($package->Assembly->Name))
+ Console::out(' Name: ' . Console::formatColor($package->Assembly->Name, ConsoleColors::LightGreen));
+ if(!is_null($package->Assembly->Version))
+ Console::out(' Version: ' . Console::formatColor($package->Assembly->Version, ConsoleColors::LightGreen));
+ if(!is_null($package->Assembly->Description))
+ Console::out(' Description: ' . Console::formatColor($package->Assembly->Description, ConsoleColors::LightGreen));
+ if(!is_null($package->Assembly->Product))
+ Console::out(' Product: ' . Console::formatColor($package->Assembly->Product, ConsoleColors::LightGreen));
+ if(!is_null($package->Assembly->Company))
+ Console::out(' Company: ' . Console::formatColor($package->Assembly->Company, ConsoleColors::LightGreen));
+ if(!is_null($package->Assembly->Copyright))
+ Console::out(' Copyright: ' . Console::formatColor($package->Assembly->Copyright, ConsoleColors::LightGreen));
+ if(!is_null($package->Assembly->Trademark))
+ Console::out(' Trademark: ' . Console::formatColor($package->Assembly->Trademark, ConsoleColors::LightGreen));
+ Console::out((string)null);
+
+ if(count($package->Dependencies) > 0)
+ {
+ $dependencies = [];
+ foreach($package->Dependencies as $dependency)
+ {
+ $dependencies[] = sprintf('%s v%s',
+ Console::formatColor($dependency->Name, ConsoleColors::Green),
+ Console::formatColor($dependency->Version, ConsoleColors::LightMagenta)
+ );
+ }
+
+ Console::out('The following dependencies will be installed:');
+ Console::out(sprintf(' %s', implode(', ', $dependencies)) . PHP_EOL);
+ }
+
+ Console::out(sprintf('Extension: %s',
+ Console::formatColor($package->Header->CompilerExtension->Extension, ConsoleColors::Green)
+ ));
+
+ if($package->Header->CompilerExtension->MaximumVersion !== null)
+ Console::out(sprintf('Maximum Version: %s',
+ Console::formatColor($package->Header->CompilerExtension->MaximumVersion, ConsoleColors::LightMagenta)
+ ));
+
+ if($package->Header->CompilerExtension->MinimumVersion !== null)
+ Console::out(sprintf('Minimum Version: %s',
+ Console::formatColor($package->Header->CompilerExtension->MinimumVersion, ConsoleColors::LightMagenta)
+ ));
+
+ if(!$user_confirmation)
+ $user_confirmation = Console::getBooleanInput(sprintf('Do you want to install %s', $package->Assembly->Package));
+
+ if($user_confirmation)
+ {
+ try
+ {
+ $package_manager->install($path);
+ }
+ catch(Exception $e)
+ {
+ Console::outException('Installation Failed', $e, 1);
+ }
+
+ return;
+ }
+
+ Console::outError('User cancelled installation', true, 1);
+ }
+
+ /**
+ * Uninstalls a version of a package or all versions of a package
+ *
+ * @param $args
+ * @return void
+ * @throws VersionNotFoundException
+ */
+ private static function uninstallPackage($args): void
+ {
+ $selected_package = ($args['package'] ?? $args['pkg']);
+ $selected_version = null;
+ if(isset($args['v']))
+ $selected_version = $args['v'];
+ if(isset($args['version']))
+ $selected_version = $args['version'];
+
+ $user_confirmation = null;
+ // For undefined array key warnings
+ if(isset($args['y']) || isset($args['Y']))
+ $user_confirmation = (bool)($args['y'] ?? $args['Y']);
+
+ if($selected_package == null)
+ Console::outError('Missing argument \'package\'', true, 1);
+
+ $package_manager = new PackageManager();
+
+ try
+ {
+ $package_entry = $package_manager->getPackage($selected_package);
+ }
+ catch (PackageLockException $e)
+ {
+ Console::outException('PackageLock error', $e, 1);
+ return;
+ }
+
+ $version_entry = null;
+ if($version_entry !== null && $package_entry !== null)
+ /** @noinspection PhpUnhandledExceptionInspection */
+ /** @noinspection PhpRedundantOptionalArgumentInspection */
+ $version_entry = $package_entry->getVersion($version_entry, false);
+
+ if($package_entry == null)
+ {
+ Console::outError(sprintf('Package "%s" is not installed', $selected_package), true, 1);
+ return;
+ }
+
+ if($version_entry == null & $selected_version !== null)
+ {
+ Console::outError(sprintf('Package "%s==%s" is not installed', $selected_package, $selected_version), true, 1);
+ return;
+ }
+
+ if($user_confirmation == null)
+ {
+ if($selected_version !== null)
+ {
+ if(!Console::getBooleanInput(sprintf('Do you want to uninstall %s==%s', $selected_package, $selected_version)))
+ {
+ Console::outError('User cancelled operation', true, 1);
+ return;
+ }
+ }
+ else
+ {
+ if(!Console::getBooleanInput(sprintf('Do you want to uninstall all versions of %s', $selected_package)))
+ {
+ Console::outError('User cancelled operation', true, 1);
+ return;
+ }
+ }
+ }
+
+ try
+ {
+ if($selected_version !== null)
+ {
+ $package_manager->uninstallPackageVersion($selected_package, $selected_version);
+ }
+ else
+ {
+ $package_manager->uninstallPackage($selected_package);
+ }
+ }
+ catch(Exception $e)
+ {
+ Console::outException('Uninstallation failed', $e, 1);
+ return;
+ }
+ }
+
+ /**
+ * Displays the main options section
+ *
+ * @return void
+ */
+ private static function displayOptions(): void
+ {
+ $options = [
+ new CliHelpSection(['help'], 'Displays this help menu about the value command'),
+ new CliHelpSection(['install', '--path', '-p'], 'Installs a specified NCC package file'),
+ new CliHelpSection(['list'], 'Lists all installed packages on the system'),
+ ];
+
+ $options_padding = Functions::detectParametersPadding($options) + 4;
+
+ Console::out('Usage: ncc install {command} [options]');
+ Console::out('Options:' . PHP_EOL);
+ foreach($options as $option)
+ {
+ Console::out(' ' . $option->toString($options_padding));
+ }
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Classes/NccExtension/ConstantCompiler.php b/src/ncc/Classes/NccExtension/ConstantCompiler.php
new file mode 100644
index 0000000..5ab4597
--- /dev/null
+++ b/src/ncc/Classes/NccExtension/ConstantCompiler.php
@@ -0,0 +1,129 @@
+Name, $input);
+ $input = str_replace(AssemblyConstants::AssemblyPackage, $assembly->Package, $input);
+ $input = str_replace(AssemblyConstants::AssemblyDescription, $assembly->Description, $input);
+ $input = str_replace(AssemblyConstants::AssemblyCompany, $assembly->Company, $input);
+ $input = str_replace(AssemblyConstants::AssemblyProduct, $assembly->Product, $input);
+ $input = str_replace(AssemblyConstants::AssemblyCopyright, $assembly->Copyright, $input);
+ $input = str_replace(AssemblyConstants::AssemblyTrademark, $assembly->Trademark, $input);
+ $input = str_replace(AssemblyConstants::AssemblyVersion, $assembly->Version, $input);
+ $input = str_replace(AssemblyConstants::AssemblyUid, $assembly->UUID, $input);
+
+ return $input;
+ }
+
+ /**
+ * Compiles build constants about the NCC build (Usually used during compiling time)
+ *
+ * @param string|null $input
+ * @return string|null
+ * @noinspection PhpUnnecessaryLocalVariableInspection
+ */
+ public static function compileBuildConstants(?string $input): ?string
+ {
+ if($input == null)
+ return null;
+
+ $input = str_replace(BuildConstants::CompileTimestamp, time(), $input);
+ $input = str_replace(BuildConstants::NccBuildVersion, NCC_VERSION_NUMBER, $input);
+ $input = str_replace(BuildConstants::NccBuildFlags, implode(' ', NCC_VERSION_FLAGS), $input);
+ $input = str_replace(BuildConstants::NccBuildBranch, NCC_VERSION_BRANCH, $input);
+
+ return $input;
+ }
+
+ /**
+ * Compiles installation constants (Usually used during compiling time)
+ *
+ * @param string|null $input
+ * @param InstallationPaths $installationPaths
+ * @return string|null
+ * @noinspection PhpUnnecessaryLocalVariableInspection
+ */
+ public static function compileInstallConstants(?string $input, InstallationPaths $installationPaths): ?string
+ {
+ if($input == null)
+ return null;
+
+ $input = str_replace(InstallConstants::InstallationPath, $installationPaths->getInstallationPath(), $input);
+ $input = str_replace(InstallConstants::BinPath, $installationPaths->getBinPath(), $input);
+ $input = str_replace(InstallConstants::SourcePath, $installationPaths->getSourcePath(), $input);
+ $input = str_replace(InstallConstants::DataPath, $installationPaths->getDataPath(), $input);
+
+ return $input;
+ }
+
+ /**
+ * Compiles DateTime constants from a Unix Timestamp
+ *
+ * @param string|null $input
+ * @param int $timestamp
+ * @return string|null
+ * @noinspection PhpUnnecessaryLocalVariableInspection
+ */
+ public static function compileDateTimeConstants(?string $input, int $timestamp): ?string
+ {
+ if($input == null)
+ return null;
+
+ $input = str_replace(DateTimeConstants::d, date('d', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::D, date('D', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::j, date('j', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::l, date('l', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::N, date('N', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::S, date('S', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::w, date('w', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::z, date('z', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::W, date('W', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::F, date('F', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::m, date('m', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::M, date('M', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::n, date('n', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::t, date('t', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::L, date('L', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::o, date('o', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::Y, date('Y', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::y, date('y', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::a, date('a', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::A, date('A', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::B, date('B', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::g, date('g', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::G, date('G', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::h, date('h', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::H, date('H', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::i, date('i', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::s, date('s', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::c, date('c', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::r, date('r', $timestamp), $input);
+ $input = str_replace(DateTimeConstants::u, date('u', $timestamp), $input);
+
+ return $input;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Classes/NccExtension/PackageCompiler.php b/src/ncc/Classes/NccExtension/PackageCompiler.php
new file mode 100644
index 0000000..8ac9bae
--- /dev/null
+++ b/src/ncc/Classes/NccExtension/PackageCompiler.php
@@ -0,0 +1,307 @@
+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->Compiler->toArray() as $prop => $value)
+ Console::outDebug(sprintf('compiler.%s: %s', $prop, ($value ?? 'n/a')));
+ }
+
+ // Select the correct compiler for the specified extension
+ /** @noinspection PhpSwitchCanBeReplacedWithMatchExpressionInspection */
+ switch(strtolower($configuration->Project->Compiler->Extension))
+ {
+ case CompilerExtensions::PHP:
+ /** @var CompilerInterface $Compiler */
+ $Compiler = new Compiler($configuration, $manager->getProjectPath());
+ break;
+
+ default:
+ throw new UnsupportedCompilerExtensionException('The compiler extension \'' . $configuration->Project->Compiler->Extension . '\' is not supported');
+ }
+
+ $build_configuration = $configuration->Build->getBuildConfiguration($build_configuration)->Name;
+ $Compiler->prepare($build_configuration);
+ $Compiler->build();
+
+ return PackageCompiler::writePackage(
+ $manager->getProjectPath(), $Compiler->getPackage(), $configuration, $build_configuration
+ );
+ }
+
+ /**
+ * Compiles the execution policies of the package
+ *
+ * @param string $path
+ * @param ProjectConfiguration $configuration
+ * @return array
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws UnsupportedRunnerException
+ */
+ public static function compileExecutionPolicies(string $path, ProjectConfiguration $configuration): array
+ {
+ if(count($configuration->ExecutionPolicies) == 0)
+ return [];
+
+ Console::out('Compiling Execution Policies');
+ $total_items = count($configuration->ExecutionPolicies);
+ $execution_units = [];
+ $processed_items = 0;
+
+ /** @var ProjectConfiguration\ExecutionPolicy $policy */
+ foreach($configuration->ExecutionPolicies as $policy)
+ {
+ if($total_items > 5)
+ {
+ Console::inlineProgressBar($processed_items, $total_items);
+ }
+
+ $unit_path = Functions::correctDirectorySeparator($path . $policy->Execute->Target);
+ $execution_units[] = Functions::compileRunner($unit_path, $policy);
+ }
+
+ if(ncc::cliMode() && $total_items > 5)
+ 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 BuildConfigurationNotFoundException
+ * @throws BuildException
+ * @throws IOException
+ */
+ public static function writePackage(string $path, Package $package, ProjectConfiguration $configuration, string $build_configuration=BuildConfigurationValues::DefaultConfiguration): string
+ {
+ // Write the package to disk
+ $FileSystem = new Filesystem();
+ $BuildConfiguration = $configuration->Build->getBuildConfiguration($build_configuration);
+ if($FileSystem->exists($path . $BuildConfiguration->OutputPath))
+ {
+ try
+ {
+ $FileSystem->remove($path . $BuildConfiguration->OutputPath);
+ }
+ catch(\ncc\ThirdParty\Symfony\Filesystem\Exception\IOException $e)
+ {
+ throw new BuildException('Cannot delete directory \'' . $path . $BuildConfiguration->OutputPath . '\', ' . $e->getMessage(), $e);
+ }
+ }
+
+ // Finally write the package to the disk
+ $FileSystem->mkdir($path . $BuildConfiguration->OutputPath);
+ $output_file = $path . $BuildConfiguration->OutputPath . DIRECTORY_SEPARATOR . $package->Assembly->Package . '.ncc';
+ $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 special formatted constants
+ *
+ * @param Package $package
+ * @param int $timestamp
+ * @return array
+ */
+ public static function compileRuntimeConstants(Package $package, int $timestamp): array
+ {
+ $compiled_constants = [];
+
+ foreach($package->Header->RuntimeConstants as $name => $value)
+ {
+ $compiled_constants[$name] = self::compileConstants($value, [
+ ConstantReferences::Assembly => $package->Assembly,
+ ConstantReferences::DateTime => $timestamp,
+ ConstantReferences::Build => null
+ ]);
+ }
+
+ return $compiled_constants;
+ }
+
+ /**
+ * 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)
+ {
+ $assembly[$key] = self::compileConstants($value, $refs);
+ }
+ $package->Assembly = Assembly::fromArray($assembly);
+ unset($assembly);
+ }
+
+ if($package->ExecutionUnits !== null && count($package->ExecutionUnits) > 0)
+ {
+ $units = [];
+ foreach($package->ExecutionUnits as $executionUnit)
+ {
+ $units[] = self::compileExecutionUnitConstants($executionUnit, $refs);
+ }
+ $package->ExecutionUnits = $units;
+ unset($units);
+ }
+ }
+
+ /**
+ * 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->ExecutionPolicy->Message = self::compileConstants($unit->ExecutionPolicy->Message, $refs);
+
+ if($unit->ExecutionPolicy->ExitHandlers !== null)
+ {
+ if($unit->ExecutionPolicy->ExitHandlers->Success !== null)
+ {
+ $unit->ExecutionPolicy->ExitHandlers->Success->Message = self::compileConstants($unit->ExecutionPolicy->ExitHandlers->Success->Message, $refs);
+ }
+
+ if($unit->ExecutionPolicy->ExitHandlers->Error !== null)
+ {
+ $unit->ExecutionPolicy->ExitHandlers->Error->Message = self::compileConstants($unit->ExecutionPolicy->ExitHandlers->Error->Message, $refs);
+ }
+
+ if($unit->ExecutionPolicy->ExitHandlers->Warning !== null)
+ {
+ $unit->ExecutionPolicy->ExitHandlers->Warning->Error = self::compileConstants($unit->ExecutionPolicy->ExitHandlers->Warning->Message, $refs);
+ }
+ }
+
+ if($unit->ExecutionPolicy->Execute !== null)
+ {
+ if($unit->ExecutionPolicy->Execute->Target !== null)
+ {
+ $unit->ExecutionPolicy->Execute->Target = self::compileConstants($unit->ExecutionPolicy->Execute->Target, $refs);
+ }
+
+ if($unit->ExecutionPolicy->Execute->WorkingDirectory !== null)
+ {
+ $unit->ExecutionPolicy->Execute->WorkingDirectory = self::compileConstants($unit->ExecutionPolicy->Execute->WorkingDirectory, $refs);
+ }
+
+ if($unit->ExecutionPolicy->Execute->Options !== null && count($unit->ExecutionPolicy->Execute->Options) > 0)
+ {
+ $options = [];
+ foreach($unit->ExecutionPolicy->Execute->Options as $key=>$value)
+ {
+ $options[self::compileConstants($key, $refs)] = self::compileConstants($value, $refs);
+ }
+ $unit->ExecutionPolicy->Execute->Options = $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::DateTime]))
+ $value = ConstantCompiler::compileDateTimeConstants($value, $refs[ConstantReferences::DateTime]);
+
+ if(isset($refs[ConstantReferences::Install]))
+ $value = ConstantCompiler::compileInstallConstants($value, $refs[ConstantReferences::Install]);
+
+ return $value;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Classes/NccExtension/Runner.php b/src/ncc/Classes/NccExtension/Runner.php
new file mode 100644
index 0000000..2133137
--- /dev/null
+++ b/src/ncc/Classes/NccExtension/Runner.php
@@ -0,0 +1,47 @@
+addUnit($package, $version, $unit, true);
+ $ExecutionPointerManager->executeUnit($package, $version, $unit->ExecutionPolicy->Name);
+ $ExecutionPointerManager->cleanTemporaryUnits();;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Classes/PackageParser.php b/src/ncc/Classes/PackageParser.php
deleted file mode 100644
index 1d54da1..0000000
--- a/src/ncc/Classes/PackageParser.php
+++ /dev/null
@@ -1,42 +0,0 @@
-PackagePath = $path;
- $this->parseFile();
- }
-
- private function parseFile()
- {
- if(file_exists($this->PackagePath) == false)
- {
- throw new FileNotFoundException('The given package path \'' . $this->PackagePath . '\' does not exist');
- }
-
- if(is_file($this->PackagePath) == false)
- {
- throw new FileNotFoundException('The given package path \'' . $this->PackagePath . '\' is not a file');
- }
-
- $file_handler = fopen($this->PackagePath, 'rb');
- $header = fread($file_handler, 14);
- }
- }
\ No newline at end of file
diff --git a/src/ncc/Classes/PhpExtension/AutoloaderGenerator.php b/src/ncc/Classes/PhpExtension/AutoloaderGenerator.php
deleted file mode 100644
index 2f94c89..0000000
--- a/src/ncc/Classes/PhpExtension/AutoloaderGenerator.php
+++ /dev/null
@@ -1,120 +0,0 @@
-project = $project;
- }
-
- /**
- * Processes the project and generates the autoloader source code.
- *
- * @param string $src
- * @param string $output
- * @param bool $static
- * @return string
- * @throws AutoloadGeneratorException
- * @throws CollectorException
- * @throws Exception
- * @throws NoUnitsFoundException
- */
- public function generateAutoload(string $src, string $output, bool $static=false): string
- {
- // Construct configuration
- $configuration = new Config([$src]);
- $configuration->setFollowSymlinks(false); // Don't follow symlinks, it won't work on some systems.
- $configuration->setOutputFile($output);
- $configuration->setTrusting(false); // Paranoid
- // Official PHP file extensions that are missing from the default configuration (whatever)
- $configuration->setInclude(ComponentFileExtensions::Php);
-
- // Construct factory
- $factory = new Factory();
- $factory->setConfig($configuration);
-
- // Create Collector
- $result = self::runCollector($factory, $configuration);
-
- // Exception raises when there are no files in the project that can be processed by the autoloader
- if(!$result->hasUnits())
- {
- throw new NoUnitsFoundException('No units were found in the project');
- }
-
- if(!$result->hasDuplicates())
- {
- foreach($result->getDuplicates() as $unit => $files)
- {
- Console::outWarning((count($files) -1). ' duplicate unit(s) detected in the project: ' . $unit);
- }
- }
-
- $template = @file_get_contents($configuration->getTemplate());
- if ($template === false)
- {
- throw new AutoloadGeneratorException("Failed to read the template file '" . $configuration->getTemplate() . "'");
- }
-
- $builder = $factory->getRenderer($result);
- return $builder->render($template);
- }
-
- /**
- * Iterates through the target directories through the collector and returns the collector results.
- *
- * @param Factory $factory
- * @param Config $config
- * @return CollectorResult
- * @throws CollectorException
- * @throws Exception
- */
- private static function runCollector(Factory $factory, Config $config): CollectorResult
- {
- $collector = $factory->getCollector();
- foreach($config->getDirectories() as $directory)
- {
- if(is_dir($directory))
- {
- $scanner = $factory->getScanner()->getIterator($directory);
- $collector->processDirectory($scanner);
- unset($scanner);
- }
- else
- {
- $file = new SplFileInfo($directory);
- $filter = $factory->getFilter(new ArrayIterator(array($file)));
- foreach($filter as $file)
- {
- $collector->processFile($file);
- }
- }
- }
-
- return $collector->getResult();
- }
-
- }
\ No newline at end of file
diff --git a/src/ncc/Classes/PhpExtension/Compiler.php b/src/ncc/Classes/PhpExtension/Compiler.php
index 4257002..d6df577 100644
--- a/src/ncc/Classes/PhpExtension/Compiler.php
+++ b/src/ncc/Classes/PhpExtension/Compiler.php
@@ -8,22 +8,26 @@
use FilesystemIterator;
use ncc\Abstracts\ComponentFileExtensions;
use ncc\Abstracts\ComponentDataType;
+ use ncc\Abstracts\ConstantReferences;
use ncc\Abstracts\Options\BuildConfigurationValues;
+ use ncc\Classes\NccExtension\PackageCompiler;
+ use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\BuildConfigurationNotFoundException;
use ncc\Exceptions\BuildException;
+ use ncc\Exceptions\FileNotFoundException;
+ use ncc\Exceptions\IOException;
use ncc\Exceptions\PackagePreparationFailedException;
+ use ncc\Exceptions\UnsupportedRunnerException;
use ncc\Interfaces\CompilerInterface;
use ncc\ncc;
use ncc\Objects\Package;
use ncc\Objects\ProjectConfiguration;
- use ncc\ThirdParty\nikic\PhpParser\Error;
use ncc\ThirdParty\nikic\PhpParser\ParserFactory;
- use ncc\ThirdParty\Symfony\Filesystem\Exception\IOException;
- use ncc\ThirdParty\Symfony\Filesystem\Filesystem;
use ncc\ThirdParty\theseer\DirectoryScanner\DirectoryScanner;
use ncc\Utilities\Base64;
use ncc\Utilities\Console;
use ncc\Utilities\Functions;
+ use ncc\Utilities\IO;
use SplFileInfo;
class Compiler implements CompilerInterface
@@ -39,28 +43,30 @@
private $package;
/**
- * @var ProjectConfiguration\BuildConfiguration|null
+ * @var string
*/
- private $selected_build_configuration;
+ private $path;
/**
* @param ProjectConfiguration $project
+ * @param string $path
*/
- public function __construct(ProjectConfiguration $project)
+ public function __construct(ProjectConfiguration $project, string $path)
{
$this->project = $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 $path
* @param string $build_configuration
* @return void
* @throws PackagePreparationFailedException
+ * @throws BuildConfigurationNotFoundException
*/
- public function prepare(string $path, string $build_configuration=BuildConfigurationValues::DefaultConfiguration): void
+ public function prepare(string $build_configuration=BuildConfigurationValues::DefaultConfiguration): void
{
try
{
@@ -72,40 +78,29 @@
throw new PackagePreparationFailedException($e->getMessage(), $e);
}
- // Auto-select the default build configuration
- if($build_configuration == BuildConfigurationValues::DefaultConfiguration)
- {
- $build_configuration = $this->project->Build->DefaultConfiguration;
- }
-
// Select the build configuration
- try
- {
- $this->selected_build_configuration = $this->project->Build->getBuildConfiguration($build_configuration);
- }
- catch (BuildConfigurationNotFoundException $e)
- {
- throw new PackagePreparationFailedException($e->getMessage(), $e);
- }
+ $selected_build_configuration = $this->project->Build->getBuildConfiguration($build_configuration);
// Create the package object
$this->package = new Package();
$this->package->Assembly = $this->project->Assembly;
$this->package->Dependencies = $this->project->Build->Dependencies;
+ $this->package->MainExecutionPolicy = $this->project->Build->Main;
// Add both the defined constants from the build configuration and the global constants.
// Global constants are overridden
- $this->package->Header->RuntimeConstants = array_merge($this->selected_build_configuration->DefineConstants, $this->package->Header->RuntimeConstants);
- $this->package->Header->RuntimeConstants = array_merge($this->project->Build->DefineConstants, $this->package->Header->RuntimeConstants);
+ $this->package->Header->RuntimeConstants = [];
+ $this->package->Header->RuntimeConstants = array_merge(
+ $selected_build_configuration->DefineConstants,
+ $this->project->Build->DefineConstants,
+ $this->package->Header->RuntimeConstants
+ );
$this->package->Header->CompilerExtension = $this->project->Project->Compiler;
$this->package->Header->CompilerVersion = NCC_VERSION_NUMBER;
- if(ncc::cliMode())
- {
- Console::out('Scanning project files');
- Console::out('theseer\DirectoryScanner - Copyright (c) 2009-2014 Arne Blankerts All rights reserved.');
- }
+ Console::out('Scanning project files');
+ Console::out('theseer\DirectoryScanner - Copyright (c) 2009-2014 Arne Blankerts All rights reserved.');
// First scan the project files and create a file struct.
$DirectoryScanner = new DirectoryScanner();
@@ -121,18 +116,12 @@
// Include file components that can be compiled
$DirectoryScanner->setIncludes(ComponentFileExtensions::Php);
- $DirectoryScanner->setExcludes($this->selected_build_configuration->ExcludeFiles);
-
- // Append trailing slash to the end of the path if it's not already there
- if(substr($path, -1) !== DIRECTORY_SEPARATOR)
- {
- $path .= DIRECTORY_SEPARATOR;
- }
-
- $source_path = $path . $this->project->Build->SourcePath;
+ $DirectoryScanner->setExcludes($selected_build_configuration->ExcludeFiles);
+ $source_path = $this->path . $this->project->Build->SourcePath;
+ // TODO: Re-implement the scanning process outside the compiler, as this is will be redundant
// Scan for components first.
- Console::out('Scanning for components... ', false);
+ Console::out('Scanning for components... ');
/** @var SplFileInfo $item */
/** @noinspection PhpRedundantOptionalArgumentInspection */
foreach($DirectoryScanner($source_path, True) as $item)
@@ -142,22 +131,20 @@
continue;
$Component = new Package\Component();
- $Component->Name = Functions::removeBasename($item->getPathname(), $path);
+ $Component->Name = Functions::removeBasename($item->getPathname(), $this->path);
$this->package->Components[] = $Component;
+
+ Console::outVerbose(sprintf('found component %s', $Component->Name));
}
- if(ncc::cliMode())
+ if(count($this->package->Components) > 0)
{
- if(count($this->package->Components) > 0)
- {
- Console::out(count($this->package->Components) . ' component(s) found');
- }
- else
- {
- Console::out('No components found');
- }
+ Console::out(count($this->package->Components) . ' component(s) found');
+ }
+ else
+ {
+ Console::out('No components found');
}
-
// Clear previous excludes and includes
$DirectoryScanner->setExcludes([]);
@@ -165,10 +152,10 @@
// Ignore component files
$DirectoryScanner->setExcludes(array_merge(
- $this->selected_build_configuration->ExcludeFiles, ComponentFileExtensions::Php
+ $selected_build_configuration->ExcludeFiles, ComponentFileExtensions::Php
));
- Console::out('Scanning for resources... ', false);
+ Console::out('Scanning for resources... ');
/** @var SplFileInfo $item */
foreach($DirectoryScanner($source_path) as $item)
{
@@ -177,174 +164,185 @@
continue;
$Resource = new Package\Resource();
- $Resource->Name = Functions::removeBasename($item->getPathname(), $path);
+ $Resource->Name = Functions::removeBasename($item->getPathname(), $this->path);
$this->package->Resources[] = $Resource;
- }
-
- if(ncc::cliMode())
- {
- if(count($this->package->Resources) > 0)
- {
- Console::out(count($this->package->Resources) . ' resources(s) found');
- }
- else
- {
- Console::out('No resources found');
- }
- }
- }
-
- /**
- * Builds the package by parsing the AST contents of the components and resources
- *
- * @param string $path
- * @return string
- * @throws BuildException
- */
- public function build(string $path): string
- {
- if($this->package == null)
- {
- throw new BuildException('The prepare() method must be called before building the package');
- }
-
- // Append trailing slash to the end of the path if it's not already there
- if(substr($path, -1) !== DIRECTORY_SEPARATOR)
- {
- $path .= DIRECTORY_SEPARATOR;
- }
-
- // Runtime variables
- $components = [];
- $resources = [];
- $processed_items = 0;
- $total_items = 0;
-
- if(count($this->package->Components) > 0)
- {
- if(ncc::cliMode())
- {
- Console::out('Compiling components');
- $total_items = count($this->package->Components);
- }
-
- // Process the components and attempt to create an AST representation of the source
- foreach($this->package->Components as $component)
- {
- if(ncc::cliMode() && $total_items > 5)
- {
- Console::inlineProgressBar($processed_items, $total_items);
- }
-
- $content = file_get_contents(Functions::correctDirectorySeparator($path . $component->Name));
- $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
-
- try
- {
- $stmts = $parser->parse($content);
- $encoded = json_encode($stmts);
-
- if($encoded === false)
- {
- $component->DataType = ComponentDataType::b64encoded;
- $component->Data = Base64::encode($content);
- $component->Checksum = hash('sha1', $component->Data);
- }
- else
- {
- $component->DataType = ComponentDataType::AST;
- $component->Data = json_decode($encoded, true);
- $component->Checksum = null;
- }
- }
- catch(Error $e)
- {
- $component->DataType = ComponentDataType::b64encoded;
- $component->Data = Base64::encode($content);
- $component->Checksum = hash('sha1', $component->Data);
- unset($e);
- }
-
- $component->Name = str_replace($this->project->Build->SourcePath, (string)null, $component->Name);
- $components[] = $component;
- $processed_items += 1;
- }
-
- if(ncc::cliMode() && $total_items > 5)
- {
- print(PHP_EOL);
- }
-
- // Update the components
- $this->package->Components = $components;
+ Console::outVerbose(sprintf('found resource %s', $Resource->Name));
}
if(count($this->package->Resources) > 0)
{
- // Process the resources
- if(ncc::cliMode())
- {
- Console::out('Processing resources');
- $processed_items = 0;
- $total_items = count($this->package->Resources);
- }
-
- foreach($this->package->Resources as $resource)
- {
- if(ncc::cliMode() && $total_items > 5)
- {
- Console::inlineProgressBar($processed_items, $total_items);
- }
-
- // Get the data and
- $resource->Data = file_get_contents(Functions::correctDirectorySeparator($path . $resource->Name));
- $resource->Data = Base64::encode($resource->Data);
- $resource->Checksum = hash('sha1', $resource->Data);
- $resource->Name = str_replace($this->project->Build->SourcePath, (string)null, $resource->Name);
- $resources[] = $resource;
- }
-
- // Update the resources
- $this->package->Resources = $resources;
+ Console::out(count($this->package->Resources) . ' resources(s) found');
}
+ else
+ {
+ Console::out('No resources found');
+ }
+ }
- if(ncc::cliMode())
+ /**
+ * Executes the compile process in the correct order and returns the finalized Package object
+ *
+ * @return Package|null
+ * @throws AccessDeniedException
+ * @throws BuildException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws UnsupportedRunnerException
+ */
+ public function build(): ?Package
+ {
+ $this->compileExecutionPolicies();
+ $this->compileComponents();
+ $this->compileResources();
+
+ PackageCompiler::compilePackageConstants($this->package, [
+ ConstantReferences::Assembly => $this->project->Assembly,
+ ConstantReferences::Build => null,
+ ConstantReferences::DateTime => time()
+ ]);
+
+ return $this->getPackage();
+ }
+
+ /**
+ * Compiles the resources of the package
+ *
+ * @return void
+ * @throws AccessDeniedException
+ * @throws BuildException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ 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->Resources) == 0)
+ return;
+
+ // Process the resources
+ Console::out('Processing resources');
+ $total_items = count($this->package->Resources);
+ $processed_items = 0;
+ $resources = [];
+
+ foreach($this->package->Resources as $resource)
{
if($total_items > 5)
- print(PHP_EOL);
- Console::out($this->package->Assembly->Package . ' compiled successfully');
+ {
+ Console::inlineProgressBar($processed_items, $total_items);
+ }
+
+ // Get the data and
+ $resource->Data = IO::fread(Functions::correctDirectorySeparator($this->path . $resource->Name));
+ $resource->Data = Base64::encode($resource->Data);
+ $resource->Name = str_replace($this->project->Build->SourcePath, (string)null, $resource->Name);
+ $resource->updateChecksum();
+ $resources[] = $resource;
+
+ Console::outDebug(sprintf('processed resource %s', $resource->Name));
}
- // Write the package to disk
- $FileSystem = new Filesystem();
+ // Update the resources
+ $this->package->Resources = $resources;
+ }
- if($FileSystem->exists($path . $this->selected_build_configuration->OutputPath))
+ /**
+ * Compiles the components of the package
+ *
+ * @return void
+ * @throws AccessDeniedException
+ * @throws BuildException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ 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->Components) == 0)
+ return;
+
+ Console::out('Compiling components');
+ $total_items = count($this->package->Components);
+ $processed_items = 0;
+ $components = [];
+
+ // Process the components and attempt to create an AST representation of the source
+ foreach($this->package->Components as $component)
{
+ if($total_items > 5)
+ {
+ Console::inlineProgressBar($processed_items, $total_items);
+ }
+
+ $content = IO::fread(Functions::correctDirectorySeparator($this->path . $component->Name));
+ $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
+
try
{
- $FileSystem->remove($path . $this->selected_build_configuration->OutputPath);
+ $stmts = $parser->parse($content);
+ $encoded = json_encode($stmts);
+ unset($stmts);
+
+ if($encoded === false)
+ {
+ $component->DataType = ComponentDataType::b64encoded;
+ $component->Data = Base64::encode($content);
+ }
+ else
+ {
+ $component->DataType = ComponentDataType::AST;
+ $component->Data = json_decode($encoded, true);
+ }
}
- catch(IOException $e)
+ catch(Exception $e)
{
- throw new BuildException('Cannot delete directory \'' . $path . $this->selected_build_configuration->OutputPath . '\', ' . $e->getMessage(), $e);
+ $component->DataType = ComponentDataType::b64encoded;
+ $component->Data = Base64::encode($content);
+ unset($e);
}
+
+ unset($parser);
+
+ $component->Name = str_replace($this->project->Build->SourcePath, (string)null, $component->Name);
+ $component->updateChecksum();
+ $components[] = $component;
+ $processed_items += 1;
+
+ Console::outDebug(sprintf('processed component %s (%s)', $component->Name, $component->DataType));
}
- // Finally write the package to the disk
- $FileSystem->mkdir($path . $this->selected_build_configuration->OutputPath);
- $output_file = $path . $this->selected_build_configuration->OutputPath . DIRECTORY_SEPARATOR . $this->package->Assembly->Package . '.ncc';
- $FileSystem->touch($output_file);
-
- try
+ if(ncc::cliMode() && $total_items > 5)
{
- $this->package->save($output_file);
- }
- catch(Exception $e)
- {
- throw new BuildException('Cannot write to output file', $e);
+ print(PHP_EOL);
}
- return $output_file;
+ // Update the components
+ $this->package->Components = $components;
}
+
+ /**
+ * @return void
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws UnsupportedRunnerException
+ */
+ public function compileExecutionPolicies(): void
+ {
+ PackageCompiler::compileExecutionPolicies($this->path, $this->project);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getPackage(): ?Package
+ {
+ return $this->package;
+ }
+
}
\ No newline at end of file
diff --git a/src/ncc/Classes/PhpExtension/Installer.php b/src/ncc/Classes/PhpExtension/Installer.php
new file mode 100644
index 0000000..ade05ec
--- /dev/null
+++ b/src/ncc/Classes/PhpExtension/Installer.php
@@ -0,0 +1,342 @@
+package = $package;
+ }
+
+ /**
+ * Processes the given component and returns the decoded component as a string representation
+ * If the processed component does not result in a string representation, none will be returned.
+ *
+ * @param Component $component
+ * @return string|null
+ * @throws ComponentChecksumException
+ * @throws ComponentDecodeException
+ * @throws UnsupportedComponentTypeException
+ */
+ public function processComponent(Package\Component $component): ?string
+ {
+ if($component->Data == null)
+ return null;
+
+ if(!$component->validateChecksum())
+ throw new ComponentChecksumException('Checksum validation failed for component ' . $component->Name . ', the package may be corrupted.');
+
+ switch($component->DataType)
+ {
+ case ComponentDataType::AST:
+ try
+ {
+ $stmts = $this->decodeRecursive($component->Data);
+ }
+ catch (Exception $e)
+ {
+ throw new ComponentDecodeException('Cannot decode component: ' . $component->Name . ', ' . $e->getMessage(), $e);
+ }
+
+ $prettyPrinter = new Standard();
+ return $prettyPrinter->prettyPrintFile($stmts);
+
+ case ComponentDataType::b64encoded:
+ return Base64::decode($component->Data);
+
+ case ComponentDataType::Plain:
+ return $component->Data;
+
+ default:
+ throw new UnsupportedComponentTypeException('Unsupported component type \'' . $component->DataType . '\'');
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function preInstall(InstallationPaths $installationPaths): void
+ {
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function postInstall(InstallationPaths $installationPaths): void
+ {
+ $autoload_path = $installationPaths->getBinPath() . DIRECTORY_SEPARATOR . 'autoload.php';
+ $autoload_src = $this->generateAutoload($installationPaths->getSourcePath(), $autoload_path);
+ IO::fwrite($autoload_path, $autoload_src);
+ }
+
+ /**
+ * Processes the given resource and returns the string representation of the resource
+ *
+ * @param Package\Resource $resource
+ * @return string|null
+ * @throws ResourceChecksumException
+ */
+ public function processResource(Package\Resource $resource): ?string
+ {
+ if(!$resource->validateChecksum())
+ throw new ResourceChecksumException('Checksum validation failed for resource ' . $resource->Name . ', the package may be corrupted.');
+ return Base64::decode($resource->Data);
+ }
+
+ /**
+ * @param $value
+ * @return array|Comment|Node
+ * @throws ReflectionException
+ * @noinspection PhpMissingReturnTypeInspection
+ */
+ private function decodeRecursive($value)
+ {
+ if (is_array($value))
+ {
+ if (isset($value['nodeType']))
+ {
+ if ($value['nodeType'] === 'Comment' || $value['nodeType'] === 'Comment_Doc')
+ {
+ return $this->decodeComment($value);
+ }
+ return $this->decodeNode($value);
+ }
+ return $this->decodeArray($value);
+ }
+ return $value;
+ }
+
+ /**
+ * @param array $array
+ * @return array
+ * @throws ReflectionException
+ */
+ private function decodeArray(array $array) : array
+ {
+ $decodedArray = [];
+ foreach ($array as $key => $value)
+ {
+ $decodedArray[$key] = $this->decodeRecursive($value);
+ }
+ return $decodedArray;
+ }
+
+ /**
+ * @param array $value
+ * @return Node
+ * @throws ReflectionException
+ */
+ private function decodeNode(array $value) : Node
+ {
+ $nodeType = $value['nodeType'];
+ if (!is_string($nodeType))
+ {
+ throw new RuntimeException('Node type must be a string');
+ }
+
+ $reflectionClass = $this->reflectionClassFromNodeType($nodeType);
+ /** @var Node $node */
+ $node = $reflectionClass->newInstanceWithoutConstructor();
+
+ if (isset($value['attributes'])) {
+ if (!is_array($value['attributes']))
+ {
+ throw new RuntimeException('Attributes must be an array');
+ }
+
+ $node->setAttributes($this->decodeArray($value['attributes']));
+ }
+
+ foreach ($value as $name => $subNode) {
+ if ($name === 'nodeType' || $name === 'attributes')
+ {
+ continue;
+ }
+
+ $node->$name = $this->decodeRecursive($subNode);
+ }
+
+ return $node;
+ }
+
+ /**
+ * @param array $value
+ * @return Comment
+ */
+ private function decodeComment(array $value) : Comment
+ {
+ $className = $value['nodeType'] === 'Comment' ? Comment::class : Comment\Doc::class;
+ if (!isset($value['text']))
+ {
+ throw new RuntimeException('Comment must have text');
+ }
+
+ return new $className(
+ $value['text'],
+ $value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1,
+ $value['endLine'] ?? -1, $value['endFilePos'] ?? -1, $value['endTokenPos'] ?? -1
+ );
+ }
+
+ /**
+ * @param string $nodeType
+ * @return ReflectionClass
+ * @throws ReflectionException
+ */
+ private function reflectionClassFromNodeType(string $nodeType) : ReflectionClass
+ {
+ if (!isset($this->reflectionClassCache[$nodeType]))
+ {
+ $className = $this->classNameFromNodeType($nodeType);
+ $this->reflectionClassCache[$nodeType] = new ReflectionClass($className);
+ }
+ return $this->reflectionClassCache[$nodeType];
+ }
+
+ /**
+ * @param string $nodeType
+ * @return string
+ */
+ private function classNameFromNodeType(string $nodeType) : string
+ {
+ $className = 'ncc\\ThirdParty\\nikic\\PhpParser\\Node\\' . strtr($nodeType, '_', '\\');
+ if (class_exists($className))
+ {
+ return $className;
+ }
+
+ $className .= '_';
+ if (class_exists($className))
+ {
+ return $className;
+ }
+
+ throw new RuntimeException("Unknown node type \"$nodeType\"");
+ }
+
+ /**
+ * Processes the project and generates the autoloader source code.
+ *
+ * @param string $src
+ * @param string $output
+ * @return string
+ * @throws AccessDeniedException
+ * @throws CollectorException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws NoUnitsFoundException
+ */
+ private function generateAutoload(string $src, string $output): string
+ {
+ // Construct configuration
+ $configuration = new Config([$src]);
+ $configuration->setFollowSymlinks(false); // Don't follow symlinks, it won't work on some systems.
+ $configuration->setTrusting(true); // Paranoid
+ $configuration->setOutputFile($output);
+ $configuration->setStaticMode(false);
+ // Official PHP file extensions that are missing from the default configuration (whatever)
+ $configuration->setInclude(ComponentFileExtensions::Php);
+ $configuration->setQuietMode(true);
+
+ // Construct factory
+ $factory = new Factory();
+ $factory->setConfig($configuration);
+
+ // Create Collector
+ $result = self::runCollector($factory, $configuration);
+
+ // Exception raises when there are no files in the project that can be processed by the autoloader
+ if(!$result->hasUnits())
+ {
+ throw new NoUnitsFoundException('No units were found in the project');
+ }
+
+ $template = IO::fread($configuration->getTemplate());
+
+ $builder = $factory->getRenderer($result);
+ return $builder->render($template);
+ }
+
+ /**
+ * Iterates through the target directories through the collector and returns the collector results.
+ *
+ * @param Factory $factory
+ * @param Config $config
+ * @return CollectorResult
+ * @throws CollectorException
+ * @throws Exception
+ */
+ private static function runCollector(Factory $factory, Config $config): CollectorResult
+ {
+ $collector = $factory->getCollector();
+ foreach($config->getDirectories() as $directory)
+ {
+ if(is_dir($directory))
+ {
+ $scanner = $factory->getScanner()->getIterator($directory);
+ $collector->processDirectory($scanner);
+ unset($scanner);
+ }
+ else
+ {
+ $file = new SplFileInfo($directory);
+ $filter = $factory->getFilter(new ArrayIterator(array($file)));
+ foreach($filter as $file)
+ {
+ $collector->processFile($file);
+ }
+ }
+ }
+
+ return $collector->getResult();
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Classes/PhpExtension/Runner.php b/src/ncc/Classes/PhpExtension/Runner.php
new file mode 100644
index 0000000..fe8dbb4
--- /dev/null
+++ b/src/ncc/Classes/PhpExtension/Runner.php
@@ -0,0 +1,67 @@
+Execute->Target = null;
+ $execution_unit->ExecutionPolicy = $policy;
+ $execution_unit->Data = Base64::encode(IO::fread($target_file));
+
+ return $execution_unit;
+ }
+
+ /**
+ * Returns the file extension to use for the target file
+ *
+ * @return string
+ */
+ public static function getFileExtension(): string
+ {
+ return '.php';
+ }
+
+ /**
+ * @param ExecutionPointer $pointer
+ * @return Process
+ * @throws RunnerExecutionException
+ */
+ public static function prepareProcess(ExecutionPointer $pointer): Process
+ {
+ $php_bin = new ExecutableFinder();
+ $php_bin = $php_bin->find('php');
+ if($php_bin == null)
+ throw new RunnerExecutionException('Cannot locate PHP executable');
+
+ if($pointer->ExecutionPolicy->Execute->Options !== null && count($pointer->ExecutionPolicy->Execute->Options) > 0)
+ return new Process(array_merge([$php_bin, $pointer->FilePointer], $pointer->ExecutionPolicy->Execute->Options));
+ return new Process([$php_bin, $pointer->FilePointer]);
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Exceptions/ComponentChecksumException.php b/src/ncc/Exceptions/ComponentChecksumException.php
new file mode 100644
index 0000000..68bb520
--- /dev/null
+++ b/src/ncc/Exceptions/ComponentChecksumException.php
@@ -0,0 +1,28 @@
+message = $message;
+ $this->previous = $previous;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Exceptions/ComponentDecodeException.php b/src/ncc/Exceptions/ComponentDecodeException.php
new file mode 100644
index 0000000..593255f
--- /dev/null
+++ b/src/ncc/Exceptions/ComponentDecodeException.php
@@ -0,0 +1,28 @@
+message = $message;
+ $this->previous = $previous;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Exceptions/ExecutionUnitNotFoundException.php b/src/ncc/Exceptions/ExecutionUnitNotFoundException.php
new file mode 100644
index 0000000..228f512
--- /dev/null
+++ b/src/ncc/Exceptions/ExecutionUnitNotFoundException.php
@@ -0,0 +1,8 @@
+message = $message;
+ $this->previous = $previous;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Exceptions/InstallationException.php b/src/ncc/Exceptions/InstallationException.php
new file mode 100644
index 0000000..1b99c67
--- /dev/null
+++ b/src/ncc/Exceptions/InstallationException.php
@@ -0,0 +1,28 @@
+message = $message;
+ $this->previous = $previous;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Exceptions/InvalidExecutionPolicyName.php b/src/ncc/Exceptions/InvalidExecutionPolicyName.php
new file mode 100644
index 0000000..2699960
--- /dev/null
+++ b/src/ncc/Exceptions/InvalidExecutionPolicyName.php
@@ -0,0 +1,14 @@
+message = $message;
+ $this->previous = $previous;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Exceptions/PackageAlreadyInstalledException.php b/src/ncc/Exceptions/PackageAlreadyInstalledException.php
new file mode 100644
index 0000000..fbf5c49
--- /dev/null
+++ b/src/ncc/Exceptions/PackageAlreadyInstalledException.php
@@ -0,0 +1,28 @@
+message = $message;
+ $this->previous = $previous;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Exceptions/PackageLockException.php b/src/ncc/Exceptions/PackageLockException.php
new file mode 100644
index 0000000..b86c20c
--- /dev/null
+++ b/src/ncc/Exceptions/PackageLockException.php
@@ -0,0 +1,28 @@
+message = $message;
+ $this->previous = $previous;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Exceptions/PackageNotFoundException.php b/src/ncc/Exceptions/PackageNotFoundException.php
new file mode 100644
index 0000000..88bd370
--- /dev/null
+++ b/src/ncc/Exceptions/PackageNotFoundException.php
@@ -0,0 +1,28 @@
+message = $message;
+ $this->previous = $previous;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Exceptions/PackageParsingException.php b/src/ncc/Exceptions/PackageParsingException.php
new file mode 100644
index 0000000..70c1be7
--- /dev/null
+++ b/src/ncc/Exceptions/PackageParsingException.php
@@ -0,0 +1,28 @@
+message = $message;
+ $this->previous = $previous;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Exceptions/ProjectConfigurationNotFoundException.php b/src/ncc/Exceptions/ProjectConfigurationNotFoundException.php
new file mode 100644
index 0000000..2897f72
--- /dev/null
+++ b/src/ncc/Exceptions/ProjectConfigurationNotFoundException.php
@@ -0,0 +1,13 @@
+message = $message;
+ $this->code = $code;
+ $this->previous = $previous;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Exceptions/RunnerExecutionException.php b/src/ncc/Exceptions/RunnerExecutionException.php
new file mode 100644
index 0000000..e568dd1
--- /dev/null
+++ b/src/ncc/Exceptions/RunnerExecutionException.php
@@ -0,0 +1,28 @@
+message = $message;
+ $this->previous = $previous;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Exceptions/UndefinedExecutionPolicyException.php b/src/ncc/Exceptions/UndefinedExecutionPolicyException.php
new file mode 100644
index 0000000..8797b5e
--- /dev/null
+++ b/src/ncc/Exceptions/UndefinedExecutionPolicyException.php
@@ -0,0 +1,28 @@
+message = $message;
+ $this->previous = $previous;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Exceptions/UnsupportedComponentTypeException.php b/src/ncc/Exceptions/UnsupportedComponentTypeException.php
new file mode 100644
index 0000000..ecd4c79
--- /dev/null
+++ b/src/ncc/Exceptions/UnsupportedComponentTypeException.php
@@ -0,0 +1,28 @@
+message = $message;
+ $this->previous = $previous;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Exceptions/UnsupportedRunnerException.php b/src/ncc/Exceptions/UnsupportedRunnerException.php
new file mode 100644
index 0000000..830a7a1
--- /dev/null
+++ b/src/ncc/Exceptions/UnsupportedRunnerException.php
@@ -0,0 +1,11 @@
+message = $message;
+ $this->previous = $previous;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Interfaces/CompilerInterface.php b/src/ncc/Interfaces/CompilerInterface.php
index 4fa160d..c098abf 100644
--- a/src/ncc/Interfaces/CompilerInterface.php
+++ b/src/ncc/Interfaces/CompilerInterface.php
@@ -3,23 +3,79 @@
namespace ncc\Interfaces;
use ncc\Abstracts\Options\BuildConfigurationValues;
+ use ncc\Exceptions\AccessDeniedException;
+ use ncc\Exceptions\BuildException;
+ use ncc\Exceptions\FileNotFoundException;
+ use ncc\Exceptions\IOException;
+ use ncc\Exceptions\UnsupportedRunnerException;
+ use ncc\Objects\Package;
+ use ncc\Objects\ProjectConfiguration;
interface CompilerInterface
{
+ /**
+ * Public constructor
+ *
+ * @param ProjectConfiguration $project
+ * @param string $path
+ */
+ public function __construct(ProjectConfiguration $project, string $path);
+
/**
* Prepares the package for the build process, this method is called before build()
*
- * @param string $path The path that the project file is located in (project.json)
* @param string $build_configuration The build configuration to use to build the project
* @return void
*/
- public function prepare(string $path, string $build_configuration=BuildConfigurationValues::DefaultConfiguration): void;
+ public function prepare(string $build_configuration=BuildConfigurationValues::DefaultConfiguration): void;
/**
- * Builds the package, returns the output path of the build
+ * Executes the compile process in the correct order and returns the finalized Package object
*
- * @param string $path The path that the project file is located in (project.json)
- * @return string Returns the output path of the build
+ * @return Package|null
+ * @throws AccessDeniedException
+ * @throws BuildException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws UnsupportedRunnerException
*/
- public function build(string $path): string;
+ public function build(): ?Package;
+
+ /**
+ * Compiles the components of the package
+ *
+ * @return void
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public function compileComponents(): void;
+
+ /**
+ * Compiles the resources of the package
+ *
+ * @return void
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public function compileResources(): void;
+
+ /**
+ * Compiles the execution policies of the package
+ *
+ * @return void
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws UnsupportedRunnerException
+ */
+ public function compileExecutionPolicies(): void;
+
+ /**
+ * Returns the current state of the package
+ *
+ * @return Package|null
+ */
+ public function getPackage(): ?Package;
}
\ No newline at end of file
diff --git a/src/ncc/Interfaces/InstallerInterface.php b/src/ncc/Interfaces/InstallerInterface.php
new file mode 100644
index 0000000..75049ce
--- /dev/null
+++ b/src/ncc/Interfaces/InstallerInterface.php
@@ -0,0 +1,59 @@
+CredentialsPath = PathFinder::getDataPath(Scopes::System) . DIRECTORY_SEPARATOR . 'credentials.store';
}
@@ -51,7 +55,7 @@
*
* @return void
* @throws AccessDeniedException
- * @throws RuntimeException
+ * @throws IOException
*/
public function constructStore(): void
{
@@ -68,12 +72,7 @@
$VaultObject = new Vault();
$VaultObject->Version = Versions::CredentialsStoreVersion;
- if(!@file_put_contents($this->CredentialsPath, ZiProto::encode($VaultObject->toArray())))
- {
- throw new RuntimeException('Cannot create file \'' . $this->CredentialsPath . '\'');
- }
-
- chmod($this->CredentialsPath, 0600);
+ IO::fwrite($this->CredentialsPath, ZiProto::encode($VaultObject->toArray()), 0600);
}
/**
@@ -81,6 +80,7 @@
*
* @return Vault
* @throws AccessDeniedException
+ * @throws IOException
* @throws RuntimeException
*/
public function getVault(): Vault
@@ -94,17 +94,15 @@
try
{
- $Vault = ZiProto::decode(file_get_contents($this->CredentialsPath));
+ $Vault = ZiProto::decode(IO::fread($this->CredentialsPath));
}
- catch(\Exception $e)
+ catch(Exception $e)
{
// TODO: Implement error-correction for corrupted credentials store.
throw new RuntimeException($e->getMessage(), $e);
}
- $Vault = Vault::fromArray($Vault);
-
- return $Vault;
+ return Vault::fromArray($Vault);
}
/**
@@ -113,15 +111,16 @@
* @param Vault $vault
* @return void
* @throws AccessDeniedException
+ * @throws IOException
*/
- public function saveVault(Vault $vault)
+ public function saveVault(Vault $vault): void
{
if(!$this->checkAccess())
{
throw new AccessDeniedException('Cannot write to credentials store without system permissions');
}
- file_put_contents($this->CredentialsPath, ZiProto::encode($vault->toArray()));
+ IO::fwrite($this->CredentialsPath, ZiProto::encode($vault->toArray()), 0600);
}
/**
@@ -132,8 +131,9 @@
* @throws AccessDeniedException
* @throws InvalidCredentialsEntryException
* @throws RuntimeException
+ * @throws IOException
*/
- public function registerEntry(Vault\Entry $entry)
+ public function registerEntry(Vault\Entry $entry): void
{
if(!preg_match('/^[\w-]+$/', $entry->Alias))
{
@@ -152,7 +152,7 @@
/**
* @return null
*/
- public function getCredentialsPath()
+ public function getCredentialsPath(): ?string
{
return $this->CredentialsPath;
}
diff --git a/src/ncc/Managers/ExecutionPointerManager.php b/src/ncc/Managers/ExecutionPointerManager.php
new file mode 100644
index 0000000..bedd2b5
--- /dev/null
+++ b/src/ncc/Managers/ExecutionPointerManager.php
@@ -0,0 +1,424 @@
+cleanTemporaryUnits();
+ }
+ catch(Exception $e)
+ {
+ unset($e);
+ }
+ }
+
+ /**
+ * @throws InvalidScopeException
+ */
+ public function __construct()
+ {
+ $this->RunnerPath = PathFinder::getRunnerPath(Scopes::System);
+ $this->TemporaryUnits = [];
+ }
+
+ /**
+ * Deletes all temporary files and directories
+ *
+ * @return void
+ */
+ public function cleanTemporaryUnits(): void
+ {
+ if(count($this->TemporaryUnits) == 0)
+ return;
+
+ try
+ {
+ foreach($this->TemporaryUnits as $datum)
+ {
+ $this->removeUnit($datum['package'], $datum['version'], $datum['name']);
+ }
+ }
+ catch(Exception $e)
+ {
+ unset($e);
+ }
+ }
+
+ /**
+ * Calculates the Package ID for the execution pointers
+ *
+ * @param string $package
+ * @param string $version
+ * @return string
+ */
+ private function getPackageId(string $package, string $version): string
+ {
+ return hash('haval128,4', $package . $version);
+ }
+
+ /**
+ * Adds a new Execution Unit to the
+ *
+ * @param string $package
+ * @param string $version
+ * @param ExecutionUnit $unit
+ * @param bool $temporary
+ * @return void
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws UnsupportedRunnerException
+ * @noinspection PhpUnused
+ */
+ public function addUnit(string $package, string $version, ExecutionUnit $unit, bool $temporary=false): void
+ {
+ if(Resolver::resolveScope() !== Scopes::System)
+ throw new AccessDeniedException('Cannot add new ExecutionUnit \'' . $unit->ExecutionPolicy->Name .'\' for ' . $package . ', insufficient permissions');
+
+ $package_id = $this->getPackageId($package, $version);
+ $package_config_path = $this->RunnerPath . DIRECTORY_SEPARATOR . $package_id . '.inx';
+ $package_bin_path = $this->RunnerPath . DIRECTORY_SEPARATOR . $package_id;
+
+ $filesystem = new Filesystem();
+
+ // Either load or create the pointers file
+ if(!$filesystem->exists($package_config_path))
+ {
+ $execution_pointers = new ExecutionPointers($package, $version);
+ }
+ else
+ {
+ $execution_pointers = ExecutionPointers::fromArray(ZiProto::decode(IO::fread($package_config_path)));
+ }
+
+ $bin_file = $package_bin_path . DIRECTORY_SEPARATOR . hash('haval128,4', $unit->ExecutionPolicy->Name);
+ $bin_file .= match ($unit->ExecutionPolicy->Runner) {
+ Runners::php => Runner::getFileExtension(),
+ default => throw new UnsupportedRunnerException('The runner \'' . $unit->ExecutionPolicy->Runner . '\' is not supported'),
+ };
+
+ if($filesystem->exists($bin_file) && $temporary)
+ return;
+
+ if(!$filesystem->exists($package_bin_path))
+ $filesystem->mkdir($package_bin_path);
+
+ if($filesystem->exists($bin_file))
+ $filesystem->remove($bin_file);
+
+ IO::fwrite($bin_file, $unit->Data);
+ $execution_pointers->addUnit($unit, $bin_file);
+ IO::fwrite($package_config_path, ZiProto::encode($execution_pointers->toArray(true)));
+
+ if($temporary)
+ {
+ $this->TemporaryUnits[] = [
+ 'package' => $package,
+ 'version' => $version,
+ 'unit' => $unit->ExecutionPolicy->Name
+ ];
+ }
+ }
+
+ /**
+ * Deletes and removes the installed unit
+ *
+ * @param string $package
+ * @param string $version
+ * @param string $name
+ * @return bool
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public function removeUnit(string $package, string $version, string $name): bool
+ {
+ if(Resolver::resolveScope() !== Scopes::System)
+ throw new AccessDeniedException('Cannot remove ExecutionUnit \'' . $name .'\' for ' . $package . ', insufficient permissions');
+
+ $package_id = $this->getPackageId($package, $version);
+ $package_config_path = $this->RunnerPath . DIRECTORY_SEPARATOR . $package_id . '.inx';
+ $package_bin_path = $this->RunnerPath . DIRECTORY_SEPARATOR . $package_id;
+
+ $filesystem = new Filesystem();
+ if(!$filesystem->exists($package_config_path))
+ return false;
+ $execution_pointers = ExecutionPointers::fromArray(ZiProto::decode(IO::fread($package_config_path)));
+ $unit = $execution_pointers->getUnit($name);
+ if($unit == null)
+ return false;
+ $results = $execution_pointers->deleteUnit($name);
+
+ // Delete everything if there are no execution pointers configured
+ if(count($execution_pointers->getPointers()) == 0)
+ {
+ $filesystem->remove($package_config_path);
+ $filesystem->remove($package_bin_path);
+
+ return $results;
+ }
+
+ // Delete the single execution pointer file
+ if($filesystem->exists($unit->FilePointer))
+ $filesystem->remove($unit->FilePointer);
+
+ return $results;
+ }
+
+ /**
+ * Returns an array of configured units for a package version
+ *
+ * @param string $package
+ * @param string $version
+ * @return array
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @noinspection PhpUnused
+ */
+ public function getUnits(string $package, string $version): array
+ {
+ $package_id = $this->getPackageId($package, $version);
+ $package_config_path = $this->RunnerPath . DIRECTORY_SEPARATOR . $package_id . '.inx';
+
+ if(!file_exists($package_config_path))
+ return [];
+
+ $execution_pointers = ExecutionPointers::fromArray(ZiProto::decode(IO::fread($package_config_path)));
+ $results = [];
+ foreach($execution_pointers->getPointers() as $pointer)
+ {
+ $results[] = $pointer->ExecutionPolicy->Name;
+ }
+
+ return $results;
+ }
+
+ /**
+ * Executes a unit
+ *
+ * @param string $package
+ * @param string $version
+ * @param string $name
+ * @return void
+ * @throws AccessDeniedException
+ * @throws ExecutionUnitNotFoundException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws NoAvailableUnitsException
+ * @throws UnsupportedRunnerException
+ * @throws RunnerExecutionException
+ */
+ public function executeUnit(string $package, string $version, string $name): void
+ {
+ $package_id = $this->getPackageId($package, $version);
+ $package_config_path = $this->RunnerPath . DIRECTORY_SEPARATOR . $package_id . '.inx';
+
+ if(!file_exists($package_config_path))
+ throw new NoAvailableUnitsException('There is no available units for \'' . $package . '=' .$version .'\'');
+
+ $execution_pointers = ExecutionPointers::fromArray(ZiProto::decode(IO::fread($package_config_path)));
+ $unit = $execution_pointers->getUnit($name);
+
+ if($unit == null)
+ throw new ExecutionUnitNotFoundException('The execution unit \'' . $name . '\' was not found for \'' . $package . '=' .$version .'\'');
+
+ $process = match (strtolower($unit->ExecutionPolicy->Runner))
+ {
+ Runners::php => Runner::prepareProcess($unit),
+ default => throw new UnsupportedRunnerException('The runner \'' . $unit->ExecutionPolicy->Runner . '\' is not supported'),
+ };
+
+ if($unit->ExecutionPolicy->Execute->WorkingDirectory !== null)
+ $process->setWorkingDirectory($unit->ExecutionPolicy->Execute->WorkingDirectory);
+ if($unit->ExecutionPolicy->Execute->Timeout !== null)
+ $process->setTimeout((float)$unit->ExecutionPolicy->Execute->Timeout);
+
+ if($unit->ExecutionPolicy->Execute->Silent)
+ {
+ $process->disableOutput();
+ $process->setTty(false);
+ }
+ elseif($unit->ExecutionPolicy->Execute->Tty)
+ {
+ $process->enableOutput();
+ $process->setTty(true);
+ }
+ else
+ {
+ $process->enableOutput();
+ }
+
+ try
+ {
+ if($unit->ExecutionPolicy->Message !== null)
+ Console::out($unit->ExecutionPolicy->Message);
+
+ $process->run(function ($type, $buffer) {
+ Console::out($buffer);
+ });
+
+ $process->wait();
+ }
+ catch(Exception $e)
+ {
+ unset($e);
+ $this->handleExit($package, $version, $unit->ExecutionPolicy->ExitHandlers->Error);
+ }
+
+ if($unit->ExecutionPolicy->ExitHandlers !== null)
+ {
+ if($process->isSuccessful() && $unit->ExecutionPolicy->ExitHandlers->Success !== null)
+ {
+ $this->handleExit($package, $version, $unit->ExecutionPolicy->ExitHandlers->Success);
+ }
+ elseif($process->isSuccessful() && $unit->ExecutionPolicy->ExitHandlers->Error !== null)
+ {
+ $this->handleExit($package, $version, $unit->ExecutionPolicy->ExitHandlers->Error);
+ }
+ else
+ {
+ $this->handleExit($package, $version, $unit->ExecutionPolicy->ExitHandlers->Success, $process);
+ $this->handleExit($package, $version, $unit->ExecutionPolicy->ExitHandlers->Warning, $process);
+ $this->handleExit($package, $version, $unit->ExecutionPolicy->ExitHandlers->Error, $process);
+ }
+ }
+ }
+
+ /**
+ * Temporarily executes a
+ *
+ * @param Package $package
+ * @param string $unit_name
+ * @return void
+ * @throws AccessDeniedException
+ * @throws ExecutionUnitNotFoundException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws NoAvailableUnitsException
+ * @throws RunnerExecutionException
+ * @throws UnsupportedRunnerException
+ */
+ public function temporaryExecute(Package $package, string $unit_name): void
+ {
+ // First get the execution unit from the package.
+ $unit = $package->getExecutionUnit($unit_name);
+
+ // Get the required units
+ $required_units = [];
+ if($unit->ExecutionPolicy->ExitHandlers !== null)
+ {
+ $required_unit = $unit->ExecutionPolicy?->ExitHandlers?->Success?->Run;
+ if($required_unit !== null)
+ $required_units[] = $required_unit;
+
+ $required_unit = $unit->ExecutionPolicy?->ExitHandlers?->Warning?->Run;
+ if($required_unit !== null)
+ $required_units[] = $required_unit;
+
+ $required_unit = $unit->ExecutionPolicy?->ExitHandlers?->Error?->Run;
+ if($required_unit !== null)
+ $required_units = $required_unit;
+ }
+
+ // Install the units temporarily
+ $this->addUnit($package->Assembly->Package, $package->Assembly->Version, $unit, true);
+ foreach($required_units as $r_unit)
+ {
+ $this->addUnit($package->Assembly->Package, $package->Assembly->Version, $r_unit, true);
+ }
+
+ $this->executeUnit($package->Assembly->Package, $package->Assembly->Version, $unit_name);
+ $this->cleanTemporaryUnits();
+ }
+
+ /**
+ * Handles an exit handler object.
+ *
+ * If Process is Null and EndProcess is true, the method will end the process
+ * if Process is not Null the exit handler will only execute if the process' exit code is the same
+ *
+ * @param string $package
+ * @param string $version
+ * @param ExitHandle $exitHandle
+ * @param Process|null $process
+ * @return bool
+ * @throws AccessDeniedException
+ * @throws ExecutionUnitNotFoundException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws NoAvailableUnitsException
+ * @throws RunnerExecutionException
+ * @throws UnsupportedRunnerException
+ */
+ public function handleExit(string $package, string $version, ExitHandle $exitHandle, ?Process $process=null): bool
+ {
+ if($exitHandle->Message !== null)
+ Console::out($exitHandle->Message);
+
+ if($process !== null && !$exitHandle->EndProcess)
+ {
+ if($exitHandle->ExitCode !== $process->getExitCode())
+ return false;
+ }
+ elseif($exitHandle->EndProcess)
+ {
+ exit($exitHandle->ExitCode);
+ }
+
+ if($exitHandle->Run !== null)
+ {
+ $this->executeUnit($package, $version, $exitHandle->Run);
+ }
+
+ return true;
+ }
+
+ }
\ No newline at end of file
diff --git a/src/ncc/Managers/PackageLockManager.php b/src/ncc/Managers/PackageLockManager.php
new file mode 100644
index 0000000..4c7d6cb
--- /dev/null
+++ b/src/ncc/Managers/PackageLockManager.php
@@ -0,0 +1,152 @@
+PackageLockPath = PathFinder::getPackageLock(Scopes::System);
+
+ try
+ {
+ $this->load();
+ }
+ catch (PackageLockException $e)
+ {
+ unset($e);
+ }
+ }
+
+ /**
+ * Loads the PackageLock from the disk
+ *
+ * @return void
+ * @throws PackageLockException
+ */
+ public function load(): void
+ {
+ if(RuntimeCache::get($this->PackageLockPath) !== null)
+ {
+ $this->PackageLock = RuntimeCache::get($this->PackageLockPath);
+ return;
+ }
+
+ if(file_exists($this->PackageLockPath) && is_file($this->PackageLockPath))
+ {
+ try
+ {
+ Console::outDebug('reading package lock file');
+ $data = IO::fread($this->PackageLockPath);
+ if(strlen($data) > 0)
+ {
+ $this->PackageLock = PackageLock::fromArray(ZiProto::decode($data));
+ }
+ else
+ {
+ $this->PackageLock = new PackageLock();
+ }
+ }
+ catch(Exception $e)
+ {
+ throw new PackageLockException('The PackageLock file cannot be parsed', $e);
+ }
+ }
+ else
+ {
+ $this->PackageLock = new PackageLock();
+ }
+
+ RuntimeCache::set($this->PackageLockPath, $this->PackageLock);
+ }
+
+ /**
+ * Saves the PackageLock to disk
+ *
+ * @return void
+ * @throws AccessDeniedException
+ * @throws PackageLockException
+ */
+ public function save(): void
+ {
+ // Don't save something that isn't loaded lol
+ if($this->PackageLock == null)
+ return;
+
+ if(Resolver::resolveScope() !== Scopes::System)
+ throw new AccessDeniedException('Cannot write to PackageLock, insufficient permissions');
+
+ try
+ {
+ IO::fwrite($this->PackageLockPath, ZiProto::encode($this->PackageLock->toArray(true)), 0755);
+ RuntimeCache::set($this->PackageLockPath, $this->PackageLock);
+ }
+ catch(IOException $e)
+ {
+ throw new PackageLockException('Cannot save the package lock file to disk', $e);
+ }
+ }
+
+ /**
+ * Constructs the package lock file if it doesn't exist
+ *
+ * @return void
+ * @throws AccessDeniedException
+ * @throws PackageLockException
+ */
+ public function constructLockFile(): void
+ {
+ try
+ {
+ $this->load();
+ }
+ catch (PackageLockException $e)
+ {
+ unset($e);
+ $this->PackageLock = new PackageLock();
+ }
+
+ $this->save();
+ }
+
+ /**
+ * @return PackageLock|null
+ * @throws PackageLockException
+ */
+ public function getPackageLock(): ?PackageLock
+ {
+ if($this->PackageLock == null)
+ $this->load();
+ return $this->PackageLock;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Managers/PackageManager.php b/src/ncc/Managers/PackageManager.php
new file mode 100644
index 0000000..3719e2b
--- /dev/null
+++ b/src/ncc/Managers/PackageManager.php
@@ -0,0 +1,490 @@
+PackagesPath = PathFinder::getPackagesPath(Scopes::System);
+ $this->PackageLockManager = new PackageLockManager();
+ $this->PackageLockManager->load();
+ }
+
+ /**
+ * Installs a local package onto the system
+ *
+ * @param string $input
+ * @return string
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws InstallationException
+ * @throws PackageAlreadyInstalledException
+ * @throws PackageLockException
+ * @throws PackageParsingException
+ * @throws UnsupportedCompilerExtensionException
+ * @throws UnsupportedRunnerException
+ * @throws VersionNotFoundException
+ */
+ public function install(string $input): string
+ {
+ if(Resolver::resolveScope() !== Scopes::System)
+ throw new AccessDeniedException('Insufficient permission to install packages');
+
+ Console::outVerbose(sprintf('Installing %s', $input));
+ if(!file_exists($input) || !is_file($input) || !is_readable($input))
+ throw new FileNotFoundException('The specified file \'' . $input .' \' does not exist or is not readable.');
+
+ $package = Package::load($input);
+
+ if($this->getPackageVersion($package->Assembly->Package, $package->Assembly->Version) !== null)
+ throw new PackageAlreadyInstalledException('The package ' . $package->Assembly->Package . '==' . $package->Assembly->Version . ' is already installed');
+
+ $extension = $package->Header->CompilerExtension->Extension;
+ $installation_paths = new InstallationPaths($this->PackagesPath . DIRECTORY_SEPARATOR . $package->Assembly->Package . '==' . $package->Assembly->Version);
+ $installer = match ($extension) {
+ CompilerExtensions::PHP => new Installer($package),
+ default => throw new UnsupportedCompilerExtensionException('The compiler extension \'' . $extension . '\' is not supported'),
+ };
+ $execution_pointer_manager = new ExecutionPointerManager();
+ PackageCompiler::compilePackageConstants($package, [
+ ConstantReferences::Install => $installation_paths
+ ]);
+
+ Console::outVerbose(sprintf('Successfully parsed %s', $package->Assembly->Package));
+
+ if(Resolver::checkLogLevel(LogLevel::Debug, Main::getLogLevel()))
+ {
+ Console::outDebug(sprintf('installer.install_path: %s', $installation_paths->getInstallationPath()));
+ Console::outDebug(sprintf('installer.data_path: %s', $installation_paths->getDataPath()));
+ Console::outDebug(sprintf('installer.bin_path: %s', $installation_paths->getBinPath()));
+ Console::outDebug(sprintf('installer.src_path: %s', $installation_paths->getSourcePath()));
+
+ foreach($package->Assembly->toArray() as $prop => $value)
+ Console::outDebug(sprintf('assembly.%s: %s', $prop, ($value ?? 'n/a')));
+ foreach($package->Header->CompilerExtension->toArray() as $prop => $value)
+ Console::outDebug(sprintf('header.compiler.%s: %s', $prop, ($value ?? 'n/a')));
+ }
+
+ Console::out('Installing ' . $package->Assembly->Package);
+
+ // 4 For Directory Creation, preInstall, postInstall & initData methods
+ $steps = (4 + count($package->Components) + count ($package->Resources) + count ($package->ExecutionUnits));
+
+ // Include the Execution units
+ if($package->Installer?->PreInstall !== null)
+ $steps += count($package->Installer->PreInstall);
+ if($package->Installer?->PostInstall!== null)
+ $steps += count($package->Installer->PostInstall);
+
+ $current_steps = 0;
+ $filesystem = new Filesystem();
+
+ try
+ {
+ $filesystem->mkdir($installation_paths->getInstallationPath(), 0755);
+ $filesystem->mkdir($installation_paths->getBinPath(), 0755);
+ $filesystem->mkdir($installation_paths->getDataPath(), 0755);
+ $filesystem->mkdir($installation_paths->getSourcePath(), 0755);
+ $current_steps += 1;
+ Console::inlineProgressBar($current_steps, $steps);
+ }
+ catch(Exception $e)
+ {
+ throw new InstallationException('Error while creating directory, ' . $e->getMessage(), $e);
+ }
+
+ try
+ {
+ self::initData($package, $installation_paths);
+ Console::outDebug(sprintf('saving shadow package to %s', $installation_paths->getDataPath() . DIRECTORY_SEPARATOR . 'pkg'));
+ $package->save($installation_paths->getDataPath() . DIRECTORY_SEPARATOR . 'pkg');
+ $current_steps += 1;
+ Console::inlineProgressBar($current_steps, $steps);
+ }
+ catch(Exception $e)
+ {
+ throw new InstallationException('Cannot initialize package install, ' . $e->getMessage(), $e);
+ }
+
+ // Execute the pre-installation stage before the installation stage
+ try
+ {
+ $installer->preInstall($installation_paths);
+ $current_steps += 1;
+ Console::inlineProgressBar($current_steps, $steps);
+ }
+ catch (Exception $e)
+ {
+ throw new InstallationException('Pre installation stage failed, ' . $e->getMessage(), $e);
+ }
+
+ if($package->Installer?->PreInstall !== null && count($package->Installer->PreInstall) > 0)
+ {
+ foreach($package->Installer->PreInstall as $unit_name)
+ {
+ try
+ {
+ $execution_pointer_manager->temporaryExecute($package, $unit_name);
+ }
+ catch(Exception $e)
+ {
+ Console::outWarning('Cannot execute unit ' . $unit_name . ', ' . $e->getMessage());
+ }
+
+ $current_steps += 1;
+ Console::inlineProgressBar($current_steps, $steps);
+ }
+ }
+
+ // Process & Install the components
+ foreach($package->Components as $component)
+ {
+ Console::outDebug(sprintf('processing component %s (%s)', $component->Name, $component->DataType));
+
+ try
+ {
+ $data = $installer->processComponent($component);
+ if($data !== null)
+ {
+ $component_path = $installation_paths->getSourcePath() . DIRECTORY_SEPARATOR . $component->Name;
+ $component_dir = dirname($component_path);
+ if(!$filesystem->exists($component_dir))
+ $filesystem->mkdir($component_dir);
+ IO::fwrite($component_path, $data);
+ }
+ }
+ catch(Exception $e)
+ {
+ throw new InstallationException('Cannot process one or more components, ' . $e->getMessage(), $e);
+ }
+
+ $current_steps += 1;
+ Console::inlineProgressBar($current_steps, $steps);
+ }
+
+ // Process & Install the resources
+ foreach($package->Resources as $resource)
+ {
+ Console::outDebug(sprintf('processing resource %s', $resource->Name));
+
+ try
+ {
+ $data = $installer->processResource($resource);
+ if($data !== null)
+ {
+ $resource_path = $installation_paths->getSourcePath() . DIRECTORY_SEPARATOR . $resource->Name;
+ $resource_dir = dirname($resource_path);
+ if(!$filesystem->exists($resource_dir))
+ $filesystem->mkdir($resource_dir);
+ IO::fwrite($resource_path, $data);
+ }
+ }
+ catch(Exception $e)
+ {
+ throw new InstallationException('Cannot process one or more resources, ' . $e->getMessage(), $e);
+ }
+
+ $current_steps += 1;
+ Console::inlineProgressBar($current_steps, $steps);
+ }
+
+ // Install execution units
+ // TODO: Implement symlink support
+ if(count($package->ExecutionUnits) > 0)
+ {
+ $execution_pointer_manager = new ExecutionPointerManager();
+ $unit_paths = [];
+
+ foreach($package->ExecutionUnits as $executionUnit)
+ {
+ $execution_pointer_manager->addUnit($package->Assembly->Package, $package->Assembly->Version, $executionUnit);
+ $current_steps += 1;
+ Console::inlineProgressBar($current_steps, $steps);
+ }
+
+ IO::fwrite($installation_paths->getDataPath() . DIRECTORY_SEPARATOR . 'exec', ZiProto::encode($unit_paths));
+ }
+
+ // Execute the post-installation stage after the installation is complete
+ try
+ {
+ $installer->postInstall($installation_paths);
+ $current_steps += 1;
+ Console::inlineProgressBar($current_steps, $steps);
+ }
+ catch (Exception $e)
+ {
+ throw new InstallationException('Post installation stage failed, ' . $e->getMessage(), $e);
+ }
+
+ if($package->Installer?->PostInstall !== null && count($package->Installer->PostInstall) > 0)
+ {
+ foreach($package->Installer->PostInstall as $unit_name)
+ {
+ try
+ {
+ $execution_pointer_manager->temporaryExecute($package, $unit_name);
+ }
+ catch(Exception $e)
+ {
+ Console::outWarning('Cannot execute unit ' . $unit_name . ', ' . $e->getMessage());
+ }
+
+ $current_steps += 1;
+ Console::inlineProgressBar($current_steps, $steps);
+ }
+ }
+
+ $this->getPackageLockManager()->getPackageLock()->addPackage($package, $installation_paths->getInstallationPath());
+ $this->getPackageLockManager()->save();
+
+ return $package->Assembly->Package;
+ }
+
+ /**
+ * Returns an existing package entry, returns null if no such entry exists
+ *
+ * @param string $package
+ * @return PackageEntry|null
+ * @throws PackageLockException
+ * @throws PackageLockException
+ */
+ public function getPackage(string $package): ?PackageEntry
+ {
+ return $this->getPackageLockManager()->getPackageLock()->getPackage($package);
+ }
+
+ /**
+ * Returns an existing version entry, returns null if no such entry exists
+ *
+ * @param string $package
+ * @param string $version
+ * @return VersionEntry|null
+ * @throws VersionNotFoundException
+ * @throws PackageLockException
+ */
+ public function getPackageVersion(string $package, string $version): ?VersionEntry
+ {
+ return $this->getPackage($package)?->getVersion($version);
+ }
+
+ /**
+ * Returns the latest version of the package, or null if there is no entry
+ *
+ * @param string $package
+ * @return VersionEntry|null
+ * @throws VersionNotFoundException
+ * @throws PackageLockException
+ */
+ public function getLatestVersion(string $package): ?VersionEntry
+ {
+ return $this->getPackage($package)?->getVersion($this->getPackage($package)?->getLatestVersion());
+ }
+
+ /**
+ * Returns an array of all packages and their installed versions
+ *
+ * @return array
+ * @throws PackageLockException
+ * @throws PackageLockException
+ */
+ public function getInstalledPackages(): array
+ {
+ return $this->getPackageLockManager()->getPackageLock()->getPackages();
+ }
+
+ /**
+ * Uninstalls a package version
+ *
+ * @param string $package
+ * @param string $version
+ * @return void
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws PackageLockException
+ * @throws PackageNotFoundException
+ * @throws VersionNotFoundException
+ */
+ public function uninstallPackageVersion(string $package, string $version): void
+ {
+ if(Resolver::resolveScope() !== Scopes::System)
+ throw new AccessDeniedException('Insufficient permission to uninstall packages');
+
+ $version_entry = $this->getPackageVersion($package, $version);
+ if($version_entry == null)
+ throw new PackageNotFoundException(sprintf('The package %s==%s was not found', $package, $version));
+
+ Console::out(sprintf('Uninstalling %s==%s', $package, $version));
+ Console::outVerbose(sprintf('Removing package %s==%s from PackageLock', $package, $version));
+ if(!$this->getPackageLockManager()->getPackageLock()->removePackageVersion($package, $version))
+ Console::outDebug('warning: removing package from package lock failed');
+
+ $this->getPackageLockManager()->save();
+
+ Console::outVerbose('Removing package files');
+ $scanner = new DirectoryScanner();
+ $filesystem = new Filesystem();
+
+ /** @var SplFileInfo $item */
+ /** @noinspection PhpRedundantOptionalArgumentInspection */
+ foreach($scanner($version_entry->Location, true) as $item)
+ {
+ if(is_file($item->getPath()))
+ {
+ Console::outDebug(sprintf('deleting %s', $item->getPath()));
+ $filesystem->remove($item->getPath());
+ }
+ }
+
+ $filesystem->remove($version_entry->Location);
+
+ if($version_entry->ExecutionUnits !== null && count($version_entry->ExecutionUnits) > 0)
+ {
+ Console::outVerbose('Uninstalling execution units');
+
+ $execution_pointer_manager = new ExecutionPointerManager();
+ foreach($version_entry->ExecutionUnits as $executionUnit)
+ {
+ if(!$execution_pointer_manager->removeUnit($package, $version, $executionUnit->ExecutionPolicy->Name))
+ Console::outDebug(sprintf('warning: removing execution unit %s failed', $executionUnit->ExecutionPolicy->Name));
+ }
+ }
+ }
+
+ /**
+ * Uninstalls all versions of a package
+ *
+ * @param string $package
+ * @return void
+ * @throws AccessDeniedException
+ * @throws PackageLockException
+ * @throws PackageNotFoundException
+ * @throws VersionNotFoundException
+ */
+ public function uninstallPackage(string $package): void
+ {
+ if(Resolver::resolveScope() !== Scopes::System)
+ throw new AccessDeniedException('Insufficient permission to uninstall packages');
+
+ $package_entry = $this->getPackage($package);
+ if($package_entry == null)
+ throw new PackageNotFoundException(sprintf('The package %s was not found', $package));
+
+ foreach($package_entry->getVersions() as $version)
+ {
+ $version_entry = $package_entry->getVersion($version);
+ try
+ {
+ $this->uninstallPackageVersion($package, $version_entry->Version);
+ }
+ catch(Exception $e)
+ {
+ Console::outDebug(sprintf('warning: unable to uninstall package %s==%s, %s (%s)', $package, $version_entry->Version, $e->getMessage(), $e->getCode()));
+ }
+ }
+ }
+
+ /**
+ * @param Package $package
+ * @param InstallationPaths $paths
+ * @throws InstallationException
+ */
+ private static function initData(Package $package, InstallationPaths $paths): void
+ {
+ // Create data files
+ $dependencies = [];
+ foreach($package->Dependencies as $dependency)
+ {
+ $dependencies[] = $dependency->toArray(true);
+ }
+
+ $data_files = [
+ $paths->getDataPath() . DIRECTORY_SEPARATOR . 'assembly' =>
+ ZiProto::encode($package->Assembly->toArray(true)),
+ $paths->getDataPath() . DIRECTORY_SEPARATOR . 'ext' =>
+ ZiProto::encode($package->Header->CompilerExtension->toArray(true)),
+ $paths->getDataPath() . DIRECTORY_SEPARATOR . 'const' =>
+ ZiProto::encode($package->Header->RuntimeConstants),
+ $paths->getDataPath() . DIRECTORY_SEPARATOR . 'dependencies' =>
+ ZiProto::encode($dependencies),
+ ];
+
+ foreach($data_files as $file => $data)
+ {
+ try
+ {
+ IO::fwrite($file, $data);
+ }
+ catch (IOException $e)
+ {
+ throw new InstallationException('Cannot write to file \'' . $file . '\', ' . $e->getMessage(), $e);
+ }
+ }
+ }
+
+ /**
+ * @return PackageLockManager|null
+ */
+ private function getPackageLockManager(): ?PackageLockManager
+ {
+ if($this->PackageLockManager == null)
+ {
+ $this->PackageLockManager = new PackageLockManager();
+ }
+
+ return $this->PackageLockManager;
+ }
+
+ }
\ No newline at end of file
diff --git a/src/ncc/Managers/ProjectManager.php b/src/ncc/Managers/ProjectManager.php
index a9d0e42..286910b 100644
--- a/src/ncc/Managers/ProjectManager.php
+++ b/src/ncc/Managers/ProjectManager.php
@@ -1,12 +1,26 @@
SelectedDirectory = $selected_directory;
$this->ProjectFilePath = null;
$this->ProjectPath = null;
- $this->detectProjectPath();
- }
-
- /**
- * Attempts to resolve the project path from the selected directory
- * Returns false if the selected directory is not a proper project or an initialized project
- *
- * @return void
- */
- private function detectProjectPath(): void
- {
- $selected_directory = $this->SelectedDirectory;
-
// Auto-resolve the trailing slash
/** @noinspection PhpStrFunctionsInspection */
- if(substr($selected_directory, -1) !== '/')
+ if(substr($path, -1) !== '/')
{
- $selected_directory .= DIRECTORY_SEPARATOR;
+ $path .= DIRECTORY_SEPARATOR;
}
// Detect if the folder exists or not
- if(!file_exists($selected_directory) || !is_dir($selected_directory))
+ if(!file_exists($path) || !is_dir($path))
{
- return;
+ throw new DirectoryNotFoundException('The given directory \'' . $path .'\' does not exist');
}
- $this->ProjectPath = $selected_directory;
- $this->ProjectFilePath = $selected_directory . 'project.json';
+ $this->ProjectPath = $path;
+ $this->ProjectFilePath = $path . 'project.json';
+
+ if(file_exists($this->ProjectFilePath))
+ $this->load();
}
/**
* Initializes the project structure
*
- * // TODO: Correct the unexpected path behavior issue when initializing a project
- *
* @param Compiler $compiler
* @param string $name
* @param string $package
- * @param string $src
+ * @param string|null $src
* @param array $options
* @throws InvalidPackageNameException
* @throws InvalidProjectNameException
* @throws MalformedJsonException
* @throws ProjectAlreadyExistsException
*/
- public function initializeProject(Compiler $compiler, string $name, string $package, string $src, array $options=[]): void
+ public function initializeProject(Compiler $compiler, string $name, string $package, ?string $src=null, array $options=[]): void
{
// Validate the project information first
if(!Validate::packageName($package))
@@ -109,41 +116,43 @@
throw new ProjectAlreadyExistsException('A project has already been initialized in \'' . $this->ProjectPath . DIRECTORY_SEPARATOR . 'project.json' . '\'');
}
- $Project = new ProjectConfiguration();
+ $this->ProjectConfiguration = new ProjectConfiguration();
// Set the compiler information
- $Project->Project->Compiler = $compiler;
+ $this->ProjectConfiguration->Project->Compiler = $compiler;
// Set the assembly information
- $Project->Assembly->Name = $name;
- $Project->Assembly->Package = $package;
- $Project->Assembly->Version = '1.0.0';
- $Project->Assembly->UUID = Uuid::v1()->toRfc4122();
+ $this->ProjectConfiguration->Assembly->Name = $name;
+ $this->ProjectConfiguration->Assembly->Package = $package;
+ $this->ProjectConfiguration->Assembly->Version = '1.0.0';
+ $this->ProjectConfiguration->Assembly->UUID = Uuid::v1()->toRfc4122();
// Set the build information
- $Project->Build->SourcePath = $src;
- $Project->Build->DefaultConfiguration = 'debug';
+ $this->ProjectConfiguration->Build->SourcePath = $src;
+ if($this->ProjectConfiguration->Build->SourcePath == null)
+ $this->ProjectConfiguration->Build->SourcePath = $this->ProjectPath;
+ $this->ProjectConfiguration->Build->DefaultConfiguration = 'debug';
// Assembly constants if the program wishes to check for this
- $Project->Build->DefineConstants['ASSEMBLY_NAME'] = '%ASSEMBLY.NAME%';
- $Project->Build->DefineConstants['ASSEMBLY_PACKAGE'] = '%ASSEMBLY.PACKAGE%';
- $Project->Build->DefineConstants['ASSEMBLY_VERSION'] = '%ASSEMBLY.VERSION%';
- $Project->Build->DefineConstants['ASSEMBLY_UID'] = '%ASSEMBLY.UID%';
+ $this->ProjectConfiguration->Build->DefineConstants['ASSEMBLY_NAME'] = '%ASSEMBLY.NAME%';
+ $this->ProjectConfiguration->Build->DefineConstants['ASSEMBLY_PACKAGE'] = '%ASSEMBLY.PACKAGE%';
+ $this->ProjectConfiguration->Build->DefineConstants['ASSEMBLY_VERSION'] = '%ASSEMBLY.VERSION%';
+ $this->ProjectConfiguration->Build->DefineConstants['ASSEMBLY_UID'] = '%ASSEMBLY.UID%';
// Generate configurations
$DebugConfiguration = new ProjectConfiguration\BuildConfiguration();
$DebugConfiguration->Name = 'debug';
$DebugConfiguration->OutputPath = 'build/debug';
$DebugConfiguration->DefineConstants["DEBUG"] = '1'; // Debugging constant if the program wishes to check for this
- $Project->Build->Configurations[] = $DebugConfiguration;
+ $this->ProjectConfiguration->Build->Configurations[] = $DebugConfiguration;
$ReleaseConfiguration = new ProjectConfiguration\BuildConfiguration();
$ReleaseConfiguration->Name = 'release';
$ReleaseConfiguration->OutputPath = 'build/release';
$ReleaseConfiguration->DefineConstants["DEBUG"] = '0'; // Debugging constant if the program wishes to check for this
- $Project->Build->Configurations[] = $ReleaseConfiguration;
+ $this->ProjectConfiguration->Build->Configurations[] = $ReleaseConfiguration;
// Finally create project.json
- $Project->toFile($this->ProjectPath . DIRECTORY_SEPARATOR . 'project.json');
+ $this->ProjectConfiguration->toFile($this->ProjectPath . DIRECTORY_SEPARATOR . 'project.json');
// And create the project directory for additional assets/resources
$Folders = [
@@ -166,15 +175,59 @@
switch($option)
{
case InitializeProjectOptions::CREATE_SOURCE_DIRECTORY:
- if(!file_exists($this->ProjectPath . DIRECTORY_SEPARATOR . 'src'))
+ if(!file_exists($this->ProjectConfiguration->Build->SourcePath))
{
- mkdir($this->ProjectPath . DIRECTORY_SEPARATOR . 'src');
+ mkdir($this->ProjectConfiguration->Build->SourcePath);
}
break;
}
}
}
+ /**
+ * Determines if a project configuration is loaded or not
+ *
+ * @return bool
+ */
+ public function projectLoaded(): bool
+ {
+ if($this->ProjectConfiguration == null)
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Attempts to load the project configuration
+ *
+ * @return void
+ * @throws MalformedJsonException
+ * @throws ProjectConfigurationNotFoundException
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public function load()
+ {
+ if(!file_exists($this->ProjectFilePath) && !is_file($this->ProjectFilePath))
+ throw new ProjectConfigurationNotFoundException('The project configuration file \'' . $this->ProjectFilePath . '\' was not found');
+
+ $this->ProjectConfiguration = ProjectConfiguration::fromFile($this->ProjectFilePath);
+ }
+
+ /**
+ * Saves the project configuration
+ *
+ * @return void
+ * @throws MalformedJsonException
+ */
+ public function save()
+ {
+ if(!$this->projectLoaded())
+ return;
+ $this->ProjectConfiguration->toFile($this->ProjectFilePath);
+ }
+
/**
* @return string|null
*/
@@ -182,4 +235,49 @@
{
return $this->ProjectFilePath;
}
+
+ /**
+ * @return ProjectConfiguration|null
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws MalformedJsonException
+ * @throws ProjectConfigurationNotFoundException
+ */
+ public function getProjectConfiguration(): ?ProjectConfiguration
+ {
+ if($this->ProjectConfiguration == null)
+ $this->load();
+ return $this->ProjectConfiguration;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getProjectPath(): ?string
+ {
+ return $this->ProjectPath;
+ }
+
+
+ /**
+ * Compiles the project into a package
+ *
+ * @param string $build_configuration
+ * @return string
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws MalformedJsonException
+ * @throws ProjectConfigurationNotFoundException
+ * @throws BuildConfigurationNotFoundException
+ * @throws BuildException
+ * @throws PackagePreparationFailedException
+ * @throws UnsupportedCompilerExtensionException
+ * @throws UnsupportedRunnerException
+ */
+ public function build(string $build_configuration=BuildConfigurationValues::DefaultConfiguration): string
+ {
+ return PackageCompiler::compile($this, $build_configuration);
+ }
}
\ No newline at end of file
diff --git a/src/ncc/Objects/ExecutionPointers.php b/src/ncc/Objects/ExecutionPointers.php
new file mode 100644
index 0000000..9181c03
--- /dev/null
+++ b/src/ncc/Objects/ExecutionPointers.php
@@ -0,0 +1,171 @@
+Package = $package;
+ $this->Version = $version;
+ $this->Pointers = [];
+ }
+
+ /**
+ * Adds an Execution Unit as a pointer
+ *
+ * @param ExecutionUnit $unit
+ * @param bool $overwrite
+ * @return bool
+ */
+ public function addUnit(ExecutionUnit $unit, string $bin_file, bool $overwrite=true): bool
+ {
+ if(Validate::exceedsPathLength($bin_file))
+ return false;
+
+ if(!file_exists($bin_file))
+ throw new FileNotFoundException('The file ' . $unit->Data . ' does not exist, cannot add unit \'' . $unit->ExecutionPolicy->Name . '\'');
+
+ if($overwrite)
+ {
+ $this->deleteUnit($unit->ExecutionPolicy->Name);
+ }
+ elseif($this->getUnit($unit->ExecutionPolicy->Name) !== null)
+ {
+ return false;
+ }
+
+ $this->Pointers[] = new ExecutionPointer($unit, $bin_file);
+ return true;
+ }
+
+ /**
+ * Deletes an existing unit from execution pointers
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function deleteUnit(string $name): bool
+ {
+ $unit = $this->getUnit($name);
+ if($unit == null)
+ return false;
+
+ $new_pointers = [];
+ foreach($this->Pointers as $pointer)
+ {
+ if($pointer->ExecutionPolicy->Name !== $name)
+ $new_pointers[] = $pointer;
+ }
+
+ $this->Pointers = $new_pointers;
+ return true;
+ }
+
+ /**
+ * Returns an existing unit from the pointers
+ *
+ * @param string $name
+ * @return ExecutionPointer|null
+ */
+ public function getUnit(string $name): ?ExecutionPointer
+ {
+ foreach($this->Pointers as $pointer)
+ {
+ if($pointer->ExecutionPolicy->Name == $name)
+ return $pointer;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns an array of execution pointers that are currently configured
+ *
+ * @return array|ExecutionPointer[]
+ */
+ public function getPointers(): array
+ {
+ return $this->Pointers;
+ }
+
+ /**
+ * Returns the version of the package that uses these execution policies.
+ *
+ * @return string
+ */
+ public function getVersion(): string
+ {
+ return $this->Version;
+ }
+
+ /**
+ * Returns the name of the package that uses these execution policies
+ *
+ * @return string
+ */
+ public function getPackage(): string
+ {
+ return $this->Package;
+ }
+
+ /**
+ * Returns an array representation of the object
+ *
+ * @param bool $bytecode
+ * @return array
+ */
+ public function toArray(bool $bytecode=false): array
+ {
+ $pointers = [];
+ foreach($this->Pointers as $pointer)
+ {
+ $pointers[] = $pointer->toArray($bytecode);
+ }
+ return $pointers;
+ }
+
+ /**
+ * Constructs object from an array representation
+ *
+ * @param array $data
+ * @return ExecutionPointers
+ */
+ public static function fromArray(array $data): self
+ {
+ $object = new self();
+
+ foreach($data as $datum)
+ {
+ $object->Pointers[] = ExecutionPointer::fromArray($datum);
+ }
+
+ return $object;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Objects/ExecutionPointers/ExecutionPointer.php b/src/ncc/Objects/ExecutionPointers/ExecutionPointer.php
new file mode 100644
index 0000000..9a5747e
--- /dev/null
+++ b/src/ncc/Objects/ExecutionPointers/ExecutionPointer.php
@@ -0,0 +1,78 @@
+ID = $unit->getID();
+ $this->ExecutionPolicy = $unit->ExecutionPolicy;
+ $this->FilePointer = $bin_file;
+ }
+
+ /**
+ * Returns an array representation of the object
+ *
+ * @param bool $bytecode
+ * @return array
+ */
+ public function toArray(bool $bytecode=false): array
+ {
+ return [
+ ($bytecode ? Functions::cbc('id') : 'id') => $this->ID,
+ ($bytecode ? Functions::cbc('execution_policy') : 'execution_policy') => $this->ExecutionPolicy->toArray($bytecode),
+ ($bytecode ? Functions::cbc('file_pointer') : 'file_pointer') => $this->FilePointer,
+ ];
+ }
+
+ /**
+ * Constructs object from an array representation
+ *
+ * @param array $data
+ * @return ExecutionPointer
+ */
+ public static function fromArray(array $data): self
+ {
+ $object = new self();
+
+ $object->ID = Functions::array_bc($data, 'id');
+ $object->ExecutionPolicy = Functions::array_bc($data, 'execution_policy');
+ $object->FilePointer = Functions::array_bc($data, 'file_pointer');
+
+ return $object;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Objects/InstallationPaths.php b/src/ncc/Objects/InstallationPaths.php
new file mode 100644
index 0000000..d7c745a
--- /dev/null
+++ b/src/ncc/Objects/InstallationPaths.php
@@ -0,0 +1,61 @@
+InstallationPath = $installation_path;
+ }
+
+ /**
+ * Returns the data path where NCC's metadata & runtime information is stored
+ *
+ * @return string
+ */
+ public function getDataPath(): string
+ {
+ return $this->InstallationPath . DIRECTORY_SEPARATOR . 'ncc';
+ }
+
+ /**
+ * Returns the source path for where the package resides
+ *
+ * @return string
+ */
+ public function getSourcePath(): string
+ {
+ return $this->InstallationPath . DIRECTORY_SEPARATOR . 'src';
+ }
+
+ /**
+ * Returns the path for where executables are located
+ *
+ * @return string
+ */
+ public function getBinPath(): string
+ {
+ return $this->InstallationPath . DIRECTORY_SEPARATOR . 'bin';
+ }
+
+ /**
+ * @return string
+ */
+ public function getInstallationPath(): string
+ {
+ return $this->InstallationPath;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Objects/NccVersionInformation/Component.php b/src/ncc/Objects/NccVersionInformation/Component.php
index 68b7568..33292ad 100644
--- a/src/ncc/Objects/NccVersionInformation/Component.php
+++ b/src/ncc/Objects/NccVersionInformation/Component.php
@@ -1,8 +1,14 @@
Vendor . DIRECTORY_SEPARATOR . $this->PackageName . DIRECTORY_SEPARATOR;
- if(file_exists($component_path . 'VERSION') == false)
- {
+ if(!file_exists($component_path . 'VERSION'))
throw new ComponentVersionNotFoundException('The file \'' . $component_path . 'VERSION' . '\' does not exist');
- }
- return file_get_contents($component_path . 'VERSION');
+ return IO::fread($component_path . 'VERSION');
}
/**
diff --git a/src/ncc/Objects/Package.php b/src/ncc/Objects/Package.php
index 78bf4dc..4dad3a6 100644
--- a/src/ncc/Objects/Package.php
+++ b/src/ncc/Objects/Package.php
@@ -4,17 +4,24 @@
namespace ncc\Objects;
+ use Exception;
+ use ncc\Abstracts\EncoderType;
+ use ncc\Abstracts\PackageStructureVersions;
+ use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\InvalidPackageException;
use ncc\Exceptions\InvalidProjectConfigurationException;
+ use ncc\Exceptions\IOException;
+ use ncc\Exceptions\PackageParsingException;
use ncc\Objects\Package\Component;
+ use ncc\Objects\Package\ExecutionUnit;
use ncc\Objects\Package\Header;
use ncc\Objects\Package\Installer;
use ncc\Objects\Package\MagicBytes;
- use ncc\Objects\Package\MainExecutionPolicy;
use ncc\Objects\Package\Resource;
use ncc\Objects\ProjectConfiguration\Assembly;
use ncc\Objects\ProjectConfiguration\Dependency;
use ncc\Utilities\Functions;
+ use ncc\Utilities\IO;
use ncc\ZiProto\ZiProto;
class Package
@@ -50,7 +57,7 @@
/**
* The Main Execution Policy object for the package if the package is an executable package.
*
- * @var MainExecutionPolicy|null
+ * @var string|null
*/
public $MainExecutionPolicy;
@@ -61,6 +68,13 @@
*/
public $Installer;
+ /**
+ * An array of execution units defined in the package
+ *
+ * @var ExecutionUnit[]
+ */
+ public $ExecutionUnits;
+
/**
* An array of resources that the package depends on
*
@@ -83,6 +97,7 @@
$this->MagicBytes = new MagicBytes();
$this->Header = new Header();
$this->Assembly = new Assembly();
+ $this->ExecutionUnits = [];
$this->Components = [];
$this->Dependencies = [];
$this->Resources = [];
@@ -126,16 +141,141 @@
return true;
}
+ /**
+ * Attempts to find the execution unit with the given name
+ *
+ * @param string $name
+ * @return ExecutionUnit|null
+ */
+ public function getExecutionUnit(string $name): ?ExecutionUnit
+ {
+ foreach($this->ExecutionUnits as $unit)
+ {
+ if($unit->ExecutionPolicy->Name == $name)
+ return $unit;
+ }
+
+ return null;
+ }
+
/**
* Writes the package contents to disk
*
* @param string $output_path
* @return void
+ * @throws IOException
*/
public function save(string $output_path): void
{
$package_contents = $this->MagicBytes->toString() . ZiProto::encode($this->toArray(true));
- file_put_contents($output_path, $package_contents);
+ IO::fwrite($output_path, $package_contents, 0777);
+ }
+
+ /**
+ * Attempts to parse the specified package path and returns the object representation
+ * of the package, including with the MagicBytes representation that is in the
+ * file headers.
+ *
+ * @param string $path
+ * @return Package
+ * @throws FileNotFoundException
+ * @throws PackageParsingException
+ */
+ public static function load(string $path): Package
+ {
+ if(!file_exists($path) || !is_file($path) || !is_readable($path))
+ {
+ throw new FileNotFoundException('The file ' . $path . ' does not exist or is not readable');
+ }
+
+ $handle = fopen($path, "rb");
+ $header = fread($handle, 256); // Read the first 256 bytes of the file
+ fclose($handle);
+
+ if(!strtoupper(substr($header, 0, 11)) == 'NCC_PACKAGE')
+ throw new PackageParsingException('The package \'' . $path . '\' does not appear to be a valid NCC Package (Missing Header)');
+
+ // Extract the package structure version
+ $package_structure_version = strtoupper(substr($header, 11, 3));
+
+ if(!in_array($package_structure_version, PackageStructureVersions::ALL))
+ throw new PackageParsingException('The package \'' . $path . '\' has a package structure version of ' . $package_structure_version . ' which is not supported by this version NCC');
+
+ // Extract the package encoding type and package type
+ $encoding_header = strtoupper(substr($header, 14, 5));
+ $encoding_type = substr($encoding_header, 0, 3);
+ $package_type = substr($encoding_header, 3, 2);
+
+ $magic_bytes = new MagicBytes();
+ $magic_bytes->PackageStructureVersion = $package_structure_version;
+
+ // Determine the encoding type
+ switch($encoding_type)
+ {
+ case '300':
+ $magic_bytes->Encoder = EncoderType::ZiProto;
+ $magic_bytes->IsCompressed = false;
+ $magic_bytes->IsEncrypted = false;
+ break;
+
+ case '301':
+ $magic_bytes->Encoder = EncoderType::ZiProto;
+ $magic_bytes->IsCompressed = true;
+ $magic_bytes->IsEncrypted = false;
+ break;
+
+ case '310':
+ $magic_bytes->Encoder = EncoderType::ZiProto;
+ $magic_bytes->IsCompressed = false;
+ $magic_bytes->IsEncrypted = true;
+ break;
+
+ case '311':
+ $magic_bytes->Encoder = EncoderType::ZiProto;
+ $magic_bytes->IsCompressed = true;
+ $magic_bytes->IsEncrypted = true;
+ break;
+
+ default:
+ throw new PackageParsingException('Cannot determine the encoding type for the package \'' . $path . '\' (Got ' . $encoding_type . ')');
+ }
+
+ // Determine the package type
+ switch($package_type)
+ {
+ case '40':
+ $magic_bytes->IsInstallable = true;
+ $magic_bytes->IsExecutable = false;
+ break;
+
+ case '41':
+ $magic_bytes->IsInstallable = false;
+ $magic_bytes->IsExecutable = true;
+ break;
+
+ case '42':
+ $magic_bytes->IsInstallable = true;
+ $magic_bytes->IsExecutable = true;
+ break;
+
+ default:
+ throw new PackageParsingException('Cannot determine the package type for the package \'' . $path . '\' (Got ' . $package_type . ')');
+ }
+
+ // TODO: Implement encryption and compression parsing
+
+ // Assuming all is good, load the entire fire into memory and parse its contents
+ try
+ {
+ $package = Package::fromArray(ZiProto::decode(substr(IO::fread($path), strlen($magic_bytes->toString()))));
+ }
+ catch(Exception $e)
+ {
+ throw new PackageParsingException('Cannot decode the contents of the package \'' . $path . '\', invalid encoding or the package is corrupted, ' . $e->getMessage(), $e);
+ }
+
+ $package->MagicBytes = $magic_bytes;
+ return $package;
}
/**
@@ -161,13 +301,17 @@
foreach($this->Resources as $resource)
$_resources[] = $resource->toArray($bytecode);
+ $_execution_units = [];
+ foreach($this->ExecutionUnits as $unit)
+ $_execution_units[] = $unit->toArray($bytecode);
return [
- ($bytecode ? Functions::cbc('header') : 'header') => $this->Header?->toArray($bytecode),
- ($bytecode ? Functions::cbc('assembly') : 'assembly') => $this->Assembly?->toArray($bytecode),
+ ($bytecode ? Functions::cbc('header') : 'header') => $this?->Header?->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->MainExecutionPolicy?->toArray($bytecode),
- ($bytecode ? Functions::cbc('installer') : 'installer') => $this->Installer?->toArray($bytecode),
+ ($bytecode ? Functions::cbc('main_execution_policy') : 'main_execution_policy') => $this?->MainExecutionPolicy,
+ ($bytecode ? Functions::cbc('installer') : 'installer') => $this?->Installer?->toArray($bytecode),
+ ($bytecode ? Functions::cbc('execution_units') : 'execution_units') => $_execution_units,
($bytecode ? Functions::cbc('resources') : 'resources') => $_resources,
($bytecode ? Functions::cbc('components') : 'components') => $_components
];
@@ -190,8 +334,6 @@
$object->Assembly = Assembly::fromArray($object->Assembly);
$object->MainExecutionPolicy = Functions::array_bc($data, 'main_execution_policy');
- if($object->MainExecutionPolicy !== null)
- $object->MainExecutionPolicy = MainExecutionPolicy::fromArray($object->MainExecutionPolicy);
$object->Installer = Functions::array_bc($data, 'installer');
if($object->Installer !== null)
@@ -224,6 +366,15 @@
}
}
+ $_execution_units = Functions::array_bc($data, 'execution_units');
+ if($_execution_units !== null)
+ {
+ foreach($_execution_units as $unit)
+ {
+ $object->ExecutionUnits[] = ExecutionUnit::fromArray($unit);
+ }
+ }
+
return $object;
}
}
\ No newline at end of file
diff --git a/src/ncc/Objects/Package/Component.php b/src/ncc/Objects/Package/Component.php
index 75406f7..b48708b 100644
--- a/src/ncc/Objects/Package/Component.php
+++ b/src/ncc/Objects/Package/Component.php
@@ -1,5 +1,7 @@
Checksum === null)
- return false;
+ return true; // Return true if the checksum is empty
if($this->Data === null)
- return false;
+ return true; // Return true if the data is null
- if(hash('sha1', $this->Data) !== $this->Checksum)
- return false;
+ if(hash('sha1', $this->Data, true) !== $this->Checksum)
+ return false; // Return false if the checksum failed
return true;
}
+ /**
+ * Updates the checksum of the resource
+ *
+ * @return void
+ */
+ public function updateChecksum(): void
+ {
+ $this->Checksum = null;
+
+ if(gettype($this->Data) == 'string')
+ {
+ $this->Checksum = hash('sha1', $this->Data, true);
+ }
+ }
+
/**
* Returns an array representation of the component.
*
diff --git a/src/ncc/Objects/Package/ExecutionUnit.php b/src/ncc/Objects/Package/ExecutionUnit.php
new file mode 100644
index 0000000..75940cc
--- /dev/null
+++ b/src/ncc/Objects/Package/ExecutionUnit.php
@@ -0,0 +1,71 @@
+ $this->ExecutionPolicy->toArray($bytecode),
+ ($bytecode ? Functions::cbc('data') : 'data') => $this->Data,
+ ];
+ }
+
+ /**
+ * Constructs object from an array representation
+ *
+ * @param array $data
+ * @return static
+ */
+ public static function fromArray(array $data): self
+ {
+ $object = new self();
+
+ $object->ExecutionPolicy = Functions::array_bc($data, 'execution_policy');
+ $object->Data = Functions::array_bc($data, 'data');
+
+ return $object;
+ }
+
+ /**
+ * @return string
+ */
+ public function getID(): string
+ {
+ if($this->ID == null)
+ $this->ID = hash('sha1', $this->ExecutionPolicy->Name);
+ return $this->ID;
+ }
+
+ }
\ No newline at end of file
diff --git a/src/ncc/Objects/Package/Header.php b/src/ncc/Objects/Package/Header.php
index e090bc5..16a627e 100644
--- a/src/ncc/Objects/Package/Header.php
+++ b/src/ncc/Objects/Package/Header.php
@@ -68,6 +68,9 @@
$object->RuntimeConstants = Functions::array_bc($data, 'runtime_constants');
$object->CompilerVersion = Functions::array_bc($data, 'compiler_version');
+ if($object->CompilerExtension !== null)
+ $object->CompilerExtension = Compiler::fromArray($object->CompilerExtension);
+
return $object;
}
}
\ No newline at end of file
diff --git a/src/ncc/Objects/Package/Installer.php b/src/ncc/Objects/Package/Installer.php
index cbeb8b0..c368aac 100644
--- a/src/ncc/Objects/Package/Installer.php
+++ b/src/ncc/Objects/Package/Installer.php
@@ -1,18 +1,81 @@
PreInstall == null && $this->PostInstall == null &&
+ $this->PreUninstall == null && $this->PostUninstall == null &&
+ $this->PreUpdate == null && $this->PostUpdate == null
+ )
+ {
+ return null;
+ }
+
+ return [
+ ($bytecode ? Functions::cbc('pre_install') : 'pre_install') => ($this->PreInstall == null ? null : $this->PreInstall),
+ ($bytecode ? Functions::cbc('post_install') : 'post_install') => ($this->PostInstall == null ? null : $this->PostInstall),
+ ($bytecode ? Functions::cbc('pre_uninstall') : 'pre_uninstall') => ($this->PreUninstall == null ? null : $this->PreUninstall),
+ ($bytecode? Functions::cbc('post_uninstall') : 'post_uninstall') => ($this->PostUninstall == null ? null : $this->PostUninstall),
+ ($bytecode? Functions::cbc('pre_update') : 'pre_update') => ($this->PreUpdate == null ? null : $this->PreUpdate),
+ ($bytecode? Functions::cbc('post_update') : 'post_update') => ($this->PostUpdate == null ? null : $this->PostUpdate)
+ ];
}
/**
@@ -20,11 +83,19 @@
*
* @param array $data
* @return Installer
+ * @noinspection DuplicatedCode
*/
public static function fromArray(array $data): self
{
$object = new self();
+ $object->PreInstall = Functions::array_bc($data, 'pre_install');
+ $object->PostInstall = Functions::array_bc($data, 'post_install');
+ $object->PreUninstall = Functions::array_bc($data, 'pre_uninstall');
+ $object->PostUninstall = Functions::array_bc($data, 'post_uninstall');
+ $object->PreUpdate = Functions::array_bc($data, 'pre_update');
+ $object->PostUpdate = Functions::array_bc($data, 'post_update');
+
return $object;
}
}
\ No newline at end of file
diff --git a/src/ncc/Objects/Package/MagicBytes.php b/src/ncc/Objects/Package/MagicBytes.php
index df54f71..3e86446 100644
--- a/src/ncc/Objects/Package/MagicBytes.php
+++ b/src/ncc/Objects/Package/MagicBytes.php
@@ -147,6 +147,12 @@
// NCC_PACKAGE1.030140
$magic_bytes .= '40';
}
+ else
+ {
+ // If no type is specified, default to installable only
+ // NCC_PACKAGE1.030140
+ $magic_bytes .= '40';
+ }
return $magic_bytes;
}
diff --git a/src/ncc/Objects/Package/MainExecutionPolicy.php b/src/ncc/Objects/Package/MainExecutionPolicy.php
deleted file mode 100644
index c005578..0000000
--- a/src/ncc/Objects/Package/MainExecutionPolicy.php
+++ /dev/null
@@ -1,30 +0,0 @@
-Data === null)
return false;
- if(hash('sha1', $this->Data) !== $this->Checksum)
+ if(hash('sha1', $this->Data, true) !== $this->Checksum)
return false;
return true;
}
+ /**
+ * Updates the checksum of the resource
+ *
+ * @return void
+ */
+ public function updateChecksum(): void
+ {
+ $this->Checksum = null;
+
+ if(gettype($this->Data) == 'string')
+ {
+ $this->Checksum = hash('sha1', $this->Data, true);
+ }
+ }
+
/**
* Returns an array representation of the resource.
*
diff --git a/src/ncc/Objects/PackageLock.php b/src/ncc/Objects/PackageLock.php
new file mode 100644
index 0000000..3e4d055
--- /dev/null
+++ b/src/ncc/Objects/PackageLock.php
@@ -0,0 +1,194 @@
+PackageLockVersion = Versions::PackageLockVersion;
+ $this->Packages = [];
+ }
+
+ /**
+ * Updates the version and timestamp
+ *
+ * @return void
+ */
+ private function update(): void
+ {
+ $this->PackageLockVersion = Versions::PackageLockVersion;
+ $this->LastUpdatedTimestamp = time();
+ }
+
+ /**
+ * @param Package $package
+ * @param string $install_path
+ * @return void
+ */
+ public function addPackage(Package $package, string $install_path): void
+ {
+ if(!isset($this->Packages[$package->Assembly->Package]))
+ {
+ $package_entry = new PackageEntry();
+ $package_entry->addVersion($package, $install_path, true);
+ $package_entry->Name = $package->Assembly->Package;
+ $this->Packages[$package->Assembly->Package] = $package_entry;
+ $this->update();
+
+ return;
+ }
+
+ $this->Packages[$package->Assembly->Package]->addVersion($package, true);
+ $this->update();
+ }
+
+ /**
+ * Removes a package version entry, removes the entire entry if there are no installed versions
+ *
+ * @param string $package
+ * @param string $version
+ * @return bool
+ */
+ public function removePackageVersion(string $package, string $version): bool
+ {
+ if(isset($this->Packages[$package]))
+ {
+ $r = $this->Packages[$package]->removeVersion($version);
+
+ // Remove the entire package entry if there's no installed versions
+ if($this->Packages[$package]->getLatestVersion() == null && $r)
+ {
+ unset($this->Packages[$package]);
+ }
+
+ $this->update();
+ return $r;
+ }
+
+ return false;
+ }
+
+ /**
+ * Removes an entire package entry
+ *
+ * @param string $package
+ * @return bool
+ */
+ public function removePackage(string $package): bool
+ {
+ if(isset($this->Packages[$package]))
+ {
+ unset($this->Packages[$package]);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets an existing package entry, returns null if no such entry exists
+ *
+ * @param string $package
+ * @return PackageEntry|null
+ */
+ public function getPackage(string $package): ?PackageEntry
+ {
+ if(isset($this->Packages[$package]))
+ {
+ return $this->Packages[$package];
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns an array of all packages and their installed versions
+ *
+ * @return array
+ */
+ public function getPackages(): array
+ {
+ $results = [];
+ foreach($this->Packages as $package => $entry)
+ $results[$package] = $entry->getVersions();
+ return $results;
+ }
+
+ /**
+ * Returns an array representation of the object
+ *
+ * @param bool $bytecode
+ * @return array
+ */
+ public function toArray(bool $bytecode=false): array
+ {
+ $package_entries = [];
+ foreach($this->Packages as $entry)
+ {
+ $package_entries[] = $entry->toArray($bytecode);
+ }
+
+ return [
+ ($bytecode ? Functions::cbc('package_lock_version') : 'package_lock_version') => $this->PackageLockVersion,
+ ($bytecode ? Functions::cbc('last_updated_timestamp') : 'last_updated_timestamp') => $this->LastUpdatedTimestamp,
+ ($bytecode ? Functions::cbc('packages') : 'packages') => $package_entries
+ ];
+ }
+
+ /**
+ * Constructs object from an array representation
+ *
+ * @param array $data
+ * @return static
+ */
+ public static function fromArray(array $data): self
+ {
+ $object = new self();
+
+ $packages = Functions::array_bc($data, 'packages');
+ if($packages !== null)
+ {
+ foreach($packages as $_datum)
+ {
+ $entry = PackageEntry::fromArray($_datum);
+ $object->Packages[$entry->Name] = $entry;
+ }
+ }
+
+ $object->PackageLockVersion = Functions::array_bc($data, 'package_lock_version');
+ $object->LastUpdatedTimestamp = Functions::array_bc($data, 'last_updated_timestamp');
+
+ return $object;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Objects/PackageLock/DependencyEntry.php b/src/ncc/Objects/PackageLock/DependencyEntry.php
new file mode 100644
index 0000000..82be14e
--- /dev/null
+++ b/src/ncc/Objects/PackageLock/DependencyEntry.php
@@ -0,0 +1,64 @@
+PackageName = $dependency->Name;
+ $this->Version = $dependency->Version;
+ }
+ }
+
+ /**
+ * Returns an array representation of the object
+ *
+ * @param bool $bytecode
+ * @return array
+ */
+ public function toArray(bool $bytecode=false): array
+ {
+ return [
+ ($bytecode ? Functions::cbc('package_name') : 'package_name') => $this->PackageName,
+ ($bytecode ? Functions::cbc('version') : 'version') => $this->Version,
+ ];
+ }
+
+ /**
+ * Constructs object from an array representation
+ *
+ * @param array $data
+ * @return DependencyEntry
+ */
+ public static function fromArray(array $data): self
+ {
+ $object = new self();
+
+ $object->PackageName = Functions::array_bc($data, 'package_name');
+ $object->Version = Functions::array_bc($data, 'version');
+
+ return $object;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Objects/PackageLock/PackageEntry.php b/src/ncc/Objects/PackageLock/PackageEntry.php
new file mode 100644
index 0000000..43cf7a9
--- /dev/null
+++ b/src/ncc/Objects/PackageLock/PackageEntry.php
@@ -0,0 +1,235 @@
+Versions = [];
+ }
+
+ /**
+ * Searches and returns a version of the package
+ *
+ * @param string $version
+ * @param bool $throw_exception
+ * @return VersionEntry|null
+ * @throws VersionNotFoundException
+ */
+ public function getVersion(string $version, bool $throw_exception=false): ?VersionEntry
+ {
+ foreach($this->Versions as $versionEntry)
+ {
+ if($versionEntry->Version == $version)
+ {
+ return $versionEntry;
+ }
+ }
+
+ if($throw_exception)
+ throw new VersionNotFoundException('The version entry is not found');
+
+ return null;
+ }
+
+ /**
+ * Removes version entry from the package
+ *
+ * @param string $version
+ * @return bool
+ * @noinspection PhpUnused
+ */
+ public function removeVersion(string $version): bool
+ {
+ $count = 0;
+ $found_node = false;
+ foreach($this->Versions as $versionEntry)
+ {
+ if($versionEntry->Version == $version)
+ {
+ $found_node = true;
+ break;
+ }
+
+ $count += 1;
+ }
+
+ if($found_node)
+ {
+ unset($this->Versions[$count]);
+ $this->updateLatestVersion();
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Adds a new version entry to the package, if overwrite is true then
+ * the entry will be overwritten if it exists, otherwise it will return
+ * false.
+ *
+ * @param Package $package
+ * @param string $install_path
+ * @param bool $overwrite
+ * @return bool
+ */
+ public function addVersion(Package $package, string $install_path, bool $overwrite=false): bool
+ {
+ try
+ {
+ if ($this->getVersion($package->Assembly->Version) !== null)
+ {
+ if (!$overwrite) return false;
+ $this->removeVersion($package->Assembly->Version);
+ }
+ }
+ catch (VersionNotFoundException $e)
+ {
+ unset($e);
+ }
+
+ $version = new VersionEntry();
+ $version->Version = $package->Assembly->Version;
+ $version->Compiler = $package->Header->CompilerExtension;
+ $version->ExecutionUnits = $package->ExecutionUnits;
+ $version->MainExecutionPolicy = $package->MainExecutionPolicy;
+ $version->Location = $install_path;
+
+ foreach($package->Dependencies as $dependency)
+ {
+ $version->Dependencies[] = new DependencyEntry($dependency);
+ }
+
+ $this->Versions[] = $version;
+ $this->updateLatestVersion();
+ return true;
+ }
+
+ /**
+ * Updates and returns the latest version of this package entry
+ *
+ * @return void
+ */
+ private function updateLatestVersion(): void
+ {
+ $latest_version = null;
+ foreach($this->Versions as $version)
+ {
+ $version = $version->Version;
+ if($latest_version == null)
+ {
+ $latest_version = $version;
+ continue;
+ }
+ if(VersionComparator::compareVersion($version, $latest_version))
+ $latest_version = $version;
+ }
+
+ $this->LatestVersion = $latest_version;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getLatestVersion(): ?string
+ {
+ return $this->LatestVersion;
+ }
+
+ /**
+ * Returns an array of all versions installed
+ *
+ * @return array
+ */
+ public function getVersions(): array
+ {
+ $r = [];
+
+ foreach($this->Versions as $version)
+ {
+ $r[] = $version->Version;
+ }
+
+ return $r;
+ }
+
+ /**
+ * Returns an array representation of the object
+ *
+ * @param bool $bytecode
+ * @return array
+ */
+ public function toArray(bool $bytecode=false): array
+ {
+ $versions = [];
+ foreach($this->Versions as $version)
+ {
+ $versions[] = $version->toArray($bytecode);
+ }
+
+ return [
+ ($bytecode ? Functions::cbc('name') : 'name') => $this->Name,
+ ($bytecode ? Functions::cbc('latest_version') : 'latest_version') => $this->LatestVersion,
+ ($bytecode ? Functions::cbc('versions') : 'versions') => $versions,
+ ];
+ }
+
+ /**
+ * Constructs object from an array representation
+ *
+ * @param array $data
+ * @return PackageEntry
+ */
+ public static function fromArray(array $data): self
+ {
+ $object = new self();
+
+ $object->Name = Functions::array_bc($data, 'name');
+ $object->LatestVersion = Functions::array_bc($data, 'latest_version');
+ $versions = Functions::array_bc($data, 'versions');
+
+ if($versions !== null)
+ {
+ foreach($versions as $_datum)
+ {
+ $object->Versions[] = VersionEntry::fromArray($_datum);
+ }
+ }
+
+ return $object;
+ }
+
+ }
\ No newline at end of file
diff --git a/src/ncc/Objects/PackageLock/VersionEntry.php b/src/ncc/Objects/PackageLock/VersionEntry.php
new file mode 100644
index 0000000..f512c4a
--- /dev/null
+++ b/src/ncc/Objects/PackageLock/VersionEntry.php
@@ -0,0 +1,126 @@
+Dependencies = [];
+ $this->ExecutionUnits = [];
+ }
+
+ /**
+ * Returns an array representation of the object
+ *
+ * @param bool $bytecode
+ * @return array
+ */
+ public function toArray(bool $bytecode=false): array
+ {
+ $dependencies = [];
+ foreach($this->Dependencies as $dependency)
+ {
+ $dependencies[] = $dependency->toArray($bytecode);
+ }
+
+ $execution_units = [];
+ foreach($this->ExecutionUnits as $executionUnit)
+ {
+ $execution_units[] = $executionUnit->toArray($bytecode);
+ }
+
+ return [
+ ($bytecode ? Functions::cbc('version') : 'version') => $this->Version,
+ ($bytecode ? Functions::cbc('compiler') : 'compiler') => $this->Compiler->toArray($bytecode),
+ ($bytecode ? Functions::cbc('dependencies') : 'dependencies') => $dependencies,
+ ($bytecode ? Functions::cbc('execution_units') : 'execution_units') => $execution_units,
+ ($bytecode ? Functions::cbc('main_execution_policy') : 'main_execution_policy') => $this->MainExecutionPolicy,
+ ($bytecode ? Functions::cbc('location') : 'location') => $this->Location,
+ ];
+ }
+
+ /**
+ * Constructs object from an array representation
+ *
+ * @param array $data
+ * @return VersionEntry
+ */
+ public static function fromArray(array $data): self
+ {
+ $object = new self();
+ $object->Version = Functions::array_bc($data, 'version');
+ $object->Compiler = Compiler::fromArray(Functions::array_bc($data, 'compiler'));
+ $object->MainExecutionPolicy = Functions::array_bc($data, 'main_execution_policy');
+ $object->Location = Functions::array_bc($data, 'location');
+
+ $dependencies = Functions::array_bc($data, 'dependencies');
+ if($dependencies !== null)
+ {
+ foreach($dependencies as $_datum)
+ {
+ $object->Dependencies[] = DependencyEntry::fromArray($_datum);
+ }
+ }
+
+ $execution_units = Functions::array_bc($data, 'execution_units');
+ if($execution_units !== null)
+ {
+ foreach($execution_units as $_datum)
+ {
+ $object->ExecutionUnits[] = ExecutionUnit::fromArray($_datum);
+ }
+ }
+
+ return $object;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Objects/ProjectConfiguration.php b/src/ncc/Objects/ProjectConfiguration.php
index f2980b1..f361a3e 100644
--- a/src/ncc/Objects/ProjectConfiguration.php
+++ b/src/ncc/Objects/ProjectConfiguration.php
@@ -1,18 +1,29 @@
Project = new Project();
$this->Assembly = new Assembly();
+ $this->ExecutionPolicies = [];
$this->Build = new Build();
}
@@ -58,13 +84,15 @@
*
* @param bool $throw_exception
* @return bool
+ * @throws BuildConfigurationNotFoundException
+ * @throws InvalidConstantNameException
+ * @throws InvalidProjectBuildConfiguration
* @throws InvalidProjectConfigurationException
* @throws InvalidPropertyValueException
* @throws RuntimeException
+ * @throws UndefinedExecutionPolicyException
* @throws UnsupportedCompilerExtensionException
* @throws UnsupportedExtensionVersionException
- * @throws InvalidProjectBuildConfiguration
- * @throws InvalidConstantNameException
*/
public function validate(bool $throw_exception=True): bool
{
@@ -77,9 +105,170 @@
if(!$this->Build->validate($throw_exception))
return false;
+ try
+ {
+ $this->getRequiredExecutionPolicies(BuildConfigurationValues::AllConfigurations);
+ }
+ catch(Exception $e)
+ {
+ if($throw_exception)
+ throw $e;
+ return false;
+ }
+
return true;
}
+ /**
+ * @param string $name
+ * @return ExecutionPolicy|null
+ */
+ private function getExecutionPolicy(string $name): ?ExecutionPolicy
+ {
+ foreach($this->ExecutionPolicies as $executionPolicy)
+ {
+ if($executionPolicy->Name == $name)
+ return $executionPolicy;
+ }
+
+ return null;
+ }
+
+ /**
+ * Runs a check on the project configuration and determines what policies are required
+ *
+ * @param string $build_configuration
+ * @return array
+ * @throws BuildConfigurationNotFoundException
+ * @throws UndefinedExecutionPolicyException
+ */
+ public function getRequiredExecutionPolicies(string $build_configuration=BuildConfigurationValues::DefaultConfiguration): array
+ {
+ if($this->ExecutionPolicies == null || count($this->ExecutionPolicies) == 0)
+ return [];
+
+ $defined_polices = [];
+ $required_policies = [];
+ /** @var ExecutionPolicy $execution_policy */
+ foreach($this->ExecutionPolicies as $execution_policy)
+ {
+ $defined_polices[] = $execution_policy->Name;
+ $execution_policy->validate();
+ }
+
+ // Check the installer by batch
+ if($this->Installer !== null)
+ {
+ $array_rep = $this->Installer->toArray();
+ /** @var string[] $value */
+ foreach($array_rep as $key => $value)
+ {
+ if($value == null || count($value) == 0)
+ continue;
+
+ foreach($value as $unit)
+ {
+ if(!in_array($unit, $defined_polices))
+ throw new UndefinedExecutionPolicyException('The property \'' . $key . '\' in the project configuration calls for an undefined execution policy \'' . $unit . '\'');
+ if(!in_array($unit, $required_policies))
+ $required_policies[] = $unit;
+ }
+ }
+ }
+
+ if($this->Build->PreBuild !== null && count($this->Build->PostBuild) > 0)
+ {
+ foreach($this->Build->PostBuild as $unit)
+ {
+ if(!in_array($unit, $defined_polices))
+ throw new UndefinedExecutionPolicyException('The property \'build.pre_build\' in the project configuration calls for an undefined execution policy \'' . $unit . '\'');
+ if(!in_array($unit, $required_policies))
+ $required_policies[] = $unit;
+ }
+ }
+
+ if($this->Build->PostBuild !== null && count($this->Build->PostBuild) > 0)
+ {
+ foreach($this->Build->PostBuild as $unit)
+ {
+ if(!in_array($unit, $defined_polices))
+ throw new UndefinedExecutionPolicyException('The property \'build.pre_build\' in the project configuration calls for an undefined execution policy \'' . $unit . '\'');
+ if(!in_array($unit, $required_policies))
+ $required_policies[] = $unit;
+ }
+ }
+
+ switch($build_configuration)
+ {
+ case BuildConfigurationValues::AllConfigurations:
+ /** @var BuildConfiguration $configuration */
+ foreach($this->Build->Configurations as $configuration)
+ {
+ foreach($this->processBuildPolicies($configuration, $defined_polices) as $policy)
+ {
+ if(!in_array($policy, $required_policies))
+ $required_policies[] = $policy;
+ }
+ }
+ break;
+
+ default:
+ $configuration = $this->Build->getBuildConfiguration($build_configuration);
+ foreach($this->processBuildPolicies($configuration, $defined_polices) as $policy)
+ {
+ if(!in_array($policy, $required_policies))
+ $required_policies[] = $policy;
+ }
+ break;
+ }
+
+ foreach($required_policies as $policy)
+ {
+ $execution_policy = $this->getExecutionPolicy($policy);
+ if($execution_policy->ExitHandlers !== null)
+ {
+ if(
+ $execution_policy->ExitHandlers->Success !== null &&
+ $execution_policy->ExitHandlers->Success->Run !== null
+ )
+ {
+ if(!in_array($execution_policy->ExitHandlers->Success->Run, $defined_polices))
+ throw new UndefinedExecutionPolicyException('The execution policy \'' . $execution_policy->Name . '\' Success exit handler points to a undefined execution policy \'' . $execution_policy->ExitHandlers->Success->Run . '\'');
+
+ if(!in_array($execution_policy->ExitHandlers->Success->Run, $required_policies))
+ $required_policies[] = $execution_policy->ExitHandlers->Success->Run;
+ }
+
+ if(
+ $execution_policy->ExitHandlers->Warning !== null &&
+ $execution_policy->ExitHandlers->Warning->Run !== null
+ )
+ {
+ if(!in_array($execution_policy->ExitHandlers->Warning->Run, $defined_polices))
+ throw new UndefinedExecutionPolicyException('The execution policy \'' . $execution_policy->Name . '\' Warning exit handler points to a undefined execution policy \'' . $execution_policy->ExitHandlers->Warning->Run . '\'');
+
+ if(!in_array($execution_policy->ExitHandlers->Warning->Run, $required_policies))
+ $required_policies[] = $execution_policy->ExitHandlers->Warning->Run;
+ }
+
+ if(
+ $execution_policy->ExitHandlers->Error !== null &&
+ $execution_policy->ExitHandlers->Error->Run !== null
+ )
+ {
+ if(!in_array($execution_policy->ExitHandlers->Error->Run, $defined_polices))
+ throw new UndefinedExecutionPolicyException('The execution policy \'' . $execution_policy->Name . '\' Error exit handler points to a undefined execution policy \'' . $execution_policy->ExitHandlers->Error->Run . '\'');
+
+ if(!in_array($execution_policy->ExitHandlers->Error->Run, $required_policies))
+ $required_policies[] = $execution_policy->ExitHandlers->Error->Run;
+ }
+ }
+
+ }
+
+ return $required_policies;
+ }
+
/**
* Returns an array representation of the object
*
@@ -88,9 +277,15 @@
*/
public function toArray(bool $bytecode=false): array
{
+ $execution_policies = [];
+ foreach($this->ExecutionPolicies as $executionPolicy)
+ {
+ $execution_policies[$executionPolicy->Name] = $executionPolicy->toArray($bytecode);
+ }
return [
($bytecode ? Functions::cbc('project') : 'project') => $this->Project->toArray($bytecode),
($bytecode ? Functions::cbc('assembly') : 'assembly') => $this->Assembly->toArray($bytecode),
+ ($bytecode ? Functions::cbc('execution_policies') : 'execution_policies') => $execution_policies,
($bytecode ? Functions::cbc('build') : 'build') => $this->Build->toArray($bytecode),
];
}
@@ -128,7 +323,26 @@
$ProjectConfigurationObject->Project = Project::fromArray(Functions::array_bc($data, 'project'));
$ProjectConfigurationObject->Assembly = Assembly::fromArray(Functions::array_bc($data, 'assembly'));
+ $ProjectConfigurationObject->ExecutionPolicies = Functions::array_bc($data, 'execution_policies');
$ProjectConfigurationObject->Build = Build::fromArray(Functions::array_bc($data, 'build'));
+ $ProjectConfigurationObject->Installer = Functions::array_bc($data, 'installer');
+
+ if($ProjectConfigurationObject->Installer !== null)
+ $ProjectConfigurationObject->Installer = Installer::fromArray($ProjectConfigurationObject->Installer);
+
+ if($ProjectConfigurationObject->ExecutionPolicies == null)
+ {
+ $ProjectConfigurationObject->ExecutionPolicies = [];
+ }
+ else
+ {
+ $policies = [];
+ foreach($ProjectConfigurationObject->ExecutionPolicies as $policy)
+ {
+ $policies[] = ExecutionPolicy::fromArray($policy);
+ }
+ $ProjectConfigurationObject->ExecutionPolicies = $policies;
+ }
return $ProjectConfigurationObject;
}
@@ -140,10 +354,45 @@
* @return ProjectConfiguration
* @throws FileNotFoundException
* @throws MalformedJsonException
+ * @throws AccessDeniedException
+ * @throws IOException
* @noinspection PhpUnused
*/
public static function fromFile(string $path): ProjectConfiguration
{
return ProjectConfiguration::fromArray(Functions::loadJsonFile($path, Functions::FORCE_ARRAY));
}
+
+ /**
+ * @param BuildConfiguration $configuration
+ * @param array $defined_polices
+ * @return array
+ * @throws UndefinedExecutionPolicyException
+ */
+ private function processBuildPolicies(BuildConfiguration $configuration, array $defined_polices): array
+ {
+ $required_policies = [];
+
+ if ($configuration->PreBuild !== null && count($configuration->PreBuild) > 0)
+ {
+ foreach ($configuration->PreBuild as $unit)
+ {
+ if (!in_array($unit, $defined_polices))
+ throw new UndefinedExecutionPolicyException('The property \'pre_build\' in the build configuration \'' . $configuration->Name . '\' calls for an undefined execution policy \'' . $unit . '\'');
+ $required_policies[] = $unit;
+ }
+ }
+
+ if ($configuration->PostBuild !== null && count($configuration->PostBuild) > 0)
+ {
+ foreach ($configuration->PostBuild as $unit)
+ {
+ if (!in_array($unit, $defined_polices))
+ throw new UndefinedExecutionPolicyException('The property \'pre_build\' in the build configuration \'' . $configuration->Name . '\' calls for an undefined execution policy \'' . $unit . '\'');
+ $required_policies[] = $unit;
+ }
+ }
+
+ return $required_policies;
+ }
}
\ No newline at end of file
diff --git a/src/ncc/Objects/ProjectConfiguration/Build.php b/src/ncc/Objects/ProjectConfiguration/Build.php
index cf45987..71caa85 100644
--- a/src/ncc/Objects/ProjectConfiguration/Build.php
+++ b/src/ncc/Objects/ProjectConfiguration/Build.php
@@ -4,6 +4,7 @@
namespace ncc\Objects\ProjectConfiguration;
+ use ncc\Abstracts\Options\BuildConfigurationValues;
use ncc\Exceptions\BuildConfigurationNotFoundException;
use ncc\Exceptions\InvalidConstantNameException;
use ncc\Exceptions\InvalidProjectBuildConfiguration;
@@ -51,6 +52,13 @@
*/
public $Scope;
+ /**
+ * The execution policy to use as the main execution point
+ *
+ * @var string|null
+ */
+ public $Main;
+
/**
* An array of constants to define by default
*
@@ -58,6 +66,20 @@
*/
public $DefineConstants;
+ /**
+ * An array of execution policies to execute pre build
+ *
+ * @var string[]
+ */
+ public $PreBuild;
+
+ /**
+ * An array of execution policies to execute post build
+ *
+ * @var string[]
+ */
+ public $PostBuild;
+
/**
* An array of dependencies that are required by default
*
@@ -125,6 +147,7 @@
* Returns an array of all the build configurations defined in the project configuration
*
* @return array
+ * @noinspection PhpUnused
*/
public function getBuildConfigurations(): array
{
@@ -148,6 +171,9 @@
*/
public function getBuildConfiguration(string $name): BuildConfiguration
{
+ if($name == BuildConfigurationValues::DefaultConfiguration)
+ $name = $this->DefaultConfiguration;
+
foreach($this->Configurations as $configuration)
{
if($configuration->Name == $name)
@@ -174,7 +200,10 @@
$ReturnResults[($bytecode ? Functions::cbc('exclude_files') : 'exclude_files')] = $this->ExcludeFiles;
$ReturnResults[($bytecode ? Functions::cbc('options') : 'options')] = $this->Options;
$ReturnResults[($bytecode ? Functions::cbc('scope') : 'scope')] = $this->Scope;
+ $ReturnResults[($bytecode ? Functions::cbc('main') : 'main')] = $this->Main;
$ReturnResults[($bytecode ? Functions::cbc('define_constants') : 'define_constants')] = $this->DefineConstants;
+ $ReturnResults[($bytecode ? Functions::cbc('pre_build') : 'pre_build')] = $this->PreBuild;
+ $ReturnResults[($bytecode ? Functions::cbc('post_build') : 'post_build')] = $this->PostBuild;
$ReturnResults[($bytecode ? Functions::cbc('dependencies') : 'dependencies')] = [];
foreach($this->Dependencies as $dependency)
@@ -207,7 +236,10 @@
$BuildObject->ExcludeFiles = (Functions::array_bc($data, 'exclude_files') ?? []);
$BuildObject->Options = (Functions::array_bc($data, 'options') ?? []);
$BuildObject->Scope = Functions::array_bc($data, 'scope');
+ $BuildObject->Main = Functions::array_bc($data, 'main');
$BuildObject->DefineConstants = (Functions::array_bc($data, 'define_constants') ?? []);
+ $BuildObject->PreBuild = (Functions::array_bc($data, 'pre_build') ?? []);
+ $BuildObject->PostBuild = (Functions::array_bc($data, 'post_build') ?? []);
if(Functions::array_bc($data, 'dependencies') !== null)
{
diff --git a/src/ncc/Objects/ProjectConfiguration/BuildConfiguration.php b/src/ncc/Objects/ProjectConfiguration/BuildConfiguration.php
index cc80ef6..45a8bf5 100644
--- a/src/ncc/Objects/ProjectConfiguration/BuildConfiguration.php
+++ b/src/ncc/Objects/ProjectConfiguration/BuildConfiguration.php
@@ -47,6 +47,20 @@
*/
public $ExcludeFiles;
+ /**
+ * An array of policies to execute pre-building the package
+ *
+ * @var string[]|string
+ */
+ public $PreBuild;
+
+ /**
+ * An array of policies to execute post-building the package
+ *
+ * @var string
+ */
+ public $PostBuild;
+
/**
* Dependencies required for the build configuration, cannot conflict with the
* default dependencies
@@ -64,6 +78,8 @@
$this->OutputPath = 'build';
$this->DefineConstants = [];
$this->ExcludeFiles = [];
+ $this->PreBuild = [];
+ $this->PostBuild = [];
$this->Dependencies = [];
}
@@ -84,6 +100,7 @@
$ReturnResults[($bytecode ? Functions::cbc('output_path') : 'output_path')] = $this->OutputPath;
$ReturnResults[($bytecode ? Functions::cbc('define_constants') : 'define_constants')] = $this->DefineConstants;
$ReturnResults[($bytecode ? Functions::cbc('exclude_files') : 'exclude_files')] = $this->ExcludeFiles;
+ $ReturnResults[($bytecode ? Functions::cbc('pre_build') : 'pre_build')] = $this->PreBuild;
$ReturnResults[($bytecode ? Functions::cbc('dependencies') : 'dependencies')] = [];
foreach($this->Dependencies as $dependency)
@@ -105,36 +122,30 @@
$BuildConfigurationObject = new BuildConfiguration();
if(Functions::array_bc($data, 'name') !== null)
- {
$BuildConfigurationObject->Name = Functions::array_bc($data, 'name');
- }
if(Functions::array_bc($data, 'options') !== null)
- {
$BuildConfigurationObject->Options = Functions::array_bc($data, 'options');
- }
if(Functions::array_bc($data, 'output_path') !== null)
- {
$BuildConfigurationObject->OutputPath = Functions::array_bc($data, 'output_path');
- }
if(Functions::array_bc($data, 'define_constants') !== null)
- {
$BuildConfigurationObject->DefineConstants = Functions::array_bc($data, 'define_constants');
- }
if(Functions::array_bc($data, 'exclude_files') !== null)
- {
$BuildConfigurationObject->ExcludeFiles = Functions::array_bc($data, 'exclude_files');
- }
+
+ if(Functions::array_bc($data, 'pre_build') !== null)
+ $BuildConfigurationObject->PreBuild = Functions::array_bc($data, 'pre_build');
+
+ if(Functions::array_bc($data, 'post_build') !== null)
+ $BuildConfigurationObject->PostBuild = Functions::array_bc($data, 'post_build');
if(Functions::array_bc($data, 'dependencies') !== null)
{
foreach(Functions::array_bc($data, 'dependencies') as $item)
- {
$BuildConfigurationObject->Dependencies[] = Dependency::fromArray($item);
- }
}
return $BuildConfigurationObject;
diff --git a/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy.php b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy.php
new file mode 100644
index 0000000..b627a30
--- /dev/null
+++ b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy.php
@@ -0,0 +1,98 @@
+ $this->Name,
+ ($bytecode ? Functions::cbc('runner') : 'runner') => $this->Runner,
+ ($bytecode ? Functions::cbc('message') : 'message') => $this->Message,
+ ($bytecode ? Functions::cbc('exec') : 'exec') => $this->Execute?->toArray($bytecode),
+ ($bytecode ? Functions::cbc('exit_handlers') : 'exit_handlers') => $this->ExitHandlers?->toArray($bytecode),
+ ];
+ }
+
+ /**
+ * @param array $data
+ * @return ExecutionPolicy
+ */
+ public static function fromArray(array $data): self
+ {
+ $object = new self();
+
+ $object->Name = Functions::array_bc($data, 'name');
+ $object->Runner = Functions::array_bc($data, 'runner');
+ $object->Message = Functions::array_bc($data, 'message');
+ $object->Execute = Functions::array_bc($data, 'exec');
+ $object->ExitHandlers = Functions::array_bc($data, 'exit_handlers');
+
+ if($object->Execute !== null)
+ $object->Execute = Execute::fromArray($object->Execute);
+
+ if($object->ExitHandlers !== null)
+ $object->ExitHandlers = ExitHandlers::fromArray($object->ExitHandlers);
+
+ return $object;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/Execute.php b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/Execute.php
new file mode 100644
index 0000000..0cc164a
--- /dev/null
+++ b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/Execute.php
@@ -0,0 +1,103 @@
+Tty = false;
+ $this->Silent = false;
+ $this->Timeout = null;
+ $this->WorkingDirectory = "%CWD%";
+ }
+
+ /**
+ * Returns an array representation of the object
+ *
+ * @param bool $bytecode
+ * @return array
+ */
+ public function toArray(bool $bytecode=false): array
+ {
+ return [
+ ($bytecode ? Functions::cbc('target') : 'target') => $this->Target,
+ ($bytecode ? Functions::cbc('working_directory') : 'working_directory') => $this->WorkingDirectory,
+ ($bytecode ? Functions::cbc('options') : 'options') => $this->Options,
+ ($bytecode ? Functions::cbc('silent') : 'silent') => $this->Silent,
+ ($bytecode ? Functions::cbc('tty') : 'tty') => $this->Tty,
+ ($bytecode ? Functions::cbc('timeout') : 'timeout') => $this->Timeout
+ ];
+ }
+
+ /**
+ * Constructs object from an array representation
+ *
+ * @param array $data
+ * @return Execute
+ */
+ public static function fromArray(array $data): self
+ {
+ $object = new self();
+
+ $object->Target = Functions::array_bc($data, 'target');
+ $object->WorkingDirectory = Functions::array_bc($data, 'working_directory');
+ $object->Options = Functions::array_bc($data, 'options');
+ $object->Silent = Functions::array_bc($data, 'silent');
+ $object->Tty = Functions::array_bc($data, 'tty');
+ $object->Timeout = Functions::array_bc($data, 'timeout');
+
+ return $object;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/ExitHandle.php b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/ExitHandle.php
new file mode 100644
index 0000000..8d78bcc
--- /dev/null
+++ b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/ExitHandle.php
@@ -0,0 +1,76 @@
+ $this->Message,
+ ($bytecode ? Functions::cbc('end_process') : 'end_process') => $this->EndProcess,
+ ($bytecode ? Functions::cbc('run') : 'run') => $this->Run,
+ ($bytecode ? Functions::cbc('exit_code') : 'exit_code') => $this->ExitCode,
+ ];
+ }
+
+ /**
+ * Constructs object from an array representation
+ *
+ * @param array $data
+ * @return ExitHandle
+ */
+ public static function fromArray(array $data): self
+ {
+ $object = new self();
+
+ $object->Message = Functions::array_bc($data, 'message');
+ $object->EndProcess = Functions::array_bc($data, 'end_process');
+ $object->Run = Functions::array_bc($data, 'run');
+ $object->ExitCode = Functions::array_bc($data, 'exit_code');
+
+ return $object;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/ExitHandlers.php b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/ExitHandlers.php
new file mode 100644
index 0000000..28a73b2
--- /dev/null
+++ b/src/ncc/Objects/ProjectConfiguration/ExecutionPolicy/ExitHandlers.php
@@ -0,0 +1,71 @@
+ $this->Success?->toArray($bytecode),
+ ($bytecode ? Functions::cbc('warning') : 'warning') => $this->Warning?->toArray($bytecode),
+ ($bytecode ? Functions::cbc('error') : 'error') => $this->Error?->toArray($bytecode),
+ ];
+ }
+
+ /**
+ * Constructs object from an array representation
+ *
+ * @param array $data
+ * @return ExitHandlers
+ */
+ public static function fromArray(array $data): self
+ {
+ $object = new self();
+
+ $object->Success = Functions::array_bc($data, 'success');
+ if($object->Success !== null)
+ $object->Success = ExitHandle::fromArray($object->Success);
+
+ $object->Warning = Functions::array_bc($data, 'warning');
+ if($object->Warning !== null)
+ $object->Warning = ExitHandle::fromArray($object->Warning);
+
+ $object->Error = Functions::array_bc($data, 'error');
+ if($object->Error !== null)
+ $object->Error = ExitHandle::fromArray($object->Error);
+
+ return $object;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Objects/ProjectConfiguration/Installer.php b/src/ncc/Objects/ProjectConfiguration/Installer.php
new file mode 100644
index 0000000..30dda88
--- /dev/null
+++ b/src/ncc/Objects/ProjectConfiguration/Installer.php
@@ -0,0 +1,88 @@
+ $this->PreInstall,
+ ($bytecode? Functions::cbc('post_install') : 'post_install') => $this->PostInstall,
+ ($bytecode? Functions::cbc('pre_uninstall') : 'pre_uninstall') => $this->PostUninstall,
+ ($bytecode? Functions::cbc('post_uninstall') : 'post_uninstall') => $this->PostUninstall,
+ ($bytecode? Functions::cbc('pre_update') : 'pre_update') => $this->PreUpdate,
+ ($bytecode? Functions::cbc('post_update') : 'post_update') => $this->PostUpdate
+ ];
+ }
+
+ /**
+ * @param array $data
+ * @return Installer
+ */
+ public static function fromArray(array $data): self
+ {
+ $object = new self();
+
+ $object->PreInstall = Functions::array_bc($data, 'pre_install');
+ $object->PostInstall = Functions::array_bc($data, 'post_install');
+ $object->PreUninstall = Functions::array_bc($data, 'pre_uninstall');
+ $object->PostUninstall = Functions::array_bc($data, 'post_uninstall');
+ $object->PreUpdate = Functions::array_bc($data, 'pre_update');
+ $object->PostUpdate = Functions::array_bc($data, 'post_update');
+
+ return $object;
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Utilities/Base64.php b/src/ncc/Utilities/Base64.php
index f1d7b5e..9c75af3 100644
--- a/src/ncc/Utilities/Base64.php
+++ b/src/ncc/Utilities/Base64.php
@@ -17,6 +17,10 @@
*/
public static function encode(string $string): string
{
+ // Builtin function is faster than raw implementation
+ if(function_exists('base64_encode'))
+ return base64_encode($string);
+
$base64 = str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/');
$bit_pattern = '';
$padding = 0;
@@ -54,6 +58,9 @@
*/
public static function decode(string $string): string
{
+ if(function_exists('base64_decode'))
+ return base64_encode($string);
+
$base64 = str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/');
$bit_pattern = '';
$padding = substr_count(substr(strrev($string), 0, 2), '=');
diff --git a/src/ncc/Utilities/Console.php b/src/ncc/Utilities/Console.php
index 870eeb9..30bd0ff 100644
--- a/src/ncc/Utilities/Console.php
+++ b/src/ncc/Utilities/Console.php
@@ -4,10 +4,17 @@
use Exception;
use ncc\Abstracts\ConsoleColors;
+ use ncc\Abstracts\LogLevel;
+ use ncc\CLI\Main;
use ncc\ncc;
class Console
{
+ /**
+ * @var int
+ */
+ private static $largestTickLength = 0;
+
/**
* Inline Progress bar, created by dealnews.com.
*
@@ -23,6 +30,20 @@
if(!ncc::cliMode())
return;
+ if(Main::getLogLevel() !== null)
+ {
+ switch(Main::getLogLevel())
+ {
+ case LogLevel::Verbose:
+ case LogLevel::Debug:
+ case LogLevel::Silent:
+ return;
+
+ default:
+ break;
+ }
+ }
+
static $start_time;
// if we go over our bound, just ignore it
@@ -44,6 +65,7 @@
$status_bar.="=";
}
+ /** @noinspection PhpRedundantOptionalArgumentInspection */
$disp=number_format($perc*100, 0);
$status_bar.=" ] $disp% $value/$total";
@@ -74,18 +96,54 @@
}
}
+ /**
+ * Appends a verbose prefix to the message
+ *
+ * @param string $log_level
+ * @param string $input
+ * @return string
+ */
+ private static function setPrefix(string $log_level, string $input): string
+ {
+ $input = match ($log_level) {
+ LogLevel::Verbose => self::formatColor('VRB:', ConsoleColors::LightCyan) . " $input",
+ LogLevel::Debug => self::formatColor('DBG:', ConsoleColors::LightMagenta) . " $input",
+ LogLevel::Info => self::formatColor('INF:', ConsoleColors::White) . " $input",
+ LogLevel::Warning => self::formatColor('WRN:', ConsoleColors::Yellow) . " $input",
+ LogLevel::Error => self::formatColor('ERR:', ConsoleColors::LightRed) . " $input",
+ LogLevel::Fatal => self::formatColor('FTL:', ConsoleColors::LightRed) . " $input",
+ default => self::formatColor('MSG:', ConsoleColors::Default) . " $input",
+ };
+
+ $tick_time = (string)microtime(true);
+ if(strlen($tick_time) > self::$largestTickLength)
+ self::$largestTickLength = strlen($tick_time);
+ if(strlen($tick_time) < self::$largestTickLength)
+ /** @noinspection PhpRedundantOptionalArgumentInspection */
+ $tick_time = str_pad($tick_time, (strlen($tick_time) + (self::$largestTickLength - strlen($tick_time))), ' ', STR_PAD_RIGHT);
+
+ return '[' . $tick_time . ' - ' . date('TH:i:sP') . '] ' . $input;
+ }
+
/**
* Simple output function
*
* @param string $message
* @param bool $newline
+ * @param bool $no_prefix
* @return void
*/
- public static function out(string $message, bool $newline=true): void
+ public static function out(string $message, bool $newline=true, bool $no_prefix=false): void
{
if(!ncc::cliMode())
return;
+ if(Main::getLogLevel() !== null && !Resolver::checkLogLevel(LogLevel::Info, Main::getLogLevel()))
+ return;
+
+ if(Main::getLogLevel() !== null && Resolver::checkLogLevel(LogLevel::Verbose, Main::getLogLevel()) && !$no_prefix)
+ $message = self::setPrefix(LogLevel::Info, $message);
+
if($newline)
{
print($message . PHP_EOL);
@@ -95,6 +153,58 @@
print($message);
}
+ /**
+ * Output debug message
+ *
+ * @param string $message
+ * @param bool $newline
+ * @return void
+ */
+ public static function outDebug(string $message, bool $newline=true): void
+ {
+ if(!ncc::cliMode())
+ return;
+
+ if(Main::getLogLevel() !== null && !Resolver::checkLogLevel(LogLevel::Debug, Main::getLogLevel()))
+ return;
+
+ $backtrace = null;
+ if(function_exists('debug_backtrace'))
+ $backtrace = debug_backtrace();
+ $trace_msg = null;
+ if($backtrace !== null && isset($backtrace[1]))
+ {
+ $trace_msg = Console::formatColor($backtrace[1]['class'], ConsoleColors::LightGray);
+ $trace_msg .= $backtrace[1]['type'];
+ $trace_msg .= Console::formatColor($backtrace[1]['function'] . '()', ConsoleColors::LightGreen);
+ $trace_msg .= ' > ';
+ }
+
+ /** @noinspection PhpUnnecessaryStringCastInspection */
+ $message = self::setPrefix(LogLevel::Debug, (string)$trace_msg . $message);
+
+ self::out($message, $newline, true);
+ }
+
+ /**
+ * Output debug message
+ *
+ * @param string $message
+ * @param bool $newline
+ * @return void
+ */
+ public static function outVerbose(string $message, bool $newline=true): void
+ {
+ if(!ncc::cliMode())
+ return;
+
+ if(Main::getLogLevel() !== null && !Resolver::checkLogLevel(LogLevel::Verbose, Main::getLogLevel()))
+ return;
+
+ self::out(self::setPrefix(LogLevel::Verbose, $message), $newline, true);
+ }
+
+
/**
* Formats the text to have a different color and returns the formatted value
*
@@ -105,6 +215,11 @@
*/
public static function formatColor(string $input, string $color_code, bool $persist=true): string
{
+ if(Main::getArgs() !== null && isset(Main::getArgs()['no-color']))
+ {
+ return $input;
+ }
+
if($persist)
{
return $color_code . $input . ConsoleColors::Default;
@@ -125,6 +240,15 @@
if(!ncc::cliMode())
return;
+ if(Main::getLogLevel() !== null && !Resolver::checkLogLevel(LogLevel::Warning, Main::getLogLevel()))
+ return;
+
+ if(Main::getLogLevel() !== null && Resolver::checkLogLevel(LogLevel::Verbose, Main::getLogLevel()))
+ {
+ self::out(self::setPrefix(LogLevel::Warning, $message), $newline, true);
+ return;
+ }
+
self::out(self::formatColor('Warning: ', ConsoleColors::Yellow) . $message, $newline);
}
@@ -141,7 +265,17 @@
if(!ncc::cliMode())
return;
- self::out(self::formatColor(ConsoleColors::Red, 'Error: ') . $message, $newline);
+ if(Main::getLogLevel() !== null && !Resolver::checkLogLevel(LogLevel::Error, Main::getLogLevel()))
+ return;
+
+ if(Main::getLogLevel() !== null && Resolver::checkLogLevel(LogLevel::Verbose, Main::getLogLevel()))
+ {
+ self::out(self::setPrefix(LogLevel::Error, $message), $newline, true);
+ }
+ else
+ {
+ self::out(self::formatColor(ConsoleColors::Red, 'Error: ') . $message, $newline);
+ }
if($exit_code !== null)
{
@@ -162,11 +296,12 @@
if(!ncc::cliMode())
return;
- if(strlen($message) > 0)
+ if(strlen($message) > 0 && Resolver::checkLogLevel(LogLevel::Error, Main::getLogLevel()))
{
- self::out(self::formatColor('Error: ' . $message, ConsoleColors::Red));
+ self::out(PHP_EOL . self::formatColor('Error: ', ConsoleColors::Red) . $message);
}
+ Console::out(PHP_EOL . '===== Exception Details =====');
self::outExceptionDetails($e);
if($exit_code !== null)
@@ -189,6 +324,39 @@
$trace_header = self::formatColor($e->getFile() . ':' . $e->getLine(), ConsoleColors::Magenta);
$trace_error = self::formatColor('error: ', ConsoleColors::Red);
self::out($trace_header . ' ' . $trace_error . $e->getMessage());
+ self::out(sprintf('Error code: %s', $e->getCode()));
+ $trace = $e->getTrace();
+ if(count($trace) > 1)
+ {
+ self::out('Stack Trace:');
+ foreach($trace as $item)
+ {
+ self::out( ' - ' . self::formatColor($item['file'], ConsoleColors::Red) . ':' . $item['line']);
+ }
+ }
+
+ if(Main::getArgs() !== null)
+ {
+ if(isset(Main::getArgs()['dbg-ex']))
+ {
+ try
+ {
+ $dump = [
+ 'constants' => ncc::getConstants(),
+ 'exception' => Functions::exceptionToArray($e)
+ ];
+ IO::fwrite(getcwd() . DIRECTORY_SEPARATOR . time() . '.json', json_encode($dump, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT), 0777);
+ }
+ catch (Exception $e)
+ {
+ self::outWarning('Cannot dump exception details, ' . $e->getMessage());
+ }
+ }
+ else
+ {
+ self::out('You can pass on \'--dbg-ex\' option to dump the exception details to a json file');
+ }
+ }
}
/**
diff --git a/src/ncc/Utilities/Functions.php b/src/ncc/Utilities/Functions.php
index e0b74d9..1838950 100644
--- a/src/ncc/Utilities/Functions.php
+++ b/src/ncc/Utilities/Functions.php
@@ -2,9 +2,22 @@
namespace ncc\Utilities;
+ use Exception;
+ use ncc\Abstracts\Runners;
+ use ncc\Abstracts\Scopes;
+ use ncc\Classes\PhpExtension\Runner;
+ use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\FileNotFoundException;
+ use ncc\Exceptions\InvalidScopeException;
+ use ncc\Exceptions\IOException;
use ncc\Exceptions\MalformedJsonException;
+ use ncc\Exceptions\UnsupportedRunnerException;
+ use ncc\Managers\CredentialManager;
+ use ncc\Managers\PackageLockManager;
use ncc\Objects\CliHelpSection;
+ use ncc\Objects\Package\ExecutionUnit;
+ use ncc\Objects\ProjectConfiguration\ExecutionPolicy;
+ use ncc\ThirdParty\Symfony\Filesystem\Filesystem;
/**
* @author Zi Xing Narrakas
@@ -22,11 +35,15 @@
* Calculates a byte-code representation of the input using CRC32
*
* @param string $input
- * @return int
+ * @return string
*/
- public static function cbc(string $input): int
+ public static function cbc(string $input): string
{
- return hexdec(hash('crc32', $input, true));
+ $cache = RuntimeCache::get("cbc_$input");
+ if($cache !== null)
+ return $cache;
+
+ return RuntimeCache::set("cbc_$input", hash('crc32', $input, true));
}
/**
@@ -36,6 +53,7 @@
* @param array $data
* @param string $select
* @return mixed|null
+ * @noinspection PhpMissingReturnTypeInspection
*/
public static function array_bc(array $data, string $select)
{
@@ -54,7 +72,9 @@
* @param string $path
* @param int $flags
* @return mixed
+ * @throws AccessDeniedException
* @throws FileNotFoundException
+ * @throws IOException
* @throws MalformedJsonException
* @noinspection PhpMissingReturnTypeInspection
*/
@@ -65,7 +85,7 @@
throw new FileNotFoundException($path);
}
- return self::loadJson(file_get_contents($path), $flags);
+ return self::loadJson(IO::fread($path), $flags);
}
/**
@@ -98,6 +118,7 @@
* @return string
* @throws MalformedJsonException
* @noinspection PhpMissingParamTypeInspection
+ * @noinspection PhpUnusedLocalVariableInspection
*/
public static function encodeJson($value, int $flags=0): string
{
@@ -123,7 +144,7 @@
* @return void
* @throws MalformedJsonException
*/
- public static function encodeJsonFile($value, string $path, int $flags=0)
+ public static function encodeJsonFile($value, string $path, int $flags=0): void
{
file_put_contents($path, self::encodeJson($value, $flags));
}
@@ -160,16 +181,19 @@
* @param string $copyright
* @param bool $basic_ascii
* @return string
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
*/
public static function getBanner(string $version, string $copyright, bool $basic_ascii=false): string
{
if($basic_ascii)
{
- $banner = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'banner_basic');
+ $banner = IO::fread(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'banner_basic');
}
else
{
- $banner = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'banner_extended');
+ $banner = IO::fread(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'banner_extended');
}
$banner_version = str_pad($version, 21);
@@ -219,26 +243,125 @@
}
/**
- * Converts the input string into a Bas64 encoding before returning it as a
- * byte representation
- *
- * @param string $string
- * @return string
+ * @param string $path
+ * @param ExecutionPolicy $policy
+ * @return ExecutionUnit
+ * @throws UnsupportedRunnerException
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
*/
- public static function byteEncode(string $string): string
+ public static function compileRunner(string $path, ExecutionPolicy $policy): ExecutionUnit
{
- return convert_uuencode(Base64::encode($string));
+ return match (strtolower($policy->Runner)) {
+ Runners::php => Runner::processUnit($path, $policy),
+ default => throw new UnsupportedRunnerException('The runner \'' . $policy->Runner . '\' is not supported'),
+ };
}
/**
- * Decodes the input string back into the normal string representation that was encoded
- * by the byteEncode() function
+ * Returns an array representation of the exception
*
- * @param string $string
+ * @param Exception $e
+ * @return array
+ */
+ public static function exceptionToArray(Exception $e): array
+ {
+ $exception = [
+ 'message' => $e->getMessage(),
+ 'code' => $e->getCode(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'trace' => null,
+ 'trace_string' => $e->getTraceAsString(),
+ ];
+
+ if($e->getPrevious() !== null)
+ {
+ $exception['trace'] = self::exceptionToArray($e);
+ }
+
+ return $exception;
+ }
+
+ /**
+ * Takes the input bytes and converts it to a readable unit representation
+ *
+ * @param int $bytes
+ * @param int $decimals
* @return string
*/
- public static function byteDecode(string $string): string
+ public static function b2u(int $bytes, int $decimals=2): string
{
- return base64_decode(convert_uudecode($string));
+ $size = array('B','kB','MB','GB','TB','PB','EB','ZB','YB');
+ $factor = floor((strlen($bytes) - 1) / 3);
+ return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$size[$factor];
+ }
+
+ /**
+ * Initializes NCC files
+ *
+ * @return void
+ * @throws AccessDeniedException
+ * @throws InvalidScopeException
+ */
+ public static function initializeFiles(): void
+ {
+ if(Resolver::resolveScope() !== Scopes::System)
+ throw new AccessDeniedException('Cannot initialize NCC files, insufficient permissions');
+
+ Console::outVerbose('Initializing NCC files');
+
+ $filesystem = new Filesystem();
+ if(!$filesystem->exists(PathFinder::getDataPath(Scopes::System)))
+ {
+ Console::outDebug(sprintf('Initializing %s', PathFinder::getDataPath(Scopes::System)));
+ $filesystem->mkdir(PathFinder::getDataPath(Scopes::System), 0755);
+ }
+
+ if(!$filesystem->exists(PathFinder::getCachePath(Scopes::System)))
+ {
+ Console::outDebug(sprintf('Initializing %s', PathFinder::getCachePath(Scopes::System)));
+ /** @noinspection PhpRedundantOptionalArgumentInspection */
+ $filesystem->mkdir(PathFinder::getCachePath(Scopes::System), 0777);
+ }
+
+ if(!$filesystem->exists(PathFinder::getRunnerPath(Scopes::System)))
+ {
+ Console::outDebug(sprintf('Initializing %s', PathFinder::getRunnerPath(Scopes::System)));
+ /** @noinspection PhpRedundantOptionalArgumentInspection */
+ $filesystem->mkdir(PathFinder::getRunnerPath(Scopes::System), 0755);
+ }
+
+ if(!$filesystem->exists(PathFinder::getPackagesPath(Scopes::System)))
+ {
+ Console::outDebug(sprintf('Initializing %s', PathFinder::getPackagesPath(Scopes::System)));
+ /** @noinspection PhpRedundantOptionalArgumentInspection */
+ $filesystem->mkdir(PathFinder::getPackagesPath(Scopes::System), 0755);
+ }
+
+ // Create credential store if needed
+ try
+ {
+ Console::outVerbose('Processing Credential Store');
+ $credential_manager = new CredentialManager();
+ $credential_manager->constructStore();
+ }
+ catch (Exception $e)
+ {
+ Console::outError('Cannot construct credential store, ' . $e->getMessage() . ' (Error Code: ' . $e->getCode() . ')');
+ }
+
+ // Create package lock if needed
+ try
+ {
+ Console::outVerbose('Processing Package Lock');
+ $package_manager = new PackageLockManager();
+ $package_manager->constructLockFile();
+ }
+ catch (Exception $e)
+ {
+ Console::outError('Cannot construct Package Lock, ' . $e->getMessage() . ' (Error Code: ' . $e->getCode() . ')');
+ }
}
}
\ No newline at end of file
diff --git a/src/ncc/Utilities/IO.php b/src/ncc/Utilities/IO.php
new file mode 100644
index 0000000..7b9c5be
--- /dev/null
+++ b/src/ncc/Utilities/IO.php
@@ -0,0 +1,97 @@
+getPath()))
+ {
+ throw new IOException(sprintf('Attempted to write data to a directory instead of a file: (%s)', $uri));
+ }
+
+ Console::outDebug(sprintf('writing %s of data to %s', Functions::b2u(strlen($data)), $uri));
+ $file = new SplFileObject($uri, $mode);
+
+ if (!$file->flock(LOCK_EX | LOCK_NB))
+ {
+ throw new IOException(sprintf('Unable to obtain lock on file: (%s)', $uri));
+ }
+ elseif (!$file->fwrite($data))
+ {
+ throw new IOException(sprintf('Unable to write content to file: (%s)... to (%s)', substr($data,0,25), $uri));
+ }
+ elseif (!$file->flock(LOCK_UN))
+ {
+ throw new IOException(sprintf('Unable to remove lock on file: (%s)', $uri));
+ }
+ elseif (!@chmod($uri, $perms))
+ {
+ throw new IOException(sprintf('Unable to chmod: (%s) to (%s)', $uri, $perms));
+ }
+ }
+
+ /**
+ * Attempts to read the specified file
+ *
+ * @param string $uri
+ * @param string $mode
+ * @param int|null $length
+ * @return string
+ * @throws AccessDeniedException
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public static function fread(string $uri, string $mode='r', ?int $length=null): string
+ {
+ $fileInfo = new SplFileInfo($uri);
+
+ if(!is_dir($fileInfo->getPath()))
+ {
+ throw new IOException(sprintf('Attempted to read data from a directory instead of a file: (%s)', $uri));
+ }
+
+ if(!file_exists($uri))
+ {
+ throw new FileNotFoundException(sprintf('Cannot find file %s', $uri));
+ }
+
+ if(!is_readable($uri))
+ {
+ throw new AccessDeniedException(sprintf('Insufficient permissions to read %s', $uri));
+ }
+
+ $file = new SplFileObject($uri, $mode);
+ if($length == null)
+ {
+ $length = $file->getSize();
+ }
+
+ if($length == 0)
+ {
+ return (string)null;
+ }
+
+ Console::outDebug(sprintf('reading %s', $uri));
+ return $file->fread($length);
+ }
+ }
\ No newline at end of file
diff --git a/src/ncc/Utilities/PathFinder.php b/src/ncc/Utilities/PathFinder.php
index 373c0fa..5e08303 100644
--- a/src/ncc/Utilities/PathFinder.php
+++ b/src/ncc/Utilities/PathFinder.php
@@ -145,49 +145,16 @@
}
/**
- * Returns the path where temporary files are stored
+ * Returns the path where Runner bin files are located and installed
*
* @param string $scope
* @param bool $win32
* @return string
* @throws InvalidScopeException
*/
- public static function getTmpPath(string $scope=Scopes::Auto, bool $win32=false): string
+ public static function getRunnerPath(string $scope=Scopes::Auto, bool $win32=false): string
{
- return self::getDataPath($scope, $win32) . DIRECTORY_SEPARATOR . 'tmp';
- }
-
- /**
- * Returns the configuration file
- *
- * @param string $scope
- * @param bool $win32
- * @return string
- * @throws InvalidScopeException
- */
- public static function getConfigurationFile(string $scope=Scopes::Auto, bool $win32=false): string
- {
- return self::getDataPath($scope, $win32) . DIRECTORY_SEPARATOR . 'config';
- }
-
- /**
- * Returns an array of all the configuration files the current user can access (For global-cross referencing)
- *
- * @param bool $win32
- * @return array
- * @throws InvalidScopeException
- */
- public static function getConfigurationFiles(bool $win32=false): array
- {
- $results = [];
- $results[] = self::getConfigurationFile(Scopes::System, $win32);
-
- if(!in_array(self::getConfigurationFile(Scopes::User, $win32), $results))
- {
- $results[] = self::getConfigurationFile(Scopes::User, $win32);
- }
-
- return $results;
+ return self::getDataPath($scope, $win32) . DIRECTORY_SEPARATOR . 'runners';
}
/**
@@ -235,18 +202,4 @@
{
return self::getDataPath($scope, $win32) . DIRECTORY_SEPARATOR . 'ext';
}
-
- /**
- * Returns the file path where files for the given extension is stored
- *
- * @param string $extension_name
- * @param string $scope
- * @param bool $win32
- * @return string
- * @throws InvalidScopeException
- */
- public static function getNamedExtensionPath(string $extension_name, string $scope=Scopes::Auto, bool $win32=false): string
- {
- return self::getExtensionPath($scope, $win32) . DIRECTORY_SEPARATOR . Security::sanitizeFilename($extension_name);
- }
}
\ No newline at end of file
diff --git a/src/ncc/Utilities/Resolver.php b/src/ncc/Utilities/Resolver.php
index 036248a..4d172a0 100644
--- a/src/ncc/Utilities/Resolver.php
+++ b/src/ncc/Utilities/Resolver.php
@@ -1,11 +1,21 @@
4096)
+ return true;
+
+ return false;
+ }
}
\ No newline at end of file
diff --git a/tests/package_lock/load_package_lock.php b/tests/package_lock/load_package_lock.php
new file mode 100644
index 0000000..258c388
--- /dev/null
+++ b/tests/package_lock/load_package_lock.php
@@ -0,0 +1,9 @@
+load();
+
+ var_dump($package_lock_manager->getPackageLock());