blob: f6ab3acc5d17f392faee66637f07f98a530bc738 [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',
Jooyung Hana427c9b2021-07-16 08:53:14 +090075 'libxml': 'libxml_rust',
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -070076 'protoc_gen_rust': 'protoc-gen-rust',
77}
78
79RENAME_STEM_MAP = {
80 # This map includes all changes to the default rust module stem names,
81 # which is used for output files when different from the module name.
82 'protoc_gen_rust': 'protoc-gen-rust',
83}
84
85RENAME_DEFAULTS_MAP = {
86 # This map includes all changes to the default prefix of rust_default
87 # module names, to avoid conflict with existing Android modules.
88 'libc': 'rust_libc',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080089}
90
91# Header added to all generated Android.bp files.
Joel Galenson56446742021-02-18 08:27:48 -080092ANDROID_BP_HEADER = (
93 '// This file is generated by cargo2android.py {args}.\n' +
94 '// Do not modify this file as changes will be overridden on upgrade.\n')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080095
96CARGO_OUT = 'cargo.out' # Name of file to keep cargo build -v output.
97
Joel Galenson3f42f802021-04-07 12:42:17 -070098# This should be kept in sync with tools/external_updater/crates_updater.py.
99ERRORS_LINE = 'Errors in ' + CARGO_OUT + ':'
100
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800101TARGET_TMP = 'target.tmp' # Name of temporary output directory.
102
103# Message to be displayed when this script is called without the --run flag.
104DRY_RUN_NOTE = (
105 'Dry-run: This script uses ./' + TARGET_TMP + ' for output directory,\n' +
106 'runs cargo clean, runs cargo build -v, saves output to ./cargo.out,\n' +
107 'and writes to Android.bp in the current and subdirectories.\n\n' +
108 'To do do all of the above, use the --run flag.\n' +
109 'See --help for other flags, and more usage notes in this script.\n')
110
111# Cargo -v output of a call to rustc.
112RUSTC_PAT = re.compile('^ +Running `rustc (.*)`$')
113
114# Cargo -vv output of a call to rustc could be split into multiple lines.
115# Assume that the first line will contain some CARGO_* env definition.
116RUSTC_VV_PAT = re.compile('^ +Running `.*CARGO_.*=.*$')
117# The combined -vv output rustc command line pattern.
118RUSTC_VV_CMD_ARGS = re.compile('^ *Running `.*CARGO_.*=.* rustc (.*)`$')
119
120# Cargo -vv output of a "cc" or "ar" command; all in one line.
121CC_AR_VV_PAT = re.compile(r'^\[([^ ]*)[^\]]*\] running:? "(cc|ar)" (.*)$')
122# Some package, such as ring-0.13.5, has pattern '... running "cc"'.
123
124# Rustc output of file location path pattern for a warning message.
125WARNING_FILE_PAT = re.compile('^ *--> ([^:]*):[0-9]+')
126
127# Rust package name with suffix -d1.d2.d3.
128VERSION_SUFFIX_PAT = re.compile(r'^(.*)-[0-9]+\.[0-9]+\.[0-9]+$')
129
130
131def altered_name(name):
132 return RENAME_MAP[name] if (name in RENAME_MAP) else name
133
134
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700135def altered_stem(name):
136 return RENAME_STEM_MAP[name] if (name in RENAME_STEM_MAP) else name
137
138
139def altered_defaults(name):
140 return RENAME_DEFAULTS_MAP[name] if (name in RENAME_DEFAULTS_MAP) else name
141
142
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800143def is_build_crate_name(name):
144 # We added special prefix to build script crate names.
145 return name.startswith('build_script_')
146
147
148def is_dependent_file_path(path):
149 # Absolute or dependent '.../' paths are not main files of this crate.
150 return path.startswith('/') or path.startswith('.../')
151
152
153def get_module_name(crate): # to sort crates in a list
154 return crate.module_name
155
156
157def pkg2crate_name(s):
158 return s.replace('-', '_').replace('.', '_')
159
160
161def file_base_name(path):
162 return os.path.splitext(os.path.basename(path))[0]
163
164
165def test_base_name(path):
166 return pkg2crate_name(file_base_name(path))
167
168
169def unquote(s): # remove quotes around str
170 if s and len(s) > 1 and s[0] == '"' and s[-1] == '"':
171 return s[1:-1]
172 return s
173
174
175def remove_version_suffix(s): # remove -d1.d2.d3 suffix
176 if VERSION_SUFFIX_PAT.match(s):
177 return VERSION_SUFFIX_PAT.match(s).group(1)
178 return s
179
180
181def short_out_name(pkg, s): # replace /.../pkg-*/out/* with .../out/*
182 return re.sub('^/.*/' + pkg + '-[0-9a-f]*/out/', '.../out/', s)
183
184
185def escape_quotes(s): # replace '"' with '\\"'
186 return s.replace('"', '\\"')
187
188
189class Crate(object):
190 """Information of a Rust crate to collect/emit for an Android.bp module."""
191
192 def __init__(self, runner, outf_name):
193 # Remembered global runner and its members.
194 self.runner = runner
195 self.debug = runner.args.debug
196 self.cargo_dir = '' # directory of my Cargo.toml
197 self.outf_name = outf_name # path to Android.bp
198 self.outf = None # open file handle of outf_name during dump*
199 # Variants/results that could be merged from multiple rustc lines.
200 self.host_supported = False
201 self.device_supported = False
202 self.has_warning = False
203 # Android module properties derived from rustc parameters.
204 self.module_name = '' # unique in Android build system
205 self.module_type = '' # rust_{binary,library,test}[_host] etc.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700206 self.defaults = '' # rust_defaults used by rust_test* modules
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700207 self.default_srcs = False # use 'srcs' defined in self.defaults
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800208 self.root_pkg = '' # parent package name of a sub/test packge, from -L
209 self.srcs = list() # main_src or merged multiple source files
210 self.stem = '' # real base name of output file
211 # Kept parsed status
212 self.errors = '' # all errors found during parsing
213 self.line_num = 1 # runner told input source line number
214 self.line = '' # original rustc command line parameters
215 # Parameters collected from rustc command line.
216 self.crate_name = '' # follows --crate-name
217 self.main_src = '' # follows crate_name parameter, shortened
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700218 self.crate_types = list() # follows --crate-type
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800219 self.cfgs = list() # follows --cfg, without feature= prefix
220 self.features = list() # follows --cfg, name in 'feature="..."'
221 self.codegens = list() # follows -C, some ignored
222 self.externs = list() # follows --extern
223 self.core_externs = list() # first part of self.externs elements
224 self.static_libs = list() # e.g. -l static=host_cpuid
225 self.shared_libs = list() # e.g. -l dylib=wayland-client, -l z
226 self.cap_lints = '' # follows --cap-lints
227 self.emit_list = '' # e.g., --emit=dep-info,metadata,link
228 self.edition = '2015' # rustc default, e.g., --edition=2018
229 self.target = '' # follows --target
230
231 def write(self, s):
232 # convenient way to output one line at a time with EOL.
233 self.outf.write(s + '\n')
234
235 def same_flags(self, other):
236 # host_supported, device_supported, has_warning are not compared but merged
237 # target is not compared, to merge different target/host modules
238 # externs is not compared; only core_externs is compared
239 return (not self.errors and not other.errors and
240 self.edition == other.edition and
241 self.cap_lints == other.cap_lints and
242 self.emit_list == other.emit_list and
243 self.core_externs == other.core_externs and
244 self.codegens == other.codegens and
245 self.features == other.features and
246 self.static_libs == other.static_libs and
247 self.shared_libs == other.shared_libs and self.cfgs == other.cfgs)
248
249 def merge_host_device(self, other):
250 """Returns true if attributes are the same except host/device support."""
251 return (self.crate_name == other.crate_name and
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700252 self.crate_types == other.crate_types and
253 self.main_src == other.main_src and
254 # before merge, each test module has an unique module name and stem
255 (self.stem == other.stem or self.crate_types == ['test']) and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800256 self.root_pkg == other.root_pkg and not self.skip_crate() and
257 self.same_flags(other))
258
259 def merge_test(self, other):
260 """Returns true if self and other are tests of same root_pkg."""
261 # Before merger, each test has its own crate_name.
262 # A merged test uses its source file base name as output file name,
263 # so a test is mergeable only if its base name equals to its crate name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700264 return (self.crate_types == other.crate_types and
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700265 self.crate_types == ['test'] and self.root_pkg == other.root_pkg and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800266 not self.skip_crate() and
267 other.crate_name == test_base_name(other.main_src) and
268 (len(self.srcs) > 1 or
269 (self.crate_name == test_base_name(self.main_src)) and
270 self.host_supported == other.host_supported and
271 self.device_supported == other.device_supported) and
272 self.same_flags(other))
273
274 def merge(self, other, outf_name):
275 """Try to merge crate into self."""
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700276 # Cargo build --tests could recompile a library for tests.
277 # We need to merge such duplicated calls to rustc, with
278 # the algorithm in merge_host_device.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800279 should_merge_host_device = self.merge_host_device(other)
280 should_merge_test = False
281 if not should_merge_host_device:
282 should_merge_test = self.merge_test(other)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800283 if should_merge_host_device or should_merge_test:
284 self.runner.init_bp_file(outf_name)
285 with open(outf_name, 'a') as outf: # to write debug info
286 self.outf = outf
287 other.outf = outf
288 self.do_merge(other, should_merge_test)
289 return True
290 return False
291
292 def do_merge(self, other, should_merge_test):
293 """Merge attributes of other to self."""
294 if self.debug:
295 self.write('\n// Before merge definition (1):')
296 self.dump_debug_info()
297 self.write('\n// Before merge definition (2):')
298 other.dump_debug_info()
299 # Merge properties of other to self.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800300 self.has_warning = self.has_warning or other.has_warning
301 if not self.target: # okay to keep only the first target triple
302 self.target = other.target
303 # decide_module_type sets up default self.stem,
304 # which can be changed if self is a merged test module.
305 self.decide_module_type()
306 if should_merge_test:
Joel Galenson57fa23a2021-07-15 10:47:35 -0700307 if (self.main_src in self.runner.args.test_blocklist and
308 not other.main_src in self.runner.args.test_blocklist):
309 self.main_src = other.main_src
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800310 self.srcs.append(other.main_src)
311 # use a short unique name as the merged module name.
312 prefix = self.root_pkg + '_tests'
313 self.module_name = self.runner.claim_module_name(prefix, self, 0)
314 self.stem = self.module_name
315 # This normalized root_pkg name although might be the same
316 # as other module's crate_name, it is not actually used for
317 # output file name. A merged test module always have multiple
318 # source files and each source file base name is used as
319 # its output file name.
320 self.crate_name = pkg2crate_name(self.root_pkg)
321 if self.debug:
322 self.write('\n// After merge definition (1):')
323 self.dump_debug_info()
324
325 def find_cargo_dir(self):
326 """Deepest directory with Cargo.toml and contains the main_src."""
327 if not is_dependent_file_path(self.main_src):
328 dir_name = os.path.dirname(self.main_src)
329 while dir_name:
330 if os.path.exists(dir_name + '/Cargo.toml'):
331 self.cargo_dir = dir_name
332 return
333 dir_name = os.path.dirname(dir_name)
334
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700335 def add_codegens_flag(self, flag):
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700336 """Ignore options not used in Android."""
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700337 # 'prefer-dynamic' does not work with common flag -C lto
Chih-Hung Hsieh63459ed2020-08-26 11:51:15 -0700338 # 'embed-bitcode' is ignored; we might control LTO with other .bp flag
Chih-Hung Hsieh6c13b722020-09-11 21:24:03 -0700339 # 'codegen-units' is set in Android global config or by default
340 if not (flag.startswith('codegen-units=') or
341 flag.startswith('debuginfo=') or
Chih-Hung Hsieh63459ed2020-08-26 11:51:15 -0700342 flag.startswith('embed-bitcode=') or
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700343 flag.startswith('extra-filename=') or
344 flag.startswith('incremental=') or
345 flag.startswith('metadata=') or
346 flag == 'prefer-dynamic'):
347 self.codegens.append(flag)
348
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800349 def parse(self, line_num, line):
350 """Find important rustc arguments to convert to Android.bp properties."""
351 self.line_num = line_num
352 self.line = line
353 args = line.split() # Loop through every argument of rustc.
354 i = 0
355 while i < len(args):
356 arg = args[i]
357 if arg == '--crate-name':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700358 i += 1
359 self.crate_name = args[i]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800360 elif arg == '--crate-type':
361 i += 1
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700362 # cargo calls rustc with multiple --crate-type flags.
363 # rustc can accept:
364 # --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
365 self.crate_types.append(args[i])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800366 elif arg == '--test':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700367 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800368 elif arg == '--target':
369 i += 1
370 self.target = args[i]
371 elif arg == '--cfg':
372 i += 1
373 if args[i].startswith('\'feature='):
374 self.features.append(unquote(args[i].replace('\'feature=', '')[:-1]))
375 else:
376 self.cfgs.append(args[i])
377 elif arg == '--extern':
378 i += 1
379 extern_names = re.sub('=/[^ ]*/deps/', ' = ', args[i])
380 self.externs.append(extern_names)
381 self.core_externs.append(re.sub(' = .*', '', extern_names))
382 elif arg == '-C': # codegen options
383 i += 1
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700384 self.add_codegens_flag(args[i])
385 elif arg.startswith('-C'):
386 # cargo has been passing "-C <xyz>" flag to rustc,
387 # but newer cargo could pass '-Cembed-bitcode=no' to rustc.
388 self.add_codegens_flag(arg[2:])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800389 elif arg == '--cap-lints':
390 i += 1
391 self.cap_lints = args[i]
392 elif arg == '-L':
393 i += 1
394 if args[i].startswith('dependency=') and args[i].endswith('/deps'):
395 if '/' + TARGET_TMP + '/' in args[i]:
396 self.root_pkg = re.sub(
397 '^.*/', '', re.sub('/' + TARGET_TMP + '/.*/deps$', '', args[i]))
398 else:
399 self.root_pkg = re.sub('^.*/', '',
400 re.sub('/[^/]+/[^/]+/deps$', '', args[i]))
401 self.root_pkg = remove_version_suffix(self.root_pkg)
402 elif arg == '-l':
403 i += 1
404 if args[i].startswith('static='):
405 self.static_libs.append(re.sub('static=', '', args[i]))
406 elif args[i].startswith('dylib='):
407 self.shared_libs.append(re.sub('dylib=', '', args[i]))
408 else:
409 self.shared_libs.append(args[i])
410 elif arg == '--out-dir' or arg == '--color': # ignored
411 i += 1
412 elif arg.startswith('--error-format=') or arg.startswith('--json='):
413 _ = arg # ignored
414 elif arg.startswith('--emit='):
415 self.emit_list = arg.replace('--emit=', '')
416 elif arg.startswith('--edition='):
417 self.edition = arg.replace('--edition=', '')
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700418 elif not arg.startswith('-'):
419 # shorten imported crate main source paths like $HOME/.cargo/
420 # registry/src/github.com-1ecc6299db9ec823/memchr-2.3.3/src/lib.rs
421 self.main_src = re.sub(r'^/[^ ]*/registry/src/', '.../', args[i])
422 self.main_src = re.sub(r'^\.\.\./github.com-[0-9a-f]*/', '.../',
423 self.main_src)
424 self.find_cargo_dir()
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700425 if self.cargo_dir: # for a subdirectory
426 if self.runner.args.no_subdir: # all .bp content to /dev/null
427 self.outf_name = '/dev/null'
428 elif not self.runner.args.onefile:
429 # Write to Android.bp in the subdirectory with Cargo.toml.
430 self.outf_name = self.cargo_dir + '/Android.bp'
431 self.main_src = self.main_src[len(self.cargo_dir) + 1:]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800432 else:
433 self.errors += 'ERROR: unknown ' + arg + '\n'
434 i += 1
435 if not self.crate_name:
436 self.errors += 'ERROR: missing --crate-name\n'
437 if not self.main_src:
438 self.errors += 'ERROR: missing main source file\n'
439 else:
440 self.srcs.append(self.main_src)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700441 if not self.crate_types:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800442 # Treat "--cfg test" as "--test"
443 if 'test' in self.cfgs:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700444 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800445 else:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700446 self.errors += 'ERROR: missing --crate-type or --test\n'
447 elif len(self.crate_types) > 1:
448 if 'test' in self.crate_types:
449 self.errors += 'ERROR: cannot handle both --crate-type and --test\n'
450 if 'lib' in self.crate_types and 'rlib' in self.crate_types:
451 self.errors += 'ERROR: cannot generate both lib and rlib crate types\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800452 if not self.root_pkg:
453 self.root_pkg = self.crate_name
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700454 self.device_supported = self.runner.args.device
455 self.host_supported = not self.runner.args.no_host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800456 self.cfgs = sorted(set(self.cfgs))
457 self.features = sorted(set(self.features))
458 self.codegens = sorted(set(self.codegens))
459 self.externs = sorted(set(self.externs))
460 self.core_externs = sorted(set(self.core_externs))
461 self.static_libs = sorted(set(self.static_libs))
462 self.shared_libs = sorted(set(self.shared_libs))
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700463 self.crate_types = sorted(set(self.crate_types))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800464 self.decide_module_type()
465 self.module_name = altered_name(self.stem)
466 return self
467
468 def dump_line(self):
469 self.write('\n// Line ' + str(self.line_num) + ' ' + self.line)
470
471 def feature_list(self):
472 """Return a string of main_src + "feature_list"."""
473 pkg = self.main_src
474 if pkg.startswith('.../'): # keep only the main package name
475 pkg = re.sub('/.*', '', pkg[4:])
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700476 elif pkg.startswith('/'): # use relative path for a local package
477 pkg = os.path.relpath(pkg)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800478 if not self.features:
479 return pkg
480 return pkg + ' "' + ','.join(self.features) + '"'
481
482 def dump_skip_crate(self, kind):
483 if self.debug:
484 self.write('\n// IGNORED: ' + kind + ' ' + self.main_src)
485 return self
486
487 def skip_crate(self):
488 """Return crate_name or a message if this crate should be skipped."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700489 if (is_build_crate_name(self.crate_name) or
490 self.crate_name in EXCLUDED_CRATES):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800491 return self.crate_name
492 if is_dependent_file_path(self.main_src):
493 return 'dependent crate'
494 return ''
495
496 def dump(self):
497 """Dump all error/debug/module code to the output .bp file."""
498 self.runner.init_bp_file(self.outf_name)
499 with open(self.outf_name, 'a') as outf:
500 self.outf = outf
501 if self.errors:
502 self.dump_line()
503 self.write(self.errors)
504 elif self.skip_crate():
505 self.dump_skip_crate(self.skip_crate())
506 else:
507 if self.debug:
508 self.dump_debug_info()
509 self.dump_android_module()
510
511 def dump_debug_info(self):
512 """Dump parsed data, when cargo2android is called with --debug."""
513
514 def dump(name, value):
515 self.write('//%12s = %s' % (name, value))
516
517 def opt_dump(name, value):
518 if value:
519 dump(name, value)
520
521 def dump_list(fmt, values):
522 for v in values:
523 self.write(fmt % v)
524
525 self.dump_line()
526 dump('module_name', self.module_name)
527 dump('crate_name', self.crate_name)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700528 dump('crate_types', self.crate_types)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800529 dump('main_src', self.main_src)
530 dump('has_warning', self.has_warning)
531 dump('for_host', self.host_supported)
532 dump('for_device', self.device_supported)
533 dump('module_type', self.module_type)
534 opt_dump('target', self.target)
535 opt_dump('edition', self.edition)
536 opt_dump('emit_list', self.emit_list)
537 opt_dump('cap_lints', self.cap_lints)
538 dump_list('// cfg = %s', self.cfgs)
539 dump_list('// cfg = \'feature "%s"\'', self.features)
540 # TODO(chh): escape quotes in self.features, but not in other dump_list
541 dump_list('// codegen = %s', self.codegens)
542 dump_list('// externs = %s', self.externs)
543 dump_list('// -l static = %s', self.static_libs)
544 dump_list('// -l (dylib) = %s', self.shared_libs)
545
546 def dump_android_module(self):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700547 """Dump one or more Android module definition, depending on crate_types."""
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700548 if len(self.crate_types) == 1:
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700549 self.dump_single_type_android_module()
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700550 return
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700551 if 'test' in self.crate_types:
552 self.write('\nERROR: multiple crate types cannot include test type')
553 return
554 # Dump one Android module per crate_type.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700555 for crate_type in self.crate_types:
556 self.decide_one_module_type(crate_type)
557 self.dump_one_android_module(crate_type)
558
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700559 def build_default_name(self):
560 """Return a short and readable name for the rust_defaults module."""
Joel Galensond37d7e62021-07-13 09:03:01 -0700561 # Choices: (1) root_pkg + '_test'? + '_defaults',
562 # (2) root_pkg + '_test'? + '_defaults_' + crate_name
563 # (3) root_pkg + '_test'? + '_defaults_' + main_src_basename_path
564 # (4) root_pkg + '_test'? + '_defaults_' + a_positive_sequence_number
565 test = "_test" if self.crate_types == ['test'] else ""
566 name1 = altered_defaults(self.root_pkg) + test + '_defaults'
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700567 if self.runner.try_claim_module_name(name1, self):
568 return name1
569 name2 = name1 + '_' + self.crate_name
570 if self.runner.try_claim_module_name(name2, self):
571 return name2
572 name3 = name1 + '_' + self.main_src_basename_path()
573 if self.runner.try_claim_module_name(name3, self):
574 return name3
575 return self.runner.claim_module_name(name1, self, 0)
576
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700577 def dump_srcs_list(self):
578 """Dump the srcs list, for defaults or regular modules."""
579 if len(self.srcs) > 1:
580 srcs = sorted(set(self.srcs)) # make a copy and dedup
581 else:
582 srcs = [self.main_src]
583 copy_out = self.runner.copy_out_module_name()
584 if copy_out:
585 srcs.append(':' + copy_out)
586 self.dump_android_property_list('srcs', '"%s"', srcs)
587
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700588 def dump_defaults_module(self):
589 """Dump a rust_defaults module to be shared by other modules."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700590 name = self.build_default_name()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700591 self.defaults = name
592 self.write('\nrust_defaults {')
593 self.write(' name: "' + name + '",')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700594 if self.runner.args.global_defaults:
595 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700596 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700597 if len(self.srcs) == 1: # only one source file; share it in defaults
598 self.default_srcs = True
599 if self.has_warning and not self.cap_lints:
600 self.write(' // has rustc warnings')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700601 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700602 if 'test' in self.crate_types:
603 self.write(' test_suites: ["general-tests"],')
604 self.write(' auto_gen_config: true,')
605 self.dump_edition_flags_libs()
Joel Galensone4f53882021-07-19 11:14:55 -0700606 if 'test' in self.crate_types and len(self.srcs) == 1:
607 self.dump_test_data()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700608 self.write('}')
609
610 def dump_single_type_android_module(self):
611 """Dump one simple Android module, which has only one crate_type."""
612 crate_type = self.crate_types[0]
613 if crate_type != 'test':
614 # do not change self.stem or self.module_name
615 self.dump_one_android_module(crate_type)
616 return
617 # Dump one test module per source file, and separate host and device tests.
618 # crate_type == 'test'
Joel Galensonf6b3c912021-06-03 16:00:54 -0700619 self.srcs = [src for src in self.srcs if not src in self.runner.args.test_blocklist]
620 if ((self.host_supported and self.device_supported and len(self.srcs) > 0) or
621 len(self.srcs) > 1):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700622 self.srcs = sorted(set(self.srcs))
623 self.dump_defaults_module()
624 saved_srcs = self.srcs
625 for src in saved_srcs:
626 self.srcs = [src]
627 saved_device_supported = self.device_supported
628 saved_host_supported = self.host_supported
629 saved_main_src = self.main_src
630 self.main_src = src
631 if saved_host_supported:
632 self.device_supported = False
633 self.host_supported = True
634 self.module_name = self.test_module_name()
635 self.decide_one_module_type(crate_type)
636 self.dump_one_android_module(crate_type)
637 if saved_device_supported:
638 self.device_supported = True
639 self.host_supported = False
640 self.module_name = self.test_module_name()
641 self.decide_one_module_type(crate_type)
642 self.dump_one_android_module(crate_type)
643 self.host_supported = saved_host_supported
644 self.device_supported = saved_device_supported
645 self.main_src = saved_main_src
646 self.srcs = saved_srcs
647
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700648 def dump_one_android_module(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800649 """Dump one Android module definition."""
650 if not self.module_type:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700651 self.write('\nERROR: unknown crate_type ' + crate_type)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800652 return
653 self.write('\n' + self.module_type + ' {')
654 self.dump_android_core_properties()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700655 if not self.defaults:
656 self.dump_edition_flags_libs()
657 if self.runner.args.host_first_multilib and self.host_supported and crate_type != 'test':
658 self.write(' compile_multilib: "first",')
Joel Galensond9c4de62021-04-23 10:26:40 -0700659 if self.runner.args.apex_available and crate_type == 'lib':
660 self.write(' apex_available: [')
661 for apex in self.runner.args.apex_available:
662 self.write(' "%s",' % apex)
663 self.write(' ],')
Ivan Lozano91920862021-07-19 10:49:08 -0400664 if self.runner.args.vendor_available:
665 self.write(' vendor_available: true,')
666 if self.runner.args.vendor_ramdisk_available:
667 self.write(' vendor_ramdisk_available: true,')
Joel Galensond9c4de62021-04-23 10:26:40 -0700668 if self.runner.args.min_sdk_version and crate_type == 'lib':
669 self.write(' min_sdk_version: "%s",' % self.runner.args.min_sdk_version)
Joel Galensone4f53882021-07-19 11:14:55 -0700670 if crate_type == 'test' and not self.default_srcs:
671 self.dump_test_data()
Joel Galenson5664f2a2021-06-10 10:13:49 -0700672 if self.runner.args.add_module_block:
673 with open(self.runner.args.add_module_block, 'r') as f:
674 self.write(' %s,' % f.read().replace('\n', '\n '))
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700675 self.write('}')
676
677 def dump_android_flags(self):
678 """Dump Android module flags property."""
ThiƩbaud Weksteena5a728b2021-04-08 14:23:49 +0200679 if not self.codegens and not self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700680 return
681 self.write(' flags: [')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800682 if self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700683 self.write(' "--cap-lints ' + self.cap_lints + '",')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700684 codegens_fmt = '"-C %s"'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700685 self.dump_android_property_list_items(codegens_fmt, self.codegens)
686 self.write(' ],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700687
688 def dump_edition_flags_libs(self):
689 if self.edition:
690 self.write(' edition: "' + self.edition + '",')
691 self.dump_android_property_list('features', '"%s"', self.features)
Joel Galenson3d6d1e72021-06-07 15:00:24 -0700692 cfgs = [cfg for cfg in self.cfgs if not cfg in self.runner.args.cfg_blocklist]
693 self.dump_android_property_list('cfgs', '"%s"', cfgs)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700694 self.dump_android_flags()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800695 if self.externs:
696 self.dump_android_externs()
Joel Galenson12467e52021-07-12 14:33:28 -0700697 all_static_libs = [lib for lib in self.static_libs if not lib in self.runner.args.lib_blocklist]
698 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 -0700699 self.dump_android_property_list('static_libs', '"lib%s"', static_libs)
Joel Galenson12467e52021-07-12 14:33:28 -0700700 whole_static_libs = [lib for lib in all_static_libs if lib in self.runner.args.whole_static_libs]
701 self.dump_android_property_list('whole_static_libs', '"lib%s"', whole_static_libs)
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700702 shared_libs = [lib for lib in self.shared_libs if not lib in self.runner.args.lib_blocklist]
703 self.dump_android_property_list('shared_libs', '"lib%s"', shared_libs)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800704
Joel Galensone4f53882021-07-19 11:14:55 -0700705 def dump_test_data(self):
706 data = [data for (name, data) in map(lambda kv: kv.split('=', 1), self.runner.args.test_data)
707 if self.srcs == [name]]
708 if data:
709 self.dump_android_property_list('data', '"%s"', data)
710
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700711 def main_src_basename_path(self):
712 return re.sub('/', '_', re.sub('.rs$', '', self.main_src))
713
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800714 def test_module_name(self):
715 """Return a unique name for a test module."""
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700716 # root_pkg+(_host|_device) + '_test_'+source_file_name
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700717 suffix = self.main_src_basename_path()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700718 host_device = '_host'
719 if self.device_supported:
720 host_device = '_device'
721 return self.root_pkg + host_device + '_test_' + suffix
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800722
723 def decide_module_type(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700724 # Use the first crate type for the default/first module.
725 crate_type = self.crate_types[0] if self.crate_types else ''
726 self.decide_one_module_type(crate_type)
727
728 def decide_one_module_type(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800729 """Decide which Android module type to use."""
730 host = '' if self.device_supported else '_host'
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700731 rlib = '_rlib' if self.runner.args.force_rlib else ''
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700732 if crate_type == 'bin': # rust_binary[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800733 self.module_type = 'rust_binary' + host
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700734 # In rare cases like protobuf-codegen, the output binary name must
735 # be renamed to use as a plugin for protoc.
736 self.stem = altered_stem(self.crate_name)
737 self.module_name = altered_name(self.crate_name)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700738 elif crate_type == 'lib': # rust_library[_host]
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700739 # TODO(chh): should this be rust_library[_host]?
740 # Assuming that Cargo.toml do not use both 'lib' and 'rlib',
741 # because we map them both to rlib.
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700742 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800743 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700744 self.module_name = altered_name(self.stem)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700745 elif crate_type == 'rlib': # rust_library[_host]
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700746 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700747 self.stem = 'lib' + self.crate_name
748 self.module_name = altered_name(self.stem)
749 elif crate_type == 'dylib': # rust_library[_host]_dylib
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800750 self.module_type = 'rust_library' + host + '_dylib'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700751 self.stem = 'lib' + self.crate_name
752 self.module_name = altered_name(self.stem) + '_dylib'
753 elif crate_type == 'cdylib': # rust_library[_host]_shared
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500754 self.module_type = 'rust_ffi' + host + '_shared'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700755 self.stem = 'lib' + self.crate_name
756 self.module_name = altered_name(self.stem) + '_shared'
757 elif crate_type == 'staticlib': # rust_library[_host]_static
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500758 self.module_type = 'rust_ffi' + host + '_static'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700759 self.stem = 'lib' + self.crate_name
760 self.module_name = altered_name(self.stem) + '_static'
761 elif crate_type == 'test': # rust_test[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800762 self.module_type = 'rust_test' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700763 # Before do_merge, stem name is based on the --crate-name parameter.
764 # and test module name is based on stem.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800765 self.stem = self.test_module_name()
766 # self.stem will be changed after merging with other tests.
767 # self.stem is NOT used for final test binary name.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700768 # rust_test uses each source file base name as part of output file name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700769 # In do_merge, this function is called again, with a module_name.
770 # We make sure that the module name is unique in each package.
771 if self.module_name:
772 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add
773 # different suffixes and distinguish multiple tests of the same
774 # crate name. We ignore -C and use claim_module_name to get
775 # unique sequential suffix.
776 self.module_name = self.runner.claim_module_name(
777 self.module_name, self, 0)
778 # Now the module name is unique, stem should also match and unique.
779 self.stem = self.module_name
780 elif crate_type == 'proc-macro': # rust_proc_macro
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800781 self.module_type = 'rust_proc_macro'
782 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700783 self.module_name = altered_name(self.stem)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800784 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]?
785 self.module_type = ''
786 self.stem = ''
787
788 def dump_android_property_list_items(self, fmt, values):
789 for v in values:
790 # fmt has quotes, so we need escape_quotes(v)
791 self.write(' ' + (fmt % escape_quotes(v)) + ',')
792
793 def dump_android_property_list(self, name, fmt, values):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700794 if not values:
795 return
796 if len(values) > 1:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800797 self.write(' ' + name + ': [')
798 self.dump_android_property_list_items(fmt, values)
799 self.write(' ],')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700800 else:
801 self.write(' ' + name + ': [' +
802 (fmt % escape_quotes(values[0])) + '],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800803
804 def dump_android_core_properties(self):
805 """Dump the module header, name, stem, etc."""
806 self.write(' name: "' + self.module_name + '",')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700807 # see properties shared by dump_defaults_module
808 if self.defaults:
809 self.write(' defaults: ["' + self.defaults + '"],')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700810 elif self.runner.args.global_defaults:
811 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800812 if self.stem != self.module_name:
813 self.write(' stem: "' + self.stem + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700814 if self.has_warning and not self.cap_lints and not self.default_srcs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700815 self.write(' // has rustc warnings')
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700816 if self.host_supported and self.device_supported and self.module_type != 'rust_proc_macro':
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800817 self.write(' host_supported: true,')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700818 if not self.defaults:
819 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700820 if not self.default_srcs:
821 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700822 if 'test' in self.crate_types and not self.defaults:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800823 # self.root_pkg can have multiple test modules, with different *_tests[n]
824 # names, but their executables can all be installed under the same _tests
825 # directory. When built from Cargo.toml, all tests should have different
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700826 # file or crate names. So we used (root_pkg + '_tests') name as the
827 # relative_install_path.
828 # However, some package like 'slab' can have non-mergeable tests that
829 # must be separated by different module names. So, here we no longer
830 # emit relative_install_path.
831 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800832 self.write(' test_suites: ["general-tests"],')
833 self.write(' auto_gen_config: true,')
Joel Galensone261a152021-01-12 11:31:53 -0800834 if 'test' in self.crate_types and self.host_supported:
835 self.write(' test_options: {')
836 self.write(' unit_test: true,')
837 self.write(' },')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800838
839 def dump_android_externs(self):
840 """Dump the dependent rlibs and dylibs property."""
841 so_libs = list()
842 rust_libs = ''
843 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$')
844 for lib in self.externs:
845 # normal value of lib: "libc = liblibc-*.rlib"
846 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib"
847 # we should use "libgetrandom", not "lib" + "getrandom_package"
848 groups = deps_libname.match(lib)
849 if groups is not None:
850 lib_name = groups.group(1)
851 else:
852 lib_name = re.sub(' .*$', '', lib)
Joel Galenson97e414a2021-05-27 09:42:32 -0700853 if lib_name in self.runner.args.dependency_blocklist:
854 continue
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800855 if lib.endswith('.rlib') or lib.endswith('.rmeta'):
856 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta.
857 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n'
858 elif lib.endswith('.so'):
859 so_libs.append(lib_name)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700860 elif lib != 'proc_macro': # --extern proc_macro is special and ignored
861 rust_libs += ' // ERROR: unknown type of lib ' + lib + '\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800862 if rust_libs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700863 self.write(' rustlibs: [\n' + rust_libs + ' ],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800864 # Are all dependent .so files proc_macros?
865 # TODO(chh): Separate proc_macros and dylib.
866 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs)
867
868
869class ARObject(object):
870 """Information of an "ar" link command."""
871
872 def __init__(self, runner, outf_name):
873 # Remembered global runner and its members.
874 self.runner = runner
875 self.pkg = ''
876 self.outf_name = outf_name # path to Android.bp
877 # "ar" arguments
878 self.line_num = 1
879 self.line = ''
880 self.flags = '' # e.g. "crs"
881 self.lib = '' # e.g. "/.../out/lib*.a"
882 self.objs = list() # e.g. "/.../out/.../*.o"
883
884 def parse(self, pkg, line_num, args_line):
885 """Collect ar obj/lib file names."""
886 self.pkg = pkg
887 self.line_num = line_num
888 self.line = args_line
889 args = args_line.split()
890 num_args = len(args)
891 if num_args < 3:
892 print('ERROR: "ar" command has too few arguments', args_line)
893 else:
894 self.flags = unquote(args[0])
895 self.lib = unquote(args[1])
896 self.objs = sorted(set(map(unquote, args[2:])))
897 return self
898
899 def write(self, s):
900 self.outf.write(s + '\n')
901
902 def dump_debug_info(self):
903 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line)
904 self.write('// ar_object for %12s' % self.pkg)
905 self.write('// flags = %s' % self.flags)
906 self.write('// lib = %s' % short_out_name(self.pkg, self.lib))
907 for o in self.objs:
908 self.write('// obj = %s' % short_out_name(self.pkg, o))
909
910 def dump_android_lib(self):
911 """Write cc_library_static into Android.bp."""
912 self.write('\ncc_library_static {')
913 self.write(' name: "' + file_base_name(self.lib) + '",')
914 self.write(' host_supported: true,')
915 if self.flags != 'crs':
916 self.write(' // ar flags = %s' % self.flags)
917 if self.pkg not in self.runner.pkg_obj2cc:
918 self.write(' ERROR: cannot find source files.\n}')
919 return
920 self.write(' srcs: [')
921 obj2cc = self.runner.pkg_obj2cc[self.pkg]
922 # Note: wflags are ignored.
923 dflags = list()
924 fflags = list()
925 for obj in self.objs:
926 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",')
927 # TODO(chh): union of dflags and flags of all obj
928 # Now, just a temporary hack that uses the last obj's flags
929 dflags = obj2cc[obj].dflags
930 fflags = obj2cc[obj].fflags
931 self.write(' ],')
932 self.write(' cflags: [')
933 self.write(' "-O3",') # TODO(chh): is this default correct?
934 self.write(' "-Wno-error",')
935 for x in fflags:
936 self.write(' "-f' + x + '",')
937 for x in dflags:
938 self.write(' "-D' + x + '",')
939 self.write(' ],')
940 self.write('}')
941
942 def dump(self):
943 """Dump error/debug/module info to the output .bp file."""
944 self.runner.init_bp_file(self.outf_name)
945 with open(self.outf_name, 'a') as outf:
946 self.outf = outf
947 if self.runner.args.debug:
948 self.dump_debug_info()
949 self.dump_android_lib()
950
951
952class CCObject(object):
953 """Information of a "cc" compilation command."""
954
955 def __init__(self, runner, outf_name):
956 # Remembered global runner and its members.
957 self.runner = runner
958 self.pkg = ''
959 self.outf_name = outf_name # path to Android.bp
960 # "cc" arguments
961 self.line_num = 1
962 self.line = ''
963 self.src = ''
964 self.obj = ''
965 self.dflags = list() # -D flags
966 self.fflags = list() # -f flags
967 self.iflags = list() # -I flags
968 self.wflags = list() # -W flags
969 self.other_args = list()
970
971 def parse(self, pkg, line_num, args_line):
972 """Collect cc compilation flags and src/out file names."""
973 self.pkg = pkg
974 self.line_num = line_num
975 self.line = args_line
976 args = args_line.split()
977 i = 0
978 while i < len(args):
979 arg = args[i]
980 if arg == '"-c"':
981 i += 1
982 if args[i].startswith('"-o'):
983 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c"
984 self.obj = unquote(args[i])[2:]
985 i += 1
986 self.src = unquote(args[i])
987 else:
988 self.src = unquote(args[i])
989 elif arg == '"-o"':
990 i += 1
991 self.obj = unquote(args[i])
992 elif arg == '"-I"':
993 i += 1
994 self.iflags.append(unquote(args[i]))
995 elif arg.startswith('"-D'):
996 self.dflags.append(unquote(args[i])[2:])
997 elif arg.startswith('"-f'):
998 self.fflags.append(unquote(args[i])[2:])
999 elif arg.startswith('"-W'):
1000 self.wflags.append(unquote(args[i])[2:])
1001 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or
1002 arg == '"-g3"'):
1003 # ignore -O -m64 -g
1004 self.other_args.append(unquote(args[i]))
1005 i += 1
1006 self.dflags = sorted(set(self.dflags))
1007 self.fflags = sorted(set(self.fflags))
1008 # self.wflags is not sorted because some are order sensitive
1009 # and we ignore them anyway.
1010 if self.pkg not in self.runner.pkg_obj2cc:
1011 self.runner.pkg_obj2cc[self.pkg] = {}
1012 self.runner.pkg_obj2cc[self.pkg][self.obj] = self
1013 return self
1014
1015 def write(self, s):
1016 self.outf.write(s + '\n')
1017
1018 def dump_debug_flags(self, name, flags):
1019 self.write('// ' + name + ':')
1020 for f in flags:
1021 self.write('// %s' % f)
1022
1023 def dump(self):
1024 """Dump only error/debug info to the output .bp file."""
1025 if not self.runner.args.debug:
1026 return
1027 self.runner.init_bp_file(self.outf_name)
1028 with open(self.outf_name, 'a') as outf:
1029 self.outf = outf
1030 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line)
1031 self.write('// cc_object for %12s' % self.pkg)
1032 self.write('// src = %s' % short_out_name(self.pkg, self.src))
1033 self.write('// obj = %s' % short_out_name(self.pkg, self.obj))
1034 self.dump_debug_flags('-I flags', self.iflags)
1035 self.dump_debug_flags('-D flags', self.dflags)
1036 self.dump_debug_flags('-f flags', self.fflags)
1037 self.dump_debug_flags('-W flags', self.wflags)
1038 if self.other_args:
1039 self.dump_debug_flags('other args', self.other_args)
1040
1041
1042class Runner(object):
1043 """Main class to parse cargo -v output and print Android module definitions."""
1044
1045 def __init__(self, args):
1046 self.bp_files = set() # Remember all output Android.bp files.
1047 self.root_pkg = '' # name of package in ./Cargo.toml
1048 # Saved flags, modes, and data.
1049 self.args = args
1050 self.dry_run = not args.run
1051 self.skip_cargo = args.skipcargo
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001052 self.cargo_path = './cargo' # path to cargo, will be set later
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001053 self.checked_out_files = False # to check only once
1054 self.build_out_files = [] # output files generated by build.rs
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001055 # All cc/ar objects, crates, dependencies, and warning files
1056 self.cc_objects = list()
1057 self.pkg_obj2cc = {}
1058 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i]
1059 self.ar_objects = list()
1060 self.crates = list()
1061 self.dependencies = list() # dependent and build script crates
1062 self.warning_files = set()
1063 # Keep a unique mapping from (module name) to crate
1064 self.name_owners = {}
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001065 # Save and dump all errors from cargo to Android.bp.
1066 self.errors = ''
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001067 self.setup_cargo_path()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001068 # Default action is cargo clean, followed by build or user given actions.
1069 if args.cargo:
1070 self.cargo = ['clean'] + args.cargo
1071 else:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001072 default_target = '--target x86_64-unknown-linux-gnu'
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -07001073 # Use the same target for both host and default device builds.
1074 # Same target is used as default in host x86_64 Android compilation.
1075 # Note: b/169872957, prebuilt cargo failed to build vsock
1076 # on x86_64-unknown-linux-musl systems.
1077 self.cargo = ['clean', 'build ' + default_target]
1078 if args.tests:
1079 self.cargo.append('build --tests ' + default_target)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001080
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001081 def setup_cargo_path(self):
1082 """Find cargo in the --cargo_bin or prebuilt rust bin directory."""
1083 if self.args.cargo_bin:
1084 self.cargo_path = os.path.join(self.args.cargo_bin, 'cargo')
1085 if not os.path.isfile(self.cargo_path):
1086 sys.exit('ERROR: cannot find cargo in ' + self.args.cargo_bin)
1087 print('WARNING: using cargo in ' + self.args.cargo_bin)
1088 return
1089 # We have only tested this on Linux.
1090 if platform.system() != 'Linux':
1091 sys.exit('ERROR: this script has only been tested on Linux with cargo.')
1092 # Assuming that this script is in development/scripts.
1093 my_dir = os.path.dirname(os.path.abspath(__file__))
1094 linux_dir = os.path.join(my_dir, '..', '..',
1095 'prebuilts', 'rust', 'linux-x86')
1096 if not os.path.isdir(linux_dir):
1097 sys.exit('ERROR: cannot find directory ' + linux_dir)
1098 rust_version = self.find_rust_version(my_dir, linux_dir)
1099 cargo_bin = os.path.join(linux_dir, rust_version, 'bin')
1100 self.cargo_path = os.path.join(cargo_bin, 'cargo')
1101 if not os.path.isfile(self.cargo_path):
1102 sys.exit('ERROR: cannot find cargo in ' + cargo_bin
1103 + '; please try --cargo_bin= flag.')
1104 return
1105
1106 def find_rust_version(self, my_dir, linux_dir):
1107 """Use my script directory, find prebuilt rust version."""
1108 # First look up build/soong/rust/config/global.go.
1109 path2global = os.path.join(my_dir, '..', '..',
1110 'build', 'soong', 'rust', 'config', 'global.go')
1111 if os.path.isfile(path2global):
1112 # try to find: RustDefaultVersion = "1.44.0"
1113 version_pat = re.compile(
1114 r'\s*RustDefaultVersion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+)".*$')
1115 with open(path2global, 'r') as inf:
1116 for line in inf:
1117 result = version_pat.match(line)
1118 if result:
1119 return result.group(1)
1120 print('WARNING: cannot find RustDefaultVersion in ' + path2global)
1121 # Otherwise, find the newest (largest) version number in linux_dir.
1122 rust_version = (0, 0, 0) # the prebuilt version to use
1123 version_pat = re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)$')
1124 for dir_name in os.listdir(linux_dir):
1125 result = version_pat.match(dir_name)
1126 if not result:
1127 continue
1128 version = (result.group(1), result.group(2), result.group(3))
1129 if version > rust_version:
1130 rust_version = version
1131 return '.'.join(rust_version)
1132
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001133 def find_out_files(self):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001134 # list1 has build.rs output for normal crates
1135 list1 = glob.glob(TARGET_TMP + '/*/*/build/' + self.root_pkg + '-*/out/*')
1136 # list2 has build.rs output for proc-macro crates
1137 list2 = glob.glob(TARGET_TMP + '/*/build/' + self.root_pkg + '-*/out/*')
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001138 return list1 + list2
1139
1140 def copy_out_files(self):
1141 """Copy build.rs output files to ./out and set up build_out_files."""
1142 if self.checked_out_files:
1143 return
1144 self.checked_out_files = True
1145 cargo_out_files = self.find_out_files()
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001146 out_files = set()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001147 if cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001148 os.makedirs('out', exist_ok=True)
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001149 for path in cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001150 file_name = path.split('/')[-1]
1151 out_files.add(file_name)
1152 shutil.copy(path, 'out/' + file_name)
1153 self.build_out_files = sorted(out_files)
1154
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001155 def has_used_out_dir(self):
1156 """Returns true if env!("OUT_DIR") is found."""
1157 return 0 == os.system('grep -rl --exclude build.rs --include \\*.rs' +
1158 ' \'env!("OUT_DIR")\' * > /dev/null')
1159
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001160 def copy_out_module_name(self):
1161 if self.args.copy_out and self.build_out_files:
1162 return 'copy_' + self.root_pkg + '_build_out'
1163 else:
1164 return ''
1165
Haibo Huang0f72c952021-03-19 11:34:15 -07001166 def read_license(self, name):
1167 if not os.path.isfile(name):
1168 return ''
1169 license = ''
1170 with open(name, 'r') as intf:
1171 line = intf.readline()
1172 # Firstly skip ANDROID_BP_HEADER
1173 while line.startswith('//'):
1174 line = intf.readline()
Joel Galensond9d13b82021-04-05 11:27:55 -07001175 # Read all lines until we see a rust_* or genrule rule.
1176 while line != '' and not (line.startswith('rust_') or line.startswith('genrule {')):
Haibo Huang0f72c952021-03-19 11:34:15 -07001177 license += line
1178 line = intf.readline()
1179 return license.strip()
1180
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001181 def dump_copy_out_module(self, outf):
1182 """Output the genrule module to copy out/* to $(genDir)."""
1183 copy_out = self.copy_out_module_name()
1184 if not copy_out:
1185 return
1186 outf.write('\ngenrule {\n')
1187 outf.write(' name: "' + copy_out + '",\n')
1188 outf.write(' srcs: ["out/*"],\n')
1189 outf.write(' cmd: "cp $(in) $(genDir)",\n')
1190 if len(self.build_out_files) > 1:
1191 outf.write(' out: [\n')
1192 for f in self.build_out_files:
1193 outf.write(' "' + f + '",\n')
1194 outf.write(' ],\n')
1195 else:
1196 outf.write(' out: ["' + self.build_out_files[0] + '"],\n')
1197 outf.write('}\n')
1198
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001199 def init_bp_file(self, name):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001200 # name could be Android.bp or sub_dir_path/Android.bp
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001201 if name not in self.bp_files:
1202 self.bp_files.add(name)
Haibo Huang0f72c952021-03-19 11:34:15 -07001203 license_section = self.read_license(name)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001204 with open(name, 'w') as outf:
Joel Galenson367360c2021-04-29 14:31:43 -07001205 print_args = filter(lambda x: x != "--no-test-mapping", sys.argv[1:])
1206 outf.write(ANDROID_BP_HEADER.format(args=' '.join(print_args)))
Haibo Huang0f72c952021-03-19 11:34:15 -07001207 outf.write('\n')
1208 outf.write(license_section)
1209 outf.write('\n')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001210 # at most one copy_out module per .bp file
1211 self.dump_copy_out_module(outf)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001212
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001213 def try_claim_module_name(self, name, owner):
1214 """Reserve and return True if it has not been reserved yet."""
1215 if name not in self.name_owners or owner == self.name_owners[name]:
1216 self.name_owners[name] = owner
1217 return True
1218 return False
1219
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001220 def claim_module_name(self, prefix, owner, counter):
1221 """Return prefix if not owned yet, otherwise, prefix+str(counter)."""
1222 while True:
1223 name = prefix
1224 if counter > 0:
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001225 name += '_' + str(counter)
1226 if self.try_claim_module_name(name, owner):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001227 return name
1228 counter += 1
1229
1230 def find_root_pkg(self):
1231 """Read name of [package] in ./Cargo.toml."""
1232 if not os.path.exists('./Cargo.toml'):
1233 return
1234 with open('./Cargo.toml', 'r') as inf:
1235 pkg_section = re.compile(r'^ *\[package\]')
1236 name = re.compile('^ *name *= * "([^"]*)"')
1237 in_pkg = False
1238 for line in inf:
1239 if in_pkg:
1240 if name.match(line):
1241 self.root_pkg = name.match(line).group(1)
1242 break
1243 else:
1244 in_pkg = pkg_section.match(line) is not None
1245
1246 def run_cargo(self):
1247 """Calls cargo -v and save its output to ./cargo.out."""
1248 if self.skip_cargo:
1249 return self
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001250 cargo_toml = './Cargo.toml'
1251 cargo_out = './cargo.out'
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001252 # Do not use Cargo.lock, because .bp rules are designed to
1253 # run with "latest" crates avaialable on Android.
1254 cargo_lock = './Cargo.lock'
1255 cargo_lock_saved = './cargo.lock.saved'
1256 had_cargo_lock = os.path.exists(cargo_lock)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001257 if not os.access(cargo_toml, os.R_OK):
1258 print('ERROR: Cannot find or read', cargo_toml)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001259 return self
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001260 if not self.dry_run:
1261 if os.path.exists(cargo_out):
1262 os.remove(cargo_out)
1263 if not self.args.use_cargo_lock and had_cargo_lock: # save it
1264 os.rename(cargo_lock, cargo_lock_saved)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001265 cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> ' + cargo_out + ' 2>&1'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001266 # set up search PATH for cargo to find the correct rustc
1267 saved_path = os.environ['PATH']
1268 os.environ['PATH'] = os.path.dirname(self.cargo_path) + ':' + saved_path
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001269 # Add [workspace] to Cargo.toml if it is not there.
1270 added_workspace = False
1271 if self.args.add_workspace:
1272 with open(cargo_toml, 'r') as in_file:
1273 cargo_toml_lines = in_file.readlines()
1274 found_workspace = '[workspace]\n' in cargo_toml_lines
1275 if found_workspace:
1276 print('### WARNING: found [workspace] in Cargo.toml')
1277 else:
1278 with open(cargo_toml, 'a') as out_file:
1279 out_file.write('[workspace]\n')
1280 added_workspace = True
1281 if self.args.verbose:
1282 print('### INFO: added [workspace] to Cargo.toml')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001283 for c in self.cargo:
1284 features = ''
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001285 if c != 'clean':
1286 if self.args.features is not None:
1287 features = ' --no-default-features'
1288 if self.args.features:
1289 features += ' --features ' + self.args.features
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001290 cmd_v_flag = ' -vv ' if self.args.vv else ' -v '
1291 cmd = self.cargo_path + cmd_v_flag
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001292 cmd += c + features + cmd_tail
1293 if self.args.rustflags and c != 'clean':
1294 cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
1295 if self.dry_run:
1296 print('Dry-run skip:', cmd)
1297 else:
1298 if self.args.verbose:
1299 print('Running:', cmd)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001300 with open(cargo_out, 'a') as out_file:
1301 out_file.write('### Running: ' + cmd + '\n')
Joel Galenson6bf54e32021-05-17 10:54:50 -07001302 ret = os.system(cmd)
1303 if ret != 0:
1304 print('*** There was an error while running cargo. ' +
1305 'See the cargo.out file for details.')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001306 if added_workspace: # restore original Cargo.toml
1307 with open(cargo_toml, 'w') as out_file:
1308 out_file.writelines(cargo_toml_lines)
1309 if self.args.verbose:
1310 print('### INFO: restored original Cargo.toml')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001311 os.environ['PATH'] = saved_path
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001312 if not self.dry_run:
1313 if not had_cargo_lock: # restore to no Cargo.lock state
1314 os.remove(cargo_lock)
1315 elif not self.args.use_cargo_lock: # restore saved Cargo.lock
1316 os.rename(cargo_lock_saved, cargo_lock)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001317 return self
1318
1319 def dump_dependencies(self):
1320 """Append dependencies and their features to Android.bp."""
1321 if not self.dependencies:
1322 return
1323 dependent_list = list()
1324 for c in self.dependencies:
1325 dependent_list.append(c.feature_list())
1326 sorted_dependencies = sorted(set(dependent_list))
1327 self.init_bp_file('Android.bp')
1328 with open('Android.bp', 'a') as outf:
1329 outf.write('\n// dependent_library ["feature_list"]\n')
1330 for s in sorted_dependencies:
1331 outf.write('// ' + s + '\n')
1332
1333 def dump_pkg_obj2cc(self):
1334 """Dump debug info of the pkg_obj2cc map."""
1335 if not self.args.debug:
1336 return
1337 self.init_bp_file('Android.bp')
1338 with open('Android.bp', 'a') as outf:
1339 sorted_pkgs = sorted(self.pkg_obj2cc.keys())
1340 for pkg in sorted_pkgs:
1341 if not self.pkg_obj2cc[pkg]:
1342 continue
1343 outf.write('\n// obj => src for %s\n' % pkg)
1344 obj2cc = self.pkg_obj2cc[pkg]
1345 for obj in sorted(obj2cc.keys()):
1346 outf.write('// ' + short_out_name(pkg, obj) + ' => ' +
1347 short_out_name(pkg, obj2cc[obj].src) + '\n')
1348
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001349 def apply_patch(self):
1350 """Apply local patch file if it is given."""
1351 if self.args.patch:
1352 if self.dry_run:
1353 print('Dry-run skip patch file:', self.args.patch)
1354 else:
1355 if not os.path.exists(self.args.patch):
1356 self.append_to_bp('ERROR cannot find patch file: ' + self.args.patch)
1357 return self
1358 if self.args.verbose:
1359 print('### INFO: applying local patch file:', self.args.patch)
Joel Galenson7e8247e2021-05-20 18:51:42 -07001360 subprocess.run(['patch', '-s', '--no-backup-if-mismatch', './Android.bp',
1361 self.args.patch], check=True)
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001362 return self
1363
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001364 def gen_bp(self):
1365 """Parse cargo.out and generate Android.bp files."""
1366 if self.dry_run:
1367 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp')
1368 elif os.path.exists(CARGO_OUT):
1369 self.find_root_pkg()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001370 if self.args.copy_out:
1371 self.copy_out_files()
1372 elif self.find_out_files() and self.has_used_out_dir():
1373 print('WARNING: ' + self.root_pkg + ' has cargo output files; ' +
1374 'please rerun with the --copy-out flag.')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001375 with open(CARGO_OUT, 'r') as cargo_out:
1376 self.parse(cargo_out, 'Android.bp')
1377 self.crates.sort(key=get_module_name)
1378 for obj in self.cc_objects:
1379 obj.dump()
1380 self.dump_pkg_obj2cc()
1381 for crate in self.crates:
1382 crate.dump()
1383 dumped_libs = set()
1384 for lib in self.ar_objects:
1385 if lib.pkg == self.root_pkg:
1386 lib_name = file_base_name(lib.lib)
1387 if lib_name not in dumped_libs:
1388 dumped_libs.add(lib_name)
1389 lib.dump()
Joel Galenson5664f2a2021-06-10 10:13:49 -07001390 if self.args.add_toplevel_block:
1391 with open(self.args.add_toplevel_block, 'r') as f:
1392 self.append_to_bp('\n' + f.read() + '\n')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001393 if self.args.dependencies and self.dependencies:
1394 self.dump_dependencies()
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001395 if self.errors:
Joel Galenson3f42f802021-04-07 12:42:17 -07001396 self.append_to_bp('\n' + ERRORS_LINE + '\n' + self.errors)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001397 return self
1398
1399 def add_ar_object(self, obj):
1400 self.ar_objects.append(obj)
1401
1402 def add_cc_object(self, obj):
1403 self.cc_objects.append(obj)
1404
1405 def add_crate(self, crate):
1406 """Merge crate with someone in crates, or append to it. Return crates."""
1407 if crate.skip_crate():
1408 if self.args.debug: # include debug info of all crates
1409 self.crates.append(crate)
1410 if self.args.dependencies: # include only dependent crates
1411 if (is_dependent_file_path(crate.main_src) and
1412 not is_build_crate_name(crate.crate_name)):
1413 self.dependencies.append(crate)
1414 else:
1415 for c in self.crates:
1416 if c.merge(crate, 'Android.bp'):
1417 return
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001418 # If not merged, decide module type and name now.
1419 crate.decide_module_type()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001420 self.crates.append(crate)
1421
1422 def find_warning_owners(self):
1423 """For each warning file, find its owner crate."""
1424 missing_owner = False
1425 for f in self.warning_files:
1426 cargo_dir = '' # find lowest crate, with longest path
1427 owner = None # owner crate of this warning
1428 for c in self.crates:
1429 if (f.startswith(c.cargo_dir + '/') and
1430 len(cargo_dir) < len(c.cargo_dir)):
1431 cargo_dir = c.cargo_dir
1432 owner = c
1433 if owner:
1434 owner.has_warning = True
1435 else:
1436 missing_owner = True
1437 if missing_owner and os.path.exists('Cargo.toml'):
1438 # owner is the root cargo, with empty cargo_dir
1439 for c in self.crates:
1440 if not c.cargo_dir:
1441 c.has_warning = True
1442
1443 def rustc_command(self, n, rustc_line, line, outf_name):
1444 """Process a rustc command line from cargo -vv output."""
1445 # cargo build -vv output can have multiple lines for a rustc command
1446 # due to '\n' in strings for environment variables.
1447 # strip removes leading spaces and '\n' at the end
1448 new_rustc = (rustc_line.strip() + line) if rustc_line else line
1449 # Use an heuristic to detect the completions of a multi-line command.
1450 # This might fail for some very rare case, but easy to fix manually.
1451 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0:
1452 return new_rustc
1453 if RUSTC_VV_CMD_ARGS.match(new_rustc):
1454 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1)
1455 self.add_crate(Crate(self, outf_name).parse(n, args))
1456 else:
1457 self.assert_empty_vv_line(new_rustc)
1458 return ''
1459
1460 def cc_ar_command(self, n, groups, outf_name):
1461 pkg = groups.group(1)
1462 line = groups.group(3)
1463 if groups.group(2) == 'cc':
1464 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line))
1465 else:
1466 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line))
1467
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001468 def append_to_bp(self, line):
1469 self.init_bp_file('Android.bp')
1470 with open('Android.bp', 'a') as outf:
1471 outf.write(line)
1472
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001473 def assert_empty_vv_line(self, line):
1474 if line: # report error if line is not empty
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001475 self.append_to_bp('ERROR -vv line: ' + line)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001476 return ''
1477
1478 def parse(self, inf, outf_name):
1479 """Parse rustc and warning messages in inf, return a list of Crates."""
1480 n = 0 # line number
1481 prev_warning = False # true if the previous line was warning: ...
1482 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
1483 for line in inf:
1484 n += 1
1485 if line.startswith('warning: '):
1486 prev_warning = True
1487 rustc_line = self.assert_empty_vv_line(rustc_line)
1488 continue
1489 new_rustc = ''
1490 if RUSTC_PAT.match(line):
1491 args_line = RUSTC_PAT.match(line).group(1)
1492 self.add_crate(Crate(self, outf_name).parse(n, args_line))
1493 self.assert_empty_vv_line(rustc_line)
1494 elif rustc_line or RUSTC_VV_PAT.match(line):
1495 new_rustc = self.rustc_command(n, rustc_line, line, outf_name)
1496 elif CC_AR_VV_PAT.match(line):
1497 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name)
1498 elif prev_warning and WARNING_FILE_PAT.match(line):
1499 self.assert_empty_vv_line(rustc_line)
1500 fpath = WARNING_FILE_PAT.match(line).group(1)
1501 if fpath[0] != '/': # ignore absolute path
1502 self.warning_files.add(fpath)
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001503 elif line.startswith('error: ') or line.startswith('error[E'):
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001504 if not self.args.ignore_cargo_errors:
1505 self.errors += line
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001506 prev_warning = False
1507 rustc_line = new_rustc
1508 self.find_warning_owners()
1509
1510
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001511def get_parser():
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001512 """Parse main arguments."""
1513 parser = argparse.ArgumentParser('cargo2android')
1514 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001515 '--add_workspace',
1516 action='store_true',
1517 default=False,
1518 help=('append [workspace] to Cargo.toml before calling cargo,' +
1519 ' to treat current directory as root of package source;' +
1520 ' otherwise the relative source file path in generated' +
1521 ' .bp file will be from the parent directory.'))
1522 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001523 '--cargo',
1524 action='append',
1525 metavar='args_string',
1526 help=('extra cargo build -v args in a string, ' +
1527 'each --cargo flag calls cargo build -v once'))
1528 parser.add_argument(
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001529 '--cargo_bin',
1530 type=str,
1531 help='use cargo in the cargo_bin directory instead of the prebuilt one')
1532 parser.add_argument(
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001533 '--copy-out',
1534 action='store_true',
1535 default=False,
1536 help=('only for root directory, ' +
1537 'copy build.rs output to ./out/* and add a genrule to copy ' +
1538 './out/* to genrule output; for crates with code pattern: ' +
1539 'include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))'))
1540 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001541 '--debug',
1542 action='store_true',
1543 default=False,
1544 help='dump debug info into Android.bp')
1545 parser.add_argument(
1546 '--dependencies',
1547 action='store_true',
1548 default=False,
1549 help='dump debug info of dependent crates')
1550 parser.add_argument(
1551 '--device',
1552 action='store_true',
1553 default=False,
1554 help='run cargo also for a default device target')
1555 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001556 '--features',
1557 type=str,
1558 help=('pass features to cargo build, ' +
1559 'empty string means no default features'))
1560 parser.add_argument(
1561 '--global_defaults',
1562 type=str,
1563 help='add a defaults name to every module')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001564 parser.add_argument(
1565 '--host-first-multilib',
1566 action='store_true',
1567 default=False,
1568 help=('add a compile_multilib:"first" property ' +
1569 'to Android.bp host modules.'))
1570 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001571 '--ignore-cargo-errors',
1572 action='store_true',
1573 default=False,
1574 help='do not append cargo/rustc error messages to Android.bp')
1575 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001576 '--no-host',
1577 action='store_true',
1578 default=False,
1579 help='do not run cargo for the host; only for the device target')
1580 parser.add_argument(
1581 '--no-subdir',
1582 action='store_true',
1583 default=False,
1584 help='do not output anything for sub-directories')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001585 parser.add_argument(
1586 '--onefile',
1587 action='store_true',
1588 default=False,
1589 help=('output all into one ./Android.bp, default will generate ' +
1590 'one Android.bp per Cargo.toml in subdirectories'))
1591 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001592 '--patch',
1593 type=str,
1594 help='apply the given patch file to generated ./Android.bp')
1595 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001596 '--run',
1597 action='store_true',
1598 default=False,
1599 help='run it, default is dry-run')
1600 parser.add_argument('--rustflags', type=str, help='passing flags to rustc')
1601 parser.add_argument(
1602 '--skipcargo',
1603 action='store_true',
1604 default=False,
1605 help='skip cargo command, parse cargo.out, and generate Android.bp')
1606 parser.add_argument(
1607 '--tests',
1608 action='store_true',
1609 default=False,
1610 help='run cargo build --tests after normal build')
1611 parser.add_argument(
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001612 '--use-cargo-lock',
1613 action='store_true',
1614 default=False,
1615 help=('run cargo build with existing Cargo.lock ' +
1616 '(used when some latest dependent crates failed)'))
1617 parser.add_argument(
Joel Galensond9c4de62021-04-23 10:26:40 -07001618 '--min-sdk-version',
1619 type=str,
1620 help='Minimum SDK version')
1621 parser.add_argument(
1622 '--apex-available',
1623 nargs='*',
1624 help='Mark the main library as apex_available with the given apexes.')
1625 parser.add_argument(
Ivan Lozano91920862021-07-19 10:49:08 -04001626 '--vendor-available',
1627 action='store_true',
1628 default=False,
1629 help='Mark the main library as vendor_available.')
1630 parser.add_argument(
1631 '--vendor-ramdisk-available',
1632 action='store_true',
1633 default=False,
1634 help='Mark the main library as vendor_ramdisk_available.')
1635 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001636 '--force-rlib',
1637 action='store_true',
1638 default=False,
1639 help='Make the main library an rlib.')
1640 parser.add_argument(
Joel Galenson12467e52021-07-12 14:33:28 -07001641 '--whole-static-libs',
1642 nargs='*',
1643 default=[],
1644 help='Make the given libraries (without lib prefixes) whole_static_libs.')
1645 parser.add_argument(
Joel Galensone4f53882021-07-19 11:14:55 -07001646 '--test-data',
1647 nargs='*',
1648 default=[],
1649 help=('Add the given file to the given test\'s data property. ' +
1650 'Usage: test-path=data-path'))
1651 parser.add_argument(
Joel Galenson97e414a2021-05-27 09:42:32 -07001652 '--dependency-blocklist',
1653 nargs='*',
1654 default=[],
Joel Galenson12467e52021-07-12 14:33:28 -07001655 help='Do not emit the given dependencies (without lib prefixes).')
Joel Galenson97e414a2021-05-27 09:42:32 -07001656 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001657 '--lib-blocklist',
1658 nargs='*',
1659 default=[],
Joel Galenson12467e52021-07-12 14:33:28 -07001660 help='Do not emit the given C libraries as dependencies (without lib prefixes).')
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001661 parser.add_argument(
Joel Galensonf6b3c912021-06-03 16:00:54 -07001662 '--test-blocklist',
1663 nargs='*',
1664 default=[],
1665 help=('Do not emit the given tests. ' +
1666 'Pass the path to the test file to exclude.'))
1667 parser.add_argument(
Joel Galenson3d6d1e72021-06-07 15:00:24 -07001668 '--cfg-blocklist',
1669 nargs='*',
1670 default=[],
1671 help='Do not emit the given cfg.')
1672 parser.add_argument(
Joel Galenson5664f2a2021-06-10 10:13:49 -07001673 '--add-toplevel-block',
1674 type=str,
1675 help='Add the contents of the given file to the top level of the Android.bp.')
1676 parser.add_argument(
1677 '--add-module-block',
1678 type=str,
1679 help='Add the contents of the given file to the main module.')
1680 parser.add_argument(
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001681 '--no-test-mapping',
1682 action='store_true',
1683 default=False,
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001684 help='Deprecated. Has no effect.')
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001685 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001686 '--verbose',
1687 action='store_true',
1688 default=False,
1689 help='echo executed commands')
1690 parser.add_argument(
1691 '--vv',
1692 action='store_true',
1693 default=False,
1694 help='run cargo with -vv instead of default -v')
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001695 parser.add_argument(
1696 '--dump-config-and-exit',
1697 type=str,
1698 help=('Dump command-line arguments (minus this flag) to a config file and exit. ' +
1699 'This is intended to help migrate from command line options to config files.'))
1700 parser.add_argument(
1701 '--config',
1702 type=str,
1703 help=('Load command-line options from the given config file. ' +
1704 'Options in this file will override those passed on the command line.'))
1705 return parser
1706
1707
1708def parse_args(parser):
1709 """Parses command-line options."""
1710 args = parser.parse_args()
1711 # Use the values specified in a config file if one was found.
1712 if args.config:
1713 with open(args.config, 'r') as f:
1714 config = json.load(f)
1715 args_dict = vars(args)
1716 for arg in config:
1717 args_dict[arg.replace('-', '_')] = config[arg]
1718 return args
1719
1720
1721def dump_config(parser, args):
1722 """Writes the non-default command-line options to the specified file."""
1723 args_dict = vars(args)
1724 # Filter out the arguments that have their default value.
Joel Galenson367360c2021-04-29 14:31:43 -07001725 # Also filter certain "temporary" arguments.
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001726 non_default_args = {}
1727 for arg in args_dict:
Joel Galenson367360c2021-04-29 14:31:43 -07001728 if args_dict[arg] != parser.get_default(
1729 arg) and arg != 'dump_config_and_exit' and arg != 'no_test_mapping':
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001730 non_default_args[arg.replace('_', '-')] = args_dict[arg]
1731 # Write to the specified file.
1732 with open(args.dump_config_and_exit, 'w') as f:
1733 json.dump(non_default_args, f, indent=2, sort_keys=True)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001734
1735
1736def main():
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001737 parser = get_parser()
1738 args = parse_args(parser)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001739 if not args.run: # default is dry-run
1740 print(DRY_RUN_NOTE)
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001741 if args.dump_config_and_exit:
1742 dump_config(parser, args)
1743 else:
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001744 Runner(args).run_cargo().gen_bp().apply_patch()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001745
1746
1747if __name__ == '__main__':
1748 main()