diff --git a/.github/workflows/check-dependency-graph.yml b/.github/workflows/check-dependency-graph.yml new file mode 100644 index 00000000..3ada3360 --- /dev/null +++ b/.github/workflows/check-dependency-graph.yml @@ -0,0 +1,53 @@ +name: Check Dependency Graph (reusable) + +on: + workflow_call: + inputs: + script-dir: + description: >- + Directory holding the vendored `build_dependency_graph.py`, with its + self-test under `tests/`. + type: string + default: tools + pages-dir: + description: >- + Directory under the repository root holding the pages (`.` when + pages sit at the root). + type: string + default: chapters + site: + description: >- + This site's name in cross-site node keys (default: the repository + name). + type: string + default: '' + fail-on-cycle: + description: Fail the job when the graph has a cycle (else warn only). + type: boolean + default: true + +jobs: + check-dependency-graph: + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Run the builder's self-test + env: + DG_SCRIPT_DIR: ${{ inputs.script-dir }} + run: python3 "$DG_SCRIPT_DIR/tests/test_build_dependency_graph.py" + + - name: Build the graph + env: + DG_SCRIPT_DIR: ${{ inputs.script-dir }} + DG_PAGES: ${{ inputs.pages-dir }} + DG_SITE: ${{ inputs.site || github.event.repository.name }} + DG_FAIL: ${{ inputs.fail-on-cycle }} + run: | + args=(--pages "$DG_PAGES" --site "$DG_SITE" --out "${RUNNER_TEMP}/dependency-graph.json") + if [ "$DG_FAIL" = "true" ]; then args+=(--fail); fi + python3 "$DG_SCRIPT_DIR/build_dependency_graph.py" "${args[@]}" diff --git a/CLAUDE.md b/CLAUDE.md index f72a1505..7ba00d3d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -225,7 +225,7 @@ while `opposition-research`, `check-dependency-updates`, `check-source-overlap`, `check-quarto-prose`, `check-orphaned-images`, `check-r-package`, `check-python-package`, `check-typed-output`, -`check-informal-definitions`, `check-literal-math-letters`, `check-wikipedia-further-reading`, `check-redundant-citations`, `check-one-div-per-subfile`, `check-term-links`, `check-paired-file-change`, `annotation-gate`, `annotation-gate-run`, `check-local-macros`, `check-fform-paren`, `check-write-idiom`, `cleanup-pr-previews`, `student-qmd`, +`check-informal-definitions`, `check-literal-math-letters`, `check-wikipedia-further-reading`, `check-redundant-citations`, `check-one-div-per-subfile`, `check-term-links`, `check-dependency-graph`, `check-paired-file-change`, `annotation-gate`, `annotation-gate-run`, `check-local-macros`, `check-fform-paren`, `check-write-idiom`, `cleanup-pr-previews`, `student-qmd`, `update-quarto-extensions`, and `update-snapshots` only ever shipped at `@v3` (or moved to `@v3` for `cleanup-pr-previews` and `update-snapshots`). diff --git a/README.md b/README.md index f0e9e31c..bc87e581 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ not reference `@main` from consumers. | `annotation-gate.yml` | Fail the run when any other job carries a notice, warning or failure annotation not on an allowlist, so an annotation cannot sit on a green job | `levels`, `allow-patterns`, `job-name` | | `annotation-gate-run.yml` | Gate a finished run from a `workflow_run` workflow and post the result as an `annotation-gate` check run, for repositories whose checks are spread over many workflow files | `run-id`, `head-sha`, `run-attempt`, `levels`, `allow-patterns` | | `check-fform-paren.yml` | Diff-scoped, warn-only check that flags `\name\paren{...}` (or `\sb{`, `\cb{`) where an f form `\namef{...}` is defined in the macros file (default `latex-macros/macros.qmd`) | `macros-file`, `globs`, `paths-ignore`, `submodules`, `fail`, `whole-tree` | +| `check-dependency-graph.yml` | Run the self-test of a notes repo's vendored `build_dependency_graph.py`, then build its definition and result graph and fail on a cycle | `script-dir`, `pages-dir`, `site`, `fail-on-cycle` | | `check-write-idiom.yml` | Diff-scoped, warn-only check that flags a line opening with `Write the`, `Write a` or `Write an` (after an optional list marker and label), because "Write ..." hides whether to derive, express, state or give | `globs`, `paths-ignore`, `fail`, `whole-tree` | | `lint-qmd.yml` | markdownlint over the prose sections of tracked `.qmd` Quarto files (code chunks stripped, YAML front matter skipped natively) with a bundled default config; default 80-char line-length ceiling encourages semantic line breaks | `config-file`, `globs`, `paths-ignore`, `fail`, `max-line-length` | | `lint-changed-lines.yml` | lintr over only the lines a PR adds or modifies (not whole changed files), so lint rules can be adopted or tightened incrementally | `path`, `install-quarto`, `use-renv`, `renv-cache-version`, `apt-packages`, `extra-packages`, `install-package`, `fail` | @@ -137,7 +138,7 @@ that need to write must have the **caller** grant it on the calling job: `models: read`, `contents: read`. - `annotation-gate`, `check-ai-tells`, `check-bibliography-dois`, - `check-code-similarity`, `check-duplicate-roxygen`, `check-equation-renders`, `check-extra`, + `check-code-similarity`, `check-dependency-graph`, `check-duplicate-roxygen`, `check-equation-renders`, `check-extra`, `check-formatting`, `check-informal-definitions`, `check-junk-files`, `check-literal-math-letters`, `check-wikipedia-further-reading`, `check-redundant-citations`, `check-one-div-per-subfile`, `check-term-links`, `check-paired-file-change`, `check-local-macros`, `check-fform-paren`, `check-write-idiom`, `check-math-definitions`, `check-merge-drops`, `check-new-line-breaks`, `check-news`, @@ -683,7 +684,7 @@ Pin `check-one-function-per-file.yml` only ever shipped at `@v2` (too new to exist at the frozen `@v1` tag). `check-dependency-updates.yml`, `check-duplicate-roxygen.yml`, -`check-informal-definitions.yml`, `check-literal-math-letters.yml`, `check-wikipedia-further-reading.yml`, `check-redundant-citations.yml`, `check-one-div-per-subfile.yml`, `check-term-links.yml`, `check-paired-file-change.yml`, `annotation-gate.yml`, `annotation-gate-run.yml`, `check-local-macros.yml`, `check-fform-paren.yml`, `check-write-idiom.yml`, +`check-informal-definitions.yml`, `check-literal-math-letters.yml`, `check-wikipedia-further-reading.yml`, `check-redundant-citations.yml`, `check-one-div-per-subfile.yml`, `check-term-links.yml`, `check-paired-file-change.yml`, `annotation-gate.yml`, `annotation-gate-run.yml`, `check-local-macros.yml`, `check-fform-paren.yml`, `check-dependency-graph.yml`, `check-write-idiom.yml`, `check-math-definitions.yml`, `check-merge-drops.yml`, `check-orphaned-images.yml`, `check-python-package.yml`, `check-quarto-book.yml`, @@ -831,7 +832,7 @@ templates intentionally track the moving major tag (currently `@v1`, except `check-code-similarity.yml`, and `check-one-function-per-file.yml` at `@v2`, and `check-dependency-updates.yml`, `check-duplicate-roxygen.yml`, -`check-informal-definitions.yml`, `check-literal-math-letters.yml`, `check-wikipedia-further-reading.yml`, `check-redundant-citations.yml`, `check-one-div-per-subfile.yml`, `check-term-links.yml`, `check-paired-file-change.yml`, `annotation-gate.yml`, `annotation-gate-run.yml`, `check-local-macros.yml`, `check-fform-paren.yml`, `check-write-idiom.yml`, +`check-informal-definitions.yml`, `check-literal-math-letters.yml`, `check-wikipedia-further-reading.yml`, `check-redundant-citations.yml`, `check-one-div-per-subfile.yml`, `check-term-links.yml`, `check-paired-file-change.yml`, `annotation-gate.yml`, `annotation-gate-run.yml`, `check-local-macros.yml`, `check-fform-paren.yml`, `check-dependency-graph.yml`, `check-write-idiom.yml`, `check-math-definitions.yml`, `check-merge-drops.yml`, `check-orphaned-images.yml`, `check-python-package.yml`, `check-quarto-book.yml`, diff --git a/changelog.d/check-dependency-graph.added.md b/changelog.d/check-dependency-graph.added.md new file mode 100644 index 00000000..2bbfa2a4 --- /dev/null +++ b/changelog.d/check-dependency-graph.added.md @@ -0,0 +1,5 @@ +- **New `check-dependency-graph.yml` reusable workflow** + ([#1129](https://github.com/Morrison-Lab/gha/issues/1129)). + It runs the self-test of a repository's vendored `build_dependency_graph.py`. + It then builds the graph and fails on a cycle. + Notes repos that vendor the script from `build-dependency-graph` call it in place of a per-repo workflow. diff --git a/examples/check-dependency-graph.yml b/examples/check-dependency-graph.yml new file mode 100644 index 00000000..12ad2969 --- /dev/null +++ b/examples/check-dependency-graph.yml @@ -0,0 +1,21 @@ +# Copy to .github/workflows/check-dependency-graph.yml in your repo. +name: Check Dependency Graph + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + check: + uses: Morrison-Lab/gha/.github/workflows/check-dependency-graph.yml@v3 + # with: + # # Pages at the repository root instead of chapters/. + # pages-dir: . + # # Where the vendored script and its tests live. + # script-dir: tools + # # Warn instead of failing on a cycle. + # fail-on-cycle: false diff --git a/website/_quarto.yml b/website/_quarto.yml index 8bb9efff..3ee68801 100644 --- a/website/_quarto.yml +++ b/website/_quarto.yml @@ -44,6 +44,7 @@ website: - reference/check-duplicate-roxygen.qmd - reference/check-formatting.qmd - reference/check-junk-files.qmd + - reference/check-dependency-graph.qmd - reference/check-math-definitions.qmd - reference/check-merge-drops.qmd - reference/check-non-standard-chars.qmd diff --git a/website/permissions.qmd b/website/permissions.qmd index 818ade03..e54d77c7 100644 --- a/website/permissions.qmd +++ b/website/permissions.qmd @@ -11,7 +11,7 @@ already include the right `permissions:` blocks; copy them as-is. ## Per-workflow grants - `annotation-gate`, `check-ai-tells`, `check-bibliography-dois`, - `check-code-similarity`, `check-duplicate-roxygen`, + `check-code-similarity`, `check-dependency-graph`, `check-duplicate-roxygen`, `check-equation-renders`, `check-extra`, `check-formatting`, `check-informal-definitions`, `check-junk-files`, `check-literal-math-letters`, `check-wikipedia-further-reading`, `check-redundant-citations`, diff --git a/website/reference/check-dependency-graph.qmd b/website/reference/check-dependency-graph.qmd new file mode 100644 index 00000000..7a86b933 --- /dev/null +++ b/website/reference/check-dependency-graph.qmd @@ -0,0 +1,56 @@ +--- +title: "check-dependency-graph.yml" +--- + +Run the self-test of a notes repository's vendored +`build_dependency_graph.py`, +then build the graph of its definition and result divs and fail on a cycle. + +The script comes from the `build-dependency-graph` action. +Notes repositories vendor it under `tools/` +so that a local render can run it as a `pre-render` step. +Nothing else runs the vendored copy's self-test, +so a change to the script in one repository +can break it without any check going red. + +## What it does + +1. Runs `/tests/test_build_dependency_graph.py`. +2. Runs `/build_dependency_graph.py` on the pages, + writing the JSON to the runner's temporary directory. + With `fail-on-cycle` it exits non-zero + when a definition or result div + depends on itself through other divs. + +Forward references are printed and never fail the job. + +## Inputs + +| Input | Type | Default | Description | +| --------------- | ------- | ------- | ----------- | +| `script-dir` | string | `tools` | Directory holding the vendored script, with its self-test under `tests/`. | +| `pages-dir` | string | `chapters` | Directory under the repository root holding the pages (`.` when pages sit at the root). | +| `site` | string | the repository name | This site's name in cross-site node keys. | +| `fail-on-cycle` | boolean | `true` | Fail the job when the graph has a cycle (else warn only). | + +## Permissions + +Only `contents: read` (the default), so no `permissions:` block is needed. + +## Example + +```yaml +name: Check dependency graph +on: + push: { branches: [main] } + pull_request: +jobs: + check: + uses: Morrison-Lab/gha/.github/workflows/check-dependency-graph.yml@v3 + # with: + # pages-dir: . +``` + +See `check-dependency-graph.yml` in +[`examples/`](https://github.com/Morrison-Lab/gha/tree/main/examples) +for the full caller stub. diff --git a/website/versioning.qmd b/website/versioning.qmd index db3b6724..940acef8 100644 --- a/website/versioning.qmd +++ b/website/versioning.qmd @@ -36,6 +36,7 @@ only ever shipped at `annotation-gate.yml`, `annotation-gate-run.yml`, `check-local-macros.yml`, `check-fform-paren.yml`, `check-write-idiom.yml`, +`check-dependency-graph.yml`, `check-math-definitions.yml`, `check-merge-drops.yml`, `check-orphaned-images.yml`, `check-python-package.yml`, `check-quarto-book.yml`, @@ -183,6 +184,7 @@ intentionally track the moving major tag (currently `@v1`, except `preview.yml`, `annotation-gate.yml`, `annotation-gate-run.yml`, `check-local-macros.yml`, `check-fform-paren.yml`, `check-write-idiom.yml`, +`check-dependency-graph.yml`, `check-math-definitions.yml`, `check-merge-drops.yml`, `check-repo-hygiene.yml`, `check-quarto-website.yml`, diff --git a/website/workflows.qmd b/website/workflows.qmd index 927362f2..2131cfc3 100644 --- a/website/workflows.qmd +++ b/website/workflows.qmd @@ -47,6 +47,7 @@ input tables and a copy-paste example. | [`annotation-gate.yml`](reference/annotation-gate.qmd) | Fail the run when any other job carries a notice, warning or failure annotation not on an allowlist, so an annotation cannot sit on a green job | `levels`, `allow-patterns`, `job-name` | | [`annotation-gate-run.yml`](reference/annotation-gate.qmd) | Gate a finished run from a `workflow_run` workflow and post the result as an `annotation-gate` check run, for repositories whose checks are spread over many workflow files | `run-id`, `head-sha`, `run-attempt`, `levels`, `allow-patterns` | | [`check-fform-paren.yml`](reference/check-fform-paren.qmd) | Diff-scoped, warn-only check that flags `\name\paren{...}` (or `\sb{`, `\cb{`) where an f form `\namef{...}` is defined in the macros file (default `latex-macros/macros.qmd`) | `macros-file`, `globs`, `paths-ignore`, `submodules`, `fail`, `whole-tree` | +| [`check-dependency-graph.yml`](reference/check-dependency-graph.qmd) | Run the self-test of a notes repo's vendored `build_dependency_graph.py`, then build its definition and result graph and fail on a cycle | `script-dir`, `pages-dir`, `site`, `fail-on-cycle` | | [`check-write-idiom.yml`](reference/check-write-idiom.qmd) | Diff-scoped, warn-only check that flags a line opening with `Write the`, `Write a` or `Write an` (after an optional list marker and label), because "Write ..." hides whether to derive, express, state or give | `globs`, `paths-ignore`, `fail`, `whole-tree` | | [`lint-qmd.yml`](reference/lint-qmd.qmd) | markdownlint over the prose sections of tracked `.qmd` Quarto files (code chunks stripped, YAML front matter skipped natively) with a bundled default config; default 80-char line-length ceiling encourages semantic line breaks | `config-file`, `globs`, `paths-ignore`, `fail`, `max-line-length` | | [`lint-changed-lines.yml`](reference/lint-changed-lines.qmd) | lintr over only the lines a PR adds or modifies (not whole changed files), so lint rules can be adopted or tightened incrementally | `path`, `install-quarto`, `use-renv`, `renv-cache-version`, `apt-packages`, `extra-packages`, `install-package`, `fail` | @@ -120,6 +121,7 @@ only ever shipped at `@v2` (too new to exist at the frozen `@v1` tag). `annotation-gate.yml`, `annotation-gate-run.yml`, `check-local-macros.yml`, `check-fform-paren.yml`, `check-write-idiom.yml`, +`check-dependency-graph.yml`, `cleanup-pr-previews.yml`, `opposition-research.yml`, `student-qmd.yml`, `update-quarto-extensions.yml`, and