xref: /aosp_15_r20/build/make/tools/compare_builds.py (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
1*9e94795aSAndroid Build Coastguard Worker#!/usr/bin/env -S python3 -u
2*9e94795aSAndroid Build Coastguard Worker
3*9e94795aSAndroid Build Coastguard Worker"""
4*9e94795aSAndroid Build Coastguard WorkerThis script helps find various build behaviors that make builds less hermetic
5*9e94795aSAndroid Build Coastguard Workerand repeatable. Depending on the flags, it runs a sequence of builds and looks
6*9e94795aSAndroid Build Coastguard Workerfor files that have changed or have been improperly regenerated, updating
7*9e94795aSAndroid Build Coastguard Workertheir timestamps incorrectly. It also looks for changes that the build has
8*9e94795aSAndroid Build Coastguard Workerdone to the source tree, and for files whose contents are dependent on the
9*9e94795aSAndroid Build Coastguard Workerlocation of the out directory.
10*9e94795aSAndroid Build Coastguard Worker
11*9e94795aSAndroid Build Coastguard WorkerThis utility has two major modes, full and incremental. By default, this tool
12*9e94795aSAndroid Build Coastguard Workerruns in full mode. To run in incremental mode, pass the --incremental flag.
13*9e94795aSAndroid Build Coastguard Worker
14*9e94795aSAndroid Build Coastguard Worker
15*9e94795aSAndroid Build Coastguard WorkerFULL MODE
16*9e94795aSAndroid Build Coastguard Worker
17*9e94795aSAndroid Build Coastguard WorkerIn full mode, this tool helps verify BUILD CORRECTNESS by examining its
18*9e94795aSAndroid Build Coastguard WorkerREPEATABILITY. In full mode, this tool runs two complete builds in different
19*9e94795aSAndroid Build Coastguard Workerdirectories and compares the CONTENTS of the two directories. Lists of any
20*9e94795aSAndroid Build Coastguard Workerfiles that are added, removed or changed are printed, sorted by the timestamp
21*9e94795aSAndroid Build Coastguard Workerof that file, to aid finding which dependencies trigger the rebuilding of
22*9e94795aSAndroid Build Coastguard Workerother files.
23*9e94795aSAndroid Build Coastguard Worker
24*9e94795aSAndroid Build Coastguard Worker
25*9e94795aSAndroid Build Coastguard WorkerINCREMENTAL MODE
26*9e94795aSAndroid Build Coastguard Worker
27*9e94795aSAndroid Build Coastguard WorkerIn incremental mode, this tool helps verfiy the SPEED of the build. It runs two
28*9e94795aSAndroid Build Coastguard Workerbuilds and looks at the TIMESTAMPS of the generated files, and reports files
29*9e94795aSAndroid Build Coastguard Workerthat were changed by the second build. In theory, an incremental build with no
30*9e94795aSAndroid Build Coastguard Workersource files touched should not have any generated targets changed. As in full
31*9e94795aSAndroid Build Coastguard Workerbuilds, the file list is returned sorted by timestamp.
32*9e94795aSAndroid Build Coastguard Worker
33*9e94795aSAndroid Build Coastguard Worker
34*9e94795aSAndroid Build Coastguard WorkerOTHER CHECKS
35*9e94795aSAndroid Build Coastguard Worker
36*9e94795aSAndroid Build Coastguard WorkerIn both full and incremental mode, this tool looks at the timestamps of all
37*9e94795aSAndroid Build Coastguard Workersource files in the tree, and reports on files that have been touched. In the
38*9e94795aSAndroid Build Coastguard Workeroutput, these are labeled with the header "Source files touched after start of
39*9e94795aSAndroid Build Coastguard Workerbuild."
40*9e94795aSAndroid Build Coastguard Worker
41*9e94795aSAndroid Build Coastguard WorkerIn addition, by default, this tool sets the OUT_DIR environment variable to
42*9e94795aSAndroid Build Coastguard Workersomething other than "out" in order to find build rules that are not respecting
43*9e94795aSAndroid Build Coastguard Workerthe OUT_DIR. If you see these, you should fix them, but if your build can not
44*9e94795aSAndroid Build Coastguard Workercomplete for some reason because of this, you can pass the --no-check-out-dir
45*9e94795aSAndroid Build Coastguard Workerflag to suppress this check.
46*9e94795aSAndroid Build Coastguard Worker
47*9e94795aSAndroid Build Coastguard Worker
48*9e94795aSAndroid Build Coastguard WorkerOTHER FLAGS
49*9e94795aSAndroid Build Coastguard Worker
50*9e94795aSAndroid Build Coastguard WorkerIn full mode, the --detect-embedded-paths flag does the two builds in different
51*9e94795aSAndroid Build Coastguard Workerdirectories, to help in finding rules that embed the out directory path into
52*9e94795aSAndroid Build Coastguard Workerthe targets.
53*9e94795aSAndroid Build Coastguard Worker
54*9e94795aSAndroid Build Coastguard WorkerThe --hide-build-output flag hides the output of successful bulds, to make
55*9e94795aSAndroid Build Coastguard Workerscript output cleaner. The output of builds that fail is still shown.
56*9e94795aSAndroid Build Coastguard Worker
57*9e94795aSAndroid Build Coastguard WorkerThe --no-build flag is useful if you have already done a build and would
58*9e94795aSAndroid Build Coastguard Workerjust like to re-run the analysis.
59*9e94795aSAndroid Build Coastguard Worker
60*9e94795aSAndroid Build Coastguard WorkerThe --target flag lets you specify a build target other than the default
61*9e94795aSAndroid Build Coastguard Workerfull build (droid). You can pass "nothing" as in the example below, or a
62*9e94795aSAndroid Build Coastguard Workerspecific target, to reduce the scope of the checks performed.
63*9e94795aSAndroid Build Coastguard Worker
64*9e94795aSAndroid Build Coastguard WorkerThe --touch flag lets you specify a list of source files to touch between
65*9e94795aSAndroid Build Coastguard Workerthe builds, to examine the consequences of editing a particular file.
66*9e94795aSAndroid Build Coastguard Worker
67*9e94795aSAndroid Build Coastguard Worker
68*9e94795aSAndroid Build Coastguard WorkerEXAMPLE COMMANDLINES
69*9e94795aSAndroid Build Coastguard Worker
70*9e94795aSAndroid Build Coastguard WorkerPlease run build/make/tools/compare_builds.py --help for a full listing
71*9e94795aSAndroid Build Coastguard Workerof the commandline flags. Here are a sampling of useful combinations.
72*9e94795aSAndroid Build Coastguard Worker
73*9e94795aSAndroid Build Coastguard Worker  1. Find files changed during an incremental build that doesn't build
74*9e94795aSAndroid Build Coastguard Worker     any targets.
75*9e94795aSAndroid Build Coastguard Worker
76*9e94795aSAndroid Build Coastguard Worker       build/make/tools/compare_builds.py --incremental --target nothing
77*9e94795aSAndroid Build Coastguard Worker
78*9e94795aSAndroid Build Coastguard Worker     Long incremental build times, or consecutive builds that re-run build actions
79*9e94795aSAndroid Build Coastguard Worker     are usually caused by files being touched as part of loading the makefiles.
80*9e94795aSAndroid Build Coastguard Worker
81*9e94795aSAndroid Build Coastguard Worker     The nothing build (m nothing) loads the make and blueprint files, generates
82*9e94795aSAndroid Build Coastguard Worker     the dependency graph, but then doesn't actually build any targets. Checking
83*9e94795aSAndroid Build Coastguard Worker     against this build is the fastest and easiest way to find files that are
84*9e94795aSAndroid Build Coastguard Worker     modified while makefiles are read, for example with $(shell) invocations.
85*9e94795aSAndroid Build Coastguard Worker
86*9e94795aSAndroid Build Coastguard Worker  2. Find packaging targets that are different, ignoring intermediate files.
87*9e94795aSAndroid Build Coastguard Worker
88*9e94795aSAndroid Build Coastguard Worker       build/make/tools/compare_builds.py --subdirs --detect-embedded-paths
89*9e94795aSAndroid Build Coastguard Worker
90*9e94795aSAndroid Build Coastguard Worker     These flags will compare the final staging directories for partitions,
91*9e94795aSAndroid Build Coastguard Worker     as well as the APKs, apexes, testcases, and the like (the full directory
92*9e94795aSAndroid Build Coastguard Worker     list is in the DEFAULT_DIRS variable below). Since these are the files
93*9e94795aSAndroid Build Coastguard Worker     that are ultimately released, it is more important that these files be
94*9e94795aSAndroid Build Coastguard Worker     replicable, even if the intermediates that went into them are not (for
95*9e94795aSAndroid Build Coastguard Worker     example, when debugging symbols are stripped).
96*9e94795aSAndroid Build Coastguard Worker
97*9e94795aSAndroid Build Coastguard Worker  3. Check that all targets are repeatable.
98*9e94795aSAndroid Build Coastguard Worker
99*9e94795aSAndroid Build Coastguard Worker       build/make/tools/compare_builds.py --detect-embedded-paths
100*9e94795aSAndroid Build Coastguard Worker
101*9e94795aSAndroid Build Coastguard Worker     This check will list all of the differences in built targets that it can
102*9e94795aSAndroid Build Coastguard Worker     find. Be aware that the AOSP tree still has quite a few targets that
103*9e94795aSAndroid Build Coastguard Worker     are flagged by this check, so OEM changes might be lost in that list.
104*9e94795aSAndroid Build Coastguard Worker     That said, each file shown here is a potential blocker for a repeatable
105*9e94795aSAndroid Build Coastguard Worker     build.
106*9e94795aSAndroid Build Coastguard Worker
107*9e94795aSAndroid Build Coastguard Worker  4. See what targets are rebuilt when a file is touched between builds.
108*9e94795aSAndroid Build Coastguard Worker
109*9e94795aSAndroid Build Coastguard Worker       build/make/tools/compare_builds.py --incremental \
110*9e94795aSAndroid Build Coastguard Worker            --touch frameworks/base/core/java/android/app/Activity.java
111*9e94795aSAndroid Build Coastguard Worker
112*9e94795aSAndroid Build Coastguard Worker     This check simulates the common engineer workflow of touching a single
113*9e94795aSAndroid Build Coastguard Worker     file and rebuilding the whole system. To see a restricted view, consider
114*9e94795aSAndroid Build Coastguard Worker     also passing a --target option for a common use case. For example:
115*9e94795aSAndroid Build Coastguard Worker
116*9e94795aSAndroid Build Coastguard Worker       build/make/tools/compare_builds.py --incremental --target framework \
117*9e94795aSAndroid Build Coastguard Worker            --touch frameworks/base/core/java/android/app/Activity.java
118*9e94795aSAndroid Build Coastguard Worker"""
119*9e94795aSAndroid Build Coastguard Worker
120*9e94795aSAndroid Build Coastguard Workerimport argparse
121*9e94795aSAndroid Build Coastguard Workerimport itertools
122*9e94795aSAndroid Build Coastguard Workerimport os
123*9e94795aSAndroid Build Coastguard Workerimport shutil
124*9e94795aSAndroid Build Coastguard Workerimport stat
125*9e94795aSAndroid Build Coastguard Workerimport subprocess
126*9e94795aSAndroid Build Coastguard Workerimport sys
127*9e94795aSAndroid Build Coastguard Worker
128*9e94795aSAndroid Build Coastguard Worker
129*9e94795aSAndroid Build Coastguard Worker# Soong
130*9e94795aSAndroid Build Coastguard WorkerSOONG_UI = "build/soong/soong_ui.bash"
131*9e94795aSAndroid Build Coastguard Worker
132*9e94795aSAndroid Build Coastguard Worker
133*9e94795aSAndroid Build Coastguard Worker# Which directories to use if no --subdirs is supplied without explicit directories.
134*9e94795aSAndroid Build Coastguard WorkerDEFAULT_DIRS = (
135*9e94795aSAndroid Build Coastguard Worker    "apex",
136*9e94795aSAndroid Build Coastguard Worker    "data",
137*9e94795aSAndroid Build Coastguard Worker    "product",
138*9e94795aSAndroid Build Coastguard Worker    "ramdisk",
139*9e94795aSAndroid Build Coastguard Worker    "recovery",
140*9e94795aSAndroid Build Coastguard Worker    "root",
141*9e94795aSAndroid Build Coastguard Worker    "system",
142*9e94795aSAndroid Build Coastguard Worker    "system_ext",
143*9e94795aSAndroid Build Coastguard Worker    "system_other",
144*9e94795aSAndroid Build Coastguard Worker    "testcases",
145*9e94795aSAndroid Build Coastguard Worker    "vendor",
146*9e94795aSAndroid Build Coastguard Worker)
147*9e94795aSAndroid Build Coastguard Worker
148*9e94795aSAndroid Build Coastguard Worker
149*9e94795aSAndroid Build Coastguard Worker# Files to skip for incremental timestamp checking
150*9e94795aSAndroid Build Coastguard WorkerBUILD_INTERNALS_PREFIX_SKIP = (
151*9e94795aSAndroid Build Coastguard Worker    "soong/.glob/",
152*9e94795aSAndroid Build Coastguard Worker    ".path/",
153*9e94795aSAndroid Build Coastguard Worker)
154*9e94795aSAndroid Build Coastguard Worker
155*9e94795aSAndroid Build Coastguard Worker
156*9e94795aSAndroid Build Coastguard WorkerBUILD_INTERNALS_SUFFIX_SKIP = (
157*9e94795aSAndroid Build Coastguard Worker    "/soong/soong_build_metrics.pb",
158*9e94795aSAndroid Build Coastguard Worker    "/.installable_test_files",
159*9e94795aSAndroid Build Coastguard Worker    "/files.db",
160*9e94795aSAndroid Build Coastguard Worker    "/.blueprint.bootstrap",
161*9e94795aSAndroid Build Coastguard Worker    "/build_number.txt",
162*9e94795aSAndroid Build Coastguard Worker    "/build.ninja",
163*9e94795aSAndroid Build Coastguard Worker    "/.out-dir",
164*9e94795aSAndroid Build Coastguard Worker    "/build_fingerprint.txt",
165*9e94795aSAndroid Build Coastguard Worker    "/build_thumbprint.txt",
166*9e94795aSAndroid Build Coastguard Worker    "/.copied_headers_list",
167*9e94795aSAndroid Build Coastguard Worker    "/.installable_files",
168*9e94795aSAndroid Build Coastguard Worker)
169*9e94795aSAndroid Build Coastguard Worker
170*9e94795aSAndroid Build Coastguard Worker
171*9e94795aSAndroid Build Coastguard Workerclass DiffType(object):
172*9e94795aSAndroid Build Coastguard Worker  def __init__(self, code, message):
173*9e94795aSAndroid Build Coastguard Worker    self.code = code
174*9e94795aSAndroid Build Coastguard Worker    self.message = message
175*9e94795aSAndroid Build Coastguard Worker
176*9e94795aSAndroid Build Coastguard WorkerDIFF_NONE = DiffType("DIFF_NONE", "Files are the same")
177*9e94795aSAndroid Build Coastguard WorkerDIFF_MODE = DiffType("DIFF_MODE", "Stat mode bits differ")
178*9e94795aSAndroid Build Coastguard WorkerDIFF_SIZE = DiffType("DIFF_SIZE", "File size differs")
179*9e94795aSAndroid Build Coastguard WorkerDIFF_SYMLINK = DiffType("DIFF_SYMLINK", "Symlinks point to different locations")
180*9e94795aSAndroid Build Coastguard WorkerDIFF_CONTENTS = DiffType("DIFF_CONTENTS", "File contents differ")
181*9e94795aSAndroid Build Coastguard Worker
182*9e94795aSAndroid Build Coastguard Worker
183*9e94795aSAndroid Build Coastguard Workerdef main():
184*9e94795aSAndroid Build Coastguard Worker  argparser = argparse.ArgumentParser(description="Diff build outputs from two builds.",
185*9e94795aSAndroid Build Coastguard Worker                                      epilog="Run this command from the root of the tree."
186*9e94795aSAndroid Build Coastguard Worker                                        + " Before running this command, the build environment"
187*9e94795aSAndroid Build Coastguard Worker                                        + " must be set up, including sourcing build/envsetup.sh"
188*9e94795aSAndroid Build Coastguard Worker                                        + " and running lunch.")
189*9e94795aSAndroid Build Coastguard Worker  argparser.add_argument("--detect-embedded-paths", action="store_true",
190*9e94795aSAndroid Build Coastguard Worker      help="Use unique out dirs to detect paths embedded in binaries.")
191*9e94795aSAndroid Build Coastguard Worker  argparser.add_argument("--incremental", action="store_true",
192*9e94795aSAndroid Build Coastguard Worker      help="Compare which files are touched in two consecutive builds without a clean in between.")
193*9e94795aSAndroid Build Coastguard Worker  argparser.add_argument("--hide-build-output", action="store_true",
194*9e94795aSAndroid Build Coastguard Worker      help="Don't print the build output for successful builds")
195*9e94795aSAndroid Build Coastguard Worker  argparser.add_argument("--no-build", dest="run_build", action="store_false",
196*9e94795aSAndroid Build Coastguard Worker      help="Don't build or clean, but do everything else.")
197*9e94795aSAndroid Build Coastguard Worker  argparser.add_argument("--no-check-out-dir", dest="check_out_dir", action="store_false",
198*9e94795aSAndroid Build Coastguard Worker      help="Don't check for rules not honoring movable out directories.")
199*9e94795aSAndroid Build Coastguard Worker  argparser.add_argument("--subdirs", nargs="*",
200*9e94795aSAndroid Build Coastguard Worker      help="Only scan these subdirs of $PRODUCT_OUT instead of the whole out directory."
201*9e94795aSAndroid Build Coastguard Worker           + " The --subdirs argument with no listed directories will give a default list.")
202*9e94795aSAndroid Build Coastguard Worker  argparser.add_argument("--target", default="droid",
203*9e94795aSAndroid Build Coastguard Worker      help="Make target to run. The default is droid")
204*9e94795aSAndroid Build Coastguard Worker  argparser.add_argument("--touch", nargs="+", default=[],
205*9e94795aSAndroid Build Coastguard Worker      help="Files to touch between builds. Must pair with --incremental.")
206*9e94795aSAndroid Build Coastguard Worker  args = argparser.parse_args(sys.argv[1:])
207*9e94795aSAndroid Build Coastguard Worker
208*9e94795aSAndroid Build Coastguard Worker  if args.detect_embedded_paths and args.incremental:
209*9e94795aSAndroid Build Coastguard Worker    sys.stderr.write("Can't pass --detect-embedded-paths and --incremental together.\n")
210*9e94795aSAndroid Build Coastguard Worker    sys.exit(1)
211*9e94795aSAndroid Build Coastguard Worker  if args.detect_embedded_paths and not args.check_out_dir:
212*9e94795aSAndroid Build Coastguard Worker    sys.stderr.write("Can't pass --detect-embedded-paths and --no-check-out-dir together.\n")
213*9e94795aSAndroid Build Coastguard Worker    sys.exit(1)
214*9e94795aSAndroid Build Coastguard Worker  if args.touch and not args.incremental:
215*9e94795aSAndroid Build Coastguard Worker    sys.stderr.write("The --incremental flag is required if the --touch flag is passed.")
216*9e94795aSAndroid Build Coastguard Worker    sys.exit(1)
217*9e94795aSAndroid Build Coastguard Worker
218*9e94795aSAndroid Build Coastguard Worker  AssertAtTop()
219*9e94795aSAndroid Build Coastguard Worker  RequireEnvVar("TARGET_PRODUCT")
220*9e94795aSAndroid Build Coastguard Worker  RequireEnvVar("TARGET_BUILD_VARIANT")
221*9e94795aSAndroid Build Coastguard Worker
222*9e94795aSAndroid Build Coastguard Worker  # Out dir file names:
223*9e94795aSAndroid Build Coastguard Worker  #   - dir_prefix - The directory we'll put everything in (except for maybe the top level
224*9e94795aSAndroid Build Coastguard Worker  #     out/ dir).
225*9e94795aSAndroid Build Coastguard Worker  #   - *work_dir - The directory that we will build directly into. This is in dir_prefix
226*9e94795aSAndroid Build Coastguard Worker  #     unless --no-check-out-dir is set.
227*9e94795aSAndroid Build Coastguard Worker  #   - *out_dir - After building, if work_dir is different from out_dir, we move the out
228*9e94795aSAndroid Build Coastguard Worker  #     directory to here so we can do the comparisions.
229*9e94795aSAndroid Build Coastguard Worker  #   - timestamp_* - Files we touch so we know the various phases between the builds, so we
230*9e94795aSAndroid Build Coastguard Worker  #     can compare timestamps of files.
231*9e94795aSAndroid Build Coastguard Worker  if args.incremental:
232*9e94795aSAndroid Build Coastguard Worker    dir_prefix = "out_incremental"
233*9e94795aSAndroid Build Coastguard Worker    if args.check_out_dir:
234*9e94795aSAndroid Build Coastguard Worker      first_work_dir = first_out_dir = dir_prefix + "/out"
235*9e94795aSAndroid Build Coastguard Worker      second_work_dir = second_out_dir = dir_prefix + "/out"
236*9e94795aSAndroid Build Coastguard Worker    else:
237*9e94795aSAndroid Build Coastguard Worker      first_work_dir = first_out_dir = "out"
238*9e94795aSAndroid Build Coastguard Worker      second_work_dir = second_out_dir = "out"
239*9e94795aSAndroid Build Coastguard Worker  else:
240*9e94795aSAndroid Build Coastguard Worker    dir_prefix = "out_full"
241*9e94795aSAndroid Build Coastguard Worker    first_out_dir = dir_prefix + "/out_1"
242*9e94795aSAndroid Build Coastguard Worker    second_out_dir = dir_prefix + "/out_2"
243*9e94795aSAndroid Build Coastguard Worker    if not args.check_out_dir:
244*9e94795aSAndroid Build Coastguard Worker      first_work_dir = second_work_dir = "out"
245*9e94795aSAndroid Build Coastguard Worker    elif args.detect_embedded_paths:
246*9e94795aSAndroid Build Coastguard Worker      first_work_dir = first_out_dir
247*9e94795aSAndroid Build Coastguard Worker      second_work_dir = second_out_dir
248*9e94795aSAndroid Build Coastguard Worker    else:
249*9e94795aSAndroid Build Coastguard Worker      first_work_dir = dir_prefix + "/work"
250*9e94795aSAndroid Build Coastguard Worker      second_work_dir = dir_prefix + "/work"
251*9e94795aSAndroid Build Coastguard Worker  timestamp_start = dir_prefix + "/timestamp_start"
252*9e94795aSAndroid Build Coastguard Worker  timestamp_between = dir_prefix + "/timestamp_between"
253*9e94795aSAndroid Build Coastguard Worker  timestamp_end = dir_prefix + "/timestamp_end"
254*9e94795aSAndroid Build Coastguard Worker
255*9e94795aSAndroid Build Coastguard Worker  if args.run_build:
256*9e94795aSAndroid Build Coastguard Worker    # Initial clean, if necessary
257*9e94795aSAndroid Build Coastguard Worker    print("Cleaning " + dir_prefix + "/")
258*9e94795aSAndroid Build Coastguard Worker    Clean(dir_prefix)
259*9e94795aSAndroid Build Coastguard Worker    print("Cleaning out/")
260*9e94795aSAndroid Build Coastguard Worker    Clean("out")
261*9e94795aSAndroid Build Coastguard Worker    CreateEmptyFile(timestamp_start)
262*9e94795aSAndroid Build Coastguard Worker    print("Running the first build in " + first_work_dir)
263*9e94795aSAndroid Build Coastguard Worker    RunBuild(first_work_dir, first_out_dir, args.target, args.hide_build_output)
264*9e94795aSAndroid Build Coastguard Worker    for f in args.touch:
265*9e94795aSAndroid Build Coastguard Worker      print("Touching " + f)
266*9e94795aSAndroid Build Coastguard Worker      TouchFile(f)
267*9e94795aSAndroid Build Coastguard Worker    CreateEmptyFile(timestamp_between)
268*9e94795aSAndroid Build Coastguard Worker    print("Running the second build in " + second_work_dir)
269*9e94795aSAndroid Build Coastguard Worker    RunBuild(second_work_dir, second_out_dir, args.target, args.hide_build_output)
270*9e94795aSAndroid Build Coastguard Worker    CreateEmptyFile(timestamp_end)
271*9e94795aSAndroid Build Coastguard Worker    print("Done building")
272*9e94795aSAndroid Build Coastguard Worker    print()
273*9e94795aSAndroid Build Coastguard Worker
274*9e94795aSAndroid Build Coastguard Worker  # Which out directories to scan
275*9e94795aSAndroid Build Coastguard Worker  if args.subdirs is not None:
276*9e94795aSAndroid Build Coastguard Worker    if args.subdirs:
277*9e94795aSAndroid Build Coastguard Worker      subdirs = args.subdirs
278*9e94795aSAndroid Build Coastguard Worker    else:
279*9e94795aSAndroid Build Coastguard Worker      subdirs = DEFAULT_DIRS
280*9e94795aSAndroid Build Coastguard Worker    first_files = ProductFiles(RequireBuildVar(first_out_dir, "PRODUCT_OUT"), subdirs)
281*9e94795aSAndroid Build Coastguard Worker    second_files = ProductFiles(RequireBuildVar(second_out_dir, "PRODUCT_OUT"), subdirs)
282*9e94795aSAndroid Build Coastguard Worker  else:
283*9e94795aSAndroid Build Coastguard Worker    first_files = OutFiles(first_out_dir)
284*9e94795aSAndroid Build Coastguard Worker    second_files = OutFiles(second_out_dir)
285*9e94795aSAndroid Build Coastguard Worker
286*9e94795aSAndroid Build Coastguard Worker  printer = Printer()
287*9e94795aSAndroid Build Coastguard Worker
288*9e94795aSAndroid Build Coastguard Worker  if args.incremental:
289*9e94795aSAndroid Build Coastguard Worker    # Find files that were rebuilt unnecessarily
290*9e94795aSAndroid Build Coastguard Worker    touched_incrementally = FindOutFilesTouchedAfter(first_files,
291*9e94795aSAndroid Build Coastguard Worker                                                     GetFileTimestamp(timestamp_between))
292*9e94795aSAndroid Build Coastguard Worker    printer.PrintList("Touched in incremental build", touched_incrementally)
293*9e94795aSAndroid Build Coastguard Worker  else:
294*9e94795aSAndroid Build Coastguard Worker    # Compare the two out dirs
295*9e94795aSAndroid Build Coastguard Worker    added, removed, changed = DiffFileList(first_files, second_files)
296*9e94795aSAndroid Build Coastguard Worker    printer.PrintList("Added", added)
297*9e94795aSAndroid Build Coastguard Worker    printer.PrintList("Removed", removed)
298*9e94795aSAndroid Build Coastguard Worker    printer.PrintList("Changed", changed, "%s %s")
299*9e94795aSAndroid Build Coastguard Worker
300*9e94795aSAndroid Build Coastguard Worker  # Find files in the source tree that were touched
301*9e94795aSAndroid Build Coastguard Worker  touched_during = FindSourceFilesTouchedAfter(GetFileTimestamp(timestamp_start))
302*9e94795aSAndroid Build Coastguard Worker  printer.PrintList("Source files touched after start of build", touched_during)
303*9e94795aSAndroid Build Coastguard Worker
304*9e94795aSAndroid Build Coastguard Worker  # Find files and dirs that were output to "out" and didn't respect $OUT_DIR
305*9e94795aSAndroid Build Coastguard Worker  if args.check_out_dir:
306*9e94795aSAndroid Build Coastguard Worker    bad_out_dir_contents = FindFilesAndDirectories("out")
307*9e94795aSAndroid Build Coastguard Worker    printer.PrintList("Files and directories created by rules that didn't respect $OUT_DIR",
308*9e94795aSAndroid Build Coastguard Worker                      bad_out_dir_contents)
309*9e94795aSAndroid Build Coastguard Worker
310*9e94795aSAndroid Build Coastguard Worker  # If we didn't find anything, print success message
311*9e94795aSAndroid Build Coastguard Worker  if not printer.printed_anything:
312*9e94795aSAndroid Build Coastguard Worker    print("No bad behaviors found.")
313*9e94795aSAndroid Build Coastguard Worker
314*9e94795aSAndroid Build Coastguard Worker
315*9e94795aSAndroid Build Coastguard Workerdef AssertAtTop():
316*9e94795aSAndroid Build Coastguard Worker  """If the current directory is not the top of an android source tree, print an error
317*9e94795aSAndroid Build Coastguard Worker     message and exit."""
318*9e94795aSAndroid Build Coastguard Worker  if not os.access(SOONG_UI, os.X_OK):
319*9e94795aSAndroid Build Coastguard Worker    sys.stderr.write("FAILED: Please run from the root of the tree.\n")
320*9e94795aSAndroid Build Coastguard Worker    sys.exit(1)
321*9e94795aSAndroid Build Coastguard Worker
322*9e94795aSAndroid Build Coastguard Worker
323*9e94795aSAndroid Build Coastguard Workerdef RequireEnvVar(name):
324*9e94795aSAndroid Build Coastguard Worker  """Gets an environment variable. If that fails, then print an error message and exit."""
325*9e94795aSAndroid Build Coastguard Worker  result = os.environ.get(name)
326*9e94795aSAndroid Build Coastguard Worker  if not result:
327*9e94795aSAndroid Build Coastguard Worker    sys.stderr.write("error: Can't determine %s. Please run lunch first.\n" % name)
328*9e94795aSAndroid Build Coastguard Worker    sys.exit(1)
329*9e94795aSAndroid Build Coastguard Worker  return result
330*9e94795aSAndroid Build Coastguard Worker
331*9e94795aSAndroid Build Coastguard Worker
332*9e94795aSAndroid Build Coastguard Workerdef RunSoong(out_dir, args, capture_output):
333*9e94795aSAndroid Build Coastguard Worker  env = dict(os.environ)
334*9e94795aSAndroid Build Coastguard Worker  env["OUT_DIR"] = out_dir
335*9e94795aSAndroid Build Coastguard Worker  args = [SOONG_UI,] + args
336*9e94795aSAndroid Build Coastguard Worker  if capture_output:
337*9e94795aSAndroid Build Coastguard Worker    proc = subprocess.Popen(args, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
338*9e94795aSAndroid Build Coastguard Worker    combined_output, none = proc.communicate()
339*9e94795aSAndroid Build Coastguard Worker    return proc.returncode, combined_output
340*9e94795aSAndroid Build Coastguard Worker  else:
341*9e94795aSAndroid Build Coastguard Worker    result = subprocess.run(args, env=env)
342*9e94795aSAndroid Build Coastguard Worker    return result.returncode, None
343*9e94795aSAndroid Build Coastguard Worker
344*9e94795aSAndroid Build Coastguard Worker
345*9e94795aSAndroid Build Coastguard Workerdef GetBuildVar(out_dir, name):
346*9e94795aSAndroid Build Coastguard Worker  """Gets a variable from the build system."""
347*9e94795aSAndroid Build Coastguard Worker  returncode, output = RunSoong(out_dir, ["--dumpvar-mode", name], True)
348*9e94795aSAndroid Build Coastguard Worker  if returncode != 0:
349*9e94795aSAndroid Build Coastguard Worker    return None
350*9e94795aSAndroid Build Coastguard Worker  else:
351*9e94795aSAndroid Build Coastguard Worker    return output.decode("utf-8").strip()
352*9e94795aSAndroid Build Coastguard Worker
353*9e94795aSAndroid Build Coastguard Worker
354*9e94795aSAndroid Build Coastguard Workerdef RequireBuildVar(out_dir, name):
355*9e94795aSAndroid Build Coastguard Worker  """Gets a variable from the builds system. If that fails, then print an error
356*9e94795aSAndroid Build Coastguard Worker     message and exit."""
357*9e94795aSAndroid Build Coastguard Worker  value = GetBuildVar(out_dir, name)
358*9e94795aSAndroid Build Coastguard Worker  if not value:
359*9e94795aSAndroid Build Coastguard Worker    sys.stderr.write("error: Can't determine %s. Please run lunch first.\n" % name)
360*9e94795aSAndroid Build Coastguard Worker    sys.exit(1)
361*9e94795aSAndroid Build Coastguard Worker  return value
362*9e94795aSAndroid Build Coastguard Worker
363*9e94795aSAndroid Build Coastguard Worker
364*9e94795aSAndroid Build Coastguard Workerdef Clean(directory):
365*9e94795aSAndroid Build Coastguard Worker  """"Deletes the supplied directory."""
366*9e94795aSAndroid Build Coastguard Worker  try:
367*9e94795aSAndroid Build Coastguard Worker    shutil.rmtree(directory)
368*9e94795aSAndroid Build Coastguard Worker  except FileNotFoundError:
369*9e94795aSAndroid Build Coastguard Worker    pass
370*9e94795aSAndroid Build Coastguard Worker
371*9e94795aSAndroid Build Coastguard Worker
372*9e94795aSAndroid Build Coastguard Workerdef RunBuild(work_dir, out_dir, target, hide_build_output):
373*9e94795aSAndroid Build Coastguard Worker  """Runs a build. If the build fails, prints a message and exits."""
374*9e94795aSAndroid Build Coastguard Worker  returncode, output = RunSoong(work_dir,
375*9e94795aSAndroid Build Coastguard Worker                    ["--build-mode", "--all-modules", "--dir=" + os.getcwd(), target],
376*9e94795aSAndroid Build Coastguard Worker                    hide_build_output)
377*9e94795aSAndroid Build Coastguard Worker  if work_dir != out_dir:
378*9e94795aSAndroid Build Coastguard Worker    os.replace(work_dir, out_dir)
379*9e94795aSAndroid Build Coastguard Worker  if returncode != 0:
380*9e94795aSAndroid Build Coastguard Worker    if hide_build_output:
381*9e94795aSAndroid Build Coastguard Worker      # The build output was hidden, so print it now for debugging
382*9e94795aSAndroid Build Coastguard Worker      sys.stderr.buffer.write(output)
383*9e94795aSAndroid Build Coastguard Worker    sys.stderr.write("FAILED: Build failed. Stopping.\n")
384*9e94795aSAndroid Build Coastguard Worker    sys.exit(1)
385*9e94795aSAndroid Build Coastguard Worker
386*9e94795aSAndroid Build Coastguard Worker
387*9e94795aSAndroid Build Coastguard Workerdef DiffFileList(first_files, second_files):
388*9e94795aSAndroid Build Coastguard Worker  """Examines the files.
389*9e94795aSAndroid Build Coastguard Worker
390*9e94795aSAndroid Build Coastguard Worker  Returns:
391*9e94795aSAndroid Build Coastguard Worker    Filenames of files in first_filelist but not second_filelist (added files)
392*9e94795aSAndroid Build Coastguard Worker    Filenames of files in second_filelist but not first_filelist (removed files)
393*9e94795aSAndroid Build Coastguard Worker    2-Tuple of filenames for the files that are in both but are different (changed files)
394*9e94795aSAndroid Build Coastguard Worker  """
395*9e94795aSAndroid Build Coastguard Worker  # List of files, relative to their respective PRODUCT_OUT directories
396*9e94795aSAndroid Build Coastguard Worker  first_filelist = sorted([x for x in first_files], key=lambda x: x[1])
397*9e94795aSAndroid Build Coastguard Worker  second_filelist = sorted([x for x in second_files], key=lambda x: x[1])
398*9e94795aSAndroid Build Coastguard Worker
399*9e94795aSAndroid Build Coastguard Worker  added = []
400*9e94795aSAndroid Build Coastguard Worker  removed = []
401*9e94795aSAndroid Build Coastguard Worker  changed = []
402*9e94795aSAndroid Build Coastguard Worker
403*9e94795aSAndroid Build Coastguard Worker  first_index = 0
404*9e94795aSAndroid Build Coastguard Worker  second_index = 0
405*9e94795aSAndroid Build Coastguard Worker
406*9e94795aSAndroid Build Coastguard Worker  while first_index < len(first_filelist) and second_index < len(second_filelist):
407*9e94795aSAndroid Build Coastguard Worker    # Path relative to source root and path relative to PRODUCT_OUT
408*9e94795aSAndroid Build Coastguard Worker    first_full_filename, first_relative_filename = first_filelist[first_index]
409*9e94795aSAndroid Build Coastguard Worker    second_full_filename, second_relative_filename = second_filelist[second_index]
410*9e94795aSAndroid Build Coastguard Worker
411*9e94795aSAndroid Build Coastguard Worker    if first_relative_filename < second_relative_filename:
412*9e94795aSAndroid Build Coastguard Worker      # Removed
413*9e94795aSAndroid Build Coastguard Worker      removed.append(first_full_filename)
414*9e94795aSAndroid Build Coastguard Worker      first_index += 1
415*9e94795aSAndroid Build Coastguard Worker    elif first_relative_filename > second_relative_filename:
416*9e94795aSAndroid Build Coastguard Worker      # Added
417*9e94795aSAndroid Build Coastguard Worker      added.append(second_full_filename)
418*9e94795aSAndroid Build Coastguard Worker      second_index += 1
419*9e94795aSAndroid Build Coastguard Worker    else:
420*9e94795aSAndroid Build Coastguard Worker      # Both present
421*9e94795aSAndroid Build Coastguard Worker      diff_type = DiffFiles(first_full_filename, second_full_filename)
422*9e94795aSAndroid Build Coastguard Worker      if diff_type != DIFF_NONE:
423*9e94795aSAndroid Build Coastguard Worker        changed.append((first_full_filename, second_full_filename))
424*9e94795aSAndroid Build Coastguard Worker      first_index += 1
425*9e94795aSAndroid Build Coastguard Worker      second_index += 1
426*9e94795aSAndroid Build Coastguard Worker
427*9e94795aSAndroid Build Coastguard Worker  while first_index < len(first_filelist):
428*9e94795aSAndroid Build Coastguard Worker    first_full_filename, first_relative_filename = first_filelist[first_index]
429*9e94795aSAndroid Build Coastguard Worker    removed.append(first_full_filename)
430*9e94795aSAndroid Build Coastguard Worker    first_index += 1
431*9e94795aSAndroid Build Coastguard Worker
432*9e94795aSAndroid Build Coastguard Worker  while second_index < len(second_filelist):
433*9e94795aSAndroid Build Coastguard Worker    second_full_filename, second_relative_filename = second_filelist[second_index]
434*9e94795aSAndroid Build Coastguard Worker    added.append(second_full_filename)
435*9e94795aSAndroid Build Coastguard Worker    second_index += 1
436*9e94795aSAndroid Build Coastguard Worker
437*9e94795aSAndroid Build Coastguard Worker  return (SortByTimestamp(added),
438*9e94795aSAndroid Build Coastguard Worker          SortByTimestamp(removed),
439*9e94795aSAndroid Build Coastguard Worker          SortByTimestamp(changed, key=lambda item: item[1]))
440*9e94795aSAndroid Build Coastguard Worker
441*9e94795aSAndroid Build Coastguard Worker
442*9e94795aSAndroid Build Coastguard Workerdef FindOutFilesTouchedAfter(files, timestamp):
443*9e94795aSAndroid Build Coastguard Worker  """Find files in the given file iterator that were touched after timestamp."""
444*9e94795aSAndroid Build Coastguard Worker  result = []
445*9e94795aSAndroid Build Coastguard Worker  for full, relative in files:
446*9e94795aSAndroid Build Coastguard Worker    ts = GetFileTimestamp(full)
447*9e94795aSAndroid Build Coastguard Worker    if ts > timestamp:
448*9e94795aSAndroid Build Coastguard Worker      result.append(TouchedFile(full, ts))
449*9e94795aSAndroid Build Coastguard Worker  return [f.filename for f in sorted(result, key=lambda f: f.timestamp)]
450*9e94795aSAndroid Build Coastguard Worker
451*9e94795aSAndroid Build Coastguard Worker
452*9e94795aSAndroid Build Coastguard Workerdef GetFileTimestamp(filename):
453*9e94795aSAndroid Build Coastguard Worker  """Get timestamp for a file (just wraps stat)."""
454*9e94795aSAndroid Build Coastguard Worker  st = os.stat(filename, follow_symlinks=False)
455*9e94795aSAndroid Build Coastguard Worker  return st.st_mtime
456*9e94795aSAndroid Build Coastguard Worker
457*9e94795aSAndroid Build Coastguard Worker
458*9e94795aSAndroid Build Coastguard Workerdef SortByTimestamp(items, key=lambda item: item):
459*9e94795aSAndroid Build Coastguard Worker  """Sort the list by timestamp of files.
460*9e94795aSAndroid Build Coastguard Worker  Args:
461*9e94795aSAndroid Build Coastguard Worker    items - the list of items to sort
462*9e94795aSAndroid Build Coastguard Worker    key - a function to extract a filename from each element in items
463*9e94795aSAndroid Build Coastguard Worker  """
464*9e94795aSAndroid Build Coastguard Worker  return [x[0] for x in sorted([(item, GetFileTimestamp(key(item))) for item in items],
465*9e94795aSAndroid Build Coastguard Worker                               key=lambda y: y[1])]
466*9e94795aSAndroid Build Coastguard Worker
467*9e94795aSAndroid Build Coastguard Worker
468*9e94795aSAndroid Build Coastguard Workerdef FindSourceFilesTouchedAfter(timestamp):
469*9e94795aSAndroid Build Coastguard Worker  """Find files in the source tree that have changed after timestamp. Ignores
470*9e94795aSAndroid Build Coastguard Worker  the out directory."""
471*9e94795aSAndroid Build Coastguard Worker  result = []
472*9e94795aSAndroid Build Coastguard Worker  for root, dirs, files in os.walk(".", followlinks=False):
473*9e94795aSAndroid Build Coastguard Worker    if root == ".":
474*9e94795aSAndroid Build Coastguard Worker      RemoveItemsFromList(dirs, (".repo", "out", "out_full", "out_incremental"))
475*9e94795aSAndroid Build Coastguard Worker    for f in files:
476*9e94795aSAndroid Build Coastguard Worker      full = os.path.sep.join((root, f))[2:]
477*9e94795aSAndroid Build Coastguard Worker      ts = GetFileTimestamp(full)
478*9e94795aSAndroid Build Coastguard Worker      if ts > timestamp:
479*9e94795aSAndroid Build Coastguard Worker        result.append(TouchedFile(full, ts))
480*9e94795aSAndroid Build Coastguard Worker  return [f.filename for f in sorted(result, key=lambda f: f.timestamp)]
481*9e94795aSAndroid Build Coastguard Worker
482*9e94795aSAndroid Build Coastguard Worker
483*9e94795aSAndroid Build Coastguard Workerdef FindFilesAndDirectories(directory):
484*9e94795aSAndroid Build Coastguard Worker  """Finds all files and directories inside a directory."""
485*9e94795aSAndroid Build Coastguard Worker  result = []
486*9e94795aSAndroid Build Coastguard Worker  for root, dirs, files in os.walk(directory, followlinks=False):
487*9e94795aSAndroid Build Coastguard Worker    result += [os.path.sep.join((root, x, "")) for x in dirs]
488*9e94795aSAndroid Build Coastguard Worker    result += [os.path.sep.join((root, x)) for x in files]
489*9e94795aSAndroid Build Coastguard Worker  return result
490*9e94795aSAndroid Build Coastguard Worker
491*9e94795aSAndroid Build Coastguard Worker
492*9e94795aSAndroid Build Coastguard Workerdef CreateEmptyFile(filename):
493*9e94795aSAndroid Build Coastguard Worker  """Create an empty file with now as the timestamp at filename."""
494*9e94795aSAndroid Build Coastguard Worker  try:
495*9e94795aSAndroid Build Coastguard Worker    os.makedirs(os.path.dirname(filename))
496*9e94795aSAndroid Build Coastguard Worker  except FileExistsError:
497*9e94795aSAndroid Build Coastguard Worker    pass
498*9e94795aSAndroid Build Coastguard Worker  open(filename, "w").close()
499*9e94795aSAndroid Build Coastguard Worker  os.utime(filename)
500*9e94795aSAndroid Build Coastguard Worker
501*9e94795aSAndroid Build Coastguard Worker
502*9e94795aSAndroid Build Coastguard Workerdef TouchFile(filename):
503*9e94795aSAndroid Build Coastguard Worker  os.utime(filename)
504*9e94795aSAndroid Build Coastguard Worker
505*9e94795aSAndroid Build Coastguard Worker
506*9e94795aSAndroid Build Coastguard Workerdef DiffFiles(first_filename, second_filename):
507*9e94795aSAndroid Build Coastguard Worker  def AreFileContentsSame(remaining, first_filename, second_filename):
508*9e94795aSAndroid Build Coastguard Worker    """Compare the file contents. They must be known to be the same size."""
509*9e94795aSAndroid Build Coastguard Worker    CHUNK_SIZE = 32*1024
510*9e94795aSAndroid Build Coastguard Worker    with open(first_filename, "rb") as first_file:
511*9e94795aSAndroid Build Coastguard Worker      with open(second_filename, "rb") as second_file:
512*9e94795aSAndroid Build Coastguard Worker        while remaining > 0:
513*9e94795aSAndroid Build Coastguard Worker          size = min(CHUNK_SIZE, remaining)
514*9e94795aSAndroid Build Coastguard Worker          if first_file.read(CHUNK_SIZE) != second_file.read(CHUNK_SIZE):
515*9e94795aSAndroid Build Coastguard Worker            return False
516*9e94795aSAndroid Build Coastguard Worker          remaining -= size
517*9e94795aSAndroid Build Coastguard Worker        return True
518*9e94795aSAndroid Build Coastguard Worker
519*9e94795aSAndroid Build Coastguard Worker  first_stat = os.stat(first_filename, follow_symlinks=False)
520*9e94795aSAndroid Build Coastguard Worker  second_stat = os.stat(first_filename, follow_symlinks=False)
521*9e94795aSAndroid Build Coastguard Worker
522*9e94795aSAndroid Build Coastguard Worker  # Mode bits
523*9e94795aSAndroid Build Coastguard Worker  if first_stat.st_mode != second_stat.st_mode:
524*9e94795aSAndroid Build Coastguard Worker    return DIFF_MODE
525*9e94795aSAndroid Build Coastguard Worker
526*9e94795aSAndroid Build Coastguard Worker  # File size
527*9e94795aSAndroid Build Coastguard Worker  if first_stat.st_size != second_stat.st_size:
528*9e94795aSAndroid Build Coastguard Worker    return DIFF_SIZE
529*9e94795aSAndroid Build Coastguard Worker
530*9e94795aSAndroid Build Coastguard Worker  # Contents
531*9e94795aSAndroid Build Coastguard Worker  if stat.S_ISLNK(first_stat.st_mode):
532*9e94795aSAndroid Build Coastguard Worker    if os.readlink(first_filename) != os.readlink(second_filename):
533*9e94795aSAndroid Build Coastguard Worker      return DIFF_SYMLINK
534*9e94795aSAndroid Build Coastguard Worker  elif stat.S_ISREG(first_stat.st_mode):
535*9e94795aSAndroid Build Coastguard Worker    if not AreFileContentsSame(first_stat.st_size, first_filename, second_filename):
536*9e94795aSAndroid Build Coastguard Worker      return DIFF_CONTENTS
537*9e94795aSAndroid Build Coastguard Worker
538*9e94795aSAndroid Build Coastguard Worker  return DIFF_NONE
539*9e94795aSAndroid Build Coastguard Worker
540*9e94795aSAndroid Build Coastguard Worker
541*9e94795aSAndroid Build Coastguard Workerclass FileIterator(object):
542*9e94795aSAndroid Build Coastguard Worker  """Object that produces an iterator containing all files in a given directory.
543*9e94795aSAndroid Build Coastguard Worker
544*9e94795aSAndroid Build Coastguard Worker  Each iteration yields a tuple containing:
545*9e94795aSAndroid Build Coastguard Worker
546*9e94795aSAndroid Build Coastguard Worker  [0] (full) Path to file relative to source tree.
547*9e94795aSAndroid Build Coastguard Worker  [1] (relative) Path to the file relative to the base directory given in the
548*9e94795aSAndroid Build Coastguard Worker      constructor.
549*9e94795aSAndroid Build Coastguard Worker  """
550*9e94795aSAndroid Build Coastguard Worker
551*9e94795aSAndroid Build Coastguard Worker  def __init__(self, base_dir):
552*9e94795aSAndroid Build Coastguard Worker    self._base_dir = base_dir
553*9e94795aSAndroid Build Coastguard Worker
554*9e94795aSAndroid Build Coastguard Worker  def __iter__(self):
555*9e94795aSAndroid Build Coastguard Worker    return self._Iterator(self, self._base_dir)
556*9e94795aSAndroid Build Coastguard Worker
557*9e94795aSAndroid Build Coastguard Worker  def ShouldIncludeFile(self, root, path):
558*9e94795aSAndroid Build Coastguard Worker    return False
559*9e94795aSAndroid Build Coastguard Worker
560*9e94795aSAndroid Build Coastguard Worker  class _Iterator(object):
561*9e94795aSAndroid Build Coastguard Worker    def __init__(self, parent, base_dir):
562*9e94795aSAndroid Build Coastguard Worker      self._parent = parent
563*9e94795aSAndroid Build Coastguard Worker      self._base_dir = base_dir
564*9e94795aSAndroid Build Coastguard Worker      self._walker = os.walk(base_dir, followlinks=False)
565*9e94795aSAndroid Build Coastguard Worker      self._current_index = 0
566*9e94795aSAndroid Build Coastguard Worker      self._current_dir = []
567*9e94795aSAndroid Build Coastguard Worker
568*9e94795aSAndroid Build Coastguard Worker    def __iter__(self):
569*9e94795aSAndroid Build Coastguard Worker      return self
570*9e94795aSAndroid Build Coastguard Worker
571*9e94795aSAndroid Build Coastguard Worker    def __next__(self):
572*9e94795aSAndroid Build Coastguard Worker      # os.walk's iterator will eventually terminate by raising StopIteration
573*9e94795aSAndroid Build Coastguard Worker      while True:
574*9e94795aSAndroid Build Coastguard Worker        if self._current_index >= len(self._current_dir):
575*9e94795aSAndroid Build Coastguard Worker          root, dirs, files = self._walker.__next__()
576*9e94795aSAndroid Build Coastguard Worker          full_paths = [os.path.sep.join((root, f)) for f in files]
577*9e94795aSAndroid Build Coastguard Worker          pairs = [(f, f[len(self._base_dir)+1:]) for f in full_paths]
578*9e94795aSAndroid Build Coastguard Worker          self._current_dir = [(full, relative) for full, relative in pairs
579*9e94795aSAndroid Build Coastguard Worker                               if self._parent.ShouldIncludeFile(root, relative)]
580*9e94795aSAndroid Build Coastguard Worker          self._current_index = 0
581*9e94795aSAndroid Build Coastguard Worker          if not self._current_dir:
582*9e94795aSAndroid Build Coastguard Worker            continue
583*9e94795aSAndroid Build Coastguard Worker        index = self._current_index
584*9e94795aSAndroid Build Coastguard Worker        self._current_index += 1
585*9e94795aSAndroid Build Coastguard Worker        return self._current_dir[index]
586*9e94795aSAndroid Build Coastguard Worker
587*9e94795aSAndroid Build Coastguard Worker
588*9e94795aSAndroid Build Coastguard Workerclass OutFiles(FileIterator):
589*9e94795aSAndroid Build Coastguard Worker  """Object that produces an iterator containing all files in a given out directory,
590*9e94795aSAndroid Build Coastguard Worker  except for files which are known to be touched as part of build setup.
591*9e94795aSAndroid Build Coastguard Worker  """
592*9e94795aSAndroid Build Coastguard Worker  def __init__(self, out_dir):
593*9e94795aSAndroid Build Coastguard Worker    super().__init__(out_dir)
594*9e94795aSAndroid Build Coastguard Worker    self._out_dir = out_dir
595*9e94795aSAndroid Build Coastguard Worker
596*9e94795aSAndroid Build Coastguard Worker  def ShouldIncludeFile(self, root, relative):
597*9e94795aSAndroid Build Coastguard Worker    # Skip files in root, although note that this could actually skip
598*9e94795aSAndroid Build Coastguard Worker    # files that are sadly generated directly into that directory.
599*9e94795aSAndroid Build Coastguard Worker    if root == self._out_dir:
600*9e94795aSAndroid Build Coastguard Worker      return False
601*9e94795aSAndroid Build Coastguard Worker    # Skiplist
602*9e94795aSAndroid Build Coastguard Worker    for skip in BUILD_INTERNALS_PREFIX_SKIP:
603*9e94795aSAndroid Build Coastguard Worker      if relative.startswith(skip):
604*9e94795aSAndroid Build Coastguard Worker        return False
605*9e94795aSAndroid Build Coastguard Worker    for skip in BUILD_INTERNALS_SUFFIX_SKIP:
606*9e94795aSAndroid Build Coastguard Worker      if relative.endswith(skip):
607*9e94795aSAndroid Build Coastguard Worker        return False
608*9e94795aSAndroid Build Coastguard Worker    return True
609*9e94795aSAndroid Build Coastguard Worker
610*9e94795aSAndroid Build Coastguard Worker
611*9e94795aSAndroid Build Coastguard Workerclass ProductFiles(FileIterator):
612*9e94795aSAndroid Build Coastguard Worker  """Object that produces an iterator containing files in listed subdirectories of $PRODUCT_OUT.
613*9e94795aSAndroid Build Coastguard Worker  """
614*9e94795aSAndroid Build Coastguard Worker  def __init__(self, product_out, subdirs):
615*9e94795aSAndroid Build Coastguard Worker    super().__init__(product_out)
616*9e94795aSAndroid Build Coastguard Worker    self._subdirs = subdirs
617*9e94795aSAndroid Build Coastguard Worker
618*9e94795aSAndroid Build Coastguard Worker  def ShouldIncludeFile(self, root, relative):
619*9e94795aSAndroid Build Coastguard Worker    for subdir in self._subdirs:
620*9e94795aSAndroid Build Coastguard Worker      if relative.startswith(subdir):
621*9e94795aSAndroid Build Coastguard Worker        return True
622*9e94795aSAndroid Build Coastguard Worker    return False
623*9e94795aSAndroid Build Coastguard Worker
624*9e94795aSAndroid Build Coastguard Worker
625*9e94795aSAndroid Build Coastguard Workerclass TouchedFile(object):
626*9e94795aSAndroid Build Coastguard Worker  """A file in the out directory with a timestamp."""
627*9e94795aSAndroid Build Coastguard Worker  def __init__(self, filename, timestamp):
628*9e94795aSAndroid Build Coastguard Worker    self.filename = filename
629*9e94795aSAndroid Build Coastguard Worker    self.timestamp = timestamp
630*9e94795aSAndroid Build Coastguard Worker
631*9e94795aSAndroid Build Coastguard Worker
632*9e94795aSAndroid Build Coastguard Workerdef RemoveItemsFromList(haystack, needles):
633*9e94795aSAndroid Build Coastguard Worker  for needle in needles:
634*9e94795aSAndroid Build Coastguard Worker    try:
635*9e94795aSAndroid Build Coastguard Worker      haystack.remove(needle)
636*9e94795aSAndroid Build Coastguard Worker    except ValueError:
637*9e94795aSAndroid Build Coastguard Worker      pass
638*9e94795aSAndroid Build Coastguard Worker
639*9e94795aSAndroid Build Coastguard Worker
640*9e94795aSAndroid Build Coastguard Workerclass Printer(object):
641*9e94795aSAndroid Build Coastguard Worker  def __init__(self):
642*9e94795aSAndroid Build Coastguard Worker    self.printed_anything = False
643*9e94795aSAndroid Build Coastguard Worker
644*9e94795aSAndroid Build Coastguard Worker  def PrintList(self, title, items, fmt="%s"):
645*9e94795aSAndroid Build Coastguard Worker    if items:
646*9e94795aSAndroid Build Coastguard Worker      if self.printed_anything:
647*9e94795aSAndroid Build Coastguard Worker        sys.stdout.write("\n")
648*9e94795aSAndroid Build Coastguard Worker      sys.stdout.write("%s:\n" % title)
649*9e94795aSAndroid Build Coastguard Worker      for item in items:
650*9e94795aSAndroid Build Coastguard Worker        sys.stdout.write("  %s\n" % fmt % item)
651*9e94795aSAndroid Build Coastguard Worker      self.printed_anything = True
652*9e94795aSAndroid Build Coastguard Worker
653*9e94795aSAndroid Build Coastguard Worker
654*9e94795aSAndroid Build Coastguard Workerif __name__ == "__main__":
655*9e94795aSAndroid Build Coastguard Worker  try:
656*9e94795aSAndroid Build Coastguard Worker    main()
657*9e94795aSAndroid Build Coastguard Worker  except KeyboardInterrupt:
658*9e94795aSAndroid Build Coastguard Worker    pass
659*9e94795aSAndroid Build Coastguard Worker
660*9e94795aSAndroid Build Coastguard Worker
661*9e94795aSAndroid Build Coastguard Worker# vim: ts=2 sw=2 sts=2 nocindent
662