diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6381e0645742..a02566c916a2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -107,6 +107,10 @@ jobs: BACKLOG_THRESHOLD: "10" BACKLOG_THRESHOLD_ARM64: "30" GH_TOKEN: ${{ github.token }} + # Runners used unless the backlog or a blacksmith-ci label moves a + # run to RUNNER_AMD64/RUNNER_ARM64. Unset, they are GitHub-hosted. + RUNNER_DEFAULT_AMD64_VAR: ${{ vars.RUNNER_DEFAULT_AMD64 }} + RUNNER_DEFAULT_ARM64_VAR: ${{ vars.RUNNER_DEFAULT_ARM64 }} RUNNER_AMD64_VAR: ${{ vars.RUNNER_AMD64 }} RUNNER_ARM64_VAR: ${{ vars.RUNNER_ARM64 }} # Self-hosted rung for the arm64 pull request container builds. They @@ -115,11 +119,11 @@ jobs: RUNNER_SELFHOSTED_ARM64_VAR: ${{ vars.RUNNER_SELFHOSTED_ARM64 }} SELFHOSTED_LINT_AUTHORS: ${{ vars.SELFHOSTED_LINT_AUTHORS }} SELFHOSTED_MAX_QUEUED: "6" - # The amd64 jobs run in two tiers, each GitHub-hosted, then its own - # self-hosted label, then RUNNER_AMD64. Only trusted runs (the - # allowlist above, plus any push to develop or master) leave - # GitHub-hosted. Unset a tier's label to skip its self-hosted step; - # see ci/README.md. + # The amd64 jobs run in two tiers, each on RUNNER_DEFAULT_AMD64, then + # its own self-hosted label, then RUNNER_AMD64. Only trusted runs + # (the allowlist above, plus any push to develop or master) leave + # the default runner. Unset a tier's label to skip its self-hosted + # step; see ci/README.md. RUNNER_SELFHOSTED_FAST_VAR: ${{ vars.RUNNER_SELFHOSTED_FAST }} FAST_SELFHOSTED_QUEUE_LIMIT: "16" RUNNER_SELFHOSTED_BUILD_VAR: ${{ vars.RUNNER_SELFHOSTED_BUILD }} @@ -173,7 +177,7 @@ jobs: runs-on: ${{ needs.check-skip.outputs['runner-fast'] }} # Pushes build the trusted-* images that later runs reuse, so they stay on - # GitHub's runners; only pull request builds use the fast tier. + # the amd64/arm64 runners; only pull request builds use the fast tier. container: permissions: contents: read @@ -629,7 +633,7 @@ jobs: !cancelled() && github.event_name == 'push' && github.ref_name == github.event.repository.default_branch - runs-on: ubuntu-24.04-arm + runs-on: ${{ vars.RUNNER_DEFAULT_ARM64 || 'ubuntu-24.04-arm' }} permissions: actions: write steps: diff --git a/.github/workflows/cache-depends-sources.yml b/.github/workflows/cache-depends-sources.yml index 18d8a80b9b36..548f886b2fd6 100644 --- a/.github/workflows/cache-depends-sources.yml +++ b/.github/workflows/cache-depends-sources.yml @@ -7,7 +7,7 @@ on: description: "Runner label to use" required: false type: string - default: ubuntu-24.04-arm + default: "" schedule: # Run daily at 6 AM UTC on the default branch to keep cache warm - cron: '0 6 * * *' @@ -18,11 +18,12 @@ permissions: jobs: cache-sources: name: Cache depends sources - # Intentionally keep scheduled cache warming on GitHub-hosted ARM runners. - # Blacksmith caches are expected to persist long enough without a warmup cron. + # Scheduled cache warming runs on RUNNER_CACHE_DEPENDS_SOURCES, by default a + # GitHub-hosted ARM runner. Blacksmith caches are expected to persist long + # enough without a warmup cron. # build.yml passes its fast tier runner, which may be self-hosted; the cache # key does not name the runner, so any arch can fill it. - runs-on: ${{ inputs.runs-on || 'ubuntu-24.04-arm' }} + runs-on: ${{ inputs.runs-on || vars.RUNNER_CACHE_DEPENDS_SOURCES || 'ubuntu-24.04-arm' }} steps: - name: Checkout code uses: actions/checkout@v6 diff --git a/.github/workflows/check-milestone.yml b/.github/workflows/check-milestone.yml index c8e1057fd6c6..fc742732fc3b 100644 --- a/.github/workflows/check-milestone.yml +++ b/.github/workflows/check-milestone.yml @@ -17,7 +17,7 @@ jobs: main: name: Require a milestone if: ${{ github.repository == 'dashpay/dash' && !github.event.pull_request.draft }} - runs-on: ${{ vars.DASH_LIGHT_RUNNER || 'ubuntu-latest' }} + runs-on: ${{ vars.RUNNER_CHECK_MILESTONE || vars.DASH_LIGHT_RUNNER || 'ubuntu-latest' }} steps: - name: Check that a milestone is set env: diff --git a/.github/workflows/clang-diff-format.yml b/.github/workflows/clang-diff-format.yml index cc8791a99f0f..1d10dc8e6fca 100644 --- a/.github/workflows/clang-diff-format.yml +++ b/.github/workflows/clang-diff-format.yml @@ -10,7 +10,7 @@ permissions: jobs: ClangFormat: - runs-on: ubuntu-latest + runs-on: ${{ vars.RUNNER_CLANG_DIFF_FORMAT || 'ubuntu-latest' }} steps: - name: Checkout uses: actions/checkout@v6 diff --git a/.github/workflows/guix-build.yml b/.github/workflows/guix-build.yml index cb6f679e9ab8..680ed9647b9e 100644 --- a/.github/workflows/guix-build.yml +++ b/.github/workflows/guix-build.yml @@ -14,7 +14,7 @@ jobs: build-image: permissions: packages: write - runs-on: ubuntu-24.04-arm + runs-on: ${{ vars.RUNNER_GUIX_BUILD || 'ubuntu-24.04-arm' }} if: | (github.event_name == 'push' && (startsWith(github.ref, 'refs/tags/') || vars.RUN_GUIX_ON_ALL_PUSH == 'true')) || contains(github.event.pull_request.labels.*.name, 'guix-build') || @@ -79,7 +79,7 @@ jobs: build: needs: build-image # runs-on: [ "self-hosted", "linux", "x64", "ubuntu-core" ] - runs-on: ubuntu-24.04-arm + runs-on: ${{ vars.RUNNER_GUIX_BUILD || 'ubuntu-24.04-arm' }} permissions: packages: read id-token: write @@ -186,7 +186,7 @@ jobs: attest: needs: build if: github.repository == 'dashpay/dash' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') - runs-on: ubuntu-24.04 + runs-on: ${{ vars.RUNNER_GUIX_ATTEST || 'ubuntu-24.04' }} permissions: id-token: write attestations: write diff --git a/.github/workflows/label-merge-conflicts.yml b/.github/workflows/label-merge-conflicts.yml index 6fc296d38bbe..8af631d2e17a 100644 --- a/.github/workflows/label-merge-conflicts.yml +++ b/.github/workflows/label-merge-conflicts.yml @@ -25,7 +25,7 @@ permissions: jobs: main: - runs-on: ${{ vars.DASH_LIGHT_RUNNER || 'ubuntu-latest' }} + runs-on: ${{ vars.RUNNER_LABEL_MERGE_CONFLICTS || vars.DASH_LIGHT_RUNNER || 'ubuntu-latest' }} steps: - name: check if prs are dirty uses: eps1lon/actions-label-merge-conflict@v3.1.0 diff --git a/.github/workflows/merge-check.yml b/.github/workflows/merge-check.yml index 274e92c61c8a..b8dacfec07ff 100644 --- a/.github/workflows/merge-check.yml +++ b/.github/workflows/merge-check.yml @@ -14,7 +14,7 @@ on: jobs: check_merge: - runs-on: ${{ vars.DASH_LIGHT_RUNNER || 'ubuntu-latest' }} + runs-on: ${{ vars.RUNNER_MERGE_CHECK || vars.DASH_LIGHT_RUNNER || 'ubuntu-latest' }} steps: - name: Checkout repository uses: actions/checkout@v6 diff --git a/.github/workflows/predict-conflicts.yml b/.github/workflows/predict-conflicts.yml index f9bf9dde931c..7d23e2ecdff2 100644 --- a/.github/workflows/predict-conflicts.yml +++ b/.github/workflows/predict-conflicts.yml @@ -24,7 +24,7 @@ jobs: predict_conflicts: # Forks also run scheduled workflows. if: github.repository == 'dashpay/dash' - runs-on: ${{ vars.DASH_LIGHT_RUNNER || 'ubuntu-latest' }} + runs-on: ${{ vars.RUNNER_PREDICT_CONFLICTS || vars.DASH_LIGHT_RUNNER || 'ubuntu-latest' }} timeout-minutes: 20 concurrency: group: predict-conflicts diff --git a/.github/workflows/prevent-master-pr.yml b/.github/workflows/prevent-master-pr.yml index ae76ec87c1c8..ab096b71eb4a 100644 --- a/.github/workflows/prevent-master-pr.yml +++ b/.github/workflows/prevent-master-pr.yml @@ -9,7 +9,7 @@ permissions: {} jobs: fail: - runs-on: ubuntu-latest + runs-on: ${{ vars.RUNNER_PREVENT_MASTER_PR || 'ubuntu-latest' }} steps: - run: exit 1 diff --git a/.github/workflows/select_dynamic_runner.py b/.github/workflows/select_dynamic_runner.py index c673a1533569..86853a7eb789 100644 --- a/.github/workflows/select_dynamic_runner.py +++ b/.github/workflows/select_dynamic_runner.py @@ -25,6 +25,11 @@ # Prefixes — any label starting with one of these indicates a non-GitHub-hosted runner NON_GITHUB_HOSTED_RUNNER_PREFIXES = ("blacksmith-",) SELF_HOSTED_LABEL = "self-hosted" +# GitHub's own runner image labels, such as ubuntu-24.04, ubuntu-24.04-arm, +# ubuntu-latest or windows-11-arm. A bare label like ubuntu-core does not match. +GITHUB_HOSTED_IMAGE_LABEL = re.compile( + r"(ubuntu|windows|macos)-(latest|slim|\d[\d.]*)(-[a-z0-9]+)*", re.IGNORECASE +) # Any queued GitHub-hosted job at all is enough to prefer our own hardware for # the arm64 pre-lint jobs. SELFHOSTED_BACKLOG_THRESHOLD = 0 @@ -336,28 +341,30 @@ def select_tier_runner( selfhosted_label: str, selfhosted_queued: int, selfhosted_queue_limit: int, + default_runner: str, blacksmith_runner: str, label_override: bool, measurement_error: Optional[str], disabled_reason: str, ) -> Tuple[str, str]: - """Pick an amd64 tier's runner: GitHub-hosted, then self-hosted, then Blacksmith. + """Pick an amd64 tier's runner: default, then self-hosted, then Blacksmith. - Runs that may not use our own hardware stay GitHub-hosted, so Blacksmith - is only ever paid for trusted runs or an explicit blacksmith-ci label. - blacksmith_runner is GitHub-hosted when RUNNER_AMD64 is unset. + default_runner is RUNNER_DEFAULT_AMD64, GitHub-hosted unless set. Runs + that may not use our own hardware stay on it, so Blacksmith is only ever + paid for trusted runs or an explicit blacksmith-ci label. + blacksmith_runner is default_runner when RUNNER_AMD64 is unset. """ if label_override: return blacksmith_runner, "label:blacksmith-ci" if measurement_error is not None: - return DEFAULT_RUNNER_AMD64, "metric-unavailable" + return default_runner, "metric-unavailable" if hosted_available: - return DEFAULT_RUNNER_AMD64, hosted_reason + return default_runner, hosted_reason if not allowed: - return DEFAULT_RUNNER_AMD64, "actor-not-allowed" + return default_runner, "actor-not-allowed" if not selfhosted_label: return blacksmith_runner, disabled_reason @@ -390,6 +397,8 @@ def select_runners( runner_selfhosted_build_var: str = "", build_hosted_queue_limit: int = DEFAULT_BUILD_HOSTED_QUEUE_LIMIT, build_selfhosted_queue_limit: int = DEFAULT_BUILD_SELFHOSTED_QUEUE_LIMIT, + runner_default_amd64_var: str = "", + runner_default_arm64_var: str = "", ) -> Dict[str, str]: label_names = [ label.get("name", "") @@ -404,6 +413,14 @@ def select_runners( selfhosted_queued: Dict[str, int] = {} measurement_error = None + # A default runner set to a label other than one of GitHub's images names + # our own hardware, so its queued jobs must not count towards the backlog. + moved_default_labels = tuple( + label + for label in (runner_default_amd64_var, runner_default_arm64_var) + if label and not GITHUB_HOSTED_IMAGE_LABEL.fullmatch(label) + ) + try: backlog_count_value, selfhosted_queued = count_queued_jobs( fetch_json, @@ -412,7 +429,7 @@ def select_runners( runner_selfhosted_arm64_var, runner_selfhosted_fast_var, runner_selfhosted_build_var, - ), + ) + moved_default_labels, ) backlog_count = str(backlog_count_value) except Exception as exc: # noqa: BLE001 @@ -447,21 +464,22 @@ def select_runners( measurement_error is None and backlog_count_value > arm64_threshold ) - runner_amd64 = DEFAULT_RUNNER_AMD64 - runner_arm64 = DEFAULT_RUNNER_ARM64 + default_amd64 = runner_default_amd64_var or DEFAULT_RUNNER_AMD64 + runner_amd64 = default_amd64 + runner_arm64 = runner_default_arm64_var or DEFAULT_RUNNER_ARM64 fallback_parts: List[str] = [] if use_blacksmith_amd64: if runner_amd64_var: runner_amd64 = runner_amd64_var else: - fallback_parts.append("amd64-github-fallback") + fallback_parts.append("amd64-default-fallback") if use_blacksmith_arm64: if runner_arm64_var: runner_arm64 = runner_arm64_var else: - fallback_parts.append("arm64-github-fallback") + fallback_parts.append("arm64-default-fallback") if measurement_error is not None: decision_parts.append("error:{}".format(measurement_error[:180])) @@ -499,7 +517,7 @@ def selfhosted( ) trusted = is_amd64_selfhosted_allowed(event_name, event, actor, allowed_authors) - blacksmith_amd64 = runner_amd64_var or DEFAULT_RUNNER_AMD64 + blacksmith_amd64 = runner_amd64_var or default_amd64 def tier( hosted_available: bool, @@ -515,6 +533,7 @@ def tier( selfhosted_label=selfhosted_label, selfhosted_queued=selfhosted_queued.get(selfhosted_label.lower(), 0), selfhosted_queue_limit=queue_limit, + default_runner=default_amd64, blacksmith_runner=blacksmith_amd64, label_override=label_override, measurement_error=measurement_error, @@ -646,6 +665,16 @@ def parse_args(argv: Sequence[str]) -> argparse.Namespace: default=env_int("BACKLOG_THRESHOLD_ARM64", default_backlog_threshold * 3), help="Queued job threshold for switching arm64 jobs to Blacksmith", ) + parser.add_argument( + "--runner-default-amd64-var", + default=os.environ.get("RUNNER_DEFAULT_AMD64_VAR", ""), + help="amd64 runner label used without escalation; empty means {}".format(DEFAULT_RUNNER_AMD64), + ) + parser.add_argument( + "--runner-default-arm64-var", + default=os.environ.get("RUNNER_DEFAULT_ARM64_VAR", ""), + help="arm64 runner label used without escalation; empty means {}".format(DEFAULT_RUNNER_ARM64), + ) parser.add_argument( "--runner-amd64-var", default=os.environ.get("RUNNER_AMD64_VAR", ""), @@ -756,6 +785,8 @@ def main(argv: Sequence[str]) -> int: runner_selfhosted_build_var=args.runner_selfhosted_build_var, build_hosted_queue_limit=args.build_hosted_queue_limit, build_selfhosted_queue_limit=args.build_selfhosted_queue_limit, + runner_default_amd64_var=args.runner_default_amd64_var, + runner_default_arm64_var=args.runner_default_arm64_var, ) write_github_output(os.environ.get("GITHUB_OUTPUT"), outputs) diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml index 658960d6c892..f501a445a89d 100644 --- a/.github/workflows/semantic-pull-request.yml +++ b/.github/workflows/semantic-pull-request.yml @@ -14,7 +14,7 @@ permissions: jobs: main: name: Validate PR title - runs-on: ${{ vars.DASH_LIGHT_RUNNER || 'ubuntu-latest' }} + runs-on: ${{ vars.RUNNER_SEMANTIC_PULL_REQUEST || vars.DASH_LIGHT_RUNNER || 'ubuntu-latest' }} steps: - uses: amannn/action-semantic-pull-request@v6 env: diff --git a/.github/workflows/test_select_dynamic_runner.py b/.github/workflows/test_select_dynamic_runner.py index f245ec47254e..d4498120bf36 100644 --- a/.github/workflows/test_select_dynamic_runner.py +++ b/.github/workflows/test_select_dynamic_runner.py @@ -260,7 +260,71 @@ def test_missing_runner_vars_fall_back_per_arch(self): self.assertEqual(outputs["runner_amd64"], MODULE.DEFAULT_RUNNER_AMD64) self.assertEqual(outputs["runner_arm64"], "blacksmith-arm64") - self.assertIn("amd64-github-fallback", outputs["decision_reason"]) + self.assertIn("amd64-default-fallback", outputs["decision_reason"]) + + def test_default_runner_vars_replace_hosted_defaults(self): + def fetch_json(_url): + raise RuntimeError("boom") + + outputs = MODULE.select_runners( + event_name="push", + event={}, + threshold=10, + arm64_threshold=30, + runner_amd64_var="", + runner_arm64_var="", + fetch_json=fetch_json, + repos=[REPO], + runner_default_amd64_var="default-amd64", + runner_default_arm64_var="default-arm64", + ) + + self.assertEqual(outputs["use_blacksmith"], "false") + self.assertEqual(outputs["runner_amd64"], "default-amd64") + self.assertEqual(outputs["runner_arm64"], "default-arm64") + self.assertEqual(outputs["runner_prelint_arm64"], "default-arm64") + self.assertEqual(outputs["runner_fast"], "default-amd64") + self.assertEqual(outputs["runner_build"], "default-amd64") + + def test_escalation_overrides_default_runner_vars(self): + outputs = MODULE.select_runners( + event_name="push", + event={}, + threshold=10, + arm64_threshold=30, + runner_amd64_var="blacksmith-amd64", + runner_arm64_var="blacksmith-arm64", + fetch_json=lambda url: backlog_responses(11)[url], + repos=[REPO], + runner_default_amd64_var="default-amd64", + runner_default_arm64_var="default-arm64", + ) + + self.assertEqual(outputs["runner_amd64"], "blacksmith-amd64") + self.assertEqual(outputs["runner_arm64"], "default-arm64") + + def test_tiers_use_default_runner_var_in_place_of_github_hosted(self): + outputs = self._select(queued_jobs=0, runner_default_amd64_var="default-amd64") + self.assertEqual(outputs["runner_fast"], "default-amd64") + self.assertEqual(outputs["runner_build"], "default-amd64") + + outputs = self._select( + queued_jobs=40, + event=pull_request_event("mallory"), + actor="mallory", + runner_default_amd64_var="default-amd64", + ) + self.assertEqual(outputs["runner_fast"], "default-amd64") + self.assertEqual(outputs["fast_decision_reason"], "actor-not-allowed") + + outputs = self._select( + queued_jobs=20, + runner_amd64_var="", + runner_selfhosted_build_var="", + runner_default_amd64_var="default-amd64", + ) + self.assertEqual(outputs["runner_build"], "default-amd64") + self.assertEqual(outputs["build_decision_reason"], "selfhosted-build-disabled") # --- amd64 tiers --------------------------------------------------------- @@ -505,6 +569,51 @@ def test_selfhosted_jobs_do_not_count_as_hosted_backlog(self): self.assertFalse(MODULE.targets_github_hosted_runner(job, ["DASH-X64-BUILD"])) self.assertTrue(MODULE.targets_github_hosted_runner(job, [""])) + def test_backlog_excludes_jobs_on_moved_default_runners(self): + responses = add_queued_jobs(backlog_responses(0), "own-amd64", 11) + + moved = self._select( + runner_default_amd64_var="own-amd64", + fetch_json=lambda url: responses[url], + ) + self.assertEqual(moved["backlog_count"], "0") + self.assertEqual(moved["runner_amd64"], "own-amd64") + self.assertEqual(moved["runner_fast"], "own-amd64") + self.assertEqual(moved["fast_decision_reason"], "hosted:queued:0") + + responses_arm64 = add_queued_jobs(backlog_responses(0), "own-arm64", 31) + moved_arm64 = self._select( + runner_default_arm64_var="own-arm64", + fetch_json=lambda url: responses_arm64[url], + ) + self.assertEqual(moved_arm64["backlog_count"], "0") + self.assertEqual(moved_arm64["runner_arm64"], "own-arm64") + + # A default naming one of GitHub's images still counts its jobs. + explicit = self._select( + queued_jobs=11, + runner_default_amd64_var=MODULE.DEFAULT_RUNNER_AMD64, + ) + self.assertEqual(explicit["backlog_count"], "11") + self.assertEqual(explicit["runner_amd64"], "blacksmith-amd64") + + responses_other = add_queued_jobs(backlog_responses(0), "ubuntu-22.04", 11) + other_image = self._select( + runner_default_amd64_var="ubuntu-22.04", + fetch_json=lambda url: responses_other[url], + ) + self.assertEqual(other_image["backlog_count"], "11") + self.assertEqual(other_image["runner_amd64"], "blacksmith-amd64") + + def test_github_hosted_image_labels(self): + for label in ("ubuntu-24.04", "ubuntu-22.04-arm", "ubuntu-latest", "ubuntu-slim", + "windows-11-arm", "macos-15-intel", "macos-latest-xlarge"): + with self.subTest(label=label): + self.assertTrue(MODULE.GITHUB_HOSTED_IMAGE_LABEL.fullmatch(label)) + for label in ("ubuntu-core", "own-amd64", "dash-x64-build", "ubuntu-24.04 "): + with self.subTest(label=label): + self.assertFalse(MODULE.GITHUB_HOSTED_IMAGE_LABEL.fullmatch(label)) + # --- arm64 pre-lint rung ------------------------------------------------- def test_prelint_arm64_is_off_until_its_label_is_set(self): diff --git a/ci/README.md b/ci/README.md index e81e71f5171b..f84139152d5c 100644 --- a/ci/README.md +++ b/ci/README.md @@ -58,7 +58,8 @@ label share its queue. both tiers unless it has the `blacksmith-ci` label. - A `blacksmith-ci` label sends both tiers to `RUNNER_AMD64`. Otherwise a failed backlog measurement keeps both GitHub-hosted. -- With `RUNNER_AMD64` unset, the Blacksmith step is GitHub-hosted. +- With `RUNNER_AMD64` unset, the Blacksmith step uses `RUNNER_DEFAULT_AMD64` + (or `ubuntu-24.04` when that variable is unset). - Unsetting a tier's label variable skips only that tier's self-hosted step, for example to keep the build tier off a busy pool while the fast tier keeps using it. Jobs already queued for the label must be cancelled and re-run. @@ -78,3 +79,60 @@ tier's label also runs Docker Buildx for the container builds, which push to GHCR, and `make -C depends download` directly on the host for cache-sources. These runs execute pull request code with access to that Docker daemon, so give each job a fresh, single-use machine. + +### Runner variables + +Every workflow job picks its runner label from a repository or organization +variable, so a fork can move CI to other runners without editing workflows. +Unset variables keep the defaults below. Most of these workflows run pull +request code, and only the self-hosted labels below apply an author allowlist, +so a self-hosted label named by any other variable must be an isolated, +single-use machine. + +| Variable | Workflow | Default | +|---|---|---| +| `RUNNER_DEFAULT_AMD64` | `build.yml` amd64 jobs | `ubuntu-24.04` | +| `RUNNER_DEFAULT_ARM64` | `build.yml` arm64 jobs and ccache pruning | `ubuntu-24.04-arm` | +| `RUNNER_AMD64`, `RUNNER_ARM64` | `build.yml` runs moved off the default runners by the backlog, the `blacksmith-ci` label or an amd64 tier's last step | the defaults above | +| `RUNNER_CHECK_SKIP` | `build.yml` `Check skip conditions`, the depends `Check cache` jobs and the existing-container checks | `ubuntu-24.04-arm` | +| `RUNNER_CACHE_DEPENDS_SOURCES` | scheduled `cache-depends-sources.yml` | `ubuntu-24.04-arm` | +| `RUNNER_GUIX_BUILD` | `guix-build.yml` image and build jobs | `ubuntu-24.04-arm` | +| `RUNNER_GUIX_ATTEST` | `guix-build.yml` release-tag `attest` job | `ubuntu-24.04` | +| `RUNNER_CLANG_DIFF_FORMAT` | `clang-diff-format.yml` | `ubuntu-latest` | +| `RUNNER_PREVENT_MASTER_PR` | `prevent-master-pr.yml` | `ubuntu-latest` | +| `RUNNER_LABEL_MERGE_CONFLICTS` | `label-merge-conflicts.yml` | `DASH_LIGHT_RUNNER`, then `ubuntu-latest` | +| `RUNNER_MERGE_CHECK` | `merge-check.yml` | `DASH_LIGHT_RUNNER`, then `ubuntu-latest` | +| `RUNNER_CHECK_MILESTONE` | `check-milestone.yml` | `DASH_LIGHT_RUNNER`, then `ubuntu-latest` | +| `RUNNER_PREDICT_CONFLICTS` | `predict-conflicts.yml` | `DASH_LIGHT_RUNNER`, then `ubuntu-latest` | +| `RUNNER_SEMANTIC_PULL_REQUEST` | `semantic-pull-request.yml` | `DASH_LIGHT_RUNNER`, then `ubuntu-latest` | +| `RUNNER_DOCKER_RELEASE` | `release_docker_hub.yml` | `ubuntu-22.04` | + +`RUNNER_DEFAULT_AMD64` and `RUNNER_AMD64` must name x86_64 runners, and +`RUNNER_DEFAULT_ARM64` and `RUNNER_ARM64` aarch64 runners, because the +container images and depends cache keys are per architecture. +`RUNNER_DEFAULT_AMD64` also stands in for GitHub-hosted in the amd64 tiers +above: it is each tier's first step, where untrusted runs stay, and its +Blacksmith step while `RUNNER_AMD64` is unset. +`RUNNER_GUIX_BUILD` must resolve to runners of a single architecture, because +its build jobs run the image its image job builds. +Jobs without a `container:` run straight on the runner and expect the tools +GitHub's Ubuntu images ship, such as `gh`, `jq`, `python3` and Docker; ccache +pruning on `RUNNER_DEFAULT_ARM64`, for example, calls `gh` and `jq`. + +The depends `Check cache` jobs run on `RUNNER_CHECK_SKIP`, and a cache hit +there skips the depends build. Blacksmith runners use their own cache rather +than GitHub's, so `RUNNER_CHECK_SKIP` and the runners that build depends must +use the same one: with Blacksmith default runners, set `RUNNER_CHECK_SKIP` to +a Blacksmith label and leave `RUNNER_SELFHOSTED_FAST` and +`RUNNER_SELFHOSTED_BUILD` unset. + +The backlog that moves runs to `RUNNER_AMD64`/`RUNNER_ARM64` counts queued +jobs in the dashpay repositories listed in `select_dynamic_runner.py`, and +treats every label that is not `self-hosted`, a self-hosted label below, a +`blacksmith-` label or a default runner set to something other than one of +GitHub's image labels (such as `ubuntu-24.04` or `ubuntu-latest`) as +GitHub-hosted. A fork that sets the default runners +does not need to set `RUNNER_AMD64`/`RUNNER_ARM64`. + +The self-hosted labels (`RUNNER_SELFHOSTED_FAST`, `RUNNER_SELFHOSTED_BUILD`, +`RUNNER_SELFHOSTED_ARM64`) are described above and in `build.yml`.