Skip to content

Commit f84913f

Browse files
authored
Merge pull request ilysenko#1032 from ilysenko/codex/optimize-cachix-hash-refresh
Optimize Cachix population for DMG hash changes
2 parents 1218935 + 8245ece commit f84913f

3 files changed

Lines changed: 143 additions & 29 deletions

File tree

.github/workflows/cachix.yml

Lines changed: 103 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ on:
44
push:
55
branches:
66
- main
7-
schedule:
8-
- cron: '37 7 * * *'
9-
workflow_dispatch: {}
7+
paths:
8+
- flake.nix
109

1110
permissions:
1211
contents: read
@@ -19,24 +18,86 @@ env:
1918
CACHIX_CACHE_NAME: codex-desktop-linux
2019
CARGO_TERM_COLOR: always
2120
NIX_CONFIG: experimental-features = nix-command flakes
22-
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
2321

2422
jobs:
23+
detect-codex-dmg-hash:
24+
name: Detect Codex DMG hash change
25+
runs-on: ubuntu-latest
26+
outputs:
27+
changed: ${{ steps.codex-dmg-hash.outputs.changed }}
28+
current: ${{ steps.codex-dmg-hash.outputs.current }}
29+
steps:
30+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
31+
with:
32+
fetch-depth: 2
33+
persist-credentials: false
34+
35+
- name: Compare Codex DMG hashes
36+
id: codex-dmg-hash
37+
env:
38+
BEFORE_SHA: ${{ github.event.before }}
39+
run: |
40+
set -euo pipefail
41+
42+
current_hash="$(
43+
scripts/ci/update-nix-hashes.sh \
44+
read-flake-hash "codexDmg = pkgs.fetchurl {" "hash = "
45+
)"
46+
previous_flake="$(mktemp)"
47+
trap 'rm -f "$previous_flake"' EXIT
48+
49+
if [ "$BEFORE_SHA" = "0000000000000000000000000000000000000000" ]; then
50+
changed=true
51+
previous_hash=missing
52+
else
53+
if ! git cat-file -e "$BEFORE_SHA:flake.nix" 2>/dev/null; then
54+
git fetch --no-tags --depth=1 origin "$BEFORE_SHA"
55+
fi
56+
git show "$BEFORE_SHA:flake.nix" > "$previous_flake"
57+
previous_hash="$(
58+
FLAKE_FILE="$previous_flake" scripts/ci/update-nix-hashes.sh \
59+
read-flake-hash "codexDmg = pkgs.fetchurl {" "hash = "
60+
)"
61+
if [ "$current_hash" = "$previous_hash" ]; then
62+
changed=false
63+
else
64+
changed=true
65+
fi
66+
fi
67+
68+
{
69+
echo "changed=$changed"
70+
echo "current=$current_hash"
71+
} >> "$GITHUB_OUTPUT"
72+
{
73+
echo "## Codex DMG hash"
74+
echo
75+
echo "- Previous: \`$previous_hash\`"
76+
echo "- Current: \`$current_hash\`"
77+
echo "- Populate Cachix: \`$changed\`"
78+
} >> "$GITHUB_STEP_SUMMARY"
79+
2580
populate:
2681
name: Build Nix flake outputs
82+
needs: detect-codex-dmg-hash
83+
if: needs.detect-codex-dmg-hash.outputs.changed == 'true'
2784
runs-on: ubuntu-latest
2885
timeout-minutes: 150
86+
env:
87+
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
2988
steps:
30-
- uses: actions/checkout@v7
89+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
90+
with:
91+
persist-credentials: false
3192

32-
- uses: cachix/install-nix-action@v31
93+
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31
3394

34-
- name: Configure Cachix for push
35-
if: env.CACHIX_AUTH_TOKEN != ''
36-
uses: cachix/cachix-action@v17
95+
- name: Configure Cachix for explicit uploads
96+
uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17
3797
with:
3898
name: ${{ env.CACHIX_CACHE_NAME }}
3999
authToken: ${{ env.CACHIX_AUTH_TOKEN }}
100+
skipPush: true
40101

