diff --git a/.agents/rules/workspace.md b/.agents/rules/workspace.md index 20d8027482..7f87eba3b2 100644 --- a/.agents/rules/workspace.md +++ b/.agents/rules/workspace.md @@ -14,7 +14,7 @@ basis: * Run the workspace helper script to configure upstream tracking and safe pushing: ```bash - .agents/scripts/setup_triangle_branch.sh + .agents/scripts/setup_client.py ``` * Never use `git push -u` or `--set-upstream` (it breaks upstream tracking). * When pushing to a checked-out PR branch from an external fork, push diff --git a/.agents/scripts/setup_client.py b/.agents/scripts/setup_client.py new file mode 100755 index 0000000000..dc031c54f1 --- /dev/null +++ b/.agents/scripts/setup_client.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +"""Configures git tracking and user environment for rules_python worktrees.""" + +from __future__ import annotations + +import argparse +import subprocess +import sys +from pathlib import Path + + +def _get_current_branch() -> str: + result = subprocess.run( + ["git", "symbolic-ref", "--short", "HEAD"], + capture_output=True, + text=True, + check=False, + ) + if result.returncode != 0: + return "" + return result.stdout.strip() + + +def _get_workspace_root() -> Path: + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, + text=True, + check=True, + ) + return Path(result.stdout.strip()) + + +def setup_triangle_branch(branch: str) -> None: + """Configures local git tracking to pull from upstream and push to origin.""" + print(f"Setting up upstream tracking for branch: {branch}") + subprocess.check_call( + ["git", "config", "--local", f"branch.{branch}.remote", "upstream"] + ) + subprocess.check_call( + ["git", "config", "--local", f"branch.{branch}.merge", "refs/heads/main"] + ) + subprocess.check_call( + ["git", "config", "--local", f"branch.{branch}.pushRemote", "origin"] + ) + subprocess.check_call(["git", "config", "--local", "push.default", "current"]) + print( + "Successfully configured upstream/main tracking and origin pushRemote " + f"for {branch}!" + ) + + +def setup_buildbuddy_user_bazelrc(workspace_root: Path) -> None: + """Links user BuildBuddy config to workspace user.bazelrc if not present.""" + source_rc = ( + Path.home() / ".config" / "rules_python" / "buildbuddy.bazelrc" + ).resolve() + target_rc = workspace_root / "user.bazelrc" + + if not source_rc.exists(): + print( + f"Note: {source_rc} not found. Skipping user.bazelrc link " + "(BuildBuddy RBE may not work for local invocations)." + ) + return + + # Only create the symlink if user.bazelrc does not already exist + if target_rc.exists() or target_rc.is_symlink(): + print(f"user.bazelrc already exists at {target_rc}. Skipping link.") + return + + target_rc.symlink_to(source_rc) + print(f"Linked {source_rc} -> {target_rc}") + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Setup git tracking and environment for rules_python workspace." + ) + parser.add_argument( + "branch", + nargs="?", + default=None, + help="Branch name to configure (defaults to current branch)", + ) + args = parser.parse_args() + + branch = args.branch or _get_current_branch() + if not branch: + print( + "Error: Could not detect current branch name and none was provided.", + file=sys.stderr, + ) + return 1 + + workspace_root = _get_workspace_root() + setup_triangle_branch(branch) + setup_buildbuddy_user_bazelrc(workspace_root) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.agents/scripts/setup_triangle_branch.sh b/.agents/scripts/setup_triangle_branch.sh deleted file mode 100755 index b6bdc2bb20..0000000000 --- a/.agents/scripts/setup_triangle_branch.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -# Helper script to set up upstream tracking for fork-and-PR workflow. -set -e - -# Detect current branch if not provided as argument -BRANCH_NAME="${1:-$(git symbolic-ref --short HEAD)}" - -if [ -z "$BRANCH_NAME" ]; then - echo "Error: Could not detect current branch name." >&2 - exit 1 -fi - -echo "Setting up upstream tracking for branch: $BRANCH_NAME" - -# 1. Set the pull/fetch behavior to track the canonical repo's main branch -git config --local "branch.${BRANCH_NAME}.remote" upstream -git config --local "branch.${BRANCH_NAME}.merge" refs/heads/main - -# 2. Override the destination remote for pushing -git config --local "branch.${BRANCH_NAME}.pushRemote" origin - -# 3. Defend against global push.default settings (ensure it pushes to current branch name) -git config --local push.default current - -echo "Successfully configured upstream/main tracking and origin pushRemote for $BRANCH_NAME!" diff --git a/.github/actions/setup-bazel/action.yaml b/.github/actions/setup-bazel/action.yaml index aac5fd6a77..c1ae945f8f 100644 --- a/.github/actions/setup-bazel/action.yaml +++ b/.github/actions/setup-bazel/action.yaml @@ -37,6 +37,14 @@ runs: build --output_groups=-pyrefly build --//python/config_settings:py_pbs_distribution=install_only_stripped ${{ inputs.buildbuddy_api_key && format('common --config={0}', inputs.buildbuddy_config) || '' }} + build --build_metadata=ROLE=CI + build --build_metadata=VISIBILITY=PUBLIC + build --build_metadata=RUN_ID=${{ github.run_id }} + build --build_metadata=ACTOR=${{ github.actor }} + build --build_metadata=COMMIT_SHA=${{ github.event.pull_request.head.sha || github.sha }} + ${{ github.event.pull_request.number && format('build --build_metadata=PR_NUMBER={0}', github.event.pull_request.number) || '' }} + ${{ (github.head_ref || github.ref_name) && format('build --build_metadata=BRANCH_NAME={0}', github.head_ref || github.ref_name) || '' }} + build --build_metadata="BUILDBUDDY_LINKS=[GHA Run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" # setup-bazel only configures Bazelisk and restores caches; the Bazel # binary itself is downloaded by Bazelisk on first invocation. Because diff --git a/buildbuddy.bazelrc b/buildbuddy.bazelrc index 861e70e2cd..ae3c88853e 100644 --- a/buildbuddy.bazelrc +++ b/buildbuddy.bazelrc @@ -5,9 +5,26 @@ # `user.bazelrc` or passed by CI. build:_buildbuddy_base --bes_results_url=https://rules-python.buildbuddy.io/invocation/ build:_buildbuddy_base --bes_backend=grpcs://rules-python.buildbuddy.io +build:_buildbuddy_base --build_metadata=REPO_URL=https://github.com/bazel-contrib/rules_python common:_buildbuddy_base --remote_cache=grpcs://rules-python.buildbuddy.io common:_buildbuddy_base --remote_timeout=10m common:_buildbuddy_base --remote_executor=grpcs://rules-python.buildbuddy.io +# Compress remote cache transfers with zstd to improve network throughput. +common:_buildbuddy_base --remote_cache_compression +# Chunk large blobs using FastCDC (SplitBlob/SpliceBlob) for efficient +# deduplicated transfer. +common:_buildbuddy_base --experimental_remote_cache_chunking +# Automatically rewind and regenerate lost inputs when evicted from remote cache. +common:_buildbuddy_base --rewind_lost_inputs + +# Diagnostic logs for troubleshooting cache misses and gRPC errors +common:_buildbuddy_base --remote_grpc_log=bazel-remote-grpc.log +common:_buildbuddy_base --execution_log_compact_file=bazel-execution-log.binpb.zst + +# Detailed timing profiles for the BuildBuddy Timing tab +common:_buildbuddy_base --noslim_profile +common:_buildbuddy_base --experimental_profile_include_target_label +common:_buildbuddy_base --experimental_profile_include_primary_output # Re-apply `--enable_platform_specific_config` after setting `--remote_executor` # so `common:macos` and `common:windows` in `.bazelrc` inherit the priority of # `--config=buildbuddy_*` (e.g. when set in `~/.bazelrc` by `setup-bazel`, diff --git a/tests/workflows/setup_bazel_test.py b/tests/workflows/setup_bazel_test.py index ba04a964c4..aebfb63555 100644 --- a/tests/workflows/setup_bazel_test.py +++ b/tests/workflows/setup_bazel_test.py @@ -45,14 +45,55 @@ def test_action_yaml_enables_buildbuddy_on_all_os(): action = yaml.safe_load((_action_dir() / "action.yaml").read_text(encoding="utf-8")) steps = action["runs"]["steps"] setup_step = next(s for s in steps if s.get("name") == "Setup Bazel") - assert "inputs.buildbuddy_api_key" in setup_step["with"]["bazelrc"] - assert "runner.os" not in setup_step["with"]["bazelrc"] + bazelrc = setup_step["with"]["bazelrc"] + assert "inputs.buildbuddy_api_key" in bazelrc + assert "runner.os" not in bazelrc + assert "ROLE=CI" in bazelrc + assert "VISIBILITY=PUBLIC" in bazelrc + assert "RUN_ID=" in bazelrc + assert "ACTOR=" in bazelrc + assert "COMMIT_SHA=" in bazelrc + assert "PR_NUMBER=" in bazelrc + assert "BRANCH_NAME=" in bazelrc + assert '--build_metadata="BUILDBUDDY_LINKS=' in bazelrc secret_step = next( s for s in steps if s.get("name") == "Write secret to user.bazelrc" ) assert secret_step["if"] == "${{ inputs.buildbuddy_api_key != '' }}" +def test_buildbuddy_bazelrc_cache_and_diagnostics(): + """Verifies buildbuddy.bazelrc includes recommended cache, diagnostic, and + profiling flags. + """ + rf = runfiles.CreateOrRaise() + buildbuddy_path = rf.root() / "rules_python/buildbuddy.bazelrc" + buildbuddy_lines = [ + line.strip() + for line in buildbuddy_path.read_text(encoding="utf-8").splitlines() + if line.strip() and not line.strip().startswith("#") + ] + expected_flags = [ + ( + "build:_buildbuddy_base " + "--build_metadata=REPO_URL=https://github.com/bazel-contrib/rules_python" + ), + "common:_buildbuddy_base --remote_cache_compression", + "common:_buildbuddy_base --experimental_remote_cache_chunking", + "common:_buildbuddy_base --rewind_lost_inputs", + "common:_buildbuddy_base --remote_grpc_log=bazel-remote-grpc.log", + ( + "common:_buildbuddy_base " + "--execution_log_compact_file=bazel-execution-log.binpb.zst" + ), + "common:_buildbuddy_base --noslim_profile", + "common:_buildbuddy_base --experimental_profile_include_target_label", + "common:_buildbuddy_base --experimental_profile_include_primary_output", + ] + for flag in expected_flags: + assert flag in buildbuddy_lines, f"Expected '{flag}' in buildbuddy.bazelrc" + + def test_buildbuddy_bazelrc_disables_remote_executor_on_macos_and_windows(): """Verifies .bazelrc and buildbuddy.bazelrc disable remote_executor on macOS and Windows.""" rf = runfiles.CreateOrRaise()