Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 1 | # 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 | |
| 18 | from __future__ import print_function |
Primiano Tucci | 916f4e5 | 2020-10-16 20:40:33 +0200 | [diff] [blame] | 19 | import collections |
Lalit Maganti | 921a3d6 | 2022-12-17 14:28:50 +0000 | [diff] [blame] | 20 | from compat import iteritems |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 21 | import errno |
Primiano Tucci | 9c41165 | 2019-08-27 07:13:59 +0200 | [diff] [blame] | 22 | import filecmp |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 23 | import json |
| 24 | import os |
| 25 | import re |
| 26 | import shutil |
| 27 | import subprocess |
| 28 | import sys |
Lalit Maganti | 921a3d6 | 2022-12-17 14:28:50 +0000 | [diff] [blame] | 29 | from typing import Dict |
| 30 | from typing import Optional |
Lalit Maganti | 01f9b05 | 2022-12-17 01:21:13 +0000 | [diff] [blame] | 31 | from typing import Set |
Lalit Maganti | 921a3d6 | 2022-12-17 14:28:50 +0000 | [diff] [blame] | 32 | from typing import Tuple |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 33 | |
Primiano Tucci | 8e62744 | 2019-08-28 07:58:38 +0200 | [diff] [blame] | 34 | BUILDFLAGS_TARGET = '//gn:gen_buildflags' |
Primiano Tucci | ec59013 | 2020-11-16 14:16:44 +0100 | [diff] [blame] | 35 | GEN_VERSION_TARGET = '//src/base:version_gen_h' |
Primiano Tucci | f0d7ef8 | 2019-10-04 15:35:24 +0100 | [diff] [blame] | 36 | TARGET_TOOLCHAIN = '//gn/standalone/toolchain:gcc_like_host' |
| 37 | HOST_TOOLCHAIN = '//gn/standalone/toolchain:gcc_like_host' |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 38 | LINKER_UNIT_TYPES = ('executable', 'shared_library', 'static_library') |
Primiano Tucci | 8e62744 | 2019-08-28 07:58:38 +0200 | [diff] [blame] | 39 | |
Primiano Tucci | 916f4e5 | 2020-10-16 20:40:33 +0200 | [diff] [blame] | 40 | # TODO(primiano): investigate these, they require further componentization. |
| 41 | ODR_VIOLATION_IGNORE_TARGETS = { |
| 42 | '//test/cts:perfetto_cts_deps', |
| 43 | '//:perfetto_integrationtests', |
| 44 | } |
| 45 | |
Primiano Tucci | 8e62744 | 2019-08-28 07:58:38 +0200 | [diff] [blame] | 46 | |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 47 | def _check_command_output(cmd, cwd): |
| 48 | try: |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 49 | output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, cwd=cwd) |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 50 | except subprocess.CalledProcessError as e: |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 51 | print( |
| 52 | 'Command "{}" failed in {}:'.format(' '.join(cmd), cwd), |
| 53 | file=sys.stderr) |
Matthew Clarkson | a990d95 | 2019-10-08 14:52:12 +0100 | [diff] [blame] | 54 | print(e.output.decode(), file=sys.stderr) |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 55 | sys.exit(1) |
| 56 | else: |
Matthew Clarkson | a990d95 | 2019-10-08 14:52:12 +0100 | [diff] [blame] | 57 | return output.decode() |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 58 | |
| 59 | |
| 60 | def repo_root(): |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 61 | """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 Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 64 | |
| 65 | |
Chinglin Yu | 2e958a2 | 2022-09-28 14:45:07 +0800 | [diff] [blame] | 66 | def _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 Tucci | eac5d71 | 2021-05-18 20:45:05 +0100 | [diff] [blame] | 71 | wrapper = os.path.abspath( |
| 72 | os.path.join(repo_root(), 'tools', 'run_buildtools_binary.py')) |
| 73 | return ['python3', wrapper, name] |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 74 | |
| 75 | |
Chinglin Yu | 2e958a2 | 2022-09-28 14:45:07 +0800 | [diff] [blame] | 76 | def prepare_out_directory(gn_args, |
| 77 | name, |
| 78 | root=repo_root(), |
| 79 | system_buildtools=False): |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 80 | """Creates the JSON build description by running GN. |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 81 | |
| 82 | Returns (path, desc) where |path| is the location of the output directory |
| 83 | and |desc| is the JSON build description. |
| 84 | """ |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 85 | 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 Tucci | eac5d71 | 2021-05-18 20:45:05 +0100 | [diff] [blame] | 91 | _check_command_output( |
Chinglin Yu | 2e958a2 | 2022-09-28 14:45:07 +0800 | [diff] [blame] | 92 | _tool_path('gn', system_buildtools) + |
| 93 | ['gen', out, '--args=%s' % gn_args], |
| 94 | cwd=repo_root()) |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 95 | return out |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 96 | |
| 97 | |
Chinglin Yu | 2e958a2 | 2022-09-28 14:45:07 +0800 | [diff] [blame] | 98 | def load_build_description(out, system_buildtools=False): |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 99 | """Creates the JSON build description by running GN.""" |
Primiano Tucci | eac5d71 | 2021-05-18 20:45:05 +0100 | [diff] [blame] | 100 | desc = _check_command_output( |
Chinglin Yu | 2e958a2 | 2022-09-28 14:45:07 +0800 | [diff] [blame] | 101 | _tool_path('gn', system_buildtools) + |
Primiano Tucci | eac5d71 | 2021-05-18 20:45:05 +0100 | [diff] [blame] | 102 | ['desc', out, '--format=json', '--all-toolchains', '//*'], |
| 103 | cwd=repo_root()) |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 104 | return json.loads(desc) |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 105 | |
| 106 | |
| 107 | def create_build_description(gn_args, root=repo_root()): |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 108 | """Prepares a GN out directory and loads the build description from it. |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 109 | |
| 110 | The temporary out directory is automatically deleted. |
| 111 | """ |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 112 | 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 Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 117 | |
| 118 | |
Chinglin Yu | 2e958a2 | 2022-09-28 14:45:07 +0800 | [diff] [blame] | 119 | def build_targets(out, targets, quiet=False, system_buildtools=False): |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 120 | """Runs ninja to build a list of GN targets in the given out directory. |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 121 | |
| 122 | Compiling these targets is required so that we can include any generated |
| 123 | source files in the amalgamated result. |
| 124 | """ |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 125 | targets = [t.replace('//', '') for t in targets] |
Oystein Eftevaag | 0783a8f | 2024-04-09 14:17:23 -0700 | [diff] [blame] | 126 | with open(os.devnull, 'w', newline='\n') as devnull: |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 127 | stdout = devnull if quiet else None |
Chinglin Yu | 2e958a2 | 2022-09-28 14:45:07 +0800 | [diff] [blame] | 128 | cmd = _tool_path('ninja', system_buildtools) + targets |
Primiano Tucci | eac5d71 | 2021-05-18 20:45:05 +0100 | [diff] [blame] | 129 | subprocess.check_call(cmd, cwd=os.path.abspath(out), stdout=stdout) |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 130 | |
| 131 | |
Chinglin Yu | 2e958a2 | 2022-09-28 14:45:07 +0800 | [diff] [blame] | 132 | def compute_source_dependencies(out, system_buildtools=False): |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 133 | """For each source file, computes a set of headers it depends on.""" |
Primiano Tucci | eac5d71 | 2021-05-18 20:45:05 +0100 | [diff] [blame] | 134 | ninja_deps = _check_command_output( |
Chinglin Yu | 2e958a2 | 2022-09-28 14:45:07 +0800 | [diff] [blame] | 135 | _tool_path('ninja', system_buildtools) + ['-t', 'deps'], cwd=out) |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 136 | deps = {} |
| 137 | current_source = None |
| 138 | for line in ninja_deps.split('\n'): |
Matthew Clarkson | 58a3ebb | 2019-10-11 11:29:18 +0100 | [diff] [blame] | 139 | filename = os.path.relpath(os.path.join(out, line.strip()), repo_root()) |
Chinglin Yu | 4c9f6d5 | 2024-03-25 08:05:46 +0000 | [diff] [blame] | 140 | # Sanitizer builds may have a dependency of ignorelist.txt. Just skip it. |
| 141 | if filename.endswith('gn/standalone/sanitizers/ignorelist.txt'): |
| 142 | continue |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 143 | 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 Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 156 | |
| 157 | |
| 158 | def label_to_path(label): |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 159 | """Turn a GN output label (e.g., //some_dir/file.cc) into a path.""" |
| 160 | assert label.startswith('//') |
| 161 | return label[2:] |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 162 | |
| 163 | |
| 164 | def label_without_toolchain(label): |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 165 | """Strips the toolchain from a GN label. |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 166 | |
| 167 | Return a GN label (e.g //buildtools:protobuf(//gn/standalone/toolchain: |
| 168 | gcc_like_host) without the parenthesised toolchain part. |
| 169 | """ |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 170 | return label.split('(')[0] |
Sami Kyostila | 3c88a1d | 2019-05-22 18:29:42 +0100 | [diff] [blame] | 171 | |
| 172 | |
| 173 | def 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 Tucci | 9c41165 | 2019-08-27 07:13:59 +0200 | [diff] [blame] | 180 | return name |
| 181 | |
Primiano Tucci | 8e62744 | 2019-08-28 07:58:38 +0200 | [diff] [blame] | 182 | |
| 183 | def gen_buildflags(gn_args, target_file): |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 184 | """Generates the perfetto_build_flags.h for the given config. |
Primiano Tucci | 8e62744 | 2019-08-28 07:58:38 +0200 | [diff] [blame] | 185 | |
| 186 | target_file: the path, relative to the repo root, where the generated |
| 187 | buildflag header will be copied into. |
| 188 | """ |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 189 | 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 Tucci | 8e62744 | 2019-08-28 07:58:38 +0200 | [diff] [blame] | 194 | |
| 195 | |
Primiano Tucci | 9c41165 | 2019-08-27 07:13:59 +0200 | [diff] [blame] | 196 | def check_or_commit_generated_files(tmp_files, check): |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 197 | """Checks that gen files are unchanged or renames them to the final location |
Primiano Tucci | 9c41165 | 2019-08-27 07:13:59 +0200 | [diff] [blame] | 198 | |
| 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 Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 204 | 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 Eftevaag | 0783a8f | 2024-04-09 14:17:23 -0700 | [diff] [blame] | 214 | os.replace(tmp_file, target_file) |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 215 | return res |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 216 | |
| 217 | |
Primiano Tucci | 916f4e5 | 2020-10-16 20:40:33 +0200 | [diff] [blame] | 218 | class 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 Maganti | 01f9b05 | 2022-12-17 01:21:13 +0000 | [diff] [blame] | 232 | def __init__(self, gn: 'GnParser', target_name: str): |
Primiano Tucci | 916f4e5 | 2020-10-16 20:40:33 +0200 | [diff] [blame] | 233 | self.gn = gn |
| 234 | self.root = gn.get_target(target_name) |
Lalit Maganti | 01f9b05 | 2022-12-17 01:21:13 +0000 | [diff] [blame] | 235 | self.source_sets: Dict[str, Set[str]] = collections.defaultdict(set) |
Primiano Tucci | 916f4e5 | 2020-10-16 20:40:33 +0200 | [diff] [blame] | 236 | 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 Maganti | 01f9b05 | 2022-12-17 01:21:13 +0000 | [diff] [blame] | 256 | def _visit(self, target_name: str, parent_path=''): |
Primiano Tucci | 916f4e5 | 2020-10-16 20:40:33 +0200 | [diff] [blame] | 257 | 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 Maganti | 01f9b05 | 2022-12-17 01:21:13 +0000 | [diff] [blame] | 261 | for ssdep in target.transitive_source_set_deps(): |
Primiano Tucci | 916f4e5 | 2020-10-16 20:40:33 +0200 | [diff] [blame] | 262 | name_and_path = '%s (via %s)' % (target_name, path) |
Lalit Maganti | 01f9b05 | 2022-12-17 01:21:13 +0000 | [diff] [blame] | 263 | 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 Tucci | 916f4e5 | 2020-10-16 20:40:33 +0200 | [diff] [blame] | 267 | 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 Maganti | 01f9b05 | 2022-12-17 01:21:13 +0000 | [diff] [blame] | 276 | self.deps_visited.add(dep.name) |
| 277 | self._visit(dep.name, next_parent_path) |
Primiano Tucci | 916f4e5 | 2020-10-16 20:40:33 +0200 | [diff] [blame] | 278 | |
Lalit Maganti | 01f9b05 | 2022-12-17 01:21:13 +0000 | [diff] [blame] | 279 | def is_header_only(self, source_set_name: str): |
Primiano Tucci | 916f4e5 | 2020-10-16 20:40:33 +0200 | [diff] [blame] | 280 | 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 Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 291 | class GnParser(object): |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 292 | """A parser with some cleverness for GN json desc files |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 293 | |
| 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 Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 307 | |
| 308 | class Target(object): |
| 309 | """Reperesents A GN target. |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 310 | |
| 311 | Maked properties are propagated up the dependency chain when a |
| 312 | source_set dependency is encountered. |
| 313 | """ |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 314 | |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 315 | def __init__(self, name, type): |
| 316 | self.name = name # e.g. //src/ipc:ipc |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 317 | |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 318 | VALID_TYPES = ('static_library', 'shared_library', 'executable', 'group', |
Lalit Maganti | 358a7e4 | 2022-11-09 15:16:21 +0000 | [diff] [blame] | 319 | 'action', 'source_set', 'proto_library', 'generated_file') |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 320 | assert (type in VALID_TYPES) |
| 321 | self.type = type |
Primiano Tucci | f0d7ef8 | 2019-10-04 15:35:24 +0100 | [diff] [blame] | 322 | self.testonly = False |
| 323 | self.toolchain = None |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 324 | |
Lalit Maganti | 3b09a3f | 2020-09-14 13:28:44 +0100 | [diff] [blame] | 325 | # These are valid only for type == proto_library. |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 326 | # This is typically: 'proto', 'protozero', 'ipc'. |
Lalit Maganti | 921a3d6 | 2022-12-17 14:28:50 +0000 | [diff] [blame] | 327 | self.proto_plugin: Optional[str] = None |
Lalit Maganti | 3b09a3f | 2020-09-14 13:28:44 +0100 | [diff] [blame] | 328 | self.proto_paths = set() |
Lalit Maganti | ba364ec | 2022-02-15 19:53:20 +0000 | [diff] [blame] | 329 | self.proto_exports = set() |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 330 | |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 331 | self.sources = set() |
Ryan | 0331ac0 | 2021-04-26 15:41:47 +0100 | [diff] [blame] | 332 | # TODO(primiano): consider whether the public section should be part of |
| 333 | # bubbled-up sources. |
| 334 | self.public_headers = set() # 'public' |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 335 | |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 336 | # These are valid only for type == 'action' |
Lalit Maganti | 88e64de | 2022-11-21 18:29:57 +0000 | [diff] [blame] | 337 | self.data = set() |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 338 | self.inputs = set() |
| 339 | self.outputs = set() |
| 340 | self.script = None |
| 341 | self.args = [] |
Lalit Maganti | 358a7e4 | 2022-11-09 15:16:21 +0000 | [diff] [blame] | 342 | self.custom_action_type = None |
Lalit Maganti | 88e64de | 2022-11-21 18:29:57 +0000 | [diff] [blame] | 343 | self.python_main = None |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 344 | |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 345 | # These variables are propagated up when encountering a dependency |
| 346 | # on a source_set target. |
| 347 | self.cflags = set() |
| 348 | self.defines = set() |
Lalit Maganti | 01f9b05 | 2022-12-17 01:21:13 +0000 | [diff] [blame] | 349 | self.deps: Set[GnParser.Target] = set() |
| 350 | self.transitive_deps: Set[GnParser.Target] = set() |
Primiano Tucci | f0d7ef8 | 2019-10-04 15:35:24 +0100 | [diff] [blame] | 351 | self.libs = set() |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 352 | self.include_dirs = set() |
| 353 | self.ldflags = set() |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 354 | |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 355 | # 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 Clarkson | 9a5dfa5 | 2019-10-03 09:54:04 +0100 | [diff] [blame] | 361 | |
Lalit Maganti | 01f9b05 | 2022-12-17 01:21:13 +0000 | [diff] [blame] | 362 | 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 Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 380 | 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 Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 386 | |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 387 | 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 Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 394 | |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 395 | def update(self, other): |
Lalit Maganti | 88e64de | 2022-11-21 18:29:57 +0000 | [diff] [blame] | 396 | for key in ('cflags', 'data', 'defines', 'deps', 'include_dirs', |
Lalit Maganti | 01f9b05 | 2022-12-17 01:21:13 +0000 | [diff] [blame] | 397 | 'ldflags', 'transitive_deps', 'libs', 'proto_paths'): |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 398 | self.__dict__[key].update(other.__dict__.get(key, [])) |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 399 | |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 400 | 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 Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 407 | |
Lalit Maganti | 01f9b05 | 2022-12-17 01:21:13 +0000 | [diff] [blame] | 408 | def get_target(self, gn_target_name: str) -> Target: |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 409 | """Returns a Target object from the fully qualified GN target name. |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 410 | |
| 411 | It bubbles up variables from source_set dependencies as described in the |
| 412 | class-level comments. |
| 413 | """ |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 414 | target = self.all_targets.get(gn_target_name) |
| 415 | if target is not None: |
Lalit Maganti | 613f97b | 2019-11-21 14:44:18 +0000 | [diff] [blame] | 416 | return target # Target already processed. |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 417 | |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 418 | desc = self.gn_desc_[gn_target_name] |
| 419 | target = GnParser.Target(gn_target_name, desc['type']) |
Primiano Tucci | f0d7ef8 | 2019-10-04 15:35:24 +0100 | [diff] [blame] | 420 | target.testonly = desc.get('testonly', False) |
| 421 | target.toolchain = desc.get('toolchain', None) |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 422 | self.all_targets[gn_target_name] = target |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 423 | |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 424 | # 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 Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 428 | |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 429 | # 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 Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 434 | |
Lalit Maganti | 79d70fe | 2022-02-15 18:31:09 +0000 | [diff] [blame] | 435 | proto_target_type, proto_desc = self.get_proto_target_type(target) |
Lalit Maganti | 921a3d6 | 2022-12-17 14:28:50 +0000 | [diff] [blame] | 436 | if proto_target_type: |
| 437 | assert proto_desc |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 438 | self.proto_libs[target.name] = target |
| 439 | target.type = 'proto_library' |
| 440 | target.proto_plugin = proto_target_type |
Lalit Maganti | 3b09a3f | 2020-09-14 13:28:44 +0100 | [diff] [blame] | 441 | target.proto_paths.update(self.get_proto_paths(proto_desc)) |
Lalit Maganti | ba364ec | 2022-02-15 19:53:20 +0000 | [diff] [blame] | 442 | target.proto_exports.update(self.get_proto_exports(proto_desc)) |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 443 | 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 Maganti | 358a7e4 | 2022-11-09 15:16:21 +0000 | [diff] [blame] | 448 | target.inputs.update(desc.get('inputs', [])) |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 449 | 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 Maganti | 88e64de | 2022-11-21 18:29:57 +0000 | [diff] [blame] | 454 | target.data.update(desc.get('metadata', {}).get('perfetto_data', [])) |
Lalit Maganti | 117272f | 2020-09-11 14:01:18 +0100 | [diff] [blame] | 455 | target.inputs.update(desc.get('inputs', [])) |
| 456 | target.sources.update(desc.get('sources', [])) |
Primiano Tucci | f0d7ef8 | 2019-10-04 15:35:24 +0100 | [diff] [blame] | 457 | outs = [re.sub('^//out/.+?/gen/', '', x) for x in desc['outputs']] |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 458 | 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 Maganti | 358a7e4 | 2022-11-09 15:16:21 +0000 | [diff] [blame] | 463 | 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 Maganti | 88e64de | 2022-11-21 18:29:57 +0000 | [diff] [blame] | 467 | python_main = desc.get('metadata', {}).get('perfetto_python_main', []) |
| 468 | target.python_main = python_main[0] if python_main else None |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 469 | |
Ryan | 0331ac0 | 2021-04-26 15:41:47 +0100 | [diff] [blame] | 470 | # 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 Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 478 | target.cflags.update(desc.get('cflags', []) + desc.get('cflags_cc', [])) |
Primiano Tucci | f0d7ef8 | 2019-10-04 15:35:24 +0100 | [diff] [blame] | 479 | target.libs.update(desc.get('libs', [])) |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 480 | target.ldflags.update(desc.get('ldflags', [])) |
| 481 | target.defines.update(desc.get('defines', [])) |
| 482 | target.include_dirs.update(desc.get('include_dirs', [])) |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 483 | |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 484 | # Recurse in dependencies. |
| 485 | for dep_name in desc.get('deps', []): |
| 486 | dep = self.get_target(dep_name) |
Lalit Maganti | 01f9b05 | 2022-12-17 01:21:13 +0000 | [diff] [blame] | 487 | |
| 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 Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 505 | target.update(dep) # Bubble up groups's cflags/ldflags etc. |
Lalit Maganti | 01f9b05 | 2022-12-17 01:21:13 +0000 | [diff] [blame] | 506 | 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 Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 523 | |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 524 | return target |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 525 | |
Lalit Maganti | ba364ec | 2022-02-15 19:53:20 +0000 | [diff] [blame] | 526 | 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 Maganti | 3b09a3f | 2020-09-14 13:28:44 +0100 | [diff] [blame] | 531 | 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 Maganti | ba364ec | 2022-02-15 19:53:20 +0000 | [diff] [blame] | 534 | return metadata.get('import_dirs', []) |
Lalit Maganti | 3b09a3f | 2020-09-14 13:28:44 +0100 | [diff] [blame] | 535 | |
Lalit Maganti | 921a3d6 | 2022-12-17 14:28:50 +0000 | [diff] [blame] | 536 | def get_proto_target_type(self, target: Target |
| 537 | ) -> Tuple[Optional[str], Optional[Dict]]: |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 538 | """ Checks if the target is a proto library and return the plugin. |
Primiano Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 539 | |
| 540 | Returns: |
Primiano Tucci | f0d7ef8 | 2019-10-04 15:35:24 +0100 | [diff] [blame] | 541 | (None, None): if the target is not a proto library. |
Lalit Maganti | 117272f | 2020-09-11 14:01:18 +0100 | [diff] [blame] | 542 | (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 Tucci | 1d40998 | 2019-09-19 10:15:18 +0100 | [diff] [blame] | 546 | """ |
Primiano Tucci | f0d7ef8 | 2019-10-04 15:35:24 +0100 | [diff] [blame] | 547 | parts = target.name.split('(', 1) |
| 548 | name = parts[0] |
| 549 | toolchain = '(' + parts[1] if len(parts) > 1 else '' |
Lalit Maganti | 117272f | 2020-09-11 14:01:18 +0100 | [diff] [blame] | 550 | |
| 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 Maganti | ba364ec | 2022-02-15 19:53:20 +0000 | [diff] [blame] | 558 | # metadata of the description. |
Lalit Maganti | 117272f | 2020-09-11 14:01:18 +0100 | [diff] [blame] | 559 | 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 Tucci | f0d7ef8 | 2019-10-04 15:35:24 +0100 | [diff] [blame] | 565 | gen_desc = self.gn_desc_.get('%s_gen%s' % (name, toolchain)) |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 566 | if gen_desc is None or gen_desc['type'] != 'action': |
Primiano Tucci | f0d7ef8 | 2019-10-04 15:35:24 +0100 | [diff] [blame] | 567 | return None, None |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 568 | args = gen_desc.get('args', []) |
| 569 | if '/protoc' not in args[0]: |
Primiano Tucci | f0d7ef8 | 2019-10-04 15:35:24 +0100 | [diff] [blame] | 570 | 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 |