| Age | Commit message (Collapse) | Author |
|
A response is built for one request and the graph never retains it, so there
is no entity for a handle to point at. The child holds a real instance
instead, revived from the object record the wire carries, and collect() frees
the copy each world holds — which is what that method is for. The
value-object rule rejects the class only because collect() assigns to $this,
so the category comes from an overrides.list entry.
HttpDownloader::get() and copy() answer with one.
Two gaps stay: decodeJson() reaches Composer\Json\JsonFile, which a guard
shadows, and Composer answers a curl request with the CurlResponse subclass
where this port flattens the value into a Response.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
A downloader carries its own options, TLS defaults and request backends, and
what it shares with the graph is the IO it collects authentication into and
the config it reads. Plugin code writing `new HttpDownloader($io, $config)`
therefore allocates a Rust-side entity of its own rather than a second
downloader the graph knows nothing about, and the guard that shadowed the
class in the worker is gone.
The request surface is not served yet: get() and copy() have no wire
representation for the Response they return, and the async surface resolves
its promises with one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Composer reaches this class two ways: the object graph hands one out through
Composer::getLoop()->getProcessExecutor(), and plugins write
`new ProcessExecutor($io)` freely. Both bind to a Rust-side entity, so the
timeout the run shares -- seeded from process-timeout and rewritten while the
run is in flight -- has one value instead of one per world, and the executor
can still be passed to the classes that take one (`new Filesystem($process)`).
Three things the stub generator was missing came with it:
- By-ref parameters. The call carries their positions and the answer carries
what each holds afterwards; a position the answer omits was never assigned
to, which is what PHP does with an untouched by-ref parameter.
ProcessExecutor::execute is the only one on a proxied class.
- Argument arity, reproduced where the real body reads func_num_args().
execute($cmd) forwards the child's output and execute($cmd, $out) captures
it, and nothing but the argument count separates the two.
- Static methods that cannot run in the worker. One that reads a static
property the Rust side owns, or that reaches a guarded class, forwards
through __shirabeCallStatic instead of being materialized. That also fixes
Filesystem::isLocalPath and getPlatformPath, whose materialized bodies
called the guarded Composer\Util\Platform.
The async surface stays an explicit error. executeAsync resolves its promise
with a Symfony Process, whose proc_open() resource and pipes belong to
whichever process called start(), so a Rust-side spawn has none to hand back;
running the real start() in the worker needs a promise representation that
crosses the boundary unresolved.
The fixture project drives the whole synchronous surface from plugin code and
compares the trace against upstream Composer byte for byte.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Comments that pointed at design notes kept outside the repository are dead
ends for anyone reading only the tree, so what each of them explained now
lives in a tagged TODO at the site it applies to. Several of those sites
also stated something the implementation does not do, and the TODOs record
the actual gap instead: the two halves of the codec recognize handle
descriptors by different rules, the scripts Command path drops the exception
class and collects output in a BufferedOutput that cannot carry an
interactive command, find_shortest_path panics where PHP throws, and the
package dispatch hand-rolls the variant selection AnyPackage should own.
The classifier document likewise described rust-snapshot,
plugin-constructible and several of the open questions as designed rather
than as built.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The worker's autoloader fell through to the real Composer source for every
Rust-owned FQCN without a proxy stub, so plugin code doing `new Filesystem()`
or subclassing `LibraryInstaller` silently ran on a second instance the Rust
side never sees. An unimplemented part of the plugin API has to fail with an
explicit error naming it, not quietly work on a disconnected copy.
The stub generator now emits a guard class for each of those FQCNs: the real
declaration, hierarchy and constants, with every constructor and method
raising an explicit error. References satisfied by the declaration alone
(`instanceof`, `X::class`, `Link::TYPE_REQUIRE`) keep working. Two FQCNs stay
resolvable to the real class, each listed with the worker-side mechanism that
makes a natively constructed instance correct.
The error had nowhere to go: `Installer::run` dropped the `Result` of both
`dispatch_script` calls, so an exception from a listener ended in exit 0.
Both propagate now, the way the exception does upstream.
Three real-plugin E2E comparisons stop at a guard and are ignored, each
naming the class it needs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Composer's Composer\Util\TlsHelper is marked deprecated for removal in
Composer 3.0 and has no caller in composer/composer outside its own
test: PHP's stream layer verifies certificate hostnames itself, and the
one surviving method delegates to composer/ca-bundle.
The Rust port had no caller either, so it, its test, and the
openssl_x509_parse shim it was the sole user of are removed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
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>
|
|
The Package family settles on pure rust-proxy: it is genuinely mutable
(60+ setters, and Composer itself mutates packages in flight), so the
planned snapshot-with-writeback treatment is dropped rather than filed
as an override. The other open questions remain open with their interim
behavior pinned instead of resolved: proxy-side getLoop() access and the
ConsoleIO table/progress-bar members are explicit errors, and the
InstalledVersions state a plugin observes after a Rust-side dump is
undefined, marked TODO(plugin) at the reload site.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
A plugin can reach ProcessExecutor::executeAsync() through the
rust-proxy stub, which resolves with a real Symfony Process instance
that can't be reconstructed on the Rust side (its state is tied to
whichever process calls proc_open(), and it refuses serialization).
Add execute_async_php() as a todo!() stub, documented as a dual-
instantiation split in plugin-class-classification.md: Rust-internal
callers keep using execute_async(), while the plugin path must forward
spawning to the PHP child once the RPC channel exists.
|
|
Answers how a given class is treated at the plugin boundary, accepting a
PHP source file, a Rust source file, or a (short or fully qualified)
class name. Reads report.json and generates it first when missing. Rust
paths resolve by normalized segment matching because the snake_case
mapping is not reversible for acronyms (io_interface.rs -> IOInterface).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Decides, for every composer/composer class, how it is treated at the
plugin boundary (rust-proxy / rust-snapshot / contract / two-world /
php-native / unsupported) so that upstream updates re-classify new or
rewritten classes without re-deriving the design by hand. Rules and
category definitions live in docs/dev/plugin-class-classification.md;
the tool (PHP + nikic/PHP-Parser) implements them as a reachability
closure with direction marks, per-method pure/mutator analysis, and a
leaf-first fixed point for unreachable classes, with three small
versioned exception lists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|