aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-20 23:37:42 +0900
committernsfisis <nsfisis@gmail.com>2026-06-20 23:37:42 +0900
commit84655bf3a4a21dbbe7aec8e3aee1e661505bbb6d (patch)
tree189f8ce5d7d53246632d5e2419c392d58f06e2b8 /crates/shirabe/src
parent10840ec1fd7f1140305449fc94175d60998572c1 (diff)
downloadphp-shirabe-84655bf3a4a21dbbe7aec8e3aee1e661505bbb6d.tar.gz
php-shirabe-84655bf3a4a21dbbe7aec8e3aee1e661505bbb6d.tar.zst
php-shirabe-84655bf3a4a21dbbe7aec8e3aee1e661505bbb6d.zip
feat(symfony-process): port full Process class from PHP
Faithfully port every method, field and constant of Symfony's Process.php into process.rs, replacing the reduced stub. Add the supporting pipes module (PipesInterface/AbstractPipes/UnixPipes/ WindowsPipes), ProcessUtils and the missing process exceptions (LogicException/InvalidArgumentException) with constructors. Methods now return anyhow::Result where PHP throws, take the env argument and a bool-returning callback, and borrow &mut for status-updating accessors; all callers are updated accordingly. Extend the php-shim with proc_open/proc_close (PHP-compatible signatures), proc_get_status, proc_terminate, posix_kill, uniqid, ftruncate, ftell_stream, fseek3, stream_get_contents3 and env helpers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src')
-rw-r--r--crates/shirabe/src/downloader/zip_downloader.rs4
-rw-r--r--crates/shirabe/src/package/version/version_guesser.rs4
-rw-r--r--crates/shirabe/src/repository/vcs/perforce_driver.rs2
-rw-r--r--crates/shirabe/src/util/filesystem.rs2
-rw-r--r--crates/shirabe/src/util/perforce.rs37
-rw-r--r--crates/shirabe/src/util/process_executor.rs57
6 files changed, 68 insertions, 38 deletions
diff --git a/crates/shirabe/src/downloader/zip_downloader.rs b/crates/shirabe/src/downloader/zip_downloader.rs
index 3d72377..d7508a6 100644
--- a/crates/shirabe/src/downloader/zip_downloader.rs
+++ b/crates/shirabe/src/downloader/zip_downloader.rs
@@ -135,7 +135,7 @@ impl ZipDownloader {
.execute_async(&command, ())
.await;
match process_result {
- Ok(process) => {
+ Ok(mut process) => {
if !process.is_successful() {
if self.cleanup_executed.contains_key(&package.get_name()) {
return Err(RuntimeException {
@@ -148,7 +148,7 @@ impl ZipDownloader {
.into());
}
- let output = process.get_error_output();
+ let output = process.get_error_output()?;
let output =
str_replace(&format!(", {}.zip or {}.ZIP", file, file), "", &output);
diff --git a/crates/shirabe/src/package/version/version_guesser.rs b/crates/shirabe/src/package/version/version_guesser.rs
index 42646cb..7f0625f 100644
--- a/crates/shirabe/src/package/version/version_guesser.rs
+++ b/crates/shirabe/src/package/version/version_guesser.rs
@@ -533,14 +533,14 @@ impl VersionGuesser {
},
&scm_cmdline,
);
- let process = tokio::runtime::Runtime::new()
+ let mut process = tokio::runtime::Runtime::new()
.unwrap()
.block_on(self.process.borrow_mut().execute_async(&cmd_line, path))?;
if !process.is_successful() {
continue;
}
- let output = process.get_output();
+ let output = process.get_output()?;
// overwrite existing if we have a shorter diff, or we have an equal diff and an index that comes later in the array (i.e. older version)
// as newer versions typically have more commits, if the feature branch is based on a newer branch it should have a longer diff to the old version
// but if it doesn't and they have equal diffs, then it probably is based on the old version
diff --git a/crates/shirabe/src/repository/vcs/perforce_driver.rs b/crates/shirabe/src/repository/vcs/perforce_driver.rs
index 3077cff..50d5eb9 100644
--- a/crates/shirabe/src/repository/vcs/perforce_driver.rs
+++ b/crates/shirabe/src/repository/vcs/perforce_driver.rs
@@ -60,7 +60,7 @@ impl PerforceDriver {
self.perforce.as_mut().unwrap().p4_login()?;
self.perforce.as_mut().unwrap().check_stream();
self.perforce.as_mut().unwrap().write_p4_client_spec()?;
- self.perforce.as_mut().unwrap().connect_client();
+ self.perforce.as_mut().unwrap().connect_client()?;
Ok(())
}
diff --git a/crates/shirabe/src/util/filesystem.rs b/crates/shirabe/src/util/filesystem.rs
index 2d73422..7a4885a 100644
--- a/crates/shirabe/src/util/filesystem.rs
+++ b/crates/shirabe/src/util/filesystem.rs
@@ -157,7 +157,7 @@ impl Filesystem {
vec!["rm".to_string(), "-rf".to_string(), directory.to_string()]
};
- let process = self
+ let mut process = self
.get_process()
.execute_async(
PhpMixed::List(cmd.iter().map(|s| PhpMixed::String(s.clone())).collect()),
diff --git a/crates/shirabe/src/util/perforce.rs b/crates/shirabe/src/util/perforce.rs
index 0d56afb..d5facd2 100644
--- a/crates/shirabe/src/util/perforce.rs
+++ b/crates/shirabe/src/util/perforce.rs
@@ -382,7 +382,7 @@ impl Perforce {
Ok(true)
}
- pub fn connect_client(&mut self) {
+ pub fn connect_client(&mut self) -> Result<()> {
let p4_create_client_command =
self.generate_p4_command(vec!["client".to_string(), "-i".to_string()], true);
@@ -390,10 +390,13 @@ impl Perforce {
p4_create_client_command,
None,
None,
- file_get_contents(&self.get_p4_client_spec()),
+ file_get_contents(&self.get_p4_client_spec())
+ .map(PhpMixed::String)
+ .unwrap_or(PhpMixed::Null),
None,
- );
- process.run(None);
+ )?;
+ process.run(None, indexmap::IndexMap::new())?;
+ Ok(())
}
pub fn sync_code_base(&mut self, source_reference: Option<&str>) -> Result<()> {
@@ -531,12 +534,20 @@ impl Perforce {
}
}
- pub fn windows_login(&mut self, password: Option<&str>) -> i64 {
+ pub fn windows_login(&mut self, password: Option<&str>) -> Result<i64> {
let command = self.generate_p4_command(vec!["login".to_string(), "-a".to_string()], true);
- let mut process = Process::new(command, None, None, password.map(|s| s.to_string()), None);
+ let mut process = Process::new(
+ command,
+ None,
+ None,
+ password
+ .map(|s| PhpMixed::String(s.to_string()))
+ .unwrap_or(PhpMixed::Null),
+ None,
+ )?;
- process.run(None)
+ process.run(None, indexmap::IndexMap::new())
}
pub fn p4_login(&mut self) -> Result<()> {
@@ -544,13 +555,19 @@ impl Perforce {
if !self.is_logged_in()? {
let password = self.query_p4_password();
if self.windows_flag {
- self.windows_login(password.as_deref());
+ self.windows_login(password.as_deref())?;
} else {
let command =
self.generate_p4_command(vec!["login".to_string(), "-a".to_string()], false);
- let mut process = Process::new(command, None, None, password, None);
- process.run(None);
+ let mut process = Process::new(
+ command,
+ None,
+ None,
+ password.map(PhpMixed::String).unwrap_or(PhpMixed::Null),
+ None,
+ )?;
+ process.run(None, indexmap::IndexMap::new())?;
if !process.is_successful() {
return Err(Exception {
diff --git a/crates/shirabe/src/util/process_executor.rs b/crates/shirabe/src/util/process_executor.rs
index 2bdf065..61fc0a1 100644
--- a/crates/shirabe/src/util/process_executor.rs
+++ b/crates/shirabe/src/util/process_executor.rs
@@ -221,9 +221,9 @@ impl ProcessExecutor {
&command_str,
cwd,
env.clone(),
- None,
+ PhpMixed::Null,
Some(Self::get_timeout() as f64),
- );
+ )?;
} else if let PhpMixed::List(ref list) = command {
let mut cmd_vec: Vec<String> = list
.iter()
@@ -238,9 +238,9 @@ impl ProcessExecutor {
cmd_vec,
cwd.map(String::from),
env,
- None,
+ PhpMixed::Null,
Some(Self::get_timeout() as f64),
- );
+ )?;
} else {
return Err(LogicException {
message: "Invalid command type".to_string(),
@@ -291,20 +291,23 @@ impl ProcessExecutor {
}),
);
- let result: Result<()> = {
- let _ = process.run(/* callback */ Some(Box::new(|_t: &str, _b: &str| {})));
+ let result: Result<()> = (|| -> Result<()> {
+ process.run(
+ /* callback */ Some(Box::new(|_t: &str, _b: &str| false)),
+ IndexMap::new(),
+ )?;
let output_is_callable_inner = output.as_deref().map(is_callable).unwrap_or(false);
if self.capture_output
&& !output_is_callable_inner
&& let Some(out) = output.as_mut()
{
- **out = PhpMixed::String(process.get_output());
+ **out = PhpMixed::String(process.get_output()?);
}
- self.error_output = process.get_error_output();
+ self.error_output = process.get_error_output()?;
Ok(())
- };
+ })();
let final_result: Result<()> = match result {
Ok(()) => Ok(()),
Err(e) => {
@@ -467,23 +470,23 @@ impl ProcessExecutor {
self.output_command_run(&command, cwd.as_deref(), true);
let process_result: Result<Process> = (if is_string(&command) {
- Ok(Process::from_shell_commandline(
+ Process::from_shell_commandline(
command.as_string().unwrap_or(""),
cwd.as_deref(),
None,
- None,
+ PhpMixed::Null,
Some(Self::get_timeout() as f64),
- ))
+ )
} else if let PhpMixed::List(ref list) = command {
- Ok(Process::new(
+ Process::new(
list.iter()
.map(|v| v.as_string().unwrap_or("").to_string())
.collect(),
cwd.clone(),
None,
- None,
+ PhpMixed::Null,
Some(Self::get_timeout() as f64),
- ))
+ )
} else {
Err(LogicException {
message: "Invalid command type".to_string(),
@@ -509,10 +512,20 @@ impl ProcessExecutor {
}
// PHP: $process->start($callback); — we operate on the stored job.process directly
- if let Some(job) = self.jobs.get_mut(&id)
+ let start_result = if let Some(job) = self.jobs.get_mut(&id)
&& let Some(p) = job.process.as_mut()
{
- p.start(None);
+ p.start(None, IndexMap::new())
+ } else {
+ Ok(())
+ };
+ if let Err(e) = start_result {
+ // PHP: $job['reject']($e) — record the rejection and settle the job as failed.
+ if let Some(job) = self.jobs.get_mut(&id) {
+ job.status = Self::STATUS_FAILED;
+ job.exception = Some(e);
+ }
+ self.mark_job_done();
}
}
@@ -571,8 +584,8 @@ impl ProcessExecutor {
if status == Self::STATUS_STARTED && has_process {
let is_running = self
.jobs
- .get(id)
- .and_then(|j| j.process.as_ref())
+ .get_mut(id)
+ .and_then(|j| j.process.as_mut())
.map(|p| p.is_running())
.unwrap_or(false);
if !is_running {
@@ -580,8 +593,8 @@ impl ProcessExecutor {
// marks the job completed/failed based on the process exit status.
let successful = self
.jobs
- .get(id)
- .and_then(|j| j.process.as_ref())
+ .get_mut(id)
+ .and_then(|j| j.process.as_mut())
.map(|p| p.is_successful())
.unwrap_or(false);
if let Some(job) = self.jobs.get_mut(id) {
@@ -594,7 +607,7 @@ impl ProcessExecutor {
self.mark_job_done();
}
- if let Some(p) = self.jobs.get(id).and_then(|j| j.process.as_ref()) {
+ if let Some(p) = self.jobs.get_mut(id).and_then(|j| j.process.as_mut()) {
p.check_timeout()?;
}
}