diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-06 01:50:34 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-06 01:50:34 +0900 |
| commit | 4de018826e9dce90fd5cb78d468641478327ec99 (patch) | |
| tree | 0843f7e3e8c50886c8470ace17fcb6fed937be4f /crates/shirabe-php-rpc/php | |
| parent | da602f1cb1d555c7826fa3d026df66b82061cda4 (diff) | |
| download | php-shirabe-4de018826e9dce90fd5cb78d468641478327ec99.tar.gz php-shirabe-4de018826e9dce90fd5cb78d468641478327ec99.tar.zst php-shirabe-4de018826e9dce90fd5cb78d468641478327ec99.zip | |
feat(plugin): run plugin-provided installers through the RPC worker
A plugin can now hand an InstallerInterface implementation to
InstallationManager::addInstaller across the wire, and a legacy
composer-installer package is loaded as one; both are backed by a
PhpInstallerProxy forwarding the whole installer contract to the entity in
the PHP worker. An installer returning a real promise is an explicit error
until promises can cross the boundary.
InstallationManager takes installers as shared handles instead of boxes, so
the object identity removeInstaller and PluginManager's registeredPlugins
compare against survives registration, and holds them in a RefCell:
Installer::run keeps a shared borrow of the manager for the whole run, and a
plugin activated inside it registers its installer from there. The type
cache keys on the installer itself, like upstream, so re-entrant
registration cannot leave a stale index behind. InstallerInterface::supports
is fallible for the same reason getCapabilities and getCommands are: it
answers over RPC.
Cloning a proxy stub clones the Rust-side entity and rebinds the copy to the
fresh handle. Previously only the classes declaring __clone got a throwing
body, and the rest let two stubs share (and twice release) one handle.
Package entities answer with AnyPackage::dup, which already carries
BasePackage::__clone and the RootAliasPackage override; the others are an
explicit error.
The package proxy covers the whole PackageInterface surface; only the link
maps and the release date still lack a wire image for their value objects.
PluginManager gains a test-only seam for the reported Plugin API version,
and the three PluginInstallerTest cases that need it are ported.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-rpc/php')
8 files changed, 80 insertions, 7 deletions
diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/EventDispatcher/EventDispatcher.php b/crates/shirabe-php-rpc/php/stubs/Composer/EventDispatcher/EventDispatcher.php index c0651081..30f8c0e8 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/EventDispatcher/EventDispatcher.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/EventDispatcher/EventDispatcher.php @@ -44,6 +44,16 @@ class EventDispatcher implements \ShirabeRustStub ]; } + public function __clone() + { + // PHP has already shallow-copied this stub, so both copies would point at one + // entity and release it twice. The Rust side clones the entity instead, applying + // whatever __clone semantics the real class defines, and this copy rebinds to the + // fresh handle. Entities without clone semantics answer with an explicit error. + [$this->__rhandle, $this->__epoch] = \ShirabeRpcRuntime::callRust($this->__rhandle, '__shirabeClone', []); + \ShirabeRustObjectRegistry::adopt($this->__rhandle, $this); + } + public function setRunScripts(bool $runScripts = true): self { \ShirabeRpcRuntime::callRust($this->__rhandle, 'setRunScripts', [$runScripts]); diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/IO/BaseIO.php b/crates/shirabe-php-rpc/php/stubs/Composer/IO/BaseIO.php index 55da6415..b3e0fb51 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/IO/BaseIO.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/IO/BaseIO.php @@ -42,6 +42,16 @@ abstract class BaseIO implements IOInterface, \ShirabeRustStub ]; } + public function __clone() + { + // PHP has already shallow-copied this stub, so both copies would point at one + // entity and release it twice. The Rust side clones the entity instead, applying + // whatever __clone semantics the real class defines, and this copy rebinds to the + // fresh handle. Entities without clone semantics answer with an explicit error. + [$this->__rhandle, $this->__epoch] = \ShirabeRpcRuntime::callRust($this->__rhandle, '__shirabeClone', []); + \ShirabeRustObjectRegistry::adopt($this->__rhandle, $this); + } + public function isInteractive() { return \ShirabeRpcRuntime::callRust($this->__rhandle, 'isInteractive', []); diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/Installer/InstallationManager.php b/crates/shirabe-php-rpc/php/stubs/Composer/Installer/InstallationManager.php index 35682561..d458d30b 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/Installer/InstallationManager.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/Installer/InstallationManager.php @@ -50,6 +50,16 @@ class InstallationManager implements \ShirabeRustStub ]; } + public function __clone() + { + // PHP has already shallow-copied this stub, so both copies would point at one + // entity and release it twice. The Rust side clones the entity instead, applying + // whatever __clone semantics the real class defines, and this copy rebinds to the + // fresh handle. Entities without clone semantics answer with an explicit error. + [$this->__rhandle, $this->__epoch] = \ShirabeRpcRuntime::callRust($this->__rhandle, '__shirabeClone', []); + \ShirabeRustObjectRegistry::adopt($this->__rhandle, $this); + } + public function reset(): void { \ShirabeRpcRuntime::callRust($this->__rhandle, 'reset', []); diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/Package/BasePackage.php b/crates/shirabe-php-rpc/php/stubs/Composer/Package/BasePackage.php index e55c3fb5..de8037ca 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/Package/BasePackage.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/Package/BasePackage.php @@ -42,6 +42,16 @@ abstract class BasePackage implements PackageInterface, \ShirabeRustStub ]; } + public function __clone() + { + // PHP has already shallow-copied this stub, so both copies would point at one + // entity and release it twice. The Rust side clones the entity instead, applying + // whatever __clone semantics the real class defines, and this copy rebinds to the + // fresh handle. Entities without clone semantics answer with an explicit error. + [$this->__rhandle, $this->__epoch] = \ShirabeRpcRuntime::callRust($this->__rhandle, '__shirabeClone', []); + \ShirabeRustObjectRegistry::adopt($this->__rhandle, $this); + } + public const STABILITY_STABLE = 0; public const STABILITY_RC = 5; public const STABILITY_BETA = 10; @@ -75,13 +85,6 @@ abstract class BasePackage implements PackageInterface, \ShirabeRustStub \ShirabeRpcRuntime::callRust($this->__rhandle, '__set', [$name, $value]); } - public function __clone() - { - // Cloning a proxy is an open design question; fail instead of silently sharing - // the Rust-side entity between two stub instances. - throw new \RuntimeException('Shirabe does not support cloning ' . static::class . ' inside the plugin process yet'); - } - public static function packageNameToRegexp(string $allowPattern, string $wrap = '{^%s$}i'): string { $cleanedAllowPattern = str_replace('\\*', '.*', preg_quote($allowPattern)); diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/PartialComposer.php b/crates/shirabe-php-rpc/php/stubs/Composer/PartialComposer.php index 588800dc..c60edbb2 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/PartialComposer.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/PartialComposer.php @@ -46,6 +46,16 @@ class PartialComposer implements \ShirabeRustStub ]; } + public function __clone() + { + // PHP has already shallow-copied this stub, so both copies would point at one + // entity and release it twice. The Rust side clones the entity instead, applying + // whatever __clone semantics the real class defines, and this copy rebinds to the + // fresh handle. Entities without clone semantics answer with an explicit error. + [$this->__rhandle, $this->__epoch] = \ShirabeRpcRuntime::callRust($this->__rhandle, '__shirabeClone', []); + \ShirabeRustObjectRegistry::adopt($this->__rhandle, $this); + } + public function setPackage(RootPackageInterface $package): void { \ShirabeRpcRuntime::callRust($this->__rhandle, 'setPackage', [$package]); diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/Repository/ArrayRepository.php b/crates/shirabe-php-rpc/php/stubs/Composer/Repository/ArrayRepository.php index 33454fa2..51e6a462 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/Repository/ArrayRepository.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/Repository/ArrayRepository.php @@ -42,6 +42,16 @@ class ArrayRepository implements RepositoryInterface, \ShirabeRustStub ]; } + public function __clone() + { + // PHP has already shallow-copied this stub, so both copies would point at one + // entity and release it twice. The Rust side clones the entity instead, applying + // whatever __clone semantics the real class defines, and this copy rebinds to the + // fresh handle. Entities without clone semantics answer with an explicit error. + [$this->__rhandle, $this->__epoch] = \ShirabeRpcRuntime::callRust($this->__rhandle, '__shirabeClone', []); + \ShirabeRustObjectRegistry::adopt($this->__rhandle, $this); + } + public function hasPackage(PackageInterface $package) { return \ShirabeRpcRuntime::callRust($this->__rhandle, 'hasPackage', [$package]); diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/Repository/RepositoryManager.php b/crates/shirabe-php-rpc/php/stubs/Composer/Repository/RepositoryManager.php index 252bab4d..5635c0dc 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/Repository/RepositoryManager.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/Repository/RepositoryManager.php @@ -42,6 +42,16 @@ class RepositoryManager implements \ShirabeRustStub ]; } + public function __clone() + { + // PHP has already shallow-copied this stub, so both copies would point at one + // entity and release it twice. The Rust side clones the entity instead, applying + // whatever __clone semantics the real class defines, and this copy rebinds to the + // fresh handle. Entities without clone semantics answer with an explicit error. + [$this->__rhandle, $this->__epoch] = \ShirabeRpcRuntime::callRust($this->__rhandle, '__shirabeClone', []); + \ShirabeRustObjectRegistry::adopt($this->__rhandle, $this); + } + public function findPackage(string $name, $constraint): ?PackageInterface { return \ShirabeRpcRuntime::callRust($this->__rhandle, 'findPackage', [$name, $constraint]); diff --git a/crates/shirabe-php-rpc/php/worker.php b/crates/shirabe-php-rpc/php/worker.php index 241dad57..12297c83 100644 --- a/crates/shirabe-php-rpc/php/worker.php +++ b/crates/shirabe-php-rpc/php/worker.php @@ -51,6 +51,16 @@ final class ShirabeRustObjectRegistry return $stub; } + /** + * Interns a stub the registry did not build: a `__clone` forwarder rebinds the copy PHP + * made to a freshly cloned entity, and that pairing has to be visible to later crossings + * of the same handle. + */ + public static function adopt(int $rhandle, object $stub): void + { + self::$internTable[$rhandle] = WeakReference::create($stub); + } + /** Invoked when an EpochBump frame arrives. No-op if the stub already died. */ public static function bumpEpoch(int $rhandle, int $epoch): void { |
