diff --git a/.github/workflows/dependents.yml b/.github/workflows/dependents.yml new file mode 100644 index 0000000..f36f2b9 --- /dev/null +++ b/.github/workflows/dependents.yml @@ -0,0 +1,101 @@ +# Dependents: test the packages that depend on this one with this branch in its place, so that a release that would break them is seen before it's registered. +# Called from the repository's own workflow, on pull requests into the main branch, and by hand: +# +# on: +# pull_request: +# branches: [main] +# workflow_dispatch: +# jobs: +# dependents: +# uses: BioJulia/Workflows/.github/workflows/dependents.yml@v1 +# permissions: +# contents: read +# with: +# dependents: | +# BioJulia/BED.jl +# BioJulia/GFF3.jl +# +# Each dependent is checked out at its default branch, this branch's package takes the place of the registered one in the dependent's project, and the dependent's tests run, as the check "dependents / ". +# A breaking release is expected to break its dependents until they adapt, so the checks report rather than block, unless a ruleset requires them. +name: "Reusable: Dependents" + +on: + workflow_call: + inputs: + dependents: + description: The repositories of the packages that depend on this one, such as `BioJulia/BED.jl`, one per line or separated by commas. + type: string + required: true + project: + description: The package's directory, relative to the repository's root, for a package in a subdirectory. + type: string + default: '' + julia-version: + description: The version of Julia to test with. + type: string + default: '1' + +permissions: {} + +jobs: + list: + name: Dependents + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.list.outputs.matrix }} + steps: + - name: List the dependents + id: list + env: + DEPENDENTS: ${{ inputs.dependents }} + run: | + set -euo pipefail + # One repository per line or separated by commas, each `owner/name`, without duplicates. + repositories=$(printf '%s\n' "$DEPENDENTS" | tr ',' '\n' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//' | { grep -v '^$' || true; } | sort -u) + if [ -z "$repositories" ]; then + echo "::error title=Dependents::The input dependents names no repository." + exit 1 + fi + while IFS= read -r repository; do + if ! [[ "$repository" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then + echo "::error title=Dependents::$repository isn't a repository, owner/name." + exit 1 + fi + done <<< "$repositories" + matrix=$(printf '%s\n' "$repositories" | python3 -c 'import json, sys; print(json.dumps([line.strip() for line in sys.stdin if line.strip()]))') + echo "matrix=$matrix" >> "$GITHUB_OUTPUT" + echo "Testing the dependents $matrix." + + test: + name: ${{ matrix.dependent }} + needs: list + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + fail-fast: false + matrix: + dependent: ${{ fromJSON(needs.list.outputs.matrix) }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: package + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: ${{ matrix.dependent }} + path: dependent + persist-credentials: false + - uses: julia-actions/setup-julia@da908477b5db94c10ffce8cfea9f2b3b9d5db687 # v3.0.3 + with: + version: ${{ inputs.julia-version }} + - uses: julia-actions/cache@9353db09a0f2bbbdec6f41791918c75d37391b36 # v3.5.0 + - name: Test the dependent with this package + env: + RELEASEFLOW_PROJECT: ${{ inputs.project }} + run: | + julia --startup-file=no --project=dependent -e ' + using Pkg + # This branch takes the place of the registered package, in the dependent'"'"'s project and the projects of its workspace. + Pkg.develop(path = joinpath("package", ENV["RELEASEFLOW_PROJECT"])) + Pkg.instantiate() + Pkg.test()' diff --git a/README.md b/README.md index 9e989de..4059adc 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,30 @@ jobs: RELEASEFLOW_TOKEN: ${{ secrets.RELEASEFLOW_TOKEN }} ``` +And the dependents' tests, in `.github/workflows/Dependents.yml`, which run the tests of the packages that depend on this one with the pull request's branch in its place: + +```yaml +name: Dependents +on: + pull_request: + branches: [main] + workflow_dispatch: +permissions: {} +jobs: + dependents: + uses: BioJulia/Workflows/.github/workflows/dependents.yml@v1 + permissions: + contents: read + with: + dependents: | + BioJulia/BED.jl + BioJulia/GFF3.jl +``` + +Each dependent is checked out at its default branch, and its tests run as the check `dependents / `, with `julia-version`, `1` unless given. +A breaking release is expected to break its dependents until they adapt, so whether a ruleset requires these checks is the maintainers' choice; without that, they report what a release would break before it's registered. +A dependent whose project takes the package from a local path, through `[sources]`, tests against that path instead. + OneFlow and trunk, in `.github/workflows/OneFlow.yml` or `.github/workflows/Trunk.yml`: ```yaml @@ -121,8 +145,10 @@ A package can follow the major tag, `v1`, or pin a release, such as `v1.0.0`; De | `skip-label` | `skip changelog` | gitflow, oneflow, trunk | The label that exempts a pull request from the changelog entry. | | `check-links` | `true` | gitflow, oneflow, trunk | Whether to check the changelog's comparison links. | | `require-entry` | `true` | trunk | Whether a pull request that isn't a release must add a changelog entry, for a changelog without an unreleased section. | -| `project` | the root | gitflow, back-merge, oneflow, trunk | The package's directory, for a package in a subdirectory, such as `lib/Toy`. | +| `project` | the root | gitflow, back-merge, oneflow, trunk, dependents | The package's directory, for a package in a subdirectory, such as `lib/Toy`. | | `registry` | `General` | gitflow, oneflow, trunk | The registry of the package's releases. | +| `dependents` | none | dependents | The repositories of the packages that depend on this one, one per line or separated by commas. | +| `julia-version` | `1` | dependents | The version of Julia that the dependents' tests run on. | For example, `with: { main-branch: master }` for a repository whose main branch is `master`. diff --git a/ReleaseFlow/CHANGELOG.md b/ReleaseFlow/CHANGELOG.md index a7fdff1..91e0bc8 100644 --- a/ReleaseFlow/CHANGELOG.md +++ b/ReleaseFlow/CHANGELOG.md @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - The reusable workflow `tag.yml`, which moves each major tag, such as `v1`, to the newest release of its major version, `vX.Y.Z`, whichever made the tag, TagBot or a maintainer, and whenever, for a repository that callers follow by its major tag; it never moves a major tag backwards, leaves pre-releases out, and reports what it did for each major tag as a notice on the run and a line of the job's summary. - This repository moves `v1` with it, through `Self-Tag.yml`, once TagBot tags a release `vX.Y.Z`, with `tag_prefix: NO_PREFIX`, in place of `ReleaseFlow-vX.Y.Z`, and with the ReleaseFlow app's token, which the ruleset on `v*` tags lets through; so the workflows' callers get a release once ReleaseFlow's is registered. +- The reusable workflow `dependents.yml`, which runs the tests of the packages that depend on this one, each checked out at its default branch, with the pull request's branch in its place, as the checks `dependents / `. ## [1.0.1] - 2026-10-20 diff --git a/ReleaseFlow/test/dependents.jl b/ReleaseFlow/test/dependents.jl new file mode 100644 index 0000000..864d012 --- /dev/null +++ b/ReleaseFlow/test/dependents.jl @@ -0,0 +1,22 @@ +# The dependents workflow +# ----------------------- + +@testset "Dependents" begin + directory = mktempdir() + script = joinpath(directory, "step.sh") + write(script, workflow_step("dependents.yml", "List the dependents")) + function list(dependents) + outputs = joinpath(directory, "outputs") + write(outputs, "") + output = IOBuffer() + process = run(pipeline(Cmd(addenv(`bash $(script)`, "DEPENDENTS" => dependents, "GITHUB_OUTPUT" => outputs); ignorestatus = true); stdout = output, stderr = output)) + return (code = process.exitcode, output = String(take!(output)), matrix = readchomp(outputs)) + end + # One per line or separated by commas, with spaces and duplicates, in order. + result = list("BioJulia/GFF3.jl\n BioJulia/BED.jl, BioJulia/XAM.jl,\n\nBioJulia/BED.jl\n") + @test result.code == 0 + @test result.matrix == """matrix=["BioJulia/BED.jl", "BioJulia/GFF3.jl", "BioJulia/XAM.jl"]""" + @test list("Example/Toy.jl").matrix == """matrix=["Example/Toy.jl"]""" + @test fails(list("BioJulia/BED.jl, not a repository"), "not a repository isn't a repository") + @test fails(list(" , \n"), "names no repository") +end diff --git a/ReleaseFlow/test/helpers.jl b/ReleaseFlow/test/helpers.jl index d431192..514ba4d 100644 --- a/ReleaseFlow/test/helpers.jl +++ b/ReleaseFlow/test/helpers.jl @@ -144,7 +144,10 @@ end passes(result) = result.code == 0 fails(result, message) = result.code == 1 && occursin(message, result.output) -# The shell of the step `name` of the workflow `file`: the block after its `run: |`, without its indentation. +# Workflows +# --------- + +# The shell of the step `name` of the workflow `file`, in .github/workflows: the block after its `run: |`, without its indentation. function workflow_step(file, name) lines = readlines(normpath(joinpath(@__DIR__, "..", "..", ".github", "workflows", file))) named = findfirst(line -> strip(line) == "- name: $(name)", lines) diff --git a/ReleaseFlow/test/runtests.jl b/ReleaseFlow/test/runtests.jl index 6ffcade..7bf9781 100644 --- a/ReleaseFlow/test/runtests.jl +++ b/ReleaseFlow/test/runtests.jl @@ -16,3 +16,4 @@ include("initial.jl") include("subdirectory.jl") include("back_merge.jl") include("tag.jl") +include("dependents.jl")