Add lib skeleton command and sort config, spc_mode suuport, etc...

This commit is contained in:
crazywhalecc 2025-12-18 15:43:58 +08:00
parent 1707c679e8
commit dd5762fbd3
No known key found for this signature in database
GPG Key ID: 1F4BDD59391F2680
19 changed files with 866 additions and 109 deletions

View File

@ -1,6 +1,7 @@
parameters:
reportUnmatchedIgnoredErrors: false
level: 4
phpVersion: 80400
paths:
- ./src/
ignoreErrors:

View File

@ -1,8 +1,11 @@
<?php
use StaticPHP\Skeleton\ArtifactGenerator;
use StaticPHP\Skeleton\PackageGenerator;
declare(strict_types=1);
use StaticPHP\Runtime\Executor\UnixCMakeExecutor;
use StaticPHP\Skeleton\ArtifactGenerator;
use StaticPHP\Skeleton\ExecutorGenerator;
use StaticPHP\Skeleton\PackageGenerator;
require_once 'vendor/autoload.php';
@ -10,12 +13,16 @@ $package_generator = new PackageGenerator('foo', 'library')
->addDependency('bar')
->addStaticLib('libfoo.a', 'unix')
->addStaticLib('libfoo.a', 'unix')
->addArtifact($artifact_generator = new ArtifactGenerator('foo')->setSource(['type' => 'url', 'url' => 'https://example.com/foo.tar.gz']));
->addArtifact($artifact_generator = new ArtifactGenerator('foo')->setSource(['type' => 'url', 'url' => 'https://example.com/foo.tar.gz']))
->enableBuild(['Darwin', 'Linux'], 'build')
->addFunctionExecutorBinding('build', new ExecutorGenerator(UnixCMakeExecutor::class));
$pkg_config = $package_generator->generateConfig();
$artifact_config = $artifact_generator->generateConfig();
$pkg_config = $package_generator->generateConfigArray();
$artifact_config = $artifact_generator->generateConfigArray();
echo "===== pkg.json =====" . PHP_EOL;
echo json_encode($pkg_config, 64|128|256) . PHP_EOL;
echo "===== artifact.json =====" . PHP_EOL;
echo json_encode($artifact_config, 64|128|256) . PHP_EOL;
echo '===== pkg.json =====' . PHP_EOL;
echo json_encode($pkg_config, 64 | 128 | 256) . PHP_EOL;
echo '===== artifact.json =====' . PHP_EOL;
echo json_encode($artifact_config, 64 | 128 | 256) . PHP_EOL;
echo '===== php code for package =====' . PHP_EOL;
echo $package_generator->generatePackageClassFile('Package\Library');

View File

@ -8,7 +8,6 @@ use StaticPHP\Artifact\ArtifactDownloader;
use StaticPHP\Artifact\Downloader\DownloadResult;
use StaticPHP\Attribute\Artifact\AfterBinaryExtract;
use StaticPHP\Attribute\Artifact\CustomBinary;
use StaticPHP\Attribute\Artifact\CustomSource;
use StaticPHP\Exception\DownloaderException;
use StaticPHP\Runtime\SystemTarget;

View File

