Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README-es.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ También puedes descargar la versión más reciente desde [GitHub releases](http
|--------|-------------|-------------------|
| `--path` | Directorio donde guardar los binarios | Directorio actual |
| `--arch` | Arquitectura de destino (amd64, arm64) | Arquitectura del sistema |
| `--os` | Sistema operativo de destino (linux, darwin, windows) | SO actual |
| `--os` | Sistema operativo de destino (linux, alpine, darwin, windows, android) | SO actual |
| `--stability` | Estabilidad del release (stable, beta) | stable |
| `--config` | Ruta al archivo de configuración | ./dload.xml |
| `--force`, `-f` | Forzar descarga aunque el binario ya exista | false |
Expand Down
2 changes: 1 addition & 1 deletion README-ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ composer require internal/dload -W
|-------|----------|--------------|
| `--path` | Папка для хранения бинарников | Текущая папка |
| `--arch` | Целевая архитектура (amd64, arm64) | Архитектура системы |
| `--os` | Целевая ОС (linux, darwin, windows) | Текущая ОС |
| `--os` | Целевая ОС (linux, alpine, darwin, windows, android) | Текущая ОС |
| `--stability` | Стабильность релиза (stable, beta) | stable |
| `--config` | Путь к конфигурационному файлу | ./dload.xml |
| `--force`, `-f` | Принудительная загрузка даже если бинарник уже есть | false |
Expand Down
2 changes: 1 addition & 1 deletion README-zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ composer require internal/dload -W
|--------|-------------|---------|
| `--path` | 二进制文件存储目录 | 当前目录 |
| `--arch` | 目标架构 (amd64, arm64) | 系统架构 |
| `--os` | 目标操作系统 (linux, darwin, windows) | 当前操作系统 |
| `--os` | 目标操作系统 (linux, alpine, darwin, windows, android) | 当前操作系统 |
| `--stability` | 发布稳定性 (stable, beta) | stable |
| `--config` | 配置文件路径 | ./dload.xml |
| `--force`, `-f` | 即使二进制文件已存在也强制下载 | false |
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ Alternatively, you can download the latest release from [GitHub releases](https:
|--------|-------------|---------|
| `--path` | Directory to store binaries | Current directory |
| `--arch` | Target architecture (amd64, arm64) | System architecture |
| `--os` | Target OS (linux, darwin, windows) | Current OS |
| `--os` | Target OS (linux, alpine, darwin, windows, android) | Current OS |
| `--stability` | Release stability (stable, beta) | stable |
| `--config` | Path to configuration file | ./dload.xml |
| `--force`, `-f` | Force download even if binary exists | false |
Expand Down
2 changes: 1 addition & 1 deletion resources/software.json
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@
"type": "github",
"uri": "oven-sh/bun",
"tag-prefix": "bun-",
"asset-pattern": "/^bun-[a-z]+-(?:x64|aarch64)\\.zip$/"
"asset-pattern": "/^bun-.*/"
}
],
"binary": {
Expand Down
5 changes: 4 additions & 1 deletion skills/dload-fetch-tool/references/registry-entry.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ Filters the release's asset list. dload then runs OS/arch detection on every mat
- match all OS/arch variants of the tool;
- leave out sibling tools, checksums, signatures, source archives.

Build variants stay in the pattern too. Among the assets for the host, dload prefers the host libc (`musl` builds on Alpine and Android, the others elsewhere), then the name with the fewest extra tokens — so `bun-linux-x64.zip` wins over `-baseline`, `-profile` and `-debug` twins — then archives. Android builds are dropped on Linux whenever the entry has a `binary` (without one, every other platform is only ranked lower). On ARM macOS and Windows, x86-64 builds are a fallback: Rosetta 2 and the Windows emulation run them.

Tokens the OS/arch matchers recognise (case-insensitive, bounded by `_` or a word boundary):

| Kind | Tokens |
|---|---|
| OS | `windows`, `linux`, `darwin`, `macos`, `alpine`, `bsd`, `freebsd`, `win32`, `win64` |
| OS | `windows`, `linux`, `darwin`, `macos`, `alpine`, `bsd`, `freebsd`, `win32`, `win64`, `android` (wins over `linux`) |
| Arch | `amd64`, `arm64`, `aarch64`, `x86_64`, `x64`, `win64` |
| Libc | `musl`, `musleabi*`, `alpine`; `gnu`, `gnueabi*`, `glibc`; none means glibc |

| Situation | Pattern |
|---|---|
Expand Down
4 changes: 3 additions & 1 deletion skills/dload-fetch-tool/references/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ curl -s "https://api.github.com/repos/<owner>/<repo>/releases/tags/<tag>" \

After filtering, dload picks the host's variant with:

- OS: `/(?:\b|_)(windows|linux|darwin|macos|alpine|bsd|freebsd|win32|win64)(?:\b|_)/i`
- OS: `/(?:\b|_)(windows|linux|darwin|macos|alpine|bsd|freebsd|win32|win64)(?:\b|_)/i`; an `android` token makes it Android, a bare `musl` makes it Linux
- Arch: `/(?:\b|_)(amd64|arm64|aarch64|x86_64|x64|win64)(?:\b|_)/i`

A candidate that matches neither is discarded. Common offenders:
Expand All @@ -44,6 +44,8 @@ A candidate that matches neither is discarded. Common offenders:

Compare with the host: `php -r "echo PHP_OS_FAMILY, ' / ', php_uname('m'), PHP_EOL;"`.

Several candidates left: dload tries them best first — host libc, then the fewest extra name tokens, then archives, then the release order. Run with `-vvv` to see the rank of every asset (`Asset ... ranked: os=0 arch=0 libc=0 extras=1 archive=0`); a wrong pick usually means the wanted build carries an extra token the others lack.

## Stage 4 — binary extraction

The chosen asset is unpacked into `temp-dir`, then `binary.pattern` selects the executable.
Expand Down
2 changes: 2 additions & 0 deletions src/Bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Internal\DLoad\Module\Binary\Internal\BinaryProviderImpl;
use Internal\DLoad\Module\Common\Architecture;
use Internal\DLoad\Module\Common\Internal\Injection\ConfigInflector;
use Internal\DLoad\Module\Common\Libc;
use Internal\DLoad\Module\Common\OperatingSystem;
use Internal\DLoad\Module\Common\Stability;
use Internal\DLoad\Module\Config\Schema\Cache as CacheConfig;
Expand Down Expand Up @@ -115,6 +116,7 @@ public function withConfig(
// Register bindings
$this->container->bind(Architecture::class);
$this->container->bind(OperatingSystem::class);
$this->container->bind(Libc::class);
$this->container->bind(Stability::class);
$this->container->bind(
RepositoryProvider::class,
Expand Down
14 changes: 13 additions & 1 deletion src/Command/Get.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Internal\Container\Container;
use Internal\DLoad\DLoad;
use Internal\DLoad\Module\Common\Architecture;
use Internal\DLoad\Module\Common\Libc;
use Internal\DLoad\Module\Common\OperatingSystem;
use Internal\DLoad\Module\Common\Stability;
use Internal\DLoad\Module\Config\Schema\Action\Download as DownloadConfig;
Expand Down Expand Up @@ -117,7 +118,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

$output->writeln('Architecture: ' . $container->get(Architecture::class)->name);
$output->writeln(' Op. system: ' . $container->get(OperatingSystem::class)->name);
$output->writeln(
' Op. system: ' . $container->get(OperatingSystem::class)->name
. self::describeLibc($container->get(Libc::class)),
);
$output->writeln(' Stability: ' . $container->get(Stability::class)->name);

$actions === [] and throw new \RuntimeException('No software to download.');
Expand Down Expand Up @@ -259,6 +263,14 @@ private static function applyFlags(InputInterface $input, Container $container):
));
}

