Refactor micro:combine command on v3

This commit is contained in:
crazywhalecc
2026-04-09 15:16:44 +08:00
parent 2256f47aed
commit 1d0ccdec45
3 changed files with 179 additions and 0 deletions

57
TODO.md Normal file
View File

@@ -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)

View File

@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Command;
use StaticPHP\Util\FileSystem;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
#[AsCommand('micro:combine', 'Combine micro.sfx and php code together')]
class MicroCombineCommand extends BaseCommand
{
public function configure(): void
{
$this->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('<error>The micro.sfx file you specified is incorrect or does not exist!</error>');
return static::FAILURE;
}
// 2. Make sure buildroot/bin/micro.sfx exists
if ($micro_file === null && !file_exists($internal)) {
$this->output->writeln('<error>You haven\'t compiled micro.sfx yet, please use "build" command and "--build-micro" to compile phpmicro first!</error>');
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('<error>The file to combine does not exist!</error>');
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('<error>The ini file to combine does not exist! (' . $ini_file . ')</error>');
return static::FAILURE;
}
$arr = parse_ini_file($ini_file);
if ($arr === false) {
$this->output->writeln('<error>Cannot parse ini file</error>');
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('<error>--with-ini-set parse failed</error>');
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('<error>Combine failed.</error>');
return static::FAILURE;
}
// 9. chmod +x
chmod($output, 0755);
$this->output->writeln('<info>Combine success! Binary file: ' . $output . '</info>');
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);
}
}

View File

@@ -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(),