blob: 5c6d318914ecfea188d189ec8bf91bf35b7c45a5 [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
19import errno
20import json
21import os
22import re
23import shutil
24import subprocess
25import sys
26
27
28def _check_command_output(cmd, cwd):
29 try:
30 output = subprocess.check_output(
31 cmd, stderr=subprocess.STDOUT, cwd=cwd)
32 except subprocess.CalledProcessError as e:
33 print('Command "{}" failed in {}:'.format(' '.join(cmd), cwd),
34 file=sys.stderr)
35 print(e.output, file=sys.stderr)
36 sys.exit(1)
37 else:
38 return output
39
40
41def repo_root():
42 """Returns an absolute path to the repository root."""
43 return os.path.join(
44 os.path.realpath(os.path.dirname(__file__)), os.path.pardir)
45
46
47def _tool_path(name):
48 return os.path.join(repo_root(), 'tools', name)
49
50
51def prepare_out_directory(gn_args, name, root=repo_root()):
52 """Creates the JSON build description by running GN.
53
54 Returns (path, desc) where |path| is the location of the output directory
55 and |desc| is the JSON build description.
56 """
57 out = os.path.join(root, 'out', name)
58 try:
59 os.makedirs(out)
60 except OSError as e:
61 if e.errno != errno.EEXIST:
62 raise
63 _check_command_output(
64 [_tool_path('gn'), 'gen', out, '--args=%s' % gn_args], cwd=repo_root())
65 return out
66
67
68def load_build_description(out):
69 """Creates the JSON build description by running GN."""
70 desc = _check_command_output(
71 [_tool_path('gn'), 'desc', out, '--format=json',
72 '--all-toolchains', '//*'],
73 cwd=repo_root())
74 return json.loads(desc)
75
76
77def create_build_description(gn_args, root=repo_root()):
78 """Prepares a GN out directory and loads the build description from it.
79
80 The temporary out directory is automatically deleted.
81 """
82 out = prepare_out_directory(gn_args, 'tmp.gn_utils', root=root)
83 try:
84 return load_build_description(out)
85 finally:
86 shutil.rmtree(out)
87
88
89def build_targets(out, targets):
90 """Runs ninja to build a list of GN targets in the given out directory.
91
92 Compiling these targets is required so that we can include any generated
93 source files in the amalgamated result.
94 """
95 targets = [t.replace('//', '') for t in targets]
96 subprocess.check_call([_tool_path('ninja')] + targets, cwd=out)
97
98
99def compute_source_dependencies(out):
100 """For each source file, computes a set of headers it depends on."""
101 ninja_deps = _check_command_output(
102 [_tool_path('ninja'), '-t', 'deps'], cwd=out)
103 deps = {}
104 current_source = None
105 for line in ninja_deps.split('\n'):
106 filename = os.path.relpath(os.path.join(out, line.strip()))
107 if not line or line[0] != ' ':
108 current_source = None
109 continue
110 elif not current_source:
111 # We're assuming the source file is always listed before the
112 # headers.
113 assert os.path.splitext(line)[1] in ['.c', '.cc', '.cpp', '.S']
114 current_source = filename
115 deps[current_source] = []
116 else:
117 assert current_source
118 deps[current_source].append(filename)
119 return deps
120
121
122def label_to_path(label):
123 """Turn a GN output label (e.g., //some_dir/file.cc) into a path."""
124 assert label.startswith('//')
125 return label[2:]
126
127
128def label_without_toolchain(label):
129 """Strips the toolchain from a GN label.
130
131 Return a GN label (e.g //buildtools:protobuf(//gn/standalone/toolchain:
132 gcc_like_host) without the parenthesised toolchain part.
133 """
134 return label.split('(')[0]
135
136
137def label_to_target_name_with_path(label):
138 """
139 Turn a GN label into a target name involving the full path.
140 e.g., //src/perfetto:tests -> src_perfetto_tests
141 """
142 name = re.sub(r'^//:?', '', label)
143 name = re.sub(r'[^a-zA-Z0-9_]', '_', name)
144 return name