41102
- name: Warn when Cachix push token is missing
42103
if: env.CACHIX_AUTH_TOKEN == ''
@@ -48,26 +109,41 @@ jobs:
48109
Add a write token for the `codex-desktop-linux` Cachix cache as a repository secret to populate it automatically.
49110
EOF
50111
51-
- name: Build cacheable Nix outputs
112+
- name: Build, upload, and collect cacheable Nix outputs
52113
run: |
53114
set -euo pipefail
54-
nix build \
55-
.#codex-desktop \
56-
.#codex-desktop-computer-use-ui \
57-
.#codex-desktop-remote-mobile-control \
58-
.#codex-desktop-computer-use-ui-remote-mobile-control \
59-
.#checks.x86_64-linux.watchdog-linux-features \
60-
.#installer \
61-
--no-link \
62-
--print-build-logs
115+
116+
outputs=(
117+
.#codex-desktop
118+
.#codex-desktop-computer-use-ui
119+
.#codex-desktop-remote-mobile-control
120+
.#codex-desktop-computer-use-ui-remote-mobile-control
121+
.#checks.x86_64-linux.watchdog-linux-features
122+
.#installer
123+
)
124+
125+
for output in "${outputs[@]}"; do
126+
echo "Building $output"
127+
store_paths_file="$RUNNER_TEMP/cachix-store-paths"
128+
nix build "$output" \
129+
--no-link \
130+
--print-build-logs \
131+
--print-out-paths > "$store_paths_file"
132+
mapfile -t store_paths < "$store_paths_file"
133+
if [ "${#store_paths[@]}" -eq 0 ]; then
134+
echo "Nix did not return a store path for $output." >&2
135+
exit 1
136+
fi
137+
if [ -n "$CACHIX_AUTH_TOKEN" ]; then
138+
printf '%s\n' "${store_paths[@]}" | cachix push "$CACHIX_CACHE_NAME"
139+
fi
140+
nix store gc
141+
done
142+
63143
{
64144
echo "## Cachix population"
65-
echo ""
66-
echo "- Cache: \`${CACHIX_CACHE_NAME}\`"
67-
echo "- Built: \`.#codex-desktop\`"
68-
echo "- Built: \`.#codex-desktop-computer-use-ui\`"
69-
echo "- Built: \`.#codex-desktop-remote-mobile-control\`"
70-
echo "- Built: \`.#codex-desktop-computer-use-ui-remote-mobile-control\`"
71-
echo "- Built: \`.#checks.x86_64-linux.watchdog-linux-features\`"
72-
echo "- Built: \`.#installer\`"
145+
echo
146+
echo "- Triggering Codex DMG hash: \`${{ needs.detect-codex-dmg-hash.outputs.current }}\`"
147+
echo "- Cache: \`$CACHIX_CACHE_NAME\`"
148+
printf -- '- Built and collected: `%s`\n' "${outputs[@]}"
73149
} >> "$GITHUB_STEP_SUMMARY"

docs/nix.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,5 +276,8 @@ Users can opt in locally with:
276276
cachix use codex-desktop-linux
277277
```
278278

279-
The scheduled `Populate Cachix` workflow builds the default package,
280-
feature-specific package variants, and `.#installer`.
279+
When a merge to `main` changes the pinned `Codex.dmg` hash, the `Populate
280+
Cachix` workflow builds the default package, feature-specific package variants,
281+
the watchdog feature check, and `.#installer`. It uploads and garbage-collects
282+
each output before starting the next one so the hosted runner does not retain
283+
every large app variant at once.

scripts/ci/cachix-workflow.test.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const assert = require("node:assert/strict");
2+
const fs = require("node:fs");
3+
const path = require("node:path");
4+
const test = require("node:test");
5+
6+
const workflow = fs.readFileSync(
7+
path.resolve(__dirname, "../../.github/workflows/cachix.yml"),
8+
"utf8",
9+
);
10+
11+
test("Cachix population runs only for an actual Codex DMG hash change", () => {
12+
assert.match(workflow, /paths:\n\s+- flake\.nix/);
13+
assert.doesNotMatch(workflow, /schedule:/);
14+
assert.doesNotMatch(workflow, /workflow_dispatch:/);
15+
assert.match(workflow, /id: codex-dmg-hash/);
16+
assert.match(workflow, /BEFORE_SHA: \$\{\{ github\.event\.before \}\}/);
17+
assert.match(workflow, /read-flake-hash "codexDmg = pkgs\.fetchurl \{" "hash = "/);
18+
assert.match(workflow, /if: needs\.detect-codex-dmg-hash\.outputs\.changed == 'true'/);
19+
});
20+
21+
test("Cachix population pushes each output before collecting the Nix store", () => {
22+
assert.match(workflow, /skipPush: true/);
23+
assert.match(workflow, /nix build "\$output"[\s\S]*--print-out-paths/);
24+
assert.doesNotMatch(workflow, /mapfile[^\n]*< <\(/);
25+
assert.match(workflow, /printf '%s\\n' "\$\{store_paths\[@\]\}" \| cachix push "\$CACHIX_CACHE_NAME"/);
26+
assert.match(workflow, /nix store gc/);
27+
assert.ok(
28+
workflow.indexOf("cachix push") < workflow.indexOf("nix store gc"),
29+
"Cachix upload must complete before garbage collection",
30+
);
31+
});
32+
33+
test("Cachix population pins every third-party action", () => {
34+
assert.doesNotMatch(workflow, /uses:\s+[^\s]+@v\d/);
35+
});

0 commit comments

Comments
 (0)