Skip to content

Commit e28580d

Browse files
committed
Refactor shell utilities: reorganize namespaces and introduce Shell base class
1 parent cc447a0 commit e28580d

File tree

10 files changed

+314
-150
lines changed

10 files changed

+314
-150
lines changed

src/SPC/util/WindowsCmd.php

Lines changed: 0 additions & 78 deletions
This file was deleted.

src/SPC/util/executor/UnixAutoconfExecutor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use SPC\builder\linux\library\LinuxLibraryBase;
99
use SPC\builder\macos\library\MacOSLibraryBase;
1010
use SPC\exception\RuntimeException;
11-
use SPC\util\UnixShell;
11+
use SPC\util\shell\UnixShell;
1212

1313
class UnixAutoconfExecutor extends Executor
1414
{

src/SPC/util/executor/UnixCMakeExecutor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use SPC\builder\linux\library\LinuxLibraryBase;
99
use SPC\builder\macos\library\MacOSLibraryBase;
1010
use SPC\store\FileSystem;
11-
use SPC\util\UnixShell;
11+
use SPC\util\shell\UnixShell;
1212

1313
/**
1414
* Unix-like OS cmake command executor.

src/SPC/util/shell/Shell.php

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SPC\util\shell;
6+
7+
use SPC\exception\ExecutionException;
8+
9+
abstract class Shell
10+
{
11+
protected ?string $cd = null;
12+
13+
protected bool $debug;
14+
15+
protected array $env = [];
16+
17+
protected string $last_cmd = '';
18+
19+
protected bool $enable_log_file = true;
20+
21+
public function __construct(?bool $debug = null, bool $enable_log_file = true)
22+
{
23+
$this->debug = $debug ?? defined('DEBUG_MODE');
24+
$this->enable_log_file = $enable_log_file;
25+
}
26+
27+
/**
28+
* Equivalent to `cd` command in shell.
29+
*
30+
* @param string $dir Directory to change to
31+
*/
32+
public function cd(string $dir): static
33+
{
34+
logger()->debug('Entering dir: ' . $dir);
35+
$c = clone $this;
36+
$c->cd = $dir;
37+
return $c;
38+
}
39+
40+
public function setEnv(array $env): static
41+
{
42+
foreach ($env as $k => $v) {
43+
if (trim($v) === '') {
44+
continue;
45+
}
46+
$this->env[$k] = trim($v);
47+
}
48+
return $this;
49+
}
50+
51+
public function appendEnv(array $env): static
52+
{
53+
foreach ($env as $k => $v) {
54+
if ($v === '') {
55+
continue;
56+
}
57+
if (!isset($this->env[$k])) {
58+
$this->env[$k] = $v;
59+
} else {
60+
$this->env[$k] = "{$v} {$this->env[$k]}";
61+
}
62+
}
63+
return $this;
64+
}
65+
66+
/**
67+
* Executes a command in the shell.
68+
*/
69+
abstract public function exec(string $cmd): static;
70+
71+
/**
72+
* Returns the last executed command.
73+
*/
74+
public function getLastCommand(): string
75+
{
76+
return $this->last_cmd;
77+
}
78+
79+
/**
80+
* Executes a command with console and log file output.
81+
*
82+
* @param string $cmd Full command to execute (including cd and env vars)
83+
* @param bool $console_output If true, output will be printed to console
84+
* @param null|string $original_command Original command string for logging
85+
*/
86+
protected function passthru(string $cmd, bool $console_output = false, ?string $original_command = null): void
87+
{
88+
// write executed command to the log file using fwrite
89+
$file_res = fopen(SPC_SHELL_LOG, 'a');
90+
if ($console_output) {
91+
$console_res = STDOUT;
92+
}
93+
$descriptors = [
94+
0 => ['file', 'php://stdin', 'r'], // stdin
95+
1 => ['pipe', 'w'], // stdout
96+
2 => ['pipe', 'w'], // stderr
97+
];
98+
$process = proc_open($cmd, $descriptors, $pipes);
99+
100+
try {
101+
if (!is_resource($process)) {
102+
throw new ExecutionException(
103+
cmd: $original_command ?? $cmd,
104+
message: 'Failed to open process for command, proc_open() failed.',
105+
code: -1,
106+
cd: $this->cd,
107+
env: $this->env
108+
);
109+
}
110+
// fclose($pipes[0]);
111+
stream_set_blocking($pipes[1], false);
112+
stream_set_blocking($pipes[2], false);
113+
114+
while (true) {
115+
$read = [$pipes[1], $pipes[2]];
116+
$write = null;
117+
$except = null;
118+
119+
$ready = stream_select($read, $write, $except, 0, 100000);
120+
121+
if ($ready === false) {
122+
$status = proc_get_status($process);
123+
if (!$status['running']) {
124+
break;
125+
}
126+
continue;
127+
}
128+
129+
if ($ready > 0) {
130+
foreach ($read as $pipe) {
131+
$chunk = fgets($pipe);
132+
if ($chunk !== false) {
133+
if ($console_output) {
134+
fwrite($console_res, $chunk);
135+
}
136+
if ($this->enable_log_file) {
137+
fwrite($file_res, $chunk);
138+
}
139+
}
140+
}
141+
}
142+
143+
$status = proc_get_status($process);
144+
if (!$status['running']) {
145+
// check exit code
146+
if ($status['exitcode'] !== 0) {
147+
if ($this->enable_log_file) {
148+
fwrite($file_res, "Command exited with non-zero code: {$status['exitcode']}\n");
149+
}
150+
throw new ExecutionException(
151+
cmd: $original_command ?? $cmd,
152+
message: "Command exited with non-zero code: {$status['exitcode']}",
153+
code: $status['exitcode'],
154+
cd: $this->cd,
155+
env: $this->env,
156+
);
157+
}
158+
break;
159+
}
160+
}
161+
} finally {
162+
fclose($pipes[1]);
163+
fclose($pipes[2]);
164+
fclose($file_res);
165+
proc_close($process);
166+
}
167+
}
168+
169+
/**
170+
* Logs the command information to a log file.
171+
*/
172+
abstract protected function logCommandInfo(string $cmd): void;
173+
}

0 commit comments

Comments
 (0)