diff --git a/src/Package/Library/onig.php b/src/Package/Library/onig.php new file mode 100644 index 00000000..2cea572b --- /dev/null +++ b/src/Package/Library/onig.php @@ -0,0 +1,24 @@ +addConfigureArgs('-DMSVC_STATIC_RUNTIME=ON') + ->build(); + FileSystem::copy("{$package->getLibDir()}\\onig.lib", "{$package->getLibDir()}\\onig_a.lib"); + } +} diff --git a/src/StaticPHP/Runtime/Executor/WindowsCMakeExecutor.php b/src/StaticPHP/Runtime/Executor/WindowsCMakeExecutor.php new file mode 100644 index 00000000..1fb9c72b --- /dev/null +++ b/src/StaticPHP/Runtime/Executor/WindowsCMakeExecutor.php @@ -0,0 +1,224 @@ +package); + if ($builder !== null) { + $this->builder = $builder; + } elseif (ApplicationContext::has(PackageBuilder::class)) { + $this->builder = ApplicationContext::get(PackageBuilder::class); + } else { + throw new SPCInternalException('PackageBuilder not found in ApplicationContext.'); + } + $this->installer = ApplicationContext::get(PackageInstaller::class); + $this->initCmd(); + + // judge that this package has artifact.source and defined build stage + if (!$this->package->hasStage('build')) { + throw new SPCInternalException("Package {$this->package->getName()} does not have a build stage defined."); + } + } + + public function build(): static + { + $this->initBuildDir(); + + if ($this->reset) { + FileSystem::resetDir($this->build_dir); + } + + // configure + if ($this->steps >= 1) { + $args = array_merge($this->configure_args, $this->getDefaultCMakeArgs()); + $args = array_diff($args, $this->ignore_args); + $configure_args = implode(' ', $args); + InteractiveTerm::setMessage('Building package: ' . ConsoleColor::yellow($this->package->getName() . ' (cmake configure)')); + $this->cmd->exec("cmake {$configure_args}"); + } + + // make + if ($this->steps >= 2) { + InteractiveTerm::setMessage('Building package: ' . ConsoleColor::yellow($this->package->getName() . ' (cmake build)')); + $this->cmd->cd($this->build_dir)->exec("cmake --build {$this->build_dir} --config Release --target install -j{$this->builder->concurrency}"); + } + + return $this; + } + + /** + * Add optional package configuration. + * This method checks if a package is available and adds the corresponding arguments to the CMake configuration. + * + * @param string $name package name to check + * @param \Closure|string $true_args arguments to use if the package is available (allow closure, returns string) + * @param string $false_args arguments to use if the package is not available + * @return $this + */ + public function optionalPackage(string $name, \Closure|string $true_args, string $false_args = ''): static + { + if ($get = $this->installer->getResolvedPackages()[$name] ?? null) { + logger()->info("Building package [{$this->package->getName()}] with {$name} support"); + $args = $true_args instanceof \Closure ? $true_args($get) : $true_args; + } else { + logger()->info("Building package [{$this->package->getName()}] without {$name} support"); + $args = $false_args; + } + $this->addConfigureArgs($args); + return $this; + } + + /** + * Add configure args. + */ + public function addConfigureArgs(...$args): static + { + $this->configure_args = [...$this->configure_args, ...$args]; + return $this; + } + + /** + * Remove some configure args, to bypass the configure option checking for some libs. + */ + public function removeConfigureArgs(...$args): static + { + $this->ignore_args = [...$this->ignore_args, ...$args]; + return $this; + } + + public function setEnv(array $env): static + { + $this->cmd->setEnv($env); + return $this; + } + + public function appendEnv(array $env): static + { + $this->cmd->appendEnv($env); + return $this; + } + + /** + * To build steps. + * + * @param int $step Step number, accept 1-3 + * @return $this + */ + public function toStep(int $step): static + { + $this->steps = $step; + return $this; + } + + /** + * Set custom CMake build directory. + * + * @param string $dir custom CMake build directory + */ + public function setBuildDir(string $dir): static + { + $this->build_dir = $dir; + return $this; + } + + /** + * Set the custom default args. + */ + public function setCustomDefaultArgs(...$args): static + { + $this->custom_default_args = $args; + return $this; + } + + /** + * Set the reset status. + * If we set it to false, it will not clean and create the specified cmake working directory. + */ + public function setReset(bool $reset): static + { + $this->reset = $reset; + return $this; + } + + /** + * Get configure argument string. + */ + public function getConfigureArgsString(): string + { + return implode(' ', array_merge($this->configure_args, $this->getDefaultCMakeArgs())); + } + + /** + * Returns the default CMake args. + */ + private function getDefaultCMakeArgs(): array + { + return $this->custom_default_args ?? [ + '-A x64', + '-DCMAKE_BUILD_TYPE=Release', + '-DBUILD_SHARED_LIBS=OFF', + '-DBUILD_STATIC_LIBS=ON', + "-DCMAKE_TOOLCHAIN_FILE={$this->makeCmakeToolchainFile()}", + '-DCMAKE_INSTALL_PREFIX=' . escapeshellarg($this->package->getBuildRootPath()), + '-B ' . escapeshellarg(FileSystem::convertPath($this->build_dir)), + ]; + } + + private function makeCmakeToolchainFile(): string + { + if (file_exists(SOURCE_PATH . '\toolchain.cmake')) { + return SOURCE_PATH . '\toolchain.cmake'; + } + return WindowsUtil::makeCmakeToolchainFile(); + } + + /** + * Initialize the CMake build directory. + * If the directory is not set, it defaults to the package's source directory with '/build' appended. + */ + private function initBuildDir(): void + { + if ($this->build_dir === null) { + $this->build_dir = "{$this->package->getSourceDir()}\\build"; + } + } + + private function initCmd(): void + { + $this->cmd = cmd()->cd($this->package->getSourceDir()); + } +} diff --git a/src/StaticPHP/Runtime/Shell/Shell.php b/src/StaticPHP/Runtime/Shell/Shell.php index bf30d9d8..7300b8e1 100644 --- a/src/StaticPHP/Runtime/Shell/Shell.php +++ b/src/StaticPHP/Runtime/Shell/Shell.php @@ -149,7 +149,8 @@ abstract class Shell ?string $original_command = null, bool $capture_output = false, bool $throw_on_error = true, - ?string $cwd = null + ?string $cwd = null, + ?array $env = null, ): array { $file_res = null; if ($this->enable_log_file) { @@ -164,7 +165,13 @@ abstract class Shell 1 => PHP_OS_FAMILY === 'Windows' ? ['socket'] : ['pipe', 'w'], // stdout 2 => PHP_OS_FAMILY === 'Windows' ? ['socket'] : ['pipe', 'w'], // stderr ]; - $process = proc_open($cmd, $descriptors, $pipes, $cwd); + if ($env !== null && $env !== []) { + // merge current PHP envs + $env = array_merge(getenv(), $env); + } else { + $env = null; + } + $process = proc_open($cmd, $descriptors, $pipes, $cwd, env_vars: $env); $output_value = ''; try { diff --git a/src/StaticPHP/Runtime/Shell/WindowsCmd.php b/src/StaticPHP/Runtime/Shell/WindowsCmd.php index a60c41b2..e9d7a6c0 100644 --- a/src/StaticPHP/Runtime/Shell/WindowsCmd.php +++ b/src/StaticPHP/Runtime/Shell/WindowsCmd.php @@ -45,23 +45,11 @@ class WindowsCmd extends Shell logger()->debug('Running command with result: ' . $cmd); } $cmd = $this->getExecString($cmd); - $result = $this->passthru($cmd, $this->console_putput, $cmd, capture_output: true, throw_on_error: false, cwd: $this->cd); + $result = $this->passthru($cmd, $this->console_putput, $cmd, capture_output: true, throw_on_error: false, cwd: $this->cd, env: $this->env); $out = explode("\n", $result['output']); return [$result['code'], $out]; } - public function setEnv(array $env): static - { - // windows currently does not support setting environment variables - throw new SPCInternalException('Windows does not support setting environment variables in shell commands.'); - } - - public function appendEnv(array $env): static - { - // windows currently does not support appending environment variables - throw new SPCInternalException('Windows does not support appending environment variables in shell commands.'); - } - public function getLastCommand(): string { return $this->last_cmd;