Skip to content

feat: use DLoad to download binaries - #49

Merged
roxblnfk merged 12 commits into
3.xfrom
dload
Oct 12, 2026
Merged

roxblnfk merged 12 commits into
3.xfrom
dload

Conversation

@roxblnfk

@roxblnfk roxblnfk commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

What was changed

  • rr get-binary and rr download-protoc-binary keep their options and their meaning, but the download itself (release lookup, asset selection, download, extraction) is done by DLoad (internal/dload), so the download progress and download errors are printed in DLoad's format. The environment summary, the overwrite prompt and .rr.yaml generation stay in the CLI. rr versions keeps its output and uses a small internal GitHub client that lists release names and asset file names.
  • Requirements (DLoad's minimums): symfony/console ^6.4 || ^7.0 || ^8.0, symfony/http-client ^6.4.17 || ^7.2 || ^8.0; PHP stays >=8.2. spiral/tokenizer and ext-json are no longer required.
  • Option mapping: --filter + --stability → rr:<filter>@<stability>, where every || alternative without an upper bound gets <2000 unless its lower bound is a calendar version (>=3.0 → >=3.0 <2000); --filter='*' (or empty) → rr:>=3.0 <2000@<stability>; --os, --arch, --location → --os, --arch, --path; --force is always passed, since the CLI asks about an existing binary itself. --refresh is always on, so DLoad's version registry (kept in the user cache, see README) never hides a fresh release. The default filter is the RoadRunner v3 one from feat: support RoadRunner v3 #48 (3.* or the installed metapackage major).
  • A bundled empty resources/dload.xml is passed as --config; otherwise a project's ./dload.xml with an rr action would silently override --filter. The file is now part of the dist archive.
  • RR_GITHUB_API_URL keeps its meaning from feat: read the GitHub API URL from RR_GITHUB_API_URL #65 (the full API URL). https://api.github.com changes nothing; a GitHub Enterprise URL scheme://host[:port]/api/v3 becomes a temporary config that re-declares the rr and protoc-gen-php-grpc entries with <repository uri="scheme://host[:port]/roadrunner-server/roadrunner">. Any other path is rejected: DLoad always serves a self-hosted API at scheme://host[:port]/api/v3.
  • Tokens, the same for all commands (DLoad's rules; versions repeats them in Internal\GitHub\Token): the public GitHub gets DLOAD_TOKEN_GITHUB_COM, else GITHUB_TOKEN; any other server only its own DLOAD_TOKEN_<HOST> (e.g. DLOAD_TOKEN_GHE_EXAMPLE_COM; - is encoded as __, a port follows ___), so GITHUB_TOKEN never leaves the public GitHub. Plain http carries a token only to a loopback host, and a redirect to another host drops it.
  • versions passes --filter to composer/semver as a whole, so || alternatives work; a release whose tag is not a version is named dev-<tag>.
  • get-binary no longer writes an empty .rr.yaml when the configuration cannot be generated.
  • On a musl host such as Alpine, get-binary and download-protoc-binary now install the musl build by default and with an explicit --os=linux (see Review notes).
  • Side effects of DLoad, documented in the README: the project gets vendor/bin/dload, and the version registry is written to ~/.cache/dload ($XDG_CACHE_HOME/dload, %LOCALAPPDATA%\dload\cache on Windows; DLOAD_CACHE_DIR moves it, DLOAD_CACHE_TTL=0 turns it off).

Breaking changes (PHP API only, the console commands are unchanged)

  • Removed: the Archive and Repository namespaces (Version1\StaticRepository included), the abstract Spiral\RoadRunner\Console\Command with getRepository(), Command\OptionInterface, Environment\Enum, both Environment\*\Factory classes.
  • Moved to Spiral\RoadRunner\Console\Internal\… and marked @internal: the options (Command\* → Internal\Option), Configuration\*, Downloader\DLoadDownloader, Environment\Stability, Environment\RoadRunnerVersion, Environment\Environment.
  • Public API left: the four command classes (now final) and Environment\OperatingSystem / Environment\Architecture (used by the Temporal SDK). OperatingSystem::createFromGlobals() takes no arguments.

Why?

One downloader for the RoadRunner ecosystem instead of a second copy of release lookup and archive handling in the CLI, and a minimal public surface for 3.0.0.

Review notes

  • DLoad ^1.19.0. Self-hosted servers come from feat(repository): support self-hosted servers via repository URLs php-internal/dload#148. 1.19.0 is not on Packagist yet (release PR chore(1.x): release 1.19.0 php-internal/dload#144), so with minimum-stability: dev Composer resolves 1.x-dev until the release.
  • The acceptance tests run against a local fake GitHub Enterprise (php -S, API under /api/v3, one server per scenario since a server in a DLoad repository URL has no path). DLoad matches an exact pre-release (-f 3.5.0-beta.1), skips .deb packages and falls back to an older release with an archive, and sorts -rc above -beta. --stability=rc still prints the CLI's "Possibly invalid stability" warning, but DLoad reads it as RC (--stability=rc is treated as dev #67). DLoad requests the first release page twice when a server ignores per_page (GitHub and GHE honour it), so followsPagination compares unique pages.
  • Version ordering — solved in the CLI with <2000. DLoad orders versions numerically, so calendar releases rank above 3.x and any filter without an upper bound (*, >=3.0, !=3.1.0) would install 2025.1.x (rr and protoc-gen-php-grpc share the release stream), while rr versions lists 3.x first. get-binary and download-protoc-binary cap such filters below 2000 per || alternative, since DLoad has no parentheses; calendar releases are installed only when named (2025.*, >=2025). A filter that names both lines, like 3.* || 2025.*, still installs the numerically highest, 2025.x.
  • DLoad has no public PHP API: everything, including Internal\DLoad\Command\Get, is @internal. The integration runs the get command in-process (suppressed InternalClass).
  • Libc is chosen by DLoad (Libc::create()): --os=unknown-musl (or alpine) forces the musl build from any host — checked on the fake from Windows, it downloads …-unknown-musl-amd64.tar.gz; for --os=linux DLoad detects the host libc by /lib/ld-musl-*.so.1. Behaviour change: on Alpine --os=linux, explicit or the default (PHP_OS is Linux there, so createFromGlobals() returns linux), now installs the musl build, where the old -linux- asset filter gave the glibc one. A glibc build can no longer be forced on a musl host.

Checklist

@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 123 files, which is 23 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e5afbabe-1513-45a5-86f2-49d7db29e3a9

📥 Commits

Reviewing files that changed from the base of the PR and between 28ecf5b and 07614a0.


📒 Files selected for processing (123)
  • .gitattributes
  • README.md
  • composer.json
  • psalm.xml
  • resources/dload.xml
  • src/Archive/Archive.php
  • src/Archive/ArchiveInterface.php
  • src/Archive/Factory.php
  • src/Archive/FactoryInterface.php
  • src/Archive/PharArchive.php
  • src/Archive/PharAwareArchive.php
  • src/Archive/TarPharArchive.php
  • src/Archive/ZipPharArchive.php
  • src/Command.php
  • src/Command/Option.php
  • src/Command/OptionInterface.php
  • src/Command/VersionFilterOption.php
  • src/Configuration/Plugins.php
  • src/Configuration/Presets.php
  • src/DownloadProtocBinaryCommand.php
  • src/Environment/Architecture.php
  • src/Environment/Architecture/Factory.php
  • src/Environment/Enum.php
  • src/Environment/Environment.php
  • src/Environment/OperatingSystem.php
  • src/Environment/OperatingSystem/Factory.php
  • src/GetBinaryCommand.php
  • src/Internal/Configuration/Generator.php
  • src/Internal/Configuration/Plugins.php
  • src/Internal/Configuration/Presets.php
  • src/Internal/Configuration/Section/AbstractSection.php
  • src/Internal/Configuration/Section/Amqp.php
  • src/Internal/Configuration/Section/Beanstalk.php
  • src/Internal/Configuration/Section/Boltdb.php
  • src/Internal/Configuration/Section/Broadcast.php
  • src/Internal/Configuration/Section/Endure.php
  • src/Internal/Configuration/Section/Fileserver.php
  • src/Internal/Configuration/Section/Grpc.php
  • src/Internal/Configuration/Section/Http.php
  • src/Internal/Configuration/Section/Jobs.php
  • src/Internal/Configuration/Section/Kv.php
  • src/Internal/Configuration/Section/Logs.php
  • src/Internal/Configuration/Section/Metrics.php
  • src/Internal/Configuration/Section/Nats.php
  • src/Internal/Configuration/Section/Otel.php
  • src/Internal/Configuration/Section/Redis.php
  • src/Internal/Configuration/Section/Reload.php
  • src/Internal/Configuration/Section/Rpc.php
  • src/Internal/Configuration/Section/SectionInterface.php
  • src/Internal/Configuration/Section/Server.php
  • src/Internal/Configuration/Section/Service.php
  • src/Internal/Configuration/Section/Sqs.php
  • src/Internal/Configuration/Section/Status.php
  • src/Internal/Configuration/Section/Tcp.php
  • src/Internal/Configuration/Section/Temporal.php
  • src/Internal/Configuration/Section/Version.php
  • src/Internal/Configuration/Section/Websockets.php
  • src/Internal/Downloader/DLoadDownloader.php
  • src/Internal/Environment/Environment.php
  • src/Internal/Environment/RoadRunnerVersion.php
  • src/Internal/Environment/Stability.php
  • src/Internal/GitHub/Release.php
  • src/Internal/GitHub/ReleasesClient.php
  • src/Internal/GitHub/Token.php
  • src/Internal/Option/ArchitectureOption.php
  • src/Internal/Option/InstallationLocationOption.php
  • src/Internal/Option/OperatingSystemOption.php
  • src/Internal/Option/Option.php
  • src/Internal/Option/StabilityOption.php
  • src/Internal/Option/VersionFilterOption.php
  • src/MakeConfigCommand.php
  • src/Repository/Asset.php
  • src/Repository/AssetInterface.php
  • src/Repository/AssetsCollection.php
  • src/Repository/Collection.php
  • src/Repository/GitHub/GitHubAsset.php
  • src/Repository/GitHub/GitHubRelease.php
  • src/Repository/GitHub/GitHubRepository.php
  • src/Repository/Release.php
  • src/Repository/ReleaseInterface.php
  • src/Repository/ReleasesCollection.php
  • src/Repository/RepositoriesCollection.php
  • src/Repository/RepositoryInterface.php
  • src/Repository/Version1/StaticRepository.php
  • src/VersionsCommand.php
  • tests/Acceptance/AuthenticationTest.php
  • tests/Acceptance/ExistingBinaryTest.php
  • tests/Acceptance/FailureTest.php
  • tests/Acceptance/Server/releases.json
  • tests/Acceptance/Server/router.php
  • tests/Acceptance/Support/FakeGitHub.php
  • tests/Acceptance/VersionSelectionTest.php
  • tests/Acceptance/VersionsTest.php
  • tests/Unit/Archive/FactoryTest.php
  • tests/Unit/Archive/PharAwareArchiveTest.php
  • tests/Unit/Command/VersionFilterOptionTest.php
  • tests/Unit/CommandTest.php
  • tests/Unit/DownloadProtocBinaryCommandTest.php
  • tests/Unit/Environment/EnumTest.php
  • tests/Unit/GetBinaryCommandTest.php
  • tests/Unit/Internal/Configuration/GeneratorTest.php
  • tests/Unit/Internal/Configuration/PluginsTest.php
  • tests/Unit/Internal/Downloader/DLoadDownloaderTest.php
  • tests/Unit/Internal/Environment/EnvironmentTest.php
  • tests/Unit/Internal/Environment/StabilityTest.php
  • tests/Unit/Internal/GitHub/ReleaseTest.php
  • tests/Unit/Internal/GitHub/ReleasesClientTest.php
  • tests/Unit/Internal/GitHub/TokenTest.php
  • tests/Unit/Internal/Option/EnvironmentOptionsTest.php
  • tests/Unit/Internal/Option/InstallationLocationOptionTest.php
  • tests/Unit/Internal/Option/VersionFilterOptionTest.php
  • tests/Unit/Repository/AssetsCollectionTest.php
  • tests/Unit/Repository/CollectionTest.php
  • tests/Unit/Repository/GitHub/GitHubAssetTest.php
  • tests/Unit/Repository/GitHub/GitHubReleaseTest.php
  • tests/Unit/Repository/GitHub/GitHubRepositoryTest.php
  • tests/Unit/Repository/ReleasesCollectionTest.php
  • tests/Unit/Repository/RepositoriesCollectionTest.php
  • tests/Unit/Repository/Version1/StaticRepositoryTest.php
  • tests/Unit/Stub/DLoadGetSpy.php
  • tests/Unit/Stub/InMemoryRepository.php
  • tests/Unit/Stub/Releases.php
  • tests/Unit/VersionsCommandTest.php

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@roxblnfk

roxblnfk commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@roxblnfk

roxblnfk commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

Blocked by php-internal/dload#143: the rr get acceptance tests (#66) point rr at a local fake GitHub through RR_GITHUB_API_URL, and on this branch that address reaches DLoad as <repository host="…">, which DLoad does not support yet. Until it does, the acceptance tests here are expected to fail.

`get-binary` and `download-protoc-binary` keep their options and output, but release lookup, asset selection, download and extraction are done by DLoad (`internal/dload` ^1.19.0); the overwrite prompt and `.rr.yaml` generation stay in the CLI. `--filter` and `--stability` become a DLoad constraint, and `--refresh` is always on so the version cache never hides a fresh release. An empty bundled `resources/dload.xml`, shipped in the dist archive, is passed as `--config`, so a project's `./dload.xml` can not override the requested version.

`RR_GITHUB_API_URL` keeps its meaning: a GitHub Enterprise Server API URL `scheme://host[:port]/api/v3` becomes a temporary config that points the `rr` and `protoc-gen-php-grpc` entries at `scheme://host[:port]/roadrunner-server/roadrunner`. `GITHUB_TOKEN` goes only to the public GitHub, as DLoad does.

A kept binary now exits with 1, and an unknown `--os` or `--arch` fails before any request. The acceptance tests run `rr get` against a fake GitHub Enterprise Server.

BREAKING CHANGE: for `get-binary` and `download-protoc-binary`, `RR_GITHUB_API_URL` must be `https://api.github.com` or end with `/api/v3`, and the token for a GitHub Enterprise Server is read from `DLOAD_TOKEN_<HOST>` (e.g. `DLOAD_TOKEN_GHE_EXAMPLE_COM`) instead of `GITHUB_TOKEN`. `symfony/console` and `symfony/http-client` 6.4 or newer are required.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
RoadRunner went from calendar releases back to 3.x and DLoad orders versions numerically, so `-f '*'` installed 2025.1.x for both `get-binary` and `download-protoc-binary`, which share the release stream. `*` and an empty filter now mean `^3.0` with the requested stability; `2025.*` still selects the calendar releases.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
The tests pin the output of `rr versions` before its GitHub client is replaced.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
DLoad downloads and unpacks the binaries, so only `rr versions` still reads the GitHub releases. It now uses a small internal client that lists release names and asset file names, and passes `--filter` to composer/semver as a whole, so `||` alternatives work. A release whose tag and name are not versions is named `dev-<tag>` instead of `d-<tag>`.

BREAKING CHANGE: the `Spiral\RoadRunner\Console\Archive` and `Spiral\RoadRunner\Console\Repository` namespaces are removed, `Version1\StaticRepository` included. `Command::getRepository()` and `Command::confirm()` are removed, and `VersionFilterOption::find()` and `VersionFilterOption::choices()` with them. `VersionsCommand` is final.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
Only the console commands and the platform constants are meant for other code: the Temporal SDK reads `Environment\OperatingSystem` and `Environment\Architecture`. Everything else is marked `@internal` and lives under `Spiral\RoadRunner\Console\Internal`, so it can change in a minor release.

BREAKING CHANGE: the `Command`, `Configuration` and `Downloader` namespaces move to `Spiral\RoadRunner\Console\Internal\Option`, `Internal\Configuration` and `Internal\Downloader`, and `Environment\Stability`, `Environment\RoadRunnerVersion` and `Environment\Environment` to `Internal\Environment`; all of them are internal now. The abstract `Spiral\RoadRunner\Console\Command`, `Command\OptionInterface`, `Environment\Enum` and both `Environment\*\Factory` classes are removed: the commands extend the Symfony command directly. The commands and `Configuration\Generator` are final. `Environment\Environment::get()` takes only the variable name, `OperatingSystem::createFromGlobals()` takes no arguments, `Stability::all()` returns a list, and `RoadRunnerVersion::PACKAGE_NAME` and `DEFAULT_CONSTRAINT` are private.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
build: drop the spiral/tokenizer and ext-json requirements

Scanning the `Section` directory with spiral/tokenizer on every `make-config` run found the same fixed set of classes; `PluginsTest` still checks that every section is listed. JSON is always enabled since PHP 8.0.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
`get-binary` printed the error and then wrote the undefined result to `.rr.yaml`; `make-config` already stopped there.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
`versions` sent `GITHUB_TOKEN` to whatever `RR_GITHUB_API_URL` named, while `get-binary` keeps it on the public GitHub. Now both follow DLoad's rules: the public GitHub gets `DLOAD_TOKEN_GITHUB_COM` or `GITHUB_TOKEN`, another server only its own `DLOAD_TOKEN_<HOST>`, and plain HTTP carries a token only to a loopback host. DLoad keeps these rules in internal classes, so they are repeated in `Internal\GitHub\Token`. Symfony HttpClient already drops the `Authorization` header on a redirect to another scheme, host or port; an acceptance test pins that.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
The commands are used only by `bin/rr`, which psalm does not analyse, so they are marked `@psalm-api`.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
DLoad orders versions numerically, so the calendar releases (2025.x) rank above 3.x, and a filter without an upper bound, like `>=3.0` or `!=3.1.0`, installed 2025.1.x while `rr versions` lists 3.x first. Each `||` alternative without an upper bound now gets `<2000`, unless its lower bound is a calendar version, and `*` means `>=3.0 <2000`, so a future 4.x is included. The calendar releases are installed only when named, like `2025.*` or `>=2025`.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
DLoad picks the libc itself, so an explicit `--os=linux` on Alpine now installs the musl build, and `--os=unknown-musl` still forces it from any host. The registry location and `vendor/bin/dload` are new to users of the CLI.

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
@roxblnfk
roxblnfk merged commit d38492e into 3.x Oct 12, 2026
15 checks passed
@roxblnfk
roxblnfk deleted the dload branch October 12, 2026 10:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment