Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/actions/codegen-declaration/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: codegen-declaration
description: >-
Fails `rainix-copy-artifacts` when a codegen hook declared a generated path that was then not written. Re-running the generators and diffing proves the committed CONTENT is current, but a generator that has STOPPED emitting a file writes nothing, so the already-correct committed copy is left alone, nothing differs and the job is green over a dead emitter (rainlanguage/rain.factory.deploy#35). Only the generator knows which paths it owns rather than deliberately leaves frozen, so it declares them on stdout as `rainix-codegen owns <path>`, teed into the log this reads. What was WRITTEN is observed, never declared: `mark` records every tracked file's mtime before the first hook and `verify` counts a moved mtime as a write. A repo whose hooks declare nothing is a no-op.
inputs:
phase:
description: >-
`mark` before the first codegen hook, `verify` after the last one and before `forge fmt`, which rewrites sources of its own.
required: true
runs:
using: composite
steps:
- name: Codegen declaration
shell: bash
env:
RAINIX_CODEGEN_PHASE: ${{ inputs.phase }}
run: |
set -euo pipefail
# path: ref runs the check from THIS composite's own checkout, so the
# check version always matches the action version (same pattern as
# mutation-ledger) regardless of any RAINIX_SHA the caller pins.
nix run "path:$(cd "$GITHUB_ACTION_PATH/../../.." && pwd)#rainix-static" -- \
codegen-declaration "$RAINIX_CODEGEN_PHASE" \
--state "$RUNNER_TEMP/rainix-codegen-mtimes.json" \
--log "$RUNNER_TEMP/rainix-codegen.log"
30 changes: 26 additions & 4 deletions .github/workflows/rainix-copy-artifacts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,60 @@ jobs:
- name: Install soldeer dependencies
if: hashFiles('soldeer.lock') != ''
run: nix develop github:rainlanguage/rainix/${{ env.RAINIX_SHA }}#sol-shell -c forge soldeer install
- name: Mark tracked files before codegen
uses: rainlanguage/rainix/.github/actions/codegen-declaration@main
with:
phase: mark
# Currency-check every committed generated artifact by re-running each
# consumer-provided codegen step. The final git diff fails if any
# committed file has drifted from its source. The build-meta.sh hook is
# consumer-supplied because rain meta build's invocation (input/output
# filenames, meta type) varies per repo.
#
# Each hook's stdout is teed into one log: a hook may declare there which
# generated paths it owns, and codegen-declaration fails any of them not
# written since the mark above — the half of the currency check the diff
# cannot do.
# `pipefail` is set in every teed step — bash otherwise reports `tee`'s
# exit status, so a failing generator would pass.
- name: Regenerate meta artifacts
if: hashFiles('script/build-meta.sh') != ''
run: nix develop github:rainlanguage/rainix/${{ env.RAINIX_SHA }}#sol-shell -c ./script/build-meta.sh
run: |
set -euo pipefail
nix develop github:rainlanguage/rainix/${{ env.RAINIX_SHA }}#sol-shell -c ./script/build-meta.sh | tee -a "$RUNNER_TEMP/rainix-codegen.log"
# Committed generated sources must be regenerable here, or the currency
# check below passes without checking anything. The codegen script is
# `script/Build.sol`, matched exactly: a repo that renames or drops it
# goes red rather than skipping regeneration and reporting green.
- name: Regenerate generated sources
run: |
set -euo pipefail
if [ -d src/generated ] && [ ! -f script/Build.sol ]; then
echo "::error::src/generated/ is committed but script/Build.sol was not found, so the committed sources cannot be currency checked here. The codegen script must be script/Build.sol."
exit 1
fi
if [ -f script/Build.sol ]; then
nix develop github:rainlanguage/rainix/${{ env.RAINIX_SHA }}#sol-shell -c forge script ./script/Build.sol
nix develop github:rainlanguage/rainix/${{ env.RAINIX_SHA }}#sol-shell -c forge script ./script/Build.sol | tee -a "$RUNNER_TEMP/rainix-codegen.log"
fi
- name: Build Solidity
run: nix develop github:rainlanguage/rainix/${{ env.RAINIX_SHA }}#sol-shell -c forge build
- name: Copy forge artifacts into committed location
if: hashFiles('script/CopyArtifacts.sol') != ''
run: nix develop github:rainlanguage/rainix/${{ env.RAINIX_SHA }}#sol-shell -c forge script ./script/CopyArtifacts.sol --ffi
run: |
set -euo pipefail
nix develop github:rainlanguage/rainix/${{ env.RAINIX_SHA }}#sol-shell -c forge script ./script/CopyArtifacts.sol --ffi | tee -a "$RUNNER_TEMP/rainix-codegen.log"
# Catch-all post-forge regen hook: consumer-supplied. Runs outside any
# nix devshell so the script picks shells per command (subgraph-shell,
# sol-shell, etc.) for whatever derived artifacts it emits.
- name: Regenerate derived artifacts
if: hashFiles('script/build.sh') != ''
run: ./script/build.sh
run: |
set -euo pipefail
./script/build.sh | tee -a "$RUNNER_TEMP/rainix-codegen.log"
- name: Assert every declared generated path was written
uses: rainlanguage/rainix/.github/actions/codegen-declaration@main
with:
phase: verify
- name: Format (so generated artifacts match committed style)
run: nix develop github:rainlanguage/rainix/${{ env.RAINIX_SHA }}#sol-shell -c forge fmt
- name: Assert committed artifacts match freshly built
Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,27 @@ single job it runs whichever of these the repo has:

then `forge fmt` and the `git diff` assert.

The diff answers "is the committed **content** current". It cannot answer "is
anything still generating it": a generator that has stopped emitting a file
writes nothing, the already-correct committed copy is left alone, and the job is
green over a dead emitter (rainlanguage/rain.factory.deploy#35). Only the
generator knows which paths it owns, as against a `src/generated/<tag>/`
snapshot deliberately frozen forever — so the generator says so, on stdout:

```
rainix-codegen owns src/lib/LibReleasedSuites.sol
```

Whether it was written is observed, never declared: the job marks every tracked
file's mtime before the first hook, tees every hook's stdout into one log, and
after the last hook fails, naming the path, on anything declared `owns` whose
mtime did not move or that is absent. Paths are repo-relative with no `.` or
`..` segments; any other `rainix-codegen` line fails, other stdout is ignored.
Nothing is declared by hand and no repo maintains a list: the same code that
computes where to write emits these lines. A repo whose hooks print none keeps
exactly today's behaviour — green, with a note that a dead emitter there is
still invisible.

```yaml
name: copy-artifacts
on: [push]
Expand Down
2 changes: 2 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@
bats test/bats/action/comment-loc-cap.test.bats
bats test/bats/action/frozen-snapshots-append-only.test.bats
bats test/bats/action/mutation-ledger.test.bats
bats test/bats/action/codegen-declaration.test.bats
bats test/bats/task/skip-simulation.test.bats
bats test/bats/task/subgraph-build.test.bats
bats test/bats/task/subgraph-deploy-version.test.bats
Expand All @@ -464,6 +465,7 @@
bats test/bats/task/no-custom-natspec.test.bats
bats test/bats/workflow/rainix-sol-static.test.bats
bats test/bats/workflow/rainix-rs-static.test.bats
bats test/bats/workflow/rainix-copy-artifacts.test.bats
bats test/bats/workflow/test-yml.test.bats
'';
additionalBuildInputs = [ pkgs.bats ] ++ sol-build-inputs ++ node-build-inputs;
Expand Down
Loading
Loading