blob: 307b73b67172134936bfce1006699425e1960ee8 [file] [log] [blame]
Sami Kyostila0a34b032019-05-16 18:28:48 +01001#!/usr/bin/env python
2# Copyright (C) 2019 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16# This tool uses a collection of BUILD.gn files and build targets to generate
17# an "amalgamated" C++ header and source file pair which compiles to an
18# equivalent program. The tool also outputs the necessary compiler and linker
19# flags needed to compile the resulting source code.
20
Sami Kyostila3c88a1d2019-05-22 18:29:42 +010021from __future__ import print_function
Sami Kyostila0a34b032019-05-16 18:28:48 +010022import argparse
Sami Kyostila0a34b032019-05-16 18:28:48 +010023import os
24import re
25import shutil
26import subprocess
27import sys
Sami Kyostila468e61d2019-05-23 15:54:01 +010028import tempfile
Sami Kyostila0a34b032019-05-16 18:28:48 +010029
Sami Kyostila3c88a1d2019-05-22 18:29:42 +010030import gn_utils
31
Sami Kyostila0a34b032019-05-16 18:28:48 +010032# Default targets to include in the result.
Primiano Tucci75ae50e2019-08-28 13:09:55 +020033# TODO(primiano): change this script to recurse into target deps when generating
34# headers, but only for proto targets. .pbzero.h files don't include each other
35# and we need to list targets here individually, which is unmaintainable.
Sami Kyostila0a34b032019-05-16 18:28:48 +010036default_targets = [
Primiano Tucci658e2d62019-06-14 10:03:32 +010037 '//:libperfetto_client_experimental',
Primiano Tuccibf92d5c2019-07-03 14:07:45 +010038 "//include/perfetto/protozero:protozero",
Primiano Tucci5a449442019-06-15 14:31:03 +010039 "//protos/perfetto/config:zero",
Primiano Tuccicb050652019-08-29 01:10:34 +020040 "//protos/perfetto/trace:zero",
Sami Kyostila0a34b032019-05-16 18:28:48 +010041]
42
43# Arguments for the GN output directory (unless overridden from the command
44# line).
Primiano Tucci9c411652019-08-27 07:13:59 +020045gn_args = ' '.join([
46 'is_debug=false',
Primiano Tucci7e05fc12019-08-27 17:29:47 +020047 'is_perfetto_build_generator=true',
48 'is_perfetto_embedder=true',
49 'use_custom_libcxx=false',
50 'enable_perfetto_ipc=true',
Primiano Tucci9c411652019-08-27 07:13:59 +020051])
Sami Kyostila0a34b032019-05-16 18:28:48 +010052
Primiano Tuccicb050652019-08-29 01:10:34 +020053# By default, the amalgamated .h only recurses in #includes but not in the
54# target deps. In the case of protos we want to follow deps even in lieu of
55# direct #includes. This is because, by design, protozero headers don't
56# inlcude each other but rely on forward declarations. The alternative would
57# be adding each proto sub-target individually (e.g. //proto/trace/gpu:zero),
58# but doing that is unmaintainable.
59recurse_in_header_deps = '^//protos/.*'
60
Sami Kyostila0a34b032019-05-16 18:28:48 +010061# Compiler flags which aren't filtered out.
62cflag_whitelist = r'^-(W.*|fno-exceptions|fPIC|std.*|fvisibility.*)$'
63
64# Linker flags which aren't filtered out.
65ldflag_whitelist = r'^-()$'
66
67# Libraries which are filtered out.
68lib_blacklist = r'^(c|gcc_eh)$'
69
70# Macros which aren't filtered out.
71define_whitelist = r'^(PERFETTO.*|GOOGLE_PROTOBUF.*)$'
72
Sami Kyostila0a34b032019-05-16 18:28:48 +010073# Includes which will be removed from the generated source.
74includes_to_remove = r'^(gtest).*$'
75
Sami Kyostila7e8509f2019-05-29 12:36:24 +010076default_cflags = [
77 # Since we're expanding header files into the generated source file, some
78 # constant may remain unused.
79 '-Wno-unused-const-variable'
80]
81
Sami Kyostila0a34b032019-05-16 18:28:48 +010082# Build flags to satisfy a protobuf (lite or full) dependency.
83protobuf_cflags = [
84 # Note that these point to the local copy of protobuf in buildtools. In
85 # reality the user of the amalgamated result will have to provide a path to
86 # an installed copy of the exact same version of protobuf which was used to
87 # generate the amalgamated build.
88 '-isystembuildtools/protobuf/src',
89 '-Lbuildtools/protobuf/src/.libs',
90 # We also need to disable some warnings for protobuf.
91 '-Wno-missing-prototypes',
92 '-Wno-missing-variable-declarations',
93 '-Wno-sign-conversion',
94 '-Wno-unknown-pragmas',
95 '-Wno-unused-macros',
96]
97
98# A mapping of dependencies to system libraries. Libraries in this map will not
99# be built statically but instead added as dependencies of the amalgamated
100# project.
101system_library_map = {
102 '//buildtools:protobuf_full': {
103 'libs': ['protobuf'],
104 'cflags': protobuf_cflags,
105 },
106 '//buildtools:protobuf_lite': {
107 'libs': ['protobuf-lite'],
108 'cflags': protobuf_cflags,
109 },
110 '//buildtools:protoc_lib': {'libs': ['protoc']},
Sami Kyostila0a34b032019-05-16 18:28:48 +0100111}
112
113# ----------------------------------------------------------------------------
114# End of configuration.
115# ----------------------------------------------------------------------------
116
117tool_name = os.path.basename(__file__)
118preamble = """// Copyright (C) 2019 The Android Open Source Project
119//
120// Licensed under the Apache License, Version 2.0 (the "License");
121// you may not use this file except in compliance with the License.
122// You may obtain a copy of the License at
123//
124// http://www.apache.org/licenses/LICENSE-2.0
125//
126// Unless required by applicable law or agreed to in writing, software
127// distributed under the License is distributed on an "AS IS" BASIS,
128// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
129// See the License for the specific language governing permissions and
130// limitations under the License.
131//
132// This file is automatically generated by %s. Do not edit.
133""" % tool_name
134
135
136def apply_blacklist(blacklist, items):
137 return [item for item in items if not re.match(blacklist, item)]
138
139
140def apply_whitelist(whitelist, items):
141 return [item for item in items if re.match(whitelist, item)]
142
143
144class Error(Exception):
145 pass
146
147
148class DependencyNode(object):
149 """A target in a GN build description along with its dependencies."""
150
151 def __init__(self, target_name):
152 self.target_name = target_name
153 self.dependencies = set()
154
155 def add_dependency(self, target_node):
156 if target_node in self.dependencies:
157 return
158 self.dependencies.add(target_node)
159
160 def iterate_depth_first(self):
161 for node in sorted(self.dependencies, key=lambda n: n.target_name):
162 for node in node.iterate_depth_first():
163 yield node
164 if self.target_name:
165 yield self
166
167
168class DependencyTree(object):
169 """A tree of GN build target dependencies."""
170
171 def __init__(self):
172 self.target_to_node_map = {}
173 self.root = self._get_or_create_node(None)
174
175 def _get_or_create_node(self, target_name):
176 if target_name in self.target_to_node_map:
177 return self.target_to_node_map[target_name]
178 node = DependencyNode(target_name)
179 self.target_to_node_map[target_name] = node
180 return node
181
182 def add_dependency(self, from_target, to_target):
183 from_node = self._get_or_create_node(from_target)
184 to_node = self._get_or_create_node(to_target)
185 assert from_node is not to_node
186 from_node.add_dependency(to_node)
187
188 def iterate_depth_first(self):
189 for node in self.root.iterate_depth_first():
190 yield node
191
192
193class AmalgamatedProject(object):
194 """In-memory representation of an amalgamated source/header pair."""
195
Sami Kyostilad4c857e2019-08-08 18:44:33 +0100196 def __init__(self, desc, source_deps, compute_deps_only=False):
Sami Kyostila0a34b032019-05-16 18:28:48 +0100197 """Constructor.
198
199 Args:
200 desc: JSON build description.
201 source_deps: A map of (source file, [dependency header]) which is
202 to detect which header files are included by each source file.
Sami Kyostilad4c857e2019-08-08 18:44:33 +0100203 compute_deps_only: If True, the project will only be used to compute
204 dependency information. Use |get_source_files()| to retrieve
205 the result.
Sami Kyostila0a34b032019-05-16 18:28:48 +0100206 """
207 self.desc = desc
208 self.source_deps = source_deps
209 self.header = []
210 self.source = []
Sami Kyostila5bece6e2019-07-25 22:44:04 +0100211 self.source_defines = []
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100212 # Note that we don't support multi-arg flags.
213 self.cflags = set(default_cflags)
Sami Kyostila0a34b032019-05-16 18:28:48 +0100214 self.ldflags = set()
215 self.defines = set()
216 self.libs = set()
217 self._dependency_tree = DependencyTree()
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100218 self._processed_sources = set()
219 self._processed_headers = set()
Primiano Tuccicb050652019-08-29 01:10:34 +0200220 self._processed_header_deps = set()
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100221 self._processed_source_headers = set() # Header files included from .cc
Sami Kyostila0a34b032019-05-16 18:28:48 +0100222 self._include_re = re.compile(r'#include "(.*)"')
Sami Kyostilad4c857e2019-08-08 18:44:33 +0100223 self._compute_deps_only = compute_deps_only
Sami Kyostila0a34b032019-05-16 18:28:48 +0100224
225 def add_target(self, target_name):
226 """Include |target_name| in the amalgamated result."""
227 self._dependency_tree.add_dependency(None, target_name)
228 self._add_target_dependencies(target_name)
229 self._add_target_flags(target_name)
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100230 self._add_target_headers(target_name)
Sami Kyostila0a34b032019-05-16 18:28:48 +0100231
Primiano Tuccicb050652019-08-29 01:10:34 +0200232 # Recurse into target deps, but only for protos. This generates headers
233 # for all the .pbzero.h files, even if they don't #include each other.
234 for _, dep in self._iterate_dep_edges(target_name):
235 if (dep not in self._processed_header_deps and
236 re.match(recurse_in_header_deps, dep)):
237 self._processed_header_deps.add(dep)
238 self.add_target(dep)
239
240
Sami Kyostila0a34b032019-05-16 18:28:48 +0100241 def _iterate_dep_edges(self, target_name):
242 target = self.desc[target_name]
243 for dep in target.get('deps', []):
244 # Ignore system libraries since they will be added as build-time
245 # dependencies.
246 if dep in system_library_map:
247 continue
248 # Don't descend into build action dependencies.
249 if self.desc[dep]['type'] == 'action':
250 continue
251 for sub_target, sub_dep in self._iterate_dep_edges(dep):
252 yield sub_target, sub_dep
253 yield target_name, dep
254
255 def _iterate_target_and_deps(self, target_name):
256 yield target_name
257 for _, dep in self._iterate_dep_edges(target_name):
258 yield dep
259
260 def _add_target_dependencies(self, target_name):
261 for target, dep in self._iterate_dep_edges(target_name):
262 self._dependency_tree.add_dependency(target, dep)
263
264 def process_dep(dep):
265 if dep in system_library_map:
266 self.libs.update(system_library_map[dep].get('libs', []))
267 self.cflags.update(system_library_map[dep].get('cflags', []))
268 self.defines.update(system_library_map[dep].get('defines', []))
269 return True
270
271 def walk_all_deps(target_name):
272 target = self.desc[target_name]
273 for dep in target.get('deps', []):
274 if process_dep(dep):
275 return
276 walk_all_deps(dep)
277 walk_all_deps(target_name)
278
279 def _filter_cflags(self, cflags):
280 # Since we want to deduplicate flags, combine two-part switches (e.g.,
281 # "-foo bar") into one value ("-foobar") so we can store the result as
282 # a set.
283 result = []
284 for flag in cflags:
285 if flag.startswith('-'):
286 result.append(flag)
287 else:
288 result[-1] += flag
289 return apply_whitelist(cflag_whitelist, result)
290
291 def _add_target_flags(self, target_name):
292 for target_name in self._iterate_target_and_deps(target_name):
293 target = self.desc[target_name]
294 self.cflags.update(self._filter_cflags(target.get('cflags', [])))
295 self.cflags.update(self._filter_cflags(target.get('cflags_cc', [])))
296 self.ldflags.update(
297 apply_whitelist(ldflag_whitelist, target.get('ldflags', [])))
298 self.libs.update(
299 apply_blacklist(lib_blacklist, target.get('libs', [])))
300 self.defines.update(
301 apply_whitelist(define_whitelist, target.get('defines', [])))
302
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100303 def _add_target_headers(self, target_name):
304 target = self.desc[target_name]
305 if not 'sources' in target:
306 return
307 headers = [gn_utils.label_to_path(s)
308 for s in target['sources'] if s.endswith('.h')]
309 for header in headers:
310 self._add_header(target_name, header)
311
Sami Kyostila0a34b032019-05-16 18:28:48 +0100312 def _get_include_dirs(self, target_name):
313 include_dirs = set()
314 for target_name in self._iterate_target_and_deps(target_name):
315 target = self.desc[target_name]
316 if 'include_dirs' in target:
317 include_dirs.update(
Sami Kyostila3c88a1d2019-05-22 18:29:42 +0100318 [gn_utils.label_to_path(d) for d in target['include_dirs']])
Sami Kyostila0a34b032019-05-16 18:28:48 +0100319 return include_dirs
320
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100321 def _add_source_included_header(
322 self, include_dirs, allowed_files, header_name):
323 if header_name in self._processed_source_headers:
Sami Kyostila0a34b032019-05-16 18:28:48 +0100324 return
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100325 self._processed_source_headers.add(header_name)
Sami Kyostila0a34b032019-05-16 18:28:48 +0100326 for include_dir in include_dirs:
327 full_path = os.path.join(include_dir, header_name)
328 if os.path.exists(full_path):
329 if not full_path in allowed_files:
330 return
331 with open(full_path) as f:
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100332 self.source.append(
Sami Kyostila0a34b032019-05-16 18:28:48 +0100333 '// %s begin header: %s' % (tool_name, full_path))
Primiano Tucci2e1ae922019-05-30 12:13:47 +0100334 self.source.extend(
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100335 self._process_source_includes(
Primiano Tucci2e1ae922019-05-30 12:13:47 +0100336 include_dirs, allowed_files, f))
Sami Kyostila0a34b032019-05-16 18:28:48 +0100337 return
Sami Kyostilad4c857e2019-08-08 18:44:33 +0100338 if self._compute_deps_only:
339 return
Sami Kyostila0a34b032019-05-16 18:28:48 +0100340 msg = 'Looked in %s' % ', '.join('"%s"' % d for d in include_dirs)
341 raise Error('Header file %s not found. %s' % (header_name, msg))
342
343 def _add_source(self, target_name, source_name):
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100344 if source_name in self._processed_sources:
Sami Kyostila0a34b032019-05-16 18:28:48 +0100345 return
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100346 self._processed_sources.add(source_name)
Sami Kyostila0a34b032019-05-16 18:28:48 +0100347 include_dirs = self._get_include_dirs(target_name)
348 deps = self.source_deps[source_name]
349 if not os.path.exists(source_name):
350 raise Error('Source file %s not found' % source_name)
351 with open(source_name) as f:
352 self.source.append(
353 '// %s begin source: %s' % (tool_name, source_name))
354 try:
355 self.source.extend(self._patch_source(source_name,
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100356 self._process_source_includes(include_dirs, deps, f)))
Sami Kyostila0a34b032019-05-16 18:28:48 +0100357 except Error as e:
358 raise Error(
359 'Failed adding source %s: %s' % (source_name, e.message))
360
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100361 def _add_header_included_header(self, include_dirs, header_name):
362 if header_name in self._processed_headers:
363 return
364 self._processed_headers.add(header_name)
365 for include_dir in include_dirs:
366 full_path = os.path.join(include_dir, header_name)
367 if os.path.exists(full_path):
368 with open(full_path) as f:
369 self.header.append(
370 '// %s begin header: %s' % (tool_name, full_path))
Primiano Tucci2e1ae922019-05-30 12:13:47 +0100371 self.header.extend(
372 self._process_header_includes(include_dirs, f))
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100373 return
Sami Kyostilad4c857e2019-08-08 18:44:33 +0100374 if self._compute_deps_only:
375 return
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100376 msg = 'Looked in %s' % ', '.join('"%s"' % d for d in include_dirs)
377 raise Error('Header file %s not found. %s' % (header_name, msg))
378
379 def _add_header(self, target_name, header_name):
380 if header_name in self._processed_headers:
381 return
382 self._processed_headers.add(header_name)
383 include_dirs = self._get_include_dirs(target_name)
384 if not os.path.exists(header_name):
Sami Kyostilad4c857e2019-08-08 18:44:33 +0100385 if self._compute_deps_only:
386 return
Primiano Tucci75ae50e2019-08-28 13:09:55 +0200387 raise Error('Header file %s not found' % header_name)
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100388 with open(header_name) as f:
389 self.header.append(
390 '// %s begin header: %s' % (tool_name, header_name))
391 try:
Primiano Tucci2e1ae922019-05-30 12:13:47 +0100392 self.header.extend(
393 self._process_header_includes(include_dirs, f))
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100394 except Error as e:
395 raise Error(
396 'Failed adding header %s: %s' % (header_name, e.message))
397
Sami Kyostila0a34b032019-05-16 18:28:48 +0100398 def _patch_source(self, source_name, lines):
399 result = []
400 namespace = re.sub(r'[^a-z]', '_',
401 os.path.splitext(os.path.basename(source_name))[0])
402 for line in lines:
403 # Protobuf generates an identical anonymous function into each
404 # message description. Rename all but the first occurrence to avoid
405 # duplicate symbol definitions.
406 line = line.replace('MergeFromFail', '%s_MergeFromFail' % namespace)
407 result.append(line)
408 return result
409
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100410 def _process_source_includes(self, include_dirs, allowed_files, file):
Sami Kyostila0a34b032019-05-16 18:28:48 +0100411 result = []
412 for line in file:
413 line = line.rstrip('\n')
414 m = self._include_re.match(line)
415 if not m:
416 result.append(line)
417 continue
418 elif re.match(includes_to_remove, m.group(1)):
419 result.append('// %s removed: %s' % (tool_name, line))
Sami Kyostilafd367762019-05-22 17:25:50 +0100420 else:
Sami Kyostila0a34b032019-05-16 18:28:48 +0100421 result.append('// %s expanded: %s' % (tool_name, line))
Sami Kyostila7e8509f2019-05-29 12:36:24 +0100422 self._add_source_included_header(
423 include_dirs, allowed_files, m.group(1))
424 return result
425
426 def _process_header_includes(self, include_dirs, file):
427 result = []
428 for line in file:
429 line = line.rstrip('\n')
430 m = self._include_re.match(line)
431 if not m:
432 result.append(line)
433 continue
434 elif re.match(includes_to_remove, m.group(1)):
435 result.append('// %s removed: %s' % (tool_name, line))
436 else:
437 result.append('// %s expanded: %s' % (tool_name, line))
438 self._add_header_included_header(include_dirs, m.group(1))
Sami Kyostila0a34b032019-05-16 18:28:48 +0100439 return result
440
441 def generate(self):
442 """Prepares the output for this amalgamated project.
443
444 Call save() to persist the result.
445 """
Sami Kyostilad4c857e2019-08-08 18:44:33 +0100446 assert not self._compute_deps_only
Sami Kyostila5bece6e2019-07-25 22:44:04 +0100447 self.source_defines.append('// %s: predefined macros' % tool_name)
448 def add_define(name):
449 # Valued macros aren't supported for now.
450 assert '=' not in name
451 self.source_defines.append('#if !defined(%s)' % name)
452 self.source_defines.append('#define %s' % name)
453 self.source_defines.append('#endif')
454 for name in self.defines:
455 add_define(name)
Sami Kyostilad4c857e2019-08-08 18:44:33 +0100456 for target_name, source_name in self.get_source_files():
457 self._add_source(target_name, source_name)
Sami Kyostila0a34b032019-05-16 18:28:48 +0100458
Sami Kyostilad4c857e2019-08-08 18:44:33 +0100459 def get_source_files(self):
460 """Return a list of (target, [source file]) that describes the source
461 files pulled in by each target which is a dependency of this project.
462 """
Sami Kyostila0a34b032019-05-16 18:28:48 +0100463 source_files = []
464 for node in self._dependency_tree.iterate_depth_first():
465 target = self.desc[node.target_name]
466 if not 'sources' in target:
467 continue
Sami Kyostila3c88a1d2019-05-22 18:29:42 +0100468 sources = [(node.target_name, gn_utils.label_to_path(s))
Sami Kyostila0a34b032019-05-16 18:28:48 +0100469 for s in target['sources'] if s.endswith('.cc')]
470 source_files.extend(sources)
Sami Kyostilad4c857e2019-08-08 18:44:33 +0100471 return source_files
Sami Kyostila0a34b032019-05-16 18:28:48 +0100472
473 def _get_nice_path(self, prefix, format):
474 basename = os.path.basename(prefix)
475 return os.path.join(
476 os.path.relpath(os.path.dirname(prefix)), format % basename)
477
478 def save(self, output_prefix):
479 """Save the generated header and source file pair.
480
481 Returns a message describing the output with build instructions.
482 """
483 header_file = self._get_nice_path(output_prefix, '%s.h')
484 source_file = self._get_nice_path(output_prefix, '%s.cc')
485 with open(header_file, 'w') as f:
486 f.write('\n'.join([preamble] + self.header + ['\n']))
487 with open(source_file, 'w') as f:
488 include_stmt = '#include "%s"' % os.path.basename(header_file)
Sami Kyostila5bece6e2019-07-25 22:44:04 +0100489 f.write('\n'.join(
490 [preamble] + self.source_defines + [include_stmt] +
491 self.source + ['\n']))
Sami Kyostila0a34b032019-05-16 18:28:48 +0100492 build_cmd = self.get_build_command(output_prefix)
493
494 return """Amalgamated project written to %s and %s.
495
496Build settings:
497 - cflags: %s
498 - ldflags: %s
499 - libs: %s
Sami Kyostila0a34b032019-05-16 18:28:48 +0100500
501Example build command:
502
503%s
504""" % (header_file, source_file, ' '.join(self.cflags), ' '.join(self.ldflags),
Sami Kyostila5bece6e2019-07-25 22:44:04 +0100505 ' '.join(self.libs), ' '.join(build_cmd))
Sami Kyostila0a34b032019-05-16 18:28:48 +0100506
507 def get_build_command(self, output_prefix):
508 """Returns an example command line for building the output source."""
509 source = self._get_nice_path(output_prefix, '%s.cc')
510 library = self._get_nice_path(output_prefix, 'lib%s.so')
511 build_cmd = ['clang++', source, '-o', library, '-shared'] + \
512 sorted(self.cflags) + sorted(self.ldflags)
513 for lib in sorted(self.libs):
514 build_cmd.append('-l%s' % lib)
Sami Kyostila0a34b032019-05-16 18:28:48 +0100515 return build_cmd
516
517
Sami Kyostila0a34b032019-05-16 18:28:48 +0100518def main():
519 parser = argparse.ArgumentParser(
520 description='Generate an amalgamated header/source pair from a GN '
521 'build description.')
522 parser.add_argument(
523 '--output',
524 help='Base name of files to create. A .cc/.h extension will be added',
Sami Kyostila3c88a1d2019-05-22 18:29:42 +0100525 default=os.path.join(gn_utils.repo_root(), 'perfetto'))
Sami Kyostila0a34b032019-05-16 18:28:48 +0100526 parser.add_argument(
527 '--gn_args', help='GN arguments used to prepare the output directory',
528 default=gn_args)
529 parser.add_argument(
Sami Kyostila468e61d2019-05-23 15:54:01 +0100530 '--keep', help='Don\'t delete the GN output directory at exit',
Sami Kyostila0a34b032019-05-16 18:28:48 +0100531 action='store_true')
532 parser.add_argument(
533 '--build', help='Also compile the generated files',
534 action='store_true')
535 parser.add_argument(
Sami Kyostila468e61d2019-05-23 15:54:01 +0100536 '--check', help='Don\'t keep the generated files',
537 action='store_true')
538 parser.add_argument('--quiet', help='Only report errors',
539 action='store_true')
540 parser.add_argument(
Sami Kyostilad4c857e2019-08-08 18:44:33 +0100541 '--dump-deps',
542 help='List all source files that the amalgamated output depends on',
543 action='store_true')
544 parser.add_argument(
Sami Kyostila0a34b032019-05-16 18:28:48 +0100545 'targets',
546 nargs=argparse.REMAINDER,
547 help='Targets to include in the output (e.g., "//:libperfetto")')
548 args = parser.parse_args()
549 targets = args.targets or default_targets
550
Sami Kyostila468e61d2019-05-23 15:54:01 +0100551 output = args.output
552 if args.check:
553 output = os.path.join(tempfile.mkdtemp(), 'perfetto_amalgamated')
554
Sami Kyostila0a34b032019-05-16 18:28:48 +0100555 try:
Sami Kyostila468e61d2019-05-23 15:54:01 +0100556 if not args.quiet:
557 print('Building project...')
Sami Kyostila3c88a1d2019-05-22 18:29:42 +0100558 out = gn_utils.prepare_out_directory(
559 args.gn_args, 'tmp.gen_amalgamated')
560 desc = gn_utils.load_build_description(out)
Sami Kyostilad4c857e2019-08-08 18:44:33 +0100561
Sami Kyostila0a34b032019-05-16 18:28:48 +0100562 # We need to build everything first so that the necessary header
Sami Kyostilad4c857e2019-08-08 18:44:33 +0100563 # dependencies get generated. However if we are just dumping dependency
564 # information this can be skipped, allowing cross-platform operation.
565 if not args.dump_deps:
566 gn_utils.build_targets(out, targets)
Sami Kyostila3c88a1d2019-05-22 18:29:42 +0100567 source_deps = gn_utils.compute_source_dependencies(out)
Sami Kyostilad4c857e2019-08-08 18:44:33 +0100568 project = AmalgamatedProject(
569 desc, source_deps, compute_deps_only=args.dump_deps)
570
571 for target in targets:
572 project.add_target(target)
573
574 if args.dump_deps:
575 source_files = [
576 source_file for _, source_file in project.get_source_files()]
577 print('\n'.join(sorted(set(source_files))))
578 return
579
580 project.generate()
Sami Kyostila468e61d2019-05-23 15:54:01 +0100581 result = project.save(output)
582 if not args.quiet:
583 print(result)
Sami Kyostila0a34b032019-05-16 18:28:48 +0100584 if args.build:
Sami Kyostila468e61d2019-05-23 15:54:01 +0100585 if not args.quiet:
586 sys.stdout.write('Building amalgamated project...')
587 sys.stdout.flush()
588 subprocess.check_call(project.get_build_command(output))
589 if not args.quiet:
590 print('done')
Sami Kyostila0a34b032019-05-16 18:28:48 +0100591 finally:
592 if not args.keep:
593 shutil.rmtree(out)
Sami Kyostila468e61d2019-05-23 15:54:01 +0100594 if args.check:
595 shutil.rmtree(os.path.dirname(output))
Sami Kyostila0a34b032019-05-16 18:28:48 +0100596
597if __name__ == '__main__':
598 sys.exit(main())