Skip to content

Commit 6d292b4

Browse files
committed
Add WindowsCMakeExecutor
1 parent f6b47ad commit 6d292b4

File tree

4 files changed

+258
-15
lines changed

4 files changed

+258
-15
lines changed

src/Package/Library/onig.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Package\Library;
6+
7+
use StaticPHP\Attribute\Package\BuildFor;
8+
use StaticPHP\Attribute\Package\Library;
9+
use StaticPHP\Package\LibraryPackage;
10+
use StaticPHP\Runtime\Executor\WindowsCMakeExecutor;
11+
use StaticPHP\Util\FileSystem;
12+
13+
#[Library('onig')]
14+
class onig
15+
{
16+
#[BuildFor('Windows')]
17+
public function buildWin(LibraryPackage $package): void
18+
{
19+
WindowsCMakeExecutor::create($package)
20+
->addConfigureArgs('-DMSVC_STATIC_RUNTIME=ON')
21+
->build();
22+
FileSystem::copy("{$package->getLibDir()}\\onig.lib", "{$package->getLibDir()}\\onig_a.lib");
23+
}
24+
}
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace StaticPHP\Runtime\Executor;
6+
7+
use StaticPHP\DI\ApplicationContext;
8+
use StaticPHP\Exception\SPCInternalException;
9+
use StaticPHP\Package\LibraryPackage;
10+
use StaticPHP\Package\PackageBuilder;
11+
use StaticPHP\Package\PackageInstaller;
12+
use StaticPHP\Runtime\Shell\WindowsCmd;
13+
use StaticPHP\Util\FileSystem;
14+
use StaticPHP\Util\InteractiveTerm;
15+
use StaticPHP\Util\System\WindowsUtil;
16+
use ZM\Logger\ConsoleColor;
17+
18+
class WindowsCMakeExecutor extends Executor
19+
{
20+
protected WindowsCmd $cmd;
21+
22+
protected array $configure_args = [];
23+
24+
protected array $ignore_args = [];
25+
26+
protected ?string $build_dir = null;
27+
28+
protected ?array $custom_default_args = null;
29+
30+
protected int $steps = 2;
31+
32+
protected bool $reset = true;
33+
34+
protected PackageBuilder $builder;
35+
36+
protected PackageInstaller $installer;
37+
38+
public function __construct(protected LibraryPackage $package, ?PackageBuilder $builder = null)
39+
{
40+
parent::__construct($this->package);
41+
if ($builder !== null) {
42+
$this->builder = $builder;
43+
} elseif (ApplicationContext::has(PackageBuilder::class)) {
44+
$this->builder = ApplicationContext::get(PackageBuilder::class);
45+
} else {
46+
throw new SPCInternalException('PackageBuilder not found in ApplicationContext.');
47+
}
48+
$this->installer = ApplicationContext::get(PackageInstaller::class);
49+
$this->initCmd();
50+
51+
// judge that this package has artifact.source and defined build stage
52+
if (!$this->package->hasStage('build')) {
53+
throw new SPCInternalException("Package {$this->package->getName()} does not have a build stage defined.");
54+
}
55+
}
56+
57+
public function build(): static
58+
{
59+
$this->initBuildDir();
60+
61+
if ($this->reset) {
62+
FileSystem::resetDir($this->build_dir);
63+
}
64+
65+
// configure
66+
if ($this->steps >= 1) {
67+
$args = array_merge($this->configure_args, $this->getDefaultCMakeArgs());
68+
$args = array_diff($args, $this->ignore_args);
69+
$configure_args = implode(' ', $args);
70+
InteractiveTerm::setMessage('Building package: ' . ConsoleColor::yellow($this->package->getName() . ' (cmake configure)'));
71+
$this->cmd->exec("cmake {$configure_args}");
72+
}
73+
74+
// make
75+
if ($this->steps >= 2) {
76+
InteractiveTerm::setMessage('Building package: ' . ConsoleColor::yellow($this->package->getName() . ' (cmake build)'));
77+
$this->cmd->cd($this->build_dir)->exec("cmake --build {$this->build_dir} --config Release --target install -j{$this->builder->concurrency}");
78+
}
79+
80+
return $this;
81+
}
82+
83+
/**
84+
* Add optional package configuration.
85+
* This method checks if a package is available and adds the corresponding arguments to the CMake configuration.
86+
*
87+
* @param string $name package name to check
88+
* @param \Closure|string $true_args arguments to use if the package is available (allow closure, returns string)
89+
* @param string $false_args arguments to use if the package is not available
90+
* @return $this
91+
*/
92+
public function optionalPackage(string $name, \Closure|string $true_args, string $false_args = ''): static
93+
{
94+
if ($get = $this->installer->getResolvedPackages()[$name] ?? null) {
95+
logger()->info("Building package [{$this->package->getName()}] with {$name} support");
96+
$args = $true_args instanceof \Closure ? $true_args($get) : $true_args;
97+
} else {
98+
logger()->info("Building package [{$this->package->getName()}] without {$name} support");
99+
$args = $false_args;
100+
}
101+
$this->addConfigureArgs($args);
102+
return $this;
103+
}
104+
105+
/**
106+
* Add configure args.
107+
*/
108+
public function addConfigureArgs(...$args): static
109+
{
110+
$this->configure_args = [...$this->configure_args, ...$args];
111+
return $this;
112+
}
113+
114+
/**
115+
* Remove some configure args, to bypass the configure option checking for some libs.
116+
*/
117+
public function removeConfigureArgs(...$args): static
118+
{
119+
$this->ignore_args = [...$this->ignore_args, ...$args];
120+
return $this;
121+
}
122+
123+
public function setEnv(array $env): static
124+
{
125+
$this->cmd->setEnv($env);
126+
return $this;
127+
}
128+
129+
public function appendEnv(array $env): static
130+
{
131+
$this->cmd->appendEnv($env);
132+
return $this;
133+
}
134+
135+
/**
136+
* To build steps.
137+
*
138+
* @param int $step Step number, accept 1-3
139+
* @return $this
140+
*/
141+
public function toStep(int $step): static
142+
{
143+
$this->steps = $step;
144+
return $this;
145+
}
146+
147+
/**
148+
* Set custom CMake build directory.
149+
*
150+
* @param string $dir custom CMake build directory
151+
*/
152+
public function setBuildDir(string $dir): static
153+
{
154+
$this->build_dir = $dir;
155+
return $this;
156+
}
157+
158+
/**
159+
* Set the custom default args.
160+
*/
161+
public function setCustomDefaultArgs(...$args): static
162+
{
163+
$this->custom_default_args = $args;
164+
return $this;
165+
}
166+
167+
/**
168+
* Set the reset status.
169+
* If we set it to false, it will not clean and create the specified cmake working directory.
170+
*/
171+
public function setReset(bool $reset): static
172+
{
173+
$this->reset = $reset;
174+
return $this;
175+
}
176+
177+
/**
178+
* Get configure argument string.
179+
*/
180+
public function getConfigureArgsString(): string
181+
{
182+
return implode(' ', array_merge($this->configure_args, $this->getDefaultCMakeArgs()));
183+
}
184+
185+
/**
186+
* Returns the default CMake args.
187+
*/
188+
private function getDefaultCMakeArgs(): array
189+
{
190+
return $this->custom_default_args ?? [
191+
'-A x64',
192+
'-DCMAKE_BUILD_TYPE=Release',
193+
'-DBUILD_SHARED_LIBS=OFF',
194+
'-DBUILD_STATIC_LIBS=ON',
195+
"-DCMAKE_TOOLCHAIN_FILE={$this->makeCmakeToolchainFile()}",
196+
'-DCMAKE_INSTALL_PREFIX=' . escapeshellarg($this->package->getBuildRootPath()),
197+
'-B ' . escapeshellarg(FileSystem::convertPath($this->build_dir)),
198+
];
199+
}
200+
201+
private function makeCmakeToolchainFile(): string
202+
{
203+
if (file_exists(SOURCE_PATH . '\toolchain.cmake')) {
204+
return SOURCE_PATH . '\toolchain.cmake';
205+
}
206+
return WindowsUtil::makeCmakeToolchainFile();
207+
}
208+
209+
/**
210+
* Initialize the CMake build directory.
211+
* If the directory is not set, it defaults to the package's source directory with '/build' appended.
212+
*/
213+
private function initBuildDir(): void
214+
{
215+
if ($this->build_dir === null) {
216+
$this->build_dir = "{$this->package->getSourceDir()}\\build";
217+
}
218+
}
219+
220+
private function initCmd(): void
221+
{
222+
$this->cmd = cmd()->cd($this->package->getSourceDir());
223+
}
224+
}

