gen_amalgamated: Check that amalgmated code is identical across platforms
For each of the supported amalgamation platforms (linux, android, mac),
check that the build contains the same set of source files. Otherwise
the generated output won't be the same for all platforms.
Bug: 132678367
Change-Id: I60f1036e91a3b3ee7b1140793b1ab9be261eb5ea
diff --git a/tools/gen_amalgamated b/tools/gen_amalgamated
index ce9ce82..57eed7c 100755
--- a/tools/gen_amalgamated
+++ b/tools/gen_amalgamated
@@ -181,13 +181,16 @@
class AmalgamatedProject(object):
"""In-memory representation of an amalgamated source/header pair."""
- def __init__(self, desc, source_deps):
+ def __init__(self, desc, source_deps, compute_deps_only=False):
"""Constructor.
Args:
desc: JSON build description.
source_deps: A map of (source file, [dependency header]) which is
to detect which header files are included by each source file.
+ compute_deps_only: If True, the project will only be used to compute
+ dependency information. Use |get_source_files()| to retrieve
+ the result.
"""
self.desc = desc
self.source_deps = source_deps
@@ -204,6 +207,7 @@
self._processed_headers = set()
self._processed_source_headers = set() # Header files included from .cc
self._include_re = re.compile(r'#include "(.*)"')
+ self._compute_deps_only = compute_deps_only
def add_target(self, target_name):
"""Include |target_name| in the amalgamated result."""
@@ -309,6 +313,8 @@
self._process_source_includes(
include_dirs, allowed_files, f))
return
+ if self._compute_deps_only:
+ return
msg = 'Looked in %s' % ', '.join('"%s"' % d for d in include_dirs)
raise Error('Header file %s not found. %s' % (header_name, msg))
@@ -343,6 +349,8 @@
self.header.extend(
self._process_header_includes(include_dirs, f))
return
+ if self._compute_deps_only:
+ return
msg = 'Looked in %s' % ', '.join('"%s"' % d for d in include_dirs)
raise Error('Header file %s not found. %s' % (header_name, msg))
@@ -352,6 +360,8 @@
self._processed_headers.add(header_name)
include_dirs = self._get_include_dirs(target_name)
if not os.path.exists(header_name):
+ if self._compute_deps_only:
+ return
raise Error('Header file %s not found' % source_name)
with open(header_name) as f:
self.header.append(
@@ -411,6 +421,7 @@
Call save() to persist the result.
"""
+ assert not self._compute_deps_only
self.source_defines.append('// %s: predefined macros' % tool_name)
def add_define(name):
# Valued macros aren't supported for now.
@@ -420,7 +431,13 @@
self.source_defines.append('#endif')
for name in self.defines:
add_define(name)
+ for target_name, source_name in self.get_source_files():
+ self._add_source(target_name, source_name)
+ def get_source_files(self):
+ """Return a list of (target, [source file]) that describes the source
+ files pulled in by each target which is a dependency of this project.
+ """
source_files = []
for node in self._dependency_tree.iterate_depth_first():
target = self.desc[node.target_name]
@@ -429,8 +446,7 @@
sources = [(node.target_name, gn_utils.label_to_path(s))
for s in target['sources'] if s.endswith('.cc')]
source_files.extend(sources)
- for target_name, source_name in source_files:
- self._add_source(target_name, source_name)
+ return source_files
def _get_nice_path(self, prefix, format):
basename = os.path.basename(prefix)
@@ -477,16 +493,6 @@
return build_cmd
-
-def create_amalgamated_project_for_targets(desc, targets, source_deps):
- """Generate an amalgamated project for a list of GN targets."""
- project = AmalgamatedProject(desc, source_deps)
- for target in targets:
- project.add_target(target)
- project.generate()
- return project
-
-
def main():
parser = argparse.ArgumentParser(
description='Generate an amalgamated header/source pair from a GN '
@@ -510,6 +516,10 @@
parser.add_argument('--quiet', help='Only report errors',
action='store_true')
parser.add_argument(
+ '--dump-deps',
+ help='List all source files that the amalgamated output depends on',
+ action='store_true')
+ parser.add_argument(
'targets',
nargs=argparse.REMAINDER,
help='Targets to include in the output (e.g., "//:libperfetto")')
@@ -526,12 +536,26 @@
out = gn_utils.prepare_out_directory(
args.gn_args, 'tmp.gen_amalgamated')
desc = gn_utils.load_build_description(out)
+
# We need to build everything first so that the necessary header
- # dependencies get generated.
- gn_utils.build_targets(out, targets)
+ # dependencies get generated. However if we are just dumping dependency
+ # information this can be skipped, allowing cross-platform operation.
+ if not args.dump_deps:
+ gn_utils.build_targets(out, targets)
source_deps = gn_utils.compute_source_dependencies(out)
- project = create_amalgamated_project_for_targets(
- desc, targets, source_deps)
+ project = AmalgamatedProject(
+ desc, source_deps, compute_deps_only=args.dump_deps)
+
+ for target in targets:
+ project.add_target(target)
+
+ if args.dump_deps:
+ source_files = [
+ source_file for _, source_file in project.get_source_files()]
+ print('\n'.join(sorted(set(source_files))))
+ return
+
+ project.generate()
result = project.save(output)
if not args.quiet:
print(result)