@ -329,8 +329,7 @@ class ArtifactDownloader
}
if ($interactive) {
$skip_msg = !empty($skipped) ? ' (Skipped ' . count($skipped) . ' artifacts for being already downloaded)' : '';
InteractiveTerm::success("Downloaded all {$count} artifacts.{$skip_msg}", true);
echo PHP_EOL;
InteractiveTerm::success("Downloaded all {$count} artifacts.{$skip_msg}\n", true);
}
}
} catch (SPCException $e) {

View File

@ -23,7 +23,6 @@ abstract class BaseCommand extends Command
\___ \| __/ _` | __| |/ __| |_) | |_| | |_) |
___) | || (_| | |_| | (__| __/| _ | __/
|____/ \__\__,_|\__|_|\___|_| |_| |_|_| {version}
';
protected bool $no_motd = false;
@ -71,7 +70,7 @@ abstract class BaseCommand extends Command
$version = $this->getVersionWithCommit();
if (!$this->no_motd) {
$str = str_replace('{version}', '' . ConsoleColor::none("v{$version}"), '' . ConsoleColor::magenta(self::$motd));
echo $this->input->getOption('no-ansi') ? strip_ansi_colors($str) : $str;
$this->output->writeln($this->input->getOption('no-ansi') ? strip_ansi_colors($str) : $str);
}
}

View File

@ -0,0 +1,402 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Command\Dev;
use StaticPHP\Command\BaseCommand;
use StaticPHP\Exception\WrongUsageException;
use StaticPHP\Registry\Registry;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Skeleton\ArtifactGenerator;
use StaticPHP\Skeleton\PackageGenerator;
use Symfony\Component\Console\Attribute\AsCommand;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\select;
use function Laravel\Prompts\text;
#[AsCommand('dev:skel', 'Generate a skeleton for a StaticPHP package')]
class SkeletonCommand extends BaseCommand
{
public function handle(): int
{
// Only available for non-Windows systems for now
// Only available when spc loading mode is SPC_MODE_VENDOR, SPC_MODE_SOURCE
if (spc_mode(SPC_MODE_PHAR)) {
$this->output->writeln('<error>The dev:skel command is not available in phar mode.</error>');
return 1;
}
if (SystemTarget::getTargetOS() === 'Windows') {
$this->output->writeln('<error>The dev:skel command is not available on Windows systems.</error>');
return 1;
}
$this->runMainMenu();
return 0;
}
public function validatePackageName(string $name): ?string
{
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $name)) {
return 'Library name can only contain letters, numbers, underscores, and hyphens.';
}
// must start with a letter
if (!preg_match('/^[a-zA-Z]/', $name)) {
return 'Library name must start with a letter.';
}
return null;
}
private function runMainMenu(): void
{
$main = select('Please select the skeleton option', [
'library' => 'Create a new library package',
'target' => 'Create a new target package',
'php-extension' => 'Create a new PHP extension',
'q' => 'Exit',
]);
$generator = match ($main) {
'library' => $this->runCreateLib(),
'target' => $this->runCreateTarget(),
'php-extension' => $this->runCreateExt(),
'q' => exit(0),
default => null,
};
$write = $generator->writeAll();
$this->output->writeln("<info>Package config in: {$write['package_config']}</info>");
$this->output->writeln("<info>Artifact config in: {$write['artifact_config']}</info>");
$this->output->writeln('<comment>Package class:</comment>');
$this->output->writeln($write['package_class_content']);
}
private function runCreateLib(): PackageGenerator
{
// init empty
$static_libs = '';
$headers = '';
$static_bins = '';
$pkg_configs = '';
// ask name
$package_name = text('Please enter your library name', placeholder: 'e.g. pcre2', validate: [$this, 'validatePackageName']);
// ask OS
$os = select("[{$package_name}] On which OS family do you want to build this library?", [
'unix' => 'Both Linux and Darwin (unix-like OS)',
'linux' => 'Linux only',
'macos' => 'Darwin(macOS) only',
'windows' => 'Windows only',
'all' => 'All supported OS (' . implode(', ', SUPPORTED_OS_FAMILY) . ')',
]);
$produce = select("[{$package_name}] What does this library produce?", [
'static_libs' => 'Static Libraries (.a/.lib)',
'headers' => 'Header Files (.h)',
'static_bins' => 'Static Binaries (executables)',
'pkg_configs' => 'Pkg-Config files (.pc)',
'all' => 'All of the above',
]);
if ($produce === 'all' || $produce === 'static_libs') {
$static_libs = text(
'Please enter the names of the static libraries produced',
placeholder: 'e.g. libpcre2.a, libbar.a',
default: str_starts_with($package_name, 'lib') ? "{$package_name}.a" : "lib{$package_name}.a",
validate: function ($value) {
$names = array_map('trim', explode(',', $value));
if (array_any($names, fn ($name) => !preg_match('/^[a-zA-Z0-9_.-]+$/', $name))) {
return 'Library names can only contain letters, numbers, underscores, hyphens, and dots.';
}
return null;
},
hint: 'Separate multiple names with commas'
);
}
if ($produce === 'all' || $produce === 'headers') {
$headers = text(
'Please enter the names of the header files produced',
placeholder: 'e.g. foo.h, bar.h',
default: str_starts_with($package_name, 'lib') ? str_replace('lib', '', $package_name) . '.h' : $package_name . '.h',
validate: function ($value) {
$names = array_map('trim', explode(',', $value));
if (array_any($names, fn ($name) => !preg_match('/^[a-zA-Z0-9_.-]+$/', $name))) {
return 'Header file names can only contain letters, numbers, underscores, hyphens, and dots.';
}
return null;
},
hint: 'Separate multiple names with commas, directories are allowed (e.g. openssl directory)'
);
}
if ($produce === 'all' || $produce === 'static_bins') {
$static_bins = text(
'Please enter the names of the static binaries produced',
placeholder: 'e.g. foo, bar',
default: $package_name,
validate: function ($value) {
$names = array_map('trim', explode(',', $value));
if (array_any($names, fn ($name) => !preg_match('/^[a-zA-Z0-9_.-]+$/', $name))) {
return 'Binary names can only contain letters, numbers, underscores, hyphens, and dots.';
}
return null;
},
hint: 'Separate multiple names with commas'
);
}
if ($produce === 'all' || $produce === 'pkg_configs') {
$pkg_configs = text(
'Please enter the names of the pkg-config files produced',
placeholder: 'e.g. foo.pc, bar.pc',
default: str_starts_with($package_name, 'lib') ? str_replace('lib', '', $package_name) . '.pc' : $package_name . '.pc',
validate: function ($value) {
if (!str_ends_with($value, '.pc')) {
return 'Pkg-config file names must end with .pc extension.';
}
return null;
},
hint: 'Separate multiple names with commas'
);
}
if ($headers === '' && $static_bins === '' && $static_libs === '' && $pkg_configs === '') {
$this->output->writeln('<error>You must specify at least one of static libraries, header files, or static binaries produced.</error>');
exit(1);
}
// ask source
$artifact_generator = $this->runCreateArtifact($package_name, true, false, null);
$package_generator = new PackageGenerator($package_name, 'library');
// set artifact
$package_generator = $package_generator->addArtifact($artifact_generator);
// set os
$package_generator = match ($os) {
'unix' => $package_generator->enableBuild(['Darwin', 'Linux'], 'build'),
'linux' => $package_generator->enableBuild(['Linux'], 'build'),
'macos' => $package_generator->enableBuild(['Darwin'], 'build'),
'windows' => $package_generator->enableBuild(['Windows'], 'build'),
'all' => $package_generator->enableBuild(SUPPORTED_OS_FAMILY, 'build'),
default => $package_generator,
};
// set produce
if ($static_libs !== '') {
$lib_names = array_map('trim', explode(',', $static_libs));
foreach ($lib_names as $lib_name) {
$package_generator = $package_generator->addStaticLib($lib_name, $os === 'all' ? 'all' : ($os === 'unix' ? 'unix' : $os));
}
}
if ($headers !== '') {
$header_names = array_map('trim', explode(',', $headers));
foreach ($header_names as $header_name) {
$package_generator = $package_generator->addHeaderFile($header_name, $os === 'all' ? 'all' : ($os === 'unix' ? 'unix' : $os));
}
}
if ($static_bins !== '') {
$bin_names = array_map('trim', explode(',', $static_bins));
foreach ($bin_names as $bin_name) {
$package_generator = $package_generator->addStaticBin($bin_name, $os === 'all' ? 'all' : ($os === 'unix' ? 'unix' : $os));
}
}
if ($pkg_configs !== '') {
$pc_names = array_map('trim', explode(',', $pkg_configs));
foreach ($pc_names as $pc_name) {
$package_generator = $package_generator->addPkgConfigFile($pc_name, $os === 'all' ? 'all' : ($os === 'unix' ? 'unix' : $os));
}
}
// ask for package config writing selection, same as artifact
$package_configs = Registry::getLoadedPackageConfigs();
$package_config_file = select("[{$package_name}] Please select the package config file to write the package config to", $package_configs);
return $package_generator->setConfigFile($package_config_file);
}
private function runCreateArtifact(
string $package_name,
?bool $create_source,
?bool $create_binary,
string|true|null $default_extract_dir = true
): ArtifactGenerator {
$artifact = new ArtifactGenerator($package_name);
if ($create_source === null) {
$create_source = confirm("[{$package_name}] Do you want to create a source artifact?");
}
if (!$create_source) {
goto binary;
}
$source_type = select("[{$package_name}] Where is the source code located?", SPC_DOWNLOAD_TYPE_DISPLAY_NAME);
$source_config = $this->askDownloadTypeConfig($package_name, $source_type, $default_extract_dir, 'source');
$artifact = $artifact->setSource($source_config);
binary:
if ($create_binary === null) {
$create_binary = confirm("[{$package_name}] Do you want to create a binary artifact?");
}
if (!$create_binary) {
goto end;
}
$binary_fix = [
'macos-x86_64' => null,
'macos-aarch64' => null,
'linux-x86_64' => null,
'linux-aarch64' => null,
'windows-x86_64' => null,
];
while (($os = select("[{$package_name}] Please configure the binary downloading options for OS", [
'macos-x86_64' => 'macos-x86_64' . ($binary_fix['macos-x86_64'] ? ' (done)' : ''),
'macos-aarch64' => 'macos-aarch64' . ($binary_fix['macos-aarch64'] ? ' (done)' : ''),
'linux-x86_64' => 'linux-x86_64' . ($binary_fix['linux-x86_64'] ? ' (done)' : ''),
'linux-aarch64' => 'linux-aarch64' . ($binary_fix['linux-aarch64'] ? ' (done)' : ''),
'windows-x86_64' => 'windows-x86_64' . ($binary_fix['windows-x86_64'] ? ' (done)' : ''),
'copy' => 'Duplicate from another OS',
'finish' => 'Submit',
])) !== 'finish') {
$source_type = select("[{$package_name}] Where is the binary for {$os} located?", SPC_DOWNLOAD_TYPE_DISPLAY_NAME);
$source_config = $this->askDownloadTypeConfig($package_name, $source_type, $default_extract_dir, 'binary');
// set to artifact
$artifact = $artifact->setBinary($os, $source_config);
$binary_fix[$os] = true;
}
end:
// generate config files, select existing package config file to write
$artifact_configs = Registry::getLoadedArtifactConfigs();
$artifact_config_file = select("[{$package_name}] Please select the artifact config file to write the artifact config to", $artifact_configs);
return $artifact->setConfigFile($artifact_config_file);
}
private function runCreateTarget(): PackageGenerator
{
throw new WrongUsageException('Not implemented');
}
private function runCreateExt(): PackageGenerator
{
throw new WrongUsageException('Not implemented');
}
private function askDownloadTypeConfig(string $package_name, int|string $source_type, bool|string|null $default_extract_dir, string $config_type): array
{
$source_config = ['type' => $source_type];
switch ($source_type) {
case 'bitbuckettag':
$source_config['repo'] = text("[{$package_name}] Please enter the BitBucket repository (e.g. user/repo)");
break;
case 'filelist':
$source_config['url'] = text(
"[{$package_name}] Please enter the file index website URL",
placeholder: 'e.g. https://ftp.gnu.org/pub/gnu/gettext/',
hint: 'Make sure the target url is a directory listing page like ftp.gnu.org.'
);
$source_config['regex'] = text(
"[{$package_name}] Please enter the regex pattern to match the archive file",
placeholder: 'e.g. /gettext-(\d+\.\d+(\.\d+)?)\.tar\.gz/',
default: "/href=\"(?<file>{$package_name}-(?<version>[^\"]+)\\.tar\\.gz)\"/",
hint: 'Make sure the regex contains a capturing group for the version number.'
);
break;
case 'git':
$source_config['url'] = text(
"[{$package_name}] Please enter the Git repository URL",
validate: function ($value) {
if (!filter_var($value, FILTER_VALIDATE_URL) && !preg_match('/^(git|ssh|http|https|git@[-\w.]+):(\/\/)?(.*?)(\.git)(\/?|#[-\d\w._]+?)$/', $value)) {
return 'Please enter a valid Git repository URL.';
}
return null;
},
hint: 'e.g. https://github.com/user/repo.git'
);
$source_config['rev'] = text(
"[{$package_name}] Please enter the Git revision (branch, tag, or commit hash)",
default: 'main',
hint: 'e.g. main, master, v1.0.0, or a commit hash'
);
break;
case 'ghrel':
$source_config['repo'] = text("[{$package_name}] Please enter the GitHub repository (e.g. user/repo)");
$source_config['match'] = text(
"[{$package_name}] Please enter the regex pattern to match the source archive file",
placeholder: 'e.g. /foo-(\d+\.\d+(\.\d+)?)\.tar\.gz/',
default: "{$package_name}-.+\\.tar\\.gz",
);
break;
case 'ghtar':
case 'ghtagtar':
$source_config['repo'] = text("[{$package_name}] Please enter the GitHub repository (e.g. user/repo)");
$source_config['prefer-stable'] = confirm("[{$package_name}] Do you want to prefer stable releases?");
if ($source_type === 'ghtagtar' && confirm('Do you want to match tags with a specific pattern?', default: false)) {
$source_config['match'] = text(
"[{$package_name}] Please enter the regex pattern to match tags",
placeholder: 'e.g. v(\d+\.\d+(\.\d+)?)',
);
}
break;
case 'local':
$source_config['dirname'] = text(
"[{$package_name}] Please enter the local directory path",
validate: function ($value) {
if (trim($value) === '') {
return 'Local source directory cannot be empty.';
}
if (!is_dir($value)) {
return 'The specified local source directory does not exist.';
}
return null;
},
);
break;
case 'pie':
$source_config['repo'] = text(
"[{$package_name}] Please enter the PIE repository name",
placeholder: 'e.g. user/repo',
);
break;
case 'url':
$source_config['url'] = text(
"[{$package_name}] Please enter the file download URL",
validate: function ($value) {
if (!filter_var($value, FILTER_VALIDATE_URL)) {
return 'Please enter a valid URL.';
}
return null;
},
);
break;
case 'custom':
break;
}
// ask extract dir if is true
if ($default_extract_dir === true) {
if (confirm('Do you want to specify a custom extract directory?')) {
$extract_hint = match ($config_type) {
'source' => 'the source will be from the `source/` dir by default',
'binary' => 'the binary will be from the `pkgroot/{arch}-{os}/` dir by default',
default => '',
};
$default_extract_dir = text(
"[{$package_name}] Please enter the source extract directory",
validate: function ($value) {
if (trim($value) === '') {
return 'Extract directory cannot be empty.';
}
return null;
},
hint: 'You can use relative path, ' . $extract_hint . '.'
);
} else {
$default_extract_dir = null;
}
}
if ($default_extract_dir !== null) {
$source_config['extract'] = $default_extract_dir;
}
// return config
return $source_config;
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Command\Dev;
use StaticPHP\Command\BaseCommand;
use StaticPHP\Registry\Registry;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand('dev:sort-config', 'Sort artifact configuration files alphabetically')]
class SortConfigCommand extends BaseCommand
{
public function handle(): int
{
// get loaded configs
$loded_configs = Registry::getLoadedArtifactConfigs();
foreach ($loded_configs as $file) {
$this->sortConfigFile($file);
}
$loaded_pkg_configs = Registry::getLoadedPackageConfigs();
foreach ($loaded_pkg_configs as $file) {
$this->sortConfigFile($file);
}
return static::SUCCESS;
}
private function sortConfigFile(mixed $file): void
{
$content = file_get_contents($file);
if ($content === false) {
$this->output->writeln("Failed to read artifact config file: {$file}");
return;
}
$data = json_decode($content, true);
if (!is_array($data)) {
$this->output->writeln("Invalid JSON format in artifact config file: {$file}");
return;
}
ksort($data);
foreach ($data as $artifact_name => &$config) {
ksort($config);
}
unset($config);
$new_content = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
file_put_contents($file, $new_content);
$this->output->writeln("Sorted artifact config file: {$file}");
}
}

View File

@ -11,26 +11,30 @@ class ArtifactConfig
{
private static array $artifact_configs = [];
public static function loadFromDir(string $dir, string $registry_name): void
public static function loadFromDir(string $dir, string $registry_name): array
{
if (!is_dir($dir)) {
throw new WrongUsageException("Directory {$dir} does not exist, cannot load artifact config.");
}
$loaded = [];
$files = glob("{$dir}/artifact.*.json");
if (is_array($files)) {
foreach ($files as $file) {
self::loadFromFile($file, $registry_name);
$loaded[] = $file;
}
}
if (file_exists("{$dir}/artifact.json")) {
self::loadFromFile("{$dir}/artifact.json", $registry_name);
$loaded[] = "{$dir}/artifact.json";
}
return $loaded;
}
/**
* Load artifact configurations from a specified JSON file.
*/
public static function loadFromFile(string $file, string $registry_name): void
public static function loadFromFile(string $file, string $registry_name): string
{
$content = file_get_contents($file);
if ($content === false) {
@ -45,6 +49,7 @@ class ArtifactConfig
self::$artifact_configs[$artifact_name] = $config;
Registry::_bindArtifactConfigFile($artifact_name, $registry_name, $file);
}
return $file;
}
/**

View File

@ -16,20 +16,24 @@ class PackageConfig
* Load package configurations from a specified directory.
* It will look for files matching the pattern 'pkg.*.json' and 'pkg.json'.
*/
public static function loadFromDir(string $dir, string $registry_name): void
public static function loadFromDir(string $dir, string $registry_name): array
{
if (!is_dir($dir)) {
throw new WrongUsageException("Directory {$dir} does not exist, cannot load pkg.json config.");
}
$loaded = [];
$files = glob("{$dir}/pkg.*.json");
if (is_array($files)) {
foreach ($files as $file) {
self::loadFromFile($file, $registry_name);
$loaded[] = $file;
}
}
if (file_exists("{$dir}/pkg.json")) {
self::loadFromFile("{$dir}/pkg.json", $registry_name);
$loaded[] = "{$dir}/pkg.json";
}
return $loaded;
}
/**
@ -37,7 +41,7 @@ class PackageConfig
*
* @param string $file the path to the json package configuration file
*/
public static function loadFromFile(string $file, string $registry_name): void
public static function loadFromFile(string $file, string $registry_name): string
{
$content = file_get_contents($file);
if ($content === false) {
@ -52,6 +56,7 @@ class PackageConfig
self::$package_configs[$pkg_name] = $config;
Registry::_bindPackageConfigFile($pkg_name, $registry_name, $file);
}
return $file;
}
/**

View File

@ -10,6 +10,7 @@ use StaticPHP\Command\Dev\EnvCommand;
use StaticPHP\Command\Dev\IsInstalledCommand;
use StaticPHP\Command\Dev\ShellCommand;
use StaticPHP\Command\Dev\SkeletonCommand;
use StaticPHP\Command\Dev\SortConfigCommand;
use StaticPHP\Command\DoctorCommand;
use StaticPHP\Command\DownloadCommand;
use StaticPHP\Command\ExtractCommand;
@ -61,6 +62,7 @@ class ConsoleApplication extends Application
new IsInstalledCommand(),
new EnvCommand(),
new SkeletonCommand(),
new SortConfigCommand(),
]);
// add additional commands from registries

View File

@ -133,9 +133,8 @@ class PackageInstaller
// show install or build options in terminal with beautiful output
$this->printInstallerInfo();
InteractiveTerm::notice('Build process will start after 2s ...');
InteractiveTerm::notice('Build process will start after 2s ...' . PHP_EOL);
sleep(2);
echo PHP_EOL;
}
// Early validation: check if packages can be built or installed before downloading

View File

@ -13,13 +13,39 @@ use Symfony\Component\Yaml\Yaml;
class Registry
{
/** @var array<string, Registry> List of loaded registries */
/** @var string[] List of loaded registries */
private static array $loaded_registries = [];
/** @var array<string, array> Loaded registry configs */
private static array $registry_configs = [];
private static array $loaded_package_configs = [];
private static array $loaded_artifact_configs = [];
/** @var array<string, array{registry: string, config: string}> Maps of package and artifact names to their registry config file paths (for reverse lookup) */
private static array $package_reversed_registry_files = [];
private static array $artifact_reversed_registry_files = [];
/**
* Get the current registry configuration.
* "Current" depends on SPC load mode
*/
public static function getRegistryConfig(?string $registry_name = null): array
{
if ($registry_name === null && spc_mode(SPC_MODE_SOURCE)) {
return self::$registry_configs['internal'];
}
if ($registry_name !== null && isset(self::$registry_configs[$registry_name])) {
return self::$registry_configs[$registry_name];
}
if ($registry_name === null) {
throw new RegistryException('No registry name specified.');
}
throw new RegistryException("Registry '{$registry_name}' is not loaded.");
}
/**
* Load a registry from file path.
* This method handles external registries that may not be in composer autoload.
@ -52,12 +78,14 @@ class Registry
return;
}
self::$loaded_registries[] = $registry_name;
self::$registry_configs[$registry_name] = $data;
self::$registry_configs[$registry_name]['_file'] = $registry_file;
logger()->debug("Loading registry '{$registry_name}' from file: {$registry_file}");
// Load composer autoload if specified (for external registries with their own dependencies)
if (isset($data['autoload']) && is_string($data['autoload'])) {
$autoload_path = self::fullpath($data['autoload'], dirname($registry_file));
$autoload_path = FileSystem::fullpath($data['autoload'], dirname($registry_file));
if (file_exists($autoload_path)) {
logger()->debug("Loading external autoload from: {$autoload_path}");
require_once $autoload_path;
@ -69,7 +97,7 @@ class Registry
// load doctor items from PSR-4 directories
if (isset($data['doctor']['psr-4']) && is_assoc_array($data['doctor']['psr-4'])) {
foreach ($data['doctor']['psr-4'] as $namespace => $path) {
$path = self::fullpath($path, dirname($registry_file));
$path = FileSystem::fullpath($path, dirname($registry_file));
DoctorLoader::loadFromPsr4Dir($path, $namespace, $auto_require);
}
}
@ -87,11 +115,11 @@ class Registry
// load package configs
if (isset($data['package']['config']) && is_array($data['package']['config'])) {
foreach ($data['package']['config'] as $path) {
$path = self::fullpath($path, dirname($registry_file));
$path = FileSystem::fullpath($path, dirname($registry_file));
if (is_file($path)) {
PackageConfig::loadFromFile($path, $registry_name);
self::$loaded_package_configs[] = PackageConfig::loadFromFile($path, $registry_name);
} elseif (is_dir($path)) {
PackageConfig::loadFromDir($path, $registry_name);
self::$loaded_package_configs = array_merge(self::$loaded_package_configs, PackageConfig::loadFromDir($path, $registry_name));
}
}
}
@ -99,11 +127,11 @@ class Registry
// load artifact configs
if (isset($data['artifact']['config']) && is_array($data['artifact']['config'])) {
foreach ($data['artifact']['config'] as $path) {
$path = self::fullpath($path, dirname($registry_file));
$path = FileSystem::fullpath($path, dirname($registry_file));
if (is_file($path)) {
ArtifactConfig::loadFromFile($path, $registry_name);
self::$loaded_artifact_configs[] = ArtifactConfig::loadFromFile($path, $registry_name);
} elseif (is_dir($path)) {
ArtifactConfig::loadFromDir($path, $registry_name);
self::$loaded_package_configs = array_merge(self::$loaded_package_configs, ArtifactConfig::loadFromDir($path, $registry_name));
}
}
}
@ -111,7 +139,7 @@ class Registry
// load packages from PSR-4 directories
if (isset($data['package']['psr-4']) && is_assoc_array($data['package']['psr-4'])) {
foreach ($data['package']['psr-4'] as $namespace => $path) {
$path = self::fullpath($path, dirname($registry_file));
$path = FileSystem::fullpath($path, dirname($registry_file));
PackageLoader::loadFromPsr4Dir($path, $namespace, $auto_require);
}
}
@ -129,7 +157,7 @@ class Registry
// load artifacts from PSR-4 directories
if (isset($data['artifact']['psr-4']) && is_assoc_array($data['artifact']['psr-4'])) {
foreach ($data['artifact']['psr-4'] as $namespace => $path) {
$path = self::fullpath($path, dirname($registry_file));
$path = FileSystem::fullpath($path, dirname($registry_file));
ArtifactLoader::loadFromPsr4Dir($path, $namespace, $auto_require);
}
}
@ -147,7 +175,7 @@ class Registry
// load additional commands from PSR-4 directories
if (isset($data['command']['psr-4']) && is_assoc_array($data['command']['psr-4'])) {
foreach ($data['command']['psr-4'] as $namespace => $path) {
$path = self::fullpath($path, dirname($registry_file));
$path = FileSystem::fullpath($path, dirname($registry_file));
$classes = FileSystem::getClassesPsr4($path, $namespace, auto_require: $auto_require);
$instances = array_map(fn ($x) => new $x(), $classes);
ConsoleApplication::_addAdditionalCommands($instances);
@ -262,6 +290,16 @@ class Registry
return self::$artifact_reversed_registry_files[$artifact_name] ?? null;
}
public static function getLoadedPackageConfigs(): array
{
return self::$loaded_package_configs;
}
public static function getLoadedArtifactConfigs(): array
{
return self::$loaded_artifact_configs;
}
/**
* Parse a class entry from the classes array.
* Supports two formats:
@ -298,7 +336,7 @@ class Registry
// If file path is provided, require it
if ($file_path !== null) {
$full_path = self::fullpath($file_path, $base_path);
$full_path = FileSystem::fullpath($file_path, $base_path);
require_once $full_path;
return;
}
@ -311,21 +349,4 @@ class Registry
" 3. Provide file path in classes map: \"{$class}\": \"path/to/file.php\""
);
}
/**
* Return full path, resolving relative paths against a base path.
*
* @param string $path Input path (relative or absolute)
* @param string $relative_path_base Base path for relative paths
*/
private static function fullpath(string $path, string $relative_path_base): string
{
if (FileSystem::isRelativePath($path)) {
$path = $relative_path_base . DIRECTORY_SEPARATOR . $path;
}
if (!file_exists($path)) {
throw new RegistryException("Path does not exist: {$path}");
}
return FileSystem::convertPath($path);
}
}

View File

@ -1,11 +1,21 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Skeleton;
use StaticPHP\Exception\FileSystemException;
use StaticPHP\Exception\ValidationException;
use StaticPHP\Util\FileSystem;
class ArtifactGenerator
{
protected ?array $source = null;
protected ?array $binary = null;
protected ?string $config_file = null;
protected bool $generate_class = false;
protected bool $generate_custom_source_func = false;
@ -45,13 +55,61 @@ class ArtifactGenerator
return $this->source;
}
public function generateConfig(): array
public function setBinary(string $os, array $config): static
{
$clone = clone $this;
if ($clone->binary === null) {
$clone->binary = [$os => $config];
} else {
$clone->binary[$os] = $config;
}
return $clone;
}
public function generateConfigArray(): array
{
$config = [];
if ($this->source) {
$config['source'] = $this->source;
}
if ($this->binary) {
$config['binary'] = $this->binary;
}
return $config;
}
public function setConfigFile(string $file): static
{
$clone = clone $this;
$clone->config_file = $file;
return $clone;
}
/**
* Write the artifact configuration to the config file.
*/
public function writeConfigFile(): string
{
if ($this->config_file === null) {
throw new ValidationException('Config file path is not set.');
}
$config_array = $this->generateConfigArray();
$config_file_json = json_decode(FileSystem::readFile($this->config_file), true);
if (!is_array($config_file_json)) {
throw new ValidationException('Existing config file is not a valid JSON array.');
}
$config_file_json[$this->name] = $config_array;
// sort keys
ksort($config_file_json);
$json_content = json_encode($config_file_json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
if ($json_content === false) {
throw new ValidationException('Failed to encode config array to JSON.');
}
if (file_put_contents($this->config_file, $json_content) === false) {
throw new FileSystemException("Failed to write config file: {$this->config_file}");
}
return $this->config_file;
}
}

View File

@ -1,9 +1,14 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Skeleton;
use StaticPHP\Exception\ValidationException;
use StaticPHP\Runtime\Executor\Executor;
use StaticPHP\Runtime\Executor\UnixAutoconfExecutor;
use StaticPHP\Runtime\Executor\UnixCMakeExecutor;
use StaticPHP\Runtime\Executor\WindowsCMakeExecutor;
class ExecutorGenerator
{
@ -13,4 +18,19 @@ class ExecutorGenerator
throw new ValidationException('Executor class must extend ' . Executor::class);
}
}
/**
* Generate the code to create an instance of the executor.
*
* @return array{0: string, 1: string} an array containing the class name and the code string
*/
public function generateCode(): array
{
return match ($this->class) {
UnixCMakeExecutor::class => [UnixCMakeExecutor::class, 'UnixCMakeExecutor::create($package)->build();'],
UnixAutoconfExecutor::class => [UnixAutoconfExecutor::class, 'UnixAutoconfExecutor::create($package)->build();'],
WindowsCMakeExecutor::class => [WindowsCMakeExecutor::class, 'WindowsCMakeExecutor::create($package)->build();'],
default => throw new ValidationException("Unsupported executor class: {$this->class}"),
};
}
}

View File

@ -1,33 +1,49 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Skeleton;
use Nette\PhpGenerator\PhpFile;
use Nette\PhpGenerator\Printer;
use StaticPHP\Attribute\Package\BuildFor;
use StaticPHP\Attribute\Package\Extension;
use StaticPHP\Attribute\Package\Library;
use StaticPHP\Attribute\Package\Target;
use StaticPHP\Exception\FileSystemException;
use StaticPHP\Exception\ValidationException;
use StaticPHP\Package\LibraryPackage;
use StaticPHP\Package\PackageInstaller;
use StaticPHP\Package\PhpExtensionPackage;
use StaticPHP\Package\TargetPackage;
use StaticPHP\Util\FileSystem;
/**
* A skeleton class for generating package files and configs.
*/
class PackageGenerator
{
/** @var array<''|'unix'|'windows'|'macos'|'linux', string[]> $depends An array of dependencies required by the package, categorized by operating system. */
/** @var array<''|'linux'|'macos'|'unix'|'windows', string[]> $depends An array of dependencies required by the package, categorized by operating system. */
protected array $depends = [];
/** @var array<''|'unix'|'windows'|'macos'|'linux', string[]> $suggests An array of suggested packages for the package, categorized by operating system. */
/** @var array<''|'linux'|'macos'|'unix'|'windows', string[]> $suggests An array of suggested packages for the package, categorized by operating system. */
protected array $suggests = [];
/** @var array<string> $frameworks An array of macOS frameworks for the package */
protected array $frameworks = [];
/** @var array<''|'unix'|'windows'|'macos'|'linux', string[]> $static_libs An array of static libraries required by the package, categorized by operating system. */
/** @var array<''|'linux'|'macos'|'unix'|'windows', string[]> $static_libs An array of static libraries required by the package, categorized by operating system. */
protected array $static_libs = [];
/** @var array<''|'unix'|'windows'|'macos'|'linux', string[]> $headers An array of header files required by the package, categorized by operating system. */
/** @var array<''|'linux'|'macos'|'unix'|'windows', string[]> $headers An array of header files required by the package, categorized by operating system. */
protected array $headers = [];
/** @var array<''|'unix'|'windows'|'macos'|'linux', string[]> $static_bins An array of static binaries required by the package, categorized by operating system. */
/** @var array<''|'linux'|'macos'|'unix'|'windows', string[]> $static_bins An array of static binaries required by the package, categorized by operating system. */
protected array $static_bins = [];
/** @var ArtifactGenerator|null $artifact Artifact */
protected ?string $config_file = null;
/** @var null|ArtifactGenerator $artifact Artifact */
protected ?ArtifactGenerator $artifact = null;
/** @var array $licenses Licenses */
@ -44,28 +60,28 @@ class PackageGenerator
protected array $func_executor_binding = [];
/**
* @param string $package_name Package name
* @param 'library'|'target'|'virtual-target'|'php-extension' $type Package type ('library', 'target', 'virtual-target', etc.)
* @param string $package_name Package name
* @param 'library'|'php-extension'|'target'|'virtual-target' $type Package type ('library', 'target', 'virtual-target', etc.)
*/
public function __construct(protected string $package_name, protected string $type) {}
/**
* Add package dependency.
*
* @param string $package Package name
* @param string $os Operating system ('' for all OSes, '@unix', '@windows', '@macos')
* @param string $package Package name
* @param string $os_category Operating system ('' for all OSes, 'unix', 'windows', 'macos')
*/
public function addDependency(string $package, string $os = ''): static
public function addDependency(string $package, string $os_category = ''): static
{
if (!in_array($os, ['', 'unix', 'windows', 'macos', 'linux'], true)) {
throw new ValidationException("Invalid OS suffix: {$os}");
if (!in_array($os_category, ['', ...SUPPORTED_OS_CATEGORY], true)) {
throw new ValidationException("Invalid OS suffix: {$os_category}");
}
$clone = clone $this;
if (!isset($clone->depends[$os])) {
$clone->depends[$os] = [];
if (!isset($clone->depends[$os_category])) {
$clone->depends[$os_category] = [];
}
if (!in_array($package, $clone->depends[$os], true)) {
$clone->depends[$os][] = $package;
if (!in_array($package, $clone->depends[$os_category], true)) {
$clone->depends[$os_category][] = $package;
}
return $clone;
}
@ -73,78 +89,78 @@ class PackageGenerator
/**
* Add package suggestion.
*
* @param string $package Package name
* @param string $os Operating system ('' for all OSes, '@unix', '@windows', '@macos')
* @param string $package Package name
* @param string $os_category Operating system ('' for all OSes)
*/
public function addSuggestion(string $package, string $os = ''): static
public function addSuggestion(string $package, string $os_category = ''): static
{
if (!in_array($os, ['', 'unix', 'windows', 'macos', 'linux'], true)) {
throw new ValidationException("Invalid OS suffix: {$os}");
if (!in_array($os_category, ['', ...SUPPORTED_OS_CATEGORY], true)) {
throw new ValidationException("Invalid OS suffix: {$os_category}");
}
$clone = clone $this;
if (!isset($clone->suggests[$os])) {
$clone->suggests[$os] = [];
if (!isset($clone->suggests[$os_category])) {
$clone->suggests[$os_category] = [];
}
if (!in_array($package, $clone->suggests[$os], true)) {
$clone->suggests[$os][] = $package;
if (!in_array($package, $clone->suggests[$os_category], true)) {
$clone->suggests[$os_category][] = $package;
}
return $clone;
}
public function addStaticLib(string $lib_a, string $os = ''): static
public function addStaticLib(string $lib_a, string $os_category = ''): static
{
if (!in_array($os, ['', 'unix', 'windows', 'macos', 'linux'], true)) {
throw new ValidationException("Invalid OS suffix: {$os}");
if (!in_array($os_category, ['', ...SUPPORTED_OS_CATEGORY], true)) {
throw new ValidationException("Invalid OS suffix: {$os_category}");
}
if (!str_ends_with($lib_a, '.lib') && !str_ends_with($lib_a, '.a')) {
throw new ValidationException("Static library must end with .lib or .a, got: {$lib_a}");
}
if (str_ends_with($lib_a, '.lib') && in_array($os, ['unix', 'linux', 'macos'], true)) {
if (str_ends_with($lib_a, '.lib') && in_array($os_category, ['unix', 'linux', 'macos'], true)) {
throw new ValidationException("Static library with .lib extension cannot be added for non-Windows OS: {$lib_a}");
}
if (str_ends_with($lib_a, '.a') && $os === 'windows') {
if (str_ends_with($lib_a, '.a') && $os_category === 'windows') {
throw new ValidationException("Static library with .a extension cannot be added for Windows OS: {$lib_a}");
}
if (isset($this->static_libs[$os]) && in_array($lib_a, $this->static_libs[$os], true)) {
if (isset($this->static_libs[$os_category]) && in_array($lib_a, $this->static_libs[$os_category], true)) {
// already exists
return $this;
}
$clone = clone $this;
if (!isset($clone->static_libs[$os])) {
$clone->static_libs[$os] = [];
if (!isset($clone->static_libs[$os_category])) {
$clone->static_libs[$os_category] = [];
}
if (!in_array($lib_a, $clone->static_libs[$os], true)) {
$clone->static_libs[$os][] = $lib_a;
if (!in_array($lib_a, $clone->static_libs[$os_category], true)) {
$clone->static_libs[$os_category][] = $lib_a;
}
return $clone;
}
public function addHeader(string $header_file, string $os = ''): static
public function addHeader(string $header_file, string $os_category = ''): static
{
if (!in_array($os, ['', 'unix', 'windows', 'macos', 'linux'], true)) {
throw new ValidationException("Invalid OS suffix: {$os}");
if (!in_array($os_category, ['', ...SUPPORTED_OS_CATEGORY], true)) {
throw new ValidationException("Invalid OS suffix: {$os_category}");
}
$clone = clone $this;
if (!isset($clone->headers[$os])) {
$clone->headers[$os] = [];
if (!isset($clone->headers[$os_category])) {
$clone->headers[$os_category] = [];
}
if (!in_array($header_file, $clone->headers[$os], true)) {
$clone->headers[$os][] = $header_file;
if (!in_array($header_file, $clone->headers[$os_category], true)) {
$clone->headers[$os_category][] = $header_file;
}
return $clone;
}
public function addStaticBin(string $bin_file, string $os = ''): static
public function addStaticBin(string $bin_file, string $os_category = ''): static
{
if (!in_array($os, ['', 'unix', 'windows', 'macos', 'linux'], true)) {
throw new ValidationException("Invalid OS suffix: {$os}");
if (!in_array($os_category, ['', ...SUPPORTED_OS_CATEGORY], true)) {
throw new ValidationException("Invalid OS suffix: {$os_category}");
}
$clone = clone $this;
if (!isset($clone->static_bins[$os])) {
$clone->static_bins[$os] = [];
if (!isset($clone->static_bins[$os_category])) {
$clone->static_bins[$os_category] = [];
}
if (!in_array($bin_file, $clone->static_bins[$os], true)) {
$clone->static_bins[$os][] = $bin_file;
if (!in_array($bin_file, $clone->static_bins[$os_category], true)) {
$clone->static_bins[$os_category][] = $bin_file;
}
return $clone;
}
@ -194,9 +210,9 @@ class PackageGenerator
/**
* Enable build for specific OS.
*
* @param 'Windows'|'Linux'|'Darwin'|array<'Windows'|'Linux'|'Darwin'> $build_for Build for OS
* @param 'Darwin'|'Linux'|'Windows'|array<'Darwin'|'Linux'|'Windows'> $build_for Build for OS
*/
public function enableBuild(string|array $build_for, ?string $build_function_name = null): static
public function enableBuild(array|string $build_for, ?string $build_function_name = null): static
{
$clone = clone $this;
if (is_array($build_for)) {
@ -205,6 +221,9 @@ class PackageGenerator
}
return $clone;
}
if (!in_array($build_for, SUPPORTED_OS_FAMILY, true)) {
throw new ValidationException("Unsupported build_for value: {$build_for}");
}
$clone->build_for_enables[$build_for] = $build_function_name ?? "buildFor{$build_for}";
return $clone;
}
@ -212,8 +231,8 @@ class PackageGenerator
/**
* Bind function executor.
*
* @param string $func_name Function name
* @param ExecutorGenerator $executor Executor generator
* @param string $func_name Function name
* @param ExecutorGenerator $executor Executor generator
*/
public function addFunctionExecutorBinding(string $func_name, ExecutorGenerator $executor): static
{
@ -222,10 +241,73 @@ class PackageGenerator
return $clone;
}
public function generatePackageClassFile(string $namespace, bool $uppercase = false): string
{
$printer = new class extends Printer {
public string $indentation = ' ';
};
$file = new PhpFile();
$namespace = $file->setStrictTypes()->addNamespace($namespace);
$uses = [];
// class name and package attribute
$class_name = str_replace('-', '_', $uppercase ? ucwords($this->package_name, '-') : $this->package_name);
$class_attribute = match ($this->type) {
'library' => Library::class,
'php-extension' => Extension::class,
'target', 'virtual-target' => Target::class,
};
$package_class = match ($this->type) {
'library' => LibraryPackage::class,
'php-extension' => PhpExtensionPackage::class,
'target', 'virtual-target' => TargetPackage::class,
};
$uses[] = $class_attribute;
$uses[] = $package_class;
$uses[] = BuildFor::class;
$uses[] = PackageInstaller::class;
foreach ($uses as $use) {
$namespace->addUse($use);
}
// add class attribute
$class = $namespace->addClass($class_name);
$class->addAttribute($class_attribute, [$this->package_name]);
// add build functions if enabled
$funcs = [];
foreach ($this->build_for_enables as $os_family => $func_name) {
if ($func_name !== null) {
$funcs[$func_name][] = $os_family;
}
}
foreach ($funcs as $name => $oss) {
$method = $class->addMethod(name: $name ?: 'build')
->setPublic()
->setReturnType('void');
// check if function executor is bound
if (isset($this->func_executor_binding[$name])) {
$executor = $this->func_executor_binding[$name];
[$executor_use, $code] = $executor->generateCode();
$namespace->addUse($executor_use);
$method->setBody($code);
}
$method->addParameter('package')->setType($package_class);
$method->addParameter('installer')->setType(PackageInstaller::class);
foreach ($oss as $os) {
$method->addAttribute(BuildFor::class, [$os]);
}
}
return $printer->printFile($file);
}
/**
* Generate package config
*/
public function generateConfig(): array
public function generateConfigArray(): array
{
$config = ['type' => $this->type];
@ -280,4 +362,51 @@ class PackageGenerator
return $config;
}
public function setConfigFile(string $config_file): static
{
$clone = clone $this;
$clone->config_file = $config_file;
return $clone;
}
public function writeConfigFile(): string
{
if ($this->config_file === null) {
throw new ValidationException('Config file path is not set.');
}
$config_array = $this->generateConfigArray();
$config_file_json = json_decode(FileSystem::readFile($this->config_file), true);
if (!is_array($config_file_json)) {
throw new ValidationException('Existing config file is not a valid JSON array.');
}
$config_file_json[$this->package_name] = $config_array;
ksort($config_file_json);
$json_content = json_encode($config_file_json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
if ($json_content === false) {
throw new ValidationException('Failed to encode package config to JSON.');
}
if (file_put_contents($this->config_file, $json_content) === false) {
throw new FileSystemException("Failed to write config file: {$this->config_file}");
}
return $this->config_file;
}
public function writeAll(): array
{
// write config
$package_config_file = $this->writeConfigFile();
$artifact_config_file = $this->artifact->writeConfigFile();
// write class file
$package_class_file_content = $this->generatePackageClassFile('StaticPHP\Packages');
$package_class_file_path = str_replace('-', '_', $this->package_name) . '.php';
// file_put_contents($package_class_file_path, $package_class_file_content); // Uncomment this line to actually write the file
return [
'package_config' => $package_config_file,
'artifact_config' => $artifact_config_file,
'package_class_file' => $package_class_file_path,
'package_class_content' => $package_class_file_content,
];
}
}

View File

@ -472,6 +472,23 @@ class FileSystem
return file_put_contents($file, implode('', $lines));
}
/**
* Return full path, resolving relative paths against a base path.
*
* @param string $path Input path (relative or absolute)
* @param string $relative_path_base Base path for relative paths
*/
public static function fullpath(string $path, string $relative_path_base): string
{
if (FileSystem::isRelativePath($path)) {
$path = $relative_path_base . DIRECTORY_SEPARATOR . $path;
}
if (!file_exists($path)) {
throw new FileSystemException("Path does not exist: {$path}");
}
return FileSystem::convertPath($path);
}
private static function replaceFile(string $filename, int $replace_type = REPLACE_FILE_STR, mixed $callback_or_search = null, mixed $to_replace = null): false|int
{
logger()->debug('Replacing file with type[' . $replace_type . ']: ' . $filename);

View File

@ -52,6 +52,7 @@ class InteractiveTerm
default => logger()->info(strip_ansi_colors($message)),
};
} else {
$output = $level === 'error' && $output instanceof ConsoleOutput ? $output->getErrorOutput() : $output;
$output->writeln(($no_ansi ? 'strip_ansi_colors' : 'strval')($message));
}
}

View File

@ -96,13 +96,32 @@ const SPC_STATUS_ALREADY_BUILT = 1;
const SPC_DOWNLOAD_TYPE_DISPLAY_NAME = [
'bitbuckettag' => 'BitBucket',
'filelist' => 'website',
'filelist' => 'File index website',
'git' => 'git',
'ghrel' => 'GitHub release',
'ghtar', 'ghtagtar' => 'GitHub tarball',
'ghtar' => 'GitHub release tarball',
'ghtagtar' => 'GitHub tag tarball',
'local' => 'local dir',
'pie' => 'PHP Installer for Extensions',
'pie' => 'PHP Installer for Extensions (PIE)',
'url' => 'url',
'php-release' => 'php.net',
'custom' => 'custom downloader',
];
const SUPPORTED_OS_CATEGORY = [
'unix',
'windows',
'linux',
'macos',
];
const SUPPORTED_OS_FAMILY = [
'Linux',
'Darwin',
'Windows',
];
const SPC_MODE_SOURCE = 1;
const SPC_MODE_VENDOR = 2;
const SPC_MODE_PHAR = 4;
const SPC_MODE_VENDOR_PHAR = SPC_MODE_VENDOR | SPC_MODE_PHAR;

View File

@ -10,6 +10,31 @@ use StaticPHP\Runtime\Shell\UnixShell;
use StaticPHP\Runtime\Shell\WindowsCmd;
use ZM\Logger\ConsoleLogger;
/**
* Get the current SPC loading mode. If passed a mode to check, will return whether current mode matches the given mode.
*/
function spc_mode(?int $check_mode = null): bool|int
{
$mode = SPC_MODE_SOURCE;
// if current file is in phar, then it's phar mode
if (str_starts_with(__FILE__, 'phar://') && Phar::running()) {
// judge whether it's vendor mode (inside vendor/) or source mode (inside src/)
if (basename(dirname(__FILE__, 3)) === 'static-php-cli' && basename(dirname(__FILE__, 5)) === 'vendor') {
$mode = SPC_MODE_VENDOR_PHAR;
} else {
$mode = SPC_MODE_PHAR;
}
} elseif (basename(dirname(__FILE__, 3)) === 'static-php-cli' && basename(dirname(__FILE__, 5)) === 'vendor') {
$mode = SPC_MODE_VENDOR;
}
if ($check_mode === null) {
return $mode;
}
// use bitwise AND to check mode
return ($mode & $check_mode) !== 0;
}
/**
* Judge if an array is an associative array
*/