1.0.0 Alpha Release #59
8 changed files with 503 additions and 10 deletions
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
358
src/ncc/Managers/SymlinkManager.php
Normal file
358
src/ncc/Managers/SymlinkManager.php
Normal 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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
8
src/ncc/Objects/SymlinkDictionary.php
Normal file
8
src/ncc/Objects/SymlinkDictionary.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace ncc\Objects;
|
||||
|
||||
class SymlinkDictionary
|
||||
{
|
||||
|
||||
}
|
73
src/ncc/Objects/SymlinkDictionary/SymlinkEntry.php
Normal file
73
src/ncc/Objects/SymlinkDictionary/SymlinkEntry.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue