diff --git a/packages/codeaf/default.nix b/packages/codeaf/default.nix index 291b4f4..9613f2c 100644 --- a/packages/codeaf/default.nix +++ b/packages/codeaf/default.nix @@ -2,4 +2,8 @@ pkgs, ... }: -pkgs.callPackage ./package.nix { } +pkgs.callPackage ./package.nix { + # Not a package of its own: codeaf is the only consumer, and codeaf is the one + # that knows which furrow version it pinned. See ./furrow.nix. + furrow = pkgs.callPackage ./furrow.nix { }; +} diff --git a/packages/codeaf/furrow.nix b/packages/codeaf/furrow.nix new file mode 100644 index 0000000..81f7bd5 --- /dev/null +++ b/packages/codeaf/furrow.nix @@ -0,0 +1,54 @@ +{ + lib, + rustPlatform, + fetchFromGitHub, +}: + +# furrow is the copy-on-write workspace snapper codeaf drives. codeaf ships a +# copy of it inside the go binary: the release asset from GitHub, glibc-dynamic +# and untouched by anything in this build, so the file codeaf writes out to +# ~/.codeaf/bin/furrow- cannot start here — NixOS answers "Could not +# start dynamically linked executable" and names stub-ld. Building furrow here +# instead gives the same version as an ordinary Nix binary, and codeaf is told +# to use it through CODEAF_FURROW, which internal/furrow reads before it ever +# looks at the embedded copy. See ./package.nix. +# +# The version here has to be the one codeaf pinned in +# internal/furrowbin/pin.json: the decoders in codeaf were written for that +# furrow. Bump the two together. +rustPlatform.buildRustPackage rec { + pname = "furrow"; + version = "0.1.0"; + + src = fetchFromGitHub { + owner = "Agent-Field"; + repo = "furrow"; + rev = "v${version}"; + hash = "sha256-xknfvnBDFxDYeBE1yAjXl1Fx3VDHrNSUhXEWQO3PK6s="; + }; + + cargoHash = "sha256-FGtrKtWFPcT3R8nF5OQFlmIiKSuZ8aiHfj76bBvga5A="; + + # Only the unit tests run here. Every cli integration test builds a fixture + # whose first step is `xattr::set(…, "user.furrow-test", …)`, and the sandbox + # /tmp is a tmpfs that answers EOPNOTSUPP for user.* xattrs, so all 54 of them + # die in setup — same reason forks_files_links_modes_and_xattrs is skipped, in + # the one lib test that touches an xattr. Upstream's CI runs the whole suite on + # a filesystem that supports them. + cargoTestFlags = [ "--lib" ]; + checkFlags = [ + "--skip" + "forks_files_links_modes_and_xattrs" + ]; + + meta = with lib; { + description = "Local-first working-state snapshots for agentic development"; + homepage = "https://github.com/Agent-Field/furrow"; + changelog = "https://github.com/Agent-Field/furrow/releases/tag/v${version}"; + license = licenses.asl20; + # Thirteen source files use std::os::unix with no windows guards, so the + # upstream release has no windows asset either. + platforms = platforms.unix; + mainProgram = "furrow"; + }; +} diff --git a/packages/codeaf/package.nix b/packages/codeaf/package.nix index 6cd0d56..aae7efd 100644 --- a/packages/codeaf/package.nix +++ b/packages/codeaf/package.nix @@ -2,55 +2,24 @@ lib, buildGoModule, fetchFromGitHub, - fetchurl, - stdenv, + makeWrapper, + furrow, }: -# Every codeaf ships a furrow inside it: internal/furrowbin embeds a pinned -# Agent-Field/furrow release, and `make build` stages it with -# `fetch -from ` instead of downloading, which is the road this build -# takes. The fetcher re-checks the sha256 named in internal/furrowbin/pin.json, -# so the hashes below are a second copy of a pin already in the source. Without -# a staged furrow codeaf still builds, and looks for the binary on PATH instead. -let - furrow = ( - { - x86_64-linux = { - goos = "linux"; - goarch = "amd64"; - source = fetchurl { - url = "https://github.com/Agent-Field/furrow/releases/download/v0.1.0/furrow-linux-amd64"; - hash = "sha256-x/h+3m81Kq0F066kJRfqychmCa3rYlGHKGQRSSZj4rQ="; - }; - }; - aarch64-linux = { - goos = "linux"; - goarch = "arm64"; - source = fetchurl { - url = "https://github.com/Agent-Field/furrow/releases/download/v0.1.0/furrow-linux-arm64"; - hash = "sha256-ifE9UG8vf1TSyJIMU7V4TwsBiK5w91jpErcb/v3x0dY="; - }; - }; - x86_64-darwin = { - goos = "darwin"; - goarch = "amd64"; - source = fetchurl { - url = "https://github.com/Agent-Field/furrow/releases/download/v0.1.0/furrow-darwin-amd64"; - hash = "sha256-B5mP5JoqoZ9O0IdVud6mWkiwjR/WgLWFKMVbNXnLaFI="; - }; - }; - aarch64-darwin = { - goos = "darwin"; - goarch = "arm64"; - source = fetchurl { - url = "https://github.com/Agent-Field/furrow/releases/download/v0.1.0/furrow-darwin-arm64"; - hash = "sha256-EJN1Fmnla1dqK2WPvZxFKcqvNWfqoJyfduDV2wL4+hY="; - }; - }; - } - .${stdenv.hostPlatform.system} or null - ); -in +# codeaf carries a furrow inside itself: internal/furrowbin embeds whatever is +# staged in internal/furrowbin/cache and, on first use, writes it out to +# ~/.codeaf/bin/furrow-. Upstream stages the GitHub release asset there, +# whose bytes are someone else's glibc-dynamic build that no Nix phase ever +# touched, so the file that lands in the state root cannot start here. Staging a +# rebuilt furrow is not an option either: upstream's fetcher re-checks the sha256 +# named in internal/furrowbin/pin.json and would refuse one. So this build stages +# nothing, and hands over ./furrow.nix through CODEAF_FURROW instead, which +# internal/furrow reads before it looks at the embedded copy at all. Nothing of +# furrow's ends up in the codeaf binary; without that variable codeaf looks for +# furrow on PATH, the way a plain `go build` does. +# +# A codeaf run without this wrapper is not the same program: it has no furrow +# unless one is on PATH. buildGoModule rec { pname = "codeaf"; version = "0.7.1"; @@ -66,6 +35,8 @@ buildGoModule rec { env.CGO_ENABLED = 0; + nativeBuildInputs = [ makeWrapper ]; + # The shipped binary carries the packed manual rather than the raw Markdown. tags = [ "codeaf_packed_manual" ]; @@ -75,20 +46,22 @@ buildGoModule rec { # and timing assumptions; it is not buildGoModule-shaped. doCheck = false; - # `make build` runs two host-side steps before compiling. Both are built for - # the build machine even when codeaf targets another platform, hence the - # unset GOOS/GOARCH; the fetcher is told the target platform by hand. The - # vendoring derivation runs preBuild too, before vendor/ exists, and these two - # need the vendored dependencies, so they wait for it. + # `make build` runs a host-side step before compiling: packing the manual into + # what the codeaf_packed_manual tag embeds. It is built for the build machine + # even when codeaf targets another platform, hence the unset GOOS/GOARCH. The + # vendoring derivation runs preBuild too, before vendor/ exists, and this step + # needs the vendored dependencies, so it waits for it. Upstream's other step, + # staging the furrow release, is deliberately not run: see ./furrow.nix. preBuild = '' if [ -d vendor ]; then env -u GOOS -u GOARCH go generate ./internal/manual - ${lib.optionalString (furrow != null) - "env -u GOOS -u GOARCH go run ./internal/furrowbin/cmd/fetch -goos=${furrow.goos} -goarch=${furrow.goarch} -from ${furrow.source}" - } fi ''; + postInstall = '' + wrapProgram $out/bin/codeaf --set CODEAF_FURROW ${furrow}/bin/furrow + ''; + ldflags = [ "-s" "-w" @@ -97,11 +70,14 @@ buildGoModule rec { ]; passthru = { + inherit furrow; category = "AI Coding Agents"; updateScript = [ "nix-update" "--flake" ".#codeaf" + # furrow.nix is not a flake output, so nix-update never touches it: bump it + # by hand alongside the version codeaf pins in internal/furrowbin/pin.json. ]; }; @@ -110,8 +86,8 @@ buildGoModule rec { homepage = "https://github.com/Agent-Field/CodeAF"; changelog = "https://github.com/Agent-Field/CodeAF/blob/v${version}/CHANGELOG.md"; license = licenses.asl20; - # Not fromSource all the way down: the binary embeds the prebuilt furrow - # release named in internal/furrowbin/pin.json. + # Runs the furrow built from source by ./furrow.nix, not the release asset + # upstream would have embedded. mainProgram = "codeaf"; platforms = platforms.linux ++ platforms.darwin; };