blob: 99fe88e56e2df248693cab9f125aef823293ee35 [file] [log] [blame]
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001#!/usr/bin/env python3
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08002#
3# Copyright (C) 2019 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16"""Call cargo -v, parse its output, and generate Android.bp.
17
18Usage: Run this script in a crate workspace root directory.
19The Cargo.toml file should work at least for the host platform.
20
21(1) Without other flags, "cargo2android.py --run"
22 calls cargo clean, calls cargo build -v, and generates Android.bp.
23 The cargo build only generates crates for the host,
24 without test crates.
25
26(2) To build crates for both host and device in Android.bp, use the
27 --device flag, for example:
28 cargo2android.py --run --device
29
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -070030 Note that cargo build is only called once with the default target
31 x86_64-unknown-linux-gnu.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080032
33(3) To build default and test crates, for host and device, use both
34 --device and --tests flags:
35 cargo2android.py --run --device --tests
36
37 This is equivalent to using the --cargo flag to add extra builds:
38 cargo2android.py --run
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080039 --cargo "build --target x86_64-unknown-linux-gnu"
40 --cargo "build --tests --target x86_64-unknown-linux-gnu"
41
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -070042If there are rustc warning messages, this script will add
43a warning comment to the owner crate module in Android.bp.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080044"""
45
46from __future__ import print_function
47
48import argparse
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -070049import glob
Joel Galenson0fbdafe2021-04-21 16:33:33 -070050import json
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080051import os
52import os.path
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -070053import platform
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080054import re
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -070055import shutil
Joel Galenson7e8247e2021-05-20 18:51:42 -070056import subprocess
Andrew Walbran80e90be2020-06-09 14:33:18 +010057import sys
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080058
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -070059# Some Rust packages include extra unwanted crates.
60# This set contains all such excluded crate names.
61EXCLUDED_CRATES = set(['protobuf_bin_gen_rust_do_not_use'])
62
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080063RENAME_MAP = {
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -070064 # This map includes all changes to the default rust module names
65 # to resolve name conflicts, avoid confusion, or work as plugin.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080066 'libbacktrace': 'libbacktrace_rust',
Andrew Walbrane51f1042020-08-11 16:42:48 +010067 'libbase': 'libbase_rust',
Luke Huanga1371af2021-06-29 18:04:40 +080068 'libbase64': 'libbase64_rust',
Victor Hsieh21bea792020-12-04 10:59:16 -080069 'libfuse': 'libfuse_rust',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080070 'libgcc': 'libgcc_rust',
71 'liblog': 'liblog_rust',
Chih-Hung Hsieh07119862020-07-24 15:34:06 -070072 'libminijail': 'libminijail_rust',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080073 'libsync': 'libsync_rust',
74 'libx86_64': 'libx86_64_rust',
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -070075 'protoc_gen_rust': 'protoc-gen-rust',
76}
77
78RENAME_STEM_MAP = {
79 # This map includes all changes to the default rust module stem names,
80 # which is used for output files when different from the module name.
81 'protoc_gen_rust': 'protoc-gen-rust',
82}
83
84RENAME_DEFAULTS_MAP = {
85 # This map includes all changes to the default prefix of rust_default
86 # module names, to avoid conflict with existing Android modules.
87 'libc': 'rust_libc',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080088}
89
90# Header added to all generated Android.bp files.
Joel Galenson56446742021-02-18 08:27:48 -080091ANDROID_BP_HEADER = (
92 '// This file is generated by cargo2android.py {args}.\n' +
93 '// Do not modify this file as changes will be overridden on upgrade.\n')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080094
95CARGO_OUT = 'cargo.out' # Name of file to keep cargo build -v output.
96
Joel Galenson3f42f802021-04-07 12:42:17 -070097# This should be kept in sync with tools/external_updater/crates_updater.py.
98ERRORS_LINE = 'Errors in ' + CARGO_OUT + ':'
99
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800100TARGET_TMP = 'target.tmp' # Name of temporary output directory.
101
102# Message to be displayed when this script is called without the --run flag.
103DRY_RUN_NOTE = (
104 'Dry-run: This script uses ./' + TARGET_TMP + ' for output directory,\n' +
105 'runs cargo clean, runs cargo build -v, saves output to ./cargo.out,\n' +
106 'and writes to Android.bp in the current and subdirectories.\n\n' +
107 'To do do all of the above, use the --run flag.\n' +
108 'See --help for other flags, and more usage notes in this script.\n')
109
110# Cargo -v output of a call to rustc.
111RUSTC_PAT = re.compile('^ +Running `rustc (.*)`$')
112
113# Cargo -vv output of a call to rustc could be split into multiple lines.
114# Assume that the first line will contain some CARGO_* env definition.
115RUSTC_VV_PAT = re.compile('^ +Running `.*CARGO_.*=.*$')
116# The combined -vv output rustc command line pattern.
117RUSTC_VV_CMD_ARGS = re.compile('^ *Running `.*CARGO_.*=.* rustc (.*)`$')
118
119# Cargo -vv output of a "cc" or "ar" command; all in one line.
120CC_AR_VV_PAT = re.compile(r'^\[([^ ]*)[^\]]*\] running:? "(cc|ar)" (.*)$')
121# Some package, such as ring-0.13.5, has pattern '... running "cc"'.
122
123# Rustc output of file location path pattern for a warning message.
124WARNING_FILE_PAT = re.compile('^ *--> ([^:]*):[0-9]+')
125
126# Rust package name with suffix -d1.d2.d3.
127VERSION_SUFFIX_PAT = re.compile(r'^(.*)-[0-9]+\.[0-9]+\.[0-9]+$')
128
129
130def altered_name(name):
131 return RENAME_MAP[name] if (name in RENAME_MAP) else name
132
133
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700134def altered_stem(name):
135 return RENAME_STEM_MAP[name] if (name in RENAME_STEM_MAP) else name
136
137
138def altered_defaults(name):
139 return RENAME_DEFAULTS_MAP[name] if (name in RENAME_DEFAULTS_MAP) else name
140
141
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800142def is_build_crate_name(name):
143 # We added special prefix to build script crate names.
144 return name.startswith('build_script_')
145
146
147def is_dependent_file_path(path):
148 # Absolute or dependent '.../' paths are not main files of this crate.
149 return path.startswith('/') or path.startswith('.../')
150
151
152def get_module_name(crate): # to sort crates in a list
153 return crate.module_name
154
155
156def pkg2crate_name(s):
157 return s.replace('-', '_').replace('.', '_')
158
159
160def file_base_name(path):
161 return os.path.splitext(os.path.basename(path))[0]
162
163
164def test_base_name(path):
165 return pkg2crate_name(file_base_name(path))
166
167
168def unquote(s): # remove quotes around str
169 if s and len(s) > 1 and s[0] == '"' and s[-1] == '"':
170 return s[1:-1]
171 return s
172
173
174def remove_version_suffix(s): # remove -d1.d2.d3 suffix
175 if VERSION_SUFFIX_PAT.match(s):
176 return VERSION_SUFFIX_PAT.match(s).group(1)
177 return s
178
179
180def short_out_name(pkg, s): # replace /.../pkg-*/out/* with .../out/*
181 return re.sub('^/.*/' + pkg + '-[0-9a-f]*/out/', '.../out/', s)
182
183
184def escape_quotes(s): # replace '"' with '\\"'
185 return s.replace('"', '\\"')
186
187
188class Crate(object):
189 """Information of a Rust crate to collect/emit for an Android.bp module."""
190
191 def __init__(self, runner, outf_name):
192 # Remembered global runner and its members.
193 self.runner = runner
194 self.debug = runner.args.debug
195 self.cargo_dir = '' # directory of my Cargo.toml
196 self.outf_name = outf_name # path to Android.bp
197 self.outf = None # open file handle of outf_name during dump*
198 # Variants/results that could be merged from multiple rustc lines.
199 self.host_supported = False
200 self.device_supported = False
201 self.has_warning = False
202 # Android module properties derived from rustc parameters.
203 self.module_name = '' # unique in Android build system
204 self.module_type = '' # rust_{binary,library,test}[_host] etc.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700205 self.defaults = '' # rust_defaults used by rust_test* modules
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700206 self.default_srcs = False # use 'srcs' defined in self.defaults
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800207 self.root_pkg = '' # parent package name of a sub/test packge, from -L
208 self.srcs = list() # main_src or merged multiple source files
209 self.stem = '' # real base name of output file
210 # Kept parsed status
211 self.errors = '' # all errors found during parsing
212 self.line_num = 1 # runner told input source line number
213 self.line = '' # original rustc command line parameters
214 # Parameters collected from rustc command line.
215 self.crate_name = '' # follows --crate-name
216 self.main_src = '' # follows crate_name parameter, shortened
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700217 self.crate_types = list() # follows --crate-type
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800218 self.cfgs = list() # follows --cfg, without feature= prefix
219 self.features = list() # follows --cfg, name in 'feature="..."'
220 self.codegens = list() # follows -C, some ignored
221 self.externs = list() # follows --extern
222 self.core_externs = list() # first part of self.externs elements
223 self.static_libs = list() # e.g. -l static=host_cpuid
224 self.shared_libs = list() # e.g. -l dylib=wayland-client, -l z
225 self.cap_lints = '' # follows --cap-lints
226 self.emit_list = '' # e.g., --emit=dep-info,metadata,link
227 self.edition = '2015' # rustc default, e.g., --edition=2018
228 self.target = '' # follows --target
229
230 def write(self, s):
231 # convenient way to output one line at a time with EOL.
232 self.outf.write(s + '\n')
233
234 def same_flags(self, other):
235 # host_supported, device_supported, has_warning are not compared but merged
236 # target is not compared, to merge different target/host modules
237 # externs is not compared; only core_externs is compared
238 return (not self.errors and not other.errors and
239 self.edition == other.edition and
240 self.cap_lints == other.cap_lints and
241 self.emit_list == other.emit_list and
242 self.core_externs == other.core_externs and
243 self.codegens == other.codegens and
244 self.features == other.features and
245 self.static_libs == other.static_libs and
246 self.shared_libs == other.shared_libs and self.cfgs == other.cfgs)
247
248 def merge_host_device(self, other):
249 """Returns true if attributes are the same except host/device support."""
250 return (self.crate_name == other.crate_name and
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700251 self.crate_types == other.crate_types and
252 self.main_src == other.main_src and
253 # before merge, each test module has an unique module name and stem
254 (self.stem == other.stem or self.crate_types == ['test']) and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800255 self.root_pkg == other.root_pkg and not self.skip_crate() and
256 self.same_flags(other))
257
258 def merge_test(self, other):
259 """Returns true if self and other are tests of same root_pkg."""
260 # Before merger, each test has its own crate_name.
261 # A merged test uses its source file base name as output file name,
262 # so a test is mergeable only if its base name equals to its crate name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700263 return (self.crate_types == other.crate_types and
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700264 self.crate_types == ['test'] and self.root_pkg == other.root_pkg and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800265 not self.skip_crate() and
266 other.crate_name == test_base_name(other.main_src) and
267 (len(self.srcs) > 1 or
268 (self.crate_name == test_base_name(self.main_src)) and
269 self.host_supported == other.host_supported and
270 self.device_supported == other.device_supported) and
271 self.same_flags(other))
272
273 def merge(self, other, outf_name):
274 """Try to merge crate into self."""
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700275 # Cargo build --tests could recompile a library for tests.
276 # We need to merge such duplicated calls to rustc, with
277 # the algorithm in merge_host_device.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800278 should_merge_host_device = self.merge_host_device(other)
279 should_merge_test = False
280 if not should_merge_host_device:
281 should_merge_test = self.merge_test(other)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800282 if should_merge_host_device or should_merge_test:
283 self.runner.init_bp_file(outf_name)
284 with open(outf_name, 'a') as outf: # to write debug info
285 self.outf = outf
286 other.outf = outf
287 self.do_merge(other, should_merge_test)
288 return True
289 return False
290
291 def do_merge(self, other, should_merge_test):
292 """Merge attributes of other to self."""
293 if self.debug:
294 self.write('\n// Before merge definition (1):')
295 self.dump_debug_info()
296 self.write('\n// Before merge definition (2):')
297 other.dump_debug_info()
298 # Merge properties of other to self.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800299 self.has_warning = self.has_warning or other.has_warning
300 if not self.target: # okay to keep only the first target triple
301 self.target = other.target
302 # decide_module_type sets up default self.stem,
303 # which can be changed if self is a merged test module.
304 self.decide_module_type()
305 if should_merge_test:
Joel Galenson57fa23a2021-07-15 10:47:35 -0700306 if (self.main_src in self.runner.args.test_blocklist and
307 not other.main_src in self.runner.args.test_blocklist):
308 self.main_src = other.main_src
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800309 self.srcs.append(other.main_src)
310 # use a short unique name as the merged module name.
311 prefix = self.root_pkg + '_tests'
312 self.module_name = self.runner.claim_module_name(prefix, self, 0)
313 self.stem = self.module_name
314 # This normalized root_pkg name although might be the same
315 # as other module's crate_name, it is not actually used for
316 # output file name. A merged test module always have multiple
317 # source files and each source file base name is used as
318 # its output file name.
319 self.crate_name = pkg2crate_name(self.root_pkg)
320 if self.debug:
321 self.write('\n// After merge definition (1):')
322 self.dump_debug_info()
323
324 def find_cargo_dir(self):
325 """Deepest directory with Cargo.toml and contains the main_src."""
326 if not is_dependent_file_path(self.main_src):
327 dir_name = os.path.dirname(self.main_src)
328 while dir_name:
329 if os.path.exists(dir_name + '/Cargo.toml'):
330 self.cargo_dir = dir_name
331 return
332 dir_name = os.path.dirname(dir_name)
333
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700334 def add_codegens_flag(self, flag):
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700335 """Ignore options not used in Android."""
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700336 # 'prefer-dynamic' does not work with common flag -C lto
Chih-Hung Hsieh63459ed2020-08-26 11:51:15 -0700337 # 'embed-bitcode' is ignored; we might control LTO with other .bp flag
Chih-Hung Hsieh6c13b722020-09-11 21:24:03 -0700338 # 'codegen-units' is set in Android global config or by default
339 if not (flag.startswith('codegen-units=') or
340 flag.startswith('debuginfo=') or
Chih-Hung Hsieh63459ed2020-08-26 11:51:15 -0700341 flag.startswith('embed-bitcode=') or
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700342 flag.startswith('extra-filename=') or
343 flag.startswith('incremental=') or
344 flag.startswith('metadata=') or
345 flag == 'prefer-dynamic'):
346 self.codegens.append(flag)
347
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800348 def parse(self, line_num, line):
349 """Find important rustc arguments to convert to Android.bp properties."""
350 self.line_num = line_num
351 self.line = line
352 args = line.split() # Loop through every argument of rustc.
353 i = 0
354 while i < len(args):
355 arg = args[i]
356 if arg == '--crate-name':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700357 i += 1
358 self.crate_name = args[i]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800359 elif arg == '--crate-type':
360 i += 1
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700361 # cargo calls rustc with multiple --crate-type flags.
362 # rustc can accept:
363 # --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
364 self.crate_types.append(args[i])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800365 elif arg == '--test':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700366 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800367 elif arg == '--target':
368 i += 1
369 self.target = args[i]
370 elif arg == '--cfg':
371 i += 1
372 if args[i].startswith('\'feature='):
373 self.features.append(unquote(args[i].replace('\'feature=', '')[:-1]))
374 else:
375 self.cfgs.append(args[i])
376 elif arg == '--extern':
377 i += 1
378 extern_names = re.sub('=/[^ ]*/deps/', ' = ', args[i])
379 self.externs.append(extern_names)
380 self.core_externs.append(re.sub(' = .*', '', extern_names))
381 elif arg == '-C': # codegen options
382 i += 1
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700383 self.add_codegens_flag(args[i])
384 elif arg.startswith('-C'):
385 # cargo has been passing "-C <xyz>" flag to rustc,
386 # but newer cargo could pass '-Cembed-bitcode=no' to rustc.
387 self.add_codegens_flag(arg[2:])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800388 elif arg == '--cap-lints':
389 i += 1
390 self.cap_lints = args[i]
391 elif arg == '-L':
392 i += 1
393 if args[i].startswith('dependency=') and args[i].endswith('/deps'):
394 if '/' + TARGET_TMP + '/' in args[i]:
395 self.root_pkg = re.sub(
396 '^.*/', '', re.sub('/' + TARGET_TMP + '/.*/deps$', '', args[i]))
397 else:
398 self.root_pkg = re.sub('^.*/', '',
399 re.sub('/[^/]+/[^/]+/deps$', '', args[i]))
400 self.root_pkg = remove_version_suffix(self.root_pkg)
401 elif arg == '-l':
402 i += 1
403 if args[i].startswith('static='):
404 self.static_libs.append(re.sub('static=', '', args[i]))
405 elif args[i].startswith('dylib='):
406 self.shared_libs.append(re.sub('dylib=', '', args[i]))
407 else:
408 self.shared_libs.append(args[i])
409 elif arg == '--out-dir' or arg == '--color': # ignored
410 i += 1
411 elif arg.startswith('--error-format=') or arg.startswith('--json='):
412 _ = arg # ignored
413 elif arg.startswith('--emit='):
414 self.emit_list = arg.replace('--emit=', '')
415 elif arg.startswith('--edition='):
416 self.edition = arg.replace('--edition=', '')
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700417 elif not arg.startswith('-'):
418 # shorten imported crate main source paths like $HOME/.cargo/
419 # registry/src/github.com-1ecc6299db9ec823/memchr-2.3.3/src/lib.rs
420 self.main_src = re.sub(r'^/[^ ]*/registry/src/', '.../', args[i])
421 self.main_src = re.sub(r'^\.\.\./github.com-[0-9a-f]*/', '.../',
422 self.main_src)
423 self.find_cargo_dir()
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700424 if self.cargo_dir: # for a subdirectory
425 if self.runner.args.no_subdir: # all .bp content to /dev/null
426 self.outf_name = '/dev/null'
427 elif not self.runner.args.onefile:
428 # Write to Android.bp in the subdirectory with Cargo.toml.
429 self.outf_name = self.cargo_dir + '/Android.bp'
430 self.main_src = self.main_src[len(self.cargo_dir) + 1:]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800431 else:
432 self.errors += 'ERROR: unknown ' + arg + '\n'
433 i += 1
434 if not self.crate_name:
435 self.errors += 'ERROR: missing --crate-name\n'
436 if not self.main_src:
437 self.errors += 'ERROR: missing main source file\n'
438 else:
439 self.srcs.append(self.main_src)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700440 if not self.crate_types:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800441 # Treat "--cfg test" as "--test"
442 if 'test' in self.cfgs:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700443 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800444 else:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700445 self.errors += 'ERROR: missing --crate-type or --test\n'
446 elif len(self.crate_types) > 1:
447 if 'test' in self.crate_types:
448 self.errors += 'ERROR: cannot handle both --crate-type and --test\n'
449 if 'lib' in self.crate_types and 'rlib' in self.crate_types:
450 self.errors += 'ERROR: cannot generate both lib and rlib crate types\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800451 if not self.root_pkg:
452 self.root_pkg = self.crate_name
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700453 self.device_supported = self.runner.args.device
454 self.host_supported = not self.runner.args.no_host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800455 self.cfgs = sorted(set(self.cfgs))
456 self.features = sorted(set(self.features))
457 self.codegens = sorted(set(self.codegens))
458 self.externs = sorted(set(self.externs))
459 self.core_externs = sorted(set(self.core_externs))
460 self.static_libs = sorted(set(self.static_libs))
461 self.shared_libs = sorted(set(self.shared_libs))
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700462 self.crate_types = sorted(set(self.crate_types))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800463 self.decide_module_type()
464 self.module_name = altered_name(self.stem)
465 return self
466
467 def dump_line(self):
468 self.write('\n// Line ' + str(self.line_num) + ' ' + self.line)
469
470 def feature_list(self):
471 """Return a string of main_src + "feature_list"."""
472 pkg = self.main_src
473 if pkg.startswith('.../'): # keep only the main package name
474 pkg = re.sub('/.*', '', pkg[4:])
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700475 elif pkg.startswith('/'): # use relative path for a local package
476 pkg = os.path.relpath(pkg)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800477 if not self.features:
478 return pkg
479 return pkg + ' "' + ','.join(self.features) + '"'
480
481 def dump_skip_crate(self, kind):
482 if self.debug:
483 self.write('\n// IGNORED: ' + kind + ' ' + self.main_src)
484 return self
485
486 def skip_crate(self):
487 """Return crate_name or a message if this crate should be skipped."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700488 if (is_build_crate_name(self.crate_name) or
489 self.crate_name in EXCLUDED_CRATES):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800490 return self.crate_name
491 if is_dependent_file_path(self.main_src):
492 return 'dependent crate'
493 return ''
494
495 def dump(self):
496 """Dump all error/debug/module code to the output .bp file."""
497 self.runner.init_bp_file(self.outf_name)
498 with open(self.outf_name, 'a') as outf:
499 self.outf = outf
500 if self.errors:
501 self.dump_line()
502 self.write(self.errors)
503 elif self.skip_crate():
504 self.dump_skip_crate(self.skip_crate())
505 else:
506 if self.debug:
507 self.dump_debug_info()
508 self.dump_android_module()
509
510 def dump_debug_info(self):
511 """Dump parsed data, when cargo2android is called with --debug."""
512
513 def dump(name, value):
514 self.write('//%12s = %s' % (name, value))
515
516 def opt_dump(name, value):
517 if value:
518 dump(name, value)
519
520 def dump_list(fmt, values):
521 for v in values:
522 self.write(fmt % v)
523
524 self.dump_line()
525 dump('module_name', self.module_name)
526 dump('crate_name', self.crate_name)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700527 dump('crate_types', self.crate_types)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800528 dump('main_src', self.main_src)
529 dump('has_warning', self.has_warning)
530 dump('for_host', self.host_supported)
531 dump('for_device', self.device_supported)
532 dump('module_type', self.module_type)
533 opt_dump('target', self.target)
534 opt_dump('edition', self.edition)
535 opt_dump('emit_list', self.emit_list)
536 opt_dump('cap_lints', self.cap_lints)
537 dump_list('// cfg = %s', self.cfgs)
538 dump_list('// cfg = \'feature "%s"\'', self.features)
539 # TODO(chh): escape quotes in self.features, but not in other dump_list
540 dump_list('// codegen = %s', self.codegens)
541 dump_list('// externs = %s', self.externs)
542 dump_list('// -l static = %s', self.static_libs)
543 dump_list('// -l (dylib) = %s', self.shared_libs)
544
545 def dump_android_module(self):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700546 """Dump one or more Android module definition, depending on crate_types."""
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700547 if len(self.crate_types) == 1:
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700548 self.dump_single_type_android_module()
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700549 return
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700550 if 'test' in self.crate_types:
551 self.write('\nERROR: multiple crate types cannot include test type')
552 return
553 # Dump one Android module per crate_type.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700554 for crate_type in self.crate_types:
555 self.decide_one_module_type(crate_type)
556 self.dump_one_android_module(crate_type)
557
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700558 def build_default_name(self):
559 """Return a short and readable name for the rust_defaults module."""
Joel Galensond37d7e62021-07-13 09:03:01 -0700560 # Choices: (1) root_pkg + '_test'? + '_defaults',
561 # (2) root_pkg + '_test'? + '_defaults_' + crate_name
562 # (3) root_pkg + '_test'? + '_defaults_' + main_src_basename_path
563 # (4) root_pkg + '_test'? + '_defaults_' + a_positive_sequence_number
564 test = "_test" if self.crate_types == ['test'] else ""
565 name1 = altered_defaults(self.root_pkg) + test + '_defaults'
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700566 if self.runner.try_claim_module_name(name1, self):
567 return name1
568 name2 = name1 + '_' + self.crate_name
569 if self.runner.try_claim_module_name(name2, self):
570 return name2
571 name3 = name1 + '_' + self.main_src_basename_path()
572 if self.runner.try_claim_module_name(name3, self):
573 return name3
574 return self.runner.claim_module_name(name1, self, 0)
575
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700576 def dump_srcs_list(self):
577 """Dump the srcs list, for defaults or regular modules."""
578 if len(self.srcs) > 1:
579 srcs = sorted(set(self.srcs)) # make a copy and dedup
580 else:
581 srcs = [self.main_src]
582 copy_out = self.runner.copy_out_module_name()
583 if copy_out:
584 srcs.append(':' + copy_out)
585 self.dump_android_property_list('srcs', '"%s"', srcs)
586
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700587 def dump_defaults_module(self):
588 """Dump a rust_defaults module to be shared by other modules."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700589 name = self.build_default_name()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700590 self.defaults = name
591 self.write('\nrust_defaults {')
592 self.write(' name: "' + name + '",')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700593 if self.runner.args.global_defaults:
594 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700595 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700596 if len(self.srcs) == 1: # only one source file; share it in defaults
597 self.default_srcs = True
598 if self.has_warning and not self.cap_lints:
599 self.write(' // has rustc warnings')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700600 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700601 if 'test' in self.crate_types:
602 self.write(' test_suites: ["general-tests"],')
603 self.write(' auto_gen_config: true,')
604 self.dump_edition_flags_libs()
605 self.write('}')
606
607 def dump_single_type_android_module(self):
608 """Dump one simple Android module, which has only one crate_type."""
609 crate_type = self.crate_types[0]
610 if crate_type != 'test':
611 # do not change self.stem or self.module_name
612 self.dump_one_android_module(crate_type)
613 return
614 # Dump one test module per source file, and separate host and device tests.
615 # crate_type == 'test'
Joel Galensonf6b3c912021-06-03 16:00:54 -0700616 self.srcs = [src for src in self.srcs if not src in self.runner.args.test_blocklist]
617 if ((self.host_supported and self.device_supported and len(self.srcs) > 0) or
618 len(self.srcs) > 1):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700619 self.srcs = sorted(set(self.srcs))
620 self.dump_defaults_module()
621 saved_srcs = self.srcs
622 for src in saved_srcs:
623 self.srcs = [src]
624 saved_device_supported = self.device_supported
625 saved_host_supported = self.host_supported
626 saved_main_src = self.main_src
627 self.main_src = src
628 if saved_host_supported:
629 self.device_supported = False
630 self.host_supported = True
631 self.module_name = self.test_module_name()
632 self.decide_one_module_type(crate_type)
633 self.dump_one_android_module(crate_type)
634 if saved_device_supported:
635 self.device_supported = True
636 self.host_supported = False
637 self.module_name = self.test_module_name()
638 self.decide_one_module_type(crate_type)
639 self.dump_one_android_module(crate_type)
640 self.host_supported = saved_host_supported
641 self.device_supported = saved_device_supported
642 self.main_src = saved_main_src
643 self.srcs = saved_srcs
644
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700645 def dump_one_android_module(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800646 """Dump one Android module definition."""
647 if not self.module_type:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700648 self.write('\nERROR: unknown crate_type ' + crate_type)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800649 return
650 self.write('\n' + self.module_type + ' {')
651 self.dump_android_core_properties()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700652 if not self.defaults:
653 self.dump_edition_flags_libs()
654 if self.runner.args.host_first_multilib and self.host_supported and crate_type != 'test':
655 self.write(' compile_multilib: "first",')
Joel Galensond9c4de62021-04-23 10:26:40 -0700656 if self.runner.args.apex_available and crate_type == 'lib':
657 self.write(' apex_available: [')
658 for apex in self.runner.args.apex_available:
659 self.write(' "%s",' % apex)
660 self.write(' ],')
661 if self.runner.args.min_sdk_version and crate_type == 'lib':
662 self.write(' min_sdk_version: "%s",' % self.runner.args.min_sdk_version)
Joel Galenson5664f2a2021-06-10 10:13:49 -0700663 if self.runner.args.add_module_block:
664 with open(self.runner.args.add_module_block, 'r') as f:
665 self.write(' %s,' % f.read().replace('\n', '\n '))
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700666 self.write('}')
667
668 def dump_android_flags(self):
669 """Dump Android module flags property."""
ThiƩbaud Weksteena5a728b2021-04-08 14:23:49 +0200670 if not self.codegens and not self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700671 return
672 self.write(' flags: [')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800673 if self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700674 self.write(' "--cap-lints ' + self.cap_lints + '",')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700675 codegens_fmt = '"-C %s"'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700676 self.dump_android_property_list_items(codegens_fmt, self.codegens)
677 self.write(' ],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700678
679 def dump_edition_flags_libs(self):
680 if self.edition:
681 self.write(' edition: "' + self.edition + '",')
682 self.dump_android_property_list('features', '"%s"', self.features)
Joel Galenson3d6d1e72021-06-07 15:00:24 -0700683 cfgs = [cfg for cfg in self.cfgs if not cfg in self.runner.args.cfg_blocklist]
684 self.dump_android_property_list('cfgs', '"%s"', cfgs)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700685 self.dump_android_flags()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800686 if self.externs:
687 self.dump_android_externs()
Joel Galenson12467e52021-07-12 14:33:28 -0700688 all_static_libs = [lib for lib in self.static_libs if not lib in self.runner.args.lib_blocklist]
689 static_libs = [lib for lib in all_static_libs if not lib in self.runner.args.whole_static_libs]
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700690 self.dump_android_property_list('static_libs', '"lib%s"', static_libs)
Joel Galenson12467e52021-07-12 14:33:28 -0700691 whole_static_libs = [lib for lib in all_static_libs if lib in self.runner.args.whole_static_libs]
692 self.dump_android_property_list('whole_static_libs', '"lib%s"', whole_static_libs)
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700693 shared_libs = [lib for lib in self.shared_libs if not lib in self.runner.args.lib_blocklist]
694 self.dump_android_property_list('shared_libs', '"lib%s"', shared_libs)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800695
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700696 def main_src_basename_path(self):
697 return re.sub('/', '_', re.sub('.rs$', '', self.main_src))
698
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800699 def test_module_name(self):
700 """Return a unique name for a test module."""
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700701 # root_pkg+(_host|_device) + '_test_'+source_file_name
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700702 suffix = self.main_src_basename_path()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700703 host_device = '_host'
704 if self.device_supported:
705 host_device = '_device'
706 return self.root_pkg + host_device + '_test_' + suffix
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800707
708 def decide_module_type(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700709 # Use the first crate type for the default/first module.
710 crate_type = self.crate_types[0] if self.crate_types else ''
711 self.decide_one_module_type(crate_type)
712
713 def decide_one_module_type(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800714 """Decide which Android module type to use."""
715 host = '' if self.device_supported else '_host'
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700716 rlib = '_rlib' if self.runner.args.force_rlib else ''
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700717 if crate_type == 'bin': # rust_binary[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800718 self.module_type = 'rust_binary' + host
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700719 # In rare cases like protobuf-codegen, the output binary name must
720 # be renamed to use as a plugin for protoc.
721 self.stem = altered_stem(self.crate_name)
722 self.module_name = altered_name(self.crate_name)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700723 elif crate_type == 'lib': # rust_library[_host]
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700724 # TODO(chh): should this be rust_library[_host]?
725 # Assuming that Cargo.toml do not use both 'lib' and 'rlib',
726 # because we map them both to rlib.
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700727 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800728 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700729 self.module_name = altered_name(self.stem)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700730 elif crate_type == 'rlib': # rust_library[_host]
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700731 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700732 self.stem = 'lib' + self.crate_name
733 self.module_name = altered_name(self.stem)
734 elif crate_type == 'dylib': # rust_library[_host]_dylib
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800735 self.module_type = 'rust_library' + host + '_dylib'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700736 self.stem = 'lib' + self.crate_name
737 self.module_name = altered_name(self.stem) + '_dylib'
738 elif crate_type == 'cdylib': # rust_library[_host]_shared
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500739 self.module_type = 'rust_ffi' + host + '_shared'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700740 self.stem = 'lib' + self.crate_name
741 self.module_name = altered_name(self.stem) + '_shared'
742 elif crate_type == 'staticlib': # rust_library[_host]_static
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500743 self.module_type = 'rust_ffi' + host + '_static'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700744 self.stem = 'lib' + self.crate_name
745 self.module_name = altered_name(self.stem) + '_static'
746 elif crate_type == 'test': # rust_test[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800747 self.module_type = 'rust_test' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700748 # Before do_merge, stem name is based on the --crate-name parameter.
749 # and test module name is based on stem.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800750 self.stem = self.test_module_name()
751 # self.stem will be changed after merging with other tests.
752 # self.stem is NOT used for final test binary name.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700753 # rust_test uses each source file base name as part of output file name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700754 # In do_merge, this function is called again, with a module_name.
755 # We make sure that the module name is unique in each package.
756 if self.module_name:
757 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add
758 # different suffixes and distinguish multiple tests of the same
759 # crate name. We ignore -C and use claim_module_name to get
760 # unique sequential suffix.
761 self.module_name = self.runner.claim_module_name(
762 self.module_name, self, 0)
763 # Now the module name is unique, stem should also match and unique.
764 self.stem = self.module_name
765 elif crate_type == 'proc-macro': # rust_proc_macro
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800766 self.module_type = 'rust_proc_macro'
767 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700768 self.module_name = altered_name(self.stem)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800769 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]?
770 self.module_type = ''
771 self.stem = ''
772
773 def dump_android_property_list_items(self, fmt, values):
774 for v in values:
775 # fmt has quotes, so we need escape_quotes(v)
776 self.write(' ' + (fmt % escape_quotes(v)) + ',')
777
778 def dump_android_property_list(self, name, fmt, values):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700779 if not values:
780 return
781 if len(values) > 1:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800782 self.write(' ' + name + ': [')
783 self.dump_android_property_list_items(fmt, values)
784 self.write(' ],')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700785 else:
786 self.write(' ' + name + ': [' +
787 (fmt % escape_quotes(values[0])) + '],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800788
789 def dump_android_core_properties(self):
790 """Dump the module header, name, stem, etc."""
791 self.write(' name: "' + self.module_name + '",')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700792 # see properties shared by dump_defaults_module
793 if self.defaults:
794 self.write(' defaults: ["' + self.defaults + '"],')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700795 elif self.runner.args.global_defaults:
796 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800797 if self.stem != self.module_name:
798 self.write(' stem: "' + self.stem + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700799 if self.has_warning and not self.cap_lints and not self.default_srcs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700800 self.write(' // has rustc warnings')
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700801 if self.host_supported and self.device_supported and self.module_type != 'rust_proc_macro':
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800802 self.write(' host_supported: true,')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700803 if not self.defaults:
804 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700805 if not self.default_srcs:
806 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700807 if 'test' in self.crate_types and not self.defaults:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800808 # self.root_pkg can have multiple test modules, with different *_tests[n]
809 # names, but their executables can all be installed under the same _tests
810 # directory. When built from Cargo.toml, all tests should have different
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700811 # file or crate names. So we used (root_pkg + '_tests') name as the
812 # relative_install_path.
813 # However, some package like 'slab' can have non-mergeable tests that
814 # must be separated by different module names. So, here we no longer
815 # emit relative_install_path.
816 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800817 self.write(' test_suites: ["general-tests"],')
818 self.write(' auto_gen_config: true,')
Joel Galensone261a152021-01-12 11:31:53 -0800819 if 'test' in self.crate_types and self.host_supported:
820 self.write(' test_options: {')
821 self.write(' unit_test: true,')
822 self.write(' },')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800823
824 def dump_android_externs(self):
825 """Dump the dependent rlibs and dylibs property."""
826 so_libs = list()
827 rust_libs = ''
828 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$')
829 for lib in self.externs:
830 # normal value of lib: "libc = liblibc-*.rlib"
831 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib"
832 # we should use "libgetrandom", not "lib" + "getrandom_package"
833 groups = deps_libname.match(lib)
834 if groups is not None:
835 lib_name = groups.group(1)
836 else:
837 lib_name = re.sub(' .*$', '', lib)
Joel Galenson97e414a2021-05-27 09:42:32 -0700838 if lib_name in self.runner.args.dependency_blocklist:
839 continue
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800840 if lib.endswith('.rlib') or lib.endswith('.rmeta'):
841 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta.
842 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n'
843 elif lib.endswith('.so'):
844 so_libs.append(lib_name)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700845 elif lib != 'proc_macro': # --extern proc_macro is special and ignored
846 rust_libs += ' // ERROR: unknown type of lib ' + lib + '\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800847 if rust_libs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700848 self.write(' rustlibs: [\n' + rust_libs + ' ],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800849 # Are all dependent .so files proc_macros?
850 # TODO(chh): Separate proc_macros and dylib.
851 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs)
852
853
854class ARObject(object):
855 """Information of an "ar" link command."""
856
857 def __init__(self, runner, outf_name):
858 # Remembered global runner and its members.
859 self.runner = runner
860 self.pkg = ''
861 self.outf_name = outf_name # path to Android.bp
862 # "ar" arguments
863 self.line_num = 1
864 self.line = ''
865 self.flags = '' # e.g. "crs"
866 self.lib = '' # e.g. "/.../out/lib*.a"
867 self.objs = list() # e.g. "/.../out/.../*.o"
868
869 def parse(self, pkg, line_num, args_line):
870 """Collect ar obj/lib file names."""
871 self.pkg = pkg
872 self.line_num = line_num
873 self.line = args_line
874 args = args_line.split()
875 num_args = len(args)
876 if num_args < 3:
877 print('ERROR: "ar" command has too few arguments', args_line)
878 else:
879 self.flags = unquote(args[0])
880 self.lib = unquote(args[1])
881 self.objs = sorted(set(map(unquote, args[2:])))
882 return self
883
884 def write(self, s):
885 self.outf.write(s + '\n')
886
887 def dump_debug_info(self):
888 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line)
889 self.write('// ar_object for %12s' % self.pkg)
890 self.write('// flags = %s' % self.flags)
891 self.write('// lib = %s' % short_out_name(self.pkg, self.lib))
892 for o in self.objs:
893 self.write('// obj = %s' % short_out_name(self.pkg, o))
894
895 def dump_android_lib(self):
896 """Write cc_library_static into Android.bp."""
897 self.write('\ncc_library_static {')
898 self.write(' name: "' + file_base_name(self.lib) + '",')
899 self.write(' host_supported: true,')
900 if self.flags != 'crs':
901 self.write(' // ar flags = %s' % self.flags)
902 if self.pkg not in self.runner.pkg_obj2cc:
903 self.write(' ERROR: cannot find source files.\n}')
904 return
905 self.write(' srcs: [')
906 obj2cc = self.runner.pkg_obj2cc[self.pkg]
907 # Note: wflags are ignored.
908 dflags = list()
909 fflags = list()
910 for obj in self.objs:
911 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",')
912 # TODO(chh): union of dflags and flags of all obj
913 # Now, just a temporary hack that uses the last obj's flags
914 dflags = obj2cc[obj].dflags
915 fflags = obj2cc[obj].fflags
916 self.write(' ],')
917 self.write(' cflags: [')
918 self.write(' "-O3",') # TODO(chh): is this default correct?
919 self.write(' "-Wno-error",')
920 for x in fflags:
921 self.write(' "-f' + x + '",')
922 for x in dflags:
923 self.write(' "-D' + x + '",')
924 self.write(' ],')
925 self.write('}')
926
927 def dump(self):
928 """Dump error/debug/module info to the output .bp file."""
929 self.runner.init_bp_file(self.outf_name)
930 with open(self.outf_name, 'a') as outf:
931 self.outf = outf
932 if self.runner.args.debug:
933 self.dump_debug_info()
934 self.dump_android_lib()
935
936
937class CCObject(object):
938 """Information of a "cc" compilation command."""
939
940 def __init__(self, runner, outf_name):
941 # Remembered global runner and its members.
942 self.runner = runner
943 self.pkg = ''
944 self.outf_name = outf_name # path to Android.bp
945 # "cc" arguments
946 self.line_num = 1
947 self.line = ''
948 self.src = ''
949 self.obj = ''
950 self.dflags = list() # -D flags
951 self.fflags = list() # -f flags
952 self.iflags = list() # -I flags
953 self.wflags = list() # -W flags
954 self.other_args = list()
955
956 def parse(self, pkg, line_num, args_line):
957 """Collect cc compilation flags and src/out file names."""
958 self.pkg = pkg
959 self.line_num = line_num
960 self.line = args_line
961 args = args_line.split()
962 i = 0
963 while i < len(args):
964 arg = args[i]
965 if arg == '"-c"':
966 i += 1
967 if args[i].startswith('"-o'):
968 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c"
969 self.obj = unquote(args[i])[2:]
970 i += 1
971 self.src = unquote(args[i])
972 else:
973 self.src = unquote(args[i])
974 elif arg == '"-o"':
975 i += 1
976 self.obj = unquote(args[i])
977 elif arg == '"-I"':
978 i += 1
979 self.iflags.append(unquote(args[i]))
980 elif arg.startswith('"-D'):
981 self.dflags.append(unquote(args[i])[2:])
982 elif arg.startswith('"-f'):
983 self.fflags.append(unquote(args[i])[2:])
984 elif arg.startswith('"-W'):
985 self.wflags.append(unquote(args[i])[2:])
986 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or
987 arg == '"-g3"'):
988 # ignore -O -m64 -g
989 self.other_args.append(unquote(args[i]))
990 i += 1
991 self.dflags = sorted(set(self.dflags))
992 self.fflags = sorted(set(self.fflags))
993 # self.wflags is not sorted because some are order sensitive
994 # and we ignore them anyway.
995 if self.pkg not in self.runner.pkg_obj2cc:
996 self.runner.pkg_obj2cc[self.pkg] = {}
997 self.runner.pkg_obj2cc[self.pkg][self.obj] = self
998 return self
999
1000 def write(self, s):
1001 self.outf.write(s + '\n')
1002
1003 def dump_debug_flags(self, name, flags):
1004 self.write('// ' + name + ':')
1005 for f in flags:
1006 self.write('// %s' % f)
1007
1008 def dump(self):
1009 """Dump only error/debug info to the output .bp file."""
1010 if not self.runner.args.debug:
1011 return
1012 self.runner.init_bp_file(self.outf_name)
1013 with open(self.outf_name, 'a') as outf:
1014 self.outf = outf
1015 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line)
1016 self.write('// cc_object for %12s' % self.pkg)
1017 self.write('// src = %s' % short_out_name(self.pkg, self.src))
1018 self.write('// obj = %s' % short_out_name(self.pkg, self.obj))
1019 self.dump_debug_flags('-I flags', self.iflags)
1020 self.dump_debug_flags('-D flags', self.dflags)
1021 self.dump_debug_flags('-f flags', self.fflags)
1022 self.dump_debug_flags('-W flags', self.wflags)
1023 if self.other_args:
1024 self.dump_debug_flags('other args', self.other_args)
1025
1026
1027class Runner(object):
1028 """Main class to parse cargo -v output and print Android module definitions."""
1029
1030 def __init__(self, args):
1031 self.bp_files = set() # Remember all output Android.bp files.
1032 self.root_pkg = '' # name of package in ./Cargo.toml
1033 # Saved flags, modes, and data.
1034 self.args = args
1035 self.dry_run = not args.run
1036 self.skip_cargo = args.skipcargo
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001037 self.cargo_path = './cargo' # path to cargo, will be set later
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001038 self.checked_out_files = False # to check only once
1039 self.build_out_files = [] # output files generated by build.rs
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001040 # All cc/ar objects, crates, dependencies, and warning files
1041 self.cc_objects = list()
1042 self.pkg_obj2cc = {}
1043 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i]
1044 self.ar_objects = list()
1045 self.crates = list()
1046 self.dependencies = list() # dependent and build script crates
1047 self.warning_files = set()
1048 # Keep a unique mapping from (module name) to crate
1049 self.name_owners = {}
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001050 # Save and dump all errors from cargo to Android.bp.
1051 self.errors = ''
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001052 self.setup_cargo_path()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001053 # Default action is cargo clean, followed by build or user given actions.
1054 if args.cargo:
1055 self.cargo = ['clean'] + args.cargo
1056 else:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001057 default_target = '--target x86_64-unknown-linux-gnu'
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -07001058 # Use the same target for both host and default device builds.
1059 # Same target is used as default in host x86_64 Android compilation.
1060 # Note: b/169872957, prebuilt cargo failed to build vsock
1061 # on x86_64-unknown-linux-musl systems.
1062 self.cargo = ['clean', 'build ' + default_target]
1063 if args.tests:
1064 self.cargo.append('build --tests ' + default_target)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001065
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001066 def setup_cargo_path(self):
1067 """Find cargo in the --cargo_bin or prebuilt rust bin directory."""
1068 if self.args.cargo_bin:
1069 self.cargo_path = os.path.join(self.args.cargo_bin, 'cargo')
1070 if not os.path.isfile(self.cargo_path):
1071 sys.exit('ERROR: cannot find cargo in ' + self.args.cargo_bin)
1072 print('WARNING: using cargo in ' + self.args.cargo_bin)
1073 return
1074 # We have only tested this on Linux.
1075 if platform.system() != 'Linux':
1076 sys.exit('ERROR: this script has only been tested on Linux with cargo.')
1077 # Assuming that this script is in development/scripts.
1078 my_dir = os.path.dirname(os.path.abspath(__file__))
1079 linux_dir = os.path.join(my_dir, '..', '..',
1080 'prebuilts', 'rust', 'linux-x86')
1081 if not os.path.isdir(linux_dir):
1082 sys.exit('ERROR: cannot find directory ' + linux_dir)
1083 rust_version = self.find_rust_version(my_dir, linux_dir)
1084 cargo_bin = os.path.join(linux_dir, rust_version, 'bin')
1085 self.cargo_path = os.path.join(cargo_bin, 'cargo')
1086 if not os.path.isfile(self.cargo_path):
1087 sys.exit('ERROR: cannot find cargo in ' + cargo_bin
1088 + '; please try --cargo_bin= flag.')
1089 return
1090
1091 def find_rust_version(self, my_dir, linux_dir):
1092 """Use my script directory, find prebuilt rust version."""
1093 # First look up build/soong/rust/config/global.go.
1094 path2global = os.path.join(my_dir, '..', '..',
1095 'build', 'soong', 'rust', 'config', 'global.go')
1096 if os.path.isfile(path2global):
1097 # try to find: RustDefaultVersion = "1.44.0"
1098 version_pat = re.compile(
1099 r'\s*RustDefaultVersion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+)".*$')
1100 with open(path2global, 'r') as inf:
1101 for line in inf:
1102 result = version_pat.match(line)
1103 if result:
1104 return result.group(1)
1105 print('WARNING: cannot find RustDefaultVersion in ' + path2global)
1106 # Otherwise, find the newest (largest) version number in linux_dir.
1107 rust_version = (0, 0, 0) # the prebuilt version to use
1108 version_pat = re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)$')
1109 for dir_name in os.listdir(linux_dir):
1110 result = version_pat.match(dir_name)
1111 if not result:
1112 continue
1113 version = (result.group(1), result.group(2), result.group(3))
1114 if version > rust_version:
1115 rust_version = version
1116 return '.'.join(rust_version)
1117
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001118 def find_out_files(self):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001119 # list1 has build.rs output for normal crates
1120 list1 = glob.glob(TARGET_TMP + '/*/*/build/' + self.root_pkg + '-*/out/*')
1121 # list2 has build.rs output for proc-macro crates
1122 list2 = glob.glob(TARGET_TMP + '/*/build/' + self.root_pkg + '-*/out/*')
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001123 return list1 + list2
1124
1125 def copy_out_files(self):
1126 """Copy build.rs output files to ./out and set up build_out_files."""
1127 if self.checked_out_files:
1128 return
1129 self.checked_out_files = True
1130 cargo_out_files = self.find_out_files()
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001131 out_files = set()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001132 if cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001133 os.makedirs('out', exist_ok=True)
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001134 for path in cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001135 file_name = path.split('/')[-1]
1136 out_files.add(file_name)
1137 shutil.copy(path, 'out/' + file_name)
1138 self.build_out_files = sorted(out_files)
1139
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001140 def has_used_out_dir(self):
1141 """Returns true if env!("OUT_DIR") is found."""
1142 return 0 == os.system('grep -rl --exclude build.rs --include \\*.rs' +
1143 ' \'env!("OUT_DIR")\' * > /dev/null')
1144
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001145 def copy_out_module_name(self):
1146 if self.args.copy_out and self.build_out_files:
1147 return 'copy_' + self.root_pkg + '_build_out'
1148 else:
1149 return ''
1150
Haibo Huang0f72c952021-03-19 11:34:15 -07001151 def read_license(self, name):
1152 if not os.path.isfile(name):
1153 return ''
1154 license = ''
1155 with open(name, 'r') as intf:
1156 line = intf.readline()
1157 # Firstly skip ANDROID_BP_HEADER
1158 while line.startswith('//'):
1159 line = intf.readline()
Joel Galensond9d13b82021-04-05 11:27:55 -07001160 # Read all lines until we see a rust_* or genrule rule.
1161 while line != '' and not (line.startswith('rust_') or line.startswith('genrule {')):
Haibo Huang0f72c952021-03-19 11:34:15 -07001162 license += line
1163 line = intf.readline()
1164 return license.strip()
1165
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001166 def dump_copy_out_module(self, outf):
1167 """Output the genrule module to copy out/* to $(genDir)."""
1168 copy_out = self.copy_out_module_name()
1169 if not copy_out:
1170 return
1171 outf.write('\ngenrule {\n')
1172 outf.write(' name: "' + copy_out + '",\n')
1173 outf.write(' srcs: ["out/*"],\n')
1174 outf.write(' cmd: "cp $(in) $(genDir)",\n')
1175 if len(self.build_out_files) > 1:
1176 outf.write(' out: [\n')
1177 for f in self.build_out_files:
1178 outf.write(' "' + f + '",\n')
1179 outf.write(' ],\n')
1180 else:
1181 outf.write(' out: ["' + self.build_out_files[0] + '"],\n')
1182 outf.write('}\n')
1183
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001184 def init_bp_file(self, name):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001185 # name could be Android.bp or sub_dir_path/Android.bp
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001186 if name not in self.bp_files:
1187 self.bp_files.add(name)
Haibo Huang0f72c952021-03-19 11:34:15 -07001188 license_section = self.read_license(name)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001189 with open(name, 'w') as outf:
Joel Galenson367360c2021-04-29 14:31:43 -07001190 print_args = filter(lambda x: x != "--no-test-mapping", sys.argv[1:])
1191 outf.write(ANDROID_BP_HEADER.format(args=' '.join(print_args)))
Haibo Huang0f72c952021-03-19 11:34:15 -07001192 outf.write('\n')
1193 outf.write(license_section)
1194 outf.write('\n')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001195 # at most one copy_out module per .bp file
1196 self.dump_copy_out_module(outf)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001197
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001198 def try_claim_module_name(self, name, owner):
1199 """Reserve and return True if it has not been reserved yet."""
1200 if name not in self.name_owners or owner == self.name_owners[name]:
1201 self.name_owners[name] = owner
1202 return True
1203 return False
1204
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001205 def claim_module_name(self, prefix, owner, counter):
1206 """Return prefix if not owned yet, otherwise, prefix+str(counter)."""
1207 while True:
1208 name = prefix
1209 if counter > 0:
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001210 name += '_' + str(counter)
1211 if self.try_claim_module_name(name, owner):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001212 return name
1213 counter += 1
1214
1215 def find_root_pkg(self):
1216 """Read name of [package] in ./Cargo.toml."""
1217 if not os.path.exists('./Cargo.toml'):
1218 return
1219 with open('./Cargo.toml', 'r') as inf:
1220 pkg_section = re.compile(r'^ *\[package\]')
1221 name = re.compile('^ *name *= * "([^"]*)"')
1222 in_pkg = False
1223 for line in inf:
1224 if in_pkg:
1225 if name.match(line):
1226 self.root_pkg = name.match(line).group(1)
1227 break
1228 else:
1229 in_pkg = pkg_section.match(line) is not None
1230
1231 def run_cargo(self):
1232 """Calls cargo -v and save its output to ./cargo.out."""
1233 if self.skip_cargo:
1234 return self
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001235 cargo_toml = './Cargo.toml'
1236 cargo_out = './cargo.out'
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001237 # Do not use Cargo.lock, because .bp rules are designed to
1238 # run with "latest" crates avaialable on Android.
1239 cargo_lock = './Cargo.lock'
1240 cargo_lock_saved = './cargo.lock.saved'
1241 had_cargo_lock = os.path.exists(cargo_lock)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001242 if not os.access(cargo_toml, os.R_OK):
1243 print('ERROR: Cannot find or read', cargo_toml)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001244 return self
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001245 if not self.dry_run:
1246 if os.path.exists(cargo_out):
1247 os.remove(cargo_out)
1248 if not self.args.use_cargo_lock and had_cargo_lock: # save it
1249 os.rename(cargo_lock, cargo_lock_saved)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001250 cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> ' + cargo_out + ' 2>&1'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001251 # set up search PATH for cargo to find the correct rustc
1252 saved_path = os.environ['PATH']
1253 os.environ['PATH'] = os.path.dirname(self.cargo_path) + ':' + saved_path
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001254 # Add [workspace] to Cargo.toml if it is not there.
1255 added_workspace = False
1256 if self.args.add_workspace:
1257 with open(cargo_toml, 'r') as in_file:
1258 cargo_toml_lines = in_file.readlines()
1259 found_workspace = '[workspace]\n' in cargo_toml_lines
1260 if found_workspace:
1261 print('### WARNING: found [workspace] in Cargo.toml')
1262 else:
1263 with open(cargo_toml, 'a') as out_file:
1264 out_file.write('[workspace]\n')
1265 added_workspace = True
1266 if self.args.verbose:
1267 print('### INFO: added [workspace] to Cargo.toml')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001268 for c in self.cargo:
1269 features = ''
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001270 if c != 'clean':
1271 if self.args.features is not None:
1272 features = ' --no-default-features'
1273 if self.args.features:
1274 features += ' --features ' + self.args.features
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001275 cmd_v_flag = ' -vv ' if self.args.vv else ' -v '
1276 cmd = self.cargo_path + cmd_v_flag
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001277 cmd += c + features + cmd_tail
1278 if self.args.rustflags and c != 'clean':
1279 cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
1280 if self.dry_run:
1281 print('Dry-run skip:', cmd)
1282 else:
1283 if self.args.verbose:
1284 print('Running:', cmd)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001285 with open(cargo_out, 'a') as out_file:
1286 out_file.write('### Running: ' + cmd + '\n')
Joel Galenson6bf54e32021-05-17 10:54:50 -07001287 ret = os.system(cmd)
1288 if ret != 0:
1289 print('*** There was an error while running cargo. ' +
1290 'See the cargo.out file for details.')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001291 if added_workspace: # restore original Cargo.toml
1292 with open(cargo_toml, 'w') as out_file:
1293 out_file.writelines(cargo_toml_lines)
1294 if self.args.verbose:
1295 print('### INFO: restored original Cargo.toml')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001296 os.environ['PATH'] = saved_path
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001297 if not self.dry_run:
1298 if not had_cargo_lock: # restore to no Cargo.lock state
1299 os.remove(cargo_lock)
1300 elif not self.args.use_cargo_lock: # restore saved Cargo.lock
1301 os.rename(cargo_lock_saved, cargo_lock)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001302 return self
1303
1304 def dump_dependencies(self):
1305 """Append dependencies and their features to Android.bp."""
1306 if not self.dependencies:
1307 return
1308 dependent_list = list()
1309 for c in self.dependencies:
1310 dependent_list.append(c.feature_list())
1311 sorted_dependencies = sorted(set(dependent_list))
1312 self.init_bp_file('Android.bp')
1313 with open('Android.bp', 'a') as outf:
1314 outf.write('\n// dependent_library ["feature_list"]\n')
1315 for s in sorted_dependencies:
1316 outf.write('// ' + s + '\n')
1317
1318 def dump_pkg_obj2cc(self):
1319 """Dump debug info of the pkg_obj2cc map."""
1320 if not self.args.debug:
1321 return
1322 self.init_bp_file('Android.bp')
1323 with open('Android.bp', 'a') as outf:
1324 sorted_pkgs = sorted(self.pkg_obj2cc.keys())
1325 for pkg in sorted_pkgs:
1326 if not self.pkg_obj2cc[pkg]:
1327 continue
1328 outf.write('\n// obj => src for %s\n' % pkg)
1329 obj2cc = self.pkg_obj2cc[pkg]
1330 for obj in sorted(obj2cc.keys()):
1331 outf.write('// ' + short_out_name(pkg, obj) + ' => ' +
1332 short_out_name(pkg, obj2cc[obj].src) + '\n')
1333
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001334 def apply_patch(self):
1335 """Apply local patch file if it is given."""
1336 if self.args.patch:
1337 if self.dry_run:
1338 print('Dry-run skip patch file:', self.args.patch)
1339 else:
1340 if not os.path.exists(self.args.patch):
1341 self.append_to_bp('ERROR cannot find patch file: ' + self.args.patch)
1342 return self
1343 if self.args.verbose:
1344 print('### INFO: applying local patch file:', self.args.patch)
Joel Galenson7e8247e2021-05-20 18:51:42 -07001345 subprocess.run(['patch', '-s', '--no-backup-if-mismatch', './Android.bp',
1346 self.args.patch], check=True)
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001347 return self
1348
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001349 def gen_bp(self):
1350 """Parse cargo.out and generate Android.bp files."""
1351 if self.dry_run:
1352 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp')
1353 elif os.path.exists(CARGO_OUT):
1354 self.find_root_pkg()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001355 if self.args.copy_out:
1356 self.copy_out_files()
1357 elif self.find_out_files() and self.has_used_out_dir():
1358 print('WARNING: ' + self.root_pkg + ' has cargo output files; ' +
1359 'please rerun with the --copy-out flag.')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001360 with open(CARGO_OUT, 'r') as cargo_out:
1361 self.parse(cargo_out, 'Android.bp')
1362 self.crates.sort(key=get_module_name)
1363 for obj in self.cc_objects:
1364 obj.dump()
1365 self.dump_pkg_obj2cc()
1366 for crate in self.crates:
1367 crate.dump()
1368 dumped_libs = set()
1369 for lib in self.ar_objects:
1370 if lib.pkg == self.root_pkg:
1371 lib_name = file_base_name(lib.lib)
1372 if lib_name not in dumped_libs:
1373 dumped_libs.add(lib_name)
1374 lib.dump()
Joel Galenson5664f2a2021-06-10 10:13:49 -07001375 if self.args.add_toplevel_block:
1376 with open(self.args.add_toplevel_block, 'r') as f:
1377 self.append_to_bp('\n' + f.read() + '\n')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001378 if self.args.dependencies and self.dependencies:
1379 self.dump_dependencies()
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001380 if self.errors:
Joel Galenson3f42f802021-04-07 12:42:17 -07001381 self.append_to_bp('\n' + ERRORS_LINE + '\n' + self.errors)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001382 return self
1383
1384 def add_ar_object(self, obj):
1385 self.ar_objects.append(obj)
1386
1387 def add_cc_object(self, obj):
1388 self.cc_objects.append(obj)
1389
1390 def add_crate(self, crate):
1391 """Merge crate with someone in crates, or append to it. Return crates."""
1392 if crate.skip_crate():
1393 if self.args.debug: # include debug info of all crates
1394 self.crates.append(crate)
1395 if self.args.dependencies: # include only dependent crates
1396 if (is_dependent_file_path(crate.main_src) and
1397 not is_build_crate_name(crate.crate_name)):
1398 self.dependencies.append(crate)
1399 else:
1400 for c in self.crates:
1401 if c.merge(crate, 'Android.bp'):
1402 return
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001403 # If not merged, decide module type and name now.
1404 crate.decide_module_type()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001405 self.crates.append(crate)
1406
1407 def find_warning_owners(self):
1408 """For each warning file, find its owner crate."""
1409 missing_owner = False
1410 for f in self.warning_files:
1411 cargo_dir = '' # find lowest crate, with longest path
1412 owner = None # owner crate of this warning
1413 for c in self.crates:
1414 if (f.startswith(c.cargo_dir + '/') and
1415 len(cargo_dir) < len(c.cargo_dir)):
1416 cargo_dir = c.cargo_dir
1417 owner = c
1418 if owner:
1419 owner.has_warning = True
1420 else:
1421 missing_owner = True
1422 if missing_owner and os.path.exists('Cargo.toml'):
1423 # owner is the root cargo, with empty cargo_dir
1424 for c in self.crates:
1425 if not c.cargo_dir:
1426 c.has_warning = True
1427
1428 def rustc_command(self, n, rustc_line, line, outf_name):
1429 """Process a rustc command line from cargo -vv output."""
1430 # cargo build -vv output can have multiple lines for a rustc command
1431 # due to '\n' in strings for environment variables.
1432 # strip removes leading spaces and '\n' at the end
1433 new_rustc = (rustc_line.strip() + line) if rustc_line else line
1434 # Use an heuristic to detect the completions of a multi-line command.
1435 # This might fail for some very rare case, but easy to fix manually.
1436 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0:
1437 return new_rustc
1438 if RUSTC_VV_CMD_ARGS.match(new_rustc):
1439 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1)
1440 self.add_crate(Crate(self, outf_name).parse(n, args))
1441 else:
1442 self.assert_empty_vv_line(new_rustc)
1443 return ''
1444
1445 def cc_ar_command(self, n, groups, outf_name):
1446 pkg = groups.group(1)
1447 line = groups.group(3)
1448 if groups.group(2) == 'cc':
1449 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line))
1450 else:
1451 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line))
1452
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001453 def append_to_bp(self, line):
1454 self.init_bp_file('Android.bp')
1455 with open('Android.bp', 'a') as outf:
1456 outf.write(line)
1457
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001458 def assert_empty_vv_line(self, line):
1459 if line: # report error if line is not empty
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001460 self.append_to_bp('ERROR -vv line: ' + line)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001461 return ''
1462
1463 def parse(self, inf, outf_name):
1464 """Parse rustc and warning messages in inf, return a list of Crates."""
1465 n = 0 # line number
1466 prev_warning = False # true if the previous line was warning: ...
1467 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
1468 for line in inf:
1469 n += 1
1470 if line.startswith('warning: '):
1471 prev_warning = True
1472 rustc_line = self.assert_empty_vv_line(rustc_line)
1473 continue
1474 new_rustc = ''
1475 if RUSTC_PAT.match(line):
1476 args_line = RUSTC_PAT.match(line).group(1)
1477 self.add_crate(Crate(self, outf_name).parse(n, args_line))
1478 self.assert_empty_vv_line(rustc_line)
1479 elif rustc_line or RUSTC_VV_PAT.match(line):
1480 new_rustc = self.rustc_command(n, rustc_line, line, outf_name)
1481 elif CC_AR_VV_PAT.match(line):
1482 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name)
1483 elif prev_warning and WARNING_FILE_PAT.match(line):
1484 self.assert_empty_vv_line(rustc_line)
1485 fpath = WARNING_FILE_PAT.match(line).group(1)
1486 if fpath[0] != '/': # ignore absolute path
1487 self.warning_files.add(fpath)
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001488 elif line.startswith('error: ') or line.startswith('error[E'):
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001489 if not self.args.ignore_cargo_errors:
1490 self.errors += line
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001491 prev_warning = False
1492 rustc_line = new_rustc
1493 self.find_warning_owners()
1494
1495
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001496def get_parser():
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001497 """Parse main arguments."""
1498 parser = argparse.ArgumentParser('cargo2android')
1499 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001500 '--add_workspace',
1501 action='store_true',
1502 default=False,
1503 help=('append [workspace] to Cargo.toml before calling cargo,' +
1504 ' to treat current directory as root of package source;' +
1505 ' otherwise the relative source file path in generated' +
1506 ' .bp file will be from the parent directory.'))
1507 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001508 '--cargo',
1509 action='append',
1510 metavar='args_string',
1511 help=('extra cargo build -v args in a string, ' +
1512 'each --cargo flag calls cargo build -v once'))
1513 parser.add_argument(
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001514 '--cargo_bin',
1515 type=str,
1516 help='use cargo in the cargo_bin directory instead of the prebuilt one')
1517 parser.add_argument(
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001518 '--copy-out',
1519 action='store_true',
1520 default=False,
1521 help=('only for root directory, ' +
1522 'copy build.rs output to ./out/* and add a genrule to copy ' +
1523 './out/* to genrule output; for crates with code pattern: ' +
1524 'include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))'))
1525 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001526 '--debug',
1527 action='store_true',
1528 default=False,
1529 help='dump debug info into Android.bp')
1530 parser.add_argument(
1531 '--dependencies',
1532 action='store_true',
1533 default=False,
1534 help='dump debug info of dependent crates')
1535 parser.add_argument(
1536 '--device',
1537 action='store_true',
1538 default=False,
1539 help='run cargo also for a default device target')
1540 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001541 '--features',
1542 type=str,
1543 help=('pass features to cargo build, ' +
1544 'empty string means no default features'))
1545 parser.add_argument(
1546 '--global_defaults',
1547 type=str,
1548 help='add a defaults name to every module')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001549 parser.add_argument(
1550 '--host-first-multilib',
1551 action='store_true',
1552 default=False,
1553 help=('add a compile_multilib:"first" property ' +
1554 'to Android.bp host modules.'))
1555 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001556 '--ignore-cargo-errors',
1557 action='store_true',
1558 default=False,
1559 help='do not append cargo/rustc error messages to Android.bp')
1560 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001561 '--no-host',
1562 action='store_true',
1563 default=False,
1564 help='do not run cargo for the host; only for the device target')
1565 parser.add_argument(
1566 '--no-subdir',
1567 action='store_true',
1568 default=False,
1569 help='do not output anything for sub-directories')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001570 parser.add_argument(
1571 '--onefile',
1572 action='store_true',
1573 default=False,
1574 help=('output all into one ./Android.bp, default will generate ' +
1575 'one Android.bp per Cargo.toml in subdirectories'))
1576 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001577 '--patch',
1578 type=str,
1579 help='apply the given patch file to generated ./Android.bp')
1580 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001581 '--run',
1582 action='store_true',
1583 default=False,
1584 help='run it, default is dry-run')
1585 parser.add_argument('--rustflags', type=str, help='passing flags to rustc')
1586 parser.add_argument(
1587 '--skipcargo',
1588 action='store_true',
1589 default=False,
1590 help='skip cargo command, parse cargo.out, and generate Android.bp')
1591 parser.add_argument(
1592 '--tests',
1593 action='store_true',
1594 default=False,
1595 help='run cargo build --tests after normal build')
1596 parser.add_argument(
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001597 '--use-cargo-lock',
1598 action='store_true',
1599 default=False,
1600 help=('run cargo build with existing Cargo.lock ' +
1601 '(used when some latest dependent crates failed)'))
1602 parser.add_argument(
Joel Galensond9c4de62021-04-23 10:26:40 -07001603 '--min-sdk-version',
1604 type=str,
1605 help='Minimum SDK version')
1606 parser.add_argument(
1607 '--apex-available',
1608 nargs='*',
1609 help='Mark the main library as apex_available with the given apexes.')
1610 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001611 '--force-rlib',
1612 action='store_true',
1613 default=False,
1614 help='Make the main library an rlib.')
1615 parser.add_argument(
Joel Galenson12467e52021-07-12 14:33:28 -07001616 '--whole-static-libs',
1617 nargs='*',
1618 default=[],
1619 help='Make the given libraries (without lib prefixes) whole_static_libs.')
1620 parser.add_argument(
Joel Galenson97e414a2021-05-27 09:42:32 -07001621 '--dependency-blocklist',
1622 nargs='*',
1623 default=[],
Joel Galenson12467e52021-07-12 14:33:28 -07001624 help='Do not emit the given dependencies (without lib prefixes).')
Joel Galenson97e414a2021-05-27 09:42:32 -07001625 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001626 '--lib-blocklist',
1627 nargs='*',
1628 default=[],
Joel Galenson12467e52021-07-12 14:33:28 -07001629 help='Do not emit the given C libraries as dependencies (without lib prefixes).')
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001630 parser.add_argument(
Joel Galensonf6b3c912021-06-03 16:00:54 -07001631 '--test-blocklist',
1632 nargs='*',
1633 default=[],
1634 help=('Do not emit the given tests. ' +
1635 'Pass the path to the test file to exclude.'))
1636 parser.add_argument(
Joel Galenson3d6d1e72021-06-07 15:00:24 -07001637 '--cfg-blocklist',
1638 nargs='*',
1639 default=[],
1640 help='Do not emit the given cfg.')
1641 parser.add_argument(
Joel Galenson5664f2a2021-06-10 10:13:49 -07001642 '--add-toplevel-block',
1643 type=str,
1644 help='Add the contents of the given file to the top level of the Android.bp.')
1645 parser.add_argument(
1646 '--add-module-block',
1647 type=str,
1648 help='Add the contents of the given file to the main module.')
1649 parser.add_argument(
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001650 '--no-test-mapping',
1651 action='store_true',
1652 default=False,
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001653 help='Deprecated. Has no effect.')
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001654 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001655 '--verbose',
1656 action='store_true',
1657 default=False,
1658 help='echo executed commands')
1659 parser.add_argument(
1660 '--vv',
1661 action='store_true',
1662 default=False,
1663 help='run cargo with -vv instead of default -v')
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001664 parser.add_argument(
1665 '--dump-config-and-exit',
1666 type=str,
1667 help=('Dump command-line arguments (minus this flag) to a config file and exit. ' +
1668 'This is intended to help migrate from command line options to config files.'))
1669 parser.add_argument(
1670 '--config',
1671 type=str,
1672 help=('Load command-line options from the given config file. ' +
1673 'Options in this file will override those passed on the command line.'))
1674 return parser
1675
1676
1677def parse_args(parser):
1678 """Parses command-line options."""
1679 args = parser.parse_args()
1680 # Use the values specified in a config file if one was found.
1681 if args.config:
1682 with open(args.config, 'r') as f:
1683 config = json.load(f)
1684 args_dict = vars(args)
1685 for arg in config:
1686 args_dict[arg.replace('-', '_')] = config[arg]
1687 return args
1688
1689
1690def dump_config(parser, args):
1691 """Writes the non-default command-line options to the specified file."""
1692 args_dict = vars(args)
1693 # Filter out the arguments that have their default value.
Joel Galenson367360c2021-04-29 14:31:43 -07001694 # Also filter certain "temporary" arguments.
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001695 non_default_args = {}
1696 for arg in args_dict:
Joel Galenson367360c2021-04-29 14:31:43 -07001697 if args_dict[arg] != parser.get_default(
1698 arg) and arg != 'dump_config_and_exit' and arg != 'no_test_mapping':
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001699 non_default_args[arg.replace('_', '-')] = args_dict[arg]
1700 # Write to the specified file.
1701 with open(args.dump_config_and_exit, 'w') as f:
1702 json.dump(non_default_args, f, indent=2, sort_keys=True)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001703
1704
1705def main():
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001706 parser = get_parser()
1707 args = parse_args(parser)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001708 if not args.run: # default is dry-run
1709 print(DRY_RUN_NOTE)
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001710 if args.dump_config_and_exit:
1711 dump_config(parser, args)
1712 else:
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001713 Runner(args).run_cargo().gen_bp().apply_patch()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001714
1715
1716if __name__ == '__main__':
1717 main()