| Age | Commit message (Collapse) | Author |
|
The class was shadowed by a guard, so a plugin doing `new Filesystem()`
got an explicit error. It is classified rust-proxy and plugin-constructible
and the Rust port is complete, so listing it as a stub target and answering
its public surface from the entity is all it takes.
The constructor rejects a caller-supplied ProcessExecutor: that class has no
proxy stub, so the argument could only be a second instance the Rust side
never sees. findShortestPath re-checks its arguments at the boundary because
the port panics where PHP throws, and a panic would take the process down
instead of reaching the plugin's catch block.
phpstan/extension-installer matches upstream Composer byte for byte again.
|
|
The worker is a long-lived child holding the environment it was handed
at spawn, so `@putenv`, the bin dir the event dispatcher prepends to
PATH, and COMPOSER_DEV_MODE never reached the PHP code running in it.
The shim now journals every write to the three storages PHP exposes, and
the outermost rpc_call replays the entries the worker has not seen yet
through __shirabe_sync_env. Replaying the writes rather than pushing a
whole snapshot keeps the worker's own $_SERVER entries intact.
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>
|
|
The assoc flag was always a literal at every call site, so the boolean
carried no information the function name could not. json_decode_assoc
and json_decode_obj make the resulting PhpMixed shape visible at the
call site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
parse_url now returns Option<UrlComponents> instead of a PhpMixed array,
and the component-selecting overload with the PHP_URL_* constants is gone:
callers read the field they want. Two call sites change behaviour as a
result, both towards PHP:
* CurlDownloader::handle_redirect tested scheme and host with is_null(), so
an unparsable Location header (PhpMixed::Bool(false)) counted as an
absolute URL. PHP's truthiness test sends it to the relative-path branch.
* Url::get_origin appended a literal port 0, which PHP treats as falsy and
leaves off.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
doWrite, doOverwrite and sanitize accept PHP's string|list<string>, which
this port modelled as PhpMixed. Every call site inside ConsoleIO passes a
single string, so take &str and return String instead, dropping the
(array) casts and the to_string_list helper.
Auditor is the only caller that passed a list: it builds table rows, whose
cells must stay separate, so it now sanitizes each cell. select() likewise
sanitizes each choice while projecting them into the keyed form.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The listeners of five tests are methods of
`Composer\Test\EventDispatcher\EventDispatcherTest`, which the worker
could not load because the class extends `PHPUnit\Framework\TestCase` and
phpunit is not part of the Composer runtime bundle. An empty stand-in for
that base class is enough: a parent class only has to exist at declaration
time, and method bodies and type declarations resolve lazily. The upstream
file is then required into the worker unchanged, so the listener bodies and
their `__DIR__` stay what Composer ships. The two assertions those bodies
call are the only PHPUnit members the stand-in has to implement.
`remove_listener` now compares a handle for an object the worker really
holds instead of a hand-written one, and `create_composer_instance` wires
the collaborators the autoloader-rebuild path reaches for, as the PHP
helper does.
Two tests stay ignored for a different reason: `Platform::put_env` writes
only the Shirabe process environment, while the listeners read `getenv()`
inside the long-lived worker.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Porting mapped every PHP `protected` member onto `pub(crate)`, which is
wider than nearly all of them need. Each item demoted here is reached
only from the module that defines it, so the crate-wide visibility
conveyed nothing.
Every `pub(crate)` that survives has at least one reader in another
module of the same crate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Split date() into date_utc() and date_local(), the latter resolving the
system's local timezone through the tzfile crate ($TZ, then
/etc/localtime, falling back to UTC when neither is readable). The
timestamps Composer renders for humans -- the GitHub OAuth token note,
the GitHub API rate limit reset time, the Perforce client spec fields and
the "today" check of the show command -- now go through date_local().
PHP resolves its default timezone from the date.timezone ini setting,
which Shirabe does not read, so date_default_timezone_get/set have no
input left to model and are dropped from the shim and its callers. The
resulting difference is recorded in docs/known-incompatibilities.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The binary called itself Composer everywhere: the application name, the
logo, --version, about, and every warning that talks about the running
program. Prompts to file a bug also pointed at Composer's issue tracker.
Add SHIRABE_VERSION and SHIRABE_RELEASE_DATE next to the Composer version
constants and report those, naming the Composer version this port tracks
alongside them. Composer::VERSION and getVersion() are untouched, so the
composer platform package, composer-runtime-api and the HTTP User-Agent
keep the value plugins and package repositories expect.
build.rs stamps the release date with the UTC date of the HEAD commit,
the way Composer's Compiler fills in @release_date@ when building the
phar. It now also fails the build when git cannot be read, instead of
letting COMPOSER_DEV_WARNING_TIME fall back to the tagged-release value
and suppress the outdated-build warning forever.
Messages about the Composer ecosystem keep their wording. Two of them are
pinned by upstream installer fixtures (Rule's "cannot be modified by
Composer" and SolverProblemsException's "you can run Composer with") and
stay as they are so those fixtures can keep being used verbatim.
The e2e list comparison against upstream Composer now skips the banner,
which cannot match by design, and compares everything below it as before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The shim reported a fixed PHP 8.1.0 through PHP_VERSION, PHP_VERSION_ID,
the major/minor/release triple and the PHP_WINDOWS_VERSION_* trio. Their
uses split in two.
Some guarded branches PHP only needs on runtimes this port cannot be:
proc_get_status reports the exit status on every call, so Symfony's
pre-8.3 exit-code cache has nothing to work around; hash_raw and
hash_file always offer xxh3, so the sha1 fallback is unreachable; and
http_get_last_response_headers is always available, so the pre-8.4
$http_response_header branch is gone. safeJunctions reads the host
Windows version rather than PHP state, and joins the Windows work on
hold.
The rest ask about the PHP the user actually runs, and now reach the
worker through a new php-rpc PhpVersion payload: the startup banner and
the 7.2.5 warning, self-update's min-php filter, the ext-*
recommendation in VersionSelector, the stream User-Agent, and whether
PhpFileParser scans for enums.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
A worker whose PHP cannot read the bundle out of the executable gets it
from an unpacked copy, which went to a directory derived from
XDG_CACHE_HOME alone. That ignored COMPOSER_CACHE_DIR, COMPOSER_HOME and
the cache-dir setting, and put the files outside the directory clear-cache
and the platform conventions cover. The callers now pass Composer's
configured cache directory down to base_path().
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
checkComposerAudit reported success instead of auditing anything, because
the binary ships no vendor/composer/installed.json on disk. It reads the one
in the embedded Composer PHP runtime now, and Composer's warning for a
missing installed.json is back.
Only that file leaves the bundle, into a temporary directory that goes away
with the handle; the runtime is unpacked whole only for a worker that cannot
read the bundle in place. Phar::extractTo's $files argument selects it,
which the shim ignored so far.
SHIRABE_COMPOSER_PHP_DIR moves into composer_runtime, so the worker and a
reader on the Rust side resolve the runtime through the same branch.
DiagnoseCommandTest::testCmdSuccess is ignored: packagist has advisories
against composer/composer 2.9.7, the version Composer::VERSION reports, so
diagnose exits 1 where the test expects 0. Upstream Composer 2.9.7 reports
the same advisories.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Plugins and scripts need the real `Composer\` classes and the packages
Composer depends on, which so far came from a checkout found through
SHIRABE_COMPOSER_PHP_DIR or a path next to the workspace. Neither exists
for a distributed binary.
The build script now archives those PHP sources into a phar the way
Compiler.php does and the executable carries it. The worker maps it with
Phar::loadPhar and reads a content-addressed sentinel back to tell a
bundle it can use from one it cannot; where its PHP cannot open the phar,
the bundle is unpacked once into the cache directory and autoloaded from
there. SHIRABE_COMPOSER_PHP_DIR still overrides both for development.
PHP locates a phar's manifest by the first __HALT_COMPILER(); token in
the file, so the executable must hold no other copy of it: phar.rs builds
the token at run time, and a linter keeps further literals out of the
sources that reach the binary.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
InstalledVersions has no Rust port, so the skeletons left behind asserted
nothing about compatibility. The tests now require the Composer checkout's
vendor/autoload.php into the PHP worker and drive the real class there, which
puts $selfDir, the registered ClassLoader and Composer\Semver\VersionParser in
the same state as the upstream PHPUnit run. The upstream setUp reflection and
the installed_relative.php require stay in PHP; the expected values are Rust.
The class Composer's own vendor directory autoloads and the one
FilesystemRepository::write dumps from include_str! are separate files, so an
added test asserts they hold the same bytes.
FilesystemRepositoryTest::testSafelyLoadInstalledVersions moves to the worker
too, against the php/stubs FilesystemRepository, whose safelyLoadInstalledVersions
runs the PCRE recursive grammar natively.
The shared worker helpers live in tests/common/php_worker.rs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Measuring every ignored test with `cargo test --workspace
--no-fail-fast -- --ignored` turned up 11 that pass today; their
reasons named todo!()s and gaps that have since been implemented.
The two run_script_command_test cases still fail, but not for the
reason given: the user's Command class is imported and executed, and
what is missing is only a way to observe it, since PhpCommandProxy::run
forwards the run to the worker-side console application.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Output buffering captures whatever the PHP interpreter would echo to
stdout. The shim routes no output through a buffer, so these functions
could never capture anything and stayed todo!(). Record the gap in
docs/known-incompatibilities.md instead.
|
|
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>
|
|
`Application::do_run` registers a `composer.json` script whose value names a
`Symfony\Component\Console\Command\Command` subclass as a live command. The
class checks and `new $dummy($script)` need a real PHP runtime, so they run in
the worker: the command object lives there and this side keeps a metadata
mirror for `list`/`help`, forwarding a run to the worker-side console
application it is added to. The name and description fixups are applied to the
worker-side object, so both sides carry the same values.
Loading the Composer PHP runtime into the worker is gated on the Rust-side
`ClassLoader`s resolving the class to a file, keeping that load out of every
run whose scripts are plain shell commands.
The worker-side console application handoff now accepts commands registered
after it was published, since the scripts scan runs after plugin commands are
collected. Whether it was published is tracked per application: the handoff is
process-wide, so a second application must replace it rather than extend it.
`shirabe_php_shim::is_subclass_of` has no callers left.
|
|
Every parent module in the symfony-* crates already re-exported its leaf
modules with `pub use`, so each item was reachable by two paths. Make the
leaf modules private and route all callers through the single re-exported
path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Composer restarts itself with Xdebug unloaded because Xdebug makes PHP
several times slower. Xdebug is never loaded into this process, so what
needs dealing with is the PHP worker: it is spawned with
`-d xdebug.mode=off` and `XDEBUG_MODE=off` (Xdebug reads the environment
variable first and lets it override every ini setting), which makes its
module init return before it installs any executor, compile, error or
opcode hook.
Rewriting the ini files and re-executing, the way xdebug-handler does,
would additionally cover Xdebug 2, which hooks unconditionally and has no
equivalent setting. That is not worth its machinery here: Xdebug 2 caps
out at PHP 7.4, while every PHP version Composer supports can run
Xdebug 3.
What remains of XdebugHandler is small enough to live beside the worker
it governs, so its crate is gone and its callers inline it. isXdebugActive
answers false without asking PHP whenever the worker is switched off, so a
command that needs no PHP does not spawn one just for the Xdebug warning;
diagnose reports what the worker measures instead, which still surfaces an
Xdebug that ignores the setting. PlatformRepository has no unloaded
extension to restore, since switching the mode off leaves it loaded.
COMPOSER_ORIGINAL_INIS is neither written nor read: it exists so a
restarted process can name the ini files it replaced, and IniHelper can
report the worker's own. IniHelperTest injects through that variable, so
none of its cases are ported.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
PluginManager::getPluginCapability hands the plugin itself to the
capability constructor. Only a PHP-implemented plugin had an entity the
child could receive, so a Rust-implemented one bailed out; it now crosses
as a handle to an R-table entity behind Shirabe\RustPluginStub, or its
Capable flavour, since `$plugin instanceof Capable` is what decides
whether Composer asks a plugin for capabilities at all.
This is what the two capability tests of PluginInstallerTest were waiting
on: the mocked Capable plugin is Rust-side, and one of them asserts the
identity of the plugin read back out of $capability->args.
|
|
doRemove() renames a directory to a random hidden name before emptying
it, and undoes that rename when the final rmdir fails, so a concurrent
process cannot recreate the path mid-removal. It also walks one level at
a time through FilesystemIterator instead of flattening the whole tree,
and lets an inner rmdir failure pass, both as upstream does.
copy() keeps the mode fopen($targetFile, 'w') would have left rather
than the origin's, symlink() and mirror() call readlink() and
getLinkTarget() where upstream does, and a directory iterator that
cannot be opened propagates its UnexpectedValueException instead of
being swallowed or flattened into an IOException.
Error message text is out of scope per docs/known-incompatibilities.md,
so the three TODO(phase-c) markers that only tracked wording are gone,
along with linkException()'s Windows-only branch. So are the arguments
no caller varies -- symlink()'s copyOnWindows, mirror()'s iterator and
options, copy()'s overwriteNewerFiles -- which removes the last
TODO(phase-c) in the file.
New shim functions: readlink, filesystem_iterator, stream_is_local,
strrev and SplFileInfo::getLinkTarget. base64_encode takes bytes so
random_bytes() can feed it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
shirabe-seld-json-lint crate
Move `Seld\JsonLint` out of shirabe-external-packages and into its own
crate, so the path is `shirabe_seld_json_lint::ParsingException` instead
of `shirabe_external_packages::seld::json_lint::ParsingException`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
shirabe-symfony-console crate
Move `Symfony\Component\Console` out of shirabe-external-packages and
into its own crate, so the path is
`shirabe_symfony_console::application::Application` instead of
`shirabe_external_packages::symfony::console::application::Application`.
The `delegate_to_inner!` and `delegate_command_trait_impls_to_inner!`
macros move with it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
shirabe-symfony-process crate
Move `Symfony\Component\Process` out of shirabe-external-packages and
into its own crate, so the path is `shirabe_symfony_process::Process`
instead of `shirabe_external_packages::symfony::process::Process`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Move `Composer\Pcre` out of shirabe-external-packages and into its own
crate, so the path is `shirabe_pcre::preg::Preg` instead of
`shirabe_external_packages::composer::pcre::preg::Preg`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
mkdir, rmdir, unlink and symlink each had a bool version and a _result
twin returning the io::Error, which left two names for one call. Keep
only the Result form and let the callers that want a boolean spell out
.is_ok(). Call sites that discard the outcome, as their PHP originals
do, are unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
InstalledVersions is a runtime API for plugins and project code; Composer
itself never reads it. Its consumers run in the PHP worker against the copy
FilesystemRepository dumps to vendor/composer/InstalledVersions.php, whose
static state is already kept in sync by __shirabe_installed_versions_reload.
Nothing in Rust read the mirrored statics, so reload() and the reflection
setters were no-ops.
The tests covered only the Rust port, not the PHP class the worker loads, so
they assert nothing about compatibility; they are left as todo!() skeletons.
This also removes the shim functions method_exists, php_dir and
require_php_file, whose only caller was the deleted module.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
shirabe never runs on HHVM, so the HHVM_VERSION_ID branches are dead.
Dropping them leaves the shim's unimplemented `constant()` without a
caller, so remove it as well.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
The shim's PATH_SEPARATOR was hardcoded to ":", so every PATH list was
split and joined on the wrong character off Unix. std::env::split_paths
and join_paths use the platform's separator, which also lets the bin-dir
membership test in EventDispatcher compare list entries instead of
regex-matching the raw PATH string.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Concatenating a directory, MAIN_SEPARATOR and a component reimplements
what Path::join already does. Joining directly also drops the separator
from the intermediate values it used to be baked into, such as
ClassLoader's $pathEnd.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
MAIN_SEPARATOR is the path separator; PHP's `'\\' === DIRECTORY_SEPARATOR`
uses it as an OS test only because PHP has no dedicated one. cfg!(windows)
says what the branch actually selects on, and leaves MAIN_SEPARATOR to the
sites that really join or split paths.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The shim's DIRECTORY_SEPARATOR was hardcoded to "/", so every ported
`'\\' === DIRECTORY_SEPARATOR` check compared against a constant that
does not track the target platform. std::path::MAIN_SEPARATOR and
MAIN_SEPARATOR_STR carry the same meaning as PHP's constant and resolve
per platform, so the Windows branches are selected on Windows targets.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The deadline is a constant baked in at build time, so PHP's define() of
COMPOSER_DEV_WARNING_TIME cannot make Application take that branch.
Application now holds the deadline in a field that __set_dev_warning_time
overrides, letting testDevWarning run instead of staying ignored. The
define() shim, whose only caller was that test, goes away with it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Ported exceptions were flat structs reached with `downcast_ref`, so
Composer's `catch (\RuntimeException $e)` only matched the exact leaf
type and `get_class($e)` had nothing to report. Each exception now
embeds an instance of the class it extends and travels inside an
`AnyThrowable`; `Catch::catch`/`catch_mut` walk that chain, and
`PhpClass::php_class_name` yields the PHP FQCN.
Dropping the `std::error::Error` impls from the exception types leaves
`AnyThrowable` as the only route into an `anyhow::Error`, so the walk
cannot be bypassed. A `no_exception_downcast` linter catches the
`downcast::<X>()` calls that would now silently answer `None`.
Three sites change behavior as a result: the `TransportException`
exit-code override reaches `MaxFileSizeExceededException`, the
`catch (\LogicException)` in findSimilar() reaches its subclasses, and
rendered exception titles carry the real class name rather than a
guess. `get_class_err()` is no longer a `todo!()`, which re-enables
FilesystemRepositoryTest::testCorruptedRepositoryFile.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Composer defines COMPOSER_DEV_WARNING_TIME from its phar stub when the
compiled version is a commit hash rather than a tag, so the value exists
only in the build artifact. It was a todo!() in the PHP shim, leaving the
warning branch in Application::do_run unreachable.
A build script now derives it the way Compiler does, from git describe
and the HEAD commit date, and composer::COMPOSER_DEV_WARNING_TIME holds
the result as a Rust constant instead of a runtime-defined one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
InstallationManager left both PRE_PACKAGE_* and POST_PACKAGE_* as empty
stubs, so a subscriber never ran at all and the difference from upstream was
silent rather than an explicit error.
Operations cross the boundary as R-table entities with generated proxy
stubs. Materializing them the way a Link crosses is not possible: a
materialized value is revived by unserialize() on the child side, so its
properties never pass through the wire decoder and a nested handle
descriptor would not come back as a stub — and an operation always holds a
PackageInterface. execute() now shares one Rc per operation through the
whole batch pipeline, so a plugin sees one object for both the pre- and the
post-event of an operation, as it does in PHP.
POST_PACKAGE_* also moves out of the operation's promise chain into the
post-exec callback list PHP runs after waitOnPromises().
The stub generator materializes non-public class constants verbatim now,
which the operation classes need for their `protected const TYPE`: a
constant has no entity behind it, so a copy in the worker cannot diverge,
and keeping the declared visibility exposes nothing the real class hides.
The E2E fixture added here compares the recorded events against upstream
Composer. It also surfaced that upstream starts an operation's chain where
it is built (a null prepare() becomes an already-fulfilled React promise
whose handlers run through the immediately drained queue) while this port
only drives its futures in wait_on_promises, so the repository state a
pre-event observes differs; that half of the comparison is a separate
`#[ignore]`d test.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
AutoloadGenerator::dump dispatches PRE_AUTOLOAD_DUMP / POST_AUTOLOAD_DUMP, and
EventDispatcher::make_autoloader answers those by walking back into the Composer
graph -- the local repository, the installation manager, and the generator
itself. Installer::run held mutable borrows of all three across the dump call,
so any plugin subscribing to either event panicked with "RefCell already
borrowed" before its listener ran.
dump() and build_package_map() now take the local repository, the installation
manager and the locker as shared handles instead of &mut dyn, and the borrow is
taken where it is used.
The generator itself is re-entered, not just borrowed twice: PHP listeners reach
it through Composer::getAutoloadGenerator() and call setDevMode() while dump()
is running, and make_autoloader asks the same object for a package map. That is
not expressible behind &mut self, so the mutable state moves into Cell/RefCell
fields and the whole AutoloadGeneratorInterface takes &self.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
AliasPackage, CompleteAliasPackage and RootAliasPackage now have generated
proxy stubs, so a package handed to a plugin no longer has to be a real
package: it crosses as the stub matching its concrete variant, answers
getAliasOf / setRootPackageAlias / isRootPackageAlias /
hasSelfVersionRequires, and can be constructed from plugin code.
The setters RootPackageInterface declares are routed through that interface
for every root package instead of through the base Package state. Only the
alias variant needs it -- RootAliasPackage overrides all nine to write
through to the package it aliases -- but a real RootPackage delegates to the
same base state either way, so both take one path.
An alias of an alias has no representation here, so narrowing the
constructor argument to a real package is an explicit error rather than a
silent demotion.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The RuntimeInterface seam asked the worker one question at a time: a round
trip per loaded extension, per ReflectionExtension::info() output and per
constant, so a single `show --platform` cost 70 to 100 of them. A `platform`
dispatch entry now answers all of it as one PHP array, which shirabe-php-rpc
decodes into a OnceLock-cached PlatformInfo, the way the diagnose command
already works.
Composer\Platform\Runtime therefore has no Rust counterpart any more. Its
work belongs to the running interpreter, and invoke()/construct() could only
be ported as a whitelist that panicked on anything unlisted; it is ported as
PHP into the worker instead, and PlatformRepository reads the answers off
PlatformInfo. Accessors panic on a name the payload does not carry, so the
worker and its consumers cannot drift apart unnoticed.
The tests describe the runtime as payload data where they used to mock the
seam, with the datasets unchanged. The one loss is the call-count assertion
of test_inet_pton_regression: the payload reports the result of
`@inet_pton('::')` rather than answering a call.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
|
|
Replace the todo!() bodies with real ports. Four autoload-generator
tests now run for real; the rest stay #[ignore]d, but each ignore reason
now names the concrete missing symbol instead of a vague subsystem.
Production additions the ports need: the deprecated
AuthHelper::addAuthenticationHeader wrapper,
EventDispatcher::__set_dispatch_script_override as the seam for PHPUnit
onlyMethods(['dispatchScript']), and a define() stub in the shim.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
PlatformRepository probes ResourceBundle, IntlChar and Imagick to derive
lib-icu-cldr, lib-icu-unicode and lib-imagick-imagemagick. Those probes ran
against the shim's hard-coded class_exists allowlist, which never names
them, so the packages were silently missing: on a machine with intl,
`show --platform` listed fewer libraries than upstream Composer does.
The runtime seam now asks the real PHP: hasClass over RPC, and construct /
invoke through the worker for the three classes PlatformRepository reaches.
A live PHP object has no PhpMixed counterpart, so the seam answers with the
entries the caller reads off it. The seam's own callers read those entries
instead of returning null and the empty string.
Two addLibrary calls also had replaces and provides swapped, dropping
`lib-libxslt replaces lib-xsl` and `lib-zip-libzip replaces lib-zip`.
`show --platform` now matches upstream Composer byte for byte, and all 59
provideLibraryTestCases datasets pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
getDownloaderForPackage reports get_class($downloader) when the resolved
downloader's installation source does not match. Rust has no runtime class
name, so the message was built from a shim stub that panicked instead —
the error could never be returned.
DownloaderInterface now requires PhpClass, the trait already used for the
same purpose on Command, and each downloader states the name PHP reports.
That leaves get_class_obj without callers, so it is gone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The reason string blamed shirabe_php_shim::runtime::constant(), which
PlatformRepository stopped reaching once its constant lookups went through
the RuntimeInterface seam. All four pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The link getters and setters and the release date accessors were explicit
errors for every non-empty value, because an immutable value has no entity
to point a handle at. They now cross as materialized values: the descriptor
names the real class and the constructor arguments, and each side builds a
genuine instance of its own.
The semver constraint a link holds is encoded structurally rather than
re-parsed from its string form, so the pretty strings and the conjunctive
flag survive the crossing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The ported docblocks copied @param and @return straight from the PHP
source. When such a tag carries nothing but a type and an argument name,
the Rust signature already states it, so the line is noise. Tags whose
text adds prose beyond the type are kept.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The shim's filesystem entry points took `&str` even though each one resolves
to a local path through `std::fs` or a syscall, so callers holding a `PathBuf`
had to stringify it at the call site. They now take `impl AsRef<Path>`, the
form `file_exists`, `is_dir`, `unlink` and the rest of the already-converted
set use.
`Phar`, `PharData` and `ZipArchive` keep their archive path as a `PathBuf`.
`PharData::compress` names the compressed sibling by appending the suffix to
the file name rather than formatting the path into a `String`.
Arguments PHP resolves through a stream wrapper (`fopen`, `file_put_contents`,
`include`) still take `&str`, as do the byte-string operations (`dirname`,
`basename`, `pathinfo`) and archive-internal entry names, which are `/`-joined
logical names rather than OS paths.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|