Releases carry their source: oxy-VERSION-src.tar.gz, reproducible; haikuports recipes for oxy and oh written from release source tarballs (refs #157) 045340293dc7
- Author
- Andy McCall @andymccall <[email protected]>
- via
- agent
agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key 8d770af21b7c
- When
- · 2026-10-29 11:47 UTC
- Signature
- ◈ sealed by agent:claude@andymccall
- Hash
045340293dc769e20918184984a2a99a12c9bd4ce37ad58ad1b0fa7b166ff0a6- Depends on
- 25c6adfaa559 2b668d92b066 8fd78eb52ad2 a42f9fb47e37 b66a5c47cc61 e4595038502a
- Depended on by
- 34c777f8893a cea2e2f860c5 16aa9e03d85b e980087ed0b1
- In bookmarks
- main
- In tags
- v0.10.0 v0.11.0 v0.9.0
- Edits
- 31 atoms, 3 tree operations, 8 files
Browse the tree at this change
removed packaging/haikuports/dev-vcs +0 −0
No line changes.
removed packaging/haikuports/dev-vcs/oxy +0 −0
No line changes.
removed packaging/haikuports/dev-vcs/oxy/oxy-0.8.0.recipe +0 −0
| … | 992 unchanged lines |
modified .flow/pipeline.kdl +3 −0
| … | 129 unchanged lines |
||
| 130 | 130 | artifact "oxy_arm64.deb" { |
|
| 131 | 131 | path "/src/target/dist-flat/oxy_arm64.deb" |
|
| 132 | 132 | } |
|
| 133 | + | artifact "oxy-src.tar.gz" { |
|
| 134 | + | path "/src/target/dist-flat/oxy-src.tar.gz" |
|
| 135 | + | } |
|
| 133 | 136 | artifact "oxy_riscv64.deb" { |
|
| 134 | 137 | path "/src/target/dist-flat/oxy_riscv64.deb" |
|
| 135 | 138 | } |
|
| … | 5 unchanged lines |
modified CHANGELOG.md +1 −0
| … | 1 unchanged lines |
||
| 2 | 2 | ||
| 3 | 3 | ## Unreleased |
|
| 4 | 4 | ||
| 5 | + | - **Releases carry their source (#157).** Each release now has `oxy-VERSION-src.tar.gz`, the source as released, reproducible (sorted, fixed times and owners, gzip without a timestamp), listed in SHA256SUMS, for packagers to fetch at a fixed checksum. `packaging/haikuports/make-recipe.py oxy|oh TARBALL VERSION` writes the haikuports recipe from it (oh's from OxygenHub's source tarball); the 0.8.0 recipe, which used the on-demand archive, is gone. |
|
| 5 | 6 | - **Added: a haikuports recipe for oxy (#156).** `packaging/haikuports/` holds the recipe that builds oxy for HaikuDepot from the release source and every crate in `Cargo.lock`, and the script that writes it for a new release; the 0.8.0 recipe builds with haikuporter on Haiku R1/beta6. |
|
| 6 | 7 | - **Fixed: an expired checkout token no longer hides your sign-in (#155).** A checkout's own token for a remote (`remote.NAME.token`) is used first, so when it expired every fetch, pull and push answered 401 although `oxy auth login` had a good sign-in for the same hub. A checkout token the hub no longer knows (401) now falls back to that sign-in, with a warning naming `oxy config --unset remote.NAME.token` to drop the stale one; never to another checkout's token, and not when the token is refused for permission (403, 404), so a narrowly scoped token is never swapped for a wider sign-in. When both are refused, the error says to sign in again and unset it. |
|
| 7 | 8 | - **Added: artifacts (Cylinder, step C1, #108).** `oxy artifact pack DIR --kind archive|package|container|runner-image|report [--arch --os --entrypoint --env K=V --label K=V]` packs a directory into an artifact: each file an ordinary blob of the repository's store, so files and chunks it holds already cost nothing, under a tree and an artifact encoding in canonical CBOR, named by the BLAKE3 of that encoding (docs/cylinder.md §4.1). `oxy artifact list | show NAME | unpack NAME DEST | verify NAME` (any unique prefix of six characters or more; `--json` on list, show and pack), and `oxy gc` keeps every artifact's blobs. The new `cylinder` crate holds the model. |
|
| … | 301 unchanged lines |
modified packaging/haikuports/README.md +22 −17
| 1 | - | # oxy on HaikuDepot |
|
| 1 | + | # oxy and oh on HaikuDepot |
|
| 2 | 2 | ||
| 3 | - | `dev-vcs/oxy/oxy-VERSION.recipe` is a [haikuports](https://github.com/haikuports/haikuports) |
|
| 4 | - | recipe for oxy (oxygen#156). It builds the release's source archive from |
|
| 5 | - | OxygenHub offline, with every crates.io crate from `Cargo.lock` as a source of |
|
| 6 | - | its own, as haikuports' other Rust recipes (ripgrep) do. |
|
| 3 | + | [haikuports](https://github.com/haikuports/haikuports) recipes for oxy |
|
| 4 | + | (`dev-vcs/oxy`) and OxygenHub's `oh` (`dev-vcs/oh`) are written by |
|
| 5 | + | `make-recipe.py` from a release's source tarball (oxygen#156, #157). |
|
| 7 | 6 | ||
| 8 | - | For a new release: |
|
| 7 | + | Each release's dist job attaches `NAME-VERSION-src.tar.gz`: the source as |
|
| 8 | + | released, reproducible (sorted, fixed times and owners, gzip without a |
|
| 9 | + | timestamp). oh's holds the oxygenhub tree with its subrepos inside, so it |
|
| 10 | + | builds alone. The recipe fetches it from the release's stable address, |
|
| 11 | + | `https://oxygenhub.io/andymccall/REPO/releases/download/vVERSION/NAME-VERSION-src.tar.gz`, |
|
| 12 | + | and every crates.io crate in its `Cargo.lock` as a source of its own with its |
|
| 13 | + | checksum, building offline as haikuports' other Rust recipes (ripgrep) do. |
|
| 9 | 14 | ||
| 15 | + | For a release (oxy shown; oh is the same with `oh` and `oxygenhub`): |
|
| 16 | + | ||
| 10 | 17 | ```sh |
|
| 11 | - | curl -o oxygen-vX.Y.Z.tar.zst 'https://oxygenhub.io/andymccall/oxygen/archive?ref=vX.Y.Z&format=tar.zst' |
|
| 12 | - | tar --zstd -xf oxygen-vX.Y.Z.tar.zst |
|
| 13 | - | python3 packaging/haikuports/make-recipe.py oxygen-vX.Y.Z/Cargo.lock X.Y.Z oxygen-vX.Y.Z.tar.zst \ |
|
| 14 | - | > packaging/haikuports/dev-vcs/oxy/oxy-X.Y.Z.recipe |
|
| 18 | + | curl -LO https://oxygenhub.io/andymccall/oxygen/releases/download/vX.Y.Z/oxy-X.Y.Z-src.tar.gz |
|
| 19 | + | python3 packaging/haikuports/make-recipe.py oxy oxy-X.Y.Z-src.tar.gz X.Y.Z > oxy-X.Y.Z.recipe |
|
| 15 | 20 | ``` |
|
| 16 | 21 | ||
| 17 | - | Test it in a Haiku R1/beta6 machine with haikuports checked out and |
|
| 18 | - | `haikuporter` installed: copy the recipe to `haikuports/dev-vcs/oxy/` and run |
|
| 19 | - | `haikuporter --no-source-packages -j1 -y oxy`. The 0.8.0 recipe built |
|
| 20 | - | `oxy-0.8.0-1-x86_64.hpkg` that way (2026-10-28), whose `oxy --version` says |
|
| 21 | - | `oxy 0.8.0 (v0.8.0)`. |
|
| 22 | + | Test it on Haiku R1/beta6 with haikuports checked out and `haikuporter` |
|
| 23 | + | installed: copy the recipe to `haikuports/dev-vcs/oxy/` and run |
|
| 24 | + | `haikuporter --no-source-packages -j1 -y oxy`. Submit it as a pull request |
|
| 25 | + | to haikuports (`oxy: new recipe`, then `oxy: bump to X.Y.Z`). |
|
| 22 | 26 | ||
| 23 | 27 | Haiku needs the system's zstd (`ZSTD_SYS_USE_PKG_CONFIG=1`: the bundled |
|
| 24 | - | zstd's hidden symbols break the link), which the recipe sets. Submitting the |
|
| 25 | - | recipe to haikuports is a pull request there. |
|
| 28 | + | zstd's hidden symbols break the link), which the recipe sets. oh's tests |
|
| 29 | + | pull in the hub, whose pipeline parser needs a newer Rust than HaikuPorts' |
|
| 30 | + | 1.94, so its recipe tests `hub-api` only. |
modified packaging/haikuports/make-recipe.py +80 −40
| 1 | 1 | #!/usr/bin/env python3 |
|
| 2 | - | """Write a haikuports recipe for oxy from a release's Cargo.lock: the source |
|
| 3 | - | archive from OxygenHub, and every crates.io crate as its own source with its |
|
| 4 | - | checksum, vendored offline in BUILD() as haikuports' Rust recipes do.""" |
|
| 5 | - | import hashlib, sys, tomllib |
|
| 2 | + | """Write a haikuports recipe for oxy or oh (oxygen#156, #157) from a release's |
|
| 3 | + | source tarball, NAME-VERSION-src.tar.gz, as the release job attaches it: the |
|
| 4 | + | tarball itself from the release's stable download address, and every |
|
| 5 | + | crates.io crate in its Cargo.lock as a source of its own with its checksum, |
|
| 6 | + | vendored offline in BUILD() as haikuports' Rust recipes (ripgrep) do. |
|
| 6 | 7 | ||
| 7 | - | lock_path, version, archive = sys.argv[1], sys.argv[2], sys.argv[3] |
|
| 8 | - | lock = tomllib.load(open(lock_path, "rb")) |
|
| 9 | - | crates = sorted((p["name"], p["version"], p["checksum"]) for p in lock["package"] if p.get("source", "").startswith("registry+")) |
|
| 10 | - | sha = hashlib.sha256(open(archive, "rb").read()).hexdigest() |
|
| 8 | + | make-recipe.py oxy|oh SRC_TARBALL VERSION > NAME-VERSION.recipe |
|
| 9 | + | """ |
|
| 10 | + | import hashlib |
|
| 11 | + | import sys |
|
| 12 | + | import tarfile |
|
| 13 | + | import tomllib |
|
| 11 | 14 | ||
| 15 | + | PORTS = { |
|
| 16 | + | "oxy": { |
|
| 17 | + | "repo": "oxygen", |
|
| 18 | + | "package": "oxy", |
|
| 19 | + | "summary": "A version control system of signed, commuting changes, with Git interop", |
|
| 20 | + | "description": [ |
|
| 21 | + | "oxy is the command line of Oxygen, a version control system built on", |
|
| 22 | + | "patch theory: changes commute, conflicts are recorded rather than blocking,", |
|
| 23 | + | "every change can be signed, and a trust policy says who may move what. It", |
|
| 24 | + | "imports and exports Git history, clones from OxygenHub and other Oxygen", |
|
| 25 | + | "hosts, and keeps repositories on disk or in S3, GCS or Azure buckets.", |
|
| 26 | + | ], |
|
| 27 | + | "docs": "README.md LICENSE*", |
|
| 28 | + | "test": "oxy-core", |
|
| 29 | + | }, |
|
| 30 | + | "oh": { |
|
| 31 | + | "repo": "oxygenhub", |
|
| 32 | + | "package": "oh", |
|
| 33 | + | "summary": "The command line of OxygenHub, the forge for Oxygen repositories", |
|
| 34 | + | "description": [ |
|
| 35 | + | "oh talks to an OxygenHub (such as https://oxygenhub.io): issues, proposals", |
|
| 36 | + | "and their reviews, releases, Flow runs, their logs and reports, and checks,", |
|
| 37 | + | "from the terminal. It works alongside oxy, the Oxygen version control", |
|
| 38 | + | "client.", |
|
| 39 | + | ], |
|
| 40 | + | "docs": "README.md LICENSE docs/oh.md", |
|
| 41 | + | "test": "hub-api", |
|
| 42 | + | }, |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | name, tarball, version = sys.argv[1], sys.argv[2], sys.argv[3] |
|
| 46 | + | port = PORTS[name] |
|
| 47 | + | prefix = f"{port['repo']}-{version}" |
|
| 48 | + | with tarfile.open(tarball) as t: |
|
| 49 | + | lock = tomllib.load(t.extractfile(f"{prefix}/Cargo.lock")) |
|
| 50 | + | crates = sorted( |
|
| 51 | + | (p["name"], p["version"], p["checksum"]) |
|
| 52 | + | for p in lock["package"] |
|
| 53 | + | if p.get("source", "").startswith("registry+") |
|
| 54 | + | ) |
|
| 55 | + | sha = hashlib.sha256(open(tarball, "rb").read()).hexdigest() |
|
| 56 | + | ||
| 12 | 57 | out = [] |
|
| 13 | 58 | w = out.append |
|
| 14 | - | w('SUMMARY="A version control system of signed, commuting changes, with Git interop"') |
|
| 15 | - | w('DESCRIPTION="oxy is the command line of Oxygen, a version control system built on \\') |
|
| 16 | - | w('patch theory: changes commute, conflicts are recorded rather than blocking, \\') |
|
| 17 | - | w('every change can be signed, and a trust policy says who may move what. It \\') |
|
| 18 | - | w('imports and exports Git history, clones from OxygenHub and other Oxygen \\') |
|
| 19 | - | w('hosts, and keeps repositories on disk or in S3, GCS or Azure buckets."') |
|
| 20 | - | w('HOMEPAGE="https://oxygenhub.io/andymccall/oxygen"') |
|
| 59 | + | w(f'SUMMARY="{port["summary"]}"') |
|
| 60 | + | w('DESCRIPTION="' + " \\\n".join(port["description"]) + '"') |
|
| 61 | + | w(f'HOMEPAGE="https://oxygenhub.io/andymccall/{port["repo"]}"') |
|
| 21 | 62 | w('COPYRIGHT="2025-2026 Andy McCall"') |
|
| 22 | 63 | w('LICENSE="MIT\n\tApache v2"') |
|
| 23 | 64 | w('REVISION="1"') |
|
| 24 | - | w(f'SOURCE_URI="$HOMEPAGE/archive?ref=v$portVersion&format=tar.zst"') |
|
| 25 | - | w(f'SOURCE_FILENAME="oxygen-v$portVersion.tar.zst"') |
|
| 65 | + | w(f'SOURCE_URI="$HOMEPAGE/releases/download/v$portVersion/{name}-$portVersion-src.tar.gz"') |
|
| 26 | 66 | w(f'CHECKSUM_SHA256="{sha}"') |
|
| 27 | - | w('SOURCE_DIR="oxygen-v$portVersion"') |
|
| 28 | - | for i, (name, ver, ck) in enumerate(crates, start=2): |
|
| 29 | - | w(f'SOURCE_URI_{i}="https://static.crates.io/crates/{name}/{name}-{ver}.crate"') |
|
| 67 | + | w(f'SOURCE_DIR="{port["repo"]}-$portVersion"') |
|
| 68 | + | for i, (crate, ver, ck) in enumerate(crates, start=2): |
|
| 69 | + | w(f'SOURCE_URI_{i}="https://static.crates.io/crates/{crate}/{crate}-{ver}.crate"') |
|
| 30 | 70 | w(f'CHECKSUM_SHA256_{i}="{ck}"') |
|
| 31 | - | w(f'SOURCE_FILENAME_{i}="{name}-{ver}.tar.gz"') |
|
| 71 | + | w(f'SOURCE_FILENAME_{i}="{crate}-{ver}.tar.gz"') |
|
| 32 | 72 | last = len(crates) + 1 |
|
| 33 | - | w(''' |
|
| 73 | + | w(f''' |
|
| 34 | 74 | ARCHITECTURES="!x86_gcc2 ?x86 x86_64" |
|
| 35 | 75 | SECONDARY_ARCHITECTURES="x86" |
|
| 36 | 76 | ||
| 37 | 77 | PROVIDES=" |
|
| 38 | - | oxy$secondaryArchSuffix = $portVersion |
|
| 39 | - | cmd:oxy |
|
| 78 | + | {name}$secondaryArchSuffix = $portVersion |
|
| 79 | + | cmd:{name} |
|
| 40 | 80 | " |
|
| 41 | 81 | REQUIRES=" |
|
| 42 | 82 | haiku$secondaryArchSuffix |
|
| … | 1 unchanged lines |
||
| 44 | 84 | " |
|
| 45 | 85 | ||
| 46 | 86 | BUILD_REQUIRES=" |
|
| 47 | - | haiku${secondaryArchSuffix}_devel |
|
| 87 | + | haiku${{secondaryArchSuffix}}_devel |
|
| 48 | 88 | devel:libzstd$secondaryArchSuffix |
|
| 49 | 89 | " |
|
| 50 | 90 | BUILD_PREREQUIRES=" |
|
| … | 3 unchanged lines |
||
| 54 | 94 | " |
|
| 55 | 95 | ||
| 56 | 96 | BUILD() |
|
| 57 | - | { |
|
| 97 | + | {{ |
|
| 58 | 98 | export CARGO_HOME=$sourceDir/../cargo |
|
| 59 | 99 | vendor=$CARGO_HOME/haiku |
|
| 60 | 100 | mkdir -p "$vendor" |
|
| 61 | - | for i in $(seq 2 ''' + str(last) + '''); do |
|
| 101 | + | for i in $(seq 2 {last}); do |
|
| 62 | 102 | eval "srcDir=\\$sourceDir$i" |
|
| 63 | 103 | eval "sha256sum=\\$CHECKSUM_SHA256_$i" |
|
| 64 | 104 | set -- "$srcDir"/* |
|
| 65 | 105 | ln -sf "$1" "$vendor" |
|
| 66 | - | cat <<-EOF >"$vendor/${1##*/}/.cargo-checksum.json" |
|
| 67 | - | { |
|
| 106 | + | cat <<-EOF >"$vendor/${{1##*/}}/.cargo-checksum.json" |
|
| 107 | + | {{ |
|
| 68 | 108 | "package": "$sha256sum", |
|
| 69 | - | "files": {} |
|
| 70 | - | } |
|
| 109 | + | "files": {{}} |
|
| 110 | + | }} |
|
| 71 | 111 | EOF |
|
| 72 | 112 | done |
|
| 73 | 113 | ||
| … | 8 unchanged lines |
||
| 82 | 122 | # The bundled zstd's hidden symbols break the link on Haiku: use the system's. |
|
| 83 | 123 | export ZSTD_SYS_USE_PKG_CONFIG=1 |
|
| 84 | 124 | export OXY_BUILD=v$portVersion |
|
| 85 | - | cargo build --release --frozen -p oxy |
|
| 86 | - | } |
|
| 125 | + | cargo build --release --frozen -p {port["package"]} |
|
| 126 | + | }} |
|
| 87 | 127 | ||
| 88 | 128 | INSTALL() |
|
| 89 | - | { |
|
| 129 | + | {{ |
|
| 90 | 130 | install -v -m 755 -d "$prefix"/bin "$docDir" |
|
| 91 | - | install -v -m 755 ./target/release/oxy "$prefix"/bin |
|
| 92 | - | install -v -m 644 README.md LICENSE* "$docDir" |
|
| 93 | - | } |
|
| 131 | + | install -v -m 755 ./target/release/{name} "$prefix"/bin |
|
| 132 | + | install -v -m 644 {port["docs"]} "$docDir" |
|
| 133 | + | }} |
|
| 94 | 134 | ||
| 95 | 135 | TEST() |
|
| 96 | - | { |
|
| 136 | + | {{ |
|
| 97 | 137 | export CARGO_HOME=$sourceDir/../cargo |
|
| 98 | 138 | export ZSTD_SYS_USE_PKG_CONFIG=1 |
|
| 99 | - | cargo test --release --frozen -p oxy-core |
|
| 100 | - | }''') |
|
| 139 | + | cargo test --release --frozen -p {port["test"]} |
|
| 140 | + | }}''') |
|
| 101 | 141 | print("\n".join(out)) |
modified scripts/dist.sh +11 −0
| … | 126 unchanged lines |
||
| 127 | 127 | fi |
|
| 128 | 128 | done |
|
| 129 | 129 | ||
| 130 | + | # The source as released (oxygen#157): the oxygen tree, |
|
| 131 | + | # under oxygen-VERSION/, for packagers (haikuports) to fetch from the |
|
| 132 | + | # release at a fixed checksum. Reproducible: sorted, fixed times and |
|
| 133 | + | # owners, gzip without a timestamp. |
|
| 134 | + | if [ -z "${ONLY:-}" ] || [[ src == $ONLY ]]; then |
|
| 135 | + | echo "== src" |
|
| 136 | + | tar --sort=name --mtime='1970-01-01 00:00:00Z' --owner=0 --group=0 --numeric-owner \ |
|
| 137 | + | --mode='u+rwX,go+rX,go-w' --exclude=./target --exclude=.oxy \ |
|
| 138 | + | --transform "s|^\.|oxygen-$VERSION|" -cf - . | gzip -9n >"$OUT/$BIN-$VERSION-src.tar.gz" |
|
| 139 | + | fi |
|
| 140 | + | ||
| 130 | 141 | rm -rf "$OUT/stage" |
|
| 131 | 142 | (cd "$OUT" && shopt -s nullglob && sha256sum -- *.tar.gz *.zip *.deb >SHA256SUMS) |
|
| 132 | 143 | # DIST_FLAT=DIR: the same files again under names without the version, for |
|
| … | 12 unchanged lines |