diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..52d6133a --- /dev/null +++ b/TODO.md @@ -0,0 +1,57 @@ +# v3 TODO List + +Tracking items identified during the v2 → v3 migration audit. + +--- + +## Commands + +- [ ] Implement `craft` command (drives full build from `craft.yml`; should be easier with v3 vendor/registry mode) +- [x] Migrate `micro:combine` command (combine `micro.sfx` with PHP code + INI injection) +- [ ] Implement `dump-extensions` command (extract required extensions from `composer.json` / `composer.lock`) +- [ ] Design and implement v3 dev toolchain commands (WIP — needs design decision): + - [ ] `dev:extensions` / equivalent listing command + - [ ] `dev:php-version`, `dev:ext-version`, `dev:lib-version` + - [ ] Doc generation commands (`dev:gen-ext-docs`, `dev:gen-ext-dep-docs`, `dev:gen-lib-dep-docs`) — pending v3 doc design + +--- + +## Source Patches (SourcePatcher → Artifact migration) + +The following v2 `SourcePatcher` hooks are not yet migrated to v3 `src/Package/Artifact/` classes: + +- [ ] Migrate `patchSQLSRVWin32` — removes `/sdl` compile flag to prevent Zend build failure on Windows +- [ ] Migrate `patchSQLSRVPhp85` — fixes `pdo_sqlsrv` directory layout for PHP 8.5 +- [ ] Migrate `patchYamlWin32` — patches `config.w32` `_a.lib` detection logic for the `yaml` extension +- [ ] Migrate `patchImagickWith84` — applies PHP 8.4 compatibility patch for `imagick` based on version detection + +--- + +## Extension Package Classes (Unix) + +Extensions that had non-trivial v2 build logic and are missing a v3 `src/Package/Extension/` class: + +- [x] `gettext` — macOS: fix `config.m4` bracket syntax for cross-version compatibility + append frameworks to linker flags (critical for macOS linking; this is a Unix-side gap, not Windows-only) + +--- + +## Windows Extensions (Early Stage) + +Windows extension support is still in early stage. The following extensions had Windows-specific configure args or patches in v2 and are pending v3 Windows implementation: + +- [ ] `amqp` — Windows configure args +- [ ] `com_dotnet` — Windows-only extension +- [ ] `dom` — remove `dllmain.c` from `config.w32` +- [ ] `ev` — fix `PHP_EV_SHARED` in `config.w32` +- [ ] `gmssl` — add `CHECK_LIB("gmssl.lib")` to `config.w32` +- [ ] `intl` — fix `PHP_INTL_SHARED` in `config.w32` +- [ ] `lz4` — Windows configure args +- [ ] `mbregex` — Windows configure args +- [ ] `sqlsrv` / `pdo_sqlsrv` — complex conditional build logic (independent `sqlsrv` without `pdo_sqlsrv`) +- [ ] `xml` — remove `dllmain.c` from `config.w32`; handles `soap`, `xmlreader`, `xmlwriter`, `simplexml` + +--- + +## Documentation + +- [ ] Write v3 user documentation (currently zero v3 docs) diff --git a/src/StaticPHP/Command/MicroCombineCommand.php b/src/StaticPHP/Command/MicroCombineCommand.php new file mode 100644 index 00000000..b46d995f --- /dev/null +++ b/src/StaticPHP/Command/MicroCombineCommand.php @@ -0,0 +1,120 @@ +addArgument('file', InputArgument::REQUIRED, 'The php or phar file to be combined'); + $this->addOption('with-micro', 'M', InputOption::VALUE_REQUIRED, 'Customize your micro.sfx file'); + $this->addOption('with-ini-set', 'I', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'ini to inject into micro.sfx when combining'); + $this->addOption('with-ini-file', 'N', InputOption::VALUE_REQUIRED, 'ini file to inject into micro.sfx when combining'); + $this->addOption('output', 'O', InputOption::VALUE_REQUIRED, 'Customize your output binary file name'); + } + + public function handle(): int + { + // 0. Initialize path variables + $internal = FileSystem::convertPath(BUILD_ROOT_PATH . '/bin/micro.sfx'); + $micro_file = $this->getOption('with-micro'); + $file = $this->getArgument('file'); + $ini_set = $this->getOption('with-ini-set'); + $ini_file = $this->getOption('with-ini-file'); + $target_ini = []; + $output = $this->getOption('output') ?? 'my-app'; + $ini_part = ''; + // 1. Make sure specified micro.sfx file exists + if ($micro_file !== null && !file_exists($micro_file)) { + $this->output->writeln('The micro.sfx file you specified is incorrect or does not exist!'); + return static::FAILURE; + } + // 2. Make sure buildroot/bin/micro.sfx exists + if ($micro_file === null && !file_exists($internal)) { + $this->output->writeln('You haven\'t compiled micro.sfx yet, please use "build" command and "--build-micro" to compile phpmicro first!'); + return static::FAILURE; + } + // 3. Use buildroot/bin/micro.sfx + if ($micro_file === null) { + $micro_file = $internal; + } + // 4. Make sure php or phar file exists + if (!is_file(FileSystem::convertPath($file))) { + $this->output->writeln('The file to combine does not exist!'); + return static::FAILURE; + } + // 5. Confirm ini files (ini-set has higher priority) + if ($ini_file !== null) { + // Check file exist first + if (!file_exists($ini_file)) { + $this->output->writeln('The ini file to combine does not exist! (' . $ini_file . ')'); + return static::FAILURE; + } + $arr = parse_ini_file($ini_file); + if ($arr === false) { + $this->output->writeln('Cannot parse ini file'); + return static::FAILURE; + } + $target_ini = array_merge($target_ini, $arr); + } + // 6. Confirm ini sets + if ($ini_set !== []) { + foreach ($ini_set as $item) { + $arr = parse_ini_string($item); + if ($arr === false) { + $this->output->writeln('--with-ini-set parse failed'); + return static::FAILURE; + } + $target_ini = array_merge($target_ini, $arr); + } + } + // 7. Generate ini injection parts + if (!empty($target_ini)) { + $ini_str = $this->encodeINI($target_ini); + logger()->debug('Injecting ini parts: ' . PHP_EOL . $ini_str); + $ini_part = "\xfd\xf6\x69\xe6"; + $ini_part .= pack('N', strlen($ini_str)); + $ini_part .= $ini_str; + } + // 8. Combine ! + $output = FileSystem::isRelativePath($output) ? (WORKING_DIR . '/' . $output) : $output; + $file_target = file_get_contents($micro_file) . $ini_part . file_get_contents($file); + if (PHP_OS_FAMILY === 'Windows' && !str_ends_with(strtolower($output), '.exe')) { + $output .= '.exe'; + } + $output = FileSystem::convertPath($output); + $result = file_put_contents($output, $file_target); + if ($result === false) { + $this->output->writeln('Combine failed.'); + return static::FAILURE; + } + // 9. chmod +x + chmod($output, 0755); + $this->output->writeln('Combine success! Binary file: ' . $output . ''); + return static::SUCCESS; + } + + private function encodeINI(array $array): string + { + $res = []; + foreach ($array as $key => $val) { + if (is_array($val)) { + $res[] = "[{$key}]"; + foreach ($val as $skey => $sval) { + $res[] = "{$skey}=" . (is_numeric($sval) ? $sval : '"' . $sval . '"'); + } + } else { + $res[] = "{$key}=" . (is_numeric($val) ? $val : '"' . $val . '"'); + } + } + return implode("\n", $res); + } +} diff --git a/src/StaticPHP/ConsoleApplication.php b/src/StaticPHP/ConsoleApplication.php index 2882b574..0c8fec83 100644 --- a/src/StaticPHP/ConsoleApplication.php +++ b/src/StaticPHP/ConsoleApplication.php @@ -20,6 +20,7 @@ use StaticPHP\Command\DownloadCommand; use StaticPHP\Command\DumpLicenseCommand; use StaticPHP\Command\ExtractCommand; use StaticPHP\Command\InstallPackageCommand; +use StaticPHP\Command\MicroCombineCommand; use StaticPHP\Command\ResetCommand; use StaticPHP\Command\SPCConfigCommand; use StaticPHP\Package\TargetPackage; @@ -65,6 +66,7 @@ class ConsoleApplication extends Application new DumpLicenseCommand(), new ResetCommand(), new CheckUpdateCommand(), + new MicroCombineCommand(), // dev commands new ShellCommand(),