blob: d0417d72fdca1abea746095b0f8b6a6f971ae5ca [file] [log] [blame]
Sami Kyostila3c88a1d2019-05-22 18:29:42 +01001# Copyright (C) 2019 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15# A collection of utilities for extracting build rule information from GN
16# projects.
17
18from __future__ import print_function
Primiano Tucci916f4e52020-10-16 20:40:33 +020019import collections
Lalit Maganti921a3d62022-12-17 14:28:50 +000020from compat import iteritems
Sami Kyostila3c88a1d2019-05-22 18:29:42 +010021import errno
Primiano Tucci9c411652019-08-27 07:13:59 +020022import filecmp
Sami Kyostila3c88a1d2019-05-22 18:29:42 +010023import json
24import os
25import re
26import shutil
27import subprocess
28import sys
Lalit Maganti921a3d62022-12-17 14:28:50 +000029from typing import Dict
30from typing import Optional
Lalit Maganti01f9b052022-12-17 01:21:13 +000031from typing import Set
Lalit Maganti921a3d62022-12-17 14:28:50 +000032from typing import Tuple
Sami Kyostila3c88a1d2019-05-22 18:29:42 +010033
Primiano Tucci8e627442019-08-28 07:58:38 +020034BUILDFLAGS_TARGET = '//gn:gen_buildflags'
Primiano Tucciec590132020-11-16 14:16:44 +010035GEN_VERSION_TARGET = '//src/base:version_gen_h'
Primiano Tuccif0d7ef82019-10-04 15:35:24 +010036TARGET_TOOLCHAIN = '//gn/standalone/toolchain:gcc_like_host'
37HOST_TOOLCHAIN = '//gn/standalone/toolchain:gcc_like_host'
Primiano Tucci1d409982019-09-19 10:15:18 +010038LINKER_UNIT_TYPES = ('executable', 'shared_library', 'static_library')
Primiano Tucci8e627442019-08-28 07:58:38 +020039
Primiano Tucci916f4e52020-10-16 20:40:33 +020040# TODO(primiano): investigate these, they require further componentization.
41ODR_VIOLATION_IGNORE_TARGETS = {
42 '//test/cts:perfetto_cts_deps',
43 '//:perfetto_integrationtests',
44}
45
Primiano Tucci8e627442019-08-28 07:58:38 +020046
Sami Kyostila3c88a1d2019-05-22 18:29:42 +010047def _check_command_output(cmd, cwd):
48 try:
Primiano Tucci834fdc72019-10-04 11:33:44 +010049 output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, cwd=cwd)
Sami Kyostila3c88a1d2019-05-22 18:29:42 +010050 except subprocess.CalledProcessError as e:
Primiano Tucci834fdc72019-10-04 11:33:44 +010051 print(
52 'Command "{}" failed in {}:'.format(' '.join(cmd), cwd),
53 file=sys.stderr)
Matthew Clarksona990d952019-10-08 14:52:12 +010054 print(e.output.decode(), file=sys.stderr)
Sami Kyostila3c88a1d2019-05-22 18:29:42 +010055 sys.exit(1)
56 else:
Matthew Clarksona990d952019-10-08 14:52:12 +010057 return output.decode()
Sami Kyostila3c88a1d2019-05-22 18:29:42 +010058
59
60def repo_root():
Primiano Tucci834fdc72019-10-04 11:33:44 +010061 """Returns an absolute path to the repository root."""
62 return os.path.join(
63 os.path.realpath(os.path.dirname(__file__)), os.path.pardir)
Sami Kyostila3c88a1d2019-05-22 18:29:42 +010064
65
Chinglin Yu2e958a22022-09-28 14:45:07 +080066def _tool_path(name, system_buildtools=False):
67 # Pass-through to use name if the caller requests to use the system
68 # toolchain.
69 if system_buildtools:
70 return [name]
Primiano Tuccieac5d712021-05-18 20:45:05 +010071 wrapper = os.path.abspath(
72 os.path.join(repo_root(), 'tools', 'run_buildtools_binary.py'))
73 return ['python3', wrapper, name]
Sami Kyostila3c88a1d2019-05-22 18:29:42 +010074
75
Chinglin Yu2e958a22022-09-28 14:45:07 +080076def prepare_out_directory(gn_args,
77 name,
78 root=repo_root(),
79 system_buildtools=False):
Primiano Tucci834fdc72019-10-04 11:33:44 +010080 """Creates the JSON build description by running GN.
Sami Kyostila3c88a1d2019-05-22 18:29:42 +010081
82 Returns (path, desc) where |path| is the location of the output directory
83 and |desc| is the JSON build description.
84 """
Primiano Tucci834fdc72019-10-04 11:33:44 +010085 out = os.path.join(root, 'out', name)
86 try:
87 os.makedirs(out)
88 except OSError as e:
89 if e.errno != errno.EEXIST:
90 raise
Primiano Tuccieac5d712021-05-18 20:45:05 +010091 _check_command_output(
Chinglin Yu2e958a22022-09-28 14:45:07 +080092 _tool_path('gn', system_buildtools) +
93 ['gen', out, '--args=%s' % gn_args],
94 cwd=repo_root())
Primiano Tucci834fdc72019-10-04 11:33:44 +010095 return out
Sami Kyostila3c88a1d2019-05-22 18:29:42 +010096
97
Chinglin Yu2e958a22022-09-28 14:45:07 +080098def load_build_description(out, system_buildtools=False):
Primiano Tucci834fdc72019-10-04 11:33:44 +010099 """Creates the JSON build description by running GN."""
Primiano Tuccieac5d712021-05-18 20:45:05 +0100100 desc = _check_command_output(
Chinglin Yu2e958a22022-09-28 14:45:07 +0800101 _tool_path('gn', system_buildtools) +
Primiano Tuccieac5d712021-05-18 20:45:05 +0100102 ['desc', out, '--format=json', '--all-toolchains', '//*'],
103 cwd=repo_root())
Primiano Tucci834fdc72019-10-04 11:33:44 +0100104 return json.loads(desc)
Sami Kyostila3c88a1d2019-05-22 18:29:42 +0100105
106
107def create_build_description(gn_args, root=repo_root()):
Primiano Tucci834fdc72019-10-04 11:33:44 +0100108 """Prepares a GN out directory and loads the build description from it.
Sami Kyostila3c88a1d2019-05-22 18:29:42 +0100109
110 The temporary out directory is automatically deleted.
111 """
Primiano Tucci834fdc72019-10-04 11:33:44 +0100112 out = prepare_out_directory(gn_args, 'tmp.gn_utils', root=root)
113 try:
114 return load_build_description(out)
115 finally:
116 shutil.rmtree(out)
Sami Kyostila3c88a1d2019-05-22 18:29:42 +0100117
118
Chinglin Yu2e958a22022-09-28 14:45:07 +0800119def build_targets(out, targets, quiet=False, system_buildtools=False):
Primiano Tucci834fdc72019-10-04 11:33:44 +0100120 """Runs ninja to build a list of GN targets in the given out directory.
Sami Kyostila3c88a1d2019-05-22 18:29:42 +0100121
122 Compiling these targets is required so that we can include any generated
123 source files in the amalgamated result.
124 """
Primiano Tucci834fdc72019-10-04 11:33:44 +0100125 targets = [t.replace('//', '') for t in targets]
Oystein Eftevaag0783a8f2024-04-09 14:17:23 -0700126 with open(os.devnull, 'w', newline='\n') as devnull:
Primiano Tucci834fdc72019-10-04 11:33:44 +0100127 stdout = devnull if quiet else None
Chinglin Yu2e958a22022-09-28 14:45:07 +0800128 cmd = _tool_path('ninja', system_buildtools) + targets
Primiano Tuccieac5d712021-05-18 20:45:05 +0100129 subprocess.check_call(cmd, cwd=os.path.abspath(out), stdout=stdout)
Sami Kyostila3c88a1d2019-05-22 18:29:42 +0100130
131
Chinglin Yu2e958a22022-09-28 14:45:07 +0800132def compute_source_dependencies(out, system_buildtools=False):
Primiano Tucci834fdc72019-10-04 11:33:44 +0100133 """For each source file, computes a set of headers it depends on."""
Primiano Tuccieac5d712021-05-18 20:45:05 +0100134 ninja_deps = _check_command_output(
Chinglin Yu2e958a22022-09-28 14:45:07 +0800135 _tool_path('ninja', system_buildtools) + ['-t', 'deps'], cwd=out)
Primiano Tucci834fdc72019-10-04 11:33:44 +0100136 deps = {}
137 current_source = None
138 for line in ninja_deps.split('\n'):
Matthew Clarkson58a3ebb2019-10-11 11:29:18 +0100139 filename = os.path.relpath(os.path.join(out, line.strip()), repo_root())
Chinglin Yu4c9f6d52024-03-25 08:05:46 +0000140 # Sanitizer builds may have a dependency of ignorelist.txt. Just skip it.
141 if filename.endswith('gn/standalone/sanitizers/ignorelist.txt'):
142 continue
Primiano Tucci834fdc72019-10-04 11:33:44 +0100143 if not line or line[0] != ' ':
144 current_source = None
145 continue
146 elif not current_source:
147 # We're assuming the source file is always listed before the
148 # headers.
149 assert os.path.splitext(line)[1] in ['.c', '.cc', '.cpp', '.S']
150 current_source = filename
151 deps[current_source] = []
152 else:
153 assert current_source
154 deps[current_source].append(filename)
155 return deps
Sami Kyostila3c88a1d2019-05-22 18:29:42 +0100156
157
158def label_to_path(label):
Primiano Tucci834fdc72019-10-04 11:33:44 +0100159 """Turn a GN output label (e.g., //some_dir/file.cc) into a path."""
160 assert label.startswith('//')
161 return label[2:]
Sami Kyostila3c88a1d2019-05-22 18:29:42 +0100162
163
164def label_without_toolchain(label):
Primiano Tucci834fdc72019-10-04 11:33:44 +0100165 """Strips the toolchain from a GN label.
Sami Kyostila3c88a1d2019-05-22 18:29:42 +0100166
167 Return a GN label (e.g //buildtools:protobuf(//gn/standalone/toolchain:
168 gcc_like_host) without the parenthesised toolchain part.
169 """
Primiano Tucci834fdc72019-10-04 11:33:44 +0100170 return label.split('(')[0]
Sami Kyostila3c88a1d2019-05-22 18:29:42 +0100171
172
173def label_to_target_name_with_path(label):
174 """
175 Turn a GN label into a target name involving the full path.
176 e.g., //src/perfetto:tests -> src_perfetto_tests
177 """
178 name = re.sub(r'^//:?', '', label)
179 name = re.sub(r'[^a-zA-Z0-9_]', '_', name)
Primiano Tucci9c411652019-08-27 07:13:59 +0200180 return name
181
Primiano Tucci8e627442019-08-28 07:58:38 +0200182
183def gen_buildflags(gn_args, target_file):
Primiano Tucci834fdc72019-10-04 11:33:44 +0100184 """Generates the perfetto_build_flags.h for the given config.
Primiano Tucci8e627442019-08-28 07:58:38 +0200185
186 target_file: the path, relative to the repo root, where the generated
187 buildflag header will be copied into.
188 """
Primiano Tucci834fdc72019-10-04 11:33:44 +0100189 tmp_out = prepare_out_directory(gn_args, 'tmp.gen_buildflags')
190 build_targets(tmp_out, [BUILDFLAGS_TARGET], quiet=True)
191 src = os.path.join(tmp_out, 'gen', 'build_config', 'perfetto_build_flags.h')
192 shutil.copy(src, os.path.join(repo_root(), target_file))
193 shutil.rmtree(tmp_out)
Primiano Tucci8e627442019-08-28 07:58:38 +0200194
195
Primiano Tucci9c411652019-08-27 07:13:59 +0200196def check_or_commit_generated_files(tmp_files, check):
Primiano Tucci834fdc72019-10-04 11:33:44 +0100197 """Checks that gen files are unchanged or renames them to the final location
Primiano Tucci9c411652019-08-27 07:13:59 +0200198
199 Takes in input a list of 'xxx.swp' files that have been written.
200 If check == False, it renames xxx.swp -> xxx.
201 If check == True, it just checks that the contents of 'xxx.swp' == 'xxx'.
202 Returns 0 if no diff was detected, 1 otherwise (to be used as exit code).
203 """
Primiano Tucci834fdc72019-10-04 11:33:44 +0100204 res = 0
205 for tmp_file in tmp_files:
206 assert (tmp_file.endswith('.swp'))
207 target_file = os.path.relpath(tmp_file[:-4])
208 if check:
209 if not filecmp.cmp(tmp_file, target_file):
210 sys.stderr.write('%s needs to be regenerated\n' % target_file)
211 res = 1
212 os.unlink(tmp_file)
213 else:
Oystein Eftevaag0783a8f2024-04-09 14:17:23 -0700214 os.replace(tmp_file, target_file)
Primiano Tucci834fdc72019-10-04 11:33:44 +0100215 return res
Primiano Tucci1d409982019-09-19 10:15:18 +0100216
217
Primiano Tucci916f4e52020-10-16 20:40:33 +0200218class ODRChecker(object):
219 """Detects ODR violations in linker units
220
221 When we turn GN source sets into Soong & Bazel file groups, there is the risk
222 to create ODR violations by including the same file group into different
223 linker unit (this is because other build systems don't have a concept
224 equivalent to GN's source_set). This class navigates the transitive
225 dependencies (mostly static libraries) of a target and detects if multiple
226 paths end up including the same file group. This is to avoid situations like:
227
228 traced.exe -> base(file group)
229 traced.exe -> libperfetto(static lib) -> base(file group)
230 """
231
Lalit Maganti01f9b052022-12-17 01:21:13 +0000232 def __init__(self, gn: 'GnParser', target_name: str):
Primiano Tucci916f4e52020-10-16 20:40:33 +0200233 self.gn = gn
234 self.root = gn.get_target(target_name)
Lalit Maganti01f9b052022-12-17 01:21:13 +0000235 self.source_sets: Dict[str, Set[str]] = collections.defaultdict(set)
Primiano Tucci916f4e52020-10-16 20:40:33 +0200236 self.deps_visited = set()
237 self.source_set_hdr_only = {}
238
239 self._visit(target_name)
240 num_violations = 0
241 if target_name in ODR_VIOLATION_IGNORE_TARGETS:
242 return
243 for sset, paths in self.source_sets.items():
244 if self.is_header_only(sset):
245 continue
246 if len(paths) != 1:
247 num_violations += 1
248 print(
249 'ODR violation in target %s, multiple paths include %s:\n %s' %
250 (target_name, sset, '\n '.join(paths)),
251 file=sys.stderr)
252 if num_violations > 0:
253 raise Exception('%d ODR violations detected. Build generation aborted' %
254 num_violations)
255
Lalit Maganti01f9b052022-12-17 01:21:13 +0000256 def _visit(self, target_name: str, parent_path=''):
Primiano Tucci916f4e52020-10-16 20:40:33 +0200257 target = self.gn.get_target(target_name)
258 path = ((parent_path + ' > ') if parent_path else '') + target_name
259 if not target:
260 raise Exception('Cannot find target %s' % target_name)
Lalit Maganti01f9b052022-12-17 01:21:13 +0000261 for ssdep in target.transitive_source_set_deps():
Primiano Tucci916f4e52020-10-16 20:40:33 +0200262 name_and_path = '%s (via %s)' % (target_name, path)
Lalit Maganti01f9b052022-12-17 01:21:13 +0000263 self.source_sets[ssdep.name].add(name_and_path)
264 deps = set(target.non_proto_or_source_set_deps()).union(
265 target.transitive_proto_deps()) - self.deps_visited
266 for dep in deps:
Primiano Tucci916f4e52020-10-16 20:40:33 +0200267 if dep.type == 'executable':
268 continue # Execs are strong boundaries and don't cause ODR violations.
269 # static_library dependencies should reset the path. It doesn't matter if
270 # we get to a source file via:
271 # source_set1 > static_lib > source.cc OR
272 # source_set1 > source_set2 > static_lib > source.cc
273 # This is NOT an ODR violation because source.cc is linked from the same
274 # static library
275 next_parent_path = path if dep.type != 'static_library' else ''
Lalit Maganti01f9b052022-12-17 01:21:13 +0000276 self.deps_visited.add(dep.name)
277 self._visit(dep.name, next_parent_path)
Primiano Tucci916f4e52020-10-16 20:40:33 +0200278
Lalit Maganti01f9b052022-12-17 01:21:13 +0000279 def is_header_only(self, source_set_name: str):
Primiano Tucci916f4e52020-10-16 20:40:33 +0200280 cached = self.source_set_hdr_only.get(source_set_name)
281 if cached is not None:
282 return cached
283 target = self.gn.get_target(source_set_name)
284 if target.type != 'source_set':
285 raise TypeError('%s is not a source_set' % source_set_name)
286 res = all(src.endswith('.h') for src in target.sources)
287 self.source_set_hdr_only[source_set_name] = res
288 return res
289
290
Primiano Tucci1d409982019-09-19 10:15:18 +0100291class GnParser(object):
Primiano Tucci834fdc72019-10-04 11:33:44 +0100292 """A parser with some cleverness for GN json desc files
Primiano Tucci1d409982019-09-19 10:15:18 +0100293
294 The main goals of this parser are:
295 1) Deal with the fact that other build systems don't have an equivalent
296 notion to GN's source_set. Conversely to Bazel's and Soong's filegroups,
297 GN source_sets expect that dependencies, cflags and other source_set
298 properties propagate up to the linker unit (static_library, executable or
299 shared_library). This parser simulates the same behavior: when a
300 source_set is encountered, some of its variables (cflags and such) are
301 copied up to the dependent targets. This is to allow gen_xxx to create
302 one filegroup for each source_set and then squash all the other flags
303 onto the linker unit.
304 2) Detect and special-case protobuf targets, figuring out the protoc-plugin
305 being used.
306 """
Primiano Tucci834fdc72019-10-04 11:33:44 +0100307
308 class Target(object):
309 """Reperesents A GN target.
Primiano Tucci1d409982019-09-19 10:15:18 +0100310
311 Maked properties are propagated up the dependency chain when a
312 source_set dependency is encountered.
313 """
Primiano Tucci1d409982019-09-19 10:15:18 +0100314
Primiano Tucci834fdc72019-10-04 11:33:44 +0100315 def __init__(self, name, type):
316 self.name = name # e.g. //src/ipc:ipc
Primiano Tucci1d409982019-09-19 10:15:18 +0100317
Primiano Tucci834fdc72019-10-04 11:33:44 +0100318 VALID_TYPES = ('static_library', 'shared_library', 'executable', 'group',
Lalit Maganti358a7e42022-11-09 15:16:21 +0000319 'action', 'source_set', 'proto_library', 'generated_file')
Primiano Tucci834fdc72019-10-04 11:33:44 +0100320 assert (type in VALID_TYPES)
321 self.type = type
Primiano Tuccif0d7ef82019-10-04 15:35:24 +0100322 self.testonly = False
323 self.toolchain = None
Primiano Tucci1d409982019-09-19 10:15:18 +0100324
Lalit Maganti3b09a3f2020-09-14 13:28:44 +0100325 # These are valid only for type == proto_library.
Primiano Tucci834fdc72019-10-04 11:33:44 +0100326 # This is typically: 'proto', 'protozero', 'ipc'.
Lalit Maganti921a3d62022-12-17 14:28:50 +0000327 self.proto_plugin: Optional[str] = None
Lalit Maganti3b09a3f2020-09-14 13:28:44 +0100328 self.proto_paths = set()
Lalit Magantiba364ec2022-02-15 19:53:20 +0000329 self.proto_exports = set()
Primiano Tucci1d409982019-09-19 10:15:18 +0100330
Primiano Tucci834fdc72019-10-04 11:33:44 +0100331 self.sources = set()
Ryan0331ac02021-04-26 15:41:47 +0100332 # TODO(primiano): consider whether the public section should be part of
333 # bubbled-up sources.
334 self.public_headers = set() # 'public'
Primiano Tucci1d409982019-09-19 10:15:18 +0100335
Primiano Tucci834fdc72019-10-04 11:33:44 +0100336 # These are valid only for type == 'action'
Lalit Maganti88e64de2022-11-21 18:29:57 +0000337 self.data = set()
Primiano Tucci834fdc72019-10-04 11:33:44 +0100338 self.inputs = set()
339 self.outputs = set()
340 self.script = None
341 self.args = []
Lalit Maganti358a7e42022-11-09 15:16:21 +0000342 self.custom_action_type = None
Lalit Maganti88e64de2022-11-21 18:29:57 +0000343 self.python_main = None
Primiano Tucci1d409982019-09-19 10:15:18 +0100344
Primiano Tucci834fdc72019-10-04 11:33:44 +0100345 # These variables are propagated up when encountering a dependency
346 # on a source_set target.
347 self.cflags = set()
348 self.defines = set()
Lalit Maganti01f9b052022-12-17 01:21:13 +0000349 self.deps: Set[GnParser.Target] = set()
350 self.transitive_deps: Set[GnParser.Target] = set()
Primiano Tuccif0d7ef82019-10-04 15:35:24 +0100351 self.libs = set()
Primiano Tucci834fdc72019-10-04 11:33:44 +0100352 self.include_dirs = set()
353 self.ldflags = set()
Primiano Tucci1d409982019-09-19 10:15:18 +0100354
Primiano Tucci834fdc72019-10-04 11:33:44 +0100355 # Deps on //gn:xxx have this flag set to True. These dependencies
356 # are special because they pull third_party code from buildtools/.
357 # We don't want to keep recursing into //buildtools in generators,
358 # this flag is used to stop the recursion and create an empty
359 # placeholder target once we hit //gn:protoc or similar.
360 self.is_third_party_dep_ = False
Matthew Clarkson9a5dfa52019-10-03 09:54:04 +0100361
Lalit Maganti01f9b052022-12-17 01:21:13 +0000362 def non_proto_or_source_set_deps(self):
363 return set(d for d in self.deps
364 if d.type != 'proto_library' and d.type != 'source_set')
365
366 def proto_deps(self):
367 return set(d for d in self.deps if d.type == 'proto_library')
368
369 def transitive_proto_deps(self):
370 return set(d for d in self.transitive_deps if d.type == 'proto_library')
371
372 def transitive_cpp_proto_deps(self):
373 return set(
374 d for d in self.transitive_deps if d.type == 'proto_library' and
375 d.proto_plugin != 'descriptor' and d.proto_plugin != 'source_set')
376
377 def transitive_source_set_deps(self):
378 return set(d for d in self.transitive_deps if d.type == 'source_set')
379
Primiano Tucci834fdc72019-10-04 11:33:44 +0100380 def __lt__(self, other):
381 if isinstance(other, self.__class__):
382 return self.name < other.name
383 raise TypeError(
384 '\'<\' not supported between instances of \'%s\' and \'%s\'' %
385 (type(self).__name__, type(other).__name__))
Primiano Tucci1d409982019-09-19 10:15:18 +0100386
Primiano Tucci834fdc72019-10-04 11:33:44 +0100387 def __repr__(self):
388 return json.dumps({
389 k: (list(sorted(v)) if isinstance(v, set) else v)
390 for (k, v) in iteritems(self.__dict__)
391 },
392 indent=4,
393 sort_keys=True)
Primiano Tucci1d409982019-09-19 10:15:18 +0100394
Primiano Tucci834fdc72019-10-04 11:33:44 +0100395 def update(self, other):
Lalit Maganti88e64de2022-11-21 18:29:57 +0000396 for key in ('cflags', 'data', 'defines', 'deps', 'include_dirs',
Lalit Maganti01f9b052022-12-17 01:21:13 +0000397 'ldflags', 'transitive_deps', 'libs', 'proto_paths'):
Primiano Tucci834fdc72019-10-04 11:33:44 +0100398 self.__dict__[key].update(other.__dict__.get(key, []))
Primiano Tucci1d409982019-09-19 10:15:18 +0100399
Primiano Tucci834fdc72019-10-04 11:33:44 +0100400 def __init__(self, gn_desc):
401 self.gn_desc_ = gn_desc
402 self.all_targets = {}
403 self.linker_units = {} # Executables, shared or static libraries.
404 self.source_sets = {}
405 self.actions = {}
406 self.proto_libs = {}
Primiano Tucci1d409982019-09-19 10:15:18 +0100407
Lalit Maganti01f9b052022-12-17 01:21:13 +0000408 def get_target(self, gn_target_name: str) -> Target:
Primiano Tucci834fdc72019-10-04 11:33:44 +0100409 """Returns a Target object from the fully qualified GN target name.
Primiano Tucci1d409982019-09-19 10:15:18 +0100410
411 It bubbles up variables from source_set dependencies as described in the
412 class-level comments.
413 """
Primiano Tucci834fdc72019-10-04 11:33:44 +0100414 target = self.all_targets.get(gn_target_name)
415 if target is not None:
Lalit Maganti613f97b2019-11-21 14:44:18 +0000416 return target # Target already processed.
Primiano Tucci1d409982019-09-19 10:15:18 +0100417
Primiano Tucci834fdc72019-10-04 11:33:44 +0100418 desc = self.gn_desc_[gn_target_name]
419 target = GnParser.Target(gn_target_name, desc['type'])
Primiano Tuccif0d7ef82019-10-04 15:35:24 +0100420 target.testonly = desc.get('testonly', False)
421 target.toolchain = desc.get('toolchain', None)
Primiano Tucci834fdc72019-10-04 11:33:44 +0100422 self.all_targets[gn_target_name] = target
Primiano Tucci1d409982019-09-19 10:15:18 +0100423
Primiano Tucci834fdc72019-10-04 11:33:44 +0100424 # We should never have GN targets directly depend on buidtools. They
425 # should hop via //gn:xxx, so we can give generators an opportunity to
426 # override them.
427 assert (not gn_target_name.startswith('//buildtools'))
Primiano Tucci1d409982019-09-19 10:15:18 +0100428
Primiano Tucci834fdc72019-10-04 11:33:44 +0100429 # Don't descend further into third_party targets. Genrators are supposed
430 # to either ignore them or route to other externally-provided targets.
431 if gn_target_name.startswith('//gn'):
432 target.is_third_party_dep_ = True
433 return target
Primiano Tucci1d409982019-09-19 10:15:18 +0100434
Lalit Maganti79d70fe2022-02-15 18:31:09 +0000435 proto_target_type, proto_desc = self.get_proto_target_type(target)
Lalit Maganti921a3d62022-12-17 14:28:50 +0000436 if proto_target_type:
437 assert proto_desc
Primiano Tucci834fdc72019-10-04 11:33:44 +0100438 self.proto_libs[target.name] = target
439 target.type = 'proto_library'
440 target.proto_plugin = proto_target_type
Lalit Maganti3b09a3f2020-09-14 13:28:44 +0100441 target.proto_paths.update(self.get_proto_paths(proto_desc))
Lalit Magantiba364ec2022-02-15 19:53:20 +0000442 target.proto_exports.update(self.get_proto_exports(proto_desc))
Primiano Tucci834fdc72019-10-04 11:33:44 +0100443 target.sources.update(proto_desc.get('sources', []))
444 assert (all(x.endswith('.proto') for x in target.sources))
445 elif target.type == 'source_set':
446 self.source_sets[gn_target_name] = target
447 target.sources.update(desc.get('sources', []))
Lalit Maganti358a7e42022-11-09 15:16:21 +0000448 target.inputs.update(desc.get('inputs', []))
Primiano Tucci834fdc72019-10-04 11:33:44 +0100449 elif target.type in LINKER_UNIT_TYPES:
450 self.linker_units[gn_target_name] = target
451 target.sources.update(desc.get('sources', []))
452 elif target.type == 'action':
453 self.actions[gn_target_name] = target
Lalit Maganti88e64de2022-11-21 18:29:57 +0000454 target.data.update(desc.get('metadata', {}).get('perfetto_data', []))
Lalit Maganti117272f2020-09-11 14:01:18 +0100455 target.inputs.update(desc.get('inputs', []))
456 target.sources.update(desc.get('sources', []))
Primiano Tuccif0d7ef82019-10-04 15:35:24 +0100457 outs = [re.sub('^//out/.+?/gen/', '', x) for x in desc['outputs']]
Primiano Tucci834fdc72019-10-04 11:33:44 +0100458 target.outputs.update(outs)
459 target.script = desc['script']
460 # Args are typically relative to the root build dir (../../xxx)
461 # because root build dir is typically out/xxx/).
462 target.args = [re.sub('^../../', '//', x) for x in desc['args']]
Lalit Maganti358a7e42022-11-09 15:16:21 +0000463 action_types = desc.get('metadata',
464 {}).get('perfetto_action_type_for_generator', [])
465 target.custom_action_type = action_types[0] if len(
466 action_types) > 0 else None
Lalit Maganti88e64de2022-11-21 18:29:57 +0000467 python_main = desc.get('metadata', {}).get('perfetto_python_main', [])
468 target.python_main = python_main[0] if python_main else None
Primiano Tucci1d409982019-09-19 10:15:18 +0100469
Ryan0331ac02021-04-26 15:41:47 +0100470 # Default for 'public' is //* - all headers in 'sources' are public.
471 # TODO(primiano): if a 'public' section is specified (even if empty), then
472 # the rest of 'sources' is considered inaccessible by gn. Consider
473 # emulating that, so that generated build files don't end up with overly
474 # accessible headers.
475 public_headers = [x for x in desc.get('public', []) if x != '*']
476 target.public_headers.update(public_headers)
477
Primiano Tucci834fdc72019-10-04 11:33:44 +0100478 target.cflags.update(desc.get('cflags', []) + desc.get('cflags_cc', []))
Primiano Tuccif0d7ef82019-10-04 15:35:24 +0100479 target.libs.update(desc.get('libs', []))
Primiano Tucci834fdc72019-10-04 11:33:44 +0100480 target.ldflags.update(desc.get('ldflags', []))
481 target.defines.update(desc.get('defines', []))
482 target.include_dirs.update(desc.get('include_dirs', []))
Primiano Tucci1d409982019-09-19 10:15:18 +0100483
Primiano Tucci834fdc72019-10-04 11:33:44 +0100484 # Recurse in dependencies.
485 for dep_name in desc.get('deps', []):
486 dep = self.get_target(dep_name)
Lalit Maganti01f9b052022-12-17 01:21:13 +0000487
488 # generated_file targets only exist for GN builds: we can safely ignore
489 # them.
490 if dep.type == 'generated_file':
491 continue
492
493 # When a proto_library depends on an action, that is always the "_gen"
494 # rule of the action which is "private" to the proto_library rule.
495 # therefore, just ignore it for dep tracking purposes.
496 if dep.type == 'action' and proto_target_type is not None:
497 target_no_toolchain = label_without_toolchain(target.name)
498 dep_no_toolchain = label_without_toolchain(dep.name)
499 assert (dep_no_toolchain == f'{target_no_toolchain}_gen')
500 continue
501
502 # Non-third party groups are only used for bubbling cflags etc so don't
503 # add a dep.
504 if dep.type == 'group' and not dep.is_third_party_dep_:
Primiano Tucci834fdc72019-10-04 11:33:44 +0100505 target.update(dep) # Bubble up groups's cflags/ldflags etc.
Lalit Maganti01f9b052022-12-17 01:21:13 +0000506 continue
507
508 # Linker units act as a hard boundary making all their internal deps
509 # opaque to the outside world. For this reason, do not propogate deps
510 # transitively across them.
511 if dep.type in LINKER_UNIT_TYPES:
512 target.deps.add(dep)
513 continue
514
515 if dep.type == 'source_set':
516 target.update(dep) # Bubble up source set's cflags/ldflags etc.
517 elif dep.type == 'proto_library':
518 target.proto_paths.update(dep.proto_paths)
519
520 target.deps.add(dep)
521 target.transitive_deps.add(dep)
522 target.transitive_deps.update(dep.transitive_deps)
Primiano Tucci1d409982019-09-19 10:15:18 +0100523
Primiano Tucci834fdc72019-10-04 11:33:44 +0100524 return target
Primiano Tucci1d409982019-09-19 10:15:18 +0100525
Lalit Magantiba364ec2022-02-15 19:53:20 +0000526 def get_proto_exports(self, proto_desc):
527 # exports in metadata will be available for source_set targets.
528 metadata = proto_desc.get('metadata', {})
529 return metadata.get('exports', [])
530
Lalit Maganti3b09a3f2020-09-14 13:28:44 +0100531 def get_proto_paths(self, proto_desc):
532 # import_dirs in metadata will be available for source_set targets.
533 metadata = proto_desc.get('metadata', {})
Lalit Magantiba364ec2022-02-15 19:53:20 +0000534 return metadata.get('import_dirs', [])
Lalit Maganti3b09a3f2020-09-14 13:28:44 +0100535
Lalit Maganti921a3d62022-12-17 14:28:50 +0000536 def get_proto_target_type(self, target: Target
537 ) -> Tuple[Optional[str], Optional[Dict]]:
Primiano Tucci834fdc72019-10-04 11:33:44 +0100538 """ Checks if the target is a proto library and return the plugin.
Primiano Tucci1d409982019-09-19 10:15:18 +0100539
540 Returns:
Primiano Tuccif0d7ef82019-10-04 15:35:24 +0100541 (None, None): if the target is not a proto library.
Lalit Maganti117272f2020-09-11 14:01:18 +0100542 (plugin, proto_desc) where |plugin| is 'proto' in the default (lite)
543 case or 'protozero' or 'ipc' or 'descriptor'; |proto_desc| is the GN
544 json desc of the target with the .proto sources (_gen target for
545 non-descriptor types or the target itself for descriptor type).
Primiano Tucci1d409982019-09-19 10:15:18 +0100546 """
Primiano Tuccif0d7ef82019-10-04 15:35:24 +0100547 parts = target.name.split('(', 1)
548 name = parts[0]
549 toolchain = '(' + parts[1] if len(parts) > 1 else ''
Lalit Maganti117272f2020-09-11 14:01:18 +0100550
551 # Descriptor targets don't have a _gen target; instead we look for the
552 # characteristic flag in the args of the target itself.
553 desc = self.gn_desc_.get(target.name)
554 if '--descriptor_set_out' in desc.get('args', []):
555 return 'descriptor', desc
556
557 # Source set proto targets have a non-empty proto_library_sources in the
Lalit Magantiba364ec2022-02-15 19:53:20 +0000558 # metadata of the description.
Lalit Maganti117272f2020-09-11 14:01:18 +0100559 metadata = desc.get('metadata', {})
560 if 'proto_library_sources' in metadata:
561 return 'source_set', desc
562
563 # In all other cases, we want to look at the _gen target as that has the
564 # important information.
Primiano Tuccif0d7ef82019-10-04 15:35:24 +0100565 gen_desc = self.gn_desc_.get('%s_gen%s' % (name, toolchain))
Primiano Tucci834fdc72019-10-04 11:33:44 +0100566 if gen_desc is None or gen_desc['type'] != 'action':
Primiano Tuccif0d7ef82019-10-04 15:35:24 +0100567 return None, None
Primiano Tucci834fdc72019-10-04 11:33:44 +0100568 args = gen_desc.get('args', [])
569 if '/protoc' not in args[0]:
Primiano Tuccif0d7ef82019-10-04 15:35:24 +0100570 return None, None
571 plugin = 'proto'
572 for arg in (arg for arg in args if arg.startswith('--plugin=')):
573 # |arg| at this point looks like:
574 # --plugin=protoc-gen-plugin=gcc_like_host/protozero_plugin
575 # or
576 # --plugin=protoc-gen-plugin=protozero_plugin
577 plugin = arg.split('=')[-1].split('/')[-1].replace('_plugin', '')
578 return plugin, gen_desc