1.0.0 Alpha Release #59

Merged
netkas merged 213 commits from v1.0.0_alpha into master 2023-01-29 23:27:58 +00:00
8 changed files with 503 additions and 10 deletions
Showing only changes of commit bf73360f2c - Show all commits

View file

@ -118,7 +118,7 @@
Console::out(PHP_EOL . 'Example Usage:' . PHP_EOL);
Console::out(' ncc exec --package com.example.program');
Console::out(' ncc exec --package com.example.program --exec-version 1.0.0');
Console::out(' ncc exec --package com.example.program --exec-version 1.0.0 --unit setup');
Console::out(' ncc exec --package com.example.program --exec-version 1.0.0 --exec-unit setup');
Console::out(' ncc exec --package com.example.program --exec-args --foo --bar --extra=test');
}
}

View file

@ -114,6 +114,27 @@
return hash('haval128,4', $package . $version);
}
/**
* Returns the path to the execution pointer file
*
* @param string $package
* @param string $version
* @param string $name
* @return string
* @throws FileNotFoundException
*/
public function getEntryPointPath(string $package, string $version, string $name): string
{
$package_id = $this->getPackageId($package, $version);
$package_bin_path = $this->RunnerPath . DIRECTORY_SEPARATOR . $package_id;
$entry_point_path = $package_bin_path . DIRECTORY_SEPARATOR . hash('haval128,4', $name) . '.entrypoint';
if(!file_exists($entry_point_path))
throw new FileNotFoundException('Cannot find entry point for ' . $package . '=' . $version . '.' . $name);
return $entry_point_path;
}
/**
* Adds a new Execution Unit to the
*
@ -139,10 +160,12 @@
$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;
$entry_point_path = $package_bin_path . DIRECTORY_SEPARATOR . hash('haval128,4', $unit->ExecutionPolicy->Name) . '.entrypoint';
Console::outDebug(sprintf('package_id=%s', $package_id));
Console::outDebug(sprintf('package_config_path=%s', $package_config_path));
Console::outDebug(sprintf('package_bin_path=%s', $package_bin_path));
Console::outDebug(sprintf('entry_point_path=%s', $entry_point_path));
$filesystem = new Filesystem();
@ -184,6 +207,16 @@
$execution_pointers->addUnit($unit, $bin_file);
IO::fwrite($package_config_path, ZiProto::encode($execution_pointers->toArray(true)));
$entry_point = sprintf("#!%s\nncc exec --package=\"%s\" --exec-version=\"%s\" --exec-unit=\"%s\" --exec-args \"$@\"",
'/bin/bash',
$package, $version, $unit->ExecutionPolicy->Name
);
if(file_exists($entry_point_path))
$filesystem->remove($entry_point_path);
IO::fwrite($entry_point_path, $entry_point);
chmod($entry_point_path, 0755);
if($temporary)
{
Console::outVerbose(sprintf('Adding temporary ExecutionUnit \'%s\' for %s', $unit->ExecutionPolicy->Name, $package));
@ -333,10 +366,9 @@
}
}
$process = new Process(array_merge([
PathFinder::findRunner(strtolower($unit->ExecutionPolicy->Runner)),
$unit->FilePointer
], $args));
$process = new Process(array_merge(
[PathFinder::findRunner(strtolower($unit->ExecutionPolicy->Runner)), $unit->FilePointer], $args)
);
if($unit->ExecutionPolicy->Execute->WorkingDirectory !== null)
{

View file

@ -125,6 +125,17 @@
{
throw new PackageLockException('Cannot save the package lock file to disk', $e);
}
try
{
Console::outDebug('synchronizing symlinks');
$symlink_manager = new SymlinkManager();
$symlink_manager->sync();
}
catch(Exception $e)
{
throw new PackageLockException('Failed to synchronize symlinks', $e);
}
}
/**

View file

@ -326,11 +326,14 @@
IO::fwrite($installation_paths->getDataPath() . DIRECTORY_SEPARATOR . 'exec', ZiProto::encode($unit_paths));
}
// After execution units are installed, create a symlink if needed
if(isset($package->Header->Options['create_symlink']) && $package->Header->Options['create_symlink'])
{
$paths = [
DIRECTORY_SEPARATOR . 'usr' . DIRECTORY_SEPARATOR . 'bin'
];
if($package->MainExecutionPolicy === null)
throw new InstallationException('Cannot create symlink, no main execution policy is defined');
$SymlinkManager = new SymlinkManager();
$SymlinkManager->add($package->Assembly->Package, $package->MainExecutionPolicy);
}
// Execute the post-installation stage after the installation is complete
@ -847,6 +850,9 @@
Console::outDebug(sprintf('warning: removing execution unit %s failed', $executionUnit->ExecutionPolicy->Name));
}
}
$symlink_manager = new SymlinkManager();
$symlink_manager->sync();
}
/**

View file

@ -0,0 +1,358 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Managers;
use Exception;
use ncc\Abstracts\Scopes;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\IOException;
use ncc\Objects\SymlinkDictionary\SymlinkEntry;
use ncc\ThirdParty\Symfony\Filesystem\Filesystem;
use ncc\Utilities\Console;
use ncc\Utilities\IO;
use ncc\Utilities\PathFinder;
use ncc\Utilities\Resolver;
use ncc\ZiProto\ZiProto;
class SymlinkManager
{
/**
* @var string
*/
private static $BinPath = DIRECTORY_SEPARATOR . 'usr' . DIRECTORY_SEPARATOR . 'local' . DIRECTORY_SEPARATOR . 'bin';
/**
* The path to the symlink dictionary file
*
* @var string
*/
private $SymlinkDictionaryPath;
/**
* An array of all the defined symlinks
*
* @var SymlinkEntry[]
*/
private $SymlinkDictionary;
/**
* Public Constructor
*/
public function __construct()
{
try
{
$this->SymlinkDictionaryPath = PathFinder::getSymlinkDictionary(Scopes::System);
$this->load();
}
catch(Exception $e)
{
Console::outWarning(sprintf('failed to load symlink dictionary from %s', $this->SymlinkDictionaryPath));
}
finally
{
if($this->SymlinkDictionary === null)
$this->SymlinkDictionary = [];
unset($e);
}
}
/**
* Loads the symlink dictionary from the file
*
* @return void
* @throws AccessDeniedException
* @throws IOException
*/
public function load(): void
{
if($this->SymlinkDictionary !== null)
return;
Console::outDebug(sprintf('loading symlink dictionary from %s', $this->SymlinkDictionaryPath));
if(!file_exists($this->SymlinkDictionaryPath))
{
Console::outDebug('symlink dictionary does not exist, creating new dictionary');
$this->SymlinkDictionary = [];
$this->save(false);
return;
}
try
{
$this->SymlinkDictionary = [];
foreach(ZiProto::decode(IO::fread($this->SymlinkDictionaryPath)) as $entry)
{
$this->SymlinkDictionary[] = SymlinkEntry::fromArray($entry);
}
}
catch(Exception $e)
{
$this->SymlinkDictionary = [];
Console::outDebug('symlink dictionary is corrupted, creating new dictionary');
$this->save(false);
}
finally
{
unset($e);
}
}
/**
* Saves the symlink dictionary to the file
*
* @param bool $throw_exception
* @return void
* @throws AccessDeniedException
* @throws IOException
*/
private function save(bool $throw_exception=true): void
{
if(Resolver::resolveScope() !== Scopes::System)
throw new AccessDeniedException('Insufficient Permissions to write to the system symlink dictionary');
Console::outDebug(sprintf('saving symlink dictionary to %s', $this->SymlinkDictionaryPath));
try
{
$dictionary = [];
foreach($this->SymlinkDictionary as $entry)
{
$dictionary[] = $entry->toArray(true);
}
IO::fwrite($this->SymlinkDictionaryPath, ZiProto::encode($dictionary));
}
catch(Exception $e)
{
if($throw_exception)
throw new IOException(sprintf('failed to save symlink dictionary to %s', $this->SymlinkDictionaryPath), $e);
Console::outWarning(sprintf('failed to save symlink dictionary to %s', $this->SymlinkDictionaryPath));
}
finally
{
unset($e);
}
}
/**
* @return string
*/
public function getSymlinkDictionaryPath(): string
{
return $this->SymlinkDictionaryPath;
}
/**
* @return array
*/
public function getSymlinkDictionary(): array
{
return $this->SymlinkDictionary;
}
/**
* Checks if a package is defined in the symlink dictionary
*
* @param string $package
* @return bool
*/
public function exists(string $package): bool
{
foreach($this->SymlinkDictionary as $entry)
{
if($entry->Package === $package)
return true;
}
return false;
}
/**
* Adds a new entry to the symlink dictionary
*
* @param string $package
* @param string $unit
* @return void
* @throws AccessDeniedException
* @throws IOException
*/
public function add(string $package, string $unit='main'): void
{
if(Resolver::resolveScope() !== Scopes::System)
throw new AccessDeniedException('Insufficient Permissions to add to the system symlink dictionary');
if($this->exists($package))
$this->remove($package);
$entry = new SymlinkEntry();
$entry->Package = $package;
$entry->ExecutionPolicyName = $unit;
$this->SymlinkDictionary[] = $entry;
$this->save();
}
/**
* Removes an entry from the symlink dictionary
*
* @param string $package
* @return void
* @throws AccessDeniedException
* @throws IOException
*/
public function remove(string $package): void
{
if(Resolver::resolveScope() !== Scopes::System)
throw new AccessDeniedException('Insufficient Permissions to remove from the system symlink dictionary');
if(!$this->exists($package))
return;
foreach($this->SymlinkDictionary as $key => $entry)
{
if($entry->Package === $package)
{
if($entry->Registered)
{
$filesystem = new Filesystem();
$symlink_name = explode('.', $entry->Package)[count(explode('.', $entry->Package)) - 1];
$symlink = self::$BinPath . DIRECTORY_SEPARATOR . $symlink_name;
if($filesystem->exists($symlink))
$filesystem->remove($symlink);
}
unset($this->SymlinkDictionary[$key]);
$this->save();
return;
}
}
throw new IOException(sprintf('failed to remove package %s from the symlink dictionary', $package));
}
/**
* Sets the package as registered
*
* @param string $package
* @return void
* @throws AccessDeniedException
* @throws IOException
*/
private function setAsRegistered(string $package): void
{
foreach($this->SymlinkDictionary as $key => $entry)
{
if($entry->Package === $package)
{
$entry->Registered = true;
$this->SymlinkDictionary[$key] = $entry;
$this->save();
return;
}
}
}
/**
* Sets the package as unregistered
*
* @param string $package
* @return void
* @throws AccessDeniedException
* @throws IOException
*/
private function setAsUnregistered(string $package): void
{
foreach($this->SymlinkDictionary as $key => $entry)
{
if($entry->Package === $package)
{
$entry->Registered = false;
$this->SymlinkDictionary[$key] = $entry;
$this->save();
return;
}
}
}
/**
* Syncs the symlink dictionary with the filesystem
*
* @return void
* @throws AccessDeniedException
* @throws IOException
*/
public function sync(): void
{
if(Resolver::resolveScope() !== Scopes::System)
throw new AccessDeniedException('Insufficient Permissions to sync the system symlink dictionary');
$filesystem = new Filesystem();
$execution_pointer_manager = new ExecutionPointerManager();
$package_lock_manager = new PackageLockManager();
foreach($this->SymlinkDictionary as $entry)
{
if($entry->Registered)
continue;
$symlink_name = explode('.', $entry->Package)[count(explode('.', $entry->Package)) - 1];
$symlink = self::$BinPath . DIRECTORY_SEPARATOR . $symlink_name;
if($filesystem->exists($symlink))
{
Console::outWarning(sprintf('Symlink %s already exists, skipping', $symlink));
continue;
}
try
{
$package_entry = $package_lock_manager->getPackageLock()->getPackage($entry->Package);
if($package_entry == null)
{
Console::outWarning(sprintf('Package %s is not installed, skipping', $entry->Package));
continue;
}
$latest_version = $package_entry->getLatestVersion();
}
catch(Exception $e)
{
$filesystem->remove($symlink);
Console::outWarning(sprintf('Failed to get package %s, skipping', $entry->Package));
continue;
}
try
{
$entry_point_path = $execution_pointer_manager->getEntryPointPath($entry->Package, $latest_version, $entry->ExecutionPolicyName);
$filesystem->symlink($entry_point_path, $symlink);
}
catch(Exception $e)
{
$filesystem->remove($symlink);
Console::outWarning(sprintf('Failed to create symlink %s, skipping', $symlink));
continue;
}
finally
{
unset($e);
}
$this->setAsRegistered($entry->Package);
}
}
}

