diff --git a/MODULE.bazel b/MODULE.bazel index 066fa82..a5a68ea 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,6 +1,6 @@ module( name = "rules_host_cache_key", - version = "0.0.3", + version = "0.0.4", ) bazel_dep(name = "platforms", version = "1.1.0") diff --git a/README.md b/README.md index 02e550f..c2f573f 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ key, so: Add to `MODULE.bazel`: ```starlark -bazel_dep(name = "rules_host_cache_key", version = "0.0.3") +bazel_dep(name = "rules_host_cache_key", version = "0.0.4") host_cache_key = use_repo_rule("@rules_host_cache_key//:defs.bzl", "host_cache_key") host_cache_key(name = "host_cache_key") @@ -80,12 +80,13 @@ The selected directory remains in the key because host tools can record its path ## When the key changes -The rule asks Bazel to watch everything it read, except search path entries -and listings on the macOS system volume: those change only with the OS build, -which Bazel watches instead. When any of it changes, Bazel recomputes the key at -its next command, including in a server that is already running, and every -action whose key changed runs again or comes from a cache entry made under the -same key. +The rule asks Bazel to watch everything it read, except search path entries and +listings on the macOS system volume: those change only with the OS build, which +Bazel watches instead. It also watches its own `host_key.py`, so upgrading this +module brings an existing output base onto the new release's key and watches. +When any of it changes, Bazel recomputes the key at its next command, including +in a server that is already running, and every action whose key changed runs +again or comes from a cache entry made under the same key. Bazel reads each watched file in full when a new server first uses the repository. Leaving the system volume out spares that check a read of every diff --git a/defs.bzl b/defs.bzl index 572a626..5c24f15 100644 --- a/defs.bzl +++ b/defs.bzl @@ -18,7 +18,13 @@ def _host_cache_key_impl(rctx): interpreter = rctx.path(python) if python.startswith("/") else rctx.which(python) if interpreter == None or not interpreter.exists: fail("host_cache_key: no Python interpreter at %r; set the `python` attribute" % python) - command = [interpreter, rctx.path(Label("//:host_key.py"))] + script = Label("//:host_key.py") + + # Bazel records nothing for a file reached through rctx.path. Unwatched, an + # upgrade that changes only host_key.py would leave an existing output base + # on the old script's key and watches until some other watched path changed. + rctx.watch(script) + command = [interpreter, rctx.path(script)] for directory in rctx.attr.search_path: command.extend(["--search-path", directory]) for prefix in rctx.attr.homebrew_prefixes: diff --git a/tests/check_platform.py b/tests/check_platform.py index 7edcd1d..a22cc96 100755 --- a/tests/check_platform.py +++ b/tests/check_platform.py @@ -33,6 +33,14 @@ ) """ +# Stands in for a release that changed only host_key.py: a report under the +# rule's interface naming another key. +UPGRADED_SCRIPT = """\ +import json + +print(json.dumps({"key": "upgraded", "fields": {}, "watch_files": [], "watch_directories": []})) +""" + def consumer_module( module_root: Path, tools: Path, prefix: Path | None, developer_dir: str = "" @@ -154,6 +162,12 @@ def expect(step: str, stderr: str, wanted: str) -> None: def check_watched_changes(bazel: str, module_root: Path, scratch: Path) -> None: + # A copy, so a step can upgrade the module's own script. Convenience links + # into an output base would be followed and copied whole. + module = scratch / "module" + shutil.copytree( + module_root, module, ignore=shutil.ignore_patterns(".git", "bazel-*") + ) # The search path names the tools through a link to their directory. tools = scratch / "tools" (scratch / "tools-one").mkdir() @@ -166,9 +180,9 @@ def check_watched_changes(bazel: str, module_root: Path, scratch: Path) -> None: write_sbom(keg, BOTTLE) workspace = scratch / "consumer" workspace.mkdir() - shutil.copy(module_root / ".bazelversion", workspace / ".bazelversion") + shutil.copy(module / ".bazelversion", workspace / ".bazelversion") (workspace / "BUILD.bazel").write_text(CONSUMER_BUILD) - (workspace / "MODULE.bazel").write_text(consumer_module(module_root, tools, prefix)) + (workspace / "MODULE.bazel").write_text(consumer_module(module, tools, prefix)) consumer = Consumer(bazel, workspace, scratch / "disk-cache") first, second = scratch / "first-output-base", scratch / "second-output-base" @@ -209,6 +223,13 @@ def check_watched_changes(bazel: str, module_root: Path, scratch: Path) -> None: consumer.build(second), "executed", ) + + # Upgrading this module can change host_key.py and nothing else; the + # key follows only because the rule watches its own script. + (module / "host_key.py").write_text(UPGRADED_SCRIPT) + expect( + "host_key.py upgraded, running server", consumer.build(second), "executed" + ) finally: consumer.shutdown()