blob: 08f23fad80496cef42afad3cb2a449e68df82c1b [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 = {
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -070064 # This map includes all changes to the default rust module names
65 # to resolve name conflicts, avoid confusion, or work as plugin.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080066 'libbacktrace': 'libbacktrace_rust',
67 'libgcc': 'libgcc_rust',
68 'liblog': 'liblog_rust',
69 'libsync': 'libsync_rust',
70 'libx86_64': 'libx86_64_rust',
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -070071 'protoc_gen_rust': 'protoc-gen-rust',
72}
73
74RENAME_STEM_MAP = {
75 # This map includes all changes to the default rust module stem names,
76 # which is used for output files when different from the module name.
77 'protoc_gen_rust': 'protoc-gen-rust',
78}
79
80RENAME_DEFAULTS_MAP = {
81 # This map includes all changes to the default prefix of rust_default
82 # module names, to avoid conflict with existing Android modules.
83 'libc': 'rust_libc',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080084}
85
86# Header added to all generated Android.bp files.
Andrew Walbran80e90be2020-06-09 14:33:18 +010087ANDROID_BP_HEADER = '// This file is generated by cargo2android.py {args}.\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080088
89CARGO_OUT = 'cargo.out' # Name of file to keep cargo build -v output.
90
91TARGET_TMP = 'target.tmp' # Name of temporary output directory.
92
93# Message to be displayed when this script is called without the --run flag.
94DRY_RUN_NOTE = (
95 'Dry-run: This script uses ./' + TARGET_TMP + ' for output directory,\n' +
96 'runs cargo clean, runs cargo build -v, saves output to ./cargo.out,\n' +
97 'and writes to Android.bp in the current and subdirectories.\n\n' +
98 'To do do all of the above, use the --run flag.\n' +
99 'See --help for other flags, and more usage notes in this script.\n')
100
101# Cargo -v output of a call to rustc.
102RUSTC_PAT = re.compile('^ +Running `rustc (.*)`$')
103
104# Cargo -vv output of a call to rustc could be split into multiple lines.
105# Assume that the first line will contain some CARGO_* env definition.
106RUSTC_VV_PAT = re.compile('^ +Running `.*CARGO_.*=.*$')
107# The combined -vv output rustc command line pattern.
108RUSTC_VV_CMD_ARGS = re.compile('^ *Running `.*CARGO_.*=.* rustc (.*)`$')
109
110# Cargo -vv output of a "cc" or "ar" command; all in one line.
111CC_AR_VV_PAT = re.compile(r'^\[([^ ]*)[^\]]*\] running:? "(cc|ar)" (.*)$')
112# Some package, such as ring-0.13.5, has pattern '... running "cc"'.
113
114# Rustc output of file location path pattern for a warning message.
115WARNING_FILE_PAT = re.compile('^ *--> ([^:]*):[0-9]+')
116
117# Rust package name with suffix -d1.d2.d3.
118VERSION_SUFFIX_PAT = re.compile(r'^(.*)-[0-9]+\.[0-9]+\.[0-9]+$')
119
120
121def altered_name(name):
122 return RENAME_MAP[name] if (name in RENAME_MAP) else name
123
124
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700125def altered_stem(name):
126 return RENAME_STEM_MAP[name] if (name in RENAME_STEM_MAP) else name
127
128
129def altered_defaults(name):
130 return RENAME_DEFAULTS_MAP[name] if (name in RENAME_DEFAULTS_MAP) else name
131
132
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800133def is_build_crate_name(name):
134 # We added special prefix to build script crate names.
135 return name.startswith('build_script_')
136
137
138def is_dependent_file_path(path):
139 # Absolute or dependent '.../' paths are not main files of this crate.
140 return path.startswith('/') or path.startswith('.../')
141
142
143def get_module_name(crate): # to sort crates in a list
144 return crate.module_name
145
146
147def pkg2crate_name(s):
148 return s.replace('-', '_').replace('.', '_')
149
150
151def file_base_name(path):
152 return os.path.splitext(os.path.basename(path))[0]
153
154
155def test_base_name(path):
156 return pkg2crate_name(file_base_name(path))
157
158
159def unquote(s): # remove quotes around str
160 if s and len(s) > 1 and s[0] == '"' and s[-1] == '"':
161 return s[1:-1]
162 return s
163
164
165def remove_version_suffix(s): # remove -d1.d2.d3 suffix
166 if VERSION_SUFFIX_PAT.match(s):
167 return VERSION_SUFFIX_PAT.match(s).group(1)
168 return s
169
170
171def short_out_name(pkg, s): # replace /.../pkg-*/out/* with .../out/*
172 return re.sub('^/.*/' + pkg + '-[0-9a-f]*/out/', '.../out/', s)
173
174
175def escape_quotes(s): # replace '"' with '\\"'
176 return s.replace('"', '\\"')
177
178
179class Crate(object):
180 """Information of a Rust crate to collect/emit for an Android.bp module."""
181
182 def __init__(self, runner, outf_name):
183 # Remembered global runner and its members.
184 self.runner = runner
185 self.debug = runner.args.debug
186 self.cargo_dir = '' # directory of my Cargo.toml
187 self.outf_name = outf_name # path to Android.bp
188 self.outf = None # open file handle of outf_name during dump*
189 # Variants/results that could be merged from multiple rustc lines.
190 self.host_supported = False
191 self.device_supported = False
192 self.has_warning = False
193 # Android module properties derived from rustc parameters.
194 self.module_name = '' # unique in Android build system
195 self.module_type = '' # rust_{binary,library,test}[_host] etc.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700196 self.defaults = '' # rust_defaults used by rust_test* modules
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800197 self.root_pkg = '' # parent package name of a sub/test packge, from -L
198 self.srcs = list() # main_src or merged multiple source files
199 self.stem = '' # real base name of output file
200 # Kept parsed status
201 self.errors = '' # all errors found during parsing
202 self.line_num = 1 # runner told input source line number
203 self.line = '' # original rustc command line parameters
204 # Parameters collected from rustc command line.
205 self.crate_name = '' # follows --crate-name
206 self.main_src = '' # follows crate_name parameter, shortened
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700207 self.crate_types = list() # follows --crate-type
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800208 self.cfgs = list() # follows --cfg, without feature= prefix
209 self.features = list() # follows --cfg, name in 'feature="..."'
210 self.codegens = list() # follows -C, some ignored
211 self.externs = list() # follows --extern
212 self.core_externs = list() # first part of self.externs elements
213 self.static_libs = list() # e.g. -l static=host_cpuid
214 self.shared_libs = list() # e.g. -l dylib=wayland-client, -l z
215 self.cap_lints = '' # follows --cap-lints
216 self.emit_list = '' # e.g., --emit=dep-info,metadata,link
217 self.edition = '2015' # rustc default, e.g., --edition=2018
218 self.target = '' # follows --target
219
220 def write(self, s):
221 # convenient way to output one line at a time with EOL.
222 self.outf.write(s + '\n')
223
224 def same_flags(self, other):
225 # host_supported, device_supported, has_warning are not compared but merged
226 # target is not compared, to merge different target/host modules
227 # externs is not compared; only core_externs is compared
228 return (not self.errors and not other.errors and
229 self.edition == other.edition and
230 self.cap_lints == other.cap_lints and
231 self.emit_list == other.emit_list and
232 self.core_externs == other.core_externs and
233 self.codegens == other.codegens and
234 self.features == other.features and
235 self.static_libs == other.static_libs and
236 self.shared_libs == other.shared_libs and self.cfgs == other.cfgs)
237
238 def merge_host_device(self, other):
239 """Returns true if attributes are the same except host/device support."""
240 return (self.crate_name == other.crate_name and
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700241 self.crate_types == other.crate_types and
242 self.main_src == other.main_src and
243 # before merge, each test module has an unique module name and stem
244 (self.stem == other.stem or self.crate_types == ['test']) and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800245 self.root_pkg == other.root_pkg and not self.skip_crate() and
246 self.same_flags(other))
247
248 def merge_test(self, other):
249 """Returns true if self and other are tests of same root_pkg."""
250 # Before merger, each test has its own crate_name.
251 # A merged test uses its source file base name as output file name,
252 # so a test is mergeable only if its base name equals to its crate name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700253 return (self.crate_types == other.crate_types and
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700254 self.crate_types == ['test'] and self.root_pkg == other.root_pkg and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800255 not self.skip_crate() and
256 other.crate_name == test_base_name(other.main_src) and
257 (len(self.srcs) > 1 or
258 (self.crate_name == test_base_name(self.main_src)) and
259 self.host_supported == other.host_supported and
260 self.device_supported == other.device_supported) and
261 self.same_flags(other))
262
263 def merge(self, other, outf_name):
264 """Try to merge crate into self."""
265 should_merge_host_device = self.merge_host_device(other)
266 should_merge_test = False
267 if not should_merge_host_device:
268 should_merge_test = self.merge_test(other)
269 # A for-device test crate can be merged with its for-host version,
270 # or merged with a different test for the same host or device.
271 # Since we run cargo once for each device or host, test crates for the
272 # first device or host will be merged first. Then test crates for a
273 # different device or host should be allowed to be merged into a
274 # previously merged one, maybe for a different device or host.
275 if should_merge_host_device or should_merge_test:
276 self.runner.init_bp_file(outf_name)
277 with open(outf_name, 'a') as outf: # to write debug info
278 self.outf = outf
279 other.outf = outf
280 self.do_merge(other, should_merge_test)
281 return True
282 return False
283
284 def do_merge(self, other, should_merge_test):
285 """Merge attributes of other to self."""
286 if self.debug:
287 self.write('\n// Before merge definition (1):')
288 self.dump_debug_info()
289 self.write('\n// Before merge definition (2):')
290 other.dump_debug_info()
291 # Merge properties of other to self.
292 self.host_supported = self.host_supported or other.host_supported
293 self.device_supported = self.device_supported or other.device_supported
294 self.has_warning = self.has_warning or other.has_warning
295 if not self.target: # okay to keep only the first target triple
296 self.target = other.target
297 # decide_module_type sets up default self.stem,
298 # which can be changed if self is a merged test module.
299 self.decide_module_type()
300 if should_merge_test:
301 self.srcs.append(other.main_src)
302 # use a short unique name as the merged module name.
303 prefix = self.root_pkg + '_tests'
304 self.module_name = self.runner.claim_module_name(prefix, self, 0)
305 self.stem = self.module_name
306 # This normalized root_pkg name although might be the same
307 # as other module's crate_name, it is not actually used for
308 # output file name. A merged test module always have multiple
309 # source files and each source file base name is used as
310 # its output file name.
311 self.crate_name = pkg2crate_name(self.root_pkg)
312 if self.debug:
313 self.write('\n// After merge definition (1):')
314 self.dump_debug_info()
315
316 def find_cargo_dir(self):
317 """Deepest directory with Cargo.toml and contains the main_src."""
318 if not is_dependent_file_path(self.main_src):
319 dir_name = os.path.dirname(self.main_src)
320 while dir_name:
321 if os.path.exists(dir_name + '/Cargo.toml'):
322 self.cargo_dir = dir_name
323 return
324 dir_name = os.path.dirname(dir_name)
325
326 def parse(self, line_num, line):
327 """Find important rustc arguments to convert to Android.bp properties."""
328 self.line_num = line_num
329 self.line = line
330 args = line.split() # Loop through every argument of rustc.
331 i = 0
332 while i < len(args):
333 arg = args[i]
334 if arg == '--crate-name':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700335 i += 1
336 self.crate_name = args[i]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800337 elif arg == '--crate-type':
338 i += 1
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700339 # cargo calls rustc with multiple --crate-type flags.
340 # rustc can accept:
341 # --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
342 self.crate_types.append(args[i])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800343 elif arg == '--test':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700344 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800345 elif arg == '--target':
346 i += 1
347 self.target = args[i]
348 elif arg == '--cfg':
349 i += 1
350 if args[i].startswith('\'feature='):
351 self.features.append(unquote(args[i].replace('\'feature=', '')[:-1]))
352 else:
353 self.cfgs.append(args[i])
354 elif arg == '--extern':
355 i += 1
356 extern_names = re.sub('=/[^ ]*/deps/', ' = ', args[i])
357 self.externs.append(extern_names)
358 self.core_externs.append(re.sub(' = .*', '', extern_names))
359 elif arg == '-C': # codegen options
360 i += 1
361 # ignore options not used in Android
362 if not (args[i].startswith('debuginfo=') or
363 args[i].startswith('extra-filename=') or
364 args[i].startswith('incremental=') or
365 args[i].startswith('metadata=')):
366 self.codegens.append(args[i])
367 elif arg == '--cap-lints':
368 i += 1
369 self.cap_lints = args[i]
370 elif arg == '-L':
371 i += 1
372 if args[i].startswith('dependency=') and args[i].endswith('/deps'):
373 if '/' + TARGET_TMP + '/' in args[i]:
374 self.root_pkg = re.sub(
375 '^.*/', '', re.sub('/' + TARGET_TMP + '/.*/deps$', '', args[i]))
376 else:
377 self.root_pkg = re.sub('^.*/', '',
378 re.sub('/[^/]+/[^/]+/deps$', '', args[i]))
379 self.root_pkg = remove_version_suffix(self.root_pkg)
380 elif arg == '-l':
381 i += 1
382 if args[i].startswith('static='):
383 self.static_libs.append(re.sub('static=', '', args[i]))
384 elif args[i].startswith('dylib='):
385 self.shared_libs.append(re.sub('dylib=', '', args[i]))
386 else:
387 self.shared_libs.append(args[i])
388 elif arg == '--out-dir' or arg == '--color': # ignored
389 i += 1
390 elif arg.startswith('--error-format=') or arg.startswith('--json='):
391 _ = arg # ignored
392 elif arg.startswith('--emit='):
393 self.emit_list = arg.replace('--emit=', '')
394 elif arg.startswith('--edition='):
395 self.edition = arg.replace('--edition=', '')
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700396 elif not arg.startswith('-'):
397 # shorten imported crate main source paths like $HOME/.cargo/
398 # registry/src/github.com-1ecc6299db9ec823/memchr-2.3.3/src/lib.rs
399 self.main_src = re.sub(r'^/[^ ]*/registry/src/', '.../', args[i])
400 self.main_src = re.sub(r'^\.\.\./github.com-[0-9a-f]*/', '.../',
401 self.main_src)
402 self.find_cargo_dir()
403 if self.cargo_dir and not self.runner.args.onefile:
404 # Write to Android.bp in the subdirectory with Cargo.toml.
405 self.outf_name = self.cargo_dir + '/Android.bp'
406 self.main_src = self.main_src[len(self.cargo_dir) + 1:]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800407 else:
408 self.errors += 'ERROR: unknown ' + arg + '\n'
409 i += 1
410 if not self.crate_name:
411 self.errors += 'ERROR: missing --crate-name\n'
412 if not self.main_src:
413 self.errors += 'ERROR: missing main source file\n'
414 else:
415 self.srcs.append(self.main_src)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700416 if not self.crate_types:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800417 # Treat "--cfg test" as "--test"
418 if 'test' in self.cfgs:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700419 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800420 else:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700421 self.errors += 'ERROR: missing --crate-type or --test\n'
422 elif len(self.crate_types) > 1:
423 if 'test' in self.crate_types:
424 self.errors += 'ERROR: cannot handle both --crate-type and --test\n'
425 if 'lib' in self.crate_types and 'rlib' in self.crate_types:
426 self.errors += 'ERROR: cannot generate both lib and rlib crate types\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800427 if not self.root_pkg:
428 self.root_pkg = self.crate_name
429 if self.target:
430 self.device_supported = True
431 self.host_supported = True # assume host supported for all builds
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700432 if self.runner.args.no_host: # unless --no-host was specified
433 self.host_supported = False
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800434 self.cfgs = sorted(set(self.cfgs))
435 self.features = sorted(set(self.features))
436 self.codegens = sorted(set(self.codegens))
437 self.externs = sorted(set(self.externs))
438 self.core_externs = sorted(set(self.core_externs))
439 self.static_libs = sorted(set(self.static_libs))
440 self.shared_libs = sorted(set(self.shared_libs))
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700441 self.crate_types = sorted(set(self.crate_types))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800442 self.decide_module_type()
443 self.module_name = altered_name(self.stem)
444 return self
445
446 def dump_line(self):
447 self.write('\n// Line ' + str(self.line_num) + ' ' + self.line)
448
449 def feature_list(self):
450 """Return a string of main_src + "feature_list"."""
451 pkg = self.main_src
452 if pkg.startswith('.../'): # keep only the main package name
453 pkg = re.sub('/.*', '', pkg[4:])
454 if not self.features:
455 return pkg
456 return pkg + ' "' + ','.join(self.features) + '"'
457
458 def dump_skip_crate(self, kind):
459 if self.debug:
460 self.write('\n// IGNORED: ' + kind + ' ' + self.main_src)
461 return self
462
463 def skip_crate(self):
464 """Return crate_name or a message if this crate should be skipped."""
465 if is_build_crate_name(self.crate_name):
466 return self.crate_name
467 if is_dependent_file_path(self.main_src):
468 return 'dependent crate'
469 return ''
470
471 def dump(self):
472 """Dump all error/debug/module code to the output .bp file."""
473 self.runner.init_bp_file(self.outf_name)
474 with open(self.outf_name, 'a') as outf:
475 self.outf = outf
476 if self.errors:
477 self.dump_line()
478 self.write(self.errors)
479 elif self.skip_crate():
480 self.dump_skip_crate(self.skip_crate())
481 else:
482 if self.debug:
483 self.dump_debug_info()
484 self.dump_android_module()
485
486 def dump_debug_info(self):
487 """Dump parsed data, when cargo2android is called with --debug."""
488
489 def dump(name, value):
490 self.write('//%12s = %s' % (name, value))
491
492 def opt_dump(name, value):
493 if value:
494 dump(name, value)
495
496 def dump_list(fmt, values):
497 for v in values:
498 self.write(fmt % v)
499
500 self.dump_line()
501 dump('module_name', self.module_name)
502 dump('crate_name', self.crate_name)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700503 dump('crate_types', self.crate_types)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800504 dump('main_src', self.main_src)
505 dump('has_warning', self.has_warning)
506 dump('for_host', self.host_supported)
507 dump('for_device', self.device_supported)
508 dump('module_type', self.module_type)
509 opt_dump('target', self.target)
510 opt_dump('edition', self.edition)
511 opt_dump('emit_list', self.emit_list)
512 opt_dump('cap_lints', self.cap_lints)
513 dump_list('// cfg = %s', self.cfgs)
514 dump_list('// cfg = \'feature "%s"\'', self.features)
515 # TODO(chh): escape quotes in self.features, but not in other dump_list
516 dump_list('// codegen = %s', self.codegens)
517 dump_list('// externs = %s', self.externs)
518 dump_list('// -l static = %s', self.static_libs)
519 dump_list('// -l (dylib) = %s', self.shared_libs)
520
521 def dump_android_module(self):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700522 """Dump one or more Android module definition, depending on crate_types."""
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700523 if len(self.crate_types) == 1:
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700524 self.dump_single_type_android_module()
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700525 return
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700526 if 'test' in self.crate_types:
527 self.write('\nERROR: multiple crate types cannot include test type')
528 return
529 # Dump one Android module per crate_type.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700530 for crate_type in self.crate_types:
531 self.decide_one_module_type(crate_type)
532 self.dump_one_android_module(crate_type)
533
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700534 def dump_defaults_module(self):
535 """Dump a rust_defaults module to be shared by other modules."""
536 name = altered_defaults(self.root_pkg) + '_defaults'
537 name = self.runner.claim_module_name(name, self, 0)
538 self.defaults = name
539 self.write('\nrust_defaults {')
540 self.write(' name: "' + name + '",')
541 self.write(' crate_name: "' + self.crate_name + '",')
542 if 'test' in self.crate_types:
543 self.write(' test_suites: ["general-tests"],')
544 self.write(' auto_gen_config: true,')
545 self.dump_edition_flags_libs()
546 self.write('}')
547
548 def dump_single_type_android_module(self):
549 """Dump one simple Android module, which has only one crate_type."""
550 crate_type = self.crate_types[0]
551 if crate_type != 'test':
552 # do not change self.stem or self.module_name
553 self.dump_one_android_module(crate_type)
554 return
555 # Dump one test module per source file, and separate host and device tests.
556 # crate_type == 'test'
557 if (self.host_supported and self.device_supported) or len(self.srcs) > 1:
558 self.srcs = sorted(set(self.srcs))
559 self.dump_defaults_module()
560 saved_srcs = self.srcs
561 for src in saved_srcs:
562 self.srcs = [src]
563 saved_device_supported = self.device_supported
564 saved_host_supported = self.host_supported
565 saved_main_src = self.main_src
566 self.main_src = src
567 if saved_host_supported:
568 self.device_supported = False
569 self.host_supported = True
570 self.module_name = self.test_module_name()
571 self.decide_one_module_type(crate_type)
572 self.dump_one_android_module(crate_type)
573 if saved_device_supported:
574 self.device_supported = True
575 self.host_supported = False
576 self.module_name = self.test_module_name()
577 self.decide_one_module_type(crate_type)
578 self.dump_one_android_module(crate_type)
579 self.host_supported = saved_host_supported
580 self.device_supported = saved_device_supported
581 self.main_src = saved_main_src
582 self.srcs = saved_srcs
583
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700584 def dump_one_android_module(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800585 """Dump one Android module definition."""
586 if not self.module_type:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700587 self.write('\nERROR: unknown crate_type ' + crate_type)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800588 return
589 self.write('\n' + self.module_type + ' {')
590 self.dump_android_core_properties()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700591 if not self.defaults:
592 self.dump_edition_flags_libs()
593 if self.runner.args.host_first_multilib and self.host_supported and crate_type != 'test':
594 self.write(' compile_multilib: "first",')
595 self.write('}')
596
597 def dump_android_flags(self):
598 """Dump Android module flags property."""
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800599 cfg_fmt = '"--cfg %s"'
600 if self.cap_lints:
601 allowed = '"--cap-lints ' + self.cap_lints + '"'
602 if not self.cfgs:
603 self.write(' flags: [' + allowed + '],')
604 else:
605 self.write(' flags: [\n ' + allowed + ',')
606 self.dump_android_property_list_items(cfg_fmt, self.cfgs)
607 self.write(' ],')
608 else:
609 self.dump_android_property_list('flags', cfg_fmt, self.cfgs)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700610
611 def dump_edition_flags_libs(self):
612 if self.edition:
613 self.write(' edition: "' + self.edition + '",')
614 self.dump_android_property_list('features', '"%s"', self.features)
615 self.dump_android_flags()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800616 if self.externs:
617 self.dump_android_externs()
618 self.dump_android_property_list('static_libs', '"lib%s"', self.static_libs)
619 self.dump_android_property_list('shared_libs', '"lib%s"', self.shared_libs)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800620
621 def test_module_name(self):
622 """Return a unique name for a test module."""
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700623 # root_pkg+(_host|_device) + '_test_'+source_file_name
624 suffix = re.sub('/', '_', re.sub('.rs$', '', self.main_src))
625 host_device = '_host'
626 if self.device_supported:
627 host_device = '_device'
628 return self.root_pkg + host_device + '_test_' + suffix
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800629
630 def decide_module_type(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700631 # Use the first crate type for the default/first module.
632 crate_type = self.crate_types[0] if self.crate_types else ''
633 self.decide_one_module_type(crate_type)
634
635 def decide_one_module_type(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800636 """Decide which Android module type to use."""
637 host = '' if self.device_supported else '_host'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700638 if crate_type == 'bin': # rust_binary[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800639 self.module_type = 'rust_binary' + host
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700640 # In rare cases like protobuf-codegen, the output binary name must
641 # be renamed to use as a plugin for protoc.
642 self.stem = altered_stem(self.crate_name)
643 self.module_name = altered_name(self.crate_name)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700644 elif crate_type == 'lib': # rust_library[_host]
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700645 # TODO(chh): should this be rust_library[_host]?
646 # Assuming that Cargo.toml do not use both 'lib' and 'rlib',
647 # because we map them both to rlib.
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700648 self.module_type = 'rust_library' + host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800649 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700650 self.module_name = altered_name(self.stem)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700651 elif crate_type == 'rlib': # rust_library[_host]
652 self.module_type = 'rust_library' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700653 self.stem = 'lib' + self.crate_name
654 self.module_name = altered_name(self.stem)
655 elif crate_type == 'dylib': # rust_library[_host]_dylib
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800656 self.module_type = 'rust_library' + host + '_dylib'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700657 self.stem = 'lib' + self.crate_name
658 self.module_name = altered_name(self.stem) + '_dylib'
659 elif crate_type == 'cdylib': # rust_library[_host]_shared
660 self.module_type = 'rust_library' + host + '_shared'
661 self.stem = 'lib' + self.crate_name
662 self.module_name = altered_name(self.stem) + '_shared'
663 elif crate_type == 'staticlib': # rust_library[_host]_static
664 self.module_type = 'rust_library' + host + '_static'
665 self.stem = 'lib' + self.crate_name
666 self.module_name = altered_name(self.stem) + '_static'
667 elif crate_type == 'test': # rust_test[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800668 self.module_type = 'rust_test' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700669 # Before do_merge, stem name is based on the --crate-name parameter.
670 # and test module name is based on stem.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800671 self.stem = self.test_module_name()
672 # self.stem will be changed after merging with other tests.
673 # self.stem is NOT used for final test binary name.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700674 # rust_test uses each source file base name as part of output file name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700675 # In do_merge, this function is called again, with a module_name.
676 # We make sure that the module name is unique in each package.
677 if self.module_name:
678 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add
679 # different suffixes and distinguish multiple tests of the same
680 # crate name. We ignore -C and use claim_module_name to get
681 # unique sequential suffix.
682 self.module_name = self.runner.claim_module_name(
683 self.module_name, self, 0)
684 # Now the module name is unique, stem should also match and unique.
685 self.stem = self.module_name
686 elif crate_type == 'proc-macro': # rust_proc_macro
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800687 self.module_type = 'rust_proc_macro'
688 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700689 self.module_name = altered_name(self.stem)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800690 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]?
691 self.module_type = ''
692 self.stem = ''
693
694 def dump_android_property_list_items(self, fmt, values):
695 for v in values:
696 # fmt has quotes, so we need escape_quotes(v)
697 self.write(' ' + (fmt % escape_quotes(v)) + ',')
698
699 def dump_android_property_list(self, name, fmt, values):
700 if values:
701 self.write(' ' + name + ': [')
702 self.dump_android_property_list_items(fmt, values)
703 self.write(' ],')
704
705 def dump_android_core_properties(self):
706 """Dump the module header, name, stem, etc."""
707 self.write(' name: "' + self.module_name + '",')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700708 # see properties shared by dump_defaults_module
709 if self.defaults:
710 self.write(' defaults: ["' + self.defaults + '"],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800711 if self.stem != self.module_name:
712 self.write(' stem: "' + self.stem + '",')
713 if self.has_warning and not self.cap_lints:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700714 self.write(' // has rustc warnings')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800715 if self.host_supported and self.device_supported:
716 self.write(' host_supported: true,')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700717 if not self.defaults:
718 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800719 if len(self.srcs) > 1:
720 self.srcs = sorted(set(self.srcs))
721 self.dump_android_property_list('srcs', '"%s"', self.srcs)
722 else:
723 self.write(' srcs: ["' + self.main_src + '"],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700724 if 'test' in self.crate_types and not self.defaults:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800725 # self.root_pkg can have multiple test modules, with different *_tests[n]
726 # names, but their executables can all be installed under the same _tests
727 # directory. When built from Cargo.toml, all tests should have different
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700728 # file or crate names. So we used (root_pkg + '_tests') name as the
729 # relative_install_path.
730 # However, some package like 'slab' can have non-mergeable tests that
731 # must be separated by different module names. So, here we no longer
732 # emit relative_install_path.
733 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800734 self.write(' test_suites: ["general-tests"],')
735 self.write(' auto_gen_config: true,')
736
737 def dump_android_externs(self):
738 """Dump the dependent rlibs and dylibs property."""
739 so_libs = list()
740 rust_libs = ''
741 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$')
742 for lib in self.externs:
743 # normal value of lib: "libc = liblibc-*.rlib"
744 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib"
745 # we should use "libgetrandom", not "lib" + "getrandom_package"
746 groups = deps_libname.match(lib)
747 if groups is not None:
748 lib_name = groups.group(1)
749 else:
750 lib_name = re.sub(' .*$', '', lib)
751 if lib.endswith('.rlib') or lib.endswith('.rmeta'):
752 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta.
753 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n'
754 elif lib.endswith('.so'):
755 so_libs.append(lib_name)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700756 elif lib != 'proc_macro': # --extern proc_macro is special and ignored
757 rust_libs += ' // ERROR: unknown type of lib ' + lib + '\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800758 if rust_libs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700759 self.write(' rustlibs: [\n' + rust_libs + ' ],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800760 # Are all dependent .so files proc_macros?
761 # TODO(chh): Separate proc_macros and dylib.
762 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs)
763
764
765class ARObject(object):
766 """Information of an "ar" link command."""
767
768 def __init__(self, runner, outf_name):
769 # Remembered global runner and its members.
770 self.runner = runner
771 self.pkg = ''
772 self.outf_name = outf_name # path to Android.bp
773 # "ar" arguments
774 self.line_num = 1
775 self.line = ''
776 self.flags = '' # e.g. "crs"
777 self.lib = '' # e.g. "/.../out/lib*.a"
778 self.objs = list() # e.g. "/.../out/.../*.o"
779
780 def parse(self, pkg, line_num, args_line):
781 """Collect ar obj/lib file names."""
782 self.pkg = pkg
783 self.line_num = line_num
784 self.line = args_line
785 args = args_line.split()
786 num_args = len(args)
787 if num_args < 3:
788 print('ERROR: "ar" command has too few arguments', args_line)
789 else:
790 self.flags = unquote(args[0])
791 self.lib = unquote(args[1])
792 self.objs = sorted(set(map(unquote, args[2:])))
793 return self
794
795 def write(self, s):
796 self.outf.write(s + '\n')
797
798 def dump_debug_info(self):
799 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line)
800 self.write('// ar_object for %12s' % self.pkg)
801 self.write('// flags = %s' % self.flags)
802 self.write('// lib = %s' % short_out_name(self.pkg, self.lib))
803 for o in self.objs:
804 self.write('// obj = %s' % short_out_name(self.pkg, o))
805
806 def dump_android_lib(self):
807 """Write cc_library_static into Android.bp."""
808 self.write('\ncc_library_static {')
809 self.write(' name: "' + file_base_name(self.lib) + '",')
810 self.write(' host_supported: true,')
811 if self.flags != 'crs':
812 self.write(' // ar flags = %s' % self.flags)
813 if self.pkg not in self.runner.pkg_obj2cc:
814 self.write(' ERROR: cannot find source files.\n}')
815 return
816 self.write(' srcs: [')
817 obj2cc = self.runner.pkg_obj2cc[self.pkg]
818 # Note: wflags are ignored.
819 dflags = list()
820 fflags = list()
821 for obj in self.objs:
822 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",')
823 # TODO(chh): union of dflags and flags of all obj
824 # Now, just a temporary hack that uses the last obj's flags
825 dflags = obj2cc[obj].dflags
826 fflags = obj2cc[obj].fflags
827 self.write(' ],')
828 self.write(' cflags: [')
829 self.write(' "-O3",') # TODO(chh): is this default correct?
830 self.write(' "-Wno-error",')
831 for x in fflags:
832 self.write(' "-f' + x + '",')
833 for x in dflags:
834 self.write(' "-D' + x + '",')
835 self.write(' ],')
836 self.write('}')
837
838 def dump(self):
839 """Dump error/debug/module info to the output .bp file."""
840 self.runner.init_bp_file(self.outf_name)
841 with open(self.outf_name, 'a') as outf:
842 self.outf = outf
843 if self.runner.args.debug:
844 self.dump_debug_info()
845 self.dump_android_lib()
846
847
848class CCObject(object):
849 """Information of a "cc" compilation command."""
850
851 def __init__(self, runner, outf_name):
852 # Remembered global runner and its members.
853 self.runner = runner
854 self.pkg = ''
855 self.outf_name = outf_name # path to Android.bp
856 # "cc" arguments
857 self.line_num = 1
858 self.line = ''
859 self.src = ''
860 self.obj = ''
861 self.dflags = list() # -D flags
862 self.fflags = list() # -f flags
863 self.iflags = list() # -I flags
864 self.wflags = list() # -W flags
865 self.other_args = list()
866
867 def parse(self, pkg, line_num, args_line):
868 """Collect cc compilation flags and src/out file names."""
869 self.pkg = pkg
870 self.line_num = line_num
871 self.line = args_line
872 args = args_line.split()
873 i = 0
874 while i < len(args):
875 arg = args[i]
876 if arg == '"-c"':
877 i += 1
878 if args[i].startswith('"-o'):
879 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c"
880 self.obj = unquote(args[i])[2:]
881 i += 1
882 self.src = unquote(args[i])
883 else:
884 self.src = unquote(args[i])
885 elif arg == '"-o"':
886 i += 1
887 self.obj = unquote(args[i])
888 elif arg == '"-I"':
889 i += 1
890 self.iflags.append(unquote(args[i]))
891 elif arg.startswith('"-D'):
892 self.dflags.append(unquote(args[i])[2:])
893 elif arg.startswith('"-f'):
894 self.fflags.append(unquote(args[i])[2:])
895 elif arg.startswith('"-W'):
896 self.wflags.append(unquote(args[i])[2:])
897 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or
898 arg == '"-g3"'):
899 # ignore -O -m64 -g
900 self.other_args.append(unquote(args[i]))
901 i += 1
902 self.dflags = sorted(set(self.dflags))
903 self.fflags = sorted(set(self.fflags))
904 # self.wflags is not sorted because some are order sensitive
905 # and we ignore them anyway.
906 if self.pkg not in self.runner.pkg_obj2cc:
907 self.runner.pkg_obj2cc[self.pkg] = {}
908 self.runner.pkg_obj2cc[self.pkg][self.obj] = self
909 return self
910
911 def write(self, s):
912 self.outf.write(s + '\n')
913
914 def dump_debug_flags(self, name, flags):
915 self.write('// ' + name + ':')
916 for f in flags:
917 self.write('// %s' % f)
918
919 def dump(self):
920 """Dump only error/debug info to the output .bp file."""
921 if not self.runner.args.debug:
922 return
923 self.runner.init_bp_file(self.outf_name)
924 with open(self.outf_name, 'a') as outf:
925 self.outf = outf
926 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line)
927 self.write('// cc_object for %12s' % self.pkg)
928 self.write('// src = %s' % short_out_name(self.pkg, self.src))
929 self.write('// obj = %s' % short_out_name(self.pkg, self.obj))
930 self.dump_debug_flags('-I flags', self.iflags)
931 self.dump_debug_flags('-D flags', self.dflags)
932 self.dump_debug_flags('-f flags', self.fflags)
933 self.dump_debug_flags('-W flags', self.wflags)
934 if self.other_args:
935 self.dump_debug_flags('other args', self.other_args)
936
937
938class Runner(object):
939 """Main class to parse cargo -v output and print Android module definitions."""
940
941 def __init__(self, args):
942 self.bp_files = set() # Remember all output Android.bp files.
943 self.root_pkg = '' # name of package in ./Cargo.toml
944 # Saved flags, modes, and data.
945 self.args = args
946 self.dry_run = not args.run
947 self.skip_cargo = args.skipcargo
948 # All cc/ar objects, crates, dependencies, and warning files
949 self.cc_objects = list()
950 self.pkg_obj2cc = {}
951 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i]
952 self.ar_objects = list()
953 self.crates = list()
954 self.dependencies = list() # dependent and build script crates
955 self.warning_files = set()
956 # Keep a unique mapping from (module name) to crate
957 self.name_owners = {}
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700958 # Save and dump all errors from cargo to Android.bp.
959 self.errors = ''
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800960 # Default action is cargo clean, followed by build or user given actions.
961 if args.cargo:
962 self.cargo = ['clean'] + args.cargo
963 else:
964 self.cargo = ['clean', 'build']
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700965 if args.no_host: # do not run "cargo build" for host
966 self.cargo = ['clean']
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800967 default_target = '--target x86_64-unknown-linux-gnu'
968 if args.device:
969 self.cargo.append('build ' + default_target)
970 if args.tests:
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700971 if not args.no_host:
972 self.cargo.append('build --tests')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800973 self.cargo.append('build --tests ' + default_target)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700974 elif args.tests and not args.no_host:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800975 self.cargo.append('build --tests')
976
977 def init_bp_file(self, name):
978 if name not in self.bp_files:
979 self.bp_files.add(name)
980 with open(name, 'w') as outf:
Andrew Walbran80e90be2020-06-09 14:33:18 +0100981 outf.write(ANDROID_BP_HEADER.format(args=' '.join(sys.argv[1:])))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800982
983 def claim_module_name(self, prefix, owner, counter):
984 """Return prefix if not owned yet, otherwise, prefix+str(counter)."""
985 while True:
986 name = prefix
987 if counter > 0:
988 name += str(counter)
989 if name not in self.name_owners:
990 self.name_owners[name] = owner
991 return name
992 if owner == self.name_owners[name]:
993 return name
994 counter += 1
995
996 def find_root_pkg(self):
997 """Read name of [package] in ./Cargo.toml."""
998 if not os.path.exists('./Cargo.toml'):
999 return
1000 with open('./Cargo.toml', 'r') as inf:
1001 pkg_section = re.compile(r'^ *\[package\]')
1002 name = re.compile('^ *name *= * "([^"]*)"')
1003 in_pkg = False
1004 for line in inf:
1005 if in_pkg:
1006 if name.match(line):
1007 self.root_pkg = name.match(line).group(1)
1008 break
1009 else:
1010 in_pkg = pkg_section.match(line) is not None
1011
1012 def run_cargo(self):
1013 """Calls cargo -v and save its output to ./cargo.out."""
1014 if self.skip_cargo:
1015 return self
1016 cargo = './Cargo.toml'
1017 if not os.access(cargo, os.R_OK):
1018 print('ERROR: Cannot find or read', cargo)
1019 return self
1020 if not self.dry_run and os.path.exists('cargo.out'):
1021 os.remove('cargo.out')
1022 cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> cargo.out 2>&1'
1023 for c in self.cargo:
1024 features = ''
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001025 if c != 'clean':
1026 if self.args.features is not None:
1027 features = ' --no-default-features'
1028 if self.args.features:
1029 features += ' --features ' + self.args.features
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001030 cmd = 'cargo -vv ' if self.args.vv else 'cargo -v '
1031 cmd += c + features + cmd_tail
1032 if self.args.rustflags and c != 'clean':
1033 cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
1034 if self.dry_run:
1035 print('Dry-run skip:', cmd)
1036 else:
1037 if self.args.verbose:
1038 print('Running:', cmd)
1039 with open('cargo.out', 'a') as cargo_out:
1040 cargo_out.write('### Running: ' + cmd + '\n')
1041 os.system(cmd)
1042 return self
1043
1044 def dump_dependencies(self):
1045 """Append dependencies and their features to Android.bp."""
1046 if not self.dependencies:
1047 return
1048 dependent_list = list()
1049 for c in self.dependencies:
1050 dependent_list.append(c.feature_list())
1051 sorted_dependencies = sorted(set(dependent_list))
1052 self.init_bp_file('Android.bp')
1053 with open('Android.bp', 'a') as outf:
1054 outf.write('\n// dependent_library ["feature_list"]\n')
1055 for s in sorted_dependencies:
1056 outf.write('// ' + s + '\n')
1057
1058 def dump_pkg_obj2cc(self):
1059 """Dump debug info of the pkg_obj2cc map."""
1060 if not self.args.debug:
1061 return
1062 self.init_bp_file('Android.bp')
1063 with open('Android.bp', 'a') as outf:
1064 sorted_pkgs = sorted(self.pkg_obj2cc.keys())
1065 for pkg in sorted_pkgs:
1066 if not self.pkg_obj2cc[pkg]:
1067 continue
1068 outf.write('\n// obj => src for %s\n' % pkg)
1069 obj2cc = self.pkg_obj2cc[pkg]
1070 for obj in sorted(obj2cc.keys()):
1071 outf.write('// ' + short_out_name(pkg, obj) + ' => ' +
1072 short_out_name(pkg, obj2cc[obj].src) + '\n')
1073
1074 def gen_bp(self):
1075 """Parse cargo.out and generate Android.bp files."""
1076 if self.dry_run:
1077 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp')
1078 elif os.path.exists(CARGO_OUT):
1079 self.find_root_pkg()
1080 with open(CARGO_OUT, 'r') as cargo_out:
1081 self.parse(cargo_out, 'Android.bp')
1082 self.crates.sort(key=get_module_name)
1083 for obj in self.cc_objects:
1084 obj.dump()
1085 self.dump_pkg_obj2cc()
1086 for crate in self.crates:
1087 crate.dump()
1088 dumped_libs = set()
1089 for lib in self.ar_objects:
1090 if lib.pkg == self.root_pkg:
1091 lib_name = file_base_name(lib.lib)
1092 if lib_name not in dumped_libs:
1093 dumped_libs.add(lib_name)
1094 lib.dump()
1095 if self.args.dependencies and self.dependencies:
1096 self.dump_dependencies()
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001097 if self.errors:
1098 self.append_to_bp('\nErrors in ' + CARGO_OUT + ':\n' + self.errors)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001099 return self
1100
1101 def add_ar_object(self, obj):
1102 self.ar_objects.append(obj)
1103
1104 def add_cc_object(self, obj):
1105 self.cc_objects.append(obj)
1106
1107 def add_crate(self, crate):
1108 """Merge crate with someone in crates, or append to it. Return crates."""
1109 if crate.skip_crate():
1110 if self.args.debug: # include debug info of all crates
1111 self.crates.append(crate)
1112 if self.args.dependencies: # include only dependent crates
1113 if (is_dependent_file_path(crate.main_src) and
1114 not is_build_crate_name(crate.crate_name)):
1115 self.dependencies.append(crate)
1116 else:
1117 for c in self.crates:
1118 if c.merge(crate, 'Android.bp'):
1119 return
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001120 # If not merged, decide module type and name now.
1121 crate.decide_module_type()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001122 self.crates.append(crate)
1123
1124 def find_warning_owners(self):
1125 """For each warning file, find its owner crate."""
1126 missing_owner = False
1127 for f in self.warning_files:
1128 cargo_dir = '' # find lowest crate, with longest path
1129 owner = None # owner crate of this warning
1130 for c in self.crates:
1131 if (f.startswith(c.cargo_dir + '/') and
1132 len(cargo_dir) < len(c.cargo_dir)):
1133 cargo_dir = c.cargo_dir
1134 owner = c
1135 if owner:
1136 owner.has_warning = True
1137 else:
1138 missing_owner = True
1139 if missing_owner and os.path.exists('Cargo.toml'):
1140 # owner is the root cargo, with empty cargo_dir
1141 for c in self.crates:
1142 if not c.cargo_dir:
1143 c.has_warning = True
1144
1145 def rustc_command(self, n, rustc_line, line, outf_name):
1146 """Process a rustc command line from cargo -vv output."""
1147 # cargo build -vv output can have multiple lines for a rustc command
1148 # due to '\n' in strings for environment variables.
1149 # strip removes leading spaces and '\n' at the end
1150 new_rustc = (rustc_line.strip() + line) if rustc_line else line
1151 # Use an heuristic to detect the completions of a multi-line command.
1152 # This might fail for some very rare case, but easy to fix manually.
1153 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0:
1154 return new_rustc
1155 if RUSTC_VV_CMD_ARGS.match(new_rustc):
1156 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1)
1157 self.add_crate(Crate(self, outf_name).parse(n, args))
1158 else:
1159 self.assert_empty_vv_line(new_rustc)
1160 return ''
1161
1162 def cc_ar_command(self, n, groups, outf_name):
1163 pkg = groups.group(1)
1164 line = groups.group(3)
1165 if groups.group(2) == 'cc':
1166 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line))
1167 else:
1168 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line))
1169
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001170 def append_to_bp(self, line):
1171 self.init_bp_file('Android.bp')
1172 with open('Android.bp', 'a') as outf:
1173 outf.write(line)
1174
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001175 def assert_empty_vv_line(self, line):
1176 if line: # report error if line is not empty
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001177 self.append_to_bp('ERROR -vv line: ' + line)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001178 return ''
1179
1180 def parse(self, inf, outf_name):
1181 """Parse rustc and warning messages in inf, return a list of Crates."""
1182 n = 0 # line number
1183 prev_warning = False # true if the previous line was warning: ...
1184 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
1185 for line in inf:
1186 n += 1
1187 if line.startswith('warning: '):
1188 prev_warning = True
1189 rustc_line = self.assert_empty_vv_line(rustc_line)
1190 continue
1191 new_rustc = ''
1192 if RUSTC_PAT.match(line):
1193 args_line = RUSTC_PAT.match(line).group(1)
1194 self.add_crate(Crate(self, outf_name).parse(n, args_line))
1195 self.assert_empty_vv_line(rustc_line)
1196 elif rustc_line or RUSTC_VV_PAT.match(line):
1197 new_rustc = self.rustc_command(n, rustc_line, line, outf_name)
1198 elif CC_AR_VV_PAT.match(line):
1199 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name)
1200 elif prev_warning and WARNING_FILE_PAT.match(line):
1201 self.assert_empty_vv_line(rustc_line)
1202 fpath = WARNING_FILE_PAT.match(line).group(1)
1203 if fpath[0] != '/': # ignore absolute path
1204 self.warning_files.add(fpath)
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001205 elif line.startswith('error: ') or line.startswith('error[E'):
1206 self.errors += line
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001207 prev_warning = False
1208 rustc_line = new_rustc
1209 self.find_warning_owners()
1210
1211
1212def parse_args():
1213 """Parse main arguments."""
1214 parser = argparse.ArgumentParser('cargo2android')
1215 parser.add_argument(
1216 '--cargo',
1217 action='append',
1218 metavar='args_string',
1219 help=('extra cargo build -v args in a string, ' +
1220 'each --cargo flag calls cargo build -v once'))
1221 parser.add_argument(
1222 '--debug',
1223 action='store_true',
1224 default=False,
1225 help='dump debug info into Android.bp')
1226 parser.add_argument(
1227 '--dependencies',
1228 action='store_true',
1229 default=False,
1230 help='dump debug info of dependent crates')
1231 parser.add_argument(
1232 '--device',
1233 action='store_true',
1234 default=False,
1235 help='run cargo also for a default device target')
1236 parser.add_argument(
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001237 '--no-host',
1238 action='store_true',
1239 default=False,
1240 help='do not run cargo for the host; only for the device target')
1241 parser.add_argument(
1242 '--host-first-multilib',
1243 action='store_true',
1244 default=False,
1245 help=('add a compile_multilib:"first" property ' +
1246 'to Android.bp host modules.'))
1247 parser.add_argument(
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001248 '--features',
1249 type=str,
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001250 help=('pass features to cargo build, ' +
1251 'empty string means no default features'))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001252 parser.add_argument(
1253 '--onefile',
1254 action='store_true',
1255 default=False,
1256 help=('output all into one ./Android.bp, default will generate ' +
1257 'one Android.bp per Cargo.toml in subdirectories'))
1258 parser.add_argument(
1259 '--run',
1260 action='store_true',
1261 default=False,
1262 help='run it, default is dry-run')
1263 parser.add_argument('--rustflags', type=str, help='passing flags to rustc')
1264 parser.add_argument(
1265 '--skipcargo',
1266 action='store_true',
1267 default=False,
1268 help='skip cargo command, parse cargo.out, and generate Android.bp')
1269 parser.add_argument(
1270 '--tests',
1271 action='store_true',
1272 default=False,
1273 help='run cargo build --tests after normal build')
1274 parser.add_argument(
1275 '--verbose',
1276 action='store_true',
1277 default=False,
1278 help='echo executed commands')
1279 parser.add_argument(
1280 '--vv',
1281 action='store_true',
1282 default=False,
1283 help='run cargo with -vv instead of default -v')
1284 return parser.parse_args()
1285
1286
1287def main():
1288 args = parse_args()
1289 if not args.run: # default is dry-run
1290 print(DRY_RUN_NOTE)
1291 Runner(args).run_cargo().gen_bp()
1292
1293
1294if __name__ == '__main__':
1295 main()