/**
* Names the libc next to the OS where builds differ by it.
*/
private static function describeLibc(Libc $libc): string
{
return $libc === Libc::System ? '' : " ({$libc->value})";
}

/**
* Prints the reason of every failed download and returns a failure exit code.
*
Expand Down
109 changes: 109 additions & 0 deletions src/Module/Common/Libc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

declare(strict_types=1);

namespace Internal\DLoad\Module\Common;

use Internal\Container\Factoriable;
use Internal\DLoad\Module\Common\Input\Build;

/**
* C standard library of a host or a build.
*
* Builds name only {@see self::Gnu} or {@see self::Musl}; the other cases describe hosts.
*
* ```php
* // Recommended: Get from container (autowired with build config); detection runs once
* $libc = $container->get(Libc::class);
*
* // Or read it from a build name
* $libc = Libc::tryFromBuildName('tool-x86_64-unknown-linux-musl.tar.gz');
* ```
*
* @internal
*/
enum Libc: string implements Factoriable
{
/**
* glibc: most Linux distributions.
*/
case Gnu = 'gnu';

/**
* musl: Alpine and a few other Linux distributions.
*/
case Musl = 'musl';

/**
* Android's own libc: it runs no glibc build, while musl builds are usually static.
*/
case Bionic = 'bionic';

/**
* The one libc the OS ships, like on Windows, macOS and BSD: builds do not differ by it.
*/
case System = 'system';

public static function create(Build $config, OperatingSystem $os): self
{
return match (true) {
\in_array(\strtolower((string) $config->os), ['alpine', 'unknown-musl'], true) => self::Musl,
$os === OperatingSystem::Android => self::Bionic,
$os !== OperatingSystem::Linux => self::System,
default => self::fromGlobals(),
};
}

public static function fromGlobals(): self
{
return self::detect(\PHP_OS_FAMILY, '/');
}

/**
* @param string $osFamily OS family in terms of {@see PHP_OS_FAMILY}.
* @param non-empty-string $root Root of the file system to look for the musl loader in.
*/
public static function detect(string $osFamily, string $root): self
{
if ($osFamily !== 'Linux') {
return self::System;
}

// The musl dynamic loader exists on musl systems only, and checking it runs nothing
$loaders = \glob(\rtrim($root, '/') . '/lib/ld-musl-*.so.1');

// `glob()` reports an error as `false`, which must not count as a found loader
return \is_array($loaders) && $loaders !== [] ? self::Musl : self::Gnu;
}

/**
* Reads the libc a build is linked against from its name.
*
* @return self::Gnu|self::Musl|null Null when the name tells nothing.
*/
public static function tryFromBuildName(string $name): ?self
{
if (\preg_match('/(?:\b|_)(musl(?:eabi(?:hf)?)?|alpine|gnu(?:eabi(?:hf)?)?|glibc)(?:\b|_)/i', $name, $matches) !== 1) {
return null;
}

$token = \strtolower($matches[1]);
return \str_starts_with($token, 'musl') || $token === 'alpine' ? self::Musl : self::Gnu;
}

/**
* Whether a host with this libc prefers the build.
*
* A build that names no libc counts as a glibc one.
*
* @param self|null $build Libc the build names.
*/
public function prefers(?self $build): bool
{
return match ($this) {
self::Gnu => $build !== self::Musl,
self::Musl, self::Bionic => $build === self::Musl,
self::System => true,
};
}
}
45 changes: 34 additions & 11 deletions src/Module/Common/OperatingSystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,35 @@ enum OperatingSystem: string implements Factoriable
case BSD = 'freebsd';
case Linux = 'linux';
case Windows = 'windows';
case Alpine = 'unknown-musl';
case Android = 'android';

