OXYGENHUB

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