From 97eff64b5bd4372c5ab17378f3ad9a8fe6e3c763 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 20 Jul 2023 01:15:28 +0800 Subject: [PATCH] add micro:combine command --- src/SPC/command/MicroCombineCommand.php | 116 ++++++++++++++++++++++++ src/SPC/store/FileSystem.php | 14 +++ 2 files changed, 130 insertions(+) create mode 100644 src/SPC/command/MicroCombineCommand.php diff --git a/src/SPC/command/MicroCombineCommand.php b/src/SPC/command/MicroCombineCommand.php new file mode 100644 index 00000000..2da6df49 --- /dev/null +++ b/src/SPC/command/MicroCombineCommand.php @@ -0,0 +1,116 @@ +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->input->getOption('with-micro'); + $file = $this->getArgument('file'); + $ini_set = $this->input->getOption('with-ini-set'); + $ini_file = $this->input->getOption('with-ini-file'); + $target_ini = []; + $output = $this->input->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 1; + } + // 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 1; + } + // 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 1; + } + // 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 1; + } + $arr = parse_ini_file($ini_file); + if ($arr === false) { + $this->output->writeln('Cannot parse ini file'); + return 1; + } + $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 1; + } + $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); + $result = file_put_contents($output, $file_target); + if ($result === false) { + $this->output->writeln('Combine failed.'); + return 1; + } + // 9. chmod +x + chmod($output, 0755); + $this->output->writeln('Combine success! Binary file: ' . $output . ''); + return 0; + } + + 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/SPC/store/FileSystem.php b/src/SPC/store/FileSystem.php index 6c4b515a..b8439dbe 100644 --- a/src/SPC/store/FileSystem.php +++ b/src/SPC/store/FileSystem.php @@ -439,6 +439,20 @@ class FileSystem self::$_extract_hook[$name][] = $callback; } + /** + * Check whether the path is a relative path (judging according to whether the first character is "/") + * + * @param string $path Path + */ + public static function isRelativePath(string $path): bool + { + // 适配 Windows 的多盘符目录形式 + if (DIRECTORY_SEPARATOR === '\\') { + return !(strlen($path) > 2 && ctype_alpha($path[0]) && $path[1] === ':'); + } + return strlen($path) > 0 && $path[0] !== '/'; + } + private static function emitSourceExtractHook(string $name) { foreach ((self::$_extract_hook[$name] ?? []) as $hook) {