# pkgmngr

This merge introduces a fully implemented package manager, various optimizations and changes to extension compiler php.

 - Implemented proper log error levels for the CLI (So stuff like `--log` and `-l` will work, and when using `verbose` or `debug` levels, special effects such as the inline progress bar will not be shown.
 - Implemented `--no-color`
 - Various bug fixes and minor optimizations to the code-base
 - Fixed various installer bug
 - Added some more documentation (But it's unfinished)

## Resolved issues & Work Items checklist

A list of issues that this merge resolves or is in progress of resolving

 - [  issue #21 ](https://git.n64.cc/nosial/ncc/-/issues/21) **Add first-time install program files to be constructed** - 
Commit [b8c88bf4226e88760df221bb95070b8c34f3768d](b8c88bf422) addresses this issue by implementing a method that initializes non-existent directories & files and setting the correct permissions for their intended purpose
 - [  Work Item #14 ](https://git.n64.cc/nosial/ncc/-/work_items/15) **Implement a CLI command to display all installed packages on the system** - Commit [3e52a30096653e3bf212b4e7e36ce6f94d245ee3](3e52a30096) Implements a CLI command by implementing a method to walk through the PackageLock and display all the installed packages indicated by the current PackageLock state
 - [  Work Item #13 ](https://git.n64.cc/nosial/ncc/-/work_items/14) **Implement a CLI command for uninstalling packages** - By result this also resolves [Work Item #12](https://git.n64.cc/nosial/ncc/-/work_items/13) **Implement Uninstaller** with commit [8da4f532a146d626db1ee388eeb763ae14523589](8da4f532a1), [263f50a6680f26c47c04c92941ac9cc82b2b43d3](263f50a668) & [2390c6395cc60a5221a0c3064283200ed47273da](2390c6395c) by implementing uninstallation methods to the PackageManager & into the CLI Menu for Package Managing
 - [  Work Item #10 ](https://git.n64.cc/nosial/ncc/-/work_items/11) **Implement Installer**
 - [  Work item #9 ](https://git.n64.cc/nosial/ncc/-/work_items/10) **Create PackageManager Class and implement Install() method**
 - [  Work item #8 ](https://git.n64.cc/nosial/ncc/-/issues/8) **Implement Package Manager**
 - [  Work item #7 ](https://git.n64.cc/nosial/ncc/-/issues/7) **Implement PackageLock**
This commit is contained in:
Zi Xing 2022-11-25 06:50:48 +00:00
commit d03f31fe6a
90 changed files with 6796 additions and 847 deletions

3
.idea/scopes/NCC_Source_files.xml generated Normal file
View file

@ -0,0 +1,3 @@
<component name="DependencyValidationManager">
<scope name="NCC Source files" pattern="file[ncc]:src/ncc/Managers//*||file[ncc]:src/ncc/Abstracts//*||file[ncc]:src/ncc/Exceptions//*||file[ncc]:src/ncc/Runtime//*||file[ncc]:src/ncc/Extensions//*||file[ncc]:src/ncc/CLI//*||file[ncc]:src/ncc/Classes//*||file[ncc]:src/ncc/Objects//*||file[ncc]:src/ncc/Interfaces//*||file[ncc]:src/ncc/Utilities//*||file:src/ncc/ncc.php||file:src/ncc/ncc||file:src/ncc/banner_basic||file:src/ncc/autoload_spl.php||file:src/ncc/autoload.php" />
</component>

View file

@ -0,0 +1,3 @@
<component name="DependencyValidationManager">
<scope name="Third Party Source Files" pattern="file[ncc]:src/ncc/ThirdParty//*" />
</component>

BIN
assets/pkg_struct_1.0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View file

@ -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
<!-- TOC -->
* [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)
<!-- TOC -->
## 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
}
```

View file

@ -12,12 +12,8 @@
<?PHP
use ncc\Abstracts\Scopes;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\ComponentVersionNotFoundException;
use ncc\Abstracts\ConsoleColors;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\InvalidScopeException;
use ncc\Managers\CredentialManager;
use ncc\ncc;
use ncc\Objects\CliHelpSection;
use ncc\ThirdParty\Symfony\Filesystem\Exception\IOException;
@ -29,7 +25,7 @@
use ncc\ThirdParty\Symfony\Yaml\Yaml;
use ncc\Utilities\Console;
use ncc\Utilities\Functions;
use ncc\Utilities\PathFinder;
use ncc\Utilities\IO;
use ncc\Utilities\Resolver;
use ncc\Utilities\Validate;
use ncc\ZiProto\ZiProto;
@ -82,6 +78,7 @@
// Initialize NCC
try
{
define('NCC_CLI_MODE', 1);
ncc::initialize();
}
catch (FileNotFoundException|\ncc\Exceptions\RuntimeException $e)
@ -192,7 +189,7 @@
];
foreach($required_files as $path)
{
if(!file_exists($path))
if(!$NCC_FILESYSTEM->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\'');

View file

@ -0,0 +1,14 @@
<?php
namespace ncc\Abstracts;
abstract class ConstantReferences
{
const Assembly = 'assembly';
const Build = 'build';
const DateTime = 'date_time';
const Install = 'install';
}

View file

@ -6,12 +6,17 @@
use ncc\Exceptions\AutoloadGeneratorException;
use ncc\Exceptions\BuildConfigurationNotFoundException;
use ncc\Exceptions\BuildException;
use ncc\Exceptions\ComponentChecksumException;
use ncc\Exceptions\ComponentDecodeException;
use ncc\Exceptions\ComponentVersionNotFoundException;
use ncc\Exceptions\ConstantReadonlyException;
use ncc\Exceptions\DirectoryNotFoundException;
use ncc\Exceptions\ExecutionUnitNotFoundException;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\InstallationException;
use ncc\Exceptions\InvalidConstantNameException;
use ncc\Exceptions\InvalidCredentialsEntryException;
use ncc\Exceptions\InvalidExecutionPolicyName;
use ncc\Exceptions\InvalidPackageException;
use ncc\Exceptions\InvalidPackageNameException;
use ncc\Exceptions\InvalidProjectBuildConfiguration;
@ -21,12 +26,25 @@
use ncc\Exceptions\InvalidScopeException;
use ncc\Exceptions\InvalidVersionConfigurationException;
use ncc\Exceptions\InvalidVersionNumberException;
use ncc\Exceptions\IOException;
use ncc\Exceptions\MalformedJsonException;
use ncc\Exceptions\NoAvailableUnitsException;
use ncc\Exceptions\NoUnitsFoundException;
use ncc\Exceptions\PackageAlreadyInstalledException;
use ncc\Exceptions\PackageLockException;
use ncc\Exceptions\PackageNotFoundException;
use ncc\Exceptions\PackageParsingException;
use ncc\Exceptions\ProjectAlreadyExistsException;
use ncc\Exceptions\ProjectConfigurationNotFoundException;
use ncc\Exceptions\ResourceChecksumException;
use ncc\Exceptions\RunnerExecutionException;
use ncc\Exceptions\RuntimeException;
use ncc\Exceptions\UndefinedExecutionPolicyException;
use ncc\Exceptions\UnsupportedComponentTypeException;
use ncc\Exceptions\UnsupportedCompilerExtensionException;
use ncc\Exceptions\UnsupportedPackageException;
use ncc\Exceptions\UnsupportedRunnerException;
use ncc\Exceptions\VersionNotFoundException;
/**
* @author Zi Xing Narrakas
@ -174,6 +192,96 @@
*/
const BuildException = -1727;
/**
* @see PackageParsingException
*/
const PackageParsingException = -1728;
/**
* @see PackageLockException
*/
const PackageLockException = -1729;
/**
* @see InstallationException
*/
const InstallationException = -1730;
/**
* @see UnsupportedComponentTypeException
*/
const UnsupportedComponentTypeException = -1731;
/**
* @see ComponentDecodeException
*/
const ComponentDecodeException = -1732;
/**
* @see ComponentChecksumException
*/
const ComponentChecksumException = -1733;
/**
* @see ResourceChecksumException
*/
const ResourceChecksumException = -1734;
/**
* @see IOException
*/
const IOException = -1735;
/**
* @see UnsupportedRunnerException
*/
const UnsupportedRunnerException = -1736;
/**
* @see VersionNotFoundException
*/
const VersionNotFoundException = -1737;
/**
* @see UndefinedExecutionPolicyException
*/
const UndefinedExecutionPolicyException = -1738;
/**
* @see InvalidExecutionPolicyName
*/
const InvalidExecutionPolicyName = -1739;
/**
* @see ProjectConfigurationNotFoundException
*/
const ProjectConfigurationNotFoundException = -1740;
/**
* @see RunnerExecutionException
*/
const RunnerExecutionException = -1741;
/**
* @see NoAvailableUnitsException
*/
const NoAvailableUnitsException = -1742;
/**
* @see ExecutionUnitNotFoundException
*/
const ExecutionUnitNotFoundException = -1743;
/**
* @see PackageAlreadyInstalledException
*/
const PackageAlreadyInstalledException = -1744;
/**
* @see PackageNotFoundException
*/
const PackageNotFoundException = -1745;
/**
* All the exception codes from NCC
*/
@ -205,6 +313,23 @@
self::InvalidPropertyValueException,
self::InvalidVersionConfigurationException,
self::UnsupportedExtensionVersionException,
self::BuildException
self::BuildException,
self::PackageParsingException,
self::PackageLockException,
self::InstallationException,
self::UnsupportedComponentTypeException,
self::ComponentDecodeException,
self::ResourceChecksumException,
self::IOException,
self::UnsupportedRunnerException,
self::VersionNotFoundException,
self::UndefinedExecutionPolicyException,
self::InvalidExecutionPolicyName,
self::ProjectConfigurationNotFoundException,
self::RunnerExecutionException,
self::NoAvailableUnitsException,
self::ExecutionUnitNotFoundException,
self::PackageAlreadyInstalledException,
self::PackageNotFoundException
];
}

View file

@ -0,0 +1,30 @@
<?php
namespace ncc\Abstracts;
abstract class LogLevel
{
const Silent = 'silent';
const Verbose = 'verbose';
const Debug = 'debug';
const Info = 'info';
const Warning = 'warn';
const Error = 'error';
const Fatal = 'fatal';
const All = [
self::Silent,
self::Verbose,
self::Debug,
self::Info,
self::Warning,
self::Error,
self::Fatal,
];
}

View file

@ -5,4 +5,6 @@
abstract class BuildConfigurationValues
{
const DefaultConfiguration = 'default';
const AllConfigurations = 'all';
}

View file

@ -0,0 +1,12 @@
<?php
namespace ncc\Abstracts;
abstract class PackageStructureVersions
{
const _1_0 = '1.0';
const ALL = [
self::_1_0
];
}

View file

@ -23,4 +23,6 @@
const WindowsPath = '/^(([%][^\/:*?<>""|]*[%])|([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';
}

View file

@ -0,0 +1,8 @@
<?php
namespace ncc\Abstracts;
abstract class Runners
{
const php = 'php';
}

View file

@ -0,0 +1,51 @@
<?php
namespace ncc\Abstracts\SpecialConstants;
abstract class AssemblyConstants
{
/**
* Assembly's Name Property
*/
const AssemblyName = '%ASSEMBLY.NAME%';
/**
* Assembly's Package Property
*/
const AssemblyPackage = '%ASSEMBLY.PACKAGE%';
/**
* Assembly's Description Property
*/
const AssemblyDescription = '%ASSEMBLY.DESCRIPTION%';
/**
* Assembly's Company Property
*/
const AssemblyCompany = '%ASSEMBLY.COMPANY%';
/**
* Assembly's Product Property
*/
const AssemblyProduct = '%ASSEMBLY.PRODUCT%';
/**
* Assembly's Copyright Property
*/
const AssemblyCopyright = '%ASSEMBLY.COPYRIGHT%';
/**
* Assembly's Trademark Property
*/
const AssemblyTrademark = '%ASSEMBLY.TRADEMARK%';
/**
* Assembly's Version Property
*/
const AssemblyVersion = '%ASSEMBLY.VERSION%';
/**
* Assembly's UUID property
*/
const AssemblyUid = '%ASSEMBLY.UID%';
}

View file

@ -0,0 +1,26 @@
<?php
namespace ncc\Abstracts\SpecialConstants;
abstract class BuildConstants
{
/**
* The Unix Timestamp for when the package was compiled
*/
const CompileTimestamp = '%COMPILE_TIMESTAMP%';
/**
* The version of NCC that was used to compile the package
*/
const NccBuildVersion = '%NCC_BUILD_VERSION%';
/**
* NCC Build Flags exploded into spaces
*/
const NccBuildFlags = '%NCC_BUILD_FLAGS%';
/**
* NCC Build Branch
*/
const NccBuildBranch = '%NCC_BUILD_BRANCH%';
}

View file

@ -0,0 +1,162 @@
<?php
namespace ncc\Abstracts\SpecialConstants;
abstract class DateTimeConstants
{
// Day Format
/**
* Day of the month, 2 digits with leading zeros
*/
const d = '%d%'; // 01 through 31
/**
* A textual representation of a day, three letters
*/
const D = '%D%'; // Mon through Sun
/**
* Day of the month without leading zeros
*/
const j = '%j%'; // 1 through 31
/**
* A full textual representation of the day of the week
*/
const l = '%l%'; // Sunday through Saturday
/**
* ISO 8601 numeric representation of the day of the week
*/
const N = '%N%'; // 1 (Monday) to 7 (Sunday)
/**
* English ordinal suffix for the day of the month, 2 characters
*/
const S = '%S%'; // st, nd, rd, th
/**
* Numeric representation of the day of the week
*/
const w = '%w%'; // 0 (sunday) through 6 (Saturday)
/**
* The day of the year (starting from 0)
*/
const z = '%z%'; // 0 through 365
// Week Format
/**
* ISO 8601 week number of year, weeks starting on Monday
*/
const W = '%W%'; // 42 (42nd week in year)
// Month Format
/**
* A full textual representation of a month, such as January or March
*/
const F = '%F%'; // January through December
/**
* Numeric representation of a month, with leading zeros
*/
const m = '%m%'; // 01 through 12
/**
* A short textual representation of a month, three letters
*/
const M = '%M%'; // Jan through Dec
/**
* Numeric representation of a month, without leading zeros
*/
const n = '%n%'; // 1 through 12
/**
* Number of days in the given month
*/
const t = '%t%'; // 28 through 31
// Year format
/**
* Whether it's a leap year
*/
const L = '%L%'; // 1 (leap year), 0 otherwise
/**
* ISO 8601 week-numbering year. This has the same value as Y,
* except that if the ISO week number (W) belongs to the previous
* or next year, that year is used instead.
*/
const o = '%o%'; // Same as Y, except that it use week number to decide which year it falls onto
/**
* A full numeric representation of a year, at least 4 digits, with - for years BCE.
*/
const Y = '%Y%'; // 1991, 2012, 2014, ...
/**
* A two digit representation of a year
*/
const y = '%y%'; // 91, 12, 14, ...
// Time Format
/**
* Lowercase Ante meridiem and Post meridiem
*/
const a = '%a%'; // am or pm
/**
* Uppercase Ante meridiem and Post meridiem
*/
const A = '%A%'; // AM or PM
/**
* Swatch Internet time
*/
const B = '%B%'; // 000 through 999
/**
* 12-hour format of an hour without leading zeros
*/
const g = '%g%'; // 1 through 12
/**
* 24-hour format of an hour without leading zeros
*/
const G = '%G%'; // 0 through 23
/**
* 12-hour format of an hour with leading zeros
*/
const h = '%h%'; // 01 through 12
/**
* 24-hour format of an hour with leading zeros
*/
const H = '%H%'; // 01 through 23
/**
* Minutes with leading zeros
*/
const i = '%i%'; // 01 through 59
/**
* Seconds with leading zeros
*/
const s = '%s%'; // 00 through 59
// DateTime format
const c = '%c%'; // 2004-02-12T15:19:21
const r = '%r%'; // Thu, 21 Dec 2000 16:01:07
const u = '%u%'; // Unix Timestamp (seconds since Jan 1 1970 00:00:00)
}

View file

@ -0,0 +1,14 @@
<?php
namespace ncc\Abstracts\SpecialConstants;
abstract class InstallConstants
{
const InstallationPath = '%INSTALL_PATH%';
const BinPath = '%INSTALL_PATH.BIN%';
const SourcePath = '%INSTALL_PATH.SRC%';
const DataPath = '%INSTALL_PATH.DATA%';
}

View file

@ -0,0 +1,8 @@
<?php
namespace ncc\Abstracts\SpecialConstants;
abstract class RuntimeConstants
{
const CWD = '%CWD%';
}

View file

@ -1,32 +0,0 @@
<?php
namespace ncc\Abstracts;
abstract class SpecialFormat
{
const AssemblyName = '%ASSEMBLY.NAME%';
const AssemblyPackage = '%ASSEMBLY.PACKAGE%';
const AssemblyDescription = '%ASSEMBLY.DESCRIPTION%';
const AssemblyCompany = '%ASSEMBLY.COMPANY%';
const AssemblyProduct = '%ASSEMBLY.PRODUCT%';
const AssemblyCopyright = '%ASSEMBLY.COPYRIGHT%';
const AssemblyTrademark = '%ASSEMBLY.TRADEMARK%';
const AssemblyVersion = '%ASSEMBLY.VERSION%';
const AssemblyUid = '%ASSEMBLY.UID%';
const CompileTimestamp = '%COMPILE_TIMESTAMP%';
const NccBuildVersion = '%NCC_BUILD_VERSION%';
const NccBuildArgs = '%NCC_BUILD_FLAGS%';
const NccBuildBranch = '%NCC_BUILD_BRANCH%';
}

View file

@ -13,4 +13,9 @@
* The current version of the package structure file format
*/
const PackageStructureVersion = '1.0';
/**
* The current version of the package lock structure file format
*/
const PackageLockVersion = '1.0.0';
}

View file

@ -3,16 +3,9 @@
namespace ncc\CLI;
use Exception;
use JetBrains\PhpStorm\NoReturn;
use ncc\Abstracts\CompilerExtensions;
use ncc\Abstracts\Options\BuildConfigurationValues;
use ncc\Classes\PhpExtension\Compiler;
use ncc\Exceptions\BuildConfigurationNotFoundException;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\MalformedJsonException;
use ncc\Interfaces\CompilerInterface;
use ncc\Managers\ProjectManager;
use ncc\Objects\CliHelpSection;
use ncc\Objects\ProjectConfiguration;
use ncc\Utilities\Console;
class BuildMenu
@ -22,8 +15,9 @@
*
* @param $args
* @return void
* @noinspection PhpNoReturnAttributeCanBeAddedInspection
*/
#[NoReturn] public static function start($args): void
public static function start($args): void
{
if(isset($args['help']))
{
@ -48,12 +42,6 @@
{
$path_arg = ($args['path'] ?? $args['p']);
// Append trailing slash
if(substr($path_arg, -1) !== DIRECTORY_SEPARATOR)
{
$path_arg .= DIRECTORY_SEPARATOR;
}
// Check if the path exists
if(!file_exists($path_arg) || !is_dir($path_arg))
{
@ -67,92 +55,38 @@
$project_path = getcwd();
}
// Load the project
try
{
$ProjectConfiguration = ProjectConfiguration::fromFile($project_path . DIRECTORY_SEPARATOR . 'project.json');
}
catch (FileNotFoundException $e)
{
Console::outException('Cannot find the file \'project.json\'', $e, 1);
return;
}
catch (MalformedJsonException $e)
{
Console::outException('The file \'project.json\' contains malformed json data, please verify the syntax of the file', $e, 1);
return;
}
// Select the correct compiler for the specified extension
switch(strtolower($ProjectConfiguration->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);
}
/**

View file

@ -1,31 +0,0 @@
<?php
namespace ncc\CLI;
class Functions
{
/**
* Simple print function with builtin EOL terminator
*
* @param string $out
* @param bool $eol
* @param int $padding
* @param int $pad_type
* @param string $pad_string
* @return void
*/
public static function print(string $out, bool $eol=true, int $padding=0, int $pad_type=0, string $pad_string=' ')
{
if($padding > 0)
{
$out = str_pad($out, $padding, $pad_string, $pad_type);
}
if($eol)
{
$out = $out . PHP_EOL;
}
print($out);
}
}

View file

@ -1,8 +1,11 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\CLI;
use Exception;
use ncc\Abstracts\LogLevel;
use ncc\Abstracts\NccBuildFlags;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\RuntimeException;
@ -12,6 +15,16 @@
class Main
{
/**
* @var array
*/
private static $args;
/**
* @var string|null
*/
private static $log_level;
/**
* Executes the main CLI process
*
@ -20,9 +33,9 @@
*/
public static function start($argv): void
{
$args = Resolver::parseArguments(implode(' ', $argv));
self::$args = Resolver::parseArguments(implode(' ', $argv));
if(isset($args['ncc-cli']))
if(isset(self::$args['ncc-cli']))
{
// Initialize NCC
try
@ -41,6 +54,38 @@
// Define CLI stuff
define('NCC_CLI_MODE', 1);
if(isset(self::$args['l']) || isset(self::$args['log-level']))
{
switch(strtolower(self::$args['l'] ?? self::$args['log-level']))
{
case LogLevel::Silent:
case LogLevel::Fatal:
case LogLevel::Error:
case LogLevel::Warning:
case LogLevel::Info:
case LogLevel::Debug:
case LogLevel::Verbose:
self::$log_level = strtolower(self::$args['l'] ?? self::$args['log-level']);
break;
default:
Console::outWarning('Unknown log level: ' . (self::$args['l'] ?? self::$args['log-level']) . ', using \'info\'');
self::$log_level = LogLevel::Info;
break;
}
}
else
{
self::$log_level = LogLevel::Info;
}
if(Resolver::checkLogLevel(self::$log_level, LogLevel::Debug))
{
Console::outDebug('Debug logging enabled');
Console::outDebug(sprintf('consts: %s', json_encode(ncc::getConstants(), JSON_UNESCAPED_SLASHES)));
Console::outDebug(sprintf('args: %s', json_encode(self::$args, JSON_UNESCAPED_SLASHES)));
}
if(in_array(NccBuildFlags::Unstable, NCC_VERSION_FLAGS))
{
Console::outWarning('This is an unstable build of NCC, expect some features to not work as expected');
@ -48,37 +93,59 @@
try
{
switch(strtolower($args['ncc-cli']))
switch(strtolower(self::$args['ncc-cli']))
{
default:
Console::out('Unknown command ' . strtolower($args['ncc-cli']));
Console::out('Unknown command ' . strtolower(self::$args['ncc-cli']));
exit(1);
case 'project':
ProjectMenu::start($args);
ProjectMenu::start(self::$args);
exit(0);
case 'build':
BuildMenu::start($args);
BuildMenu::start(self::$args);
exit(0);
case 'credential':
CredentialMenu::start($args);
CredentialMenu::start(self::$args);
exit(0);
case 'package':
PackageManagerMenu::start(self::$args);
exit(0);
case '1':
case 'help':
HelpMenu::start($args);
HelpMenu::start(self::$args);
exit(0);
}
}
catch(Exception $e)
{
Console::outException('Error: ' . $e->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;
}
}

View file

@ -0,0 +1,345 @@
<?php
namespace ncc\CLI;
use Exception;
use ncc\Abstracts\ConsoleColors;
use ncc\Abstracts\Scopes;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\PackageLockException;
use ncc\Exceptions\VersionNotFoundException;
use ncc\Managers\PackageManager;
use ncc\Objects\CliHelpSection;
use ncc\Objects\Package;
use ncc\Utilities\Console;
use ncc\Utilities\Functions;
use ncc\Utilities\Resolver;
class PackageManagerMenu
{
/**
* Displays the main help menu
*
* @param $args
* @return void
*/
public static function start($args): void
{
if(isset($args['install']))
{
try
{
self::installPackage($args);
return;
}
catch (Exception $e)
{
Console::outException('Installation Failed', $e, 1);
return;
}
}
if(isset($args['uninstall']))
{
try
{
self::uninstallPackage($args);
return;
}
catch (Exception $e)
{
Console::outException('Uninstallation Failed', $e, 1);
return;
}
}
if(isset($args['list']))
{
try
{
self::getInstallPackages();
return;
}
catch(Exception $e)
{
Console::outException('List Failed', $e, 1);
return;
}
}
self::displayOptions();
exit(0);
}
/**
* Displays all installed packages
*
* @return void
*/
private static function getInstallPackages(): void
{
$package_manager = new PackageManager();
try
{
$installed_packages = $package_manager->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));
}
}
}

View file

@ -0,0 +1,129 @@
<?php
namespace ncc\Classes\NccExtension;
use ncc\Abstracts\SpecialConstants\BuildConstants;
use ncc\Abstracts\SpecialConstants\DateTimeConstants;
use ncc\Abstracts\SpecialConstants\InstallConstants;
use ncc\Abstracts\SpecialConstants\AssemblyConstants;
use ncc\Objects\InstallationPaths;
use ncc\Objects\Package;
use ncc\Objects\ProjectConfiguration;
use ncc\Objects\ProjectConfiguration\Assembly;
class ConstantCompiler
{
/**
* Compiles assembly constants about the project (Usually used during compiling time)
*
* @param string|null $input
* @param Assembly $assembly
* @return string|null
* @noinspection PhpUnnecessaryLocalVariableInspection
*/
public static function compileAssemblyConstants(?string $input, Assembly $assembly): ?string
{
if($input == null)
return null;
$input = str_replace(AssemblyConstants::AssemblyName, $assembly->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;
}
}

View file

@ -0,0 +1,307 @@
<?php
namespace ncc\Classes\NccExtension;
use Exception;
use ncc\Abstracts\CompilerExtensions;
use ncc\Abstracts\ConstantReferences;
use ncc\Abstracts\LogLevel;
use ncc\Abstracts\Options\BuildConfigurationValues;
use ncc\Classes\PhpExtension\Compiler;
use ncc\CLI\Main;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\BuildConfigurationNotFoundException;
use ncc\Exceptions\BuildException;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\IOException;
use ncc\Exceptions\MalformedJsonException;
use ncc\Exceptions\PackagePreparationFailedException;
use ncc\Exceptions\ProjectConfigurationNotFoundException;
use ncc\Exceptions\UnsupportedCompilerExtensionException;
use ncc\Exceptions\UnsupportedRunnerException;
use ncc\Interfaces\CompilerInterface;
use ncc\Managers\ProjectManager;
use ncc\ncc;
use ncc\Objects\Package;
use ncc\Objects\ProjectConfiguration;
use ncc\Objects\ProjectConfiguration\Assembly;
use ncc\ThirdParty\Symfony\Filesystem\Filesystem;
use ncc\Utilities\Console;
use ncc\Utilities\Functions;
use ncc\Utilities\Resolver;
class PackageCompiler
{
/**
* Compiles the project into a package
*
* @param ProjectManager $manager
* @param string $build_configuration
* @return string
* @throws AccessDeniedException
* @throws BuildConfigurationNotFoundException
* @throws BuildException
* @throws FileNotFoundException
* @throws IOException
* @throws MalformedJsonException
* @throws PackagePreparationFailedException
* @throws ProjectConfigurationNotFoundException
* @throws UnsupportedCompilerExtensionException
* @throws UnsupportedRunnerException
*/
public static function compile(ProjectManager $manager, string $build_configuration=BuildConfigurationValues::DefaultConfiguration): string
{
$configuration = $manager->getProjectConfiguration();
if(Main::getLogLevel() !== null && Resolver::checkLogLevel(LogLevel::Debug, Main::getLogLevel()))
{
foreach($configuration->Assembly->toArray() as $prop => $value)
Console::outDebug(sprintf('assembly.%s: %s', $prop, ($value ?? 'n/a')));
foreach($configuration->Project->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;
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace ncc\Classes\NccExtension;
use ncc\Abstracts\Runners;
use ncc\Abstracts\Scopes;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\ExecutionUnitNotFoundException;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\IOException;
use ncc\Exceptions\NoAvailableUnitsException;
use ncc\Exceptions\RunnerExecutionException;
use ncc\Exceptions\UnsupportedRunnerException;
use ncc\Managers\ExecutionPointerManager;
use ncc\Objects\ExecutionPointers\ExecutionPointer;
use ncc\Objects\Package\ExecutionUnit;
use ncc\Utilities\PathFinder;
use ncc\Utilities\Resolver;
class Runner
{
/**
* Temporarily executes an execution unit, removes it once it is executed
*
* @param string $package
* @param string $version
* @param ExecutionUnit $unit
* @return void
* @throws AccessDeniedException
* @throws UnsupportedRunnerException
* @throws ExecutionUnitNotFoundException
* @throws FileNotFoundException
* @throws IOException
* @throws NoAvailableUnitsException
* @throws RunnerExecutionException
*/
public static function temporaryExecute(string $package, string $version, ExecutionUnit $unit)
{
if(Resolver::resolveScope() !== Scopes::System)
throw new AccessDeniedException('Cannot temporarily execute a unit with insufficent permissions');
$ExecutionPointerManager = new ExecutionPointerManager();
$ExecutionPointerManager->addUnit($package, $version, $unit, true);
$ExecutionPointerManager->executeUnit($package, $version, $unit->ExecutionPolicy->Name);
$ExecutionPointerManager->cleanTemporaryUnits();;
}
}

View file

@ -1,42 +0,0 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Classes;
use ncc\Exceptions\FileNotFoundException;
class PackageParser
{
/**
* @var string
*/
private $PackagePath;
/**
* Package Parser public constructor.
*
* @param string $path
*/
public function __construct(string $path)
{
$this->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);
}
}

View file

@ -1,120 +0,0 @@
<?php
namespace ncc\Classes\PhpExtension;
use ArrayIterator;
use ncc\Abstracts\ComponentFileExtensions;
use ncc\Exceptions\AutoloadGeneratorException;
use ncc\Exceptions\NoUnitsFoundException;
use ncc\Objects\ProjectConfiguration;
use ncc\ThirdParty\theseer\Autoload\CollectorException;
use ncc\ThirdParty\theseer\Autoload\CollectorResult;
use ncc\ThirdParty\theseer\Autoload\Config;
use ncc\ThirdParty\theseer\Autoload\Factory;
use ncc\ThirdParty\theseer\DirectoryScanner\Exception;
use ncc\Utilities\Console;
use SplFileInfo;
class AutoloaderGenerator
{
/**
* @var ProjectConfiguration
*/
private ProjectConfiguration $project;
/**
* @param ProjectConfiguration $project
*/
public function __construct(ProjectConfiguration $project)
{
$this->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();
}
}

View file

@ -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 <arne@blankerts.de> All rights reserved.');
}
Console::out('Scanning project files');
Console::out('theseer\DirectoryScanner - Copyright (c) 2009-2014 Arne Blankerts <arne@blankerts.de> 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;
}
}

View file

@ -0,0 +1,342 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Classes\PhpExtension;
use ArrayIterator;
use Exception;
use ncc\Abstracts\ComponentDataType;
use ncc\Abstracts\ComponentFileExtensions;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\ComponentChecksumException;
use ncc\Exceptions\ComponentDecodeException;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\IOException;
use ncc\Exceptions\NoUnitsFoundException;
use ncc\Exceptions\ResourceChecksumException;
use ncc\Exceptions\UnsupportedComponentTypeException;
use ncc\Interfaces\InstallerInterface;
use ncc\Objects\InstallationPaths;
use ncc\Objects\Package;
use ncc\Objects\Package\Component;
use ncc\ThirdParty\nikic\PhpParser\Comment;
use ncc\ThirdParty\nikic\PhpParser\Node;
use ncc\ThirdParty\nikic\PhpParser\PrettyPrinter\Standard;
use ncc\ThirdParty\theseer\Autoload\CollectorException;
use ncc\ThirdParty\theseer\Autoload\CollectorResult;
use ncc\ThirdParty\theseer\Autoload\Config;
use ncc\ThirdParty\theseer\Autoload\Factory;
use ncc\Utilities\Base64;
use ncc\Utilities\IO;
use ReflectionClass;
use ReflectionException;
use RuntimeException;
use SplFileInfo;
use function is_array;
use function is_string;
class Installer implements InstallerInterface
{
/**
* @var ReflectionClass[] Node type to reflection class map
*/
private $reflectionClassCache;
/**
* @var Package
*/
private $package;
/**
* @inheritDoc
*/
public function __construct(Package $package)
{
$this->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();
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace ncc\Classes\PhpExtension;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\IOException;
use ncc\Exceptions\RunnerExecutionException;
use ncc\Interfaces\RunnerInterface;
use ncc\Objects\ExecutionPointers\ExecutionPointer;
use ncc\Objects\Package\ExecutionUnit;
use ncc\Objects\ProjectConfiguration\ExecutionPolicy;
use ncc\ThirdParty\Symfony\Process\ExecutableFinder;
use ncc\ThirdParty\Symfony\Process\Process;
use ncc\Utilities\Base64;
use ncc\Utilities\IO;
class Runner implements RunnerInterface
{
/**
* @param string $path
* @param ExecutionPolicy $policy
* @return ExecutionUnit
* @throws FileNotFoundException
* @throws AccessDeniedException
* @throws IOException
*/
public static function processUnit(string $path, ExecutionPolicy $policy): ExecutionUnit
{
$execution_unit = new ExecutionUnit();
$target_file = $path;
if(!file_exists($target_file) && !is_file($target_file))
throw new FileNotFoundException($target_file);
$policy->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]);
}
}

View file

@ -0,0 +1,28 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class ComponentChecksumException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::ComponentChecksumException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,28 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class ComponentDecodeException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::ComponentDecodeException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,8 @@
<?php
namespace ncc\Exceptions;
class ExecutionUnitNotFoundException extends \Exception
{
}

View file

@ -0,0 +1,28 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class IOException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::IOException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,28 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class InstallationException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::InstallationException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace ncc\Exceptions;
use Exception;
use Throwable;
class InvalidExecutionPolicyName extends Exception
{
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View file

@ -0,0 +1,28 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class NoAvailableUnitsException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::NoAvailableUnitsException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,28 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class PackageAlreadyInstalledException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::PackageAlreadyInstalledException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,28 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class PackageLockException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::PackageLockException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,28 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class PackageNotFoundException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::PackageNotFoundException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,28 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class PackageParsingException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::PackageParsingException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace ncc\Exceptions;
use Exception;
class ProjectConfigurationNotFoundException extends Exception
{
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, $previous);
}
}

View file

@ -0,0 +1,29 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use Throwable;
class ResourceChecksumException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->message = $message;
$this->code = $code;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,28 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class RunnerExecutionException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::RunnerExecutionException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,28 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class UndefinedExecutionPolicyException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::UndefinedExecutionPolicyException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,28 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class UnsupportedComponentTypeException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::UnsupportedComponentTypeException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace ncc\Exceptions;
class UnsupportedRunnerException extends \Exception
{
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View file

@ -0,0 +1,28 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class VersionNotFoundException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::VersionNotFoundException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -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;
}

View file

@ -0,0 +1,59 @@
<?php
namespace ncc\Interfaces;
use Exception;
use ncc\Exceptions\ComponentChecksumException;
use ncc\Exceptions\ComponentDecodeException;
use ncc\Exceptions\UnsupportedComponentTypeException;
use ncc\Objects\InstallationPaths;
use ncc\Objects\Package;
use ncc\Objects\Package\Component;
interface InstallerInterface
{
/**
* Public Constructor
*
* @param Package $package
*/
public function __construct(Package $package);
/**
* Processes the component and optionally returns a string of the final component
*
* @param Component $component
* @return string|null
* @throws ComponentChecksumException
* @throws ComponentDecodeException
* @throws UnsupportedComponentTypeException
*/
public function processComponent(Package\Component $component): ?string;
/**
* Processes the resource and optionally returns a string of the final resource
*
* @param Package\Resource $resource
* @return string|null
* @throws
*/
public function processResource(Package\Resource $resource): ?string;
/**
* Method called before the installation stage begins
*
* @param InstallationPaths $installationPaths
* @throws Exception
* @return void
*/
public function preInstall(InstallationPaths $installationPaths): void;
/**
* Method called after the installation stage is completed and all the files have been installed
*
* @param InstallationPaths $installationPaths
* @throws Exception
* @return void
*/
public function postInstall(InstallationPaths $installationPaths): void;
}

View file

@ -0,0 +1,44 @@
<?php
namespace ncc\Interfaces;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\IOException;
use ncc\Exceptions\RunnerExecutionException;
use ncc\Objects\ExecutionPointers\ExecutionPointer;
use ncc\Objects\InstallationPaths;
use ncc\Objects\Package\ExecutionUnit;
use ncc\Objects\ProjectConfiguration\ExecutionPolicy;
use ncc\ThirdParty\Symfony\Process\Process;
interface RunnerInterface
{
/**
* Processes the ExecutionPolicy
*
* @param string $path
* @param ExecutionPolicy $policy
* @return ExecutionUnit
* @throws FileNotFoundException
* @throws AccessDeniedException
* @throws IOException
*/
public static function processUnit(string $path, ExecutionPolicy $policy): ExecutionUnit;
/**
* Returns the file extension to use for the target file
*
* @return string
*/
public static function getFileExtension(): string;
/**
* Prepares a process object for the execution pointer
*
* @param ExecutionPointer $pointer
* @return Process
* @throws RunnerExecutionException
*/
public static function prepareProcess(ExecutionPointer $pointer): Process;
}

View file

@ -4,12 +4,15 @@
namespace ncc\Managers;
use Exception;
use ncc\Abstracts\Scopes;
use ncc\Abstracts\Versions;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\InvalidCredentialsEntryException;
use ncc\Exceptions\IOException;
use ncc\Exceptions\RuntimeException;
use ncc\Objects\Vault;
use ncc\Utilities\IO;
use ncc\Utilities\PathFinder;
use ncc\Utilities\Resolver;
use ncc\ZiProto\ZiProto;
@ -26,6 +29,7 @@
*/
public function __construct()
{
/** @noinspection PhpUnhandledExceptionInspection */
$this->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;
}

View file

@ -0,0 +1,424 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Managers;
use Exception;
use ncc\Abstracts\Runners;
use ncc\Abstracts\Scopes;
use ncc\Classes\PhpExtension\Runner;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\ExecutionUnitNotFoundException;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\InvalidScopeException;
use ncc\Exceptions\IOException;
use ncc\Exceptions\NoAvailableUnitsException;
use ncc\Exceptions\RunnerExecutionException;
use ncc\Exceptions\UnsupportedRunnerException;
use ncc\Objects\ExecutionPointers;
use ncc\Objects\Package;
use ncc\Objects\Package\ExecutionUnit;
use ncc\Objects\ProjectConfiguration\ExecutionPolicy\ExitHandle;
use ncc\ThirdParty\Symfony\Filesystem\Filesystem;
use ncc\ThirdParty\Symfony\Process\Process;
use ncc\Utilities\Console;
use ncc\Utilities\IO;
use ncc\Utilities\PathFinder;
use ncc\Utilities\Resolver;
use ncc\ZiProto\ZiProto;
class ExecutionPointerManager
{
/**
* The path for where all the runners are located
*
* @var string
*/
private $RunnerPath;
/**
* An array of temporary unit names to destroy once the object is destroyed
*
* @var string[]
*/
private $TemporaryUnits;
/**
* Deletes all the temporary files on destruct
*/
public function __destruct()
{
try
{
$this->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;
}
}

View file

@ -0,0 +1,152 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Managers;
use Exception;
use ncc\Abstracts\Scopes;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\IOException;
use ncc\Exceptions\PackageLockException;
use ncc\Objects\PackageLock;
use ncc\Utilities\Console;
use ncc\Utilities\IO;
use ncc\Utilities\PathFinder;
use ncc\Utilities\Resolver;
use ncc\Utilities\RuntimeCache;
use ncc\ZiProto\ZiProto;
class PackageLockManager
{
/**
* @var PackageLock|null
*/
private $PackageLock;
/**
* @var string
*/
private $PackageLockPath;
/**
* Public Constructor
*/
public function __construct()
{
/** @noinspection PhpUnhandledExceptionInspection */
$this->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;
}
}

View file

@ -0,0 +1,490 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Managers;
use Exception;
use ncc\Abstracts\CompilerExtensions;
use ncc\Abstracts\ConstantReferences;
use ncc\Abstracts\LogLevel;
use ncc\Abstracts\Scopes;
use ncc\Classes\NccExtension\PackageCompiler;
use ncc\Classes\PhpExtension\Installer;
use ncc\CLI\Main;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\InstallationException;
use ncc\Exceptions\InvalidScopeException;
use ncc\Exceptions\IOException;
use ncc\Exceptions\PackageAlreadyInstalledException;
use ncc\Exceptions\PackageLockException;
use ncc\Exceptions\PackageNotFoundException;
use ncc\Exceptions\PackageParsingException;
use ncc\Exceptions\UnsupportedCompilerExtensionException;
use ncc\Exceptions\UnsupportedRunnerException;
use ncc\Exceptions\VersionNotFoundException;
use ncc\Objects\InstallationPaths;
use ncc\Objects\Package;
use ncc\Objects\PackageLock\PackageEntry;
use ncc\Objects\PackageLock\VersionEntry;
use ncc\ThirdParty\Symfony\Filesystem\Filesystem;
use ncc\ThirdParty\theseer\DirectoryScanner\DirectoryScanner;
use ncc\Utilities\Console;
use ncc\Utilities\IO;
use ncc\Utilities\PathFinder;
use ncc\Utilities\Resolver;
use ncc\ZiProto\ZiProto;
use SplFileInfo;
class PackageManager
{
/**
* @var string
*/
private $PackagesPath;
/**
* @var PackageLockManager|null
*/
private $PackageLockManager;
/**
* @throws InvalidScopeException
* @throws PackageLockException
*/
public function __construct()
{
$this->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;
}
}

View file

@ -1,12 +1,26 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Managers;
use ncc\Abstracts\Options\BuildConfigurationValues;
use ncc\Abstracts\Options\InitializeProjectOptions;
use ncc\Classes\NccExtension\PackageCompiler;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\BuildConfigurationNotFoundException;
use ncc\Exceptions\BuildException;
use ncc\Exceptions\DirectoryNotFoundException;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\InvalidPackageNameException;
use ncc\Exceptions\InvalidProjectNameException;
use ncc\Exceptions\IOException;
use ncc\Exceptions\MalformedJsonException;
use ncc\Exceptions\PackagePreparationFailedException;
use ncc\Exceptions\ProjectAlreadyExistsException;
use ncc\Exceptions\ProjectConfigurationNotFoundException;
use ncc\Exceptions\UnsupportedCompilerExtensionException;
use ncc\Exceptions\UnsupportedRunnerException;
use ncc\Objects\ProjectConfiguration;
use ncc\Objects\ProjectConfiguration\Compiler;
use ncc\ThirdParty\Symfony\Uid\Uuid;
@ -14,84 +28,77 @@
class ProjectManager
{
/**
* The selected directory for managing the project
*
* @var string|null
*/
private ?string $SelectedDirectory;
/**
* The path that points to the project's main project.json file
*
* @var string|null
* @var string
*/
private ?string $ProjectFilePath;
private $ProjectFilePath;
/**
* The path that points the project's main directory
*
* @var string|null
* @var string
*/
private ?string $ProjectPath;
private $ProjectPath;
/**
* The loaded project configuration, null if no project file is loaded
*
* @var ProjectConfiguration|null
*/
private $ProjectConfiguration;
/**
* Public Constructor
*
* @param string $selected_directory
* @param string $path
* @throws AccessDeniedException
* @throws DirectoryNotFoundException
* @throws FileNotFoundException
* @throws IOException
* @throws MalformedJsonException
* @throws ProjectConfigurationNotFoundException
*/
public function __construct(string $selected_directory)
public function __construct(string $path)
{
$this->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);
}
}

View file

@ -0,0 +1,171 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects;
use ncc\Exceptions\FileNotFoundException;
use ncc\Objects\ExecutionPointers\ExecutionPointer;
use ncc\Objects\Package\ExecutionUnit;
use ncc\Utilities\Validate;
class ExecutionPointers
{
/**
* @var string
*/
private $Package;
/**
* @var string
*/
private $Version;
/**
* @var ExecutionPointer[]
*/
private $Pointers;
/**
* @param string|null $package
* @param string|null $version
*/
public function __construct(?string $package=null, ?string $version=null)
{
$this->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;
}
}

View file

@ -0,0 +1,78 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\ExecutionPointers;
use ncc\Objects\Package\ExecutionUnit;
use ncc\Objects\ProjectConfiguration\ExecutionPolicy;
use ncc\Utilities\Functions;
class ExecutionPointer
{
/**
* @var string
*/
public $ID;
/**
* The execution policy for this execution unit
*
* @var ExecutionPolicy
*/
public $ExecutionPolicy;
/**
* The file pointer for where the target script should be executed
*
* @var string
*/
public $FilePointer;
/**
* Public Constructor with optional ExecutionUnit parameter to construct object from
*
* @param ExecutionUnit|null $unit
*/
public function __construct(?ExecutionUnit $unit=null, ?string $bin_file=null)
{
if($unit == null)
return;
$this->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;
}
}

View file

@ -0,0 +1,61 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects;
class InstallationPaths
{
/**
* The path of where the package will be installed at
*
* @var string
*/
private $InstallationPath;
/**
* @param string $installation_path
*/
public function __construct(string $installation_path)
{
$this->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;
}
}

View file

@ -1,8 +1,14 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\NccVersionInformation;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\ComponentVersionNotFoundException;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\IOException;
use ncc\Utilities\IO;
class Component
{
@ -25,18 +31,19 @@
*
* @return string
* @throws ComponentVersionNotFoundException
* @throws AccessDeniedException
* @throws FileNotFoundException
* @throws IOException
*/
public function getVersion(): string
{
$third_party_path = NCC_EXEC_LOCATION . DIRECTORY_SEPARATOR . 'ThirdParty' . DIRECTORY_SEPARATOR;
$component_path = $third_party_path . $this->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');
}
/**

View file

@ -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;
}
}

View file

@ -1,5 +1,7 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\Package;
use ncc\Utilities\Functions;
@ -33,12 +35,12 @@
*
* @var string
*/
public $Checksum;
private $Checksum;
/**
* The raw data of the component, this is to be processed by the compiler extension
*
* @var string
* @var mixed
*/
public $Data;
@ -51,17 +53,32 @@
public function validateChecksum(): bool
{
if($this->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.
*

View file

@ -0,0 +1,71 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\Package;
use ncc\Objects\ProjectConfiguration\ExecutionPolicy;
use ncc\Utilities\Functions;
class ExecutionUnit
{
/**
* @var string|null
*/
private $ID;
/**
* The execution policy for this execution unit
*
* @var ExecutionPolicy
*/
public $ExecutionPolicy;
/**
* The data of the unit to execute
*
* @var string
*/
public $Data;
/**
* Returns an array representation of the object
*
* @param bool $bytecode
* @return array
*/
public function toArray(bool $bytecode=false): array
{
return [
($bytecode ? Functions::cbc('execution_policy') : 'execution_policy') => $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;
}
}

View file

@ -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;
}
}

View file

@ -1,18 +1,81 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\Package;
use ncc\Utilities\Functions;
class Installer
{
/**
* An array of execution policies to execute pre install
*
* @var string[]|null
*/
public $PreInstall;
/**
* An array of execution policies to execute post install
*
* @var string[]|null
*/
public $PostInstall;
/**
* An array of execution policies to execute pre uninstall
*
* @var string[]|null
*/
public $PreUninstall;
/**
* An array of execution policies to execute post uninstall
*
* @var string[]|null
*/
public $PostUninstall;
/**
* An array of execution policies to execute pre update
*
* @var string[]|null
*/
public $PreUpdate;
/**
* An array of execution policies to execute post update
*
* @var string[]|null
*/
public $PostUpdate;
/**
* Returns an array representation of the object
* Returns null if all properties are not set
*
* @param bool $bytecode
* @return array
* @return array|null
*/
public function toArray(bool $bytecode=false): array
public function toArray(bool $bytecode=false): ?array
{
return [];
if(
$this->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;
}
}

View file

@ -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;
}

View file

@ -1,30 +0,0 @@
<?php
namespace ncc\Objects\Package;
class MainExecutionPolicy
{
/**
* Returns an array representation of the object
*
* @param bool $bytecode
* @return array
*/
public function toArray(bool $bytecode=false): array
{
return [];
}
/**
* Constructs object from an array representation
*
* @param array $data
* @return MainExecutionPolicy
*/
public static function fromArray(array $data): self
{
$object = new self();
return $object;
}
}

View file

@ -21,7 +21,7 @@
*
* @var string
*/
public $Checksum;
private $Checksum;
/**
* The raw data of the resource
@ -44,12 +44,27 @@
if($this->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.
*

View file

@ -0,0 +1,194 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects;
use ncc\Abstracts\Versions;
use ncc\Objects\PackageLock\PackageEntry;
use ncc\Utilities\Functions;
class PackageLock
{
/**
* The version of package lock file structure
*
* @var string
*/
public $PackageLockVersion;
/**
* The Unix Timestamp for when this package lock file was last updated
*
* @var int
*/
public $LastUpdatedTimestamp;
/**
* An array of installed packages in the PackageLock file
*
* @var PackageEntry[]
*/
public $Packages;
/**
* Public Constructor
*/
public function __construct()
{
$this->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;
}
}

View file

@ -0,0 +1,64 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\PackageLock;
use ncc\Objects\ProjectConfiguration\Dependency;
use ncc\Utilities\Functions;
class DependencyEntry
{
/**
* The name of the package dependency
*
* @var string
*/
public $PackageName;
/**
* The version of the package dependency
*
* @var string
*/
public $Version;
public function __construct(?Dependency $dependency=null)
{
if($dependency !== null)
{
$this->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;
}
}

View file

@ -0,0 +1,235 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\PackageLock;
use ncc\Exceptions\VersionNotFoundException;
use ncc\Objects\Package;
use ncc\ThirdParty\jelix\Version\VersionComparator;
use ncc\Utilities\Functions;
class PackageEntry
{
/**
* The name of the package that's installed
*
* @var string
*/
public $Name;
/**
* The latest version of the package entry, this is updated automatically
*
* @var string|null
*/
private $LatestVersion;
/**
* An array of installed versions for this package
*
* @var VersionEntry[]
*/
public $Versions;
/**
* Public Constructor
*/
public function __construct()
{
$this->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;
}
}

View file

@ -0,0 +1,126 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\PackageLock;
use ncc\Objects\Package\ExecutionUnit;
use ncc\Objects\ProjectConfiguration\Compiler;
use ncc\Utilities\Functions;
class VersionEntry
{
/**
* The version of the package that's installed
*
* @var string
*/
public $Version;
/**
* The compiler extension used for the package
*
* @var Compiler
*/
public $Compiler;
/**
* An array of packages that this package depends on
*
* @var DependencyEntry[]
*/
public $Dependencies;
/**
* @var ExecutionUnit[]
*/
public $ExecutionUnits;
/**
* The main execution policy for this version entry if applicable
*
* @var string|null
*/
public $MainExecutionPolicy;
/**
* The path where the package is located
*
* @var string
*/
public $Location;
/**
* Public Constructor
*/
public function __construct()
{
$this->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;
}
}

View file

@ -1,18 +1,29 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects;
use Exception;
use ncc\Abstracts\Options\BuildConfigurationValues;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\BuildConfigurationNotFoundException;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\InvalidConstantNameException;
use ncc\Exceptions\InvalidProjectBuildConfiguration;
use ncc\Exceptions\InvalidProjectConfigurationException;
use ncc\Exceptions\InvalidPropertyValueException;
use ncc\Exceptions\IOException;
use ncc\Exceptions\MalformedJsonException;
use ncc\Exceptions\RuntimeException;
use ncc\Exceptions\UndefinedExecutionPolicyException;
use ncc\Exceptions\UnsupportedCompilerExtensionException;
use ncc\Exceptions\UnsupportedExtensionVersionException;
use ncc\Objects\ProjectConfiguration\Assembly;
use ncc\Objects\ProjectConfiguration\Build;
use ncc\Objects\ProjectConfiguration\BuildConfiguration;
use ncc\Objects\ProjectConfiguration\ExecutionPolicy;
use ncc\Objects\ProjectConfiguration\Installer;
use ncc\Objects\ProjectConfiguration\Project;
use ncc\Utilities\Functions;
@ -36,6 +47,20 @@
*/
public $Assembly;
/**
* An array of execution policies
*
* @var ExecutionPolicy[]
*/
public $ExecutionPolicies;
/**
* Execution Policies to execute by the NCC installer
*
* @var Installer|null
*/
public $Installer;
/**
* Build configuration for the project
*
@ -50,6 +75,7 @@
{
$this->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;
}
}

View file

@ -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)
{

View file

@ -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;

View file

@ -0,0 +1,98 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\ProjectConfiguration;
use ncc\Objects\InstallationPaths;
use ncc\Objects\ProjectConfiguration\ExecutionPolicy\Execute;
use ncc\Objects\ProjectConfiguration\ExecutionPolicy\ExitHandlers;
use ncc\Utilities\Functions;
class ExecutionPolicy
{
/**
* The unique name of the execution policy
*
* @var string
*/
public $Name;
/**
* The name of a supported runner instance
*
* @var string
*/
public $Runner;
/**
* The message to display when the policy is invoked
*
* @var string|null
*/
public $Message;
/**
* The execution process of the policy
*
* @var Execute
*/
public $Execute;
/**
* The configuration for exit handling
*
* @var ExitHandlers
*/
public $ExitHandlers;
/**
* @param bool $throw_exception
* @return bool
*/
public function validate(bool $throw_exception=True): bool
{
// TODO: Implement validation method
return true;
}
/**
* Returns an array representation of the object
*
* @param bool $bytecode
* @return array
*/
public function toArray(bool $bytecode=false): array
{
return [
($bytecode ? Functions::cbc('name') : 'name') => $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;
}
}

View file

@ -0,0 +1,103 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\ProjectConfiguration\ExecutionPolicy;
use ncc\Utilities\Functions;
class Execute
{
/**
* The target file to execute
*
* @var string
*/
public $Target;
/**
* The working directory to execute the policy in, if not specified the
* value "%CWD%" will be used as the default
*
* @var string|null
*/
public $WorkingDirectory;
/**
* An array of options to pass on to the process
*
* @var array|null
*/
public $Options;
/**
* Indicates if the output should be displayed or suppressed
*
* @var bool|null
*/
public $Silent;
/**
* Indicates if the process should run in Tty mode (Overrides Silent mode)
*
* @var bool|null
*/
public $Tty;
/**
* The number of seconds to wait before giving up on the process, will automatically execute the error handler
* if one is set.
*
* @var int
*/
public $Timeout;
/**
* Public Constructor
*/
public function __construct()
{
$this->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;
}
}

View file

@ -0,0 +1,76 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\ProjectConfiguration\ExecutionPolicy;
use ncc\Utilities\Functions;
class ExitHandle
{
/**
* The message to display when the handle is triggered
*
* @var string|null
*/
public $Message;
/**
* Indicates if the process should exit if the handle is triggered,
* by default NCC will choose the applicable value for this property,
* for instance; if the exit handle is registered for "error", the
* property will be set to true, otherwise for "success" and "warning"
* the property will be false.
*
* @var bool|null
*/
public $EndProcess;
/**
* The name of another execution policy to execute (optionally) when this exit handle is triggered
*
* @var string|null
*/
public $Run;
/**
* The exit code that needs to be returned from the process to trigger this handle
*
* @var int
*/
public $ExitCode;
/**
* Returns an array representation of the object
*
* @param bool $bytecode
* @return array
*/
public function toArray(bool $bytecode=false): array
{
return [
($bytecode ? Functions::cbc('message') : 'message') => $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;
}
}

View file

@ -0,0 +1,71 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\ProjectConfiguration\ExecutionPolicy;
use ncc\Utilities\Functions;
class ExitHandlers
{
/**
* The handle to execute when the process exits with a success exit code
*
* @var ExitHandle
*/
public $Success;
/**
* The handle to execute when the process exits with a warning exit code
*
* @var ExitHandle
*/
public $Warning;
/**
* The handle to execute when the process exits with a error exit code
*
* @var ExitHandle
*/
public $Error;
/**
* Returns an array representation of the object
*
* @param bool $bytecode
* @return array
*/
public function toArray(bool $bytecode=false): array
{
return [
($bytecode ? Functions::cbc('success') : 'success') => $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;
}
}

View file

@ -0,0 +1,88 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\ProjectConfiguration;
use ncc\Utilities\Functions;
class Installer
{
/**
* An array of execution policies to execute pre-installation
*
* @var string[]|null
*/
public $PreInstall;
/**
* An array of execution policies to execute post-installation
*
* @var string[]|null
*/
public $PostInstall;
/**
* An array of execution policies to execute pre-uninstallation
*
* @var string[]|null
*/
public $PreUninstall;
/**
* An array of execution policies to execute post-uninstallation
*
* @var string[]|null
*/
public $PostUninstall;
/**
* An array of execution policies to execute pre-update
*
* @var string[]|null
*/
public $PreUpdate;
/**
* An array of execution policies to execute post-update
*
* @var string[]|null
*/
public $PostUpdate;
/**
* Returns an array representation of the object
*
* @param bool $bytecode
* @return array
*/
public function toArray(bool $bytecode=false): array
{
return [
($bytecode ? Functions::cbc('pre_install') : 'pre_install') => $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;
}
}

View file

@ -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), '=');

View file

@ -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');
}
}
}
/**

View file

@ -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() . ')');
}
}
}

97
src/ncc/Utilities/IO.php Normal file
View file

@ -0,0 +1,97 @@
<?php
namespace ncc\Utilities;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\IOException;
use SplFileInfo;
use SplFileObject;
class IO
{
/**
* Attempts to write the specified file, with proper error handling
*
* @param string $uri
* @param string $data
* @param int $perms
* @param string $mode
* @return void
* @throws IOException
*/
public static function fwrite(string $uri, string $data, int $perms=0644, string $mode='w'): void
{
$fileInfo = new SplFileInfo($uri);
if(!is_dir($fileInfo->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);
}
}

View file

@ -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);
}
}

View file

@ -1,11 +1,21 @@
<?php
namespace ncc\Utilities;
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Utilities;
use ncc\Abstracts\LogLevel;
use ncc\Abstracts\Scopes;
class Resolver
{
/**
* The cache value of the User ID
*
* @var string|null
*/
private static $UserIdCache;
/**
* @param string|null $input
* @return string
@ -20,10 +30,13 @@
$input = strtoupper($input);
if(self::$UserIdCache == null)
self::$UserIdCache = posix_getuid();
// Resolve the scope if it's set to automatic
if($input == Scopes::Auto)
{
if(posix_getuid() == 0)
if(self::$UserIdCache == 0)
{
$input = Scopes::System;
}
@ -34,7 +47,7 @@
}
// Auto-Correct the scope if the current user ID is 0
if($input == Scopes::User && posix_getuid() == 0)
if($input == Scopes::User && self::$UserIdCache == 0)
{
$input = Scopes::System;
}
@ -45,9 +58,12 @@
/**
* Parse arguments
*
* @param array|string [$message] input arguments
* @param array|string $message [$message] input arguments
* @param int $max_arguments
* @return array Configs Key/Value
* @noinspection RegExpRedundantEscape
* @noinspection RegExpSimplifiable
* @noinspection PhpMissingParamTypeInspection
*/
public static function parseArguments($message=null, int $max_arguments=1000): array
{
@ -135,4 +151,94 @@
{
return hash('haval128,3', self::resolveFullConstantName($scope, $name));
}
/**
* Checks if the input level matches the current level
*
* @param string|null $input
* @param string|null $current_level
* @return bool
*/
public static function checkLogLevel(?string $input, ?string $current_level): bool
{
if($input == null)
return false;
if($current_level == null)
return false;
$input = strtolower($input);
if(!Validate::checkLogLevel($input))
return false;
$current_level = strtolower($current_level);
if(!Validate::checkLogLevel($current_level))
return false;
switch($current_level)
{
case LogLevel::Debug:
$levels = [
LogLevel::Debug,
LogLevel::Verbose,
LogLevel::Info,
LogLevel::Warning,
LogLevel::Fatal,
LogLevel::Error
];
if(in_array($input, $levels))
return true;
return false;
case LogLevel::Verbose:
$levels = [
LogLevel::Verbose,
LogLevel::Info,
LogLevel::Warning,
LogLevel::Fatal,
LogLevel::Error
];
if(in_array($input, $levels))
return true;
return false;
case LogLevel::Info:
$levels = [
LogLevel::Info,
LogLevel::Warning,
LogLevel::Fatal,
LogLevel::Error
];
if(in_array($input, $levels))
return true;
return false;
case LogLevel::Warning:
$levels = [
LogLevel::Warning,
LogLevel::Fatal,
LogLevel::Error
];
if(in_array($input, $levels))
return true;
return false;
case LogLevel::Error:
$levels = [
LogLevel::Fatal,
LogLevel::Error
];
if(in_array($input, $levels))
return true;
return false;
case LogLevel::Fatal:
if($input == LogLevel::Fatal)
return true;
return false;
default:
case LogLevel::Silent:
return false;
}
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace ncc\Utilities;
class RuntimeCache
{
/**
* An array of cache entries
*
* @var array
*/
private static $cache = [];
/**
* Sets a value, returns the value
*
* @param $key
* @param $value
* @return mixed
*/
public static function set($key, $value)
{
self::$cache[$key] = $value;
return $value;
}
/**
* Gets an existing value, null if it doesn't exist
*
* @param $key
* @return mixed|null
*/
public static function get($key)
{
if(isset(self::$cache[$key]))
return self::$cache[$key];
return null;
}
}

View file

@ -2,6 +2,7 @@
namespace ncc\Utilities;
use ncc\Abstracts\LogLevel;
use ncc\Abstracts\RegexPatterns;
use ncc\Abstracts\Scopes;
@ -208,4 +209,53 @@
return true;
}
/**
* Validates the execution policy name
*
* @param string $input
* @return bool
*/
public static function executionPolicyName(string $input): bool
{
if($input == null)
{
return false;
}
if(!preg_match(RegexPatterns::ExecutionPolicyName, $input))
{
return false;
}
return true;
}
/**
* Determines if the given log level is valid or not
*
* @param string $input
* @return bool
*/
public static function checkLogLevel(string $input): bool
{
if(!in_array(strtolower($input), LogLevel::All))
return false;
return true;
}
/**
* Determines if given input exceeds the path length limit
*
* @param string $input
* @return bool
*/
public static function exceedsPathLength(string $input): bool
{
if(strlen($input) > 4096)
return true;
return false;
}
}

View file

@ -0,0 +1,9 @@
<?php
require(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'autoload.php');
\ncc\ncc::initialize();
$package_lock_manager = new \ncc\Managers\PackageLockManager();
$package_lock_manager->load();
var_dump($package_lock_manager->getPackageLock());