| Age | Commit message (Collapse) | Author |
|
file_get_contents() and file_get_contents_with_max_length() return
Vec<u8> instead of a from_utf8_lossy'd String.
Call sites whose consumer takes a &str still convert lossily and are
marked TODO(bytes).
file_get_contents_with_max_length() now reads at most the requested
number of bytes instead of the whole file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
`config --list` and `config <key>` rendered a value with `as_string()`,
which yields None for everything but a string, so every int-valued
setting printed as an empty string: `cache-ttl`, `cache-files-ttl` and
the resolved value of `cache-files-maxsize`. PHP builds that output by
concatenating the value into the message, which casts it, so the ports
now go through `php_to_string`.
Two more differences in the same rendering path:
The `[a, b]` branch flattened the array through `as_list()`, which sees
only `PhpMixed::List`, so a keyed array reaching it rendered as `[]`.
PHP takes that branch whenever the first key is numeric, so the values
now come from `array_values_mixed`, which covers both representations.
The raw-vs-resolved comparison deciding between `raw (value)` and
`value` compared the two as strings. PHP compares with `!==`, which
also compares the type: with a `cache-ttl` of `"15552000"` in
composer.json, the raw string and the resolved int are not identical
and Composer prints `15552000 (15552000)`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The stat array was modelled as IndexMap<String, PhpMixed>, forcing
callers through untyped lookups with an unwrap_or(0) fallback PHP has no
counterpart for. Only `size` and `comp_size` are kept: statIndex has a
single call site in Composer and it reads nothing else.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
`ProcessExecutor::execute_args` existed only to turn `execute`'s
`anyhow::Result` into an exit code of 1, so every one of its ~87 call
sites silently took the "command failed" branch on an error PHP would
have thrown. It is gone; callers use `execute` and propagate with `?`.
Where the enclosing function had no `Result` to propagate into, its
signature grew one, up to and including `Git::get_version`,
`Svn::binary_version`, `GitHub`/`GitLab`/`Bitbucket::authorize_oauth`,
`InitCommand::get_git_config` and `DiagnoseCommand::check_git`.
The VCS drivers had the same problem in the other direction: their
`get_contents` returned `Result<Response, Box<TransportException>>`, a
type too narrow for the PHP method, which lets any Throwable out of the
`catch (TransportException $e)` block. Every non-transport error was
therefore rewritten into a `TransportException` with code 0, which the
callers switch on. They now return
`anyhow::Result<Result<Response, Box<TransportException>>>`: the outer
`Result` carries what PHP does not catch, the inner one the exception
the drivers handle.
That signature also restores `GitLabDriver::getContents`: the 400/401
`TransportException`s it raises to force authentication are thrown
inside its own `try` block and handled by its own `catch`, but the port
returned them straight to the caller, so the authentication flow behind
them never ran.
`impl_php_exception!` gains `From<Box<$ty>> for anyhow::Error` so a
caught exception can be re-propagated with `?`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
PHP's PREG_PATTERN_ORDER is column-oriented, but 7 of the 10 call sites
read it row-wise, rebuilding each occurrence by indexing every column at
the same offset. Return an iterator of PregMatches instead, which is also
what the set-order and offset-capture variants were carrying, so the
three functions collapse into one and PregMatchesAll,
PregMatchesAllWithOffsets, CaptureKey and preg_match_map! all go away.
The offset-capture call sites are served by the new PregMatches
get_offset/name_offset accessors.
The search stays eager: regex::Captures borrows only the subject, so the
matches outlive the pattern resolved for the call, and PHP's
preg_match_all is eager too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The capture groups were discarded at 162 of the preg_match call sites,
which only tested the Option. They now call preg_is_match, which lets the
regex engine skip capture tracking.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Every call site but one passed offset 0. The remaining one, the UTF-8
chunking loop in Application, slices the subject instead: its pattern has
no anchor or lookaround, so matching a suffix is equivalent to starting
the search at that offset.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The shim carried the PCRE A (anchored) modifier alongside every compiled
pattern so preg_match2 could honour it by searching the sub-slice at the
offset. Only two call sites ever passed such a pattern, and each can cut
that slice itself, so the flag is gone from the cache, ResolvedPattern,
PregPattern and the php_regex! macro, and a pattern still carrying A is
now rejected rather than silently searched unanchored.
StringInput::tokenize and PhpFileCleaner::match search from their cursor
with a `^`-prefixed pattern instead. The rule is written down in
docs/dev/regex-porting.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The two preg_match_all variants took the same arguments and differed
only in what they returned: a Vec of columns, or the named-and-numbered
PregMatchesAll. The latter is the one all but two call sites already
used, so preg_match_all2 takes over the plain PHP name and the Vec
variant goes away.
Its remaining readers only ever wanted group 0's column, which they now
take through CaptureKey::ByIndex(0); in the formatter this replaces the
array_shift that popped that column off the PREG_PATTERN_ORDER array.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
preg_match copied every group into a Vec<Option<String>> while
preg_match2 handed back the borrowed captures. They now differ only in
the offset argument, so preg_match delegates with offset 0 and its
callers read groups through PregMatches::get.
Going through preg_match2 also makes preg_match honour the PCRE A
modifier, which it used to ignore; no caller passes such a pattern.
VersionParser::manipulate_version_string takes an index accessor instead
of a slice, and VersionParser::normalize matches against a copy of the
subject because the captures outlive the assignments to $version.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Preg had shed everything it owned: after the last few rounds its methods
were one-line forwards to the shim's preg_*(), differing only in a
default argument or a wrapper the caller unwrapped anyway. The 460 call
sites now name the shim function, and shirabe-pcre is gone from the
workspace along with its LICENSE entry.
The forwards expand as they read: isMatch becomes
preg_match2(.., 0).is_some() (is_none() where PHP negates it), isMatch3
and match3 drop the .is_some(), matchAll counts through
preg_match_all2(..).occurrence_count(), and replace4/replace5 spell out
the limit and count arguments preg_replace2 takes. Callbacks are the one
place the shapes differ: preg_replace_callback carries an error out of
the callback, so the fourteen infallible closures wrap their result in
Ok() and expect() it back.
Config::process() is the fifteenth, and it drops the `error` cell it
captured to smuggle a failure past a closure that could only return a
String. The `?` in the closure now carries it, which is what the PHP
does -- a throw from the callback leaves preg_replace_callback at the
failing match rather than running the remaining replacements and
reporting the last error.
The module doc that explained why composer/pcre's exceptions and
*StrictGroups() variants have no counterpart moves to the shim's preg
module, where the functions it describes live.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
PregMatches keyed both forms of a capture group through CaptureKey, so
every read built one: a usize wrapped in an enum, or worse, a String
allocated to name a group that regex::Captures can look up from a &str.
It now mirrors regex::Captures instead -- get() takes the group number,
name() the group name -- and the enum drops out of the type entirely.
That is 285 call sites across 59 files, and the named ones carry most of
the win: `matches.get(&CaptureKey::ByName("host".to_string()))` reads as
`matches.name("host")`. ProcessExecutor loses a `user_key` binding that
existed only to build the key once.
CaptureKey stays as the key type of PregMatchesAll and
PregMatchesAllWithOffsets, where numbered and named entries share one
IndexMap and a key type is the point. Five files still name it.
Also retargets the two preg_match_all comments that described the
occurrence count through `matches[&CaptureKey::ByIndex(0)].len()`, an
Index impl these types no longer carry.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
is_match_named and is_match_with_indexed_captures reshaped a match into
a name-keyed map or a number-positioned vec, each allocating a String
per group up front for callers that then read one or two of them. Every
one of the eleven call sites ports a plain Preg::isMatch in PHP, so they
now call is_match3 and reach for the group they want through
get(&CaptureKey::ByIndex(N)) / get(&CaptureKey::ByName(..)), the same
way the rest of the tree already reads a match.
Falling out of that: PregNamedGroups existed only to type the first
variant; PregMatches::iter() only to build both; and PregMatches::pattern
only to give iter() the capture names. PregMatches is now a plain
wrapper over regex::Captures, so preg_replace_callback no longer clones
the resolved pattern for every match, and preg_match_map! is internal to
the shim again.
SvnDriver::get_file_content and get_change_date recover the flat
`isMatch(..) && $match[2] !== null` condition the PHP has, which the vec
shape had forced into a nested if.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
PregMatches was an IndexMap of owned Strings copied out of the match, so
every preg_match2/preg_replace_callback call allocated a String per
capture group (twice over for a named group) whether or not the caller
read it. It now wraps the regex::Captures itself, held alongside the
pattern it came from so groups stay reachable by both their named and
their numbered form, and hands out &str borrowed from the subject. The
subject's lifetime becomes a parameter of the type.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
`Composer\Pcre\Preg` fills `$matches` through a by-ref parameter, and the
port mirrored that with a `&mut` (or `Option<&mut>`) out-param plus a
bool or count return. Callers had to declare an empty map one line ahead
of the call, and the type never said the map is only meaningful when the
call matched. Return the matches instead:
- match3/match4/is_match3/is_match4 -> Option<PregMatchedGroups>
- is_match_named -> Option<PregNamedGroups>
- match_all2/is_match_all -> PregMatchesAll
- is_match_all_with_offsets3 -> PregMatchesAllWithOffsets
Nothing is lost: the bool is `Option::is_some()`, and the occurrence
count is the length of any one column of a PREG_PATTERN_ORDER map, now
spelled `PregMatchesAll::occurrence_count()`. is_match() still answers
the bool question directly for callers that want no groups.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
PHP fills `$matches` through a by-ref parameter, which the port mirrored
with a `&mut` out-param plus a bool or count return. Every caller then
had to declare an empty binding one line ahead of the call, and nothing
in the type said the binding is only meaningful when the call succeeded.
Return the matches instead: preg_match() and preg_match2() hand back an
Option, and the three preg_match_all* functions hand back the collection
they used to fill.
The occurrence count the two map-shaped preg_match_all* functions used
to return is the length of any one of the map's columns, so it is not
lost -- Preg::match_all() and friends derive it via occurrence_count().
preg_replace2() keeps its `count: Option<&mut usize>`: that one is not
derivable from the replaced string, and callers that do not want it pay
nothing for passing None.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
preg_match_all() and preg_match_all_set_order() were the last preg_*
functions handing back a bare Vec<String>, where a group that did not
participate is indistinguishable from one that captured "". Hand back
Option<String> as the map-shaped functions already do; php_match_row(),
the last of the truncate-then-pad helpers, goes with them.
preg_split_delim_capture() keeps its Vec<String>. preg_split() accepts
no PREG_UNMATCHED_AS_NULL, so there is no null form to move it to, and
its result interleaves split segments -- which can never be absent --
with the captured delimiters.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The shim carried two reporting modes for the preg_* $matches maps: PHP's
default (trailing unmatched groups dropped, interior ones ""), and the
PREG_UNMATCHED_AS_NULL form, picked by calling a *_unmatched_as_null()
variant. The regex crate hands out Option<Match>, which maps onto the
null form directly, and no caller distinguished a dropped group from a
null one -- preg_match() and preg_match_all2() already reported nulls
unconditionally. Keep only the null form; the shim API no longer mirrors
PHP's flag set, which is intended.
Preg::is_match_with_indexed_captures() modelled PHP's "unset" as a
truncated Vec<String>, and now returns Vec<Option<String>>. That is what
Composer actually does: Preg::isMatch() always sets
PREG_UNMATCHED_AS_NULL, and its callers test groups with `!== null`.
preg_match_all(), preg_match_all_set_order() and
preg_split_delim_capture() still hand back Vec<String> and keep the ""
form.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The five IndexMap shapes that the preg_* functions and Preg fill in are
now distinct types generated by preg_match_map!, so a matches map no
longer interchanges with any other map of the same key and value type.
Index<usize> is kept alongside Index<&Q> because call sites such as
config_command and event_dispatcher reach for a group by its position in
the map rather than by its capture key.
|
|
preg_match2() and preg_match_all_offset_capture() each become a pair over
a shared private impl, and their flags arguments are gone: no caller passed
anything but 0 or PREG_UNMATCHED_AS_NULL. Preg::match5()/is_match5() lose
their own flags argument for the same reason -- both call sites passed 0,
and the value had nowhere left to go -- so they are renumbered to
match4()/is_match4(). PREG_UNMATCHED_AS_NULL itself is now unreferenced.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Each branch of the flag is now its own function. The PREG_UNMATCHED_AS_NULL
variant needs neither the trailing-group truncation nor the empty-string
fallback, so it loses the break condition and the last_participating scan.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Preg::match5() rejected PREG_OFFSET_CAPTURE and check_set_order() rejected
PREG_SET_ORDER, mirroring the PHP where those flags would change the type
of $matches. The Rust ports of both take a typed `matches` out-param
instead, so neither flag can reach them; check_set_order() already had no
caller. The three constants are unreferenced once the guards are gone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
preg_split2()'s limit was always -1 and its flags were always either 0 or
PREG_SPLIT_DELIM_CAPTURE alone, so both arguments are gone: the shim now
exposes preg_split() and preg_split_delim_capture() over a shared
preg_split_impl(). That leaves Preg::split()/split4() as bare
pass-throughs, so callers use the shim functions directly and the wrappers
are dropped along with the now-unreferenced PREG_SPLIT_* constants.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The two functions ran the same search and differed only in how they
reported a non-participating group: one as ("", 0) in a bespoke struct,
the other as (null, -1) in a capture-key map. Neither shape covered both
callers, because PHP reaches preg_match_all() with different flags from
each: Preg::matchAllWithOffsets() always ORs in PREG_UNMATCHED_AS_NULL,
while OutputFormatter::formatAndWrap() passes PREG_OFFSET_CAPTURE alone.
Keep the capture-key map, which also exposes named groups, and take the
flags that decide between null and "" for an unmatched group. The offset
is -1 either way, so the ("", 0) approximation is gone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The sole caller, Preg::match_all_with_offsets5(), always passed flags=0
and offset=0, so PREG_UNMATCHED_AS_NULL is now unconditional and the
subject is scanned from the beginning. That method is only reached from
Preg::match_all_with_offsets(), so it is no longer public either.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Every caller reached preg_match_all2() through Preg::match_all5(), which
always passed flags = PREG_UNMATCHED_AS_NULL and offset = 0. Inline those
constants and make match_all5() private, since the two public wrappers are
its only callers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Callers had to build a temporary Vec<&str> at every call site to satisfy
the &[&str] parameter. Taking IntoIterator and yielding the matched items
lets them pass owned or borrowed strings directly.
The flags variant and PREG_GREP_INVERT go away with it: no caller passes
flags, and Composer's Preg::grep() has no such parameter either.
|
|
|
|
preg_replace_callback2 had one caller, Preg::replace_callback6, which
was itself reached only from Preg::replace_callback with the default
limit, count and flags. Fold the two shim functions into one and drop
those parameters along with replace_callback6.
The surviving callback takes callback2's IndexMap of matches: it can be
keyed by group name, and it omits trailing non-participating groups the
way PHP does, which is what the callbacks in Process and ProgressBar
test for.
|
|
The stub token search compared every 18-byte window of the file case
insensitively. The token starts with a byte that ASCII case folding
leaves alone, so memchr can pick out the candidate offsets and leave
only those to the case-insensitive compare.
Over the 35 MB executable, finding the token in the embedded bundle
stub drops from 2.3 ms to 0.1 ms, and a scan that finds nothing drops
from 24 ms to 3.6 ms.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
This is the type Filesystem::directory_size() actually iterates, and it
swallowed a failed stat as 0 the same way. PHP's iterator yields
\SplFileInfo there, so raise the same \RuntimeException it would.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
These sites have no PHP counterpart to mirror, so they read std::env
directly. Going through the shim's getenv() keeps every environment
read in one place and lets a lint forbid the direct form.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The signature took usize, so PHP's negative $start and $length, which
count from the end of the string, could not be expressed. Take i64 and
an optional length, and apply PHP's clamping rules.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
serialize(), var_export() and the string cast each hand-rolled
`format!("{}", f)`, which never emits PHP's exponential spelling and
carries the wrong precision for the string cast. PHP writes 1.0E+20
where Rust writes 1e20, and (string) 1/3 is 0.33333333333333, not the
shortest round-trip form.
Delegate all three to smart_str_append_double, which shirabe-php-src
already provides and shirabe-php-rpc's codec already used: precision -1
for serialize() and var_export(), 14 for the string cast.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
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>
|
|
json_decode built a serde_json::Value first and then converted it. Drive
serde_json's Deserializer with a DeserializeSeed instead, so the value is
built in one pass without the intermediate representation.
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>
|
|
fstat and lstat now return Option<FileStat> instead of a PhpMixed array, so
Platform::is_tty and Filesystem::is_junction read `mode` as a field. The
array carried each of the 13 values twice — once under its numeric index and
once under its name — which no caller relied on, and building it spelled the
field list out four times.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
posix_getpwuid now returns Option<PasswdEntry> instead of a PhpMixed array,
so Platform reads the field it wants rather than digging through the map.
posix_getuid and posix_geteuid return u32, matching the uid PasswdEntry is
looked up by.
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>
|
|
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>
|
|
PhpDirHandle recorded the opened path and nothing else, and its only
caller asked just whether the open had succeeded. A resource type that
has to gain readdir and closedir before it means anything is worse than
the std::fs::read_dir call it wraps, so Filesystem::isReadable now makes
that call directly.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The two TODOs marked deferred work that nothing reaches.
FILE_USE_INCLUDE_PATH appears nowhere in Composer or its vendored
dependencies, and the only site passing LOCK_EX to file_put_contents is
Symfony's Filesystem::appendToFile(), which no caller invokes and which
raises the flag only when handed a third argument. The stream $context
is dropped just as narrowly: the network scheme that would carry one is
already marked at the call site in RemoteFilesystem, leaving the shim
reachable through the local-file branch alone.
Record what these functions do and do not support instead, so the tags
stay a list of work that is actually pending.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The comment claimed the port loses the file type bits that PHP's
fileperms() reports. It does not: MetadataExt::mode() hands back the raw
st_mode, the same value php_stat() returns for FS_PERMS, and both agree
on directories, regular files and executables.
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>
|
|
`http_build_query` ignores `numeric_prefix` because a string-keyed slice never
holds an integer key, and every caller passes `"&"` as the separator. Drop both
parameters and hard-code the separator.
Callers that pass a map of scalar literals to `http_build_query_mixed` no longer
need to build an `IndexMap<String, PhpMixed>` for it, so move them to the slice
form.
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>
|
|
`__halt_compiler` is a PHP keyword, so a stub may spell the token in any
letter case. The native phar reader compared the bytes exactly and rejected
such an archive, and the lint that keeps a second token out of the
executable missed lowercase ones, which would shadow the embedded bundle.
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>
|