blob: 64af8455c30da3ecf83073b9595a8335ce27d878 [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
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +010042 Note that when there are tests for this module or for its reverse
43 dependencies, these tests will be added to the TEST_MAPPING file.
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -070044
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -070045If there are rustc warning messages, this script will add
46a warning comment to the owner crate module in Android.bp.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080047"""
48
49from __future__ import print_function
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +010050from update_crate_tests import TestMapping
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080051
52import argparse
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -070053import glob
Joel Galenson0fbdafe2021-04-21 16:33:33 -070054import json
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080055import os
56import os.path
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -070057import platform
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080058import re
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -070059import shutil
Joel Galenson7e8247e2021-05-20 18:51:42 -070060import subprocess
Andrew Walbran80e90be2020-06-09 14:33:18 +010061import sys
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080062
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -070063# Some Rust packages include extra unwanted crates.
64# This set contains all such excluded crate names.
65EXCLUDED_CRATES = set(['protobuf_bin_gen_rust_do_not_use'])
66
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080067RENAME_MAP = {
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -070068 # This map includes all changes to the default rust module names
69 # to resolve name conflicts, avoid confusion, or work as plugin.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080070 'libbacktrace': 'libbacktrace_rust',
Andrew Walbrane51f1042020-08-11 16:42:48 +010071 'libbase': 'libbase_rust',
Victor Hsieh21bea792020-12-04 10:59:16 -080072 'libfuse': 'libfuse_rust',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080073 'libgcc': 'libgcc_rust',
74 'liblog': 'liblog_rust',
Chih-Hung Hsieh07119862020-07-24 15:34:06 -070075 'libminijail': 'libminijail_rust',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080076 'libsync': 'libsync_rust',
77 'libx86_64': 'libx86_64_rust',
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -070078 'protoc_gen_rust': 'protoc-gen-rust',
79}
80
81RENAME_STEM_MAP = {
82 # This map includes all changes to the default rust module stem names,
83 # which is used for output files when different from the module name.
84 'protoc_gen_rust': 'protoc-gen-rust',
85}
86
87RENAME_DEFAULTS_MAP = {
88 # This map includes all changes to the default prefix of rust_default
89 # module names, to avoid conflict with existing Android modules.
90 'libc': 'rust_libc',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080091}
92
93# Header added to all generated Android.bp files.
Joel Galenson56446742021-02-18 08:27:48 -080094ANDROID_BP_HEADER = (
95 '// This file is generated by cargo2android.py {args}.\n' +
96 '// Do not modify this file as changes will be overridden on upgrade.\n')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080097
98CARGO_OUT = 'cargo.out' # Name of file to keep cargo build -v output.
99
Joel Galenson3f42f802021-04-07 12:42:17 -0700100# This should be kept in sync with tools/external_updater/crates_updater.py.
101ERRORS_LINE = 'Errors in ' + CARGO_OUT + ':'
102
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800103TARGET_TMP = 'target.tmp' # Name of temporary output directory.
104
105# Message to be displayed when this script is called without the --run flag.
106DRY_RUN_NOTE = (
107 'Dry-run: This script uses ./' + TARGET_TMP + ' for output directory,\n' +
108 'runs cargo clean, runs cargo build -v, saves output to ./cargo.out,\n' +
109 'and writes to Android.bp in the current and subdirectories.\n\n' +
110 'To do do all of the above, use the --run flag.\n' +
111 'See --help for other flags, and more usage notes in this script.\n')
112
113# Cargo -v output of a call to rustc.
114RUSTC_PAT = re.compile('^ +Running `rustc (.*)`$')
115
116# Cargo -vv output of a call to rustc could be split into multiple lines.
117# Assume that the first line will contain some CARGO_* env definition.
118RUSTC_VV_PAT = re.compile('^ +Running `.*CARGO_.*=.*$')
119# The combined -vv output rustc command line pattern.
120RUSTC_VV_CMD_ARGS = re.compile('^ *Running `.*CARGO_.*=.* rustc (.*)`$')
121
122# Cargo -vv output of a "cc" or "ar" command; all in one line.
123CC_AR_VV_PAT = re.compile(r'^\[([^ ]*)[^\]]*\] running:? "(cc|ar)" (.*)$')
124# Some package, such as ring-0.13.5, has pattern '... running "cc"'.
125
126# Rustc output of file location path pattern for a warning message.
127WARNING_FILE_PAT = re.compile('^ *--> ([^:]*):[0-9]+')
128
129# Rust package name with suffix -d1.d2.d3.
130VERSION_SUFFIX_PAT = re.compile(r'^(.*)-[0-9]+\.[0-9]+\.[0-9]+$')
131
132
133def altered_name(name):
134 return RENAME_MAP[name] if (name in RENAME_MAP) else name
135
136
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700137def altered_stem(name):
138 return RENAME_STEM_MAP[name] if (name in RENAME_STEM_MAP) else name
139
140
141def altered_defaults(name):
142 return RENAME_DEFAULTS_MAP[name] if (name in RENAME_DEFAULTS_MAP) else name
143
144
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800145def is_build_crate_name(name):
146 # We added special prefix to build script crate names.
147 return name.startswith('build_script_')
148
149
150def is_dependent_file_path(path):
151 # Absolute or dependent '.../' paths are not main files of this crate.
152 return path.startswith('/') or path.startswith('.../')
153
154
155def get_module_name(crate): # to sort crates in a list
156 return crate.module_name
157
158
159def pkg2crate_name(s):
160 return s.replace('-', '_').replace('.', '_')
161
162
163def file_base_name(path):
164 return os.path.splitext(os.path.basename(path))[0]
165
166
167def test_base_name(path):
168 return pkg2crate_name(file_base_name(path))
169
170
171def unquote(s): # remove quotes around str
172 if s and len(s) > 1 and s[0] == '"' and s[-1] == '"':
173 return s[1:-1]
174 return s
175
176
177def remove_version_suffix(s): # remove -d1.d2.d3 suffix
178 if VERSION_SUFFIX_PAT.match(s):
179 return VERSION_SUFFIX_PAT.match(s).group(1)
180 return s
181
182
183def short_out_name(pkg, s): # replace /.../pkg-*/out/* with .../out/*
184 return re.sub('^/.*/' + pkg + '-[0-9a-f]*/out/', '.../out/', s)
185
186
187def escape_quotes(s): # replace '"' with '\\"'
188 return s.replace('"', '\\"')
189
190
191class Crate(object):
192 """Information of a Rust crate to collect/emit for an Android.bp module."""
193
194 def __init__(self, runner, outf_name):
195 # Remembered global runner and its members.
196 self.runner = runner
197 self.debug = runner.args.debug
198 self.cargo_dir = '' # directory of my Cargo.toml
199 self.outf_name = outf_name # path to Android.bp
200 self.outf = None # open file handle of outf_name during dump*
201 # Variants/results that could be merged from multiple rustc lines.
202 self.host_supported = False
203 self.device_supported = False
204 self.has_warning = False
205 # Android module properties derived from rustc parameters.
206 self.module_name = '' # unique in Android build system
207 self.module_type = '' # rust_{binary,library,test}[_host] etc.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700208 self.defaults = '' # rust_defaults used by rust_test* modules
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700209 self.default_srcs = False # use 'srcs' defined in self.defaults
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800210 self.root_pkg = '' # parent package name of a sub/test packge, from -L
211 self.srcs = list() # main_src or merged multiple source files
212 self.stem = '' # real base name of output file
213 # Kept parsed status
214 self.errors = '' # all errors found during parsing
215 self.line_num = 1 # runner told input source line number
216 self.line = '' # original rustc command line parameters
217 # Parameters collected from rustc command line.
218 self.crate_name = '' # follows --crate-name
219 self.main_src = '' # follows crate_name parameter, shortened
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700220 self.crate_types = list() # follows --crate-type
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800221 self.cfgs = list() # follows --cfg, without feature= prefix
222 self.features = list() # follows --cfg, name in 'feature="..."'
223 self.codegens = list() # follows -C, some ignored
224 self.externs = list() # follows --extern
225 self.core_externs = list() # first part of self.externs elements
226 self.static_libs = list() # e.g. -l static=host_cpuid
227 self.shared_libs = list() # e.g. -l dylib=wayland-client, -l z
228 self.cap_lints = '' # follows --cap-lints
229 self.emit_list = '' # e.g., --emit=dep-info,metadata,link
230 self.edition = '2015' # rustc default, e.g., --edition=2018
231 self.target = '' # follows --target
232
233 def write(self, s):
234 # convenient way to output one line at a time with EOL.
235 self.outf.write(s + '\n')
236
237 def same_flags(self, other):
238 # host_supported, device_supported, has_warning are not compared but merged
239 # target is not compared, to merge different target/host modules
240 # externs is not compared; only core_externs is compared
241 return (not self.errors and not other.errors and
242 self.edition == other.edition and
243 self.cap_lints == other.cap_lints and
244 self.emit_list == other.emit_list and
245 self.core_externs == other.core_externs and
246 self.codegens == other.codegens and
247 self.features == other.features and
248 self.static_libs == other.static_libs and
249 self.shared_libs == other.shared_libs and self.cfgs == other.cfgs)
250
251 def merge_host_device(self, other):
252 """Returns true if attributes are the same except host/device support."""
253 return (self.crate_name == other.crate_name and
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700254 self.crate_types == other.crate_types and
255 self.main_src == other.main_src and
256 # before merge, each test module has an unique module name and stem
257 (self.stem == other.stem or self.crate_types == ['test']) and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800258 self.root_pkg == other.root_pkg and not self.skip_crate() and
259 self.same_flags(other))
260
261 def merge_test(self, other):
262 """Returns true if self and other are tests of same root_pkg."""
263 # Before merger, each test has its own crate_name.
264 # A merged test uses its source file base name as output file name,
265 # so a test is mergeable only if its base name equals to its crate name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700266 return (self.crate_types == other.crate_types and
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700267 self.crate_types == ['test'] and self.root_pkg == other.root_pkg and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800268 not self.skip_crate() and
269 other.crate_name == test_base_name(other.main_src) and
270 (len(self.srcs) > 1 or
271 (self.crate_name == test_base_name(self.main_src)) and
272 self.host_supported == other.host_supported and
273 self.device_supported == other.device_supported) and
274 self.same_flags(other))
275
276 def merge(self, other, outf_name):
277 """Try to merge crate into self."""
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700278 # Cargo build --tests could recompile a library for tests.
279 # We need to merge such duplicated calls to rustc, with
280 # the algorithm in merge_host_device.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800281 should_merge_host_device = self.merge_host_device(other)
282 should_merge_test = False
283 if not should_merge_host_device:
284 should_merge_test = self.merge_test(other)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800285 if should_merge_host_device or should_merge_test:
286 self.runner.init_bp_file(outf_name)
287 with open(outf_name, 'a') as outf: # to write debug info
288 self.outf = outf
289 other.outf = outf
290 self.do_merge(other, should_merge_test)
291 return True
292 return False
293
294 def do_merge(self, other, should_merge_test):
295 """Merge attributes of other to self."""
296 if self.debug:
297 self.write('\n// Before merge definition (1):')
298 self.dump_debug_info()
299 self.write('\n// Before merge definition (2):')
300 other.dump_debug_info()
301 # Merge properties of other to self.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800302 self.has_warning = self.has_warning or other.has_warning
303 if not self.target: # okay to keep only the first target triple
304 self.target = other.target
305 # decide_module_type sets up default self.stem,
306 # which can be changed if self is a merged test module.
307 self.decide_module_type()
308 if should_merge_test:
309 self.srcs.append(other.main_src)
310 # use a short unique name as the merged module name.
311 prefix = self.root_pkg + '_tests'
312 self.module_name = self.runner.claim_module_name(prefix, self, 0)
313 self.stem = self.module_name
314 # This normalized root_pkg name although might be the same
315 # as other module's crate_name, it is not actually used for
316 # output file name. A merged test module always have multiple
317 # source files and each source file base name is used as
318 # its output file name.
319 self.crate_name = pkg2crate_name(self.root_pkg)
320 if self.debug:
321 self.write('\n// After merge definition (1):')
322 self.dump_debug_info()
323
324 def find_cargo_dir(self):
325 """Deepest directory with Cargo.toml and contains the main_src."""
326 if not is_dependent_file_path(self.main_src):
327 dir_name = os.path.dirname(self.main_src)
328 while dir_name:
329 if os.path.exists(dir_name + '/Cargo.toml'):
330 self.cargo_dir = dir_name
331 return
332 dir_name = os.path.dirname(dir_name)
333
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700334 def add_codegens_flag(self, flag):
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700335 """Ignore options not used in Android."""
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700336 # 'prefer-dynamic' does not work with common flag -C lto
Chih-Hung Hsieh63459ed2020-08-26 11:51:15 -0700337 # 'embed-bitcode' is ignored; we might control LTO with other .bp flag
Chih-Hung Hsieh6c13b722020-09-11 21:24:03 -0700338 # 'codegen-units' is set in Android global config or by default
339 if not (flag.startswith('codegen-units=') or
340 flag.startswith('debuginfo=') or
Chih-Hung Hsieh63459ed2020-08-26 11:51:15 -0700341 flag.startswith('embed-bitcode=') or
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700342 flag.startswith('extra-filename=') or
343 flag.startswith('incremental=') or
344 flag.startswith('metadata=') or
345 flag == 'prefer-dynamic'):
346 self.codegens.append(flag)
347
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800348 def parse(self, line_num, line):
349 """Find important rustc arguments to convert to Android.bp properties."""
350 self.line_num = line_num
351 self.line = line
352 args = line.split() # Loop through every argument of rustc.
353 i = 0
354 while i < len(args):
355 arg = args[i]
356 if arg == '--crate-name':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700357 i += 1
358 self.crate_name = args[i]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800359 elif arg == '--crate-type':
360 i += 1
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700361 # cargo calls rustc with multiple --crate-type flags.
362 # rustc can accept:
363 # --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
364 self.crate_types.append(args[i])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800365 elif arg == '--test':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700366 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800367 elif arg == '--target':
368 i += 1
369 self.target = args[i]
370 elif arg == '--cfg':
371 i += 1
372 if args[i].startswith('\'feature='):
373 self.features.append(unquote(args[i].replace('\'feature=', '')[:-1]))
374 else:
375 self.cfgs.append(args[i])
376 elif arg == '--extern':
377 i += 1
378 extern_names = re.sub('=/[^ ]*/deps/', ' = ', args[i])
379 self.externs.append(extern_names)
380 self.core_externs.append(re.sub(' = .*', '', extern_names))
381 elif arg == '-C': # codegen options
382 i += 1
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700383 self.add_codegens_flag(args[i])
384 elif arg.startswith('-C'):
385 # cargo has been passing "-C <xyz>" flag to rustc,
386 # but newer cargo could pass '-Cembed-bitcode=no' to rustc.
387 self.add_codegens_flag(arg[2:])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800388 elif arg == '--cap-lints':
389 i += 1
390 self.cap_lints = args[i]
391 elif arg == '-L':
392 i += 1
393 if args[i].startswith('dependency=') and args[i].endswith('/deps'):
394 if '/' + TARGET_TMP + '/' in args[i]:
395 self.root_pkg = re.sub(
396 '^.*/', '', re.sub('/' + TARGET_TMP + '/.*/deps$', '', args[i]))
397 else:
398 self.root_pkg = re.sub('^.*/', '',
399 re.sub('/[^/]+/[^/]+/deps$', '', args[i]))
400 self.root_pkg = remove_version_suffix(self.root_pkg)
401 elif arg == '-l':
402 i += 1
403 if args[i].startswith('static='):
404 self.static_libs.append(re.sub('static=', '', args[i]))
405 elif args[i].startswith('dylib='):
406 self.shared_libs.append(re.sub('dylib=', '', args[i]))
407 else:
408 self.shared_libs.append(args[i])
409 elif arg == '--out-dir' or arg == '--color': # ignored
410 i += 1
411 elif arg.startswith('--error-format=') or arg.startswith('--json='):
412 _ = arg # ignored
413 elif arg.startswith('--emit='):
414 self.emit_list = arg.replace('--emit=', '')
415 elif arg.startswith('--edition='):
416 self.edition = arg.replace('--edition=', '')
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700417 elif not arg.startswith('-'):
418 # shorten imported crate main source paths like $HOME/.cargo/
419 # registry/src/github.com-1ecc6299db9ec823/memchr-2.3.3/src/lib.rs
420 self.main_src = re.sub(r'^/[^ ]*/registry/src/', '.../', args[i])
421 self.main_src = re.sub(r'^\.\.\./github.com-[0-9a-f]*/', '.../',
422 self.main_src)
423 self.find_cargo_dir()
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700424 if self.cargo_dir: # for a subdirectory
425 if self.runner.args.no_subdir: # all .bp content to /dev/null
426 self.outf_name = '/dev/null'
427 elif not self.runner.args.onefile:
428 # Write to Android.bp in the subdirectory with Cargo.toml.
429 self.outf_name = self.cargo_dir + '/Android.bp'
430 self.main_src = self.main_src[len(self.cargo_dir) + 1:]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800431 else:
432 self.errors += 'ERROR: unknown ' + arg + '\n'
433 i += 1
434 if not self.crate_name:
435 self.errors += 'ERROR: missing --crate-name\n'
436 if not self.main_src:
437 self.errors += 'ERROR: missing main source file\n'
438 else:
439 self.srcs.append(self.main_src)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700440 if not self.crate_types:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800441 # Treat "--cfg test" as "--test"
442 if 'test' in self.cfgs:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700443 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800444 else:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700445 self.errors += 'ERROR: missing --crate-type or --test\n'
446 elif len(self.crate_types) > 1:
447 if 'test' in self.crate_types:
448 self.errors += 'ERROR: cannot handle both --crate-type and --test\n'
449 if 'lib' in self.crate_types and 'rlib' in self.crate_types:
450 self.errors += 'ERROR: cannot generate both lib and rlib crate types\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800451 if not self.root_pkg:
452 self.root_pkg = self.crate_name
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700453 self.device_supported = self.runner.args.device
454 self.host_supported = not self.runner.args.no_host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800455 self.cfgs = sorted(set(self.cfgs))
456 self.features = sorted(set(self.features))
457 self.codegens = sorted(set(self.codegens))
458 self.externs = sorted(set(self.externs))
459 self.core_externs = sorted(set(self.core_externs))
460 self.static_libs = sorted(set(self.static_libs))
461 self.shared_libs = sorted(set(self.shared_libs))
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700462 self.crate_types = sorted(set(self.crate_types))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800463 self.decide_module_type()
464 self.module_name = altered_name(self.stem)
465 return self
466
467 def dump_line(self):
468 self.write('\n// Line ' + str(self.line_num) + ' ' + self.line)
469
470 def feature_list(self):
471 """Return a string of main_src + "feature_list"."""
472 pkg = self.main_src
473 if pkg.startswith('.../'): # keep only the main package name
474 pkg = re.sub('/.*', '', pkg[4:])
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700475 elif pkg.startswith('/'): # use relative path for a local package
476 pkg = os.path.relpath(pkg)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800477 if not self.features:
478 return pkg
479 return pkg + ' "' + ','.join(self.features) + '"'
480
481 def dump_skip_crate(self, kind):
482 if self.debug:
483 self.write('\n// IGNORED: ' + kind + ' ' + self.main_src)
484 return self
485
486 def skip_crate(self):
487 """Return crate_name or a message if this crate should be skipped."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700488 if (is_build_crate_name(self.crate_name) or
489 self.crate_name in EXCLUDED_CRATES):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800490 return self.crate_name
491 if is_dependent_file_path(self.main_src):
492 return 'dependent crate'
493 return ''
494
495 def dump(self):
496 """Dump all error/debug/module code to the output .bp file."""
497 self.runner.init_bp_file(self.outf_name)
498 with open(self.outf_name, 'a') as outf:
499 self.outf = outf
500 if self.errors:
501 self.dump_line()
502 self.write(self.errors)
503 elif self.skip_crate():
504 self.dump_skip_crate(self.skip_crate())
505 else:
506 if self.debug:
507 self.dump_debug_info()
508 self.dump_android_module()
509
510 def dump_debug_info(self):
511 """Dump parsed data, when cargo2android is called with --debug."""
512
513 def dump(name, value):
514 self.write('//%12s = %s' % (name, value))
515
516 def opt_dump(name, value):
517 if value:
518 dump(name, value)
519
520 def dump_list(fmt, values):
521 for v in values:
522 self.write(fmt % v)
523
524 self.dump_line()
525 dump('module_name', self.module_name)
526 dump('crate_name', self.crate_name)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700527 dump('crate_types', self.crate_types)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800528 dump('main_src', self.main_src)
529 dump('has_warning', self.has_warning)
530 dump('for_host', self.host_supported)
531 dump('for_device', self.device_supported)
532 dump('module_type', self.module_type)
533 opt_dump('target', self.target)
534 opt_dump('edition', self.edition)
535 opt_dump('emit_list', self.emit_list)
536 opt_dump('cap_lints', self.cap_lints)
537 dump_list('// cfg = %s', self.cfgs)
538 dump_list('// cfg = \'feature "%s"\'', self.features)
539 # TODO(chh): escape quotes in self.features, but not in other dump_list
540 dump_list('// codegen = %s', self.codegens)
541 dump_list('// externs = %s', self.externs)
542 dump_list('// -l static = %s', self.static_libs)
543 dump_list('// -l (dylib) = %s', self.shared_libs)
544
545 def dump_android_module(self):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700546 """Dump one or more Android module definition, depending on crate_types."""
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700547 if len(self.crate_types) == 1:
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700548 self.dump_single_type_android_module()
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700549 return
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700550 if 'test' in self.crate_types:
551 self.write('\nERROR: multiple crate types cannot include test type')
552 return
553 # Dump one Android module per crate_type.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700554 for crate_type in self.crate_types:
555 self.decide_one_module_type(crate_type)
556 self.dump_one_android_module(crate_type)
557
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700558 def build_default_name(self):
559 """Return a short and readable name for the rust_defaults module."""
560 # Choices: (1) root_pkg + '_defaults',
561 # (2) root_pkg + '_defaults_' + crate_name
562 # (3) root_pkg + '_defaults_' + main_src_basename_path
563 # (4) root_pkg + '_defaults_' + a_positive_sequence_number
564 name1 = altered_defaults(self.root_pkg) + '_defaults'
565 if self.runner.try_claim_module_name(name1, self):
566 return name1
567 name2 = name1 + '_' + self.crate_name
568 if self.runner.try_claim_module_name(name2, self):
569 return name2
570 name3 = name1 + '_' + self.main_src_basename_path()
571 if self.runner.try_claim_module_name(name3, self):
572 return name3
573 return self.runner.claim_module_name(name1, self, 0)
574
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700575 def dump_srcs_list(self):
576 """Dump the srcs list, for defaults or regular modules."""
577 if len(self.srcs) > 1:
578 srcs = sorted(set(self.srcs)) # make a copy and dedup
579 else:
580 srcs = [self.main_src]
581 copy_out = self.runner.copy_out_module_name()
582 if copy_out:
583 srcs.append(':' + copy_out)
584 self.dump_android_property_list('srcs', '"%s"', srcs)
585
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700586 def dump_defaults_module(self):
587 """Dump a rust_defaults module to be shared by other modules."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700588 name = self.build_default_name()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700589 self.defaults = name
590 self.write('\nrust_defaults {')
591 self.write(' name: "' + name + '",')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700592 if self.runner.args.global_defaults:
593 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700594 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700595 if len(self.srcs) == 1: # only one source file; share it in defaults
596 self.default_srcs = True
597 if self.has_warning and not self.cap_lints:
598 self.write(' // has rustc warnings')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700599 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700600 if 'test' in self.crate_types:
601 self.write(' test_suites: ["general-tests"],')
602 self.write(' auto_gen_config: true,')
603 self.dump_edition_flags_libs()
604 self.write('}')
605
606 def dump_single_type_android_module(self):
607 """Dump one simple Android module, which has only one crate_type."""
608 crate_type = self.crate_types[0]
609 if crate_type != 'test':
610 # do not change self.stem or self.module_name
611 self.dump_one_android_module(crate_type)
612 return
613 # Dump one test module per source file, and separate host and device tests.
614 # crate_type == 'test'
Joel Galensonf6b3c912021-06-03 16:00:54 -0700615 self.srcs = [src for src in self.srcs if not src in self.runner.args.test_blocklist]
616 if ((self.host_supported and self.device_supported and len(self.srcs) > 0) or
617 len(self.srcs) > 1):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700618 self.srcs = sorted(set(self.srcs))
619 self.dump_defaults_module()
620 saved_srcs = self.srcs
621 for src in saved_srcs:
622 self.srcs = [src]
623 saved_device_supported = self.device_supported
624 saved_host_supported = self.host_supported
625 saved_main_src = self.main_src
626 self.main_src = src
627 if saved_host_supported:
628 self.device_supported = False
629 self.host_supported = True
630 self.module_name = self.test_module_name()
631 self.decide_one_module_type(crate_type)
632 self.dump_one_android_module(crate_type)
633 if saved_device_supported:
634 self.device_supported = True
635 self.host_supported = False
636 self.module_name = self.test_module_name()
637 self.decide_one_module_type(crate_type)
638 self.dump_one_android_module(crate_type)
639 self.host_supported = saved_host_supported
640 self.device_supported = saved_device_supported
641 self.main_src = saved_main_src
642 self.srcs = saved_srcs
643
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700644 def dump_one_android_module(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800645 """Dump one Android module definition."""
646 if not self.module_type:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700647 self.write('\nERROR: unknown crate_type ' + crate_type)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800648 return
649 self.write('\n' + self.module_type + ' {')
650 self.dump_android_core_properties()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700651 if not self.defaults:
652 self.dump_edition_flags_libs()
653 if self.runner.args.host_first_multilib and self.host_supported and crate_type != 'test':
654 self.write(' compile_multilib: "first",')
Joel Galensond9c4de62021-04-23 10:26:40 -0700655 if self.runner.args.apex_available and crate_type == 'lib':
656 self.write(' apex_available: [')
657 for apex in self.runner.args.apex_available:
658 self.write(' "%s",' % apex)
659 self.write(' ],')
660 if self.runner.args.min_sdk_version and crate_type == 'lib':
661 self.write(' min_sdk_version: "%s",' % self.runner.args.min_sdk_version)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700662 self.write('}')
663
664 def dump_android_flags(self):
665 """Dump Android module flags property."""
Thiébaud Weksteena5a728b2021-04-08 14:23:49 +0200666 if not self.codegens and not self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700667 return
668 self.write(' flags: [')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800669 if self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700670 self.write(' "--cap-lints ' + self.cap_lints + '",')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700671 codegens_fmt = '"-C %s"'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700672 self.dump_android_property_list_items(codegens_fmt, self.codegens)
673 self.write(' ],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700674
675 def dump_edition_flags_libs(self):
676 if self.edition:
677 self.write(' edition: "' + self.edition + '",')
678 self.dump_android_property_list('features', '"%s"', self.features)
Joel Galenson3d6d1e72021-06-07 15:00:24 -0700679 cfgs = [cfg for cfg in self.cfgs if not cfg in self.runner.args.cfg_blocklist]
680 self.dump_android_property_list('cfgs', '"%s"', cfgs)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700681 self.dump_android_flags()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800682 if self.externs:
683 self.dump_android_externs()
684 self.dump_android_property_list('static_libs', '"lib%s"', self.static_libs)
685 self.dump_android_property_list('shared_libs', '"lib%s"', self.shared_libs)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800686
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700687 def main_src_basename_path(self):
688 return re.sub('/', '_', re.sub('.rs$', '', self.main_src))
689
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800690 def test_module_name(self):
691 """Return a unique name for a test module."""
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700692 # root_pkg+(_host|_device) + '_test_'+source_file_name
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700693 suffix = self.main_src_basename_path()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700694 host_device = '_host'
695 if self.device_supported:
696 host_device = '_device'
697 return self.root_pkg + host_device + '_test_' + suffix
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800698
699 def decide_module_type(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700700 # Use the first crate type for the default/first module.
701 crate_type = self.crate_types[0] if self.crate_types else ''
702 self.decide_one_module_type(crate_type)
703
704 def decide_one_module_type(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800705 """Decide which Android module type to use."""
706 host = '' if self.device_supported else '_host'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700707 if crate_type == 'bin': # rust_binary[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800708 self.module_type = 'rust_binary' + host
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700709 # In rare cases like protobuf-codegen, the output binary name must
710 # be renamed to use as a plugin for protoc.
711 self.stem = altered_stem(self.crate_name)
712 self.module_name = altered_name(self.crate_name)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700713 elif crate_type == 'lib': # rust_library[_host]
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700714 # TODO(chh): should this be rust_library[_host]?
715 # Assuming that Cargo.toml do not use both 'lib' and 'rlib',
716 # because we map them both to rlib.
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700717 self.module_type = 'rust_library' + host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800718 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700719 self.module_name = altered_name(self.stem)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700720 elif crate_type == 'rlib': # rust_library[_host]
721 self.module_type = 'rust_library' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700722 self.stem = 'lib' + self.crate_name
723 self.module_name = altered_name(self.stem)
724 elif crate_type == 'dylib': # rust_library[_host]_dylib
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800725 self.module_type = 'rust_library' + host + '_dylib'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700726 self.stem = 'lib' + self.crate_name
727 self.module_name = altered_name(self.stem) + '_dylib'
728 elif crate_type == 'cdylib': # rust_library[_host]_shared
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500729 self.module_type = 'rust_ffi' + host + '_shared'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700730 self.stem = 'lib' + self.crate_name
731 self.module_name = altered_name(self.stem) + '_shared'
732 elif crate_type == 'staticlib': # rust_library[_host]_static
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500733 self.module_type = 'rust_ffi' + host + '_static'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700734 self.stem = 'lib' + self.crate_name
735 self.module_name = altered_name(self.stem) + '_static'
736 elif crate_type == 'test': # rust_test[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800737 self.module_type = 'rust_test' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700738 # Before do_merge, stem name is based on the --crate-name parameter.
739 # and test module name is based on stem.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800740 self.stem = self.test_module_name()
741 # self.stem will be changed after merging with other tests.
742 # self.stem is NOT used for final test binary name.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700743 # rust_test uses each source file base name as part of output file name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700744 # In do_merge, this function is called again, with a module_name.
745 # We make sure that the module name is unique in each package.
746 if self.module_name:
747 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add
748 # different suffixes and distinguish multiple tests of the same
749 # crate name. We ignore -C and use claim_module_name to get
750 # unique sequential suffix.
751 self.module_name = self.runner.claim_module_name(
752 self.module_name, self, 0)
753 # Now the module name is unique, stem should also match and unique.
754 self.stem = self.module_name
755 elif crate_type == 'proc-macro': # rust_proc_macro
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800756 self.module_type = 'rust_proc_macro'
757 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700758 self.module_name = altered_name(self.stem)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800759 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]?
760 self.module_type = ''
761 self.stem = ''
762
763 def dump_android_property_list_items(self, fmt, values):
764 for v in values:
765 # fmt has quotes, so we need escape_quotes(v)
766 self.write(' ' + (fmt % escape_quotes(v)) + ',')
767
768 def dump_android_property_list(self, name, fmt, values):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700769 if not values:
770 return
771 if len(values) > 1:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800772 self.write(' ' + name + ': [')
773 self.dump_android_property_list_items(fmt, values)
774 self.write(' ],')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700775 else:
776 self.write(' ' + name + ': [' +
777 (fmt % escape_quotes(values[0])) + '],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800778
779 def dump_android_core_properties(self):
780 """Dump the module header, name, stem, etc."""
781 self.write(' name: "' + self.module_name + '",')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700782 # see properties shared by dump_defaults_module
783 if self.defaults:
784 self.write(' defaults: ["' + self.defaults + '"],')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700785 elif self.runner.args.global_defaults:
786 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800787 if self.stem != self.module_name:
788 self.write(' stem: "' + self.stem + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700789 if self.has_warning and not self.cap_lints and not self.default_srcs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700790 self.write(' // has rustc warnings')
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700791 if self.host_supported and self.device_supported and self.module_type != 'rust_proc_macro':
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800792 self.write(' host_supported: true,')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700793 if not self.defaults:
794 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700795 if not self.default_srcs:
796 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700797 if 'test' in self.crate_types and not self.defaults:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800798 # self.root_pkg can have multiple test modules, with different *_tests[n]
799 # names, but their executables can all be installed under the same _tests
800 # directory. When built from Cargo.toml, all tests should have different
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700801 # file or crate names. So we used (root_pkg + '_tests') name as the
802 # relative_install_path.
803 # However, some package like 'slab' can have non-mergeable tests that
804 # must be separated by different module names. So, here we no longer
805 # emit relative_install_path.
806 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800807 self.write(' test_suites: ["general-tests"],')
808 self.write(' auto_gen_config: true,')
Joel Galensone261a152021-01-12 11:31:53 -0800809 if 'test' in self.crate_types and self.host_supported:
810 self.write(' test_options: {')
811 self.write(' unit_test: true,')
812 self.write(' },')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800813
814 def dump_android_externs(self):
815 """Dump the dependent rlibs and dylibs property."""
816 so_libs = list()
817 rust_libs = ''
818 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$')
819 for lib in self.externs:
820 # normal value of lib: "libc = liblibc-*.rlib"
821 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib"
822 # we should use "libgetrandom", not "lib" + "getrandom_package"
823 groups = deps_libname.match(lib)
824 if groups is not None:
825 lib_name = groups.group(1)
826 else:
827 lib_name = re.sub(' .*$', '', lib)
Joel Galenson97e414a2021-05-27 09:42:32 -0700828 if lib_name in self.runner.args.dependency_blocklist:
829 continue
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800830 if lib.endswith('.rlib') or lib.endswith('.rmeta'):
831 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta.
832 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n'
833 elif lib.endswith('.so'):
834 so_libs.append(lib_name)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700835 elif lib != 'proc_macro': # --extern proc_macro is special and ignored
836 rust_libs += ' // ERROR: unknown type of lib ' + lib + '\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800837 if rust_libs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700838 self.write(' rustlibs: [\n' + rust_libs + ' ],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800839 # Are all dependent .so files proc_macros?
840 # TODO(chh): Separate proc_macros and dylib.
841 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs)
842
843
844class ARObject(object):
845 """Information of an "ar" link command."""
846
847 def __init__(self, runner, outf_name):
848 # Remembered global runner and its members.
849 self.runner = runner
850 self.pkg = ''
851 self.outf_name = outf_name # path to Android.bp
852 # "ar" arguments
853 self.line_num = 1
854 self.line = ''
855 self.flags = '' # e.g. "crs"
856 self.lib = '' # e.g. "/.../out/lib*.a"
857 self.objs = list() # e.g. "/.../out/.../*.o"
858
859 def parse(self, pkg, line_num, args_line):
860 """Collect ar obj/lib file names."""
861 self.pkg = pkg
862 self.line_num = line_num
863 self.line = args_line
864 args = args_line.split()
865 num_args = len(args)
866 if num_args < 3:
867 print('ERROR: "ar" command has too few arguments', args_line)
868 else:
869 self.flags = unquote(args[0])
870 self.lib = unquote(args[1])
871 self.objs = sorted(set(map(unquote, args[2:])))
872 return self
873
874 def write(self, s):
875 self.outf.write(s + '\n')
876
877 def dump_debug_info(self):
878 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line)
879 self.write('// ar_object for %12s' % self.pkg)
880 self.write('// flags = %s' % self.flags)
881 self.write('// lib = %s' % short_out_name(self.pkg, self.lib))
882 for o in self.objs:
883 self.write('// obj = %s' % short_out_name(self.pkg, o))
884
885 def dump_android_lib(self):
886 """Write cc_library_static into Android.bp."""
887 self.write('\ncc_library_static {')
888 self.write(' name: "' + file_base_name(self.lib) + '",')
889 self.write(' host_supported: true,')
890 if self.flags != 'crs':
891 self.write(' // ar flags = %s' % self.flags)
892 if self.pkg not in self.runner.pkg_obj2cc:
893 self.write(' ERROR: cannot find source files.\n}')
894 return
895 self.write(' srcs: [')
896 obj2cc = self.runner.pkg_obj2cc[self.pkg]
897 # Note: wflags are ignored.
898 dflags = list()
899 fflags = list()
900 for obj in self.objs:
901 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",')
902 # TODO(chh): union of dflags and flags of all obj
903 # Now, just a temporary hack that uses the last obj's flags
904 dflags = obj2cc[obj].dflags
905 fflags = obj2cc[obj].fflags
906 self.write(' ],')
907 self.write(' cflags: [')
908 self.write(' "-O3",') # TODO(chh): is this default correct?
909 self.write(' "-Wno-error",')
910 for x in fflags:
911 self.write(' "-f' + x + '",')
912 for x in dflags:
913 self.write(' "-D' + x + '",')
914 self.write(' ],')
915 self.write('}')
916
917 def dump(self):
918 """Dump error/debug/module info to the output .bp file."""
919 self.runner.init_bp_file(self.outf_name)
920 with open(self.outf_name, 'a') as outf:
921 self.outf = outf
922 if self.runner.args.debug:
923 self.dump_debug_info()
924 self.dump_android_lib()
925
926
927class CCObject(object):
928 """Information of a "cc" compilation command."""
929
930 def __init__(self, runner, outf_name):
931 # Remembered global runner and its members.
932 self.runner = runner
933 self.pkg = ''
934 self.outf_name = outf_name # path to Android.bp
935 # "cc" arguments
936 self.line_num = 1
937 self.line = ''
938 self.src = ''
939 self.obj = ''
940 self.dflags = list() # -D flags
941 self.fflags = list() # -f flags
942 self.iflags = list() # -I flags
943 self.wflags = list() # -W flags
944 self.other_args = list()
945
946 def parse(self, pkg, line_num, args_line):
947 """Collect cc compilation flags and src/out file names."""
948 self.pkg = pkg
949 self.line_num = line_num
950 self.line = args_line
951 args = args_line.split()
952 i = 0
953 while i < len(args):
954 arg = args[i]
955 if arg == '"-c"':
956 i += 1
957 if args[i].startswith('"-o'):
958 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c"
959 self.obj = unquote(args[i])[2:]
960 i += 1
961 self.src = unquote(args[i])
962 else:
963 self.src = unquote(args[i])
964 elif arg == '"-o"':
965 i += 1
966 self.obj = unquote(args[i])
967 elif arg == '"-I"':
968 i += 1
969 self.iflags.append(unquote(args[i]))
970 elif arg.startswith('"-D'):
971 self.dflags.append(unquote(args[i])[2:])
972 elif arg.startswith('"-f'):
973 self.fflags.append(unquote(args[i])[2:])
974 elif arg.startswith('"-W'):
975 self.wflags.append(unquote(args[i])[2:])
976 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or
977 arg == '"-g3"'):
978 # ignore -O -m64 -g
979 self.other_args.append(unquote(args[i]))
980 i += 1
981 self.dflags = sorted(set(self.dflags))
982 self.fflags = sorted(set(self.fflags))
983 # self.wflags is not sorted because some are order sensitive
984 # and we ignore them anyway.
985 if self.pkg not in self.runner.pkg_obj2cc:
986 self.runner.pkg_obj2cc[self.pkg] = {}
987 self.runner.pkg_obj2cc[self.pkg][self.obj] = self
988 return self
989
990 def write(self, s):
991 self.outf.write(s + '\n')
992
993 def dump_debug_flags(self, name, flags):
994 self.write('// ' + name + ':')
995 for f in flags:
996 self.write('// %s' % f)
997
998 def dump(self):
999 """Dump only error/debug info to the output .bp file."""
1000 if not self.runner.args.debug:
1001 return
1002 self.runner.init_bp_file(self.outf_name)
1003 with open(self.outf_name, 'a') as outf:
1004 self.outf = outf
1005 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line)
1006 self.write('// cc_object for %12s' % self.pkg)
1007 self.write('// src = %s' % short_out_name(self.pkg, self.src))
1008 self.write('// obj = %s' % short_out_name(self.pkg, self.obj))
1009 self.dump_debug_flags('-I flags', self.iflags)
1010 self.dump_debug_flags('-D flags', self.dflags)
1011 self.dump_debug_flags('-f flags', self.fflags)
1012 self.dump_debug_flags('-W flags', self.wflags)
1013 if self.other_args:
1014 self.dump_debug_flags('other args', self.other_args)
1015
1016
1017class Runner(object):
1018 """Main class to parse cargo -v output and print Android module definitions."""
1019
1020 def __init__(self, args):
1021 self.bp_files = set() # Remember all output Android.bp files.
1022 self.root_pkg = '' # name of package in ./Cargo.toml
1023 # Saved flags, modes, and data.
1024 self.args = args
1025 self.dry_run = not args.run
1026 self.skip_cargo = args.skipcargo
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001027 self.cargo_path = './cargo' # path to cargo, will be set later
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001028 self.checked_out_files = False # to check only once
1029 self.build_out_files = [] # output files generated by build.rs
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001030 # All cc/ar objects, crates, dependencies, and warning files
1031 self.cc_objects = list()
1032 self.pkg_obj2cc = {}
1033 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i]
1034 self.ar_objects = list()
1035 self.crates = list()
1036 self.dependencies = list() # dependent and build script crates
1037 self.warning_files = set()
1038 # Keep a unique mapping from (module name) to crate
1039 self.name_owners = {}
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001040 # Save and dump all errors from cargo to Android.bp.
1041 self.errors = ''
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001042 self.setup_cargo_path()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001043 # Default action is cargo clean, followed by build or user given actions.
1044 if args.cargo:
1045 self.cargo = ['clean'] + args.cargo
1046 else:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001047 default_target = '--target x86_64-unknown-linux-gnu'
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -07001048 # Use the same target for both host and default device builds.
1049 # Same target is used as default in host x86_64 Android compilation.
1050 # Note: b/169872957, prebuilt cargo failed to build vsock
1051 # on x86_64-unknown-linux-musl systems.
1052 self.cargo = ['clean', 'build ' + default_target]
1053 if args.tests:
1054 self.cargo.append('build --tests ' + default_target)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001055
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001056 def setup_cargo_path(self):
1057 """Find cargo in the --cargo_bin or prebuilt rust bin directory."""
1058 if self.args.cargo_bin:
1059 self.cargo_path = os.path.join(self.args.cargo_bin, 'cargo')
1060 if not os.path.isfile(self.cargo_path):
1061 sys.exit('ERROR: cannot find cargo in ' + self.args.cargo_bin)
1062 print('WARNING: using cargo in ' + self.args.cargo_bin)
1063 return
1064 # We have only tested this on Linux.
1065 if platform.system() != 'Linux':
1066 sys.exit('ERROR: this script has only been tested on Linux with cargo.')
1067 # Assuming that this script is in development/scripts.
1068 my_dir = os.path.dirname(os.path.abspath(__file__))
1069 linux_dir = os.path.join(my_dir, '..', '..',
1070 'prebuilts', 'rust', 'linux-x86')
1071 if not os.path.isdir(linux_dir):
1072 sys.exit('ERROR: cannot find directory ' + linux_dir)
1073 rust_version = self.find_rust_version(my_dir, linux_dir)
1074 cargo_bin = os.path.join(linux_dir, rust_version, 'bin')
1075 self.cargo_path = os.path.join(cargo_bin, 'cargo')
1076 if not os.path.isfile(self.cargo_path):
1077 sys.exit('ERROR: cannot find cargo in ' + cargo_bin
1078 + '; please try --cargo_bin= flag.')
1079 return
1080
1081 def find_rust_version(self, my_dir, linux_dir):
1082 """Use my script directory, find prebuilt rust version."""
1083 # First look up build/soong/rust/config/global.go.
1084 path2global = os.path.join(my_dir, '..', '..',
1085 'build', 'soong', 'rust', 'config', 'global.go')
1086 if os.path.isfile(path2global):
1087 # try to find: RustDefaultVersion = "1.44.0"
1088 version_pat = re.compile(
1089 r'\s*RustDefaultVersion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+)".*$')
1090 with open(path2global, 'r') as inf:
1091 for line in inf:
1092 result = version_pat.match(line)
1093 if result:
1094 return result.group(1)
1095 print('WARNING: cannot find RustDefaultVersion in ' + path2global)
1096 # Otherwise, find the newest (largest) version number in linux_dir.
1097 rust_version = (0, 0, 0) # the prebuilt version to use
1098 version_pat = re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)$')
1099 for dir_name in os.listdir(linux_dir):
1100 result = version_pat.match(dir_name)
1101 if not result:
1102 continue
1103 version = (result.group(1), result.group(2), result.group(3))
1104 if version > rust_version:
1105 rust_version = version
1106 return '.'.join(rust_version)
1107
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001108 def find_out_files(self):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001109 # list1 has build.rs output for normal crates
1110 list1 = glob.glob(TARGET_TMP + '/*/*/build/' + self.root_pkg + '-*/out/*')
1111 # list2 has build.rs output for proc-macro crates
1112 list2 = glob.glob(TARGET_TMP + '/*/build/' + self.root_pkg + '-*/out/*')
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001113 return list1 + list2
1114
1115 def copy_out_files(self):
1116 """Copy build.rs output files to ./out and set up build_out_files."""
1117 if self.checked_out_files:
1118 return
1119 self.checked_out_files = True
1120 cargo_out_files = self.find_out_files()
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001121 out_files = set()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001122 if cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001123 os.makedirs('out', exist_ok=True)
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001124 for path in cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001125 file_name = path.split('/')[-1]
1126 out_files.add(file_name)
1127 shutil.copy(path, 'out/' + file_name)
1128 self.build_out_files = sorted(out_files)
1129
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001130 def has_used_out_dir(self):
1131 """Returns true if env!("OUT_DIR") is found."""
1132 return 0 == os.system('grep -rl --exclude build.rs --include \\*.rs' +
1133 ' \'env!("OUT_DIR")\' * > /dev/null')
1134
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001135 def copy_out_module_name(self):
1136 if self.args.copy_out and self.build_out_files:
1137 return 'copy_' + self.root_pkg + '_build_out'
1138 else:
1139 return ''
1140
Haibo Huang0f72c952021-03-19 11:34:15 -07001141 def read_license(self, name):
1142 if not os.path.isfile(name):
1143 return ''
1144 license = ''
1145 with open(name, 'r') as intf:
1146 line = intf.readline()
1147 # Firstly skip ANDROID_BP_HEADER
1148 while line.startswith('//'):
1149 line = intf.readline()
Joel Galensond9d13b82021-04-05 11:27:55 -07001150 # Read all lines until we see a rust_* or genrule rule.
1151 while line != '' and not (line.startswith('rust_') or line.startswith('genrule {')):
Haibo Huang0f72c952021-03-19 11:34:15 -07001152 license += line
1153 line = intf.readline()
1154 return license.strip()
1155
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001156 def dump_copy_out_module(self, outf):
1157 """Output the genrule module to copy out/* to $(genDir)."""
1158 copy_out = self.copy_out_module_name()
1159 if not copy_out:
1160 return
1161 outf.write('\ngenrule {\n')
1162 outf.write(' name: "' + copy_out + '",\n')
1163 outf.write(' srcs: ["out/*"],\n')
1164 outf.write(' cmd: "cp $(in) $(genDir)",\n')
1165 if len(self.build_out_files) > 1:
1166 outf.write(' out: [\n')
1167 for f in self.build_out_files:
1168 outf.write(' "' + f + '",\n')
1169 outf.write(' ],\n')
1170 else:
1171 outf.write(' out: ["' + self.build_out_files[0] + '"],\n')
1172 outf.write('}\n')
1173
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001174 def init_bp_file(self, name):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001175 # name could be Android.bp or sub_dir_path/Android.bp
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001176 if name not in self.bp_files:
1177 self.bp_files.add(name)
Haibo Huang0f72c952021-03-19 11:34:15 -07001178 license_section = self.read_license(name)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001179 with open(name, 'w') as outf:
Joel Galenson367360c2021-04-29 14:31:43 -07001180 print_args = filter(lambda x: x != "--no-test-mapping", sys.argv[1:])
1181 outf.write(ANDROID_BP_HEADER.format(args=' '.join(print_args)))
Haibo Huang0f72c952021-03-19 11:34:15 -07001182 outf.write('\n')
1183 outf.write(license_section)
1184 outf.write('\n')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001185 # at most one copy_out module per .bp file
1186 self.dump_copy_out_module(outf)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001187
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -07001188 def dump_test_mapping_files(self):
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001189 """Dump all TEST_MAPPING files."""
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -07001190 if self.dry_run:
1191 print('Dry-run skip dump of TEST_MAPPING')
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001192 elif self.args.no_test_mapping:
1193 print('Skipping generation of TEST_MAPPING')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -07001194 else:
Jeff Vander Stoep1b24dc32021-02-03 18:52:42 +01001195 test_mapping = TestMapping(None)
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +01001196 for bp_file_name in self.bp_files:
1197 test_mapping.create_test_mapping(os.path.dirname(bp_file_name))
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -07001198 return self
1199
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001200 def try_claim_module_name(self, name, owner):
1201 """Reserve and return True if it has not been reserved yet."""
1202 if name not in self.name_owners or owner == self.name_owners[name]:
1203 self.name_owners[name] = owner
1204 return True
1205 return False
1206
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001207 def claim_module_name(self, prefix, owner, counter):
1208 """Return prefix if not owned yet, otherwise, prefix+str(counter)."""
1209 while True:
1210 name = prefix
1211 if counter > 0:
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001212 name += '_' + str(counter)
1213 if self.try_claim_module_name(name, owner):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001214 return name
1215 counter += 1
1216
1217 def find_root_pkg(self):
1218 """Read name of [package] in ./Cargo.toml."""
1219 if not os.path.exists('./Cargo.toml'):
1220 return
1221 with open('./Cargo.toml', 'r') as inf:
1222 pkg_section = re.compile(r'^ *\[package\]')
1223 name = re.compile('^ *name *= * "([^"]*)"')
1224 in_pkg = False
1225 for line in inf:
1226 if in_pkg:
1227 if name.match(line):
1228 self.root_pkg = name.match(line).group(1)
1229 break
1230 else:
1231 in_pkg = pkg_section.match(line) is not None
1232
1233 def run_cargo(self):
1234 """Calls cargo -v and save its output to ./cargo.out."""
1235 if self.skip_cargo:
1236 return self
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001237 cargo_toml = './Cargo.toml'
1238 cargo_out = './cargo.out'
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001239 # Do not use Cargo.lock, because .bp rules are designed to
1240 # run with "latest" crates avaialable on Android.
1241 cargo_lock = './Cargo.lock'
1242 cargo_lock_saved = './cargo.lock.saved'
1243 had_cargo_lock = os.path.exists(cargo_lock)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001244 if not os.access(cargo_toml, os.R_OK):
1245 print('ERROR: Cannot find or read', cargo_toml)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001246 return self
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001247 if not self.dry_run:
1248 if os.path.exists(cargo_out):
1249 os.remove(cargo_out)
1250 if not self.args.use_cargo_lock and had_cargo_lock: # save it
1251 os.rename(cargo_lock, cargo_lock_saved)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001252 cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> ' + cargo_out + ' 2>&1'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001253 # set up search PATH for cargo to find the correct rustc
1254 saved_path = os.environ['PATH']
1255 os.environ['PATH'] = os.path.dirname(self.cargo_path) + ':' + saved_path
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001256 # Add [workspace] to Cargo.toml if it is not there.
1257 added_workspace = False
1258 if self.args.add_workspace:
1259 with open(cargo_toml, 'r') as in_file:
1260 cargo_toml_lines = in_file.readlines()
1261 found_workspace = '[workspace]\n' in cargo_toml_lines
1262 if found_workspace:
1263 print('### WARNING: found [workspace] in Cargo.toml')
1264 else:
1265 with open(cargo_toml, 'a') as out_file:
1266 out_file.write('[workspace]\n')
1267 added_workspace = True
1268 if self.args.verbose:
1269 print('### INFO: added [workspace] to Cargo.toml')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001270 for c in self.cargo:
1271 features = ''
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001272 if c != 'clean':
1273 if self.args.features is not None:
1274 features = ' --no-default-features'
1275 if self.args.features:
1276 features += ' --features ' + self.args.features
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001277 cmd_v_flag = ' -vv ' if self.args.vv else ' -v '
1278 cmd = self.cargo_path + cmd_v_flag
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001279 cmd += c + features + cmd_tail
1280 if self.args.rustflags and c != 'clean':
1281 cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
1282 if self.dry_run:
1283 print('Dry-run skip:', cmd)
1284 else:
1285 if self.args.verbose:
1286 print('Running:', cmd)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001287 with open(cargo_out, 'a') as out_file:
1288 out_file.write('### Running: ' + cmd + '\n')
Joel Galenson6bf54e32021-05-17 10:54:50 -07001289 ret = os.system(cmd)
1290 if ret != 0:
1291 print('*** There was an error while running cargo. ' +
1292 'See the cargo.out file for details.')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001293 if added_workspace: # restore original Cargo.toml
1294 with open(cargo_toml, 'w') as out_file:
1295 out_file.writelines(cargo_toml_lines)
1296 if self.args.verbose:
1297 print('### INFO: restored original Cargo.toml')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001298 os.environ['PATH'] = saved_path
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001299 if not self.dry_run:
1300 if not had_cargo_lock: # restore to no Cargo.lock state
1301 os.remove(cargo_lock)
1302 elif not self.args.use_cargo_lock: # restore saved Cargo.lock
1303 os.rename(cargo_lock_saved, cargo_lock)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001304 return self
1305
1306 def dump_dependencies(self):
1307 """Append dependencies and their features to Android.bp."""
1308 if not self.dependencies:
1309 return
1310 dependent_list = list()
1311 for c in self.dependencies:
1312 dependent_list.append(c.feature_list())
1313 sorted_dependencies = sorted(set(dependent_list))
1314 self.init_bp_file('Android.bp')
1315 with open('Android.bp', 'a') as outf:
1316 outf.write('\n// dependent_library ["feature_list"]\n')
1317 for s in sorted_dependencies:
1318 outf.write('// ' + s + '\n')
1319
1320 def dump_pkg_obj2cc(self):
1321 """Dump debug info of the pkg_obj2cc map."""
1322 if not self.args.debug:
1323 return
1324 self.init_bp_file('Android.bp')
1325 with open('Android.bp', 'a') as outf:
1326 sorted_pkgs = sorted(self.pkg_obj2cc.keys())
1327 for pkg in sorted_pkgs:
1328 if not self.pkg_obj2cc[pkg]:
1329 continue
1330 outf.write('\n// obj => src for %s\n' % pkg)
1331 obj2cc = self.pkg_obj2cc[pkg]
1332 for obj in sorted(obj2cc.keys()):
1333 outf.write('// ' + short_out_name(pkg, obj) + ' => ' +
1334 short_out_name(pkg, obj2cc[obj].src) + '\n')
1335
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001336 def apply_patch(self):
1337 """Apply local patch file if it is given."""
1338 if self.args.patch:
1339 if self.dry_run:
1340 print('Dry-run skip patch file:', self.args.patch)
1341 else:
1342 if not os.path.exists(self.args.patch):
1343 self.append_to_bp('ERROR cannot find patch file: ' + self.args.patch)
1344 return self
1345 if self.args.verbose:
1346 print('### INFO: applying local patch file:', self.args.patch)
Joel Galenson7e8247e2021-05-20 18:51:42 -07001347 subprocess.run(['patch', '-s', '--no-backup-if-mismatch', './Android.bp',
1348 self.args.patch], check=True)
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001349 return self
1350
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001351 def gen_bp(self):
1352 """Parse cargo.out and generate Android.bp files."""
1353 if self.dry_run:
1354 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp')
1355 elif os.path.exists(CARGO_OUT):
1356 self.find_root_pkg()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001357 if self.args.copy_out:
1358 self.copy_out_files()
1359 elif self.find_out_files() and self.has_used_out_dir():
1360 print('WARNING: ' + self.root_pkg + ' has cargo output files; ' +
1361 'please rerun with the --copy-out flag.')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001362 with open(CARGO_OUT, 'r') as cargo_out:
1363 self.parse(cargo_out, 'Android.bp')
1364 self.crates.sort(key=get_module_name)
1365 for obj in self.cc_objects:
1366 obj.dump()
1367 self.dump_pkg_obj2cc()
1368 for crate in self.crates:
1369 crate.dump()
1370 dumped_libs = set()
1371 for lib in self.ar_objects:
1372 if lib.pkg == self.root_pkg:
1373 lib_name = file_base_name(lib.lib)
1374 if lib_name not in dumped_libs:
1375 dumped_libs.add(lib_name)
1376 lib.dump()
1377 if self.args.dependencies and self.dependencies:
1378 self.dump_dependencies()
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001379 if self.errors:
Joel Galenson3f42f802021-04-07 12:42:17 -07001380 self.append_to_bp('\n' + ERRORS_LINE + '\n' + self.errors)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001381 return self
1382
1383 def add_ar_object(self, obj):
1384 self.ar_objects.append(obj)
1385
1386 def add_cc_object(self, obj):
1387 self.cc_objects.append(obj)
1388
1389 def add_crate(self, crate):
1390 """Merge crate with someone in crates, or append to it. Return crates."""
1391 if crate.skip_crate():
1392 if self.args.debug: # include debug info of all crates
1393 self.crates.append(crate)
1394 if self.args.dependencies: # include only dependent crates
1395 if (is_dependent_file_path(crate.main_src) and
1396 not is_build_crate_name(crate.crate_name)):
1397 self.dependencies.append(crate)
1398 else:
1399 for c in self.crates:
1400 if c.merge(crate, 'Android.bp'):
1401 return
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001402 # If not merged, decide module type and name now.
1403 crate.decide_module_type()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001404 self.crates.append(crate)
1405
1406 def find_warning_owners(self):
1407 """For each warning file, find its owner crate."""
1408 missing_owner = False
1409 for f in self.warning_files:
1410 cargo_dir = '' # find lowest crate, with longest path
1411 owner = None # owner crate of this warning
1412 for c in self.crates:
1413 if (f.startswith(c.cargo_dir + '/') and
1414 len(cargo_dir) < len(c.cargo_dir)):
1415 cargo_dir = c.cargo_dir
1416 owner = c
1417 if owner:
1418 owner.has_warning = True
1419 else:
1420 missing_owner = True
1421 if missing_owner and os.path.exists('Cargo.toml'):
1422 # owner is the root cargo, with empty cargo_dir
1423 for c in self.crates:
1424 if not c.cargo_dir:
1425 c.has_warning = True
1426
1427 def rustc_command(self, n, rustc_line, line, outf_name):
1428 """Process a rustc command line from cargo -vv output."""
1429 # cargo build -vv output can have multiple lines for a rustc command
1430 # due to '\n' in strings for environment variables.
1431 # strip removes leading spaces and '\n' at the end
1432 new_rustc = (rustc_line.strip() + line) if rustc_line else line
1433 # Use an heuristic to detect the completions of a multi-line command.
1434 # This might fail for some very rare case, but easy to fix manually.
1435 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0:
1436 return new_rustc
1437 if RUSTC_VV_CMD_ARGS.match(new_rustc):
1438 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1)
1439 self.add_crate(Crate(self, outf_name).parse(n, args))
1440 else:
1441 self.assert_empty_vv_line(new_rustc)
1442 return ''
1443
1444 def cc_ar_command(self, n, groups, outf_name):
1445 pkg = groups.group(1)
1446 line = groups.group(3)
1447 if groups.group(2) == 'cc':
1448 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line))
1449 else:
1450 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line))
1451
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001452 def append_to_bp(self, line):
1453 self.init_bp_file('Android.bp')
1454 with open('Android.bp', 'a') as outf:
1455 outf.write(line)
1456
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001457 def assert_empty_vv_line(self, line):
1458 if line: # report error if line is not empty
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001459 self.append_to_bp('ERROR -vv line: ' + line)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001460 return ''
1461
1462 def parse(self, inf, outf_name):
1463 """Parse rustc and warning messages in inf, return a list of Crates."""
1464 n = 0 # line number
1465 prev_warning = False # true if the previous line was warning: ...
1466 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
1467 for line in inf:
1468 n += 1
1469 if line.startswith('warning: '):
1470 prev_warning = True
1471 rustc_line = self.assert_empty_vv_line(rustc_line)
1472 continue
1473 new_rustc = ''
1474 if RUSTC_PAT.match(line):
1475 args_line = RUSTC_PAT.match(line).group(1)
1476 self.add_crate(Crate(self, outf_name).parse(n, args_line))
1477 self.assert_empty_vv_line(rustc_line)
1478 elif rustc_line or RUSTC_VV_PAT.match(line):
1479 new_rustc = self.rustc_command(n, rustc_line, line, outf_name)
1480 elif CC_AR_VV_PAT.match(line):
1481 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name)
1482 elif prev_warning and WARNING_FILE_PAT.match(line):
1483 self.assert_empty_vv_line(rustc_line)
1484 fpath = WARNING_FILE_PAT.match(line).group(1)
1485 if fpath[0] != '/': # ignore absolute path
1486 self.warning_files.add(fpath)
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001487 elif line.startswith('error: ') or line.startswith('error[E'):
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001488 if not self.args.ignore_cargo_errors:
1489 self.errors += line
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001490 prev_warning = False
1491 rustc_line = new_rustc
1492 self.find_warning_owners()
1493
1494
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001495def get_parser():
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001496 """Parse main arguments."""
1497 parser = argparse.ArgumentParser('cargo2android')
1498 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001499 '--add_workspace',
1500 action='store_true',
1501 default=False,
1502 help=('append [workspace] to Cargo.toml before calling cargo,' +
1503 ' to treat current directory as root of package source;' +
1504 ' otherwise the relative source file path in generated' +
1505 ' .bp file will be from the parent directory.'))
1506 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001507 '--cargo',
1508 action='append',
1509 metavar='args_string',
1510 help=('extra cargo build -v args in a string, ' +
1511 'each --cargo flag calls cargo build -v once'))
1512 parser.add_argument(
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001513 '--cargo_bin',
1514 type=str,
1515 help='use cargo in the cargo_bin directory instead of the prebuilt one')
1516 parser.add_argument(
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001517 '--copy-out',
1518 action='store_true',
1519 default=False,
1520 help=('only for root directory, ' +
1521 'copy build.rs output to ./out/* and add a genrule to copy ' +
1522 './out/* to genrule output; for crates with code pattern: ' +
1523 'include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))'))
1524 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001525 '--debug',
1526 action='store_true',
1527 default=False,
1528 help='dump debug info into Android.bp')
1529 parser.add_argument(
1530 '--dependencies',
1531 action='store_true',
1532 default=False,
1533 help='dump debug info of dependent crates')
1534 parser.add_argument(
1535 '--device',
1536 action='store_true',
1537 default=False,
1538 help='run cargo also for a default device target')
1539 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001540 '--features',
1541 type=str,
1542 help=('pass features to cargo build, ' +
1543 'empty string means no default features'))
1544 parser.add_argument(
1545 '--global_defaults',
1546 type=str,
1547 help='add a defaults name to every module')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001548 parser.add_argument(
1549 '--host-first-multilib',
1550 action='store_true',
1551 default=False,
1552 help=('add a compile_multilib:"first" property ' +
1553 'to Android.bp host modules.'))
1554 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001555 '--ignore-cargo-errors',
1556 action='store_true',
1557 default=False,
1558 help='do not append cargo/rustc error messages to Android.bp')
1559 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001560 '--no-host',
1561 action='store_true',
1562 default=False,
1563 help='do not run cargo for the host; only for the device target')
1564 parser.add_argument(
1565 '--no-subdir',
1566 action='store_true',
1567 default=False,
1568 help='do not output anything for sub-directories')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001569 parser.add_argument(
1570 '--onefile',
1571 action='store_true',
1572 default=False,
1573 help=('output all into one ./Android.bp, default will generate ' +
1574 'one Android.bp per Cargo.toml in subdirectories'))
1575 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001576 '--patch',
1577 type=str,
1578 help='apply the given patch file to generated ./Android.bp')
1579 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001580 '--run',
1581 action='store_true',
1582 default=False,
1583 help='run it, default is dry-run')
1584 parser.add_argument('--rustflags', type=str, help='passing flags to rustc')
1585 parser.add_argument(
1586 '--skipcargo',
1587 action='store_true',
1588 default=False,
1589 help='skip cargo command, parse cargo.out, and generate Android.bp')
1590 parser.add_argument(
1591 '--tests',
1592 action='store_true',
1593 default=False,
1594 help='run cargo build --tests after normal build')
1595 parser.add_argument(
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001596 '--use-cargo-lock',
1597 action='store_true',
1598 default=False,
1599 help=('run cargo build with existing Cargo.lock ' +
1600 '(used when some latest dependent crates failed)'))
1601 parser.add_argument(
Joel Galensond9c4de62021-04-23 10:26:40 -07001602 '--min-sdk-version',
1603 type=str,
1604 help='Minimum SDK version')
1605 parser.add_argument(
1606 '--apex-available',
1607 nargs='*',
1608 help='Mark the main library as apex_available with the given apexes.')
1609 parser.add_argument(
Joel Galenson97e414a2021-05-27 09:42:32 -07001610 '--dependency-blocklist',
1611 nargs='*',
1612 default=[],
1613 help='Do not emit the given dependencies.')
1614 parser.add_argument(
Joel Galensonf6b3c912021-06-03 16:00:54 -07001615 '--test-blocklist',
1616 nargs='*',
1617 default=[],
1618 help=('Do not emit the given tests. ' +
1619 'Pass the path to the test file to exclude.'))
1620 parser.add_argument(
Joel Galenson3d6d1e72021-06-07 15:00:24 -07001621 '--cfg-blocklist',
1622 nargs='*',
1623 default=[],
1624 help='Do not emit the given cfg.')
1625 parser.add_argument(
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001626 '--no-test-mapping',
1627 action='store_true',
1628 default=False,
1629 help='Do not generate a TEST_MAPPING file. Use only to speed up debugging.')
1630 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001631 '--verbose',
1632 action='store_true',
1633 default=False,
1634 help='echo executed commands')
1635 parser.add_argument(
1636 '--vv',
1637 action='store_true',
1638 default=False,
1639 help='run cargo with -vv instead of default -v')
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001640 parser.add_argument(
1641 '--dump-config-and-exit',
1642 type=str,
1643 help=('Dump command-line arguments (minus this flag) to a config file and exit. ' +
1644 'This is intended to help migrate from command line options to config files.'))
1645 parser.add_argument(
1646 '--config',
1647 type=str,
1648 help=('Load command-line options from the given config file. ' +
1649 'Options in this file will override those passed on the command line.'))
1650 return parser
1651
1652
1653def parse_args(parser):
1654 """Parses command-line options."""
1655 args = parser.parse_args()
1656 # Use the values specified in a config file if one was found.
1657 if args.config:
1658 with open(args.config, 'r') as f:
1659 config = json.load(f)
1660 args_dict = vars(args)
1661 for arg in config:
1662 args_dict[arg.replace('-', '_')] = config[arg]
1663 return args
1664
1665
1666def dump_config(parser, args):
1667 """Writes the non-default command-line options to the specified file."""
1668 args_dict = vars(args)
1669 # Filter out the arguments that have their default value.
Joel Galenson367360c2021-04-29 14:31:43 -07001670 # Also filter certain "temporary" arguments.
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001671 non_default_args = {}
1672 for arg in args_dict:
Joel Galenson367360c2021-04-29 14:31:43 -07001673 if args_dict[arg] != parser.get_default(
1674 arg) and arg != 'dump_config_and_exit' and arg != 'no_test_mapping':
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001675 non_default_args[arg.replace('_', '-')] = args_dict[arg]
1676 # Write to the specified file.
1677 with open(args.dump_config_and_exit, 'w') as f:
1678 json.dump(non_default_args, f, indent=2, sort_keys=True)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001679
1680
1681def main():
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001682 parser = get_parser()
1683 args = parse_args(parser)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001684 if not args.run: # default is dry-run
1685 print(DRY_RUN_NOTE)
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001686 if args.dump_config_and_exit:
1687 dump_config(parser, args)
1688 else:
1689 Runner(args).run_cargo().gen_bp().apply_patch().dump_test_mapping_files()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001690
1691
1692if __name__ == '__main__':
1693 main()