| Age | Commit message (Collapse) | Author |
|
test_update was skipped for a stale reason; running it uncovered three
distinct bugs it was actually catching:
- ApplicationTester::run never restored SHELL_VERBOSITY after
Application::configureIO mutates it, so one dataset's -vv verbosity
leaked into later runs sharing the process (Symfony's tester restores
it in a finally block; the port dropped that).
- Installer::do_install built its RepositorySet with a hardcoded empty
temporary_constraints map instead of self.temporary_constraints, so
--with never actually constrained the resolver.
- BumpCommand was missing a <warning> tag pair around one of its output
lines.
|
|
actually run
Without this, Application::do_run force-disabled interactivity whenever
stdin wasn't a tty (as under cargo test), so ApplicationTester runs with
set_inputs silently produced non-interactive default output instead of
consuming the answers, masking real behavior as several stale #[ignore]s
blaming already-implemented ProcessExecutor/Process todo!()s.
Port composer/tests/bootstrap.php's env setup into a bootstrap() helper
called from get_application_tester(), un-ignore the now-passing
init/update command tests, and update init_command_test's expected
schema-validation wording to match the jsonschema crate (already the
accepted wording per 541a8b4f, not an unported gap).
|
|
PHP's printTable builds a 5-column row for text output, with the raw
Link object (cast via __toString) as its own column separate from the
formatted description string. The port had collapsed both into one
column, dropping an empty column for successful checks and throwing
off the rendered column widths/spacing versus real Composer output.
|
|
|
|
executeBatch now builds one future per operation — the PHP promise
chain prepare -> install/update/uninstall -> cleanup -> repo->write,
including the '<Op> of <pkg> failed' rejection handler covering the
chain up to cleanup — and drives the whole batch through
waitOnPromises()/Loop::wait, so archive extraction (the unzip
subprocesses gated by ProcessExecutor's semaphore) finally overlaps
across packages. Alias operations stay synchronous in the collection
loop like PHP.
The shared repository is threaded through the chains as
RefCell<&mut dyn InstalledRepositoryInterface>: execute() wraps the
incoming &mut once, and InstallerInterface::install/update/uninstall
take the cell so implementations borrow it only in their synchronous
head/tail, never across an await. InstallationManager's own
install/update/uninstall/download/get_installer/get_install_path/
mark_for_notification move to &self (cache and notifiable_packages
behind RefCell) so every chain can capture &self.
WritableRepositoryInterface::write and the InstallationManagerInterface
get_install_path it relies on lose their &mut manager requirement —
the per-op repo->write inside the chains only reads install paths.
Warm-cache create-project laravel/laravel: the package-operations
phase (109 installs) drops from ~3.0-3.8s serial to ~1.9s, on par
with real Composer (~2.1s) measured back-to-back; the resulting
vendor tree, installed.json included, stays byte-identical to
Composer's (diff -rq clean).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
InstallationManager::downloadAndExecuteBatch now matches PHP: every
update/install operation's installer->download() promise is collected
and driven concurrently through waitOnPromises()/Loop::wait instead of
being awaited one package at a time. Concurrency caps stay where PHP
puts them (HttpDownloader 12, ProcessExecutor 10 via their semaphores).
Error semantics follow PHP too: all downloads settle before the first
rejection is rethrown, rather than aborting on the first failure.
To let the collected futures and the cleanup closures own their
installer beyond the loop iteration that created them, the installer
registry becomes Vec<Rc<dyn InstallerInterface>> and get_installer
hands out clones (PHP closures capture $installer the same way), with
InstallerInterface methods taking &self across the six implementors —
the only genuinely mutable state was LibraryInstaller.vendor_dir
(canonicalized in place), now behind a RefCell.
as_plugin_installer_mut/as_binary_presence_interface lose their &mut.
The cleanup_promises entries are now the real thing: the PHP closure
including the getInstallationSource() guard and the
installer->cleanup($opType, $package, $initialPackage) call, replacing
the no-op futures (drops one TODO(phase-b) and two TODO(phase-c)).
Verified against the real network: create-project laravel/laravel
produces a vendor tree byte-identical to real Composer's (diff -rq
clean across all 109 packages including vendor/composer).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Concurrent package operations call into the same downloader instances
through Rc<RefCell<dyn DownloaderInterface>>; with &mut self methods
every call holds a RefMut across its awaits, which panics with
'already mutably borrowed' the moment two operations overlap. This is
groundwork for fanning out InstallationManager's download/install
loops (same rework HttpDownloader/CurlDownloader already got).
- DownloaderInterface/ChangeReportInterface/ArchiveDownloader/
VcsDownloader methods now take &self; as_change_report_interface
returns &dyn instead of &mut dyn.
- Implementors move their genuinely mutable state behind cells:
FileDownloader.additional_cleanup_paths, the archive downloaders'
cleanup_executed, ZipDownloader.zip_archive_object,
VcsDownloaderBase.has_cleaned_changes, GitDownloader's stash/discard/
cache maps and GitUtil, SvnDownloader.cache_credentials,
PerforceDownloader.perforce. FileDownloader.io gains a RefCell layer
so get_local_changes can keep PHP's NullIO swap under &self.
- ProcessExecutor::execute_async now returns a future that captures
everything up front instead of borrowing the executor, and call
sites build the future before awaiting, so no borrow on the shared
executor is held while a subprocess runs.
- Filesystem::remove_directory_async becomes remove_directory_async_via
taking the Rc handle: the Filesystem is only borrowed for the sync
head/tail, never across the rm subprocess await (sync borrow_mut
users like rename/ensure_directory_exists would otherwise collide).
- DownloadManager async call sites hold shared borrows only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
execute_async was a serial pump: it queued a job, then drove it to
completion itself via wait_id()'s blocking usleep loop before
returning, so concurrent callers never overlapped even when polled
together through FuturesUnordered.
Rewrite it as a single &self async fn mirroring the CurlDownloader/
HttpDownloader rework: a tokio Semaphore sized by max_jobs
(COMPOSER_MAX_PARALLEL_PROCESSES, PHP parity) gates admission, the
child is started non-blocking, and an async 1ms sleep loop pumps
is_running()/check_timeout() while yielding to the reactor so sibling
jobs genuinely run in parallel. The Job table, STATUS_* lifecycle,
start_job/mark_job_done/count_active_jobs/wait/wait_id all had no
remaining callers and are removed.
&self also lets callers hold only a shared borrow across their awaits
(zip_downloader, version_guesser, filesystem via the new
get_process_handle), which would otherwise panic with 'already
mutably borrowed' once two async jobs overlap on the same
Rc<RefCell<ProcessExecutor>>.
The async mock branch no longer consumes the expectation before
hitting its todo!(): the panic made that bookkeeping unobservable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
regex::Regex::clone() does not share the underlying meta engine's
search-cache pool, so every fresh clone pays a ~10us warmup cost on
its first use. Two changes together eliminate this across nearly all
preg_* call sites:
- A php_regex! macro resolves PHP-style patterns to a per-call-site
&'static regex::Regex (via regex-macro's LazyLock), applied at the
majority of call sites throughout the codebase.
- Call sites still passing dynamic pattern strings go through
PATTERN_CACHE, which now stores Arc<(Regex, bool)> and hands out
Arc::clone()s instead of cloning the Regex itself.
PregPattern::resolve() returns a ResolvedPattern enum (Arc or
'static reference) rather than an owned Regex, so neither path ever
clones the Regex proper.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
serde_json::from_reader issues one read() syscall per byte against an
unbuffered Reader. FileRetriever passed a raw File straight through,
so resolving the composer-schema.json $ref read its ~71KB contents
one byte at a time (twice per require, since schema validation runs
both before and during the update).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
security-advisories in a p2 provider response is a JSON array, which
decodes to PhpMixed::List rather than PhpMixed::Array. The check only
matched Array, so it always missed and silently fell through to the
api-url fallback instead of using the already-cached provider data.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
The `regex` crate does not support negative lookahead, so the ported
`^dev-(?!main$|master$|trunk$|latest$)` pattern panicked at runtime on
any `require` invocation that reached version-selection. Replace it
with equivalent hand-written string logic per docs/dev/regex-porting.md.
|
|
CurlDownloader::auth_helper was never cloned out to another owner, so
Rc<RefCell<AuthHelper>> only needed the RefCell for interior
mutability (all methods take &self). Likewise HttpDownloader::dispatch
cloned self.rfs into a local binding it only ever used synchronously
(copy/get_contents don't await), so the clone bought nothing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
PHP's handleRedirect() throws a bare TransportException when the
Location header is missing, and the caller's single catch block always
unlinks the `~` partial file via rejectJob(). The Rust decide() loop
splits each failure path into its own branch and had unlinked on every
other one, but missed this branch, leaking the partial file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
Replace sync_executor::block_on's reactor-less busy-spin poller with
tokio::task::block_in_place + Handle::current().block_on(), riding a
single tokio Runtime entered once in main.rs (falling back to a
disposable one when no ambient runtime exists, e.g. in tests). This
lets HttpDownloader::dispatch await CurlDownloader::download directly
instead of bouncing through the separate curl_runtime() bridge, which
is now deleted.
Manual create-project verification against the real network caught a
concurrency bug this exposed: async_fetch_file held http_downloader's
RefMut across the await on add(), which only panics once downloads
genuinely overlap. add() only needs &self, so borrow() fixes it.
With everything now sharing one real reactor, the FuturesOrdered
fan-out added for ComposerRepository::get_security_advisories/
load_async_packages finally overlaps for real: fetching 8 packages'
metadata dropped from ~7-40s to a consistent ~3-4s in a before/after
comparison, with identical resulting lock files.
sync_executor::block_on's call sites are still synchronous rather
than async fn propagated up to Command::execute, which remains the
end goal (see the TODO(phase-e) in sync_executor.rs) - nested block_on
calls elsewhere don't get this same overlap, only prevented panics.
|
|
get_security_advisories/load_async_packages serialized every
start_cached_async_download call through sync_executor::block_on
per name, matching PHP's structure but not its promise-based
concurrency. Wrap the mutable state those downloads touch (cache,
fresh_metadata_urls, packages_not_found_cache, degraded_mode) in
RefCell/Cell so start_cached_async_download and its async_fetch_file
helpers can drop to &self, then fan out all downloads for a batch via
FuturesOrdered before processing responses sequentially in original
order, mirroring PHP's build-all-promises-then-wait shape.
Real I/O overlap still awaits item 7 (HttpDownloader::add currently
resolves through the curl_runtime()/sync_executor::block_on bridge
either way), so this is architectural groundwork, not a perf win yet.
|
|
11 downloader/installer integration-test files each redefined an
identical current_thread `run()` helper to block on async code. Extract
one shared multi_thread Runtime into tests/common/async_runtime.rs so
concurrent #[test] threads can all block_on it, matching the direction
item 7 (top-level Runtime) will take in production code.
|
|
Loop::wait already had the target signature and a TODO(phase-c-promise)
marker noting it drove promises serially; swap the for-loop for
FuturesUnordered so all promises are polled together instead of one at
a time, keeping the "remember only the first error" semantics.
This adds the first real use of the futures dependency (already present
in Cargo.toml/Cargo.lock from earlier prep work, now finally consumed),
so those lockfile/manifest changes land in this commit.
Real overlap still doesn't happen yet: each promise (HttpDownloader::add/
add_copy etc.) resolves through a blocking bridge (curl_runtime()/
sync_executor::block_on) that fully occupies the thread until it
settles, so this is groundwork for once a single top-level Runtime
replaces those bridges. Updated the TODO(phase-c-promise) comment to
reflect that.
|
|
Replaces Job/Request/JobHandle/id_gen/running_jobs/max_jobs with a
tokio::sync::Semaphore permit held for the duration of each request.
get/add/copy/add_copy are now &self (add/add_copy are also genuinely
async); a shared execute()/dispatch() core replaces
add_job/run_rfs_job/start_job/settle_job, returning the Response
directly instead of deferring to wait()/count_active_jobs()/
get_response() (all removed — confirmed zero callers, same for the
now-unused STATUS_* constants).
get()/copy() stay synchronous rather than becoming async wrappers
around add()/add_copy(), bridging via the existing sync_executor
instead of the curl_runtime() introduced for CurlDownloader: their
callers (~35 files reaching HttpDownloader) are mostly plain sync fns
with no async boundary anywhere in the call chain, and forcing that
propagation now would pull forward the dedicated async-propagation
task. curl-eligible requests still route through curl_runtime()
inside dispatch(), same as before — nesting sync_executor::block_on
(no real reactor) around curl_runtime().block_on() (a real, separate
Runtime) is safe; it's only nesting curl_runtime() inside itself that
would panic.
CurlDownloader no longer needs Rc<RefCell<>> wrapping despite the
original design sketch: since item 2 made all of its methods &self,
a plain Option<CurlDownloader> field works fine under HttpDownloader's
own &self methods.
get/add/copy/add_copy becoming &self (rather than &mut self) requires
no changes at any of their ~35 calling files: RefMut/Ref both deref to
a type that can call &self methods just fine.
Verified manually against real network I/O (sandbox disabled): `shirabe
show -a` (get()'s sync_executor-bridged path) and `shirabe
create-project` (add_copy()'s genuinely async path via file_downloader.rs)
both complete correctly with no hang.
|
|
Freeform notes about intentionally-unported production behavior are
easy to miss on a read-through and impossible to grep for later.
|
|
Replaces the Job-table + tick()-driven polling loop with one async
download() that sends, decides (retry/redirect/fail/succeed via a new
decide() extracted from the former run_job), and loops until it
resolves — no more resolve/reject callbacks. The client switches from
reqwest::blocking::Client to the non-blocking reqwest::Client, with
body streaming now via tokio::fs.
Because real async I/O needs a live tokio reactor and none runs yet at
the process level (sync_executor::block_on is a no-reactor busy-spin
executor that only works when awaited futures resolve synchronously),
HttpDownloader::start_job drives CurlDownloader::download() through a
dedicated temporary current_thread Runtime (curl_runtime(), marked
TODO(phase-e)) instead. This keeps concurrency characteristics
unchanged for now — start_job still resolves one job at a time — real
parallel I/O lands once HttpDownloader/Loop are rearchitected on top of
FuturesUnordered.
count_active_jobs' curl.tick() polling and the Job.settled/curl_id
plumbing are removed as dead weight now that start_job settles curl
jobs synchronously, same as the rfs path already did.
abort_request is dropped: it had no caller (the PHP Promise-cancellation
flow it backs was never ported), and the job table it operated on no
longer exists.
Verified manually against real network I/O (sandbox disabled): `shirabe
show -a` (JSON metadata, in-memory body) and `shirabe create-project`
(actual dist zip download + extraction) both complete correctly with no
hang. Two unrelated pre-existing bugs surfaced during manual testing
(an event-dispatcher subscriber wiring gap during `require`, and a
RefCell reentrancy panic in `diagnose`) reproduce identically on the
pre-change code and are out of scope here.
|
|
In prep for the upcoming &self conversion of add()/get()/copy(), the
mock hook needs interior mutability too. The struct's Clone derive is
dropped since nothing clones the whole state anymore, only the shared
Rc handle.
|
|
Wrap RemoteFilesystem in Rc<RefCell<>> inside HttpDownloader, in prep
for the upcoming &self conversion of add()/get(). copy()/get_contents()
now bundle the response headers into their return value instead of
requiring a follow-up get_last_headers() call, since two separate calls
through a shared RefCell could otherwise race: nothing would guarantee
the reader observes the headers from its own request rather than one
clobbered by a concurrently borrowed call. get_last_headers() itself is
left in place, mirroring RemoteFilesystem::getLastHeaders() in PHP.
|
|
CurlDownloader's download() is about to become an &self async method
as part of the HttpDownloader async rearchitecture; its auth_helper
field needs interior mutability ahead of that change. RemoteFilesystem
keeps its own AuthHelper as a plain field since it stays &mut self.
|
|
reset() eagerly rebuilt the ProxyManager singleton immediately,
capturing env vars before a caller could set them for the next
request. PHP's reset() just nulls the static instance; getInstance()
lazily constructs on next use. Match that so proxy env vars set after
reset() are observed.
get_instance() also ensured the singleton was constructed under its
own lock, dropped that lock, and returned the bare Mutex; every caller
then took a second, independent lock. A reset() landing in that gap
would leave the caller observing None and panicking on
.as_ref().unwrap(), a state the old eager-reconstructing reset() could
not produce. Return the already-locked MutexGuard from get_instance()
instead, so construction and use happen under one lock, and update all
call sites accordingly.
Holding that guard across a loop body then deadlocked in
diagnose_command, since check_http_proxy transitively re-enters
get_instance() via HttpDownloader -> CurlDownloader, and
std::sync::Mutex is not reentrant. Re-acquire the lock fresh each
iteration with a short-lived guard instead.
Finally, Mutex::new is a const fn, so the OnceLock wrapper around it
was unnecessary indirection; a bare static Mutex<Option<ProxyManager>>
initializes to the same state without the get_or_init/get dance.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
locker_test.rs defined its own installation_manager, identical to
test_case.rs's (both build a bare InstallationManager over a mock
HttpDownloader). Expose the shared one via pub(crate) and drop the
duplicate.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
reasons
Implement test_incapable_plugin_is_correctly_detected and
test_querying_non_provided_capability_returns_null_safely against a
real PluginManager, using hand-written PluginInterface/Capable stubs
in place of PHPUnit's ad-hoc mocks. Wire the shared config_stub test
helper into the plugin test binary, and derive Debug on the SetUp
struct per this project's convention.
The remaining plugin_installer_test.rs cases stay #[ignore]d:
install/update/uninstall wiring in PluginInstaller and class
instantiation in PluginManager::register_package are still
TODO(plugin) stubs, so no plugin ever actually gets registered. Each
now records its blocking TODO(plugin) site via // TODO(phase-d).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
No test logic changes.
|
|
zip/git/file downloader tests that were already #[ignore]d for a
documented reason lacked the required in-body // TODO(phase-d)
comment; record it for each.
Two git_downloader_test.rs cases,
test_download_uses_various_protocols_and_sets_push_url_for_github and
test_update_doesnt_throws_runtime_exception_if_git_command_fails_at_first_but_is_able_to_recover,
had been left as todo!() with a TODO(phase-d) claiming
ComposerMirror::process_git_url's github regex lacks PCRE delimiters
and panics. That regex already has delimiters and does not panic
(verified directly), and other tests in this same file already use
Package::set_source_mirrors to give a real package a mirror-prefixed
getSourceUrls() list, so the stated blocker no longer applies. Port
both tests using that existing technique instead of recording a
TODO(phase-d) for them.
No other test logic changes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
Implement previously-todo!() tests in auth_helper_test.rs,
process_executor_test.rs, remote_filesystem_test.rs, and
stream_context_factory_test.rs by porting the corresponding PHPUnit
test methods. Extend IOStub with writeRaw/setAuthentication call
tracking and askAndValidate/getAuthentication overrides to model the
PHPUnit mocks these tests rely on, deduping the resulting
call-recording fields into a small generic CallRecorder<T> helper
instead of repeating the same RefCell<Vec<T>> push/borrow().clone()
boilerplate five times.
testStoreAuthWithPromptInvalidAnswer and
testPromptAuthIfNeededMultipleBitbucketDownloads had initially lost
the ported PHPUnit mock's argument/call-count assertions
(askAndValidate's exact prompt string, and
hasAuthentication/getAuthentication's exactly(2) call counts),
silently narrowing what the tests verify; IOStub now records these
calls and the tests assert on them, matching upstream.
Tests left unportable (PHP set_error_handler machinery, closures in
data providers, network/subclass-mock dependencies, etc.) keep
#[ignore] with a single // TODO(phase-d) reason recorded in the
function body.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
Resolve the base_run render_exception TODO by downcasting the
OutputInterface handle to the concrete ConsoleOutput type, mirroring
`$output instanceof ConsoleOutputInterface` from Symfony's
Application::run.
|
|
|
|
CommandLoaderInterface::get() returned Box<dyn Command>, which didn't
match the Rc<RefCell<dyn SymfonyCommand>> Application::add() expects,
leaving both call sites as todo!() panics.
|
|
|
|
InputDefinition stores options as Rc<InputOption> for sharing, so
CompletionSuggestions::suggest_option[s] now accepts Rc<InputOption>
instead of owned values, resolving the ownership mismatch left as a
todo!() in Application::complete and CompleteCommand.
|
|
Buffering warnings into a Vec was a stale Phase B workaround for a
borrow conflict that no longer exists now that io is a separately
cloned Rc<RefCell<dyn IOInterface>> handle; write them directly in
the loop like the original PHP does.
|
|
|
|
BaseConfigCommand::initialize skipped the parent BaseCommand::initialize
chain (plugin enable/disable resolution, PRE_COMMAND_RUN event dispatch,
COMPOSER_NO_* env option overrides), unlike PHP's parent::initialize()
call. The trait-disambiguation blocker cited in the old TODO was already
solved elsewhere via the base_command_initialize free function; wire it
in here too so ConfigCommand and RepositoryCommand match PHP behavior.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
Application::find is now available, so getScripts can look up each
script's associated command and read its description, ignoring
CommandNotFoundException/NamespaceNotFoundException the same way the
PHP code does for scripts with no associated command.
|
|
Downcasts the generic Application handle to the concrete shirabe
Application to read getInitialWorkingDirectory(), so exec once again
switches back to the directory it started in (e.g. after `composer
global exec`), matching PHP's behavior.
|
|
Application::find/reset_composer and the shared ApplicationHandle are
now available, so GlobalCommand can reset the composer instance before
building the sub-command input and proxy execution through the full
Application::run dispatch, matching PHP's behavior. Un-ignores the
tests that only depended on this wiring, and re-points the remaining
ignores at their real (unrelated) blockers.
|
|
Application::find and BaseCommand::reset_composer are now available,
so the deferred update_dependencies/run_dump_autoload_command stubs
can find, reset, and run the sibling command directly, matching
InitCommand's PHP behavior.
|
|
BaseCommand::createComposerInstance and initialize() OR in the
Application's getDisablePluginsByDefault()/getDisableScriptsByDefault()
on top of the --no-plugins/--no-scripts flags; this was deferred with
a TODO(phase-c) since the shared Application handle wasn't wired up
yet. The same get_application() + downcast pattern used by get_io()
now covers this too.
|
|
Convert errors/warnings/config to RefCell so load() can satisfy the
trait's &self signature, matching upstream's `instanceof
ValidatingArrayLoader` check in VcsRepository. This makes the
InvalidPackageException downcast path in VcsRepository reachable for
the first time instead of being permanently dead code.
|
|
fallback paths
VcsDriverInterface::get_source/get_dist were made fallible in the Rust
port even though PHP's are infallible, forcing GitBitbucketDriver's
fallback delegation to silently swallow errors via
unwrap_or_default()/ok().flatten(). Make the trait infallible to match
PHP, updating the mechanical Ok(...) wrapping in all implementors.
Also narrow attempt_clone_fallback's cleanup to only trigger on
RuntimeException, mirroring PHP's catch (\RuntimeException $e), using
the same downcast pattern already used by has_composer_file for
TransportException.
|
|
paths
Both code paths were left as phase-c placeholders (debug-formatted
reason_data, and a security-advisory fallback that ignored
getMatchingSecurityAdvisories entirely). The blockers noted in those
TODOs were already resolved elsewhere (RuleSetGenerator now wires
reason_data for alias rules, and BasePackageHandle/PackageInterfaceHandle
are the same type), so port the PHP logic faithfully.
|
|
AutoloadGenerator::dump
The two phase-c TODOs blocking these calls were already resolved
elsewhere (RepositoryInterfaceHandle::as_installed_repository_interface_mut
was added after this file was ported), so reinstall now actually
performs the uninstall/install operations and regenerates the
autoloader instead of no-oping. Mirrors the pattern already used in
installer.rs and dump_autoload_command.rs.
|
|
get_remote_contents was a full stub always returning None, so any
file:// download raised a TransportException. Read local files
directly for the file scheme, mirroring PHP's file_get_contents
transparently handling the file:// stream wrapper. Also fixes
file_get_contents5 to strip the file:// prefix like the 0-arg
variant already did.
|
|
Composer's classmap generator re-issues the same PHP-derived pattern
string for every scanned file (and every token within it), relying on
PCRE's built-in compiled-pattern cache to make that free. shirabe had
no equivalent, so every Preg::* call recompiled the pattern from
scratch via regex::Regex::new(), making `composer create-project`
autoload generation ~130x slower than Composer on a fresh laravel/laravel
install (130s vs ~1s). Memoizing compiled patterns by their raw string
in compile_php_pattern closes that gap.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
Platform::get_env("CI").is_some()/is_none() only checked whether the
variable was set, unlike PHP's (bool) Platform::getEnv('CI') which
treats "" and "0" as falsy. CI="0" (used by some CI providers to
explicitly disable CI mode) would previously flip behavior compared to
Composer.
|