private const ERROR_UNKNOWN_OS = 'Current OS `%s` may not be supported';

public static function create(Build $config): static
{
return self::tryFrom((string) $config->os) ?? self::fromGlobals();
return self::tryFrom((string) $config->os)
?? self::tryFromString((string) $config->os)
?? self::fromGlobals();
}

public static function fromGlobals(): self
{
return self::tryFromString(\PHP_OS_FAMILY) ?? throw new \OutOfRangeException(
\sprintf(self::ERROR_UNKNOWN_OS, \PHP_OS_FAMILY),
// The variable is set by the Android runtime and Termux
return self::fromHost(\PHP_OS_FAMILY, \getenv('ANDROID_ROOT') !== false);
}

/**
* @param string $osFamily OS family in terms of {@see PHP_OS_FAMILY}.
* @param bool $androidRuntime Whether the Android runtime is present.
*/
public static function fromHost(string $osFamily, bool $androidRuntime): self
{
$os = self::tryFromString($osFamily) ?? throw new \OutOfRangeException(
\sprintf(self::ERROR_UNKNOWN_OS, $osFamily),
);

// PHP reports Android as Linux
return $os === self::Linux && $androidRuntime ? self::Android : $os;
}

public static function tryFromString(string $name): ?self
Expand All @@ -50,22 +65,30 @@ public static function tryFromString(string $name): ?self
'windows', 'win32', 'win64' => self::Windows,
'bsd', 'freebsd' => self::BSD,
'darwin', 'macos' => self::Darwin,
'linux' => \str_contains(\PHP_OS, 'alpine')
? self::Alpine
: self::Linux,
'android' => self::Android,
// The libc is a separate trait, see {@see Libc}
'linux', 'alpine', 'unknown-musl' => self::Linux,
default => null,
};
}

public static function tryFromBuildName(string $name): ?self
{
return \preg_match(
// Android builds are also named after Linux, like `aarch64-linux-android`
if (\preg_match('/(?:\b|_)android(?:\b|_)/i', $name) === 1) {
return self::Android;
}

if (\preg_match(
'/(?:\b|_)(windows|linux|darwin|macos|alpine|bsd|freebsd|win32|win64)(?:\b|_)/i',
$name,
$matches,
) === 1
? self::tryFromString(\strtolower($matches[1]))
: null;
) === 1) {
return self::tryFromString(\strtolower($matches[1]));
}

// Only Linux builds name the libc alone, like `unknown-musl`
return Libc::tryFromBuildName($name) === Libc::Musl ? self::Linux : null;
}

public function getBinaryExtension(): string
Expand Down
17 changes: 17 additions & 0 deletions src/Module/Common/Pipeline/Interceptor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Internal\DLoad\Module\Common\Pipeline;

/**
* Father interface for all interceptors.
*
* Extend it with an interface that declares the interceptor method of a concrete pipeline.
*
* @template TInput
* @template-covariant TOutput
*
* @internal
*/
interface Interceptor {}
Loading
Loading