diff options
| -rw-r--r-- | Cargo.lock | 5 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | LICENSE.md | 2 | ||||
| -rw-r--r-- | crates/shirabe-php-rpc/LICENSE (renamed from crates/shirabe-xdebug-handler/LICENSE) | 0 | ||||
| -rw-r--r-- | crates/shirabe-php-rpc/php/worker.php | 5 | ||||
| -rw-r--r-- | crates/shirabe-php-rpc/src/lib.rs | 62 | ||||
| -rw-r--r-- | crates/shirabe-php-rpc/src/xdebug.rs | 35 | ||||
| -rw-r--r-- | crates/shirabe-xdebug-handler/Cargo.toml | 11 | ||||
| -rw-r--r-- | crates/shirabe-xdebug-handler/src/lib.rs | 3 | ||||
| -rw-r--r-- | crates/shirabe-xdebug-handler/src/xdebug_handler.rs | 32 | ||||
| -rw-r--r-- | crates/shirabe/Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/shirabe/src/command/diagnose_command.rs | 3 | ||||
| -rw-r--r-- | crates/shirabe/src/console/application.rs | 3 | ||||
| -rw-r--r-- | crates/shirabe/src/main.rs | 10 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/platform_repository.rs | 20 | ||||
| -rw-r--r-- | crates/shirabe/src/util/ini_helper.rs | 8 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/ini_helper_test.rs | 109 | ||||
| -rw-r--r-- | docs/dev/php-rpc.md | 3 | ||||
| -rw-r--r-- | docs/dev/xdebug.md | 28 | ||||
| -rw-r--r-- | docs/known-incompatibilities.md | 7 |
20 files changed, 155 insertions, 193 deletions
@@ -2110,7 +2110,6 @@ dependencies = [ "shirabe-symfony-filesystem", "shirabe-symfony-finder", "shirabe-symfony-process", - "shirabe-xdebug-handler", "tempfile", "tokio", "tracing", @@ -2273,10 +2272,6 @@ dependencies = [ ] [[package]] -name = "shirabe-xdebug-handler" -version = "0.0.1" - -[[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -27,7 +27,6 @@ shirabe-symfony-filesystem = { path = "crates/shirabe-symfony-filesystem" } shirabe-symfony-finder = { path = "crates/shirabe-symfony-finder" } shirabe-symfony-process = { path = "crates/shirabe-symfony-process" } shirabe-symfony-string = { path = "crates/shirabe-symfony-string" } -shirabe-xdebug-handler = { path = "crates/shirabe-xdebug-handler" } anyhow = "1.0.102" async-trait = "0.1.89" base64 = "0.22.1" @@ -31,6 +31,7 @@ license of the package it is ported from: | [`shirabe-class-map-generator`](crates/shirabe-class-map-generator/LICENSE) | composer/class-map-generator | | [`shirabe-metadata-minifier`](crates/shirabe-metadata-minifier/LICENSE) | composer/metadata-minifier | | [`shirabe-pcre`](crates/shirabe-pcre/LICENSE) | composer/pcre | +| [`shirabe-php-rpc`](crates/shirabe-php-rpc/LICENSE) | composer/xdebug-handler | | [`shirabe-seld-json-lint`](crates/shirabe-seld-json-lint/LICENSE) | seld/jsonlint | | [`shirabe-seld-signal`](crates/shirabe-seld-signal/LICENSE) | seld/signal-handler | | [`shirabe-semver`](crates/shirabe-semver/LICENSE) | composer/semver | @@ -40,7 +41,6 @@ license of the package it is ported from: | [`shirabe-symfony-finder`](crates/shirabe-symfony-finder/LICENSE) | symfony/finder | | [`shirabe-symfony-process`](crates/shirabe-symfony-process/LICENSE) | symfony/process | | [`shirabe-symfony-string`](crates/shirabe-symfony-string/LICENSE) | symfony/string | -| [`shirabe-xdebug-handler`](crates/shirabe-xdebug-handler/LICENSE) | composer/xdebug-handler | ## PHP diff --git a/crates/shirabe-xdebug-handler/LICENSE b/crates/shirabe-php-rpc/LICENSE index 963618a1..963618a1 100644 --- a/crates/shirabe-xdebug-handler/LICENSE +++ b/crates/shirabe-php-rpc/LICENSE diff --git a/crates/shirabe-php-rpc/php/worker.php b/crates/shirabe-php-rpc/php/worker.php index 578c30d0..5946a52f 100644 --- a/crates/shirabe-php-rpc/php/worker.php +++ b/crates/shirabe-php-rpc/php/worker.php @@ -526,8 +526,8 @@ final class ShirabePlatformRuntime } } -// Port of Composer\XdebugHandler\XdebugHandler::setXdebugDetails(), which the diagnose payload -// reports as `xdebug_active`. +// Port of Composer\XdebugHandler\XdebugHandler::setXdebugDetails(), which the `xdebug_active` +// query and the diagnose payload both report. $xdebug_active = static function (): bool { if (!extension_loaded('xdebug')) { return false; @@ -562,6 +562,7 @@ $xdebug_active = static function (): bool { ShirabeRpcRuntime::$dispatch = [ 'constant' => static fn($args) => defined($args[0]) ? constant($args[0]) : null, + 'xdebug_active' => static fn($args) => $xdebug_active(), 'get_all_ini_files' => static function ($args) { $paths = [(string) php_ini_loaded_file()]; $scanned = php_ini_scanned_files(); diff --git a/crates/shirabe-php-rpc/src/lib.rs b/crates/shirabe-php-rpc/src/lib.rs index a4742664..1b165cfa 100644 --- a/crates/shirabe-php-rpc/src/lib.rs +++ b/crates/shirabe-php-rpc/src/lib.rs @@ -3,6 +3,7 @@ pub mod frame; pub mod session; pub mod value; +pub mod xdebug; pub use value::{PhpClassHandle, PhpObjHandle, PhpObject, PluginValue, RustObjHandle}; @@ -536,14 +537,23 @@ pub fn phpversion(extension: &str) -> Option<String> { } } -/// `Composer\XdebugHandler\XdebugHandler::getAllIniFiles()` (minus the `self::$name` branch, -/// which is unreachable since this port never constructs an XdebugHandler): `[(string) +/// What `Composer\XdebugHandler\XdebugHandler::getAllIniFiles()` measures: `[(string) /// php_ini_loaded_file()]` merged with the trimmed, comma-split `php_ini_scanned_files()` list -/// when scanning is active. +/// when scanning is active. The worker runs on the ini files of the machine, so these are the +/// user's own. pub fn get_all_ini_files() -> Vec<String> { string_list(call("get_all_ini_files", ""), "get_all_ini_files") } +/// `Composer\XdebugHandler\XdebugHandler::isXdebugActive()` as measured in the worker, which is +/// the process Xdebug is loaded into. +pub(crate) fn xdebug_active() -> bool { + match call("xdebug_active", "") { + PhpMixed::Bool(b) => b, + other => panic!("PHP RPC: `xdebug_active` did not return a bool: {other:?}"), + } +} + fn string_list(value: PhpMixed, name: &str) -> Vec<String> { match value { PhpMixed::List(items) => items @@ -1070,7 +1080,16 @@ fn spawn_worker() -> anyhow::Result<Worker> { // supported) serialize_precision; pin the child to it in case a distro php.ini overrides // the default. .arg("-d") - .arg("serialize_precision=-1") + .arg("serialize_precision=-1"); + if xdebug::switches_xdebug_off() { + // The environment variable takes precedence over every ini setting, so switching the + // mode off takes both. See `docs/dev/xdebug.md`. + command + .arg("-d") + .arg("xdebug.mode=off") + .env("XDEBUG_MODE", "off"); + } + command .arg(&script_path) .arg(WORKER_SOCKET_FD.to_string()) .arg(&stubs_dir); @@ -1147,6 +1166,41 @@ mod tests { } #[test] + fn worker_starts_with_xdebug_switched_off() { + if PhpExecutableFinder::new().find(false).is_none() { + // No PHP in this environment; the worker cannot start. + return; + } + + let mut worker = spawn_worker().expect("failed to spawn PHP worker"); + + // The `-d xdebug.mode=off` half is invisible from PHP while the extension is not loaded + // (an unregistered ini entry is not readable), so only the environment half — the one + // that overrides every ini setting — can be asserted here. + frame::write_frame( + &mut worker.stream, + &Frame::CallFunction { + corr_id: 1, + function_name: "getenv".to_string(), + args: vec![PluginValue::string("XDEBUG_MODE")], + out_param_positions: Vec::new(), + }, + ) + .expect("failed to ask the worker for its Xdebug mode"); + let reply = frame::read_frame(&mut worker.stream).expect("failed to read the worker reply"); + match reply { + Frame::Return { value, .. } => assert_eq!( + value.to_php_mixed().expect("unusable reply"), + PhpMixed::String("off".to_string()) + ), + other => panic!("unexpected reply: {other:?}"), + } + + worker.child.kill().expect("failed to kill PHP worker"); + worker.child.wait().expect("failed to reap PHP worker"); + } + + #[test] fn queries_string_lists_when_php_available() { if PhpExecutableFinder::new().find(false).is_none() { // No PHP in this environment; the worker cannot start. diff --git a/crates/shirabe-php-rpc/src/xdebug.rs b/crates/shirabe-php-rpc/src/xdebug.rs new file mode 100644 index 00000000..6ed048cf --- /dev/null +++ b/crates/shirabe-php-rpc/src/xdebug.rs @@ -0,0 +1,35 @@ +//! ref: composer/vendor/composer/xdebug-handler/src/XdebugHandler.php +//! +//! Keeping Xdebug out of the PHP worker, which is where Composer's restart of itself lands in +//! this port. See `docs/dev/xdebug.md`. + +use shirabe_php_shim::getenv; + +/// `XdebugHandler::$name.XdebugHandler::SUFFIX_ALLOW`, where the name is the uppercased prefix of +/// the one construction Composer makes: `new XdebugHandler('Composer')` in `bin/composer`. +const ALLOW: &str = "COMPOSER_ALLOW_XDEBUG"; + +/// PHP: `XdebugHandler::isXdebugActive()`. Whether Xdebug is loaded and running in an active mode. +/// +/// Answered without asking PHP whenever the worker is started with the mode switched off, since +/// that settles the question for every Xdebug that honours the setting — Xdebug 2, which has no +/// such setting, is reported inactive while it is not. `diagnose` reports what the worker measures +/// instead. +pub fn is_xdebug_active() -> bool { + if switches_xdebug_off() { + return false; + } + + crate::xdebug_active() +} + +/// Whether the worker is started with Xdebug switched off, which is this port's stand-in for +/// `XdebugHandler::check()` restarting the process. The answer is a property of the environment +/// alone, so it holds whether or not the worker has been spawned yet. +pub(crate) fn switches_xdebug_off() -> bool { + // PHP: `!((bool) explode('|', getenv($this->envAllowXdebug))[0])`, where the pipe-separated + // form is the handoff to a process xdebug-handler restarted. Nothing restarts here, so what + // is left is PHP's truthiness of the value. + let allow_xdebug = getenv(ALLOW).unwrap_or_default(); + matches!(allow_xdebug.to_string_lossy().as_ref(), "" | "0") +} diff --git a/crates/shirabe-xdebug-handler/Cargo.toml b/crates/shirabe-xdebug-handler/Cargo.toml deleted file mode 100644 index 3deda4ac..00000000 --- a/crates/shirabe-xdebug-handler/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "shirabe-xdebug-handler" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -description = "A Rust port of composer/xdebug-handler" -repository.workspace = true -license.workspace = true - -[lints] -workspace = true diff --git a/crates/shirabe-xdebug-handler/src/lib.rs b/crates/shirabe-xdebug-handler/src/lib.rs deleted file mode 100644 index c95f237d..00000000 --- a/crates/shirabe-xdebug-handler/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod xdebug_handler; - -pub use xdebug_handler::*; diff --git a/crates/shirabe-xdebug-handler/src/xdebug_handler.rs b/crates/shirabe-xdebug-handler/src/xdebug_handler.rs deleted file mode 100644 index 6ccedc6a..00000000 --- a/crates/shirabe-xdebug-handler/src/xdebug_handler.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! ref: composer/vendor/composer/xdebug-handler/src/XdebugHandler.php - -#[derive(Debug)] -pub struct XdebugHandler; - -impl XdebugHandler { - pub fn is_xdebug_active() -> bool { - // TODO(php-runtime) - false - } - - pub fn get_skipped_version() -> Option<String> { - // TODO(php-runtime) - // The restart-to-disable-xdebug mechanism is not ported (`is_xdebug_active` is - // hardcoded `false`), so a restart never happens and `self::$skipped` stays at - // its PHP default of `""`. - Some(String::new()) - } - - pub fn get_all_ini_files() -> Vec<String> { - // TODO(php-runtime) - // No XdebugHandler is ever constructed (`self::$name` stays null), because the - // `new XdebugHandler('Composer'); $xdebug->check();` bootstrap in `bin/composer` is not - // ported (see the TODO(phase-c) at the top of shirabe's main.rs), so the - // COMPOSER_ORIGINAL_INIS env-var branch is unreachable here. - // - // Callers that need the real PHP runtime's ini files (php_ini_loaded_file() / - // php_ini_scanned_files()) query shirabe_php_rpc directly instead of going through this - // stub; see IniHelper::get_all in the shirabe crate. - vec![String::new()] - } -} diff --git a/crates/shirabe/Cargo.toml b/crates/shirabe/Cargo.toml index ccc2ca8a..6d29c886 100644 --- a/crates/shirabe/Cargo.toml +++ b/crates/shirabe/Cargo.toml @@ -22,7 +22,6 @@ shirabe-symfony-console.workspace = true shirabe-symfony-filesystem.workspace = true shirabe-symfony-finder.workspace = true shirabe-symfony-process.workspace = true -shirabe-xdebug-handler.workspace = true anyhow.workspace = true async-trait.workspace = true base64.workspace = true diff --git a/crates/shirabe/src/command/diagnose_command.rs b/crates/shirabe/src/command/diagnose_command.rs index 2af2acfc..5094f06a 100644 --- a/crates/shirabe/src/command/diagnose_command.rs +++ b/crates/shirabe/src/command/diagnose_command.rs @@ -887,9 +887,6 @@ impl DiagnoseCommand { if filter_var_boolean(diagnostics.ini_get("xdebug.profiler_enabled").unwrap_or("")) { warnings.insert("xdebug_profile".to_string(), PhpMixed::Bool(true)); } else if diagnostics.xdebug_active { - // PHP: XdebugHandler::isXdebugActive(). As with IniHelper::get_all, the port of that - // method in shirabe_xdebug_handler is a stub, so the real runtime is queried through - // the diagnose payload instead. warnings.insert("xdebug_loaded".to_string(), PhpMixed::Bool(true)); } diff --git a/crates/shirabe/src/console/application.rs b/crates/shirabe/src/console/application.rs index cc456c3c..8fed08e9 100644 --- a/crates/shirabe/src/console/application.rs +++ b/crates/shirabe/src/console/application.rs @@ -98,7 +98,6 @@ use shirabe_symfony_console::style::style_interface::StyleInterface; use shirabe_symfony_console::style::symfony_style::SymfonyStyle; use shirabe_symfony_console::terminal::Terminal; use shirabe_symfony_process::exception::ProcessTimedOutException; -use shirabe_xdebug_handler::XdebugHandler; /// The PHP `Composer\Console\Application` and `Symfony\Component\Console\Application` are /// flattened into a single struct. Methods that are overridden by subclass and called via @@ -2275,7 +2274,7 @@ impl ApplicationHandle { io.write_error(&format!("<warning>Composer supports PHP 7.2.5 and above, you will most likely encounter problems with your PHP {}. Upgrading is strongly recommended but you can use Composer 2.2.x LTS as a fallback.</warning>", PHP_VERSION)); } - if XdebugHandler::is_xdebug_active() + if shirabe_php_rpc::xdebug::is_xdebug_active() && Platform::get_env("COMPOSER_DISABLE_XDEBUG_WARN").is_none() { io.write_error("<warning>Composer is operating slower than normal because you have Xdebug enabled. See https://getcomposer.org/xdebug</warning>"); diff --git a/crates/shirabe/src/main.rs b/crates/shirabe/src/main.rs index dcf33803..f8b64c46 100644 --- a/crates/shirabe/src/main.rs +++ b/crates/shirabe/src/main.rs @@ -29,18 +29,14 @@ fn init_tracing() { } fn main() { - // TODO(phase-c): PHP: `$xdebug = new XdebugHandler('Composer'); $xdebug->check(); unset($xdebug);` - // (restart the process without Xdebug loaded, for performance) is not ported. Since no - // XdebugHandler is ever constructed, `self::$name` never gets set, so the - // COMPOSER_ORIGINAL_INIS-env-var branch of XdebugHandler::getAllIniFiles() (see - // shirabe-xdebug-handler's xdebug_handler.rs and shirabe/src/util/ini_helper.rs) is - // unreachable in this port, not merely unexercised by current tests. - // Take the $_ENV / $_SERVER snapshots before any putenv() mutates the real environment. // See `docs/dev/env-vars-porting.md` for details. std::sync::LazyLock::force(&PHP_ENV); std::sync::LazyLock::force(&PHP_SERVER); + // Composer calls XdebugHandler::check() here to restart without Xdebug, which is intentionally + // unported. See `docs/dev/xdebug.md`. + // The single process-wide tokio Runtime. `shirabe::run` and everything under it // (Command::execute and friends) is still synchronous top to bottom; entering the runtime // here (rather than driving `run` via `.block_on`) just makes it ambiently available via diff --git a/crates/shirabe/src/repository/platform_repository.rs b/crates/shirabe/src/repository/platform_repository.rs index 7e123151..3e1f35ec 100644 --- a/crates/shirabe/src/repository/platform_repository.rs +++ b/crates/shirabe/src/repository/platform_repository.rs @@ -20,11 +20,10 @@ use shirabe_pcre::{CaptureKey, Preg}; use shirabe_php_rpc::PlatformInfo; use shirabe_php_shim::{ InvalidArgumentException, PhpMixed, UnexpectedValueException, array_map_str_fn, - array_slice_strs, explode, get_class, implode, in_array_strict, is_string, php_regex, - str_replace, str_starts_with, strpos, strtolower, var_export, + array_slice_strs, explode, get_class, implode, is_string, php_regex, str_replace, + str_starts_with, strpos, strtolower, var_export, }; use shirabe_semver::constraint::SimpleConstraint; -use shirabe_xdebug_handler::XdebugHandler; use std::sync::{LazyLock, Mutex}; static LAST_SEEN_PLATFORM_PHP: LazyLock<Mutex<Option<String>>> = LazyLock::new(|| Mutex::new(None)); @@ -303,17 +302,10 @@ impl PlatformRepository { } // Check for Xdebug in a restarted process - if !in_array_strict( - "xdebug".to_string(), - &loaded_extensions - .iter() - .map(|s| PhpMixed::String(s.clone())) - .collect::<Vec<_>>(), - ) && let Some(xdebug_pretty_version) = XdebugHandler::get_skipped_version() - && !xdebug_pretty_version.is_empty() - { - self.add_extension("xdebug", &xdebug_pretty_version)?; - } + // PHP re-adds the extension `XdebugHandler::getSkippedVersion()` names, since its restart + // runs on an ini file with Xdebug commented out. The worker is instead started with + // `xdebug.mode=off` (see docs/dev/xdebug.md), which leaves the extension loaded, so it is + // already among $loadedExtensions. // Another quick loop, just for possible libraries // Doing it this way to know that functions or constants exist before diff --git a/crates/shirabe/src/util/ini_helper.rs b/crates/shirabe/src/util/ini_helper.rs index 4ff9a5e2..08f5c816 100644 --- a/crates/shirabe/src/util/ini_helper.rs +++ b/crates/shirabe/src/util/ini_helper.rs @@ -4,10 +4,12 @@ pub struct IniHelper; impl IniHelper { /// Returns an array of php.ini locations with at least one entry. + /// + /// PHP asks `XdebugHandler::getAllIniFiles()`, which answers from `COMPOSER_ORIGINAL_INIS` + /// when it is set, because a restarted process runs on a generated ini file and has to name + /// the ones it replaced. Nothing here restarts PHP, so the worker always runs on the machine's + /// own ini files and the variable is not consulted. pub fn get_all() -> Vec<String> { - // PHP: XdebugHandler::getAllIniFiles(). shirabe_xdebug_handler::XdebugHandler's port - // of that method is a stub that returns the PHP default, so query the real PHP - // runtime's loaded/scanned ini files here instead. shirabe_php_rpc::get_all_ini_files() } diff --git a/crates/shirabe/tests/util/ini_helper_test.rs b/crates/shirabe/tests/util/ini_helper_test.rs index 90015169..f9ef3219 100644 --- a/crates/shirabe/tests/util/ini_helper_test.rs +++ b/crates/shirabe/tests/util/ini_helper_test.rs @@ -1,104 +1,7 @@ //! ref: composer/tests/Composer/Test/Util/IniHelperTest.php - -use shirabe::util::ini_helper::IniHelper; -use shirabe::util::platform::Platform; -use shirabe_php_shim::{getenv, putenv}; - -#[allow(dead_code)] -fn set_up() -> TearDown { - // Register our name with XdebugHandler. - // The PHP test constructs `new XdebugHandler('composer')` so that - // getAllIniFiles() reads the COMPOSER_ORIGINAL_INIS env var. The Rust - // XdebugHandler is a unit struct with no name-registration API, so this - // step is a no-op here. - // Save current state - let env_original = - getenv("COMPOSER_ORIGINAL_INIS").map(|value| value.to_string_lossy().into_owned()); - TearDown { env_original } -} - -#[allow(dead_code)] -fn tear_down(env_original: &Option<String>) { - // Restore original state - if let Some(env_original) = env_original { - unsafe { putenv("COMPOSER_ORIGINAL_INIS", env_original) }; - } else { - Platform::clear_env("COMPOSER_ORIGINAL_INIS"); - } -} - -#[allow(dead_code)] -struct TearDown { - env_original: Option<String>, -} - -impl Drop for TearDown { - fn drop(&mut self) { - tear_down(&self.env_original); - } -} - -fn set_env(paths: &[&str]) { - unsafe { - putenv( - "COMPOSER_ORIGINAL_INIS", - std::env::join_paths(paths).unwrap(), - ) - }; -} - -#[test] -#[ignore = "XdebugHandler is never constructed with a name (the bin/composer restart-without-Xdebug \ -bootstrap is unported, see TODO(phase-c) in shirabe's main.rs), so IniHelper::get_all() queries \ -the real PHP runtime directly instead of consulting COMPOSER_ORIGINAL_INIS"] -fn test_with_no_ini() { - let paths = [""]; - - set_env(&paths); - assert!(IniHelper::get_message().contains("does not exist")); - assert_eq!( - paths.iter().map(|s| s.to_string()).collect::<Vec<_>>(), - IniHelper::get_all() - ); -} - -#[test] -#[ignore = "XdebugHandler is never constructed with a name (the bin/composer restart-without-Xdebug \ -bootstrap is unported, see TODO(phase-c) in shirabe's main.rs), so IniHelper::get_all() queries \ -the real PHP runtime directly instead of consulting COMPOSER_ORIGINAL_INIS"] -fn test_with_loaded_ini_only() { - let paths = ["loaded.ini"]; - - set_env(&paths); - assert!(IniHelper::get_message().contains("loaded.ini")); -} - -#[test] -#[ignore = "XdebugHandler is never constructed with a name (the bin/composer restart-without-Xdebug \ -bootstrap is unported, see TODO(phase-c) in shirabe's main.rs), so IniHelper::get_all() queries \ -the real PHP runtime directly instead of consulting COMPOSER_ORIGINAL_INIS"] -fn test_with_loaded_ini_and_additional() { - let paths = ["loaded.ini", "one.ini", "two.ini"]; - - set_env(&paths); - assert!(IniHelper::get_message().contains("multiple ini files")); - assert_eq!( - paths.iter().map(|s| s.to_string()).collect::<Vec<_>>(), - IniHelper::get_all() - ); -} - -#[test] -#[ignore = "XdebugHandler is never constructed with a name (the bin/composer restart-without-Xdebug \ -bootstrap is unported, see TODO(phase-c) in shirabe's main.rs), so IniHelper::get_all() queries \ -the real PHP runtime directly instead of consulting COMPOSER_ORIGINAL_INIS"] -fn test_without_loaded_ini_and_additional() { - let paths = ["", "one.ini", "two.ini"]; - - set_env(&paths); - assert!(IniHelper::get_message().contains("multiple ini files")); - assert_eq!( - paths.iter().map(|s| s.to_string()).collect::<Vec<_>>(), - IniHelper::get_all() - ); -} +//! +//! All four cases of this test feed IniHelper made-up ini paths through COMPOSER_ORIGINAL_INIS. +//! That variable exists so a process restarted without Xdebug can name the ini files it replaced; +//! nothing here restarts PHP, so IniHelper::get_all() reports the ini files the PHP worker loaded +//! and never reads it (see docs/dev/xdebug.md). With no way to feed the helper, none of the cases +//! are ported. diff --git a/docs/dev/php-rpc.md b/docs/dev/php-rpc.md index 314386d8..8418db88 100644 --- a/docs/dev/php-rpc.md +++ b/docs/dev/php-rpc.md @@ -11,7 +11,8 @@ domain socket. There is exactly one child process per Shirabe process, shared by The existing `PhpExecutableFinder` class resolves the PHP binary. The child is started with `-d serialize_precision=-1` so the wire codec's float formatting is pinned to the default PHP -behavior. +behavior, and with `-d xdebug.mode=off` unless `COMPOSER_ALLOW_XDEBUG` asks for Xdebug to stay +(see `xdebug.md`). ## Transport diff --git a/docs/dev/xdebug.md b/docs/dev/xdebug.md new file mode 100644 index 00000000..be198cca --- /dev/null +++ b/docs/dev/xdebug.md @@ -0,0 +1,28 @@ +# Xdebug + +Composer restarts itself without Xdebug loaded, because Xdebug makes PHP +several times slower. Shirabe is written in Rust, where Xdebug does not exist, +but it invokes a PHP command for plugins, scripts and platform queries. +When Shirabe spawns a PHP worker, Xdebug is disabled as Composer does. + +The PHP worker is spawned with the `-d xdebug.mode=off` flag and the +`XDEBUG_MODE=off` environment variable. The way to disable Xdebug in Shirabe is +different from Composer: Composer restarts its own process with a temporary +INI file, where Xdebug extension is disabled. The difference probably does not +matter, both for users and for plugin authors. + +## Enable Xdebug in Shirabe's PHP worker + +It is the same as Composer: setting `COMPOSER_ALLOW_XDEBUG` to 1 makes Shirabe +leave Xdebug enabled. + +``` +$ COMPOSER_ALLOW_XDEBUG=1 shirabe install +``` + +## Xdebug 2 support + +Shirabe does not try to disable Xdebug version 2 because Xdebug 2 has no +`xdebug.mode` or `XDEBUG_MODE`, while Composer disables Xdebug 2 too. The +performance penalty seems to be small as Shirabe's CPU-heavy workloads are +written in Rust. diff --git a/docs/known-incompatibilities.md b/docs/known-incompatibilities.md index 99c0e322..daaa8bcd 100644 --- a/docs/known-incompatibilities.md +++ b/docs/known-incompatibilities.md @@ -32,6 +32,13 @@ The following are intentionally left unchanged for ecosystem compatibility: TODO: a CLI flag or an environment variable to force Shirabe to use compatible paths. +## Xdebug + +Composer disables Xdebug on startup. Shirabe emulates it, but the exact +behavior is slightly different. See [docs/dev/xdebug.md](./dev/xdebug.md) +for details. + + ## Plugins ### Reflection |
