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