| #!/usr/bin/env python3 |
| # Copyright 2023 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. |
| |
| """Check very quickly whether the Ninja build plan needs to be rebuilt.""" |
| |
| import argparse |
| import os |
| import sys |
| |
| # NOTE: Do not use pathlib.Path here since benchmarking shows that this makes |
| # this script _significantly_ slower (e.g. 42ms vs 16ms !) compared to os.path |
| # operations. |
| |
| |
| def ninja_plan_is_up_to_date(ninja_build_dir: str) -> bool: |
| """Return True if the Ninja build plan is up-to-date. |
| |
| This expects the Ninja build plan to be generated by GN, which |
| produces a `build_ninja.d` file with the right set of dependencies. |
| |
| Args: |
| ninja_build_dir: Ninja build output directory. |
| |
| Returns: |
| True if the build plan is up-to-date, False if the next Ninja call |
| would invoke a regen step. |
| """ |
| # This reads the build.ninja.d directly and tries to stat() all |
| # dependencies in it directly (around 7000+), which is much |
| # faster than Ninja trying to stat all build graph paths! |
| build_ninja_d = os.path.join(ninja_build_dir, "build.ninja.d") |
| if not os.path.exists(build_ninja_d): |
| return False |
| |
| with open(build_ninja_d) as f: |
| build_ninja_deps = f.read().split(" ") |
| |
| assert len(build_ninja_deps) > 1 |
| # The first item is the top-level manifest file followed by a colon, |
| # e.g. 'build.ninja:' |
| ninja_stamp = os.path.join(ninja_build_dir, build_ninja_deps[0][:-1]) |
| ninja_stamp_timestamp = os.stat(ninja_stamp).st_mtime |
| |
| try: |
| for dep in build_ninja_deps[1:]: |
| dep_path = os.path.join(ninja_build_dir, dep) |
| dep_timestamp = os.stat(dep_path).st_mtime |
| if dep_timestamp > ninja_stamp_timestamp: |
| return False |
| except FileNotFoundError: |
| return False |
| |
| return True |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument("ninja_build_dir", help="Ninja build directory.") |
| parser.add_argument( |
| "--quiet", action="store_true", help="Do not print anything." |
| ) |
| args = parser.parse_args() |
| if ninja_plan_is_up_to_date(args.ninja_build_dir): |
| if not args.quiet: |
| print("up-to-date!") |
| return 0 |
| |
| if not args.quiet: |
| print("regen required!") |
| return 1 |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main()) |