build: enforce ifttt-lint in pre-commit and CI (#4460) Ad-hoc "Keep in sync" comments drift silently because no automated check verifies that paired sections are updated together. Configure `ifttt-lint` in `.pre-commit-config.yaml` and `.github/workflows/ci.yaml` (extending `download-tool` to extract archive members) and convert existing sync comments to `LINT.IfChange` and `LINT.ThenChange` directives.
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index d00d3e8..0420712 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml
@@ -13,17 +13,19 @@ # limitations under the License. --- +# LINT.IfChange(buildifier) buildifier: - # keep these arguments in sync with .pre-commit-config.yaml # Use a specific version to avoid skew issues when new versions are released. version: 8.2.1 warnings: "all" +# LINT.ThenChange(//.pre-commit-config.yaml:buildifier) # NOTE: Minimum supported version is 7.x +# LINT.IfChange(minimum_supported_version) .minimum_supported_version: &minimum_supported_version # For testing minimum supported version. - # NOTE: Keep in sync with //:version.bzl bazel: 7.x skip_in_bazel_downstream_pipeline: "Bazel 7 required" +# LINT.ThenChange(//version.bzl:minimum_bazel_version) .reusable_config: &reusable_config # USE_BAZEL_DIFF is disabled because target_compatible_with is used heavily, # but BazelCI's bazel-diff integration errors when handling them. @@ -117,7 +119,7 @@ - //tests:version_3_14_test - //tests:version_default_test -# Keep in sync with .bcr/gazelle/presubmit.yml +# LINT.IfChange(gazelle_common_bcr) .gazelle_common_bcr: &gazelle_common_bcr bazel: ${{ bazel }} working_directory: gazelle/examples/bzlmod_build_file_generation @@ -134,20 +136,21 @@ - ":modules_map" test_targets: - "//..." +# LINT.ThenChange(//.bcr/gazelle/presubmit.yml:tasks) matrix: - # Keep in sync with .bcr/presubmit.yml + # LINT.IfChange(bcr_matrix) platform: - ubuntu2204 - debian13 - macos_arm64 - windows - # Keep in sync with .bcr/presubmit.yml bazel: [7.x, 8.x, 9.x] + # LINT.ThenChange(//.bcr/presubmit.yml:matrix) tasks: - # Keep in sync with .bcr/presubmit.yml + # LINT.IfChange(bcr_test) bcr_test: name: "BCR: Bazel {bazel}" platform: ${{ platform }} @@ -161,8 +164,9 @@ - "//..." test_targets: - "//..." + # LINT.ThenChange(//.bcr/presubmit.yml:tasks) - # Keep in sync with .bcr/gazelle/presubmit.yml + # LINT.IfChange(gazelle_bcr) gazelle_bcr_ubuntu: <<: *gazelle_common_bcr name: "Gazelle: BCR, Bazel {bazel}" @@ -175,6 +179,7 @@ <<: *gazelle_common_bcr name: "Gazelle: BCR, Bazel {bazel}" platform: macos_arm64 + # LINT.ThenChange(//.bcr/gazelle/presubmit.yml:matrix) gazelle_extension_min: <<: *common_workspace_flags_min_bazel
diff --git a/.bcr/gazelle/presubmit.yml b/.bcr/gazelle/presubmit.yml index ef1b0c7..6995a5d 100644 --- a/.bcr/gazelle/presubmit.yml +++ b/.bcr/gazelle/presubmit.yml
@@ -14,6 +14,7 @@ bcr_test_module: module_path: "examples/bzlmod_build_file_generation" + # LINT.IfChange(matrix) matrix: platform: [ "debian13", @@ -21,6 +22,8 @@ "ubuntu2204", ] bazel: [7.*, 8.*, 9.*] + # LINT.ThenChange(//.bazelci/presubmit.yml:gazelle_bcr) + # LINT.IfChange(tasks) tasks: run_tests: name: "Run test module" @@ -39,3 +42,4 @@ - ":modules_map" test_targets: - "//..." + # LINT.ThenChange(//.bazelci/presubmit.yml:gazelle_common_bcr)
diff --git a/.bcr/presubmit.yml b/.bcr/presubmit.yml index 2787306..314973e 100644 --- a/.bcr/presubmit.yml +++ b/.bcr/presubmit.yml
@@ -14,9 +14,12 @@ bcr_test_module: module_path: "examples/bzlmod" + # LINT.IfChange(matrix) matrix: platform: ["debian13", "macos", "ubuntu2204", "windows"] bazel: [7.x, 8.x, 9.x] + # LINT.ThenChange(//.bazelci/presubmit.yml:bcr_matrix) + # LINT.IfChange(tasks) tasks: run_tests: name: "Run test module" @@ -31,3 +34,4 @@ - "//..." test_targets: - "//..." + # LINT.ThenChange(//.bazelci/presubmit.yml:bcr_test)
diff --git a/.github/actions/download-tool/action.yaml b/.github/actions/download-tool/action.yaml index 165b7f5..235635f 100644 --- a/.github/actions/download-tool/action.yaml +++ b/.github/actions/download-tool/action.yaml
@@ -11,6 +11,12 @@ sha256: description: "Expected hex SHA-256 digest of the tool binary" required: true + archive-member: + description: >- + Optional archive member path to extract when the downloaded payload is a + tar or zip archive + required: false + default: "" outputs: path: @@ -46,6 +52,7 @@ if: steps.cache.outputs.cache-hit != 'true' shell: bash env: + TOOL_ARCHIVE_MEMBER: ${{ inputs.archive-member }} TOOL_PATH: ${{ steps.tool_path.outputs.path }} TOOL_SHA256: ${{ inputs.sha256 }} TOOL_URLS: ${{ inputs.urls }} @@ -53,5 +60,6 @@ "${GITHUB_ACTION_PATH}/../../../dev/verified_download.py" --urls "${TOOL_URLS}" --sha256 "${TOOL_SHA256}" + --archive-member "${TOOL_ARCHIVE_MEMBER}" --executable --output "${TOOL_PATH}"
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b5a547a..048d848 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml
@@ -21,15 +21,16 @@ runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 + # LINT.IfChange(ruff) - uses: astral-sh/ruff-action@v4.1.0 with: - # Keep in sync with .pre-commit-config.yaml version: 0.15.14 args: check --extend-exclude testdata - uses: astral-sh/ruff-action@v4.1.0 with: version: 0.15.14 args: format --check --exclude testdata + # LINT.ThenChange(//.pre-commit-config.yaml:ruff) keep_sorted: name: keep-sorted @@ -41,15 +42,48 @@ uses: ./.github/actions/download-tool with: name: keep-sorted - # Keep in sync with .pre-commit-config.yaml + # LINT.IfChange(keep_sorted) urls: | https://github.com/google/keep-sorted/releases/download/v0.10.0/keep-sorted_linux_amd64 sha256: 079710c01619cc3dc11c9ddce18ac8db8d86701e7d12f9c5b62b8c4c4a664cca + # LINT.ThenChange(//.pre-commit-config.yaml:keep_sorted) - name: Check keep-sorted env: KEEP_SORTED_BIN: ${{ steps.keep-sorted.outputs.path }} run: ./.github/workflows/check_keep_sorted.py -- "$KEEP_SORTED_BIN" + ifttt_lint: + name: ifttt-lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - name: Download ifttt-lint + id: ifttt-lint + uses: ./.github/actions/download-tool + with: + name: ifttt-lint + archive-member: ifttt-lint + # LINT.IfChange(ifttt_lint) + urls: | + https://github.com/simonepri/ifttt-lint/releases/download/v0.11.3/ifttt-lint-x86_64-unknown-linux-gnu.tar.gz + sha256: 1dd2071f17d5f5c11d698ae60e81c0ed1d419ca90a2d64f5d3810bdf28ddd4fe + # LINT.ThenChange(//.pre-commit-config.yaml:ifttt_lint) + - name: Check ifttt-lint + env: + IFTTT_LINT_BIN: ${{ steps.ifttt-lint.outputs.path }} + run: >- + "$IFTTT_LINT_BIN" --vcs git '**/*' + - name: Check ifttt-lint diff + if: github.event_name == 'pull_request' + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + IFTTT_LINT_BIN: ${{ steps.ifttt-lint.outputs.path }} + run: >- + "$IFTTT_LINT_BIN" --vcs git --diff "${BASE_SHA}...${HEAD_SHA}" + build_release_artifacts: name: Build release artifacts uses: ./.github/workflows/release_build_artifacts.yaml
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cd11aa5..aa09867 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml
@@ -21,21 +21,30 @@ hooks: - id: check-merge-conflict - repo: https://github.com/google/keep-sorted - # Keep in sync with .github/workflows/ci.yaml + # LINT.IfChange(keep_sorted) rev: v0.10.0 + # LINT.ThenChange(//.github/workflows/ci.yaml:keep_sorted) hooks: - id: keep-sorted + - repo: https://github.com/simonepri/ifttt-lint + # LINT.IfChange(ifttt_lint) + rev: v0.11.3 + # LINT.ThenChange(//.github/workflows/ci.yaml:ifttt_lint) + hooks: + - id: ifttt-lint + - id: ifttt-lint-diff + # LINT.IfChange(buildifier) - repo: https://github.com/keith/pre-commit-buildifier rev: 8.2.1 hooks: - id: buildifier args: &args - # Keep this argument in sync with .bazelci/presubmit.yaml - --warnings=all - id: buildifier-lint args: *args + # LINT.ThenChange(//.bazelci/presubmit.yml:buildifier) - repo: https://github.com/astral-sh/ruff-pre-commit - # Keep in sync with .github/workflows/ruff.yaml + # LINT.IfChange(ruff) rev: v0.15.14 hooks: - id: ruff-check @@ -43,6 +52,7 @@ exclude: testdata - id: ruff-format exclude: testdata + # LINT.ThenChange(//.github/workflows/ci.yaml:ruff) - repo: local hooks: - id: update-deleted-packages
diff --git a/MODULE.bazel b/MODULE.bazel index 75087fd..24f68a7 100644 --- a/MODULE.bazel +++ b/MODULE.bazel
@@ -486,7 +486,7 @@ dev_dependency = True, ) -# Keep in sync with //:version.bzl +# LINT.IfChange(bazel_binaries) bazel_binaries.local( name = "self", path = "tests/integration/bazel_from_env", @@ -508,6 +508,7 @@ # "build_bazel_bazel_rolling", "build_bazel_bazel_self", ) +# LINT.ThenChange(//version.bzl:supported_bazel_versions) # TODO @aignas 2025-01-27: should this be moved to `//python/extensions:uv.bzl` or should # it stay as it is? I think I may prefer to move it. @@ -516,11 +517,15 @@ # Here is how we can define platforms for the `uv` binaries - this will affect # all of the downstream callers because we are using the extension without # `dev_dependency = True`. +# LINT.IfChange(uv_defaults) uv.default( base_url = "https://github.com/astral-sh/uv/releases/download", manifest_filename = "dist-manifest.json", version = "0.13.5", ) + +# LINT.ThenChange(//python/uv/private/uv_defaults.bzl:uv_defaults) +# LINT.IfChange(uv_platforms) uv.default( compatible_with = [ "@platforms//os:macos", @@ -577,6 +582,8 @@ ], platform = "x86_64-unknown-linux-gnu", ) + +# LINT.ThenChange(//python/uv/private/uv_defaults.bzl:uv_platforms) use_repo(uv, "uv") register_toolchains("@uv//:all")
diff --git a/dev/verified_download.py b/dev/verified_download.py index d7c25c5..baf4510 100755 --- a/dev/verified_download.py +++ b/dev/verified_download.py
@@ -53,6 +53,14 @@ help="Expected lowercase or uppercase hex SHA-256 digest.", ) parser.add_argument( + "--archive-member", + default="", + help=( + "Optional archive member path to extract when the downloaded " + "payload is a zip or tar archive." + ), + ) + parser.add_argument( "--output", type=Path, required=True, @@ -275,7 +283,7 @@ """Async CLI entry point.""" args = _parse_args(argv) urls = parse_url_list(args.urls) - sources = [(url, args.sha256, "") for url in urls] + sources = [(url, args.sha256, args.archive_member) for url in urls] await download_verified_file( args.output, sources,
diff --git a/docs/conf.py b/docs/conf.py index 750e344..bc58082 100644 --- a/docs/conf.py +++ b/docs/conf.py
@@ -199,8 +199,9 @@ "current_version": os.environ.get("READTHEDOCS_VERSION", ""), } -# Keep this in sync with the stardoc templates +# LINT.IfChange(permalink_icon) html_permalinks_icon = "¶" +# LINT.ThenChange(//sphinxdocs/sphinxdocs/private/proto_to_markdown.py:permalink_icon) # These folders are copied to the documentation's HTML output html_static_path = ["_static"]
diff --git a/python/private/local_runtime_repo.bzl b/python/private/local_runtime_repo.bzl index f5597db..9a69803 100644 --- a/python/private/local_runtime_repo.bzl +++ b/python/private/local_runtime_repo.bzl
@@ -169,7 +169,7 @@ # appropriate path. interpreter_path = info["base_executable"] - # NOTE: Keep in sync with recursive glob in define_local_runtime_toolchain_impl + # LINT.IfChange(include_path) include_path = rctx.path(info["include"]) # The reported include path may not exist, and watching a non-existant @@ -185,6 +185,7 @@ # appear as part of this repo. logger.debug(lambda: "Symlinking {} to include".format(include_path)) rctx.symlink(include_path, "include") + # LINT.ThenChange(//python/private/local_runtime_repo_setup.bzl:includes) rctx.report_progress("Symlinking external Python shared libraries")
diff --git a/python/private/local_runtime_repo_setup.bzl b/python/private/local_runtime_repo_setup.bzl index 05ca622..c35f834 100644 --- a/python/private/local_runtime_repo_setup.bzl +++ b/python/private/local_runtime_repo_setup.bzl
@@ -99,6 +99,7 @@ system_provided = True, ) + # LINT.IfChange(includes) native.filegroup( name = "includes", srcs = native.glob( @@ -107,6 +108,7 @@ allow_empty = True, # A Python install may not have C headers ), ) + # LINT.ThenChange(//python/private/local_runtime_repo.bzl:include_path) # header libraries. cc_library(
diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index 206c473..8d95f9d 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt
@@ -331,11 +331,13 @@ # Create the runfiles tree by extracting the zip file def create_runfiles_root(): + # LINT.IfChange(runfiles_root_structure) temp_dir = tempfile.mkdtemp('', 'Bazel.runfiles_') extract_zip(os.path.dirname(__file__), temp_dir) # IMPORTANT: Later code does `rm -fr` on dirname(runfiles_root) -- it's # important that deletion code be in sync with this directory structure return join(temp_dir, 'runfiles') + # LINT.ThenChange(:runfiles_root_cleanup) def _symlink_tree(link_from, link_to): # Ensure the source is an absolute path to simplify symlinking @@ -659,11 +661,13 @@ delete_dirs = [] if IS_ZIPFILE: + # LINT.IfChange(runfiles_root_cleanup) runfiles_root = create_runfiles_root() # NOTE: dirname() is called because create_runfiles_root() creates a # sub-directory within a temporary directory, and we want to remove the # whole temporary directory. delete_dirs.append(dirname(runfiles_root)) + # LINT.ThenChange(:runfiles_root_structure) else: runfiles_root = find_runfiles_root(main_rel_path)
diff --git a/python/private/zipapp/zip_main_template.py b/python/private/zipapp/zip_main_template.py index c93f21b..7c965fc 100644 --- a/python/private/zipapp/zip_main_template.py +++ b/python/private/zipapp/zip_main_template.py
@@ -228,11 +228,13 @@ else: extract_root = tempfile.mkdtemp("", "Bazel.runfiles_") + # LINT.IfChange(runfiles_root_structure) extract_zip(dirname(__file__), extract_root) print_verbose("extracted to:", extract_root) # IMPORTANT: Later code does `rm -fr` on dirname(runfiles_root) -- it's # important that deletion code be in sync with this directory structure return join(extract_root, "runfiles") + # LINT.ThenChange(:runfiles_root_cleanup) def run_subprocess(subprocess_argv, env, cwd): @@ -330,6 +332,7 @@ sys.exit(ret_code) finally: if not EXTRACT_ROOT: + # LINT.IfChange(runfiles_root_cleanup) # NOTE: dirname() is called because create_runfiles_root() creates a # sub-directory within a temporary directory, and we want to remove the # whole temporary directory. @@ -340,6 +343,7 @@ shutil.rmtree(extract_root, onerror=remove_readonly) else: shutil.rmtree(extract_root, True) + # LINT.ThenChange(:runfiles_root_structure) def finish_venv_setup(runfiles_root):
diff --git a/python/uv/private/uv_defaults.bzl b/python/uv/private/uv_defaults.bzl index d50cec9..b7fce2b 100644 --- a/python/uv/private/uv_defaults.bzl +++ b/python/uv/private/uv_defaults.bzl
@@ -4,11 +4,11 @@ `MODULE.bazel`. Keep the values here in sync with them. """ -# Keep in sync with `uv.default(version = ...)` in MODULE.bazel. +# LINT.IfChange(uv_defaults) DEFAULT_VERSION = "0.13.5" -# Keep in sync with `uv.default(base_url = ...)` in MODULE.bazel. DEFAULT_BASE_URL = "https://github.com/astral-sh/uv/releases/download" +# LINT.ThenChange(//MODULE.bazel:uv_defaults) def _platform(platform, *, os, cpu): # Same shape as the platform structs `set_config` creates. @@ -21,7 +21,7 @@ target_settings = [], ) -# Keep in sync with the `uv.default(platform = ...)` calls in MODULE.bazel. +# LINT.IfChange(uv_platforms) DEFAULT_PLATFORMS = { platform: _platform(platform, os = os, cpu = cpu) for platform, os, cpu in [ @@ -35,3 +35,4 @@ ("x86_64-unknown-linux-gnu", "linux", "x86_64"), ] } +# LINT.ThenChange(//MODULE.bazel:uv_platforms)
diff --git a/sphinxdocs/sphinxdocs/private/proto_to_markdown.py b/sphinxdocs/sphinxdocs/private/proto_to_markdown.py index aeecc35..0e26012 100644 --- a/sphinxdocs/sphinxdocs/private/proto_to_markdown.py +++ b/sphinxdocs/sphinxdocs/private/proto_to_markdown.py
@@ -58,7 +58,9 @@ def _link_here_icon(anchor: str) -> str: # The headerlink class activates some special logic to show/hide # text upon mouse-over; it's how headings show a clickable link. + # LINT.IfChange(permalink_icon) return _link("¶", anchor, classes=".headerlink") + # LINT.ThenChange(//docs/conf.py:permalink_icon) def _inline_anchor(anchor: str) -> str:
diff --git a/tests/workflows/check_keep_sorted_test.py b/tests/workflows/check_keep_sorted_test.py index a55da46..a5dba8b 100644 --- a/tests/workflows/check_keep_sorted_test.py +++ b/tests/workflows/check_keep_sorted_test.py
@@ -9,6 +9,7 @@ import subprocess from collections.abc import Iterator from pathlib import Path +from typing import Any import check_keep_sorted import pytest @@ -37,41 +38,52 @@ return path -def test_ci_workflow_uses_download_tool_in_sync_with_pre_commit() -> None: +def _assert_ci_download_step_in_sync( + *, + repo_url: str, + expected_hooks: list[str], + job_id: str, + step_id: str, + url_template: str, + archive_member: str = "", +) -> list[dict[str, Any]]: pre_commit = yaml.safe_load( _repo_file(".pre-commit-config.yaml").read_text(encoding="utf-8") ) - repos = pre_commit["repos"] - matching = [ - repo - for repo in repos - if repo.get("repo") == "https://github.com/google/keep-sorted" - ] + matching = [repo for repo in pre_commit["repos"] if repo.get("repo") == repo_url] assert len(matching) == 1 - entry = matching[0] - rev = entry["rev"] - hook_ids = [hook["id"] for hook in entry["hooks"]] - assert hook_ids == ["keep-sorted"] + rev = matching[0]["rev"] + assert [hook["id"] for hook in matching[0]["hooks"]] == expected_hooks ci = yaml.safe_load( _repo_file(".github/workflows/ci.yaml").read_text(encoding="utf-8") ) - job = ci["jobs"]["keep_sorted"] + job = ci["jobs"][job_id] assert job["runs-on"] == "ubuntu-latest" steps = job["steps"] - download_step = next(s for s in steps if s.get("id") == "keep-sorted") + download_step = next(s for s in steps if s.get("id") == step_id) assert download_step["uses"] == "./.github/actions/download-tool" - assert download_step["with"]["name"] == "keep-sorted" + assert download_step["with"]["name"] == step_id + assert download_step["with"].get("archive-member", "") == archive_member urls = [ line.strip() for line in download_step["with"]["urls"].splitlines() if line.strip() ] - assert urls == [ - f"https://github.com/google/keep-sorted/releases/download/{rev}/keep-sorted_linux_amd64" - ] + assert urls == [url_template.format(rev=rev)] assert len(download_step["with"]["sha256"]) == 64 + return steps + + +def test_ci_workflow_uses_download_tool_in_sync_with_pre_commit() -> None: + steps = _assert_ci_download_step_in_sync( + repo_url="https://github.com/google/keep-sorted", + expected_hooks=["keep-sorted"], + job_id="keep_sorted", + step_id="keep-sorted", + url_template="https://github.com/google/keep-sorted/releases/download/{rev}/keep-sorted_linux_amd64", + ) check_step = next(s for s in steps if s.get("name") == "Check keep-sorted") assert check_step["env"] == { @@ -83,6 +95,39 @@ ) +def test_ci_workflow_ifttt_lint_in_sync_with_pre_commit() -> None: + steps = _assert_ci_download_step_in_sync( + repo_url="https://github.com/simonepri/ifttt-lint", + expected_hooks=["ifttt-lint", "ifttt-lint-diff"], + job_id="ifttt_lint", + step_id="ifttt-lint", + url_template="https://github.com/simonepri/ifttt-lint/releases/download/{rev}/ifttt-lint-x86_64-unknown-linux-gnu.tar.gz", + archive_member="ifttt-lint", + ) + + checkout_step = steps[0] + assert checkout_step["uses"].startswith("actions/checkout@") + assert checkout_step["with"]["fetch-depth"] == 0 + + check_step = next(s for s in steps if s.get("name") == "Check ifttt-lint") + assert check_step["env"] == { + "IFTTT_LINT_BIN": "${{ steps.ifttt-lint.outputs.path }}" + } + assert check_step["run"] == "\"$IFTTT_LINT_BIN\" --vcs git '**/*'" + + diff_step = next(s for s in steps if s.get("name") == "Check ifttt-lint diff") + assert diff_step["if"] == "github.event_name == 'pull_request'" + assert diff_step["env"] == { + "BASE_SHA": "${{ github.event.pull_request.base.sha }}", + "HEAD_SHA": "${{ github.event.pull_request.head.sha }}", + "IFTTT_LINT_BIN": "${{ steps.ifttt-lint.outputs.path }}", + } + assert ( + diff_step["run"] + == '"$IFTTT_LINT_BIN" --vcs git --diff "${BASE_SHA}...${HEAD_SHA}"' + ) + + def test_lint_files_formats_findings_across_concurrent_batches( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None:
diff --git a/tests/workflows/verified_download_test.py b/tests/workflows/verified_download_test.py index 5b279e2..e64fa10 100644 --- a/tests/workflows/verified_download_test.py +++ b/tests/workflows/verified_download_test.py
@@ -39,7 +39,8 @@ assert bool(script.stat().st_mode & stat.S_IXUSR) action = yaml.safe_load((action_dir / "action.yaml").read_text(encoding="utf-8")) - assert set(action["inputs"].keys()) == {"name", "urls", "sha256"} + assert set(action["inputs"].keys()) == {"name", "urls", "sha256", "archive-member"} + assert action["inputs"]["archive-member"]["default"] == "" assert action["outputs"]["path"]["value"] == "${{ steps.tool_path.outputs.path }}" steps = action["runs"]["steps"] @@ -55,8 +56,10 @@ s for s in steps if s.get("name") == "Download and verify tool" ) assert download_step["if"] == "steps.cache.outputs.cache-hit != 'true'" + assert download_step["env"]["TOOL_ARCHIVE_MEMBER"] == "${{ inputs.archive-member }}" assert download_step["env"]["TOOL_URLS"] == "${{ inputs.urls }}" assert '--urls "${TOOL_URLS}"' in download_step["run"] + assert '--archive-member "${TOOL_ARCHIVE_MEMBER}"' in download_step["run"] assert "dev/verified_download.py" in download_step["run"] @@ -102,6 +105,27 @@ assert f"::warning::Failed to fetch {missing_a}" in out assert f"::warning::Failed to fetch {missing_b}" in out + tgz_path = tmp_path / "tool.tar.gz" + with tarfile.open(tgz_path, "w:gz") as tar: + tar.add(src_file, arcname="tool") + archive_dest = tmp_path / "out" / "archive-tool" + rc = verified_download.main( + [ + "--urls", + tgz_path.as_uri(), + "--sha256", + _sha256(payload), + "--archive-member", + "tool", + "--executable", + "--output", + str(archive_dest), + ] + ) + assert rc == 0 + assert archive_dest.read_bytes() == payload + assert bool(archive_dest.stat().st_mode & stat.S_IXUSR) + def test_falls_back_to_tgz_and_zip_archive_tuples_and_reuses_cached_file( tmp_path: Path, capsys: pytest.CaptureFixture[str]
diff --git a/version.bzl b/version.bzl index 4d85b5c..d48f48b 100644 --- a/version.bzl +++ b/version.bzl
@@ -15,25 +15,26 @@ # Currently used Bazel version. This version is what the rules here are tested # against. -# This version should be updated together with the version of Bazel -# in .bazelversion. +# LINT.IfChange(bazel_version) BAZEL_VERSION = "8.x" +# LINT.ThenChange(//.bazelversion) -# NOTE: Keep in sync with .bazelci/presubmit.yml # This is the minimum supported bazel version, that we have some tests for. +# LINT.IfChange(minimum_bazel_version) MINIMUM_BAZEL_VERSION = "7.4.1" +# LINT.ThenChange(//.bazelci/presubmit.yml:minimum_supported_version) # Versions of Bazel which users should be able to use. # Ensures we don't break backwards-compatibility, # accidentally forcing users to update their LTS-supported bazel. # These are the versions used when testing nested workspaces with # rules_bazel_integration_test. -# -# Keep in sync with MODULE.bazel's bazel_binaries config +# LINT.IfChange(supported_bazel_versions) SUPPORTED_BAZEL_VERSIONS = [ BAZEL_VERSION, MINIMUM_BAZEL_VERSION, ] +# LINT.ThenChange(//MODULE.bazel:bazel_binaries) def bazel_version_to_binary_label(version): return "@build_bazel_bazel_%s//:bazel_binary" % version.replace(".", "_")