View file

@ -0,0 +1,8 @@
<?php
namespace ncc\Objects;
class SymlinkDictionary
{
}

View file

@ -0,0 +1,73 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\SymlinkDictionary;
use ncc\Utilities\Functions;
class SymlinkEntry
{
/**
* The name of the package that the symlink is for
*
* @var string
*/
public $Package;
/**
* The name of the execution policy to execute
*
* @var string
*/
public $ExecutionPolicyName;
/**
* Indicates if this symlink is currently registered by NCC
*
* @var bool
*/
public $Registered;
/**
* Public Constructor
*/
public function __construct()
{
$this->ExecutionPolicyName = 'main';
$this->Registered = false;
}
/**
* Returns a string representation of the object
*
* @param bool $bytecode
* @return array
*/
public function toArray(bool $bytecode=false): array
{
return [
($bytecode ? Functions::cbc('package') : 'package') => $this->Package,
($bytecode ? Functions::cbc('registered') : 'registered') => $this->Registered,
($bytecode ? Functions::cbc('execution_policy_name') : 'execution_policy_name') => $this->ExecutionPolicyName
];
}
/**
* Constructs a new SymlinkEntry from an array representation
*
* @param array $data
* @return SymlinkEntry
*/
public static function fromArray(array $data): SymlinkEntry
{
$entry = new SymlinkEntry();
$entry->Package = Functions::array_bc($data, 'package');
$entry->Registered = (bool)Functions::array_bc($data, 'registered');
$entry->ExecutionPolicyName = Functions::array_bc($data, 'execution_policy_name');
return $entry;
}
}

View file

@ -2,6 +2,9 @@
namespace ncc;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\IOException;
use ncc\Exceptions\MalformedJsonException;
use ncc\Exceptions\RuntimeException;
use ncc\Objects\NccVersionInformation;
@ -34,8 +37,10 @@
*
* @param boolean $reload Indicates if the cached version is to be ignored and the version file to be reloaded and validated
* @return NccVersionInformation
* @throws Exceptions\FileNotFoundException
* @throws Exceptions\RuntimeException
* @throws AccessDeniedException
* @throws FileNotFoundException
* @throws IOException
* @throws RuntimeException
*/
public static function getVersionInformation(bool $reload=False): NccVersionInformation
{