From b5de70a39804e5e8bb436b1efb028c8ef7efe1a3 Mon Sep 17 00:00:00 2001 From: Tom Charnock Date: Fri, 2 Oct 2026 21:49:06 +0200 Subject: [PATCH 1/2] Add a Nix flake packaging lc and astra, with a NixOS module packages.default builds lc's wheel from the flake source and wraps lc, astra and git-annex's four executables in `uv tool run --from `, so uv installs Python and lc's dependencies at first run, as with uv tool install. No uv.lock is tracked: dependencies resolve with --exclude-newer set to the commit's date, so one commit resolves one set of them. The package also provides uv and git and puts them first on the wrappers' PATH, so lc, git add and uv add use the same uv and git. packages.lc is an alias for `nix run ...#lc`. nixosModules.default adds programs.lightcone.enable. It installs the package, sets programs.git.package to the package's git, enables nix-ld, and sets python-preference = "only-managed" in /etc/uv/uv.toml. NixOS cannot run the interpreters and wheels uv downloads without nix-ld, and a Nix Python on PATH would otherwise be picked for project environments. Evaluation fails if the package's uv is below 0.12; `.override { uv = ...; }` changes it. hatch-vcs cannot see tags inside a Nix build, so the flake versions itself .dev0+g.nix, with derived from lastRelease. The dev and +g parts keep lc's rerun pin on the exact commit; .nix marks the dev count as unknown rather than zero. lastRelease must be set to the new tag after every release; nix.yml fails until it is. nix.yml also builds the package on x86_64-linux, aarch64-linux and aarch64-darwin and runs lc init in a new directory. Checked on NixOS: nix flake check --all-systems passes, the module evaluates with the default package and with a uv override, and the getting-started guide runs end to end in nix shell, including the fresh-clone check. Signed-off-by: Tom Charnock --- .github/workflows/nix.yml | 65 ++++++++++++++++++++ flake.lock | 27 +++++++++ flake.nix | 122 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 .github/workflows/nix.yml create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 00000000..8e2134fd --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,65 @@ +name: Nix + +on: + push: + branches: [main] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + +jobs: + build: + if: github.event_name == 'push' || github.event.pull_request.draft == false + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest] + steps: + - uses: actions/checkout@v4 + + - uses: cachix/install-nix-action@v31 + + - name: Build + run: nix build --print-build-logs + + - name: Smoke test + run: | + ./result/bin/lc --version + ./result/bin/astra --version + ./result/bin/git-annex version + cd "$RUNNER_TEMP" + "$GITHUB_WORKSPACE/result/bin/lc" init demo + "$GITHUB_WORKSPACE/result/bin/lc" init demo --check + + version: + if: github.event_name == 'push' || github.event.pull_request.draft == false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: cachix/install-nix-action@v31 + + - name: Set up uv + uses: astral-sh/setup-uv@v6 + + - name: Flake evaluates on every system + run: nix flake check --all-systems + + - name: lastRelease is the latest tag + run: | + expected=$(uvx --from setuptools-scm python -m setuptools_scm) + name=$(nix eval --raw .#packages.x86_64-linux.default.name) + actual=${name#lightcone-} + echo "hatch-vcs: $expected" + echo "nix: $actual" + if [ "${expected%%.dev*}" != "${actual%%.dev*}" ]; then + tag=$(git describe --tags --abbrev=0) + echo "::error file=flake.nix::set lastRelease = \"${tag#v}\";" + exit 1 + fi diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..bd3498c3 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1790652569, + "narHash": "sha256-641r7xrlSOHYoktL9/aArPIYNgZJ9eM5URzSEOtPJDY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b6c8664de9b6cc07fe5666a29f91884ba81197c4", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..207e18e7 --- /dev/null +++ b/flake.nix @@ -0,0 +1,122 @@ +{ + description = "Lightcone Research: lc and astra"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + outputs = + { self, nixpkgs }: + let + inherit (nixpkgs) lib; + forAllSystems = lib.genAttrs [ + "x86_64-linux" + "aarch64-linux" + "aarch64-darwin" + ]; + lastRelease = "0.5.0rc5"; + nextVersion = + let + parts = builtins.match "(.*[^0-9])([0-9]+)" lastRelease; + in + "${builtins.elemAt parts 0}${toString (lib.toInt (builtins.elemAt parts 1) + 1)}"; + commit = if self ? rev then builtins.substring 0 9 self.rev else self.dirtyShortRev or "unknown"; + version = "${nextVersion}.dev0+g${commit}.nix"; + excludeNewer = + let + at = start: length: builtins.substring start length self.lastModifiedDate; + in + "${at 0 4}-${at 4 2}-${at 6 2}T${at 8 2}:${at 10 2}:${at 12 2}Z"; + in + { + packages = forAllSystems ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + in + rec { + default = pkgs.callPackage ( + { + lib, + python3Packages, + runCommand, + makeWrapper, + uv, + git, + }: + let + wheel = python3Packages.buildPythonPackage { + pname = "lightcone-cli"; + inherit version; + pyproject = true; + src = ./.; + build-system = [ + python3Packages.hatchling + python3Packages.hatch-vcs + ]; + env.SETUPTOOLS_SCM_PRETEND_VERSION = version; + dontCheckRuntimeDeps = true; + }; + in + runCommand "lightcone-${version}" + { + nativeBuildInputs = [ makeWrapper ]; + passthru = { inherit uv git; }; + meta.mainProgram = "lc"; + } + '' + mkdir -p $out/bin + ln -s ${uv}/bin/* ${git}/bin/* $out/bin/ + whl=$(echo ${wheel.dist}/*.whl) + for exe in lc astra git-annex git-annex-shell git-remote-annex git-remote-tor-annex; do + makeWrapper ${lib.getExe uv} $out/bin/$exe \ + --prefix PATH : $out/bin \ + --add-flags "tool run --quiet --exclude-newer ${excludeNewer} --from $whl $exe" + done + '' + ) { }; + lc = default; + } + ); + + nixosModules.default = + { + config, + lib, + pkgs, + ... + }: + let + cfg = config.programs.lightcone; + in + { + options.programs.lightcone = { + enable = lib.mkEnableOption "the Lightcone Research tools (lc, astra)"; + package = lib.mkOption { + type = lib.types.package; + default = self.packages.${pkgs.stdenv.hostPlatform.system}.default; + defaultText = lib.literalExpression "lightcone.packages.\${system}.default"; + description = "The package providing lc, astra, git-annex, uv and git."; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = lib.versionAtLeast cfg.package.uv.version "0.12"; + message = "programs.lightcone needs uv 0.12 or later, got ${cfg.package.uv.version}."; + } + ]; + programs.nix-ld.enable = true; + programs.git = { + enable = true; + package = lib.mkDefault cfg.package.git; + }; + environment = { + systemPackages = [ cfg.package ]; + etc."uv/uv.toml".text = lib.mkDefault '' + python-preference = "only-managed" + ''; + }; + }; + }; + }; +} From f706cfbf77b329b0e6b01485e4c4b88993b64224 Mon Sep 17 00:00:00 2001 From: Tom Charnock Date: Wed, 7 Oct 2026 13:12:46 +0200 Subject: [PATCH 2/2] Run the flake's tools on uv-managed Python and expose lc's wheel Signed-off-by: Tom Charnock --- flake.nix | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 207e18e7..8677f6d7 100644 --- a/flake.nix +++ b/flake.nix @@ -59,7 +59,10 @@ runCommand "lightcone-${version}" { nativeBuildInputs = [ makeWrapper ]; - passthru = { inherit uv git; }; + passthru = { + inherit uv git; + dist = wheel.dist; + }; meta.mainProgram = "lc"; } '' @@ -69,7 +72,7 @@ for exe in lc astra git-annex git-annex-shell git-remote-annex git-remote-tor-annex; do makeWrapper ${lib.getExe uv} $out/bin/$exe \ --prefix PATH : $out/bin \ - --add-flags "tool run --quiet --exclude-newer ${excludeNewer} --from $whl $exe" + --add-flags "tool run --quiet --managed-python --exclude-newer ${excludeNewer} --from $whl $exe" done '' ) { };