diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b3a66ad44..71da543a6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,6 +26,8 @@ jobs: os: macos-15-intel - target: x86_64-unknown-linux-gnu os: ubuntu-22.04 + - target: aarch64-unknown-linux-gnu + os: ubuntu-22.04-arm runs-on: ${{ matrix.os }} timeout-minutes: 45 steps: @@ -87,7 +89,7 @@ jobs: strategy: fail-fast: false matrix: - os: [macos-14, macos-15-intel, ubuntu-22.04] + os: [macos-14, macos-15-intel, ubuntu-22.04, ubuntu-22.04-arm] runs-on: ${{ matrix.os }} timeout-minutes: 15 env: diff --git a/README.md b/README.md index 3885554d2..0c73fb4b7 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ diffr main...HEAD -- src/ # merge-base comparison ## Installation -CLI and terminal UI (Apple Silicon macOS, Intel macOS, and x64 Linux): +CLI and terminal UI (Apple Silicon macOS, Intel macOS, and x64/ARM64 Linux): ```sh brew install devdotfast/tap/diffr diff --git a/diffr-ts/README.md b/diffr-ts/README.md index 1f88c0445..48a361b32 100644 --- a/diffr-ts/README.md +++ b/diffr-ts/README.md @@ -29,6 +29,9 @@ Keep these files in sync: ## Release +Linux ARM64 fetch support requires a release containing an `aarch64-unknown-linux-gnu` +archive and regenerated pins; the bundled 0.1.5 pins do not include it. + After tagging the matching Rust release and building with `cargo build --locked`, run from `diffr-ts`. New pins select the CLI-only release archives; older pins continue to use the full archives: diff --git a/diffr-ts/bin/fetch.mjs b/diffr-ts/bin/fetch.mjs index 79420b87f..6ce74518f 100755 --- a/diffr-ts/bin/fetch.mjs +++ b/diffr-ts/bin/fetch.mjs @@ -7,7 +7,7 @@ import { fileURLToPath } from "node:url"; import { parseArgs } from "node:util"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -const targets = { "darwin-arm64": "aarch64-apple-darwin", "darwin-x64": "x86_64-apple-darwin", "linux-x64": "x86_64-unknown-linux-gnu" }; +const targets = { "darwin-arm64": "aarch64-apple-darwin", "darwin-x64": "x86_64-apple-darwin", "linux-x64": "x86_64-unknown-linux-gnu", "linux-arm64": "aarch64-unknown-linux-gnu" }; try { await main(); diff --git a/diffr-ts/scripts/pin.mjs b/diffr-ts/scripts/pin.mjs index 70df95dfe..b858ec44c 100755 --- a/diffr-ts/scripts/pin.mjs +++ b/diffr-ts/scripts/pin.mjs @@ -7,7 +7,7 @@ import { fileURLToPath } from "node:url"; const here = dirname(fileURLToPath(import.meta.url)); const root = join(here, ".."); const version = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version; -const targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu"]; +const targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"]; const artifact = "diffr-cli"; const sha256 = {}; for (const target of targets) { diff --git a/diffr-ts/src/fetch.test.ts b/diffr-ts/src/fetch.test.ts index 6e78ae5ad..282902a9c 100644 --- a/diffr-ts/src/fetch.test.ts +++ b/diffr-ts/src/fetch.test.ts @@ -6,14 +6,15 @@ import { join } from "node:path"; const fetchScript = join(import.meta.dir, "..", "bin", "fetch.mjs"); const { version } = JSON.parse(readFileSync(join(import.meta.dir, "..", "package.json"), "utf8")); -const targets: Record = { "darwin-arm64": "aarch64-apple-darwin", "darwin-x64": "x86_64-apple-darwin", "linux-x64": "x86_64-unknown-linux-gnu" }; +const targets: Record = { "darwin-arm64": "aarch64-apple-darwin", "darwin-x64": "x86_64-apple-darwin", "linux-x64": "x86_64-unknown-linux-gnu", "linux-arm64": "aarch64-unknown-linux-gnu" }; const target = targets[`${process.platform}-${process.arch}`]; const dirs: string[] = []; afterEach(() => { for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true }); }); -function fixture(entry = "diffr", artifact?: string) { +function fixture(entry = "diffr", artifact?: string, platform = process.platform, arch = process.arch) { + const target = targets[`${platform}-${arch}`]; if (!target) throw new Error("Fetch tests require a supported release platform"); const dir = mkdtempSync(join(tmpdir(), "diffr-fetch-test-")); dirs.push(dir); @@ -25,9 +26,11 @@ function fixture(entry = "diffr", artifact?: string) { const pins = join(dir, "pins.json"); writeFileSync(pins, JSON.stringify({ version, artifact, sha256: { [target]: hash } })); const mock = join(dir, "mock.mjs"); - // Intercept only the network boundary, leaving the actual CLI and tar intact. + // Mock the platform and network, leaving the actual CLI and tar intact. writeFileSync(mock, ` import { readFileSync, writeFileSync } from "node:fs"; + Object.defineProperty(process, "platform", { value: ${JSON.stringify(platform)} }); + Object.defineProperty(process, "arch", { value: ${JSON.stringify(arch)} }); globalThis.fetch = async (url) => { writeFileSync(${JSON.stringify(join(dir, "requested"))}, url); if (process.env.FETCH_FAILURE) throw new Error("offline"); @@ -120,6 +123,20 @@ test("missing option values fail clearly", () => { expect(f.run(["--into"]).code).toBe(1); }); +test("Linux ARM64 downloads and stamps the aarch64 release", () => { + const f = fixture("diffr", "diffr-cli", "linux", "arm64"); + const result = f.run(["--required"]); + expect(result.code, result.err).toBe(0); + expect(readFileSync(join(f.dir, "requested"), "utf8")).toBe( + `https://github.com/devdotfast/diffr/releases/download/${version}/diffr-cli-${version}-aarch64-unknown-linux-gnu.tar.gz`, + ); + expect(readFileSync(join(f.into, "diffr"), "utf8")).toBe(f.content); + expect(JSON.parse(readFileSync(join(f.into, "diffr.stamp.json"), "utf8"))).toEqual({ + version, target: "aarch64-unknown-linux-gnu", + }); + expect(f.run(["--check"], true).code).toBe(0); +}); + test("CLI-only pins download the CLI archive", () => { const f = fixture("diffr", "diffr-cli"); const result = f.run(["--required"]); diff --git a/scripts/release.py b/scripts/release.py index fad9aa55f..ecc44e4c8 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -6,7 +6,7 @@ import subprocess import tarfile -TARGETS = ("aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu") +TARGETS = ("aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu") ROOT = Path(__file__).resolve().parent.parent @@ -55,9 +55,14 @@ def formula(version, output): end on_linux do - depends_on arch: :x86_64 - url "{url}-x86_64-unknown-linux-gnu.tar.gz" - sha256 "{checksums[archive_name(version, 'x86_64-unknown-linux-gnu')]}" + on_arm do + url "{url}-aarch64-unknown-linux-gnu.tar.gz" + sha256 "{checksums[archive_name(version, 'aarch64-unknown-linux-gnu')]}" + end + on_intel do + url "{url}-x86_64-unknown-linux-gnu.tar.gz" + sha256 "{checksums[archive_name(version, 'x86_64-unknown-linux-gnu')]}" + end end def install