blob: 12c982fda526829b29ea239ed0daa1a810644e4e [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()
606 self.write('}')
607
608 def dump_single_type_android_module(self):
609 """Dump one simple Android module, which has only one crate_type."""
610 crate_type = self.crate_types[0]
611 if crate_type != 'test':
612 # do not change self.stem or self.module_name
613 self.dump_one_android_module(crate_type)
614 return
615 # Dump one test module per source file, and separate host and device tests.
616 # crate_type == 'test'
Joel Galensonf6b3c912021-06-03 16:00:54 -0700617 self.srcs = [src for src in self.srcs if not src in self.runner.args.test_blocklist]
618 if ((self.host_supported and self.device_supported and len(self.srcs) > 0) or
619 len(self.srcs) > 1):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700620 self.srcs = sorted(set(self.srcs))
621 self.dump_defaults_module()
622 saved_srcs = self.srcs
623 for src in saved_srcs:
624 self.srcs = [src]
625 saved_device_supported = self.device_supported
626 saved_host_supported = self.host_supported
627 saved_main_src = self.main_src
628 self.main_src = src
629 if saved_host_supported:
630 self.device_supported = False
631 self.host_supported = True
632 self.module_name = self.test_module_name()
633 self.decide_one_module_type(crate_type)
634 self.dump_one_android_module(crate_type)
635 if saved_device_supported:
636 self.device_supported = True
637 self.host_supported = False
638 self.module_name = self.test_module_name()
639 self.decide_one_module_type(crate_type)
640 self.dump_one_android_module(crate_type)
641 self.host_supported = saved_host_supported
642 self.device_supported = saved_device_supported
643 self.main_src = saved_main_src
644 self.srcs = saved_srcs
645
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700646 def dump_one_android_module(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800647 """Dump one Android module definition."""
648 if not self.module_type:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700649 self.write('\nERROR: unknown crate_type ' + crate_type)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800650 return
651 self.write('\n' + self.module_type + ' {')
652 self.dump_android_core_properties()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700653 if not self.defaults:
654 self.dump_edition_flags_libs()
655 if self.runner.args.host_first_multilib and self.host_supported and crate_type != 'test':
656 self.write(' compile_multilib: "first",')
Joel Galensond9c4de62021-04-23 10:26:40 -0700657 if self.runner.args.apex_available and crate_type == 'lib':
658 self.write(' apex_available: [')
659 for apex in self.runner.args.apex_available:
660 self.write(' "%s",' % apex)
661 self.write(' ],')
Ivan Lozano91920862021-07-19 10:49:08 -0400662 if self.runner.args.vendor_available:
663 self.write(' vendor_available: true,')
664 if self.runner.args.vendor_ramdisk_available:
665 self.write(' vendor_ramdisk_available: true,')
Joel Galensond9c4de62021-04-23 10:26:40 -0700666 if self.runner.args.min_sdk_version and crate_type == 'lib':
667 self.write(' min_sdk_version: "%s",' % self.runner.args.min_sdk_version)
Joel Galenson5664f2a2021-06-10 10:13:49 -0700668 if self.runner.args.add_module_block:
669 with open(self.runner.args.add_module_block, 'r') as f:
670 self.write(' %s,' % f.read().replace('\n', '\n '))
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700671 self.write('}')
672
673 def dump_android_flags(self):
674 """Dump Android module flags property."""
ThiƩbaud Weksteena5a728b2021-04-08 14:23:49 +0200675 if not self.codegens and not self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700676 return
677 self.write(' flags: [')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800678 if self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700679 self.write(' "--cap-lints ' + self.cap_lints + '",')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700680 codegens_fmt = '"-C %s"'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700681 self.dump_android_property_list_items(codegens_fmt, self.codegens)
682 self.write(' ],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700683
684 def dump_edition_flags_libs(self):
685 if self.edition:
686 self.write(' edition: "' + self.edition + '",')
687 self.dump_android_property_list('features', '"%s"', self.features)
Joel Galenson3d6d1e72021-06-07 15:00:24 -0700688 cfgs = [cfg for cfg in self.cfgs if not cfg in self.runner.args.cfg_blocklist]
689 self.dump_android_property_list('cfgs', '"%s"', cfgs)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700690 self.dump_android_flags()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800691 if self.externs:
692 self.dump_android_externs()
Joel Galenson12467e52021-07-12 14:33:28 -0700693 all_static_libs = [lib for lib in self.static_libs if not lib in self.runner.args.lib_blocklist]
694 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 -0700695 self.dump_android_property_list('static_libs', '"lib%s"', static_libs)
Joel Galenson12467e52021-07-12 14:33:28 -0700696 whole_static_libs = [lib for lib in all_static_libs if lib in self.runner.args.whole_static_libs]
697 self.dump_android_property_list('whole_static_libs', '"lib%s"', whole_static_libs)
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700698 shared_libs = [lib for lib in self.shared_libs if not lib in self.runner.args.lib_blocklist]
699 self.dump_android_property_list('shared_libs', '"lib%s"', shared_libs)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800700
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700701 def main_src_basename_path(self):
702 return re.sub('/', '_', re.sub('.rs$', '', self.main_src))
703
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800704 def test_module_name(self):
705 """Return a unique name for a test module."""
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700706 # root_pkg+(_host|_device) + '_test_'+source_file_name
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700707 suffix = self.main_src_basename_path()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700708 host_device = '_host'
709 if self.device_supported:
710 host_device = '_device'
711 return self.root_pkg + host_device + '_test_' + suffix
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800712
713 def decide_module_type(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700714 # Use the first crate type for the default/first module.
715 crate_type = self.crate_types[0] if self.crate_types else ''
716 self.decide_one_module_type(crate_type)
717
718 def decide_one_module_type(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800719 """Decide which Android module type to use."""
720 host = '' if self.device_supported else '_host'
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700721 rlib = '_rlib' if self.runner.args.force_rlib else ''
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700722 if crate_type == 'bin': # rust_binary[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800723 self.module_type = 'rust_binary' + host
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700724 # In rare cases like protobuf-codegen, the output binary name must
725 # be renamed to use as a plugin for protoc.
726 self.stem = altered_stem(self.crate_name)
727 self.module_name = altered_name(self.crate_name)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700728 elif crate_type == 'lib': # rust_library[_host]
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700729 # TODO(chh): should this be rust_library[_host]?
730 # Assuming that Cargo.toml do not use both 'lib' and 'rlib',
731 # because we map them both to rlib.
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700732 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800733 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700734 self.module_name = altered_name(self.stem)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700735 elif crate_type == 'rlib': # rust_library[_host]
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700736 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700737 self.stem = 'lib' + self.crate_name
738 self.module_name = altered_name(self.stem)
739 elif crate_type == 'dylib': # rust_library[_host]_dylib
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800740 self.module_type = 'rust_library' + host + '_dylib'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700741 self.stem = 'lib' + self.crate_name
742 self.module_name = altered_name(self.stem) + '_dylib'
743 elif crate_type == 'cdylib': # rust_library[_host]_shared
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500744 self.module_type = 'rust_ffi' + host + '_shared'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700745 self.stem = 'lib' + self.crate_name
746 self.module_name = altered_name(self.stem) + '_shared'
747 elif crate_type == 'staticlib': # rust_library[_host]_static
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500748 self.module_type = 'rust_ffi' + host + '_static'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700749 self.stem = 'lib' + self.crate_name
750 self.module_name = altered_name(self.stem) + '_static'
751 elif crate_type == 'test': # rust_test[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800752 self.module_type = 'rust_test' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700753 # Before do_merge, stem name is based on the --crate-name parameter.
754 # and test module name is based on stem.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800755 self.stem = self.test_module_name()
756 # self.stem will be changed after merging with other tests.
757 # self.stem is NOT used for final test binary name.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700758 # rust_test uses each source file base name as part of output file name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700759 # In do_merge, this function is called again, with a module_name.
760 # We make sure that the module name is unique in each package.
761 if self.module_name:
762 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add
763 # different suffixes and distinguish multiple tests of the same
764 # crate name. We ignore -C and use claim_module_name to get
765 # unique sequential suffix.
766 self.module_name = self.runner.claim_module_name(
767 self.module_name, self, 0)
768 # Now the module name is unique, stem should also match and unique.
769 self.stem = self.module_name
770 elif crate_type == 'proc-macro': # rust_proc_macro
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800771 self.module_type = 'rust_proc_macro'
772 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700773 self.module_name = altered_name(self.stem)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800774 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]?
775 self.module_type = ''
776 self.stem = ''
777
778 def dump_android_property_list_items(self, fmt, values):
779 for v in values:
780 # fmt has quotes, so we need escape_quotes(v)
781 self.write(' ' + (fmt % escape_quotes(v)) + ',')
782
783 def dump_android_property_list(self, name, fmt, values):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700784 if not values:
785 return
786 if len(values) > 1:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800787 self.write(' ' + name + ': [')
788 self.dump_android_property_list_items(fmt, values)
789 self.write(' ],')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700790 else:
791 self.write(' ' + name + ': [' +
792 (fmt % escape_quotes(values[0])) + '],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800793
794 def dump_android_core_properties(self):
795 """Dump the module header, name, stem, etc."""
796 self.write(' name: "' + self.module_name + '",')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700797 # see properties shared by dump_defaults_module
798 if self.defaults:
799 self.write(' defaults: ["' + self.defaults + '"],')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700800 elif self.runner.args.global_defaults:
801 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800802 if self.stem != self.module_name:
803 self.write(' stem: "' + self.stem + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700804 if self.has_warning and not self.cap_lints and not self.default_srcs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700805 self.write(' // has rustc warnings')
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700806 if self.host_supported and self.device_supported and self.module_type != 'rust_proc_macro':
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800807 self.write(' host_supported: true,')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700808 if not self.defaults:
809 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700810 if not self.default_srcs:
811 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700812 if 'test' in self.crate_types and not self.defaults:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800813 # self.root_pkg can have multiple test modules, with different *_tests[n]
814 # names, but their executables can all be installed under the same _tests
815 # directory. When built from Cargo.toml, all tests should have different
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700816 # file or crate names. So we used (root_pkg + '_tests') name as the
817 # relative_install_path.
818 # However, some package like 'slab' can have non-mergeable tests that
819 # must be separated by different module names. So, here we no longer
820 # emit relative_install_path.
821 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800822 self.write(' test_suites: ["general-tests"],')
823 self.write(' auto_gen_config: true,')
Joel Galensone261a152021-01-12 11:31:53 -0800824 if 'test' in self.crate_types and self.host_supported:
825 self.write(' test_options: {')
826 self.write(' unit_test: true,')
827 self.write(' },')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800828
829 def dump_android_externs(self):
830 """Dump the dependent rlibs and dylibs property."""
831 so_libs = list()
832 rust_libs = ''
833 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$')
834 for lib in self.externs:
835 # normal value of lib: "libc = liblibc-*.rlib"
836 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib"
837 # we should use "libgetrandom", not "lib" + "getrandom_package"
838 groups = deps_libname.match(lib)
839 if groups is not None:
840 lib_name = groups.group(1)
841 else:
842 lib_name = re.sub(' .*$', '', lib)
Joel Galenson97e414a2021-05-27 09:42:32 -0700843 if lib_name in self.runner.args.dependency_blocklist:
844 continue
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800845 if lib.endswith('.rlib') or lib.endswith('.rmeta'):
846 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta.
847 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n'
848 elif lib.endswith('.so'):
849 so_libs.append(lib_name)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700850 elif lib != 'proc_macro': # --extern proc_macro is special and ignored
851 rust_libs += ' // ERROR: unknown type of lib ' + lib + '\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800852 if rust_libs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700853 self.write(' rustlibs: [\n' + rust_libs + ' ],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800854 # Are all dependent .so files proc_macros?
855 # TODO(chh): Separate proc_macros and dylib.
856 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs)
857
858
859class ARObject(object):
860 """Information of an "ar" link command."""
861
862 def __init__(self, runner, outf_name):
863 # Remembered global runner and its members.
864 self.runner = runner
865 self.pkg = ''
866 self.outf_name = outf_name # path to Android.bp
867 # "ar" arguments
868 self.line_num = 1
869 self.line = ''
870 self.flags = '' # e.g. "crs"
871 self.lib = '' # e.g. "/.../out/lib*.a"
872 self.objs = list() # e.g. "/.../out/.../*.o"
873
874 def parse(self, pkg, line_num, args_line):
875 """Collect ar obj/lib file names."""
876 self.pkg = pkg
877 self.line_num = line_num
878 self.line = args_line
879 args = args_line.split()
880 num_args = len(args)
881 if num_args < 3:
882 print('ERROR: "ar" command has too few arguments', args_line)
883 else:
884 self.flags = unquote(args[0])
885 self.lib = unquote(args[1])
886 self.objs = sorted(set(map(unquote, args[2:])))
887 return self
888
889 def write(self, s):
890 self.outf.write(s + '\n')
891
892 def dump_debug_info(self):
893 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line)
894 self.write('// ar_object for %12s' % self.pkg)
895 self.write('// flags = %s' % self.flags)
896 self.write('// lib = %s' % short_out_name(self.pkg, self.lib))
897 for o in self.objs:
898 self.write('// obj = %s' % short_out_name(self.pkg, o))
899
900 def dump_android_lib(self):
901 """Write cc_library_static into Android.bp."""
902 self.write('\ncc_library_static {')
903 self.write(' name: "' + file_base_name(self.lib) + '",')
904 self.write(' host_supported: true,')
905 if self.flags != 'crs':
906 self.write(' // ar flags = %s' % self.flags)
907 if self.pkg not in self.runner.pkg_obj2cc:
908 self.write(' ERROR: cannot find source files.\n}')
909 return
910 self.write(' srcs: [')
911 obj2cc = self.runner.pkg_obj2cc[self.pkg]
912 # Note: wflags are ignored.
913 dflags = list()
914 fflags = list()
915 for obj in self.objs:
916 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",')
917 # TODO(chh): union of dflags and flags of all obj
918 # Now, just a temporary hack that uses the last obj's flags
919 dflags = obj2cc[obj].dflags
920 fflags = obj2cc[obj].fflags
921 self.write(' ],')
922 self.write(' cflags: [')
923 self.write(' "-O3",') # TODO(chh): is this default correct?
924 self.write(' "-Wno-error",')
925 for x in fflags:
926 self.write(' "-f' + x + '",')
927 for x in dflags:
928 self.write(' "-D' + x + '",')
929 self.write(' ],')
930 self.write('}')
931
932 def dump(self):
933 """Dump error/debug/module info to the output .bp file."""
934 self.runner.init_bp_file(self.outf_name)
935 with open(self.outf_name, 'a') as outf:
936 self.outf = outf
937 if self.runner.args.debug:
938 self.dump_debug_info()
939 self.dump_android_lib()
940
941
942class CCObject(object):
943 """Information of a "cc" compilation command."""
944
945 def __init__(self, runner, outf_name):
946 # Remembered global runner and its members.
947 self.runner = runner
948 self.pkg = ''
949 self.outf_name = outf_name # path to Android.bp
950 # "cc" arguments
951 self.line_num = 1
952 self.line = ''
953 self.src = ''
954 self.obj = ''
955 self.dflags = list() # -D flags
956 self.fflags = list() # -f flags
957 self.iflags = list() # -I flags
958 self.wflags = list() # -W flags
959 self.other_args = list()
960
961 def parse(self, pkg, line_num, args_line):
962 """Collect cc compilation flags and src/out file names."""
963 self.pkg = pkg
964 self.line_num = line_num
965 self.line = args_line
966 args = args_line.split()
967 i = 0
968 while i < len(args):
969 arg = args[i]
970 if arg == '"-c"':
971 i += 1
972 if args[i].startswith('"-o'):
973 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c"
974 self.obj = unquote(args[i])[2:]
975 i += 1
976 self.src = unquote(args[i])
977 else:
978 self.src = unquote(args[i])
979 elif arg == '"-o"':
980 i += 1
981 self.obj = unquote(args[i])
982 elif arg == '"-I"':
983 i += 1
984 self.iflags.append(unquote(args[i]))
985 elif arg.startswith('"-D'):
986 self.dflags.append(unquote(args[i])[2:])
987 elif arg.startswith('"-f'):
988 self.fflags.append(unquote(args[i])[2:])
989 elif arg.startswith('"-W'):
990 self.wflags.append(unquote(args[i])[2:])
991 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or
992 arg == '"-g3"'):
993 # ignore -O -m64 -g
994 self.other_args.append(unquote(args[i]))
995 i += 1
996 self.dflags = sorted(set(self.dflags))
997 self.fflags = sorted(set(self.fflags))
998 # self.wflags is not sorted because some are order sensitive
999 # and we ignore them anyway.
1000 if self.pkg not in self.runner.pkg_obj2cc:
1001 self.runner.pkg_obj2cc[self.pkg] = {}
1002 self.runner.pkg_obj2cc[self.pkg][self.obj] = self
1003 return self
1004
1005 def write(self, s):
1006 self.outf.write(s + '\n')
1007
1008 def dump_debug_flags(self, name, flags):
1009 self.write('// ' + name + ':')
1010 for f in flags:
1011 self.write('// %s' % f)
1012
1013 def dump(self):
1014 """Dump only error/debug info to the output .bp file."""
1015 if not self.runner.args.debug:
1016 return
1017 self.runner.init_bp_file(self.outf_name)
1018 with open(self.outf_name, 'a') as outf:
1019 self.outf = outf
1020 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line)
1021 self.write('// cc_object for %12s' % self.pkg)
1022 self.write('// src = %s' % short_out_name(self.pkg, self.src))
1023 self.write('// obj = %s' % short_out_name(self.pkg, self.obj))
1024 self.dump_debug_flags('-I flags', self.iflags)
1025 self.dump_debug_flags('-D flags', self.dflags)
1026 self.dump_debug_flags('-f flags', self.fflags)
1027 self.dump_debug_flags('-W flags', self.wflags)
1028 if self.other_args:
1029 self.dump_debug_flags('other args', self.other_args)
1030
1031
1032class Runner(object):
1033 """Main class to parse cargo -v output and print Android module definitions."""
1034
1035 def __init__(self, args):
1036 self.bp_files = set() # Remember all output Android.bp files.
1037 self.root_pkg = '' # name of package in ./Cargo.toml
1038 # Saved flags, modes, and data.
1039 self.args = args
1040 self.dry_run = not args.run
1041 self.skip_cargo = args.skipcargo
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001042 self.cargo_path = './cargo' # path to cargo, will be set later
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001043 self.checked_out_files = False # to check only once
1044 self.build_out_files = [] # output files generated by build.rs
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001045 # All cc/ar objects, crates, dependencies, and warning files
1046 self.cc_objects = list()
1047 self.pkg_obj2cc = {}
1048 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i]
1049 self.ar_objects = list()
1050 self.crates = list()
1051 self.dependencies = list() # dependent and build script crates
1052 self.warning_files = set()
1053 # Keep a unique mapping from (module name) to crate
1054 self.name_owners = {}
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001055 # Save and dump all errors from cargo to Android.bp.
1056 self.errors = ''
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001057 self.setup_cargo_path()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001058 # Default action is cargo clean, followed by build or user given actions.
1059 if args.cargo:
1060 self.cargo = ['clean'] + args.cargo
1061 else:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001062 default_target = '--target x86_64-unknown-linux-gnu'
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -07001063 # Use the same target for both host and default device builds.
1064 # Same target is used as default in host x86_64 Android compilation.
1065 # Note: b/169872957, prebuilt cargo failed to build vsock
1066 # on x86_64-unknown-linux-musl systems.
1067 self.cargo = ['clean', 'build ' + default_target]
1068 if args.tests:
1069 self.cargo.append('build --tests ' + default_target)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001070
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001071 def setup_cargo_path(self):
1072 """Find cargo in the --cargo_bin or prebuilt rust bin directory."""
1073 if self.args.cargo_bin:
1074 self.cargo_path = os.path.join(self.args.cargo_bin, 'cargo')
1075 if not os.path.isfile(self.cargo_path):
1076 sys.exit('ERROR: cannot find cargo in ' + self.args.cargo_bin)
1077 print('WARNING: using cargo in ' + self.args.cargo_bin)
1078 return
1079 # We have only tested this on Linux.
1080 if platform.system() != 'Linux':
1081 sys.exit('ERROR: this script has only been tested on Linux with cargo.')
1082 # Assuming that this script is in development/scripts.
1083 my_dir = os.path.dirname(os.path.abspath(__file__))
1084 linux_dir = os.path.join(my_dir, '..', '..',
1085 'prebuilts', 'rust', 'linux-x86')
1086 if not os.path.isdir(linux_dir):
1087 sys.exit('ERROR: cannot find directory ' + linux_dir)
1088 rust_version = self.find_rust_version(my_dir, linux_dir)
1089 cargo_bin = os.path.join(linux_dir, rust_version, 'bin')
1090 self.cargo_path = os.path.join(cargo_bin, 'cargo')
1091 if not os.path.isfile(self.cargo_path):
1092 sys.exit('ERROR: cannot find cargo in ' + cargo_bin
1093 + '; please try --cargo_bin= flag.')
1094 return
1095
1096 def find_rust_version(self, my_dir, linux_dir):
1097 """Use my script directory, find prebuilt rust version."""
1098 # First look up build/soong/rust/config/global.go.
1099 path2global = os.path.join(my_dir, '..', '..',
1100 'build', 'soong', 'rust', 'config', 'global.go')
1101 if os.path.isfile(path2global):
1102 # try to find: RustDefaultVersion = "1.44.0"
1103 version_pat = re.compile(
1104 r'\s*RustDefaultVersion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+)".*$')
1105 with open(path2global, 'r') as inf:
1106 for line in inf:
1107 result = version_pat.match(line)
1108 if result:
1109 return result.group(1)
1110 print('WARNING: cannot find RustDefaultVersion in ' + path2global)
1111 # Otherwise, find the newest (largest) version number in linux_dir.
1112 rust_version = (0, 0, 0) # the prebuilt version to use
1113 version_pat = re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)$')
1114 for dir_name in os.listdir(linux_dir):
1115 result = version_pat.match(dir_name)
1116 if not result:
1117 continue
1118 version = (result.group(1), result.group(2), result.group(3))
1119 if version > rust_version:
1120 rust_version = version
1121 return '.'.join(rust_version)
1122
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001123 def find_out_files(self):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001124 # list1 has build.rs output for normal crates
1125 list1 = glob.glob(TARGET_TMP + '/*/*/build/' + self.root_pkg + '-*/out/*')
1126 # list2 has build.rs output for proc-macro crates
1127 list2 = glob.glob(TARGET_TMP + '/*/build/' + self.root_pkg + '-*/out/*')
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001128 return list1 + list2
1129
1130 def copy_out_files(self):
1131 """Copy build.rs output files to ./out and set up build_out_files."""
1132 if self.checked_out_files:
1133 return
1134 self.checked_out_files = True
1135 cargo_out_files = self.find_out_files()
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001136 out_files = set()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001137 if cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001138 os.makedirs('out', exist_ok=True)
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001139 for path in cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001140 file_name = path.split('/')[-1]
1141 out_files.add(file_name)
1142 shutil.copy(path, 'out/' + file_name)
1143 self.build_out_files = sorted(out_files)
1144
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001145 def has_used_out_dir(self):
1146 """Returns true if env!("OUT_DIR") is found."""
1147 return 0 == os.system('grep -rl --exclude build.rs --include \\*.rs' +
1148 ' \'env!("OUT_DIR")\' * > /dev/null')
1149
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001150 def copy_out_module_name(self):
1151 if self.args.copy_out and self.build_out_files:
1152 return 'copy_' + self.root_pkg + '_build_out'
1153 else:
1154 return ''
1155
Haibo Huang0f72c952021-03-19 11:34:15 -07001156 def read_license(self, name):
1157 if not os.path.isfile(name):
1158 return ''
1159 license = ''
1160 with open(name, 'r') as intf:
1161 line = intf.readline()
1162 # Firstly skip ANDROID_BP_HEADER
1163 while line.startswith('//'):
1164 line = intf.readline()
Joel Galensond9d13b82021-04-05 11:27:55 -07001165 # Read all lines until we see a rust_* or genrule rule.
1166 while line != '' and not (line.startswith('rust_') or line.startswith('genrule {')):
Haibo Huang0f72c952021-03-19 11:34:15 -07001167 license += line
1168 line = intf.readline()
1169 return license.strip()
1170
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001171 def dump_copy_out_module(self, outf):
1172 """Output the genrule module to copy out/* to $(genDir)."""
1173 copy_out = self.copy_out_module_name()
1174 if not copy_out:
1175 return
1176 outf.write('\ngenrule {\n')
1177 outf.write(' name: "' + copy_out + '",\n')
1178 outf.write(' srcs: ["out/*"],\n')
1179 outf.write(' cmd: "cp $(in) $(genDir)",\n')
1180 if len(self.build_out_files) > 1:
1181 outf.write(' out: [\n')
1182 for f in self.build_out_files:
1183 outf.write(' "' + f + '",\n')
1184 outf.write(' ],\n')
1185 else:
1186 outf.write(' out: ["' + self.build_out_files[0] + '"],\n')
1187 outf.write('}\n')
1188
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001189 def init_bp_file(self, name):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001190 # name could be Android.bp or sub_dir_path/Android.bp
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001191 if name not in self.bp_files:
1192 self.bp_files.add(name)
Haibo Huang0f72c952021-03-19 11:34:15 -07001193 license_section = self.read_license(name)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001194 with open(name, 'w') as outf:
Joel Galenson367360c2021-04-29 14:31:43 -07001195 print_args = filter(lambda x: x != "--no-test-mapping", sys.argv[1:])
1196 outf.write(ANDROID_BP_HEADER.format(args=' '.join(print_args)))
Haibo Huang0f72c952021-03-19 11:34:15 -07001197 outf.write('\n')
1198 outf.write(license_section)
1199 outf.write('\n')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001200 # at most one copy_out module per .bp file
1201 self.dump_copy_out_module(outf)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001202
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001203 def try_claim_module_name(self, name, owner):
1204 """Reserve and return True if it has not been reserved yet."""
1205 if name not in self.name_owners or owner == self.name_owners[name]:
1206 self.name_owners[name] = owner
1207 return True
1208 return False
1209
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001210 def claim_module_name(self, prefix, owner, counter):
1211 """Return prefix if not owned yet, otherwise, prefix+str(counter)."""
1212 while True:
1213 name = prefix
1214 if counter > 0:
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001215 name += '_' + str(counter)
1216 if self.try_claim_module_name(name, owner):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001217 return name
1218 counter += 1
1219
1220 def find_root_pkg(self):
1221 """Read name of [package] in ./Cargo.toml."""
1222 if not os.path.exists('./Cargo.toml'):
1223 return
1224 with open('./Cargo.toml', 'r') as inf:
1225 pkg_section = re.compile(r'^ *\[package\]')
1226 name = re.compile('^ *name *= * "([^"]*)"')
1227 in_pkg = False
1228 for line in inf:
1229 if in_pkg:
1230 if name.match(line):
1231 self.root_pkg = name.match(line).group(1)
1232 break
1233 else:
1234 in_pkg = pkg_section.match(line) is not None
1235
1236 def run_cargo(self):
1237 """Calls cargo -v and save its output to ./cargo.out."""
1238 if self.skip_cargo:
1239 return self
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001240 cargo_toml = './Cargo.toml'
1241 cargo_out = './cargo.out'
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001242 # Do not use Cargo.lock, because .bp rules are designed to
1243 # run with "latest" crates avaialable on Android.
1244 cargo_lock = './Cargo.lock'
1245 cargo_lock_saved = './cargo.lock.saved'
1246 had_cargo_lock = os.path.exists(cargo_lock)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001247 if not os.access(cargo_toml, os.R_OK):
1248 print('ERROR: Cannot find or read', cargo_toml)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001249 return self
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001250 if not self.dry_run:
1251 if os.path.exists(cargo_out):
1252 os.remove(cargo_out)
1253 if not self.args.use_cargo_lock and had_cargo_lock: # save it
1254 os.rename(cargo_lock, cargo_lock_saved)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001255 cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> ' + cargo_out + ' 2>&1'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001256 # set up search PATH for cargo to find the correct rustc
1257 saved_path = os.environ['PATH']
1258 os.environ['PATH'] = os.path.dirname(self.cargo_path) + ':' + saved_path
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001259 # Add [workspace] to Cargo.toml if it is not there.
1260 added_workspace = False
1261 if self.args.add_workspace:
1262 with open(cargo_toml, 'r') as in_file:
1263 cargo_toml_lines = in_file.readlines()
1264 found_workspace = '[workspace]\n' in cargo_toml_lines
1265 if found_workspace:
1266 print('### WARNING: found [workspace] in Cargo.toml')
1267 else:
1268 with open(cargo_toml, 'a') as out_file:
1269 out_file.write('[workspace]\n')
1270 added_workspace = True
1271 if self.args.verbose:
1272 print('### INFO: added [workspace] to Cargo.toml')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001273 for c in self.cargo:
1274 features = ''
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001275 if c != 'clean':
1276 if self.args.features is not None:
1277 features = ' --no-default-features'
1278 if self.args.features:
1279 features += ' --features ' + self.args.features
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001280 cmd_v_flag = ' -vv ' if self.args.vv else ' -v '
1281 cmd = self.cargo_path + cmd_v_flag
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001282 cmd += c + features + cmd_tail
1283 if self.args.rustflags and c != 'clean':
1284 cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
1285 if self.dry_run:
1286 print('Dry-run skip:', cmd)
1287 else:
1288 if self.args.verbose:
1289 print('Running:', cmd)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001290 with open(cargo_out, 'a') as out_file:
1291 out_file.write('### Running: ' + cmd + '\n')
Joel Galenson6bf54e32021-05-17 10:54:50 -07001292 ret = os.system(cmd)
1293 if ret != 0:
1294 print('*** There was an error while running cargo. ' +
1295 'See the cargo.out file for details.')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001296 if added_workspace: # restore original Cargo.toml
1297 with open(cargo_toml, 'w') as out_file:
1298 out_file.writelines(cargo_toml_lines)
1299 if self.args.verbose:
1300 print('### INFO: restored original Cargo.toml')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001301 os.environ['PATH'] = saved_path
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001302 if not self.dry_run:
1303 if not had_cargo_lock: # restore to no Cargo.lock state
1304 os.remove(cargo_lock)
1305 elif not self.args.use_cargo_lock: # restore saved Cargo.lock
1306 os.rename(cargo_lock_saved, cargo_lock)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001307 return self
1308
1309 def dump_dependencies(self):
1310 """Append dependencies and their features to Android.bp."""
1311 if not self.dependencies:
1312 return
1313 dependent_list = list()
1314 for c in self.dependencies:
1315 dependent_list.append(c.feature_list())
1316 sorted_dependencies = sorted(set(dependent_list))
1317 self.init_bp_file('Android.bp')
1318 with open('Android.bp', 'a') as outf:
1319 outf.write('\n// dependent_library ["feature_list"]\n')
1320 for s in sorted_dependencies:
1321 outf.write('// ' + s + '\n')
1322
1323 def dump_pkg_obj2cc(self):
1324 """Dump debug info of the pkg_obj2cc map."""
1325 if not self.args.debug:
1326 return
1327 self.init_bp_file('Android.bp')
1328 with open('Android.bp', 'a') as outf:
1329 sorted_pkgs = sorted(self.pkg_obj2cc.keys())
1330 for pkg in sorted_pkgs:
1331 if not self.pkg_obj2cc[pkg]:
1332 continue
1333 outf.write('\n// obj => src for %s\n' % pkg)
1334 obj2cc = self.pkg_obj2cc[pkg]
1335 for obj in sorted(obj2cc.keys()):
1336 outf.write('// ' + short_out_name(pkg, obj) + ' => ' +
1337 short_out_name(pkg, obj2cc[obj].src) + '\n')
1338
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001339 def apply_patch(self):
1340 """Apply local patch file if it is given."""
1341 if self.args.patch:
1342 if self.dry_run:
1343 print('Dry-run skip patch file:', self.args.patch)
1344 else:
1345 if not os.path.exists(self.args.patch):
1346 self.append_to_bp('ERROR cannot find patch file: ' + self.args.patch)
1347 return self
1348 if self.args.verbose:
1349 print('### INFO: applying local patch file:', self.args.patch)
Joel Galenson7e8247e2021-05-20 18:51:42 -07001350 subprocess.run(['patch', '-s', '--no-backup-if-mismatch', './Android.bp',
1351 self.args.patch], check=True)
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001352 return self
1353
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001354 def gen_bp(self):
1355 """Parse cargo.out and generate Android.bp files."""
1356 if self.dry_run:
1357 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp')
1358 elif os.path.exists(CARGO_OUT):
1359 self.find_root_pkg()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001360 if self.args.copy_out:
1361 self.copy_out_files()
1362 elif self.find_out_files() and self.has_used_out_dir():
1363 print('WARNING: ' + self.root_pkg + ' has cargo output files; ' +
1364 'please rerun with the --copy-out flag.')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001365 with open(CARGO_OUT, 'r') as cargo_out:
1366 self.parse(cargo_out, 'Android.bp')
1367 self.crates.sort(key=get_module_name)
1368 for obj in self.cc_objects:
1369 obj.dump()
1370 self.dump_pkg_obj2cc()
1371 for crate in self.crates:
1372 crate.dump()
1373 dumped_libs = set()
1374 for lib in self.ar_objects:
1375 if lib.pkg == self.root_pkg:
1376 lib_name = file_base_name(lib.lib)
1377 if lib_name not in dumped_libs:
1378 dumped_libs.add(lib_name)
1379 lib.dump()
Joel Galenson5664f2a2021-06-10 10:13:49 -07001380 if self.args.add_toplevel_block:
1381 with open(self.args.add_toplevel_block, 'r') as f:
1382 self.append_to_bp('\n' + f.read() + '\n')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001383 if self.args.dependencies and self.dependencies:
1384 self.dump_dependencies()
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001385 if self.errors:
Joel Galenson3f42f802021-04-07 12:42:17 -07001386 self.append_to_bp('\n' + ERRORS_LINE + '\n' + self.errors)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001387 return self
1388
1389 def add_ar_object(self, obj):
1390 self.ar_objects.append(obj)
1391
1392 def add_cc_object(self, obj):
1393 self.cc_objects.append(obj)
1394
1395 def add_crate(self, crate):
1396 """Merge crate with someone in crates, or append to it. Return crates."""
1397 if crate.skip_crate():
1398 if self.args.debug: # include debug info of all crates
1399 self.crates.append(crate)
1400 if self.args.dependencies: # include only dependent crates
1401 if (is_dependent_file_path(crate.main_src) and
1402 not is_build_crate_name(crate.crate_name)):
1403 self.dependencies.append(crate)
1404 else:
1405 for c in self.crates:
1406 if c.merge(crate, 'Android.bp'):
1407 return
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001408 # If not merged, decide module type and name now.
1409 crate.decide_module_type()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001410 self.crates.append(crate)
1411
1412 def find_warning_owners(self):
1413 """For each warning file, find its owner crate."""
1414 missing_owner = False
1415 for f in self.warning_files:
1416 cargo_dir = '' # find lowest crate, with longest path
1417 owner = None # owner crate of this warning
1418 for c in self.crates:
1419 if (f.startswith(c.cargo_dir + '/') and
1420 len(cargo_dir) < len(c.cargo_dir)):
1421 cargo_dir = c.cargo_dir
1422 owner = c
1423 if owner:
1424 owner.has_warning = True
1425 else:
1426 missing_owner = True
1427 if missing_owner and os.path.exists('Cargo.toml'):
1428 # owner is the root cargo, with empty cargo_dir
1429 for c in self.crates:
1430 if not c.cargo_dir:
1431 c.has_warning = True
1432
1433 def rustc_command(self, n, rustc_line, line, outf_name):
1434 """Process a rustc command line from cargo -vv output."""
1435 # cargo build -vv output can have multiple lines for a rustc command
1436 # due to '\n' in strings for environment variables.
1437 # strip removes leading spaces and '\n' at the end
1438 new_rustc = (rustc_line.strip() + line) if rustc_line else line
1439 # Use an heuristic to detect the completions of a multi-line command.
1440 # This might fail for some very rare case, but easy to fix manually.
1441 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0:
1442 return new_rustc
1443 if RUSTC_VV_CMD_ARGS.match(new_rustc):
1444 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1)
1445 self.add_crate(Crate(self, outf_name).parse(n, args))
1446 else:
1447 self.assert_empty_vv_line(new_rustc)
1448 return ''
1449
1450 def cc_ar_command(self, n, groups, outf_name):
1451 pkg = groups.group(1)
1452 line = groups.group(3)
1453 if groups.group(2) == 'cc':
1454 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line))
1455 else:
1456 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line))
1457
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001458 def append_to_bp(self, line):
1459 self.init_bp_file('Android.bp')
1460 with open('Android.bp', 'a') as outf:
1461 outf.write(line)
1462
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001463 def assert_empty_vv_line(self, line):
1464 if line: # report error if line is not empty
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001465 self.append_to_bp('ERROR -vv line: ' + line)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001466 return ''
1467
1468 def parse(self, inf, outf_name):
1469 """Parse rustc and warning messages in inf, return a list of Crates."""
1470 n = 0 # line number
1471 prev_warning = False # true if the previous line was warning: ...
1472 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
1473 for line in inf:
1474 n += 1
1475 if line.startswith('warning: '):
1476 prev_warning = True
1477 rustc_line = self.assert_empty_vv_line(rustc_line)
1478 continue
1479 new_rustc = ''
1480 if RUSTC_PAT.match(line):
1481 args_line = RUSTC_PAT.match(line).group(1)
1482 self.add_crate(Crate(self, outf_name).parse(n, args_line))
1483 self.assert_empty_vv_line(rustc_line)
1484 elif rustc_line or RUSTC_VV_PAT.match(line):
1485 new_rustc = self.rustc_command(n, rustc_line, line, outf_name)
1486 elif CC_AR_VV_PAT.match(line):
1487 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name)
1488 elif prev_warning and WARNING_FILE_PAT.match(line):
1489 self.assert_empty_vv_line(rustc_line)
1490 fpath = WARNING_FILE_PAT.match(line).group(1)
1491 if fpath[0] != '/': # ignore absolute path
1492 self.warning_files.add(fpath)
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001493 elif line.startswith('error: ') or line.startswith('error[E'):
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001494 if not self.args.ignore_cargo_errors:
1495 self.errors += line
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001496 prev_warning = False
1497 rustc_line = new_rustc
1498 self.find_warning_owners()
1499
1500
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001501def get_parser():
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001502 """Parse main arguments."""
1503 parser = argparse.ArgumentParser('cargo2android')
1504 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001505 '--add_workspace',
1506 action='store_true',
1507 default=False,
1508 help=('append [workspace] to Cargo.toml before calling cargo,' +
1509 ' to treat current directory as root of package source;' +
1510 ' otherwise the relative source file path in generated' +
1511 ' .bp file will be from the parent directory.'))
1512 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001513 '--cargo',
1514 action='append',
1515 metavar='args_string',
1516 help=('extra cargo build -v args in a string, ' +
1517 'each --cargo flag calls cargo build -v once'))
1518 parser.add_argument(
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001519 '--cargo_bin',
1520 type=str,
1521 help='use cargo in the cargo_bin directory instead of the prebuilt one')
1522 parser.add_argument(
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001523 '--copy-out',
1524 action='store_true',
1525 default=False,
1526 help=('only for root directory, ' +
1527 'copy build.rs output to ./out/* and add a genrule to copy ' +
1528 './out/* to genrule output; for crates with code pattern: ' +
1529 'include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))'))
1530 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001531 '--debug',
1532 action='store_true',
1533 default=False,
1534 help='dump debug info into Android.bp')
1535 parser.add_argument(
1536 '--dependencies',
1537 action='store_true',
1538 default=False,
1539 help='dump debug info of dependent crates')
1540 parser.add_argument(
1541 '--device',
1542 action='store_true',
1543 default=False,
1544 help='run cargo also for a default device target')
1545 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001546 '--features',
1547 type=str,
1548 help=('pass features to cargo build, ' +
1549 'empty string means no default features'))
1550 parser.add_argument(
1551 '--global_defaults',
1552 type=str,
1553 help='add a defaults name to every module')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001554 parser.add_argument(
1555 '--host-first-multilib',
1556 action='store_true',
1557 default=False,
1558 help=('add a compile_multilib:"first" property ' +
1559 'to Android.bp host modules.'))
1560 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001561 '--ignore-cargo-errors',
1562 action='store_true',
1563 default=False,
1564 help='do not append cargo/rustc error messages to Android.bp')
1565 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001566 '--no-host',
1567 action='store_true',
1568 default=False,
1569 help='do not run cargo for the host; only for the device target')
1570 parser.add_argument(
1571 '--no-subdir',
1572 action='store_true',
1573 default=False,
1574 help='do not output anything for sub-directories')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001575 parser.add_argument(
1576 '--onefile',
1577 action='store_true',
1578 default=False,
1579 help=('output all into one ./Android.bp, default will generate ' +
1580 'one Android.bp per Cargo.toml in subdirectories'))
1581 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001582 '--patch',
1583 type=str,
1584 help='apply the given patch file to generated ./Android.bp')
1585 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001586 '--run',
1587 action='store_true',
1588 default=False,
1589 help='run it, default is dry-run')
1590 parser.add_argument('--rustflags', type=str, help='passing flags to rustc')
1591 parser.add_argument(
1592 '--skipcargo',
1593 action='store_true',
1594 default=False,
1595 help='skip cargo command, parse cargo.out, and generate Android.bp')
1596 parser.add_argument(
1597 '--tests',
1598 action='store_true',
1599 default=False,
1600 help='run cargo build --tests after normal build')
1601 parser.add_argument(
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001602 '--use-cargo-lock',
1603 action='store_true',
1604 default=False,
1605 help=('run cargo build with existing Cargo.lock ' +
1606 '(used when some latest dependent crates failed)'))
1607 parser.add_argument(
Joel Galensond9c4de62021-04-23 10:26:40 -07001608 '--min-sdk-version',
1609 type=str,
1610 help='Minimum SDK version')
1611 parser.add_argument(
1612 '--apex-available',
1613 nargs='*',
1614 help='Mark the main library as apex_available with the given apexes.')
1615 parser.add_argument(
Ivan Lozano91920862021-07-19 10:49:08 -04001616 '--vendor-available',
1617 action='store_true',
1618 default=False,
1619 help='Mark the main library as vendor_available.')
1620 parser.add_argument(
1621 '--vendor-ramdisk-available',
1622 action='store_true',
1623 default=False,
1624 help='Mark the main library as vendor_ramdisk_available.')
1625 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001626 '--force-rlib',
1627 action='store_true',
1628 default=False,
1629 help='Make the main library an rlib.')
1630 parser.add_argument(
Joel Galenson12467e52021-07-12 14:33:28 -07001631 '--whole-static-libs',
1632 nargs='*',
1633 default=[],
1634 help='Make the given libraries (without lib prefixes) whole_static_libs.')
1635 parser.add_argument(
Joel Galenson97e414a2021-05-27 09:42:32 -07001636 '--dependency-blocklist',
1637 nargs='*',
1638 default=[],
Joel Galenson12467e52021-07-12 14:33:28 -07001639 help='Do not emit the given dependencies (without lib prefixes).')
Joel Galenson97e414a2021-05-27 09:42:32 -07001640 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001641 '--lib-blocklist',
1642 nargs='*',
1643 default=[],
Joel Galenson12467e52021-07-12 14:33:28 -07001644 help='Do not emit the given C libraries as dependencies (without lib prefixes).')
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001645 parser.add_argument(
Joel Galensonf6b3c912021-06-03 16:00:54 -07001646 '--test-blocklist',
1647 nargs='*',
1648 default=[],
1649 help=('Do not emit the given tests. ' +
1650 'Pass the path to the test file to exclude.'))
1651 parser.add_argument(
Joel Galenson3d6d1e72021-06-07 15:00:24 -07001652 '--cfg-blocklist',
1653 nargs='*',
1654 default=[],
1655 help='Do not emit the given cfg.')
1656 parser.add_argument(
Joel Galenson5664f2a2021-06-10 10:13:49 -07001657 '--add-toplevel-block',
1658 type=str,
1659 help='Add the contents of the given file to the top level of the Android.bp.')
1660 parser.add_argument(
1661 '--add-module-block',
1662 type=str,
1663 help='Add the contents of the given file to the main module.')
1664 parser.add_argument(
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001665 '--no-test-mapping',
1666 action='store_true',
1667 default=False,
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001668 help='Deprecated. Has no effect.')
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001669 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001670 '--verbose',
1671 action='store_true',
1672 default=False,
1673 help='echo executed commands')
1674 parser.add_argument(
1675 '--vv',
1676 action='store_true',
1677 default=False,
1678 help='run cargo with -vv instead of default -v')
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001679 parser.add_argument(
1680 '--dump-config-and-exit',
1681 type=str,
1682 help=('Dump command-line arguments (minus this flag) to a config file and exit. ' +
1683 'This is intended to help migrate from command line options to config files.'))
1684 parser.add_argument(
1685 '--config',
1686 type=str,
1687 help=('Load command-line options from the given config file. ' +
1688 'Options in this file will override those passed on the command line.'))
1689 return parser
1690
1691
1692def parse_args(parser):
1693 """Parses command-line options."""
1694 args = parser.parse_args()
1695 # Use the values specified in a config file if one was found.
1696 if args.config:
1697 with open(args.config, 'r') as f:
1698 config = json.load(f)
1699 args_dict = vars(args)
1700 for arg in config:
1701 args_dict[arg.replace('-', '_')] = config[arg]
1702 return args
1703
1704
1705def dump_config(parser, args):
1706 """Writes the non-default command-line options to the specified file."""
1707 args_dict = vars(args)
1708 # Filter out the arguments that have their default value.
Joel Galenson367360c2021-04-29 14:31:43 -07001709 # Also filter certain "temporary" arguments.
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001710 non_default_args = {}
1711 for arg in args_dict:
Joel Galenson367360c2021-04-29 14:31:43 -07001712 if args_dict[arg] != parser.get_default(
1713 arg) and arg != 'dump_config_and_exit' and arg != 'no_test_mapping':
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001714 non_default_args[arg.replace('_', '-')] = args_dict[arg]
1715 # Write to the specified file.
1716 with open(args.dump_config_and_exit, 'w') as f:
1717 json.dump(non_default_args, f, indent=2, sort_keys=True)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001718
1719
1720def main():
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001721 parser = get_parser()
1722 args = parse_args(parser)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001723 if not args.run: # default is dry-run
1724 print(DRY_RUN_NOTE)
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001725 if args.dump_config_and_exit:
1726 dump_config(parser, args)
1727 else:
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001728 Runner(args).run_cargo().gen_bp().apply_patch()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001729
1730
1731if __name__ == '__main__':
1732 main()