Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ Not covered:
- Linux distributions without dpkg, and Windows. Both fail with an error
instead of producing a key that covers less than it claims.

On macOS the default key follows the global `xcode-select` selection, even when
the process starting Bazel exports `DEVELOPER_DIR`. Strict action environments
do not inherit that job-local override. If host-dependent actions explicitly
select a different developer directory, set the rule's `developer_dir` to match.
Actions that acquire Xcode declare that bundle as their own inputs instead.
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. When any of it changes, Bazel
Expand All @@ -93,6 +100,7 @@ All attributes of `host_cache_key` are optional:

| Attribute | Default | Use |
| --- | --- | --- |
| `developer_dir` | empty | Global Xcode selection, or the directory explicitly selected by host-dependent actions. |
| `parent` | `@platforms//host` | Your own host platform, when your build defines one. |
| `search_path` | `/bin`, `/usr/bin`, `/usr/local/bin` | Add directories your actions find tools in. |
| `homebrew_prefixes` | the three standard prefixes | Other Homebrew installations. |
Expand Down
8 changes: 7 additions & 1 deletion defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def _host_cache_key_impl(rctx):
command.extend(["--search-path", directory])
for prefix in rctx.attr.homebrew_prefixes:
command.extend(["--homebrew-prefix", prefix])
result = rctx.execute(command, environment = {"LC_ALL": "C"}, quiet = True)
result = rctx.execute(command, environment = {"DEVELOPER_DIR": rctx.attr.developer_dir, "LC_ALL": "C"}, quiet = True)
if result.return_code != 0:
fail("host_cache_key: computing the host key failed:\n" + result.stderr)
report = json.decode(result.stdout)
Expand Down Expand Up @@ -62,6 +62,12 @@ Hosts with identical tools share results.
`:key.json` records the fields the key digests, for comparing two hosts.
""",
attrs = {
"developer_dir": attr.string(
default = "",
doc = "Developer directory used by macOS actions, or empty for the " +
"global xcode-select selection. Match an explicit action " +
"DEVELOPER_DIR here; job-local acquisition paths are not host tools.",
),
"exec_property": attr.string(
default = "host-cache-key",
doc = "Name of the execution property that carries the key.",
Expand Down
69 changes: 65 additions & 4 deletions tests/check_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import argparse
import json
import os
import platform
import re
import shutil
Expand All @@ -33,7 +34,10 @@
"""


def consumer_module(module_root: Path, tools: Path, prefix: Path | None) -> str:
def consumer_module(
module_root: Path, tools: Path, prefix: Path | None, developer_dir: str = ""
) -> str:
developer = f"developer_dir = {json.dumps(developer_dir)}," if developer_dir else ""
homebrew = f'homebrew_prefixes = ["{prefix}"],' if prefix is not None else ""
search_path = (
f'search_path = ["/bin", "/usr/bin", "/usr/local/bin", "{tools}"],'
Expand All @@ -50,6 +54,7 @@ def consumer_module(module_root: Path, tools: Path, prefix: Path | None) -> str:

host_cache_key(
name = "host_cache_key",
{developer}
{homebrew}
{search_path}
)
Expand Down Expand Up @@ -79,7 +84,9 @@ def __init__(self, bazel: str, workspace: Path, disk_cache: Path) -> None:
self.disk_cache = disk_cache
self.output_bases: list[Path] = []

def build(self, output_base: Path) -> str:
def build(
self, output_base: Path, environment: dict[str, str] | None = None
) -> str:
if output_base not in self.output_bases:
self.output_bases.append(output_base)
completed = subprocess.run(
Expand All @@ -93,6 +100,7 @@ def build(self, output_base: Path) -> str:
f"--disk_cache={self.disk_cache}",
],
cwd=self.workspace,
env=environment,
capture_output=True,
text=True,
check=False,
Expand Down Expand Up @@ -191,7 +199,9 @@ def check_watched_changes(bazel: str, module_root: Path, scratch: Path) -> None:
consumer.shutdown()


def check_this_host(bazel: str, module_root: Path, scratch: Path) -> None:
def check_this_host(
bazel: str, module_root: Path, scratch: Path, alternate_developer_dir: str
) -> None:
workspace = scratch / "defaults"
workspace.mkdir()
shutil.copy(module_root / ".bazelversion", workspace / ".bazelversion")
Expand All @@ -202,6 +212,56 @@ def check_this_host(bazel: str, module_root: Path, scratch: Path) -> None:
try:
expect("default attributes", consumer.build(output_base), "executed")
report = consumer.key_report(output_base)
if platform.system() == "Darwin":
# A workflow may export an acquired Xcode's job-local path; the
# default platform still describes the globally selected host tools.
overridden = {**os.environ, "DEVELOPER_DIR": str(scratch / "unselected")}
expect(
"inherited developer override, running server",
consumer.build(output_base, overridden),
"reused",
)
fresh = scratch / "override-output-base"
expect(
"inherited developer override, fresh base",
consumer.build(fresh, overridden),
"disk cache hit",
)
if consumer.key_report(fresh) != report:
raise SystemExit(
"check_platform: inherited Xcode selection changed the host key"
)
if alternate_developer_dir:
(workspace / "MODULE.bazel").write_text(
consumer_module(module_root, Path(), None, alternate_developer_dir)
)
expect(
"explicit developer selection, running server",
consumer.build(output_base),
"executed",
)
changed = consumer.key_report(output_base)
changed_fields = changed["fields"]
assert isinstance(changed_fields, dict)
fields = report["fields"]
assert isinstance(fields, dict)
if changed_fields["developer_tools"] == fields["developer_tools"]:
raise SystemExit(
"check_platform: alternate Xcode must have different tool identity"
)
expect(
"explicit developer selection, fresh base",
consumer.build(fresh),
"disk cache hit",
)
(workspace / "MODULE.bazel").write_text(
consumer_module(module_root, Path(), None)
)
expect(
"global developer selection restored",
consumer.build(output_base),
"disk cache hit",
)
finally:
consumer.shutdown()
fields = report["fields"]
Expand All @@ -220,12 +280,13 @@ def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--bazel", default="bazel")
parser.add_argument("--module-root", type=Path, default=ROOT)
parser.add_argument("--alternate-developer-dir", default="")
args = parser.parse_args()
module_root = args.module_root.resolve()
with tempfile.TemporaryDirectory() as directory:
scratch = Path(directory).resolve()
check_watched_changes(args.bazel, module_root, scratch)
check_this_host(args.bazel, module_root, scratch)
check_this_host(args.bazel, module_root, scratch, args.alternate_developer_dir)
print("check_platform: host changes reach action keys", flush=True)


Expand Down
Loading