blob: b379fd4109c6b233ab8630d48f350853b7fefae2 [file] [log] [blame]
#!/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())