aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-rpc
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe-php-rpc')
-rw-r--r--crates/shirabe-php-rpc/LICENSE21
-rw-r--r--crates/shirabe-php-rpc/php/worker.php5
-rw-r--r--crates/shirabe-php-rpc/src/lib.rs62
-rw-r--r--crates/shirabe-php-rpc/src/xdebug.rs35
4 files changed, 117 insertions, 6 deletions
diff --git a/crates/shirabe-php-rpc/LICENSE b/crates/shirabe-php-rpc/LICENSE
new file mode 100644
index 00000000..963618a1
--- /dev/null
+++ b/crates/shirabe-php-rpc/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Composer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
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")
+}