diff options
| -rw-r--r-- | crates/shirabe-php-rpc/php/stubs/Composer/Package/AliasPackage.php | 35 | ||||
| -rw-r--r-- | crates/shirabe-php-rpc/php/stubs/Composer/Package/CompleteAliasPackage.php | 140 | ||||
| -rw-r--r-- | crates/shirabe-php-rpc/php/stubs/Composer/Package/RootAliasPackage.php | 120 | ||||
| -rw-r--r-- | crates/shirabe-php-rpc/src/lib.rs | 12 | ||||
| -rw-r--r-- | crates/shirabe/src/package/handle.rs | 20 | ||||
| -rw-r--r-- | crates/shirabe/src/plugin/php_plugin_proxy.rs | 158 | ||||
| -rw-r--r-- | crates/shirabe/tests/plugin/alias_package_test.rs | 89 | ||||
| -rw-r--r-- | crates/shirabe/tests/plugin/fixtures/alias-v1/Alias/Plugin.php | 63 | ||||
| -rw-r--r-- | crates/shirabe/tests/plugin/fixtures/alias-v1/composer.json | 12 | ||||
| -rw-r--r-- | crates/shirabe/tests/plugin/main.rs | 1 | ||||
| -rw-r--r-- | scripts/plugin-stub-generator/targets.list | 3 |
11 files changed, 613 insertions, 40 deletions
diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/Package/AliasPackage.php b/crates/shirabe-php-rpc/php/stubs/Composer/Package/AliasPackage.php new file mode 100644 index 00000000..f7e2e404 --- /dev/null +++ b/crates/shirabe-php-rpc/php/stubs/Composer/Package/AliasPackage.php @@ -0,0 +1,35 @@ +<?php + +// Generated by scripts/plugin-stub-generator; do not edit by hand. +// Proxy stub for Composer\Package\AliasPackage: the public surface forwards to the Rust-side entity over RPC. + +namespace Composer\Package; + +class AliasPackage extends BasePackage +{ + public function __construct(BasePackage $aliasOf, string $version, string $prettyVersion) + { + [$this->__rhandle, $this->__epoch] = \ShirabeRpcRuntime::callRust(0, '__shirabeConstruct', [static::class, [$aliasOf, $version, $prettyVersion]]); + \ShirabeRustObjectRegistry::adopt($this->__rhandle, $this); + } + + public function getAliasOf() + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getAliasOf', []); + } + + public function setRootPackageAlias(bool $value): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setRootPackageAlias', [$value]); + } + + public function isRootPackageAlias(): bool + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'isRootPackageAlias', []); + } + + public function hasSelfVersionRequires(): bool + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'hasSelfVersionRequires', []); + } +} diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/Package/CompleteAliasPackage.php b/crates/shirabe-php-rpc/php/stubs/Composer/Package/CompleteAliasPackage.php new file mode 100644 index 00000000..19555915 --- /dev/null +++ b/crates/shirabe-php-rpc/php/stubs/Composer/Package/CompleteAliasPackage.php @@ -0,0 +1,140 @@ +<?php + +// Generated by scripts/plugin-stub-generator; do not edit by hand. +// Proxy stub for Composer\Package\CompleteAliasPackage: the public surface forwards to the Rust-side entity over RPC. + +namespace Composer\Package; + +class CompleteAliasPackage extends AliasPackage implements CompletePackageInterface +{ + public function __construct(CompletePackage $aliasOf, string $version, string $prettyVersion) + { + [$this->__rhandle, $this->__epoch] = \ShirabeRpcRuntime::callRust(0, '__shirabeConstruct', [static::class, [$aliasOf, $version, $prettyVersion]]); + \ShirabeRustObjectRegistry::adopt($this->__rhandle, $this); + } + + public function getScripts(): array + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getScripts', []); + } + + public function setScripts(array $scripts): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setScripts', [$scripts]); + } + + public function getRepositories(): array + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getRepositories', []); + } + + public function setRepositories(array $repositories): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setRepositories', [$repositories]); + } + + public function getLicense(): array + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getLicense', []); + } + + public function setLicense(array $license): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setLicense', [$license]); + } + + public function getKeywords(): array + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getKeywords', []); + } + + public function setKeywords(array $keywords): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setKeywords', [$keywords]); + } + + public function getDescription(): ?string + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getDescription', []); + } + + public function setDescription(?string $description): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setDescription', [$description]); + } + + public function getHomepage(): ?string + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getHomepage', []); + } + + public function setHomepage(?string $homepage): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setHomepage', [$homepage]); + } + + public function getAuthors(): array + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getAuthors', []); + } + + public function setAuthors(array $authors): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setAuthors', [$authors]); + } + + public function getSupport(): array + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getSupport', []); + } + + public function setSupport(array $support): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setSupport', [$support]); + } + + public function getFunding(): array + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getFunding', []); + } + + public function setFunding(array $funding): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setFunding', [$funding]); + } + + public function isAbandoned(): bool + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'isAbandoned', []); + } + + public function getReplacementPackage(): ?string + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getReplacementPackage', []); + } + + public function setAbandoned($abandoned): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setAbandoned', [$abandoned]); + } + + public function getArchiveName(): ?string + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getArchiveName', []); + } + + public function setArchiveName(?string $name): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setArchiveName', [$name]); + } + + public function getArchiveExcludes(): array + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getArchiveExcludes', []); + } + + public function setArchiveExcludes(array $excludes): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setArchiveExcludes', [$excludes]); + } +} diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/Package/RootAliasPackage.php b/crates/shirabe-php-rpc/php/stubs/Composer/Package/RootAliasPackage.php new file mode 100644 index 00000000..9c4a294f --- /dev/null +++ b/crates/shirabe-php-rpc/php/stubs/Composer/Package/RootAliasPackage.php @@ -0,0 +1,120 @@ +<?php + +// Generated by scripts/plugin-stub-generator; do not edit by hand. +// Proxy stub for Composer\Package\RootAliasPackage: the public surface forwards to the Rust-side entity over RPC. + +namespace Composer\Package; + +class RootAliasPackage extends CompleteAliasPackage implements RootPackageInterface +{ + public function __construct(RootPackage $aliasOf, string $version, string $prettyVersion) + { + [$this->__rhandle, $this->__epoch] = \ShirabeRpcRuntime::callRust(0, '__shirabeConstruct', [static::class, [$aliasOf, $version, $prettyVersion]]); + \ShirabeRustObjectRegistry::adopt($this->__rhandle, $this); + } + + public function getAliases(): array + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getAliases', []); + } + + public function getMinimumStability(): string + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getMinimumStability', []); + } + + public function getStabilityFlags(): array + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getStabilityFlags', []); + } + + public function getReferences(): array + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getReferences', []); + } + + public function getPreferStable(): bool + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getPreferStable', []); + } + + public function getConfig(): array + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getConfig', []); + } + + public function setRequires(array $requires): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setRequires', [$requires]); + } + + public function setDevRequires(array $devRequires): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setDevRequires', [$devRequires]); + } + + public function setConflicts(array $conflicts): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setConflicts', [$conflicts]); + } + + public function setProvides(array $provides): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setProvides', [$provides]); + } + + public function setReplaces(array $replaces): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setReplaces', [$replaces]); + } + + public function setAutoload(array $autoload): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setAutoload', [$autoload]); + } + + public function setDevAutoload(array $devAutoload): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setDevAutoload', [$devAutoload]); + } + + public function setStabilityFlags(array $stabilityFlags): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setStabilityFlags', [$stabilityFlags]); + } + + public function setMinimumStability(string $minimumStability): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setMinimumStability', [$minimumStability]); + } + + public function setPreferStable(bool $preferStable): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setPreferStable', [$preferStable]); + } + + public function setConfig(array $config): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setConfig', [$config]); + } + + public function setReferences(array $references): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setReferences', [$references]); + } + + public function setAliases(array $aliases): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setAliases', [$aliases]); + } + + public function setSuggests(array $suggests): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setSuggests', [$suggests]); + } + + public function setExtra(array $extra): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'setExtra', [$extra]); + } +} diff --git a/crates/shirabe-php-rpc/src/lib.rs b/crates/shirabe-php-rpc/src/lib.rs index e2f5fd1e..1b501caf 100644 --- a/crates/shirabe-php-rpc/src/lib.rs +++ b/crates/shirabe-php-rpc/src/lib.rs @@ -876,6 +876,18 @@ const STUB_FILES: &[(&str, &str)] = &[ include_str!("../php/stubs/Composer/Package/RootPackage.php"), ), ( + "Composer/Package/AliasPackage.php", + include_str!("../php/stubs/Composer/Package/AliasPackage.php"), + ), + ( + "Composer/Package/CompleteAliasPackage.php", + include_str!("../php/stubs/Composer/Package/CompleteAliasPackage.php"), + ), + ( + "Composer/Package/RootAliasPackage.php", + include_str!("../php/stubs/Composer/Package/RootAliasPackage.php"), + ), + ( "Composer/Repository/ArrayRepository.php", include_str!("../php/stubs/Composer/Repository/ArrayRepository.php"), ), diff --git a/crates/shirabe/src/package/handle.rs b/crates/shirabe/src/package/handle.rs index aed8be0f..45d46f49 100644 --- a/crates/shirabe/src/package/handle.rs +++ b/crates/shirabe/src/package/handle.rs @@ -76,6 +76,26 @@ impl AnyPackage { } } + /// For `AliasPackage`'s own methods, which `PackageInterface` does not carry and the + /// alias subclasses inherit rather than redeclare. + pub fn as_alias_package(&self) -> Option<&AliasPackage> { + match self { + Self::AliasPackage(p) => Some(p), + Self::CompleteAliasPackage(p) => Some(&p.inner), + Self::RootAliasPackage(p) => Some(&p.inner.inner), + _ => None, + } + } + + pub fn as_alias_package_mut(&mut self) -> Option<&mut AliasPackage> { + match self { + Self::AliasPackage(p) => Some(p), + Self::CompleteAliasPackage(p) => Some(&mut p.inner), + Self::RootAliasPackage(p) => Some(&mut p.inner.inner), + _ => None, + } + } + pub fn as_root_package_interface(&self) -> Option<&dyn RootPackageInterface> { match self { Self::RootPackage(p) => Some(p), diff --git a/crates/shirabe/src/plugin/php_plugin_proxy.rs b/crates/shirabe/src/plugin/php_plugin_proxy.rs index 354b89aa..bd40b358 100644 --- a/crates/shirabe/src/plugin/php_plugin_proxy.rs +++ b/crates/shirabe/src/plugin/php_plugin_proxy.rs @@ -143,19 +143,14 @@ pub(crate) fn rust_handle_value(rhandle: u64, class: &str) -> PluginValue { } /// The proxy stub class matching a package's concrete variant. -fn package_stub_class( - package: &std::rc::Rc<std::cell::RefCell<AnyPackage>>, -) -> Result<&'static str, PhpThrow> { +fn package_stub_class(package: &std::rc::Rc<std::cell::RefCell<AnyPackage>>) -> &'static str { match &*package.borrow() { - AnyPackage::Package(_) => Ok("Composer\\Package\\Package"), - AnyPackage::CompletePackage(_) => Ok("Composer\\Package\\CompletePackage"), - AnyPackage::RootPackage(_) => Ok("Composer\\Package\\RootPackage"), - // TODO(plugin): alias packages need proxy stubs of their own before they can cross. - AnyPackage::AliasPackage(_) - | AnyPackage::CompleteAliasPackage(_) - | AnyPackage::RootAliasPackage(_) => Err(runtime_throw( - "alias packages are not available over RPC yet".to_string(), - )), + AnyPackage::Package(_) => "Composer\\Package\\Package", + AnyPackage::CompletePackage(_) => "Composer\\Package\\CompletePackage", + AnyPackage::RootPackage(_) => "Composer\\Package\\RootPackage", + AnyPackage::AliasPackage(_) => "Composer\\Package\\AliasPackage", + AnyPackage::CompleteAliasPackage(_) => "Composer\\Package\\CompleteAliasPackage", + AnyPackage::RootAliasPackage(_) => "Composer\\Package\\RootAliasPackage", } } @@ -177,10 +172,10 @@ fn repository_stub_class(repository: &RepositoryInterfaceHandle) -> Result<&'sta /// Registers a package and returns its wire descriptor. pub(crate) fn package_handle_value( package: &std::rc::Rc<std::cell::RefCell<AnyPackage>>, -) -> Result<PluginValue, PhpThrow> { - let class = package_stub_class(package)?; +) -> PluginValue { + let class = package_stub_class(package); let rhandle = register_entity(RustEntity::Package(package.clone())); - Ok(rust_handle_value(rhandle, class)) + rust_handle_value(rhandle, class) } /// The PHP class name (= proxy stub class) of a Rust IO instance, for the `__class` field of @@ -376,6 +371,42 @@ pub(crate) fn construct_entity(args: &[PluginValue]) -> Result<PluginValue, PhpT "Composer\\Package\\CompletePackage" => AnyPackage::CompletePackage( crate::package::CompletePackage::new(string_arg(0)?, string_arg(1)?, string_arg(2)?), ), + // The alias target has to be a package that already lives on the Rust side; an alias of + // an alias has no Rust representation, so its narrowing is an explicit error too. + "Composer\\Package\\AliasPackage" => { + let alias_of = package_from_arg(&class, ctor_args.first())? + .as_package() + .ok_or_else(|| runtime_throw(format!("{class} expects a real Package to alias")))?; + AnyPackage::AliasPackage(crate::package::AliasPackage::new( + alias_of, + string_arg(1)?, + string_arg(2)?, + )) + } + "Composer\\Package\\CompleteAliasPackage" => { + let alias_of = package_from_arg(&class, ctor_args.first())? + .as_complete_package() + .ok_or_else(|| { + runtime_throw(format!("{class} expects a real CompletePackage to alias")) + })?; + AnyPackage::CompleteAliasPackage(crate::package::CompleteAliasPackage::new( + alias_of, + string_arg(1)?, + string_arg(2)?, + )) + } + "Composer\\Package\\RootAliasPackage" => { + let alias_of = package_from_arg(&class, ctor_args.first())? + .as_root_package() + .ok_or_else(|| { + runtime_throw(format!("{class} expects a real RootPackage to alias")) + })?; + AnyPackage::RootAliasPackage(crate::package::RootAliasPackage::new( + alias_of, + string_arg(1)?, + string_arg(2)?, + )) + } // TODO(plugin): the remaining proxied classes get a construction story on demand, // driven by explicit errors from real plugins. Each one has to decide what a // plugin-built instance means for the Rust-side graph, which is why none of them is @@ -450,7 +481,7 @@ fn dispatch_composer_method( } "getPackage" => { let package = composer.borrow().get_package().as_rc().clone(); - package_handle_value(&package) + Ok(package_handle_value(&package)) } "getEventDispatcher" => { let dispatcher = composer.borrow().get_event_dispatcher(); @@ -791,7 +822,7 @@ fn dispatch_repository_method( })?; let mut items = Vec::with_capacity(packages.len()); for package in packages { - items.push(package_handle_value(package.as_rc())?); + items.push(package_handle_value(package.as_rc())); } Ok(PluginValue::List(items)) } @@ -1340,6 +1371,31 @@ fn dispatch_package_method( .set_transport_options(options); return Ok(PluginValue::Null); } + // `RootAliasPackage` overrides each of these to write through to its alias target, and + // `RootPackage` reaches the same base state either way, so both go through the interface. + "setRequires" | "setDevRequires" | "setConflicts" | "setProvides" | "setReplaces" + | "setAutoload" | "setDevAutoload" | "setSuggests" | "setExtra" + if package.borrow().is_root() => + { + let mut borrowed = package.borrow_mut(); + let package = borrowed + .as_root_package_interface_mut() + .expect("a root package exposes RootPackageInterface"); + match method_name { + "setRequires" => package.set_requires(link_map_arg(method_name, args.first())?), + "setDevRequires" => { + package.set_dev_requires(link_map_arg(method_name, args.first())?) + } + "setConflicts" => package.set_conflicts(link_map_arg(method_name, args.first())?), + "setProvides" => package.set_provides(link_map_arg(method_name, args.first())?), + "setReplaces" => package.set_replaces(link_map_arg(method_name, args.first())?), + "setAutoload" => package.set_autoload(map_arg(method_name, args.first())?), + "setDevAutoload" => package.set_dev_autoload(map_arg(method_name, args.first())?), + "setSuggests" => package.set_suggests(string_map_arg(method_name, args.first())?), + _ => package.set_extra(map_arg(method_name, args.first())?), + } + return Ok(PluginValue::Null); + } // `Package`'s own setters. The concrete subclasses inherit them (their PHP overrides in // `RootPackage` delegate to the same base state), so the base package answers for every // real variant. @@ -1527,6 +1583,35 @@ fn dispatch_package_method( let this = PackageInterfaceHandle::from_rc_unchecked(package.clone()); return Ok(PluginValue::Bool(this.equals(&other))); } + // The subclasses narrow `getAliasOf`'s return type to their own alias target, but every + // variant holds the one entity. + "getAliasOf" | "isRootPackageAlias" | "hasSelfVersionRequires" => { + let borrowed = package.borrow(); + let alias = borrowed.as_alias_package().ok_or_else(|| { + runtime_throw(format!( + "`{method_name}` is not available on this package over RPC" + )) + })?; + return Ok(match method_name { + "getAliasOf" => package_handle_value(alias.get_alias_of().as_rc()), + "isRootPackageAlias" => PluginValue::Bool(alias.is_root_package_alias()), + _ => PluginValue::Bool(alias.has_self_version_requires()), + }); + } + "setRootPackageAlias" => { + let value = bool_arg(method_name, args.first())?; + package + .borrow_mut() + .as_alias_package_mut() + .ok_or_else(|| { + runtime_throw( + "`setRootPackageAlias` is not available on this package over RPC" + .to_string(), + ) + })? + .set_root_package_alias(value); + return Ok(PluginValue::Null); + } _ => {} } @@ -2138,16 +2223,14 @@ impl PhpInstallerProxy { )) } - fn package_arg(package: &PackageInterfaceHandle) -> anyhow::Result<PluginValue> { - Ok(package_handle_value(package.as_rc())?) + fn package_arg(package: &PackageInterfaceHandle) -> PluginValue { + package_handle_value(package.as_rc()) } - fn optional_package_arg( - package: &Option<PackageInterfaceHandle>, - ) -> anyhow::Result<PluginValue> { + fn optional_package_arg(package: &Option<PackageInterfaceHandle>) -> PluginValue { match package { Some(package) => Self::package_arg(package), - None => Ok(PluginValue::Null), + None => PluginValue::Null, } } @@ -2209,7 +2292,7 @@ impl crate::installer::InstallerInterface for PhpInstallerProxy { repo: &InstalledRepositoryInterfaceHandle, package: PackageInterfaceHandle, ) -> anyhow::Result<bool> { - let args = vec![Self::repo_arg(repo)?, Self::package_arg(&package)?]; + let args = vec![Self::repo_arg(repo)?, Self::package_arg(&package)]; match self.call("isInstalled", args)? { PluginValue::Bool(installed) => Ok(installed), other => Err(self.unsupported_shape("isInstalled", &other)), @@ -2222,8 +2305,8 @@ impl crate::installer::InstallerInterface for PhpInstallerProxy { prev_package: Option<PackageInterfaceHandle>, ) -> anyhow::Result<Option<PhpMixed>> { let args = vec![ - Self::package_arg(&package)?, - Self::optional_package_arg(&prev_package)?, + Self::package_arg(&package), + Self::optional_package_arg(&prev_package), ]; let value = self.call("download", args)?; self.promise_result("download", value) @@ -2237,8 +2320,8 @@ impl crate::installer::InstallerInterface for PhpInstallerProxy { ) -> anyhow::Result<Option<PhpMixed>> { let args = vec![ PluginValue::string(r#type), - Self::package_arg(&package)?, - Self::optional_package_arg(&prev_package)?, + Self::package_arg(&package), + Self::optional_package_arg(&prev_package), ]; let value = self.call("prepare", args)?; self.promise_result("prepare", value) @@ -2249,7 +2332,7 @@ impl crate::installer::InstallerInterface for PhpInstallerProxy { repo: &InstalledRepositoryInterfaceHandle, package: PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>> { - let args = vec![Self::repo_arg(repo)?, Self::package_arg(&package)?]; + let args = vec![Self::repo_arg(repo)?, Self::package_arg(&package)]; let value = self.call("install", args)?; self.promise_result("install", value) } @@ -2262,8 +2345,8 @@ impl crate::installer::InstallerInterface for PhpInstallerProxy { ) -> anyhow::Result<Option<PhpMixed>> { let args = vec![ Self::repo_arg(repo)?, - Self::package_arg(&initial)?, - Self::package_arg(&target)?, + Self::package_arg(&initial), + Self::package_arg(&target), ]; let value = self.call("update", args)?; self.promise_result("update", value) @@ -2274,7 +2357,7 @@ impl crate::installer::InstallerInterface for PhpInstallerProxy { repo: &InstalledRepositoryInterfaceHandle, package: PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>> { - let args = vec![Self::repo_arg(repo)?, Self::package_arg(&package)?]; + let args = vec![Self::repo_arg(repo)?, Self::package_arg(&package)]; let value = self.call("uninstall", args)?; self.promise_result("uninstall", value) } @@ -2287,8 +2370,8 @@ impl crate::installer::InstallerInterface for PhpInstallerProxy { ) -> anyhow::Result<Option<PhpMixed>> { let args = vec![ PluginValue::string(r#type), - Self::package_arg(&package)?, - Self::optional_package_arg(&prev_package)?, + Self::package_arg(&package), + Self::optional_package_arg(&prev_package), ]; let value = self.call("cleanup", args)?; self.promise_result("cleanup", value) @@ -2298,12 +2381,7 @@ impl crate::installer::InstallerInterface for PhpInstallerProxy { // PHP declares `getInstallPath(): string`; a failure here is a plugin error the // infallible signature cannot carry, so it aborts rather than answering a path that // would silently install the package in the wrong place. - let args = vec![Self::package_arg(&package).unwrap_or_else(|error| { - panic!( - "{}::getInstallPath argument failed: {error:#}", - self.handle.class - ) - })]; + let args = vec![Self::package_arg(&package)]; let value = self.call("getInstallPath", args).unwrap_or_else(|error| { panic!( "{}::getInstallPath failed over RPC: {error:#}", diff --git a/crates/shirabe/tests/plugin/alias_package_test.rs b/crates/shirabe/tests/plugin/alias_package_test.rs new file mode 100644 index 00000000..b32d2b67 --- /dev/null +++ b/crates/shirabe/tests/plugin/alias_package_test.rs @@ -0,0 +1,89 @@ +//! Shirabe-specific integration tests for the alias package proxy stubs. Upstream Composer has +//! no test for this (its plugins run in-process), so the fixture `fixtures/alias-v1` is +//! Shirabe-owned. + +use crate::async_runtime::run; +use crate::plugin_installer_test::{lock_php_worker, new_installer, php_runtime_available, set_up}; +use shirabe::installer::InstallerInterface; +use shirabe::package::loader::{ArrayLoader, JsonLoader, JsonLoaderInput}; +use shirabe::package::{ + CompleteAliasPackageHandle, CompletePackageHandle, PackageInterfaceHandle, RootPackageHandle, +}; +use shirabe_php_shim::PhpMixed; + +fn alias_fixture_package() -> PackageInterfaceHandle { + let loader = JsonLoader::new(Box::new(ArrayLoader::new(None, false))); + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/plugin/fixtures/alias-v1/composer.json"); + loader + .load(JsonLoaderInput::String( + path.canonicalize().unwrap().to_str().unwrap().to_string(), + )) + .unwrap() +} + +#[test] +fn test_alias_packages_cross_the_plugin_boundary() { + if !php_runtime_available() { + return; + } + let _worker = lock_php_worker(); + let set_up = set_up(); + + let aliased = CompletePackageHandle::new( + "vendor/aliased".to_string(), + "1.0.0.0".to_string(), + "1.0.0".to_string(), + ); + aliased.set_description("an aliased package".to_string()); + let alias = CompleteAliasPackageHandle::new( + aliased.clone(), + "2.0.0.0".to_string(), + "2.0.0".to_string(), + ); + set_up + .repository + .borrow_mut() + .add_package(alias.clone().into()) + .unwrap(); + + let real_root = RootPackageHandle::new( + "dummy/root".to_string(), + "1.0.0.0".to_string(), + "1.0.0".to_string(), + ); + let root_alias = shirabe::package::RootAliasPackageHandle::new( + real_root.clone(), + "1.1.0.0".to_string(), + "1.1.0".to_string(), + ); + set_up.composer.borrow_mut().set_package(root_alias.into()); + + let installer = new_installer(&set_up); + set_up.pm.borrow_mut().load_installed_plugins().unwrap(); + run(installer.install(&set_up.repository, alias_fixture_package())).unwrap(); + + assert_eq!( + "alias: Composer\\Package\\CompleteAliasPackage vendor/aliased 2.0.0 of Composer\\Package\\CompletePackage 1.0.0\n\ + alias description: an aliased package\n\ + alias self-version: no\n\ + alias root-flag: no\n\ + alias root-flag: yes\n\ + root: Composer\\Package\\RootAliasPackage dummy/root 1.1.0 of Composer\\Package\\RootPackage 1.0.0\n\ + root aliasOf identity: same\n\ + root minimum stability: stable\n\ + built: Composer\\Package\\CompleteAliasPackage vendor/aliased 9.9.9 of Composer\\Package\\CompletePackage 1.0.0\n", + set_up.io.borrow().get_output() + ); + + // The flag the plugin set landed on the Rust-side entity, not on a child-side copy. + let alias: shirabe::package::AliasPackageHandle = alias.into(); + assert!(alias.is_root_package_alias()); + + // `RootAliasPackage`'s setters write through to the package it aliases. + assert_eq!("dev", real_root.get_minimum_stability()); + assert_eq!( + Some(&PhpMixed::String("alias-v1".to_string())), + real_root.get_extra().get("seen-by") + ); +} diff --git a/crates/shirabe/tests/plugin/fixtures/alias-v1/Alias/Plugin.php b/crates/shirabe/tests/plugin/fixtures/alias-v1/Alias/Plugin.php new file mode 100644 index 00000000..373348e8 --- /dev/null +++ b/crates/shirabe/tests/plugin/fixtures/alias-v1/Alias/Plugin.php @@ -0,0 +1,63 @@ +<?php + +namespace Alias; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Composer\Package\AliasPackage; +use Composer\Package\CompleteAliasPackage; +use Composer\Package\RootAliasPackage; +use Composer\Plugin\PluginInterface; + +class Plugin implements PluginInterface +{ + public function activate(Composer $composer, IOInterface $io) + { + $aliased = null; + foreach ($composer->getRepositoryManager()->getLocalRepository()->getPackages() as $package) { + if (!$package instanceof AliasPackage) { + continue; + } + $aliased = $package->getAliasOf(); + $io->write('alias: ' . self::describe($package)); + $io->write('alias description: ' . $package->getDescription()); + $io->write('alias self-version: ' . ($package->hasSelfVersionRequires() ? 'yes' : 'no')); + $io->write('alias root-flag: ' . ($package->isRootPackageAlias() ? 'yes' : 'no')); + $package->setRootPackageAlias(true); + $io->write('alias root-flag: ' . ($package->isRootPackageAlias() ? 'yes' : 'no')); + } + + $root = $composer->getPackage(); + if (!$root instanceof RootAliasPackage) { + throw new \RuntimeException('not a RootAliasPackage: ' . get_class($root)); + } + $io->write('root: ' . self::describe($root)); + $io->write('root aliasOf identity: ' . ($root->getAliasOf() === $root->getAliasOf() ? 'same' : 'distinct')); + $io->write('root minimum stability: ' . $root->getMinimumStability()); + $root->setMinimumStability('dev'); + $root->setExtra(['seen-by' => 'alias-v1']); + + $built = new CompleteAliasPackage($aliased, '9.9.9.9', '9.9.9'); + $io->write('built: ' . self::describe($built)); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + } + + public function uninstall(Composer $composer, IOInterface $io) + { + } + + private static function describe(AliasPackage $package): string + { + return sprintf( + '%s %s %s of %s %s', + get_class($package), + $package->getName(), + $package->getPrettyVersion(), + get_class($package->getAliasOf()), + $package->getAliasOf()->getPrettyVersion() + ); + } +} diff --git a/crates/shirabe/tests/plugin/fixtures/alias-v1/composer.json b/crates/shirabe/tests/plugin/fixtures/alias-v1/composer.json new file mode 100644 index 00000000..ac831b6e --- /dev/null +++ b/crates/shirabe/tests/plugin/fixtures/alias-v1/composer.json @@ -0,0 +1,12 @@ +{ + "name": "alias-v1", + "version": "1.0.0", + "type": "composer-plugin", + "autoload": { "psr-0": { "Alias": "" } }, + "extra": { + "class": "Alias\\Plugin" + }, + "require": { + "composer-plugin-api": "^2.0" + } +} diff --git a/crates/shirabe/tests/plugin/main.rs b/crates/shirabe/tests/plugin/main.rs index f5a29ba6..7e32a616 100644 --- a/crates/shirabe/tests/plugin/main.rs +++ b/crates/shirabe/tests/plugin/main.rs @@ -3,6 +3,7 @@ mod async_runtime; #[path = "../common/config_stub.rs"] mod config_stub; +mod alias_package_test; mod e2e_command_provider_test; mod e2e_extension_installer_test; mod e2e_installer_test; diff --git a/scripts/plugin-stub-generator/targets.list b/scripts/plugin-stub-generator/targets.list index 8aece425..347680d2 100644 --- a/scripts/plugin-stub-generator/targets.list +++ b/scripts/plugin-stub-generator/targets.list @@ -16,6 +16,9 @@ Composer\Package\BasePackage Composer\Package\Package Composer\Package\CompletePackage Composer\Package\RootPackage +Composer\Package\AliasPackage +Composer\Package\CompleteAliasPackage +Composer\Package\RootAliasPackage Composer\Repository\ArrayRepository Composer\Repository\WritableArrayRepository Composer\Repository\InstalledArrayRepository |
