cargoxx-pkgs registry skeleton
Empty package registry for cargoxx. flake.nix walks
recipes/<name>/versions/*.toml, exposes each (name, version) as
packages.<system>.{<name>_<safe_ver>, <name>}, and builds via
cargoxx.lib.${system}.buildCppPackage with pkgs.fetchgit.
.gitea/workflows/validate-pr.yml validates schema, refetches and verifies
source sha256, smoke-builds, pushes $out to the binary cache, and labels
auto-merge once the PR author is in maintainers.txt.
.gitea/workflows/auto-merge.yml merges via tea on the auto-merge label.
This commit is contained in:
19
.gitea/workflows/auto-merge.yml
Normal file
19
.gitea/workflows/auto-merge.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: auto-merge
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [labeled]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
merge:
|
||||||
|
if: github.event.label.name == 'auto-merge'
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- name: merge
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
run: |
|
||||||
|
tea pr merge \
|
||||||
|
--repo "${{ github.repository }}" \
|
||||||
|
--style squash \
|
||||||
|
"${{ github.event.pull_request.number }}"
|
||||||
109
.gitea/workflows/validate-pr.yml
Normal file
109
.gitea/workflows/validate-pr.yml
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
name: validate-pr
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'recipes/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# 1. Identify which recipes the PR touches.
|
||||||
|
- name: detect changed packages
|
||||||
|
id: changed
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
base="${{ github.event.pull_request.base.sha }}"
|
||||||
|
changed=$(git diff --name-only "$base"...HEAD -- 'recipes/' \
|
||||||
|
| awk -F/ '{print $2}' | sort -u)
|
||||||
|
if [[ -z "$changed" ]]; then
|
||||||
|
echo "no recipe changes — nothing to validate"
|
||||||
|
echo "packages=" >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "changed packages: $changed"
|
||||||
|
echo "packages=$changed" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# 2. Schema check (placeholder — refine once a JSON schema lives in-tree).
|
||||||
|
- name: schema check
|
||||||
|
if: steps.changed.outputs.packages != ''
|
||||||
|
run: |
|
||||||
|
for pkg in ${{ steps.changed.outputs.packages }}; do
|
||||||
|
for f in recipes/$pkg/versions/*.toml; do
|
||||||
|
echo "checking $f"
|
||||||
|
# TODO: validate against a schema definition once one exists.
|
||||||
|
grep -q '^schema = 1$' "$f" || { echo "missing schema = 1"; exit 1; }
|
||||||
|
grep -q '^name =' "$f" || { echo "missing name"; exit 1; }
|
||||||
|
grep -q '^version =' "$f" || { echo "missing version"; exit 1; }
|
||||||
|
grep -q '^\[source\]' "$f" || { echo "missing [source]"; exit 1; }
|
||||||
|
grep -q '^commit =' "$f" || { echo "missing source.commit"; exit 1; }
|
||||||
|
grep -q '^sha256 =' "$f" || { echo "missing source.sha256"; exit 1; }
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# 3. Source fixity — re-fetch and confirm the sha256 matches.
|
||||||
|
- name: source fixity
|
||||||
|
if: steps.changed.outputs.packages != ''
|
||||||
|
run: |
|
||||||
|
for pkg in ${{ steps.changed.outputs.packages }}; do
|
||||||
|
for f in recipes/$pkg/versions/*.toml; do
|
||||||
|
url=$(awk -F'"' '/^url =/ {print $2; exit}' "$f")
|
||||||
|
rev=$(awk -F'"' '/^commit =/ {print $2; exit}' "$f")
|
||||||
|
expected=$(awk -F'"' '/^sha256 =/ {print $2; exit}' "$f")
|
||||||
|
actual=$(nix flake prefetch --extra-experimental-features 'nix-command flakes' \
|
||||||
|
"git+${url}?rev=${rev}" --json | jq -r .hash)
|
||||||
|
if [[ "$actual" != "$expected" ]]; then
|
||||||
|
echo "sha256 mismatch in $f: expected $expected, got $actual"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# 4. Build smoke — every changed package must build.
|
||||||
|
- name: build smoke
|
||||||
|
if: steps.changed.outputs.packages != ''
|
||||||
|
run: |
|
||||||
|
for pkg in ${{ steps.changed.outputs.packages }}; do
|
||||||
|
nix build --extra-experimental-features 'nix-command flakes' \
|
||||||
|
.#${pkg} --no-link --print-out-paths
|
||||||
|
done
|
||||||
|
|
||||||
|
# 5. Cache push (only on the validated outputs, before merge).
|
||||||
|
- name: push to binary cache
|
||||||
|
if: steps.changed.outputs.packages != ''
|
||||||
|
env:
|
||||||
|
NIX_SECRET_KEY_FILE: ${{ secrets.NIX_CACHE_SECRET_KEY_FILE }}
|
||||||
|
CACHE_URL: ${{ vars.CARGOXX_CACHE_URL }}
|
||||||
|
run: |
|
||||||
|
for pkg in ${{ steps.changed.outputs.packages }}; do
|
||||||
|
nix copy --extra-experimental-features 'nix-command flakes' \
|
||||||
|
--to "${CACHE_URL}?secret-key=${NIX_SECRET_KEY_FILE}" \
|
||||||
|
.#${pkg}
|
||||||
|
done
|
||||||
|
|
||||||
|
# 6. Maintainer match.
|
||||||
|
- name: maintainer check
|
||||||
|
if: steps.changed.outputs.packages != ''
|
||||||
|
run: |
|
||||||
|
author='${{ github.event.pull_request.user.login }}'
|
||||||
|
for pkg in ${{ steps.changed.outputs.packages }}; do
|
||||||
|
list="recipes/$pkg/maintainers.txt"
|
||||||
|
if [[ ! -f "$list" ]]; then
|
||||||
|
echo "new package $pkg — maintainers.txt will be added by this PR"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if ! grep -E -q "^\s*${author}\s*(\#.*)?$" "$list"; then
|
||||||
|
echo "PR author '$author' is not in $list"
|
||||||
|
gh pr edit ${{ github.event.pull_request.number }} \
|
||||||
|
--add-label needs-human-review
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: label auto-merge
|
||||||
|
if: steps.changed.outputs.packages != ''
|
||||||
|
run: |
|
||||||
|
gh pr edit ${{ github.event.pull_request.number }} --add-label auto-merge
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/result
|
||||||
|
/result-*
|
||||||
|
flake.lock
|
||||||
70
README.md
Normal file
70
README.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# cargoxx-pkgs
|
||||||
|
|
||||||
|
Public package registry for [cargoxx](../cargoxx).
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
recipes/
|
||||||
|
<name>/
|
||||||
|
maintainers.txt # gitea usernames, one per line
|
||||||
|
meta.toml # description, homepage, license, repository
|
||||||
|
versions/
|
||||||
|
1.0.0.toml # one file per published version
|
||||||
|
1.0.1.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Version recipe schema
|
||||||
|
|
||||||
|
`recipes/<name>/versions/<v>.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
schema = 1
|
||||||
|
name = "<package-name>"
|
||||||
|
version = "<semver>"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
type = "git"
|
||||||
|
url = "https://gitea.example/<owner>/<repo>"
|
||||||
|
commit = "<40-char-commit>"
|
||||||
|
sha256 = "sha256-<base64>" # SRI form; from `nix flake prefetch`
|
||||||
|
|
||||||
|
[dependencies] # mirrors the package's Cargoxx.toml
|
||||||
|
fmt = "10.2"
|
||||||
|
otherlib = { version = "0.3", registry = "cargoxx" }
|
||||||
|
|
||||||
|
[lock]
|
||||||
|
nixpkgs_rev = "<40-char>"
|
||||||
|
flake_utils_rev = "<40-char>"
|
||||||
|
cargoxx_rev = "<40-char>" # cargoxx version that built this
|
||||||
|
|
||||||
|
[meta]
|
||||||
|
description = "..."
|
||||||
|
homepage = "https://..."
|
||||||
|
license = "MIT"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Maintainers
|
||||||
|
|
||||||
|
`recipes/<name>/maintainers.txt` lists Gitea usernames authorized to
|
||||||
|
publish new versions of `<name>` or edit `maintainers.txt` itself.
|
||||||
|
Comments start with `#`; blank lines are ignored.
|
||||||
|
|
||||||
|
## Publishing
|
||||||
|
|
||||||
|
Use `cargoxx publish` from a checked-out cargoxx project. The tool
|
||||||
|
opens a PR against this repository. CI validates the PR (source
|
||||||
|
fixity, build smoke, dependency closure, maintainer match) and
|
||||||
|
auto-merges when all checks pass.
|
||||||
|
|
||||||
|
## Building locally
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nix build .#<name>
|
||||||
|
```
|
||||||
|
|
||||||
|
Yields the same `$out` layout cargoxx libraries always produce —
|
||||||
|
`lib/cmake/<name>/<name>Config.cmake`, `lib/pkgconfig/<name>.pc`,
|
||||||
|
`lib/lib<name>.a`, `include/<name>/`. Consume from any CMake or
|
||||||
|
pkg-config project, or from another cargoxx project via
|
||||||
|
`{ version = "...", registry = "cargoxx" }`.
|
||||||
88
flake.nix
Normal file
88
flake.nix
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
{
|
||||||
|
description = "cargoxx package registry";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
|
||||||
|
# During local development we point at the sibling cargoxx checkout
|
||||||
|
# via an absolute `git+file://` URL. Once the registry lives on Gitea
|
||||||
|
# this becomes a Gitea URL pinned to a specific cargoxx revision —
|
||||||
|
# that pin, alongside `lock.cargoxx_rev` in each recipe, is what
|
||||||
|
# makes registry derivations deterministic across consumers (see
|
||||||
|
# docs/library-reuse-and-publish.md in the cargoxx repo).
|
||||||
|
cargoxx.url = "git+file:///home/mozart/cargoxx";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils, cargoxx }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
|
||||||
|
# version_safe: 1.2.3 → "1_2_3". The attr name `<pkg>_<safe>` is
|
||||||
|
# the canonical handle for a (name, version) pair; bare `<pkg>`
|
||||||
|
# aliases the highest-versioned entry.
|
||||||
|
sanitizeVersion = v:
|
||||||
|
builtins.replaceStrings ["." "-" "+"] ["_" "_" "_"] v;
|
||||||
|
|
||||||
|
# Read recipes/<name>/versions/<v>.toml → buildCppPackage'd drv.
|
||||||
|
mkPackage = recipeFile:
|
||||||
|
let r = builtins.fromTOML (builtins.readFile recipeFile);
|
||||||
|
in cargoxx.lib.${system}.buildCppPackage {
|
||||||
|
src = pkgs.fetchgit {
|
||||||
|
inherit (r.source) url;
|
||||||
|
rev = r.source.commit;
|
||||||
|
hash = r.source.sha256;
|
||||||
|
};
|
||||||
|
name = "${r.name}-${r.version}";
|
||||||
|
};
|
||||||
|
|
||||||
|
# builtins.readDir → attrset of name → type ("regular"/"directory").
|
||||||
|
# Yields [{ name, type }] entries, filtering on a predicate.
|
||||||
|
dirEntries = path: predicate:
|
||||||
|
if builtins.pathExists path
|
||||||
|
then pkgs.lib.filterAttrs (n: t: predicate n t) (builtins.readDir path)
|
||||||
|
else {};
|
||||||
|
|
||||||
|
# All package directories under recipes/.
|
||||||
|
packageNames = builtins.attrNames
|
||||||
|
(dirEntries ./recipes (_: t: t == "directory"));
|
||||||
|
|
||||||
|
# For one package: enumerate its versions, returning an attrset
|
||||||
|
# { "<pkg>_<safe>" = <drv>; "<pkg>" = <highest-version drv>; }.
|
||||||
|
versionsFor = pkgName:
|
||||||
|
let
|
||||||
|
versionsDir = ./recipes + "/${pkgName}/versions";
|
||||||
|
files = builtins.attrNames
|
||||||
|
(dirEntries versionsDir (n: t:
|
||||||
|
t == "regular" && pkgs.lib.hasSuffix ".toml" n));
|
||||||
|
versions = map (f:
|
||||||
|
let
|
||||||
|
version = pkgs.lib.removeSuffix ".toml" f;
|
||||||
|
attr = "${pkgName}_${sanitizeVersion version}";
|
||||||
|
drv = mkPackage (versionsDir + "/${f}");
|
||||||
|
in { inherit version attr drv; }
|
||||||
|
) files;
|
||||||
|
byVersion = pkgs.lib.listToAttrs
|
||||||
|
(map (v: { name = v.attr; value = v.drv; }) versions);
|
||||||
|
highest = pkgs.lib.last
|
||||||
|
(pkgs.lib.sort (a: b:
|
||||||
|
builtins.compareVersions a.version b.version < 0) versions);
|
||||||
|
latestAlias =
|
||||||
|
if versions == [] then {}
|
||||||
|
else { ${pkgName} = highest.drv; };
|
||||||
|
in byVersion // latestAlias;
|
||||||
|
|
||||||
|
packagesAttrs = pkgs.lib.foldl' (acc: n:
|
||||||
|
acc // (versionsFor n)) {} packageNames;
|
||||||
|
in {
|
||||||
|
packages = packagesAttrs;
|
||||||
|
|
||||||
|
# Bare `nix flake check` should pass with zero recipes.
|
||||||
|
checks = {
|
||||||
|
schema-ok = pkgs.runCommand "registry-schema-ok" {} ''
|
||||||
|
touch $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
0
recipes/.gitkeep
Normal file
0
recipes/.gitkeep
Normal file
Reference in New Issue
Block a user