diff --git a/phpstan.neon b/phpstan.neon index cf6e4974..45e512ba 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,6 +1,7 @@ parameters: reportUnmatchedIgnoredErrors: false level: 4 + phpVersion: 80400 paths: - ./src/ ignoreErrors: diff --git a/skeleton-test.php b/skeleton-test.php index 689d3385..59fbbb7e 100644 --- a/skeleton-test.php +++ b/skeleton-test.php @@ -1,8 +1,11 @@ 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'); diff --git a/src/Package/Artifact/zig.php b/src/Package/Artifact/zig.php index a7347395..2ac7b454 100644 --- a/src/Package/Artifact/zig.php +++ b/src/Package/Artifact/zig.php @@ -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; diff --git a/src/StaticPHP/Artifact/ArtifactDownloader.php b/src/StaticPHP/Artifact/ArtifactDownloader.php index 315cfb11..b53ddd8a 100644 --- a/src/StaticPHP/Artifact/ArtifactDownloader.php +++ b/src/StaticPHP/Artifact/ArtifactDownloader.php @@ -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) { diff --git a/src/StaticPHP/Command/BaseCommand.php b/src/StaticPHP/Command/BaseCommand.php index e416be26..02f84ffb 100644 --- a/src/StaticPHP/Command/BaseCommand.php +++ b/src/StaticPHP/Command/BaseCommand.php @@ -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); } } diff --git a/src/StaticPHP/Command/Dev/SkeletonCommand.php b/src/StaticPHP/Command/Dev/SkeletonCommand.php new file mode 100644 index 00000000..b19eb4a1 --- /dev/null +++ b/src/StaticPHP/Command/Dev/SkeletonCommand.php @@ -0,0 +1,402 @@ +output->writeln('The dev:skel command is not available in phar mode.'); + return 1; + } + if (SystemTarget::getTargetOS() === 'Windows') { + $this->output->writeln('The dev:skel command is not available on Windows systems.'); + 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("Package config in: {$write['package_config']}"); + $this->output->writeln("Artifact config in: {$write['artifact_config']}"); + $this->output->writeln('Package class:'); + $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('You must specify at least one of static libraries, header files, or static binaries produced.'); + 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=\"(?{$package_name}-(?[^\"]+)\\.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; + } +} diff --git a/src/StaticPHP/Command/Dev/SortConfigCommand.php b/src/StaticPHP/Command/Dev/SortConfigCommand.php new file mode 100644 index 00000000..aa3a9ecd --- /dev/null +++ b/src/StaticPHP/Command/Dev/SortConfigCommand.php @@ -0,0 +1,49 @@ +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}"); + } +} diff --git a/src/StaticPHP/Config/ArtifactConfig.php b/src/StaticPHP/Config/ArtifactConfig.php index e840c0bf..49abae92 100644 --- a/src/StaticPHP/Config/ArtifactConfig.php +++ b/src/StaticPHP/Config/ArtifactConfig.php @@ -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; } /** diff --git a/src/StaticPHP/Config/PackageConfig.php b/src/StaticPHP/Config/PackageConfig.php index 3342dfc7..56ef7ab1 100644 --- a/src/StaticPHP/Config/PackageConfig.php +++ b/src/StaticPHP/Config/PackageConfig.php @@ -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; } /** diff --git a/src/StaticPHP/ConsoleApplication.php b/src/StaticPHP/ConsoleApplication.php index bd0cfd60..fd7650aa 100644 --- a/src/StaticPHP/ConsoleApplication.php +++ b/src/StaticPHP/ConsoleApplication.php @@ -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 diff --git a/src/StaticPHP/Package/PackageInstaller.php b/src/StaticPHP/Package/PackageInstaller.php index 4c44ce92..ae3b7346 100644 --- a/src/StaticPHP/Package/PackageInstaller.php +++ b/src/StaticPHP/Package/PackageInstaller.php @@ -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 diff --git a/src/StaticPHP/Registry/Registry.php b/src/StaticPHP/Registry/Registry.php index 909de021..e464ed47 100644 --- a/src/StaticPHP/Registry/Registry.php +++ b/src/StaticPHP/Registry/Registry.php @@ -13,13 +13,39 @@ use Symfony\Component\Yaml\Yaml; class Registry { - /** @var array List of loaded registries */ + /** @var string[] List of loaded registries */ private static array $loaded_registries = []; + /** @var array Loaded registry configs */ + private static array $registry_configs = []; + + private static array $loaded_package_configs = []; + + private static array $loaded_artifact_configs = []; + /** @var array 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); - } } diff --git a/src/StaticPHP/Skeleton/ArtifactGenerator.php b/src/StaticPHP/Skeleton/ArtifactGenerator.php index 031d095b..e05f94d1 100644 --- a/src/StaticPHP/Skeleton/ArtifactGenerator.php +++ b/src/StaticPHP/Skeleton/ArtifactGenerator.php @@ -1,11 +1,21 @@ 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; + } } diff --git a/src/StaticPHP/Skeleton/ExecutorGenerator.php b/src/StaticPHP/Skeleton/ExecutorGenerator.php index 02edf2eb..69d7a9f8 100644 --- a/src/StaticPHP/Skeleton/ExecutorGenerator.php +++ b/src/StaticPHP/Skeleton/ExecutorGenerator.php @@ -1,9 +1,14 @@ 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}"), + }; + } } diff --git a/src/StaticPHP/Skeleton/PackageGenerator.php b/src/StaticPHP/Skeleton/PackageGenerator.php index 95d3c87f..89802899 100644 --- a/src/StaticPHP/Skeleton/PackageGenerator.php +++ b/src/StaticPHP/Skeleton/PackageGenerator.php @@ -1,33 +1,49 @@ $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 $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, + ]; + } } diff --git a/src/StaticPHP/Util/FileSystem.php b/src/StaticPHP/Util/FileSystem.php index 2f540d70..1c21a92b 100644 --- a/src/StaticPHP/Util/FileSystem.php +++ b/src/StaticPHP/Util/FileSystem.php @@ -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); diff --git a/src/StaticPHP/Util/InteractiveTerm.php b/src/StaticPHP/Util/InteractiveTerm.php index 1682ed1f..0570f31c 100644 --- a/src/StaticPHP/Util/InteractiveTerm.php +++ b/src/StaticPHP/Util/InteractiveTerm.php @@ -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)); } } diff --git a/src/globals/defines.php b/src/globals/defines.php index 3e6d2360..dbcb63f2 100644 --- a/src/globals/defines.php +++ b/src/globals/defines.php @@ -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; diff --git a/src/globals/functions.php b/src/globals/functions.php index 93cd1ae0..bb22f3a7 100644 --- a/src/globals/functions.php +++ b/src/globals/functions.php @@ -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 */