src/StaticPHP/Runtime/Shell/Shell.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ protected function passthru(
149149
?string $original_command = null,
150150
bool $capture_output = false,
151151
bool $throw_on_error = true,
152-
?string $cwd = null
152+
?string $cwd = null,
153+
?array $env = null,
153154
): array {
154155
$file_res = null;
155156
if ($this->enable_log_file) {
@@ -164,7 +165,13 @@ protected function passthru(
164165
1 => PHP_OS_FAMILY === 'Windows' ? ['socket'] : ['pipe', 'w'], // stdout
165166
2 => PHP_OS_FAMILY === 'Windows' ? ['socket'] : ['pipe', 'w'], // stderr
166167
];
167-
$process = proc_open($cmd, $descriptors, $pipes, $cwd);
168+
if ($env !== null && $env !== []) {
169+
// merge current PHP envs
170+
$env = array_merge(getenv(), $env);
171+
} else {
172+
$env = null;
173+
}
174+
$process = proc_open($cmd, $descriptors, $pipes, $cwd, env_vars: $env);
168175

169176
$output_value = '';
170177
try {

src/StaticPHP/Runtime/Shell/WindowsCmd.php

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,23 +45,11 @@ public function execWithResult(string $cmd, bool $with_log = true): array
4545
logger()->debug('Running command with result: ' . $cmd);
4646
}
4747
$cmd = $this->getExecString($cmd);
48-
$result = $this->passthru($cmd, $this->console_putput, $cmd, capture_output: true, throw_on_error: false, cwd: $this->cd);
48+
$result = $this->passthru($cmd, $this->console_putput, $cmd, capture_output: true, throw_on_error: false, cwd: $this->cd, env: $this->env);
4949
$out = explode("\n", $result['output']);
5050
return [$result['code'], $out];
5151
}
5252

53-
public function setEnv(array $env): static
54-
{
55-
// windows currently does not support setting environment variables
56-
throw new SPCInternalException('Windows does not support setting environment variables in shell commands.');
57-
}
58-
59-
public function appendEnv(array $env): static
60-
{
61-
// windows currently does not support appending environment variables
62-
throw new SPCInternalException('Windows does not support appending environment variables in shell commands.');
63-
}
64-
6553
public function getLastCommand(): string
6654
{
6755
return $this->last_cmd;

0 commit comments

Comments
 (0)