aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--crates/shirabe-php-rpc/php/stubs/Composer/Package/AliasPackage.php35
-rw-r--r--crates/shirabe-php-rpc/php/stubs/Composer/Package/CompleteAliasPackage.php140
-rw-r--r--crates/shirabe-php-rpc/php/stubs/Composer/Package/RootAliasPackage.php120
-rw-r--r--crates/shirabe-php-rpc/src/lib.rs12
-rw-r--r--crates/shirabe/src/package/handle.rs20
-rw-r--r--crates/shirabe/src/plugin/php_plugin_proxy.rs158
-rw-r--r--crates/shirabe/tests/plugin/alias_package_test.rs89
-rw-r--r--crates/shirabe/tests/plugin/fixtures/alias-v1/Alias/Plugin.php63
-rw-r--r--crates/shirabe/tests/plugin/fixtures/alias-v1/composer.json12
-rw-r--r--crates/shirabe/tests/plugin/main.rs1
-rw-r--r--scripts/plugin-stub-generator/targets.list3
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