blob: 38a816ee3755dd667ce8f37ab2964239d0a6219c [file] [log] [blame]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001#!/usr/bin/env python
2#
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
30 This is equivalent to using the --cargo flag to add extra builds:
31 cargo2android.py --run
32 --cargo "build"
33 --cargo "build --target x86_64-unknown-linux-gnu"
34
35 On MacOS, use x86_64-apple-darwin as target triple.
36 Here the host target triple is used as a fake cross compilation target.
37 If the crate's Cargo.toml and environment configuration works for an
38 Android target, use that target triple as the cargo build flag.
39
40(3) To build default and test crates, for host and device, use both
41 --device and --tests flags:
42 cargo2android.py --run --device --tests
43
44 This is equivalent to using the --cargo flag to add extra builds:
45 cargo2android.py --run
46 --cargo "build"
47 --cargo "build --tests"
48 --cargo "build --target x86_64-unknown-linux-gnu"
49 --cargo "build --tests --target x86_64-unknown-linux-gnu"
50
51Since Android rust builds by default treat all warnings as errors,
52if there are rustc warning messages, this script will add
53deny_warnings:false to the owner crate module in Android.bp.
54"""
55
56from __future__ import print_function
57
58import argparse
59import os
60import os.path
61import re
Andrew Walbran80e90be2020-06-09 14:33:18 +010062import sys
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080063
64RENAME_MAP = {
65 # This map includes all changes to the default rust library module
66 # names to resolve name conflicts or avoid confusion.
67 'libbacktrace': 'libbacktrace_rust',
68 'libgcc': 'libgcc_rust',
69 'liblog': 'liblog_rust',
70 'libsync': 'libsync_rust',
71 'libx86_64': 'libx86_64_rust',
72}
73
74# Header added to all generated Android.bp files.
Andrew Walbran80e90be2020-06-09 14:33:18 +010075ANDROID_BP_HEADER = '// This file is generated by cargo2android.py {args}.\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080076
77CARGO_OUT = 'cargo.out' # Name of file to keep cargo build -v output.
78
79TARGET_TMP = 'target.tmp' # Name of temporary output directory.
80
81# Message to be displayed when this script is called without the --run flag.
82DRY_RUN_NOTE = (
83 'Dry-run: This script uses ./' + TARGET_TMP + ' for output directory,\n' +
84 'runs cargo clean, runs cargo build -v, saves output to ./cargo.out,\n' +
85 'and writes to Android.bp in the current and subdirectories.\n\n' +
86 'To do do all of the above, use the --run flag.\n' +
87 'See --help for other flags, and more usage notes in this script.\n')
88
89# Cargo -v output of a call to rustc.
90RUSTC_PAT = re.compile('^ +Running `rustc (.*)`$')
91
92# Cargo -vv output of a call to rustc could be split into multiple lines.
93# Assume that the first line will contain some CARGO_* env definition.
94RUSTC_VV_PAT = re.compile('^ +Running `.*CARGO_.*=.*$')
95# The combined -vv output rustc command line pattern.
96RUSTC_VV_CMD_ARGS = re.compile('^ *Running `.*CARGO_.*=.* rustc (.*)`$')
97
98# Cargo -vv output of a "cc" or "ar" command; all in one line.
99CC_AR_VV_PAT = re.compile(r'^\[([^ ]*)[^\]]*\] running:? "(cc|ar)" (.*)$')
100# Some package, such as ring-0.13.5, has pattern '... running "cc"'.
101
102# Rustc output of file location path pattern for a warning message.
103WARNING_FILE_PAT = re.compile('^ *--> ([^:]*):[0-9]+')
104
105# Rust package name with suffix -d1.d2.d3.
106VERSION_SUFFIX_PAT = re.compile(r'^(.*)-[0-9]+\.[0-9]+\.[0-9]+$')
107
108
109def altered_name(name):
110 return RENAME_MAP[name] if (name in RENAME_MAP) else name
111
112
113def is_build_crate_name(name):
114 # We added special prefix to build script crate names.
115 return name.startswith('build_script_')
116
117
118def is_dependent_file_path(path):
119 # Absolute or dependent '.../' paths are not main files of this crate.
120 return path.startswith('/') or path.startswith('.../')
121
122
123def get_module_name(crate): # to sort crates in a list
124 return crate.module_name
125
126
127def pkg2crate_name(s):
128 return s.replace('-', '_').replace('.', '_')
129
130
131def file_base_name(path):
132 return os.path.splitext(os.path.basename(path))[0]
133
134
135def test_base_name(path):
136 return pkg2crate_name(file_base_name(path))
137
138
139def unquote(s): # remove quotes around str
140 if s and len(s) > 1 and s[0] == '"' and s[-1] == '"':
141 return s[1:-1]
142 return s
143
144
145def remove_version_suffix(s): # remove -d1.d2.d3 suffix
146 if VERSION_SUFFIX_PAT.match(s):
147 return VERSION_SUFFIX_PAT.match(s).group(1)
148 return s
149
150
151def short_out_name(pkg, s): # replace /.../pkg-*/out/* with .../out/*
152 return re.sub('^/.*/' + pkg + '-[0-9a-f]*/out/', '.../out/', s)
153
154
155def escape_quotes(s): # replace '"' with '\\"'
156 return s.replace('"', '\\"')
157
158
159class Crate(object):
160 """Information of a Rust crate to collect/emit for an Android.bp module."""
161
162 def __init__(self, runner, outf_name):
163 # Remembered global runner and its members.
164 self.runner = runner
165 self.debug = runner.args.debug
166 self.cargo_dir = '' # directory of my Cargo.toml
167 self.outf_name = outf_name # path to Android.bp
168 self.outf = None # open file handle of outf_name during dump*
169 # Variants/results that could be merged from multiple rustc lines.
170 self.host_supported = False
171 self.device_supported = False
172 self.has_warning = False
173 # Android module properties derived from rustc parameters.
174 self.module_name = '' # unique in Android build system
175 self.module_type = '' # rust_{binary,library,test}[_host] etc.
176 self.root_pkg = '' # parent package name of a sub/test packge, from -L
177 self.srcs = list() # main_src or merged multiple source files
178 self.stem = '' # real base name of output file
179 # Kept parsed status
180 self.errors = '' # all errors found during parsing
181 self.line_num = 1 # runner told input source line number
182 self.line = '' # original rustc command line parameters
183 # Parameters collected from rustc command line.
184 self.crate_name = '' # follows --crate-name
185 self.main_src = '' # follows crate_name parameter, shortened
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700186 self.crate_types = list() # follows --crate-type
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800187 self.cfgs = list() # follows --cfg, without feature= prefix
188 self.features = list() # follows --cfg, name in 'feature="..."'
189 self.codegens = list() # follows -C, some ignored
190 self.externs = list() # follows --extern
191 self.core_externs = list() # first part of self.externs elements
192 self.static_libs = list() # e.g. -l static=host_cpuid
193 self.shared_libs = list() # e.g. -l dylib=wayland-client, -l z
194 self.cap_lints = '' # follows --cap-lints
195 self.emit_list = '' # e.g., --emit=dep-info,metadata,link
196 self.edition = '2015' # rustc default, e.g., --edition=2018
197 self.target = '' # follows --target
198
199 def write(self, s):
200 # convenient way to output one line at a time with EOL.
201 self.outf.write(s + '\n')
202
203 def same_flags(self, other):
204 # host_supported, device_supported, has_warning are not compared but merged
205 # target is not compared, to merge different target/host modules
206 # externs is not compared; only core_externs is compared
207 return (not self.errors and not other.errors and
208 self.edition == other.edition and
209 self.cap_lints == other.cap_lints and
210 self.emit_list == other.emit_list and
211 self.core_externs == other.core_externs and
212 self.codegens == other.codegens and
213 self.features == other.features and
214 self.static_libs == other.static_libs and
215 self.shared_libs == other.shared_libs and self.cfgs == other.cfgs)
216
217 def merge_host_device(self, other):
218 """Returns true if attributes are the same except host/device support."""
219 return (self.crate_name == other.crate_name and
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700220 self.crate_types == other.crate_types and
221 self.main_src == other.main_src and
222 # before merge, each test module has an unique module name and stem
223 (self.stem == other.stem or self.crate_types == ['test']) and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800224 self.root_pkg == other.root_pkg and not self.skip_crate() and
225 self.same_flags(other))
226
227 def merge_test(self, other):
228 """Returns true if self and other are tests of same root_pkg."""
229 # Before merger, each test has its own crate_name.
230 # A merged test uses its source file base name as output file name,
231 # so a test is mergeable only if its base name equals to its crate name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700232 return (self.crate_types == other.crate_types and
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700233 self.crate_types == ['test'] and self.root_pkg == other.root_pkg and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800234 not self.skip_crate() and
235 other.crate_name == test_base_name(other.main_src) and
236 (len(self.srcs) > 1 or
237 (self.crate_name == test_base_name(self.main_src)) and
238 self.host_supported == other.host_supported and
239 self.device_supported == other.device_supported) and
240 self.same_flags(other))
241
242 def merge(self, other, outf_name):
243 """Try to merge crate into self."""
244 should_merge_host_device = self.merge_host_device(other)
245 should_merge_test = False
246 if not should_merge_host_device:
247 should_merge_test = self.merge_test(other)
248 # A for-device test crate can be merged with its for-host version,
249 # or merged with a different test for the same host or device.
250 # Since we run cargo once for each device or host, test crates for the
251 # first device or host will be merged first. Then test crates for a
252 # different device or host should be allowed to be merged into a
253 # previously merged one, maybe for a different device or host.
254 if should_merge_host_device or should_merge_test:
255 self.runner.init_bp_file(outf_name)
256 with open(outf_name, 'a') as outf: # to write debug info
257 self.outf = outf
258 other.outf = outf
259 self.do_merge(other, should_merge_test)
260 return True
261 return False
262
263 def do_merge(self, other, should_merge_test):
264 """Merge attributes of other to self."""
265 if self.debug:
266 self.write('\n// Before merge definition (1):')
267 self.dump_debug_info()
268 self.write('\n// Before merge definition (2):')
269 other.dump_debug_info()
270 # Merge properties of other to self.
271 self.host_supported = self.host_supported or other.host_supported
272 self.device_supported = self.device_supported or other.device_supported
273 self.has_warning = self.has_warning or other.has_warning
274 if not self.target: # okay to keep only the first target triple
275 self.target = other.target
276 # decide_module_type sets up default self.stem,
277 # which can be changed if self is a merged test module.
278 self.decide_module_type()
279 if should_merge_test:
280 self.srcs.append(other.main_src)
281 # use a short unique name as the merged module name.
282 prefix = self.root_pkg + '_tests'
283 self.module_name = self.runner.claim_module_name(prefix, self, 0)
284 self.stem = self.module_name
285 # This normalized root_pkg name although might be the same
286 # as other module's crate_name, it is not actually used for
287 # output file name. A merged test module always have multiple
288 # source files and each source file base name is used as
289 # its output file name.
290 self.crate_name = pkg2crate_name(self.root_pkg)
291 if self.debug:
292 self.write('\n// After merge definition (1):')
293 self.dump_debug_info()
294
295 def find_cargo_dir(self):
296 """Deepest directory with Cargo.toml and contains the main_src."""
297 if not is_dependent_file_path(self.main_src):
298 dir_name = os.path.dirname(self.main_src)
299 while dir_name:
300 if os.path.exists(dir_name + '/Cargo.toml'):
301 self.cargo_dir = dir_name
302 return
303 dir_name = os.path.dirname(dir_name)
304
305 def parse(self, line_num, line):
306 """Find important rustc arguments to convert to Android.bp properties."""
307 self.line_num = line_num
308 self.line = line
309 args = line.split() # Loop through every argument of rustc.
310 i = 0
311 while i < len(args):
312 arg = args[i]
313 if arg == '--crate-name':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700314 i += 1
315 self.crate_name = args[i]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800316 elif arg == '--crate-type':
317 i += 1
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700318 # cargo calls rustc with multiple --crate-type flags.
319 # rustc can accept:
320 # --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
321 self.crate_types.append(args[i])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800322 elif arg == '--test':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700323 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800324 elif arg == '--target':
325 i += 1
326 self.target = args[i]
327 elif arg == '--cfg':
328 i += 1
329 if args[i].startswith('\'feature='):
330 self.features.append(unquote(args[i].replace('\'feature=', '')[:-1]))
331 else:
332 self.cfgs.append(args[i])
333 elif arg == '--extern':
334 i += 1
335 extern_names = re.sub('=/[^ ]*/deps/', ' = ', args[i])
336 self.externs.append(extern_names)
337 self.core_externs.append(re.sub(' = .*', '', extern_names))
338 elif arg == '-C': # codegen options
339 i += 1
340 # ignore options not used in Android
341 if not (args[i].startswith('debuginfo=') or
342 args[i].startswith('extra-filename=') or
343 args[i].startswith('incremental=') or
344 args[i].startswith('metadata=')):
345 self.codegens.append(args[i])
346 elif arg == '--cap-lints':
347 i += 1
348 self.cap_lints = args[i]
349 elif arg == '-L':
350 i += 1
351 if args[i].startswith('dependency=') and args[i].endswith('/deps'):
352 if '/' + TARGET_TMP + '/' in args[i]:
353 self.root_pkg = re.sub(
354 '^.*/', '', re.sub('/' + TARGET_TMP + '/.*/deps$', '', args[i]))
355 else:
356 self.root_pkg = re.sub('^.*/', '',
357 re.sub('/[^/]+/[^/]+/deps$', '', args[i]))
358 self.root_pkg = remove_version_suffix(self.root_pkg)
359 elif arg == '-l':
360 i += 1
361 if args[i].startswith('static='):
362 self.static_libs.append(re.sub('static=', '', args[i]))
363 elif args[i].startswith('dylib='):
364 self.shared_libs.append(re.sub('dylib=', '', args[i]))
365 else:
366 self.shared_libs.append(args[i])
367 elif arg == '--out-dir' or arg == '--color': # ignored
368 i += 1
369 elif arg.startswith('--error-format=') or arg.startswith('--json='):
370 _ = arg # ignored
371 elif arg.startswith('--emit='):
372 self.emit_list = arg.replace('--emit=', '')
373 elif arg.startswith('--edition='):
374 self.edition = arg.replace('--edition=', '')
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700375 elif not arg.startswith('-'):
376 # shorten imported crate main source paths like $HOME/.cargo/
377 # registry/src/github.com-1ecc6299db9ec823/memchr-2.3.3/src/lib.rs
378 self.main_src = re.sub(r'^/[^ ]*/registry/src/', '.../', args[i])
379 self.main_src = re.sub(r'^\.\.\./github.com-[0-9a-f]*/', '.../',
380 self.main_src)
381 self.find_cargo_dir()
382 if self.cargo_dir and not self.runner.args.onefile:
383 # Write to Android.bp in the subdirectory with Cargo.toml.
384 self.outf_name = self.cargo_dir + '/Android.bp'
385 self.main_src = self.main_src[len(self.cargo_dir) + 1:]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800386 else:
387 self.errors += 'ERROR: unknown ' + arg + '\n'
388 i += 1
389 if not self.crate_name:
390 self.errors += 'ERROR: missing --crate-name\n'
391 if not self.main_src:
392 self.errors += 'ERROR: missing main source file\n'
393 else:
394 self.srcs.append(self.main_src)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700395 if not self.crate_types:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800396 # Treat "--cfg test" as "--test"
397 if 'test' in self.cfgs:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700398 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800399 else:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700400 self.errors += 'ERROR: missing --crate-type or --test\n'
401 elif len(self.crate_types) > 1:
402 if 'test' in self.crate_types:
403 self.errors += 'ERROR: cannot handle both --crate-type and --test\n'
404 if 'lib' in self.crate_types and 'rlib' in self.crate_types:
405 self.errors += 'ERROR: cannot generate both lib and rlib crate types\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800406 if not self.root_pkg:
407 self.root_pkg = self.crate_name
408 if self.target:
409 self.device_supported = True
410 self.host_supported = True # assume host supported for all builds
411 self.cfgs = sorted(set(self.cfgs))
412 self.features = sorted(set(self.features))
413 self.codegens = sorted(set(self.codegens))
414 self.externs = sorted(set(self.externs))
415 self.core_externs = sorted(set(self.core_externs))
416 self.static_libs = sorted(set(self.static_libs))
417 self.shared_libs = sorted(set(self.shared_libs))
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700418 self.crate_types = sorted(set(self.crate_types))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800419 self.decide_module_type()
420 self.module_name = altered_name(self.stem)
421 return self
422
423 def dump_line(self):
424 self.write('\n// Line ' + str(self.line_num) + ' ' + self.line)
425
426 def feature_list(self):
427 """Return a string of main_src + "feature_list"."""
428 pkg = self.main_src
429 if pkg.startswith('.../'): # keep only the main package name
430 pkg = re.sub('/.*', '', pkg[4:])
431 if not self.features:
432 return pkg
433 return pkg + ' "' + ','.join(self.features) + '"'
434
435 def dump_skip_crate(self, kind):
436 if self.debug:
437 self.write('\n// IGNORED: ' + kind + ' ' + self.main_src)
438 return self
439
440 def skip_crate(self):
441 """Return crate_name or a message if this crate should be skipped."""
442 if is_build_crate_name(self.crate_name):
443 return self.crate_name
444 if is_dependent_file_path(self.main_src):
445 return 'dependent crate'
446 return ''
447
448 def dump(self):
449 """Dump all error/debug/module code to the output .bp file."""
450 self.runner.init_bp_file(self.outf_name)
451 with open(self.outf_name, 'a') as outf:
452 self.outf = outf
453 if self.errors:
454 self.dump_line()
455 self.write(self.errors)
456 elif self.skip_crate():
457 self.dump_skip_crate(self.skip_crate())
458 else:
459 if self.debug:
460 self.dump_debug_info()
461 self.dump_android_module()
462
463 def dump_debug_info(self):
464 """Dump parsed data, when cargo2android is called with --debug."""
465
466 def dump(name, value):
467 self.write('//%12s = %s' % (name, value))
468
469 def opt_dump(name, value):
470 if value:
471 dump(name, value)
472
473 def dump_list(fmt, values):
474 for v in values:
475 self.write(fmt % v)
476
477 self.dump_line()
478 dump('module_name', self.module_name)
479 dump('crate_name', self.crate_name)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700480 dump('crate_types', self.crate_types)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800481 dump('main_src', self.main_src)
482 dump('has_warning', self.has_warning)
483 dump('for_host', self.host_supported)
484 dump('for_device', self.device_supported)
485 dump('module_type', self.module_type)
486 opt_dump('target', self.target)
487 opt_dump('edition', self.edition)
488 opt_dump('emit_list', self.emit_list)
489 opt_dump('cap_lints', self.cap_lints)
490 dump_list('// cfg = %s', self.cfgs)
491 dump_list('// cfg = \'feature "%s"\'', self.features)
492 # TODO(chh): escape quotes in self.features, but not in other dump_list
493 dump_list('// codegen = %s', self.codegens)
494 dump_list('// externs = %s', self.externs)
495 dump_list('// -l static = %s', self.static_libs)
496 dump_list('// -l (dylib) = %s', self.shared_libs)
497
498 def dump_android_module(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700499 # Dump one Android module per crate_type.
500 if len(self.crate_types) == 1:
501 # do not change self.stem or self.module_name
502 self.dump_one_android_module(self.crate_types[0])
503 return
504 for crate_type in self.crate_types:
505 self.decide_one_module_type(crate_type)
506 self.dump_one_android_module(crate_type)
507
508 def dump_one_android_module(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800509 """Dump one Android module definition."""
510 if not self.module_type:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700511 self.write('\nERROR: unknown crate_type ' + crate_type)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800512 return
513 self.write('\n' + self.module_type + ' {')
514 self.dump_android_core_properties()
515 if self.edition:
516 self.write(' edition: "' + self.edition + '",')
517 self.dump_android_property_list('features', '"%s"', self.features)
518 cfg_fmt = '"--cfg %s"'
519 if self.cap_lints:
520 allowed = '"--cap-lints ' + self.cap_lints + '"'
521 if not self.cfgs:
522 self.write(' flags: [' + allowed + '],')
523 else:
524 self.write(' flags: [\n ' + allowed + ',')
525 self.dump_android_property_list_items(cfg_fmt, self.cfgs)
526 self.write(' ],')
527 else:
528 self.dump_android_property_list('flags', cfg_fmt, self.cfgs)
529 if self.externs:
530 self.dump_android_externs()
531 self.dump_android_property_list('static_libs', '"lib%s"', self.static_libs)
532 self.dump_android_property_list('shared_libs', '"lib%s"', self.shared_libs)
533 self.write('}')
534
535 def test_module_name(self):
536 """Return a unique name for a test module."""
537 # root_pkg+'_tests_'+(crate_name|source_file_path)
538 suffix = self.crate_name
539 if not suffix:
540 suffix = re.sub('/', '_', re.sub('.rs$', '', self.main_src))
541 return self.root_pkg + '_tests_' + suffix
542
543 def decide_module_type(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700544 # Use the first crate type for the default/first module.
545 crate_type = self.crate_types[0] if self.crate_types else ''
546 self.decide_one_module_type(crate_type)
547
548 def decide_one_module_type(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800549 """Decide which Android module type to use."""
550 host = '' if self.device_supported else '_host'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700551 if crate_type == 'bin': # rust_binary[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800552 self.module_type = 'rust_binary' + host
553 self.stem = self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700554 self.module_name = altered_name(self.stem)
555 elif crate_type == 'lib': # rust_library[_host]_rlib
556 # TODO(chh): should this be rust_library[_host]?
557 # Assuming that Cargo.toml do not use both 'lib' and 'rlib',
558 # because we map them both to rlib.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800559 self.module_type = 'rust_library' + host + '_rlib'
560 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700561 self.module_name = altered_name(self.stem)
562 elif crate_type == 'rlib': # rust_library[_host]_rlib
563 self.module_type = 'rust_library' + host + '_rlib'
564 self.stem = 'lib' + self.crate_name
565 self.module_name = altered_name(self.stem)
566 elif crate_type == 'dylib': # rust_library[_host]_dylib
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800567 self.module_type = 'rust_library' + host + '_dylib'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700568 self.stem = 'lib' + self.crate_name
569 self.module_name = altered_name(self.stem) + '_dylib'
570 elif crate_type == 'cdylib': # rust_library[_host]_shared
571 self.module_type = 'rust_library' + host + '_shared'
572 self.stem = 'lib' + self.crate_name
573 self.module_name = altered_name(self.stem) + '_shared'
574 elif crate_type == 'staticlib': # rust_library[_host]_static
575 self.module_type = 'rust_library' + host + '_static'
576 self.stem = 'lib' + self.crate_name
577 self.module_name = altered_name(self.stem) + '_static'
578 elif crate_type == 'test': # rust_test[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800579 self.module_type = 'rust_test' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700580 # Before do_merge, stem name is based on the --crate-name parameter.
581 # and test module name is based on stem.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800582 self.stem = self.test_module_name()
583 # self.stem will be changed after merging with other tests.
584 # self.stem is NOT used for final test binary name.
585 # rust_test uses each source file base name as its output file name,
586 # unless crate_name is specified by user in Cargo.toml.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700587 # In do_merge, this function is called again, with a module_name.
588 # We make sure that the module name is unique in each package.
589 if self.module_name:
590 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add
591 # different suffixes and distinguish multiple tests of the same
592 # crate name. We ignore -C and use claim_module_name to get
593 # unique sequential suffix.
594 self.module_name = self.runner.claim_module_name(
595 self.module_name, self, 0)
596 # Now the module name is unique, stem should also match and unique.
597 self.stem = self.module_name
598 elif crate_type == 'proc-macro': # rust_proc_macro
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800599 self.module_type = 'rust_proc_macro'
600 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700601 self.module_name = altered_name(self.stem)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800602 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]?
603 self.module_type = ''
604 self.stem = ''
605
606 def dump_android_property_list_items(self, fmt, values):
607 for v in values:
608 # fmt has quotes, so we need escape_quotes(v)
609 self.write(' ' + (fmt % escape_quotes(v)) + ',')
610
611 def dump_android_property_list(self, name, fmt, values):
612 if values:
613 self.write(' ' + name + ': [')
614 self.dump_android_property_list_items(fmt, values)
615 self.write(' ],')
616
617 def dump_android_core_properties(self):
618 """Dump the module header, name, stem, etc."""
619 self.write(' name: "' + self.module_name + '",')
620 if self.stem != self.module_name:
621 self.write(' stem: "' + self.stem + '",')
622 if self.has_warning and not self.cap_lints:
623 self.write(' deny_warnings: false,')
624 if self.host_supported and self.device_supported:
625 self.write(' host_supported: true,')
626 self.write(' crate_name: "' + self.crate_name + '",')
627 if len(self.srcs) > 1:
628 self.srcs = sorted(set(self.srcs))
629 self.dump_android_property_list('srcs', '"%s"', self.srcs)
630 else:
631 self.write(' srcs: ["' + self.main_src + '"],')
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700632 if 'test' in self.crate_types:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800633 # self.root_pkg can have multiple test modules, with different *_tests[n]
634 # names, but their executables can all be installed under the same _tests
635 # directory. When built from Cargo.toml, all tests should have different
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700636 # file or crate names. So we used (root_pkg + '_tests') name as the
637 # relative_install_path.
638 # However, some package like 'slab' can have non-mergeable tests that
639 # must be separated by different module names. So, here we no longer
640 # emit relative_install_path.
641 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800642 self.write(' test_suites: ["general-tests"],')
643 self.write(' auto_gen_config: true,')
644
645 def dump_android_externs(self):
646 """Dump the dependent rlibs and dylibs property."""
647 so_libs = list()
648 rust_libs = ''
649 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$')
650 for lib in self.externs:
651 # normal value of lib: "libc = liblibc-*.rlib"
652 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib"
653 # we should use "libgetrandom", not "lib" + "getrandom_package"
654 groups = deps_libname.match(lib)
655 if groups is not None:
656 lib_name = groups.group(1)
657 else:
658 lib_name = re.sub(' .*$', '', lib)
659 if lib.endswith('.rlib') or lib.endswith('.rmeta'):
660 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta.
661 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n'
662 elif lib.endswith('.so'):
663 so_libs.append(lib_name)
664 else:
665 rust_libs += ' // ERROR: unknown type of lib ' + lib_name + '\n'
666 if rust_libs:
667 self.write(' rlibs: [\n' + rust_libs + ' ],')
668 # Are all dependent .so files proc_macros?
669 # TODO(chh): Separate proc_macros and dylib.
670 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs)
671
672
673class ARObject(object):
674 """Information of an "ar" link command."""
675
676 def __init__(self, runner, outf_name):
677 # Remembered global runner and its members.
678 self.runner = runner
679 self.pkg = ''
680 self.outf_name = outf_name # path to Android.bp
681 # "ar" arguments
682 self.line_num = 1
683 self.line = ''
684 self.flags = '' # e.g. "crs"
685 self.lib = '' # e.g. "/.../out/lib*.a"
686 self.objs = list() # e.g. "/.../out/.../*.o"
687
688 def parse(self, pkg, line_num, args_line):
689 """Collect ar obj/lib file names."""
690 self.pkg = pkg
691 self.line_num = line_num
692 self.line = args_line
693 args = args_line.split()
694 num_args = len(args)
695 if num_args < 3:
696 print('ERROR: "ar" command has too few arguments', args_line)
697 else:
698 self.flags = unquote(args[0])
699 self.lib = unquote(args[1])
700 self.objs = sorted(set(map(unquote, args[2:])))
701 return self
702
703 def write(self, s):
704 self.outf.write(s + '\n')
705
706 def dump_debug_info(self):
707 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line)
708 self.write('// ar_object for %12s' % self.pkg)
709 self.write('// flags = %s' % self.flags)
710 self.write('// lib = %s' % short_out_name(self.pkg, self.lib))
711 for o in self.objs:
712 self.write('// obj = %s' % short_out_name(self.pkg, o))
713
714 def dump_android_lib(self):
715 """Write cc_library_static into Android.bp."""
716 self.write('\ncc_library_static {')
717 self.write(' name: "' + file_base_name(self.lib) + '",')
718 self.write(' host_supported: true,')
719 if self.flags != 'crs':
720 self.write(' // ar flags = %s' % self.flags)
721 if self.pkg not in self.runner.pkg_obj2cc:
722 self.write(' ERROR: cannot find source files.\n}')
723 return
724 self.write(' srcs: [')
725 obj2cc = self.runner.pkg_obj2cc[self.pkg]
726 # Note: wflags are ignored.
727 dflags = list()
728 fflags = list()
729 for obj in self.objs:
730 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",')
731 # TODO(chh): union of dflags and flags of all obj
732 # Now, just a temporary hack that uses the last obj's flags
733 dflags = obj2cc[obj].dflags
734 fflags = obj2cc[obj].fflags
735 self.write(' ],')
736 self.write(' cflags: [')
737 self.write(' "-O3",') # TODO(chh): is this default correct?
738 self.write(' "-Wno-error",')
739 for x in fflags:
740 self.write(' "-f' + x + '",')
741 for x in dflags:
742 self.write(' "-D' + x + '",')
743 self.write(' ],')
744 self.write('}')
745
746 def dump(self):
747 """Dump error/debug/module info to the output .bp file."""
748 self.runner.init_bp_file(self.outf_name)
749 with open(self.outf_name, 'a') as outf:
750 self.outf = outf
751 if self.runner.args.debug:
752 self.dump_debug_info()
753 self.dump_android_lib()
754
755
756class CCObject(object):
757 """Information of a "cc" compilation command."""
758
759 def __init__(self, runner, outf_name):
760 # Remembered global runner and its members.
761 self.runner = runner
762 self.pkg = ''
763 self.outf_name = outf_name # path to Android.bp
764 # "cc" arguments
765 self.line_num = 1
766 self.line = ''
767 self.src = ''
768 self.obj = ''
769 self.dflags = list() # -D flags
770 self.fflags = list() # -f flags
771 self.iflags = list() # -I flags
772 self.wflags = list() # -W flags
773 self.other_args = list()
774
775 def parse(self, pkg, line_num, args_line):
776 """Collect cc compilation flags and src/out file names."""
777 self.pkg = pkg
778 self.line_num = line_num
779 self.line = args_line
780 args = args_line.split()
781 i = 0
782 while i < len(args):
783 arg = args[i]
784 if arg == '"-c"':
785 i += 1
786 if args[i].startswith('"-o'):
787 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c"
788 self.obj = unquote(args[i])[2:]
789 i += 1
790 self.src = unquote(args[i])
791 else:
792 self.src = unquote(args[i])
793 elif arg == '"-o"':
794 i += 1
795 self.obj = unquote(args[i])
796 elif arg == '"-I"':
797 i += 1
798 self.iflags.append(unquote(args[i]))
799 elif arg.startswith('"-D'):
800 self.dflags.append(unquote(args[i])[2:])
801 elif arg.startswith('"-f'):
802 self.fflags.append(unquote(args[i])[2:])
803 elif arg.startswith('"-W'):
804 self.wflags.append(unquote(args[i])[2:])
805 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or
806 arg == '"-g3"'):
807 # ignore -O -m64 -g
808 self.other_args.append(unquote(args[i]))
809 i += 1
810 self.dflags = sorted(set(self.dflags))
811 self.fflags = sorted(set(self.fflags))
812 # self.wflags is not sorted because some are order sensitive
813 # and we ignore them anyway.
814 if self.pkg not in self.runner.pkg_obj2cc:
815 self.runner.pkg_obj2cc[self.pkg] = {}
816 self.runner.pkg_obj2cc[self.pkg][self.obj] = self
817 return self
818
819 def write(self, s):
820 self.outf.write(s + '\n')
821
822 def dump_debug_flags(self, name, flags):
823 self.write('// ' + name + ':')
824 for f in flags:
825 self.write('// %s' % f)
826
827 def dump(self):
828 """Dump only error/debug info to the output .bp file."""
829 if not self.runner.args.debug:
830 return
831 self.runner.init_bp_file(self.outf_name)
832 with open(self.outf_name, 'a') as outf:
833 self.outf = outf
834 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line)
835 self.write('// cc_object for %12s' % self.pkg)
836 self.write('// src = %s' % short_out_name(self.pkg, self.src))
837 self.write('// obj = %s' % short_out_name(self.pkg, self.obj))
838 self.dump_debug_flags('-I flags', self.iflags)
839 self.dump_debug_flags('-D flags', self.dflags)
840 self.dump_debug_flags('-f flags', self.fflags)
841 self.dump_debug_flags('-W flags', self.wflags)
842 if self.other_args:
843 self.dump_debug_flags('other args', self.other_args)
844
845
846class Runner(object):
847 """Main class to parse cargo -v output and print Android module definitions."""
848
849 def __init__(self, args):
850 self.bp_files = set() # Remember all output Android.bp files.
851 self.root_pkg = '' # name of package in ./Cargo.toml
852 # Saved flags, modes, and data.
853 self.args = args
854 self.dry_run = not args.run
855 self.skip_cargo = args.skipcargo
856 # All cc/ar objects, crates, dependencies, and warning files
857 self.cc_objects = list()
858 self.pkg_obj2cc = {}
859 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i]
860 self.ar_objects = list()
861 self.crates = list()
862 self.dependencies = list() # dependent and build script crates
863 self.warning_files = set()
864 # Keep a unique mapping from (module name) to crate
865 self.name_owners = {}
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700866 # Save and dump all errors from cargo to Android.bp.
867 self.errors = ''
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800868 # Default action is cargo clean, followed by build or user given actions.
869 if args.cargo:
870 self.cargo = ['clean'] + args.cargo
871 else:
872 self.cargo = ['clean', 'build']
873 default_target = '--target x86_64-unknown-linux-gnu'
874 if args.device:
875 self.cargo.append('build ' + default_target)
876 if args.tests:
877 self.cargo.append('build --tests')
878 self.cargo.append('build --tests ' + default_target)
879 elif args.tests:
880 self.cargo.append('build --tests')
881
882 def init_bp_file(self, name):
883 if name not in self.bp_files:
884 self.bp_files.add(name)
885 with open(name, 'w') as outf:
Andrew Walbran80e90be2020-06-09 14:33:18 +0100886 outf.write(ANDROID_BP_HEADER.format(args=' '.join(sys.argv[1:])))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800887
888 def claim_module_name(self, prefix, owner, counter):
889 """Return prefix if not owned yet, otherwise, prefix+str(counter)."""
890 while True:
891 name = prefix
892 if counter > 0:
893 name += str(counter)
894 if name not in self.name_owners:
895 self.name_owners[name] = owner
896 return name
897 if owner == self.name_owners[name]:
898 return name
899 counter += 1
900
901 def find_root_pkg(self):
902 """Read name of [package] in ./Cargo.toml."""
903 if not os.path.exists('./Cargo.toml'):
904 return
905 with open('./Cargo.toml', 'r') as inf:
906 pkg_section = re.compile(r'^ *\[package\]')
907 name = re.compile('^ *name *= * "([^"]*)"')
908 in_pkg = False
909 for line in inf:
910 if in_pkg:
911 if name.match(line):
912 self.root_pkg = name.match(line).group(1)
913 break
914 else:
915 in_pkg = pkg_section.match(line) is not None
916
917 def run_cargo(self):
918 """Calls cargo -v and save its output to ./cargo.out."""
919 if self.skip_cargo:
920 return self
921 cargo = './Cargo.toml'
922 if not os.access(cargo, os.R_OK):
923 print('ERROR: Cannot find or read', cargo)
924 return self
925 if not self.dry_run and os.path.exists('cargo.out'):
926 os.remove('cargo.out')
927 cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> cargo.out 2>&1'
928 for c in self.cargo:
929 features = ''
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -0700930 if c != 'clean':
931 if self.args.features is not None:
932 features = ' --no-default-features'
933 if self.args.features:
934 features += ' --features ' + self.args.features
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800935 cmd = 'cargo -vv ' if self.args.vv else 'cargo -v '
936 cmd += c + features + cmd_tail
937 if self.args.rustflags and c != 'clean':
938 cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
939 if self.dry_run:
940 print('Dry-run skip:', cmd)
941 else:
942 if self.args.verbose:
943 print('Running:', cmd)
944 with open('cargo.out', 'a') as cargo_out:
945 cargo_out.write('### Running: ' + cmd + '\n')
946 os.system(cmd)
947 return self
948
949 def dump_dependencies(self):
950 """Append dependencies and their features to Android.bp."""
951 if not self.dependencies:
952 return
953 dependent_list = list()
954 for c in self.dependencies:
955 dependent_list.append(c.feature_list())
956 sorted_dependencies = sorted(set(dependent_list))
957 self.init_bp_file('Android.bp')
958 with open('Android.bp', 'a') as outf:
959 outf.write('\n// dependent_library ["feature_list"]\n')
960 for s in sorted_dependencies:
961 outf.write('// ' + s + '\n')
962
963 def dump_pkg_obj2cc(self):
964 """Dump debug info of the pkg_obj2cc map."""
965 if not self.args.debug:
966 return
967 self.init_bp_file('Android.bp')
968 with open('Android.bp', 'a') as outf:
969 sorted_pkgs = sorted(self.pkg_obj2cc.keys())
970 for pkg in sorted_pkgs:
971 if not self.pkg_obj2cc[pkg]:
972 continue
973 outf.write('\n// obj => src for %s\n' % pkg)
974 obj2cc = self.pkg_obj2cc[pkg]
975 for obj in sorted(obj2cc.keys()):
976 outf.write('// ' + short_out_name(pkg, obj) + ' => ' +
977 short_out_name(pkg, obj2cc[obj].src) + '\n')
978
979 def gen_bp(self):
980 """Parse cargo.out and generate Android.bp files."""
981 if self.dry_run:
982 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp')
983 elif os.path.exists(CARGO_OUT):
984 self.find_root_pkg()
985 with open(CARGO_OUT, 'r') as cargo_out:
986 self.parse(cargo_out, 'Android.bp')
987 self.crates.sort(key=get_module_name)
988 for obj in self.cc_objects:
989 obj.dump()
990 self.dump_pkg_obj2cc()
991 for crate in self.crates:
992 crate.dump()
993 dumped_libs = set()
994 for lib in self.ar_objects:
995 if lib.pkg == self.root_pkg:
996 lib_name = file_base_name(lib.lib)
997 if lib_name not in dumped_libs:
998 dumped_libs.add(lib_name)
999 lib.dump()
1000 if self.args.dependencies and self.dependencies:
1001 self.dump_dependencies()
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001002 if self.errors:
1003 self.append_to_bp('\nErrors in ' + CARGO_OUT + ':\n' + self.errors)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001004 return self
1005
1006 def add_ar_object(self, obj):
1007 self.ar_objects.append(obj)
1008
1009 def add_cc_object(self, obj):
1010 self.cc_objects.append(obj)
1011
1012 def add_crate(self, crate):
1013 """Merge crate with someone in crates, or append to it. Return crates."""
1014 if crate.skip_crate():
1015 if self.args.debug: # include debug info of all crates
1016 self.crates.append(crate)
1017 if self.args.dependencies: # include only dependent crates
1018 if (is_dependent_file_path(crate.main_src) and
1019 not is_build_crate_name(crate.crate_name)):
1020 self.dependencies.append(crate)
1021 else:
1022 for c in self.crates:
1023 if c.merge(crate, 'Android.bp'):
1024 return
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001025 # If not merged, decide module type and name now.
1026 crate.decide_module_type()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001027 self.crates.append(crate)
1028
1029 def find_warning_owners(self):
1030 """For each warning file, find its owner crate."""
1031 missing_owner = False
1032 for f in self.warning_files:
1033 cargo_dir = '' # find lowest crate, with longest path
1034 owner = None # owner crate of this warning
1035 for c in self.crates:
1036 if (f.startswith(c.cargo_dir + '/') and
1037 len(cargo_dir) < len(c.cargo_dir)):
1038 cargo_dir = c.cargo_dir
1039 owner = c
1040 if owner:
1041 owner.has_warning = True
1042 else:
1043 missing_owner = True
1044 if missing_owner and os.path.exists('Cargo.toml'):
1045 # owner is the root cargo, with empty cargo_dir
1046 for c in self.crates:
1047 if not c.cargo_dir:
1048 c.has_warning = True
1049
1050 def rustc_command(self, n, rustc_line, line, outf_name):
1051 """Process a rustc command line from cargo -vv output."""
1052 # cargo build -vv output can have multiple lines for a rustc command
1053 # due to '\n' in strings for environment variables.
1054 # strip removes leading spaces and '\n' at the end
1055 new_rustc = (rustc_line.strip() + line) if rustc_line else line
1056 # Use an heuristic to detect the completions of a multi-line command.
1057 # This might fail for some very rare case, but easy to fix manually.
1058 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0:
1059 return new_rustc
1060 if RUSTC_VV_CMD_ARGS.match(new_rustc):
1061 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1)
1062 self.add_crate(Crate(self, outf_name).parse(n, args))
1063 else:
1064 self.assert_empty_vv_line(new_rustc)
1065 return ''
1066
1067 def cc_ar_command(self, n, groups, outf_name):
1068 pkg = groups.group(1)
1069 line = groups.group(3)
1070 if groups.group(2) == 'cc':
1071 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line))
1072 else:
1073 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line))
1074
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001075 def append_to_bp(self, line):
1076 self.init_bp_file('Android.bp')
1077 with open('Android.bp', 'a') as outf:
1078 outf.write(line)
1079
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001080 def assert_empty_vv_line(self, line):
1081 if line: # report error if line is not empty
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001082 self.append_to_bp('ERROR -vv line: ' + line)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001083 return ''
1084
1085 def parse(self, inf, outf_name):
1086 """Parse rustc and warning messages in inf, return a list of Crates."""
1087 n = 0 # line number
1088 prev_warning = False # true if the previous line was warning: ...
1089 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
1090 for line in inf:
1091 n += 1
1092 if line.startswith('warning: '):
1093 prev_warning = True
1094 rustc_line = self.assert_empty_vv_line(rustc_line)
1095 continue
1096 new_rustc = ''
1097 if RUSTC_PAT.match(line):
1098 args_line = RUSTC_PAT.match(line).group(1)
1099 self.add_crate(Crate(self, outf_name).parse(n, args_line))
1100 self.assert_empty_vv_line(rustc_line)
1101 elif rustc_line or RUSTC_VV_PAT.match(line):
1102 new_rustc = self.rustc_command(n, rustc_line, line, outf_name)
1103 elif CC_AR_VV_PAT.match(line):
1104 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name)
1105 elif prev_warning and WARNING_FILE_PAT.match(line):
1106 self.assert_empty_vv_line(rustc_line)
1107 fpath = WARNING_FILE_PAT.match(line).group(1)
1108 if fpath[0] != '/': # ignore absolute path
1109 self.warning_files.add(fpath)
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001110 elif line.startswith('error: ') or line.startswith('error[E'):
1111 self.errors += line
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001112 prev_warning = False
1113 rustc_line = new_rustc
1114 self.find_warning_owners()
1115
1116
1117def parse_args():
1118 """Parse main arguments."""
1119 parser = argparse.ArgumentParser('cargo2android')
1120 parser.add_argument(
1121 '--cargo',
1122 action='append',
1123 metavar='args_string',
1124 help=('extra cargo build -v args in a string, ' +
1125 'each --cargo flag calls cargo build -v once'))
1126 parser.add_argument(
1127 '--debug',
1128 action='store_true',
1129 default=False,
1130 help='dump debug info into Android.bp')
1131 parser.add_argument(
1132 '--dependencies',
1133 action='store_true',
1134 default=False,
1135 help='dump debug info of dependent crates')
1136 parser.add_argument(
1137 '--device',
1138 action='store_true',
1139 default=False,
1140 help='run cargo also for a default device target')
1141 parser.add_argument(
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001142 '--features',
1143 type=str,
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001144 help=('pass features to cargo build, ' +
1145 'empty string means no default features'))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001146 parser.add_argument(
1147 '--onefile',
1148 action='store_true',
1149 default=False,
1150 help=('output all into one ./Android.bp, default will generate ' +
1151 'one Android.bp per Cargo.toml in subdirectories'))
1152 parser.add_argument(
1153 '--run',
1154 action='store_true',
1155 default=False,
1156 help='run it, default is dry-run')
1157 parser.add_argument('--rustflags', type=str, help='passing flags to rustc')
1158 parser.add_argument(
1159 '--skipcargo',
1160 action='store_true',
1161 default=False,
1162 help='skip cargo command, parse cargo.out, and generate Android.bp')
1163 parser.add_argument(
1164 '--tests',
1165 action='store_true',
1166 default=False,
1167 help='run cargo build --tests after normal build')
1168 parser.add_argument(
1169 '--verbose',
1170 action='store_true',
1171 default=False,
1172 help='echo executed commands')
1173 parser.add_argument(
1174 '--vv',
1175 action='store_true',
1176 default=False,
1177 help='run cargo with -vv instead of default -v')
1178 return parser.parse_args()
1179
1180
1181def main():
1182 args = parse_args()
1183 if not args.run: # default is dry-run
1184 print(DRY_RUN_NOTE)
1185 Runner(args).run_cargo().gen_bp()
1186
1187
1188if __name__ == '__main__':
1189 main()