| #!/usr/bin/env fuchsia-vendored-python |
| # Copyright 2022 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """ |
| Process build "force clean fences". If it detects that the build should be clobbered, |
| it will run `gn clean`. |
| |
| A fence is crossed when a machine checks out a different revision and |
| there is a diff between $BUILD_DIR/.force_clean_fences and the outputs of the main |
| get_fences.py file and get_fences.py files under vendor/*/. |
| """ |
| |
| import argparse |
| import pathlib |
| import subprocess |
| import sys |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description="Process build force-clean fences.") |
| parser.add_argument( |
| "--gn-bin", |
| type=pathlib.Path, |
| required=True, |
| help="Path to prebuilt GN binary.") |
| parser.add_argument( |
| "--checkout-dir", |
| type=pathlib.Path, |
| required=True, |
| help="Path to $FUCHSIA_DIR.") |
| parser.add_argument( |
| "--build-dir", |
| type=pathlib.Path, |
| required=True, |
| help="Path to the root build dir, e.g. $FUCHSIA_DIR/out/default.") |
| parser.add_argument("--verbose", action="store_true") |
| args = parser.parse_args() |
| |
| def _log(message): |
| if args.verbose: |
| print(message) |
| |
| # check that inputs are valid |
| if not args.gn_bin.is_file(): |
| raise RuntimeError(f"{args.gn_bin} is not a file") |
| if not args.checkout_dir.is_dir(): |
| raise RuntimeError(f"{args.checkout_dir} is not a directory") |
| if not args.build_dir.is_dir(): |
| raise RuntimeError(f"{args.build_dir} is not a directory") |
| |
| # Find the main get_fences.py script and all those under vendor/*/ |
| get_fences_scripts = [] |
| subdirs = [args.checkout_dir] |
| vendor_dir = args.checkout_dir / "vendor" |
| if vendor_dir.exists(): |
| subdirs.extend(vendor_dir.iterdir()) |
| for subdir in subdirs: |
| get_fences_script = subdir / "build" / "force_clean" / "get_fences.py" |
| if get_fences_script.exists(): |
| get_fences_scripts.append(get_fences_script) |
| |
| # execute fences scripts using the same interpreter as us |
| current_fences = [] |
| for script in sorted(get_fences_scripts): |
| _log(f"generating clean-build fences from {script}") |
| current_fences.append( |
| subprocess.run( |
| [sys.executable, script], |
| stdout=subprocess.PIPE, |
| text=True, |
| check=True).stdout) |
| |
| current_fences = "\n".join(current_fences) |
| |
| existing_fences = None |
| existing_fences_path = args.build_dir / ".force_clean_fences" |
| if existing_fences_path.exists(): |
| _log("reading existing build dir's force-clean fences") |
| with open(existing_fences_path, "r") as f: |
| existing_fences = f.read() |
| |
| def _write_fences(): |
| _log( |
| f"writing new fences:\n=============\n{current_fences}\n=============" |
| ) |
| with open(existing_fences_path, "w") as f: |
| f.write(current_fences) |
| |
| # clobber if needed |
| if existing_fences == None: |
| _log("no fences file found, assuming nothing to clean") |
| _write_fences() |
| |
| elif existing_fences != current_fences: |
| _log(f"new fences found, clobbering build...") |
| subprocess.run([args.gn_bin, "clean", args.build_dir]) |
| _write_fences() |
| else: |
| _log("force_clean fences up-to-date, not clobbering") |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |