From 1cbf2de61acea5a08a29719bb9052a5df42dc030 Mon Sep 17 00:00:00 2001 From: Rick Newton-Rogers Date: Fri, 30 Oct 2026 10:02:46 -0400 Subject: [PATCH 1/4] Add a `workflows-scripts` action that returns the scripts of the running commit A caller that pins a reusable workflow to a release gets the workflow YAML from that release, but the scripts the YAML runs come from a separate checkout of this repository with no `ref:`, so they come from the default branch and a tag does not pin them. The new composite action locates the scripts and configuration files through `github.action_path`, which GitHub resolves for a `$/` reference to this repository at the commit of the running workflow. An optional `copy_into_workspace` input copies the scripts into the workspace, for consumers that only see the workspace, and the action returns the scripts path in POSIX, Windows and workspace-relative forms. --- .github/actions/workflows-scripts/action.yml | 75 ++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 .github/actions/workflows-scripts/action.yml diff --git a/.github/actions/workflows-scripts/action.yml b/.github/actions/workflows-scripts/action.yml new file mode 100644 index 00000000..6243eaf5 --- /dev/null +++ b/.github/actions/workflows-scripts/action.yml @@ -0,0 +1,75 @@ +name: Locate the workflows scripts +description: >- + Returns the paths of the scripts and configuration files that belong to the + running commit of swiftlang/github-workflows, and can copy the scripts into + the workspace. Reference it with `uses: $/.github/actions/workflows-scripts` + from a workflow in this repository, so the scripts come from the same commit + as the workflow even when a caller pins the workflow to a release. + +inputs: + copy_into_workspace: + description: >- + Whether to copy the scripts into the job's workspace. Set this when the + scripts must be visible somewhere other than on the runner's own file + system, such as inside a container or a virtual machine that is given only + the workspace. Run it after the checkout of the repository under test, + which would otherwise remove the copy. + required: false + default: "false" + +outputs: + scripts_directory: + description: "Absolute path of the scripts directory." + value: ${{ steps.locate.outputs.scripts_directory }} + scripts_directory_windows: + description: "The scripts_directory with backslash separators, for PowerShell." + value: ${{ steps.locate.outputs.scripts_directory_windows }} + scripts_directory_relative: + description: "The scripts directory relative to the workspace. Empty unless copy_into_workspace is true." + value: ${{ steps.locate.outputs.scripts_directory_relative }} + configs_directory: + description: "Absolute path of the directory of shared configuration files. It is never copied." + value: ${{ steps.locate.outputs.configs_directory }} + +runs: + using: composite + steps: + - name: Locate the scripts + id: locate + shell: bash + env: + ACTION_PATH: ${{ github.action_path }} + COPY_INTO_WORKSPACE: ${{ inputs.copy_into_workspace }} + run: | + set -euo pipefail + + # The runner downloads the whole repository at the running commit and + # places this action inside it, so the workflows directory is two levels up. + workflows_directory="${ACTION_PATH//\\//}/../../workflows" + + # Windows keeps the parent references, which its tools resolve themselves; + # elsewhere they are resolved so the paths are clean. + if [ "$RUNNER_OS" != "Windows" ]; then + workflows_directory="$(cd "$workflows_directory" && pwd -P)" + fi + + if [ "$COPY_INTO_WORKSPACE" = "true" ]; then + relative=".github-workflows-scripts" + scripts="$GITHUB_WORKSPACE/$relative" + rm -rf "$scripts" + mkdir -p "$scripts" + cp -R "$workflows_directory/scripts/." "$scripts/" + else + relative="" + scripts="$workflows_directory/scripts" + fi + configs="$workflows_directory/configs" + + { + echo "scripts_directory=$scripts" + # The Windows dispatch invokes the runner through PowerShell, which + # needs backslashes. + echo "scripts_directory_windows=${scripts//\//\\}" + echo "scripts_directory_relative=$relative" + echo "configs_directory=$configs" + } >> "$GITHUB_OUTPUT" From 1918c197d857b08eaa46f3261ca9cb34274d34bb Mon Sep 17 00:00:00 2001 From: Rick Newton-Rogers Date: Fri, 30 Oct 2026 10:02:46 -0400 Subject: [PATCH 2/4] Take the unified workflows' scripts from the running commit `package_test.yml`, `toolchain_matrix.yml`, `benchmarks.yml` and `execute_matrix.yml` resolved their scripts with two steps that checked out the default branch of `workflows_repository`, so a release tag did not pin them. The two inputs existed only to point at a fork, which can instead be tested by calling its workflow at its own ref. Replaced those steps with `uses: $/.github/actions/workflows-scripts`. The generator jobs run `generate-matrix.swift` straight from the action's directory. The `execute_matrix.yml` jobs ask for the copy in the workspace, so the Linux container mount, the Windows prefix translation and the FreeBSD VM's rsync of the workspace keep working without changes to the runner scripts. Removed `workflows_repository` and `workflows_ref` and their forwarding, and updated the FreeBSD test for the new relative directory. --- .github/workflows/benchmarks.yml | 47 ++------------ .github/workflows/execute_matrix.yml | 62 +++---------------- .github/workflows/package_test.yml | 46 ++------------ .github/workflows/toolchain_matrix.yml | 42 ++----------- .../MatrixTestSupport/EntryPoint.swift | 2 +- tests/execute-matrix-tests.sh | 4 +- 6 files changed, 29 insertions(+), 174 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index eef1905f..986f52e9 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -74,15 +74,6 @@ on: type: boolean description: "Whether PRs can be tested together with linked PRs mentioned in the PR description." default: false - - workflows_repository: - type: string - description: "Repository to take the matrix scripts from. Point this at a fork to test a change to the workflows before it lands; it carries no version, so Dependabot has only the `uses:` line to bump." - default: "swiftlang/github-workflows" - workflows_ref: - type: string - description: "Ref to take the scripts from. Empty uses workflows_repository's default branch, which is correct for a released version; set it when workflows_repository is a fork whose default branch does not carry the change." - default: "" jobs: generate-matrix: name: Generate benchmark matrix @@ -94,37 +85,13 @@ jobs: uses: actions/checkout@v7 with: persist-credentials: false - - name: Resolve the workflows source - id: workflows_source - shell: bash - env: - WORKFLOWS_REPOSITORY: ${{ inputs.workflows_repository }} - WORKFLOWS_REF: ${{ inputs.workflows_ref }} - run: | - set -euo pipefail - - # Empty uses the default branch. - echo "ref=$WORKFLOWS_REF" >> $GITHUB_OUTPUT - - if [ "$GITHUB_REPOSITORY" = "$WORKFLOWS_REPOSITORY" ]; then - echo "needs_checkout=false" >> $GITHUB_OUTPUT - echo "root_directory=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT - else - echo "needs_checkout=true" >> $GITHUB_OUTPUT - echo "root_directory=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT - fi - - name: Checkout the workflows repository - if: ${{ steps.workflows_source.outputs.needs_checkout == 'true' }} - uses: actions/checkout@v7 - with: - repository: ${{ inputs.workflows_repository }} - ref: ${{ steps.workflows_source.outputs.ref }} - path: github-workflows - persist-credentials: false + - name: Locate the workflows scripts + id: workflows_scripts + uses: $/.github/actions/workflows-scripts - name: Generate matrix id: generate env: - WORKFLOWS_CHECKOUT: ${{ steps.workflows_source.outputs.root_directory }} + SCRIPTS_DIRECTORY: ${{ steps.workflows_scripts.outputs.scripts_directory }} LINUX_USER_ENV_VARS: ${{ inputs.linux_env_vars }} MACOS_USER_ENV_VARS: ${{ inputs.macos_env_vars }} BENCHMARK_PACKAGE_PATHS: ${{ inputs.benchmark_package_paths }} @@ -185,7 +152,7 @@ jobs: MACOS_COMMAND="$threshold_command" export LINUX_COMMAND MACOS_COMMAND - matrix_yaml=$(swift "${WORKFLOWS_CHECKOUT}/.github/workflows/scripts/matrix/generate-matrix.swift") + matrix_yaml=$(swift "${SCRIPTS_DIRECTORY}/matrix/generate-matrix.swift") echo "matrix_yaml<> $GITHUB_OUTPUT echo "$matrix_yaml" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT @@ -193,12 +160,10 @@ jobs: benchmarks: name: Benchmarks needs: generate-matrix - uses: ./.github/workflows/execute_matrix.yml + uses: $/.github/workflows/execute_matrix.yml with: name: ${{ inputs.name }} matrix_yaml_string: ${{ needs.generate-matrix.outputs.matrix_yaml }} - workflows_repository: ${{ inputs.workflows_repository }} - workflows_ref: ${{ inputs.workflows_ref }} job_timeout: ${{ inputs.job_timeout }} needs_token: ${{ inputs.needs_token }} enable_cross_pr_testing: ${{ inputs.enable_cross_pr_testing }} diff --git a/.github/workflows/execute_matrix.yml b/.github/workflows/execute_matrix.yml index 1e4111a1..c2de8262 100644 --- a/.github/workflows/execute_matrix.yml +++ b/.github/workflows/execute_matrix.yml @@ -42,15 +42,6 @@ on: type: boolean description: "Whether PRs can be tested together with linked PRs mentioned in the PR description" default: false - - workflows_repository: - type: string - description: "Repository to take the matrix scripts from. Point this at a fork to test a change to the workflows before it lands; it carries no version, so Dependabot has only the `uses:` line to bump." - default: "swiftlang/github-workflows" - workflows_ref: - type: string - description: "Ref to take the scripts from. Empty uses workflows_repository's default branch, which is correct for a released version; set it when workflows_repository is a fork whose default branch does not carry the change." - default: "" concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.name }} cancel-in-progress: true @@ -165,44 +156,11 @@ jobs: persist-credentials: false submodules: true - - name: Resolve the workflows source - id: workflows_source - shell: bash - env: - WORKFLOWS_REPOSITORY: ${{ inputs.workflows_repository }} - WORKFLOWS_REF: ${{ inputs.workflows_ref }} - run: | - set -euo pipefail - - # Empty uses the default branch. - echo "ref=$WORKFLOWS_REF" >> $GITHUB_OUTPUT - - if [ "$GITHUB_REPOSITORY" = "$WORKFLOWS_REPOSITORY" ]; then - echo "needs_checkout=false" >> $GITHUB_OUTPUT - root="$GITHUB_WORKSPACE" - scripts_relative=".github/workflows/scripts" - else - echo "needs_checkout=true" >> $GITHUB_OUTPUT - root="$GITHUB_WORKSPACE/github-workflows" - scripts_relative="github-workflows/.github/workflows/scripts" - fi - - echo "root_directory=$root" >> $GITHUB_OUTPUT - # The Windows dispatch invokes the runner through PowerShell, which - # needs backslashes. - echo "root_directory_windows=$(echo "$root" | sed 's|/|\\|g')" >> $GITHUB_OUTPUT - # The FreeBSD VM works in its own copy of the workspace, at a path the host - # does not know, so it rebuilds ${SCRIPTS_ROOT} from this relative form. - echo "scripts_root_relative=$scripts_relative" >> $GITHUB_OUTPUT - - - name: Checkout the workflows repository - if: ${{ steps.workflows_source.outputs.needs_checkout == 'true' }} - uses: actions/checkout@v7 + - name: Locate the workflows scripts + id: workflows_scripts + uses: $/.github/actions/workflows-scripts with: - repository: ${{ inputs.workflows_repository }} - ref: ${{ steps.workflows_source.outputs.ref }} - path: github-workflows - persist-credentials: false + copy_into_workspace: true # Linux and Windows install their toolchain inside the job runner, so they # check out linked PRs there afterwards, to compile the checkout script with @@ -213,7 +171,7 @@ jobs: if: ${{ inputs.enable_cross_pr_testing && github.event_name == 'pull_request' && matrix.config.platform == 'macOS' }} shell: bash env: - SCRIPTS_ROOT: ${{ steps.workflows_source.outputs.root_directory }}/.github/workflows/scripts + SCRIPTS_ROOT: ${{ steps.workflows_scripts.outputs.scripts_directory }} PR_REPO: ${{ github.repository }} PR_NUMBER: ${{ github.event.number }} run: | @@ -226,7 +184,7 @@ jobs: shell: bash env: GITHUB_TOKEN: ${{ inputs.needs_token && secrets.GITHUB_TOKEN || '' }} - SCRIPTS_ROOT: ${{ steps.workflows_source.outputs.root_directory }}/.github/workflows/scripts + SCRIPTS_ROOT: ${{ steps.workflows_scripts.outputs.scripts_directory }} CONTAINER_JSON: ${{ toJson(matrix.config.swift_build.container) }} MATRIX_SWIFT_VERSION: ${{ matrix.config.swift_build.swift_version }} MATRIX_TOOLCHAIN: ${{ matrix.config.swift_build.toolchain }} @@ -265,7 +223,7 @@ jobs: timeout-minutes: ${{ matrix.config.timeout_minutes || inputs.job_timeout }} shell: bash env: - SCRIPTS_ROOT: ${{ steps.workflows_source.outputs.root_directory }}/.github/workflows/scripts + SCRIPTS_ROOT: ${{ steps.workflows_scripts.outputs.scripts_directory }} SDK_TRIPLES: ${{ join(matrix.config.swift_build.sdk.triples, ' --android-sdk-triple=') }} run: | # The SDK build exported the matched toolchain and SDK bundle to GITHUB_ENV for @@ -280,7 +238,7 @@ jobs: timeout-minutes: ${{ matrix.config.timeout_minutes || inputs.job_timeout }} env: GITHUB_TOKEN: ${{ inputs.needs_token && secrets.GITHUB_TOKEN || '' }} - SCRIPTS_ROOT: ${{ steps.workflows_source.outputs.root_directory_windows }}\.github\workflows\scripts + SCRIPTS_ROOT: ${{ steps.workflows_scripts.outputs.scripts_directory_windows }} CONTAINER_IMAGE: ${{ matrix.config.swift_build.container.image }} CROSS_PR_TESTING: ${{ inputs.enable_cross_pr_testing && github.event_name == 'pull_request' }} CROSS_PR_REPO: ${{ github.repository }} @@ -308,7 +266,7 @@ jobs: shell: bash env: GITHUB_TOKEN: ${{ inputs.needs_token && secrets.GITHUB_TOKEN || '' }} - SCRIPTS_ROOT: ${{ steps.workflows_source.outputs.root_directory }}/.github/workflows/scripts + SCRIPTS_ROOT: ${{ steps.workflows_scripts.outputs.scripts_directory }} XCODE_TARGETS_JSON: ${{ toJson(matrix.config.xcode_build.targets) }} XCODE_DEBUG_OUTPUT: ${{ matrix.config.xcode_build.debug_output }} SWIFTLY_TOOLCHAIN: ${{ matrix.config.xcode_build.swiftly_toolchain }} @@ -399,7 +357,7 @@ jobs: MATRIX_COMMAND: ${{ matrix.config.command }} # Shell-quoted on the host, where jq is available. MATRIX_COMMAND_ARGUMENTS: ${{ steps.freebsd_arguments.outputs.command_arguments }} - SCRIPTS_ROOT_RELATIVE: ${{ steps.workflows_source.outputs.scripts_root_relative }} + SCRIPTS_ROOT_RELATIVE: ${{ steps.workflows_scripts.outputs.scripts_directory_relative }} GITHUB_TOKEN: ${{ inputs.needs_token && secrets.GITHUB_TOKEN || '' }} CROSS_PR_TESTING: ${{ inputs.enable_cross_pr_testing && github.event_name == 'pull_request' }} CROSS_PR_REPO: ${{ github.repository }} diff --git a/.github/workflows/package_test.yml b/.github/workflows/package_test.yml index db859f75..c20db540 100644 --- a/.github/workflows/package_test.yml +++ b/.github/workflows/package_test.yml @@ -404,15 +404,6 @@ on: type: number description: "Timeout in minutes for FreeBSD jobs. 0 uses job_timeout. FreeBSD provisions a virtual machine and installs a toolchain into it before building, so it often needs longer." default: 0 - - workflows_repository: - type: string - description: "Repository to take the matrix scripts from. Point this at a fork to test a change to the workflows before it lands; it carries no version, so Dependabot has only the `uses:` line to bump." - default: "swiftlang/github-workflows" - workflows_ref: - type: string - description: "Ref to take the scripts from. Empty uses workflows_repository's default branch, which is correct for a released version; set it when workflows_repository is a fork whose default branch does not carry the change." - default: "" jobs: generate-matrix: name: Generate test matrix @@ -425,44 +416,19 @@ jobs: with: persist-credentials: false - - name: Resolve the workflows source - id: workflows_source - shell: bash - env: - WORKFLOWS_REPOSITORY: ${{ inputs.workflows_repository }} - WORKFLOWS_REF: ${{ inputs.workflows_ref }} - run: | - set -euo pipefail - - # Empty uses the default branch. - echo "ref=$WORKFLOWS_REF" >> $GITHUB_OUTPUT - - if [ "$GITHUB_REPOSITORY" = "$WORKFLOWS_REPOSITORY" ]; then - echo "needs_checkout=false" >> $GITHUB_OUTPUT - echo "root_directory=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT - else - echo "needs_checkout=true" >> $GITHUB_OUTPUT - echo "root_directory=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT - fi - - - name: Checkout the workflows repository - if: ${{ steps.workflows_source.outputs.needs_checkout == 'true' }} - uses: actions/checkout@v7 - with: - repository: ${{ inputs.workflows_repository }} - ref: ${{ steps.workflows_source.outputs.ref }} - path: github-workflows - persist-credentials: false + - name: Locate the workflows scripts + id: workflows_scripts + uses: $/.github/actions/workflows-scripts - name: Generate matrix id: generate run: | - matrix_yaml=$(swift "${WORKFLOWS_CHECKOUT}/.github/workflows/scripts/matrix/generate-matrix.swift") + matrix_yaml=$(swift "${SCRIPTS_DIRECTORY}/matrix/generate-matrix.swift") echo "matrix_yaml<> $GITHUB_OUTPUT echo "$matrix_yaml" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT env: - WORKFLOWS_CHECKOUT: ${{ steps.workflows_source.outputs.root_directory }} + SCRIPTS_DIRECTORY: ${{ steps.workflows_scripts.outputs.scripts_directory }} ENABLE_LINUX: ${{ inputs.enable_linux }} ENABLE_MACOS: ${{ inputs.enable_macos }} ENABLE_MACOS_SWIFTLY: ${{ inputs.enable_macos_swiftly }} @@ -547,8 +513,6 @@ jobs: with: name: ${{ inputs.name }} matrix_yaml_string: ${{ needs.generate-matrix.outputs.matrix_yaml }} - workflows_repository: ${{ inputs.workflows_repository }} - workflows_ref: ${{ inputs.workflows_ref }} job_timeout: ${{ inputs.job_timeout }} needs_token: ${{ inputs.needs_token }} enable_cross_pr_testing: ${{ inputs.enable_cross_pr_testing }} diff --git a/.github/workflows/toolchain_matrix.yml b/.github/workflows/toolchain_matrix.yml index e5025498..242a8c44 100644 --- a/.github/workflows/toolchain_matrix.yml +++ b/.github/workflows/toolchain_matrix.yml @@ -128,14 +128,6 @@ on: type: boolean description: "Check subdirectory Package.swift files when detecting the minimum version." default: false - workflows_repository: - type: string - description: "Repository to take the matrix scripts from. Point this at a fork to test a change to the workflows before it lands; it carries no version, so Dependabot has only the `uses:` line to bump." - default: "swiftlang/github-workflows" - workflows_ref: - type: string - description: "Ref to take the scripts from. Empty uses workflows_repository's default branch, which is correct for a released version; set it when workflows_repository is a fork whose default branch does not carry the change." - default: "" outputs: matrix_yaml: description: "The toolchain matrix, as a YAML string to pass to execute_matrix.yml." @@ -152,37 +144,13 @@ jobs: uses: actions/checkout@v7 with: persist-credentials: false - - name: Resolve the workflows source - id: workflows_source - shell: bash - env: - WORKFLOWS_REPOSITORY: ${{ inputs.workflows_repository }} - WORKFLOWS_REF: ${{ inputs.workflows_ref }} - run: | - set -euo pipefail - - # Empty uses the default branch. - echo "ref=$WORKFLOWS_REF" >> $GITHUB_OUTPUT - - if [ "$GITHUB_REPOSITORY" = "$WORKFLOWS_REPOSITORY" ]; then - echo "needs_checkout=false" >> $GITHUB_OUTPUT - echo "root_directory=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT - else - echo "needs_checkout=true" >> $GITHUB_OUTPUT - echo "root_directory=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT - fi - - name: Checkout the workflows repository - if: ${{ steps.workflows_source.outputs.needs_checkout == 'true' }} - uses: actions/checkout@v7 - with: - repository: ${{ inputs.workflows_repository }} - ref: ${{ steps.workflows_source.outputs.ref }} - path: github-workflows - persist-credentials: false + - name: Locate the workflows scripts + id: workflows_scripts + uses: $/.github/actions/workflows-scripts - name: Generate matrix id: generate env: - WORKFLOWS_CHECKOUT: ${{ steps.workflows_source.outputs.root_directory }} + SCRIPTS_DIRECTORY: ${{ steps.workflows_scripts.outputs.scripts_directory }} MATRIX_MODE: toolchains ENABLE_LINUX: ${{ inputs.enable_linux }} ENABLE_MACOS: ${{ inputs.enable_macos }} @@ -215,7 +183,7 @@ jobs: SWIFT_NIGHTLY_RELEASE: ${{ inputs.swift_nightly_release }} ENABLE_SUBDIRECTORY_MANIFEST_SEARCH: ${{ inputs.find_subdirectory_manifests }} run: | - matrix_yaml=$(swift "${WORKFLOWS_CHECKOUT}/.github/workflows/scripts/matrix/generate-matrix.swift") + matrix_yaml=$(swift "${SCRIPTS_DIRECTORY}/matrix/generate-matrix.swift") echo "matrix_yaml<> $GITHUB_OUTPUT echo "$matrix_yaml" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT diff --git a/tests/MatrixGeneratorValidator/Sources/MatrixTestSupport/EntryPoint.swift b/tests/MatrixGeneratorValidator/Sources/MatrixTestSupport/EntryPoint.swift index 21bdbabd..019b5230 100644 --- a/tests/MatrixGeneratorValidator/Sources/MatrixTestSupport/EntryPoint.swift +++ b/tests/MatrixGeneratorValidator/Sources/MatrixTestSupport/EntryPoint.swift @@ -79,7 +79,7 @@ public struct EntryPoint: Sendable, CustomStringConvertible { } resolved[key] = declared } else if value.contains("${{ steps.") { - // Where the scripts were checked out, which the test supplies itself. + // Where the scripts were placed, which the test supplies itself. continue } else if value.contains("${{") { throw WorkflowError.unresolvedExpression(workflow: workflow, key: key, value: value) diff --git a/tests/execute-matrix-tests.sh b/tests/execute-matrix-tests.sh index b0e8cf76..f87383c7 100755 --- a/tests/execute-matrix-tests.sh +++ b/tests/execute-matrix-tests.sh @@ -39,7 +39,7 @@ WORKDIR=$(mktemp -d) trap 'rm -rf "$WORKDIR"' EXIT STUB_DIR="$WORKDIR/stubs" -VM_SCRIPTS_DIR="$WORKDIR/github-workflows/.github/workflows/scripts" +VM_SCRIPTS_DIR="$WORKDIR/.github-workflows-scripts" mkdir -p "$STUB_DIR" "$VM_SCRIPTS_DIR" # The FreeBSD script reports the toolchain version before it builds. @@ -127,7 +127,7 @@ run_freebsd_vm() { MATRIX_COMMAND="$WORKDIR/record.sh" \ MATRIX_COMMAND_ARGUMENTS="$3" \ BUILD_FLAGS="--from-build-flags" \ - SCRIPTS_ROOT_RELATIVE="github-workflows/.github/workflows/scripts" \ + SCRIPTS_ROOT_RELATIVE=".github-workflows-scripts" \ CROSS_PR_TESTING="false" \ sh -c "$FREEBSD_RUN_SCRIPT" >"$STEP_LOG" 2>&1 ) From 5a4bb17f76f82e7d4cc8dd61f0e4f558f1233853 Mon Sep 17 00:00:00 2001 From: Rick Newton-Rogers Date: Fri, 30 Oct 2026 10:02:46 -0400 Subject: [PATCH 3/4] Take the soundness scripts and configs from the running commit Each soundness job that ran a script or read a default configuration file checked out `swiftlang/github-workflows` from the default branch, so a release of `soundness.yml` did not pin them. Replaced the checkout and the script-root step in eight jobs with `uses: $/.github/actions/workflows-scripts` and read the scripts and configs from its outputs. The jobs use the action's own directory, with no copy, since none of them runs the scripts in another file system. --- .github/workflows/soundness.yml | 168 ++++++++------------------------ 1 file changed, 40 insertions(+), 128 deletions(-) diff --git a/.github/workflows/soundness.yml b/.github/workflows/soundness.yml index 9c5663d2..d3f9905e 100644 --- a/.github/workflows/soundness.yml +++ b/.github/workflows/soundness.yml @@ -196,26 +196,15 @@ jobs: with: persist-credentials: false submodules: true - - name: Checkout swiftlang/github-workflows repository - if: ${{ github.repository != 'swiftlang/github-workflows' }} - uses: actions/checkout@v7 - with: - repository: swiftlang/github-workflows - path: github-workflows - - name: Determine script-root path - id: script_path - run: | - if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then - echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT - else - echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT - fi + - name: Locate the workflows scripts + id: workflows_scripts + uses: $/.github/actions/workflows-scripts - name: Run documentation check env: ADDITIONAL_DOCC_ARGUMENTS: ${{ inputs.docs_check_additional_arguments }} DOCC_ANALYZE: ${{ inputs.docs_check_analyze }} DOCS_TARGETS: ${{ inputs.docs_check_targets}} - SCRIPT_ROOT: ${{ steps.script_path.outputs.root }} + SCRIPTS_DIRECTORY: ${{ steps.workflows_scripts.outputs.scripts_directory }} run: | set -x ${{ inputs.linux_pre_build_command }} @@ -227,7 +216,7 @@ jobs: if [ "${DOCC_ANALYZE}" != "true" ]; then analyze_arg="--no-analyze" fi - "${SCRIPT_ROOT}/.github/workflows/scripts/check-docs.sh" ${analyze_arg} ${doc_target_arg} --additional-docc-arguments ${ADDITIONAL_DOCC_ARGUMENTS} + "${SCRIPTS_DIRECTORY}/check-docs.sh" ${analyze_arg} ${doc_target_arg} --additional-docc-arguments ${ADDITIONAL_DOCC_ARGUMENTS} docs-check-macos: name: Documentation check (macOS) @@ -240,20 +229,9 @@ jobs: with: persist-credentials: false submodules: true - - name: Checkout swiftlang/github-workflows repository - if: ${{ github.repository != 'swiftlang/github-workflows' }} - uses: actions/checkout@v7 - with: - repository: swiftlang/github-workflows - path: github-workflows - - name: Determine script-root path - id: script_path - run: | - if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then - echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT - else - echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT - fi + - name: Locate the workflows scripts + id: workflows_scripts + uses: $/.github/actions/workflows-scripts - name: Select Xcode env: XCODE_VERSION: ${{ inputs.docs_check_macos_xcode_version }} @@ -267,7 +245,7 @@ jobs: ADDITIONAL_DOCC_ARGUMENTS: ${{ inputs.docs_check_macos_additional_arguments }} DOCC_ANALYZE: ${{ inputs.docs_check_macos_analyze }} DOCS_TARGETS: ${{ inputs.docs_check_macos_targets}} - SCRIPT_ROOT: ${{ steps.script_path.outputs.root }} + SCRIPTS_DIRECTORY: ${{ steps.workflows_scripts.outputs.scripts_directory }} run: | analyze_arg="" doc_target_arg="" @@ -278,7 +256,7 @@ jobs: analyze_arg="--no-analyze" fi set -x - "${SCRIPT_ROOT}/.github/workflows/scripts/check-docs.sh" ${analyze_arg} ${doc_target_arg} --additional-docc-arguments ${ADDITIONAL_DOCC_ARGUMENTS} + "${SCRIPTS_DIRECTORY}/check-docs.sh" ${analyze_arg} ${doc_target_arg} --additional-docc-arguments ${ADDITIONAL_DOCC_ARGUMENTS} unacceptable-language-check: name: Unacceptable language check @@ -291,25 +269,14 @@ jobs: with: persist-credentials: false submodules: true - - name: Checkout swiftlang/github-workflows repository - if: ${{ github.repository != 'swiftlang/github-workflows' }} - uses: actions/checkout@v7 - with: - repository: swiftlang/github-workflows - path: github-workflows - - name: Determine script-root path - id: script_path - run: | - if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then - echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT - else - echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT - fi + - name: Locate the workflows scripts + id: workflows_scripts + uses: $/.github/actions/workflows-scripts - name: Run unacceptable language check env: UNACCEPTABLE_WORD_LIST: ${{ inputs.unacceptable_language_check_word_list}} - SCRIPT_ROOT: ${{ steps.script_path.outputs.root }} - run: ${SCRIPT_ROOT}/.github/workflows/scripts/check-unacceptable-language.sh + SCRIPTS_DIRECTORY: ${{ steps.workflows_scripts.outputs.scripts_directory }} + run: ${SCRIPTS_DIRECTORY}/check-unacceptable-language.sh license-header-check: name: License headers check @@ -322,25 +289,14 @@ jobs: with: persist-credentials: false submodules: true - - name: Checkout swiftlang/github-workflows repository - if: ${{ github.repository != 'swiftlang/github-workflows' }} - uses: actions/checkout@v7 - with: - repository: swiftlang/github-workflows - path: github-workflows - - name: Determine script-root path - id: script_path - run: | - if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then - echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT - else - echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT - fi + - name: Locate the workflows scripts + id: workflows_scripts + uses: $/.github/actions/workflows-scripts - name: Run license header check env: PROJECT_NAME: ${{ inputs.license_header_check_project_name }} - SCRIPT_ROOT: ${{ steps.script_path.outputs.root }} - run: ${SCRIPT_ROOT}/.github/workflows/scripts/check-license-header.sh + SCRIPTS_DIRECTORY: ${{ steps.workflows_scripts.outputs.scripts_directory }} + run: ${SCRIPTS_DIRECTORY}/check-license-header.sh broken-symlink-check: name: Broken symlinks check @@ -353,24 +309,13 @@ jobs: with: persist-credentials: false submodules: true - - name: Checkout swiftlang/github-workflows repository - if: ${{ github.repository != 'swiftlang/github-workflows' }} - uses: actions/checkout@v7 - with: - repository: swiftlang/github-workflows - path: github-workflows - - name: Determine script-root path - id: script_path - run: | - if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then - echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT - else - echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT - fi + - name: Locate the workflows scripts + id: workflows_scripts + uses: $/.github/actions/workflows-scripts - name: Run broken symlinks check env: - SCRIPT_ROOT: ${{ steps.script_path.outputs.root }} - run: ${SCRIPT_ROOT}/.github/workflows/scripts/check-broken-symlinks.sh + SCRIPTS_DIRECTORY: ${{ steps.workflows_scripts.outputs.scripts_directory }} + run: ${SCRIPTS_DIRECTORY}/check-broken-symlinks.sh format-check: name: Format check @@ -385,27 +330,16 @@ jobs: with: persist-credentials: false submodules: true - - name: Checkout swiftlang/github-workflows repository - if: ${{ github.repository != 'swiftlang/github-workflows' }} - uses: actions/checkout@v7 - with: - repository: swiftlang/github-workflows - path: github-workflows - - name: Determine script-root path - id: script_path - run: | - if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then - echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT - else - echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT - fi + - name: Locate the workflows scripts + id: workflows_scripts + uses: $/.github/actions/workflows-scripts - name: Mark the workspace as safe # https://github.com/actions/checkout/issues/766 run: git config --global --add safe.directory ${GITHUB_WORKSPACE} - name: Run format check env: - SCRIPT_ROOT: ${{ steps.script_path.outputs.root }} - run: ${SCRIPT_ROOT}/.github/workflows/scripts/check-swift-format.sh + SCRIPTS_DIRECTORY: ${{ steps.workflows_scripts.outputs.scripts_directory }} + run: ${SCRIPTS_DIRECTORY}/check-swift-format.sh shell-check: name: Shell check @@ -441,29 +375,18 @@ jobs: with: persist-credentials: false submodules: true - - name: Checkout swiftlang/github-workflows repository - if: ${{ github.repository != 'swiftlang/github-workflows' }} - uses: actions/checkout@v7 - with: - repository: swiftlang/github-workflows - path: github-workflows - - name: Determine script-root path - id: script_path - run: | - if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then - echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT - else - echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT - fi + - name: Locate the workflows scripts + id: workflows_scripts + uses: $/.github/actions/workflows-scripts - name: Run yamllint env: - SCRIPT_ROOT: ${{ steps.script_path.outputs.root }} + CONFIGS_DIRECTORY: ${{ steps.workflows_scripts.outputs.configs_directory }} run: | which yamllint || (apt -q update && apt install -yq yamllint) cd ${GITHUB_WORKSPACE} if [ ! -f ".yamllint.yml" ]; then echo "Downloading default yamllint config file" - cat "${SCRIPT_ROOT}/.github/workflows/configs/yamllint.yml" > .yamllint.yml + cat "${CONFIGS_DIRECTORY}/yamllint.yml" > .yamllint.yml fi yamllint --strict --config-file .yamllint.yml . @@ -478,28 +401,17 @@ jobs: with: persist-credentials: false submodules: true - - name: Checkout swiftlang/github-workflows repository - if: ${{ github.repository != 'swiftlang/github-workflows' }} - uses: actions/checkout@v7 - with: - repository: swiftlang/github-workflows - path: github-workflows - - name: Determine script-root path - id: script_path - run: | - if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then - echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT - else - echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT - fi + - name: Locate the workflows scripts + id: workflows_scripts + uses: $/.github/actions/workflows-scripts - name: Run flake8 env: - SCRIPT_ROOT: ${{ steps.script_path.outputs.root }} + CONFIGS_DIRECTORY: ${{ steps.workflows_scripts.outputs.configs_directory }} run: | pip3 install flake8 flake8-import-order cd ${GITHUB_WORKSPACE} if [ ! -f ".flake8" ]; then echo "Downloading default flake8 config file" - cat "${SCRIPT_ROOT}/.github/workflows/configs/.flake8" > .flake8 + cat "${CONFIGS_DIRECTORY}/.flake8" > .flake8 fi flake8 From fed9948243bf89277d3d4fe3d1dd4c57132eef88 Mon Sep 17 00:00:00 2001 From: Rick Newton-Rogers Date: Fri, 30 Oct 2026 10:02:46 -0400 Subject: [PATCH 4/4] Describe how the workflows get their scripts Requirement 3 of the architecture document said the scripts are checked out explicitly, which no longer describes how they are found. The document also never said what `$/` means, what the `workflows-scripts` action returns, or how to test a change now that `workflows_repository` and `workflows_ref` are gone. Rewrote requirement 3 to state only the requirement, that the scripts come from the commit of the running workflow, and moved the mechanism into a "How workflows get their scripts" section. It explains `$/`, tabulates the action's input and outputs, notes that `execute_matrix.yml` copies the scripts for containers and the FreeBSD VM after the checkout, and says a change is tested by calling the workflow at its own branch or fork ref. Listed the action among the components. --- .github/workflows/architecture.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/architecture.md b/.github/workflows/architecture.md index ce6c3d3b..0760c290 100644 --- a/.github/workflows/architecture.md +++ b/.github/workflows/architecture.md @@ -14,9 +14,9 @@ Workflows and actions must be versioned using GitHub releases to guarantee stabi No reusable workflow offered should show skipped jobs. Skipped jobs add visual noise, look confusing to contributors and imply that something is configured incorrectly. -### 3. Scripts must be cloned not curled +### 3. Scripts must come from the workflow's own commit -Some jobs rely on scripts to execute their functionality. This is often preferred over inline scripts in the workflow definition `yml` files since it gives a better developer experience to the maintainers of those scripts. However, one downside of this approach is that these scripts are not present when a reusable workflow is executed from another repository. It is important that a unified solution checks the scripts out explicitly rather than curling them, since curling frequently runs into rate limiting with GitHub's API. +Some jobs rely on scripts to execute their functionality. This is often preferred over inline scripts in the workflow definition `yml` files since it gives a better developer experience to the maintainers of those scripts. However, one downside of this approach is that these scripts are not present when a reusable workflow is executed from another repository, and a script taken from the default branch does not match a workflow pinned to a release. It is important that a unified solution takes the scripts from the same commit as the workflow, and does so without curling them, since curling frequently runs into rate limiting with GitHub's API. "How workflows get their scripts" below describes how this is done. ### 4. Custom matrix builds @@ -43,8 +43,27 @@ The design is intended to be layered, so that adopters may use the whole stack o * `toolchain_matrix.yml`: produces a matrix of toolchains with no command attached, for callers supplying their own * `execute_matrix.yml`: expands a work definition into one job per entry and dispatches each to its platform. FreeBSD runs in a VM step here rather than through a job runner * `generate-matrix.swift`: produces the canonical work definition YAML or JSON. It reads the workflow's inputs from the environment and fails the run rather than emitting a matrix that would silently drop a job the caller asked for +* `.github/actions/workflows-scripts`: the composite action that makes the scripts and configuration files of the running commit available and returns their paths * `job-runner-linux.sh`, `job-runner-macos.sh`, `job-runner-windows.ps1`: helper scripts which execute the defined work. They handle the complexity of running inside/outside Docker and installing Swift if required. +## How workflows get their scripts + +`$/` in a `uses:` reference means the repository that holds the running workflow file, at the commit that file was loaded from. In a workflow called from another repository it still means this repository, at the tag or branch the caller named. Every workflow here reaches its scripts with `uses: $/.github/actions/workflows-scripts`, so the scripts always match the workflow that runs them. See "Workflow syntax for GitHub Actions" in GitHub's documentation for the `$/` syntax. + +The action takes one input and returns four outputs: + +| Name | Meaning | +| --- | --- | +| `copy_into_workspace` (input) | Copy the scripts into `.github-workflows-scripts` in the workspace. Defaults to `false`. | +| `scripts_directory` | Absolute path of the scripts. | +| `scripts_directory_windows` | `scripts_directory` with backslashes, for PowerShell. | +| `scripts_directory_relative` | The scripts directory relative to the workspace. Empty unless `copy_into_workspace` is `true`. | +| `configs_directory` | Absolute path of the shared configuration files. It is never copied. | + +`execute_matrix.yml` sets `copy_into_workspace` because the Linux and Windows containers and the FreeBSD VM are given only the workspace; the job runners read the copy there. The other workflows use the scripts where the runner placed them. A job that sets `copy_into_workspace` must run the action after checking out the repository under test; otherwise the checkout removes the copy. + +To test a change to the workflows or scripts, call the workflow at the branch or fork that holds it, for example `rnro/github-workflows/.github/workflows/package_test.yml@my-branch`. The scripts come from that same ref, so no separate input names the repository or ref for them. + ## Swift version labels A version list names toolchains with labels: a version such as `6.3`, `nightly-release`, `nightly-main`, or a position counted back from the current release, `current`, `current-1` and `current-2`. A job's name carries the label the caller wrote. Job names are required status checks, so the default lists use the relative labels: a new Swift release changes which versions they name and leaves the checks alone. A caller who writes versions out gets those names, and the two spellings can be mixed in one list.