blob: df841d9cea055d9624cc8e188edfbcacca468094 [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
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -070063# Some Rust packages include extra unwanted crates.
64# This set contains all such excluded crate names.
65EXCLUDED_CRATES = set(['protobuf_bin_gen_rust_do_not_use'])
66
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080067RENAME_MAP = {
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -070068 # This map includes all changes to the default rust module names
69 # to resolve name conflicts, avoid confusion, or work as plugin.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080070 'libbacktrace': 'libbacktrace_rust',
71 'libgcc': 'libgcc_rust',
72 'liblog': 'liblog_rust',
73 'libsync': 'libsync_rust',
74 'libx86_64': 'libx86_64_rust',
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -070075 'protoc_gen_rust': 'protoc-gen-rust',
76}
77
78RENAME_STEM_MAP = {
79 # This map includes all changes to the default rust module stem names,
80 # which is used for output files when different from the module name.
81 'protoc_gen_rust': 'protoc-gen-rust',
82}
83
84RENAME_DEFAULTS_MAP = {
85 # This map includes all changes to the default prefix of rust_default
86 # module names, to avoid conflict with existing Android modules.
87 'libc': 'rust_libc',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080088}
89
90# Header added to all generated Android.bp files.
Andrew Walbran80e90be2020-06-09 14:33:18 +010091ANDROID_BP_HEADER = '// This file is generated by cargo2android.py {args}.\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080092
93CARGO_OUT = 'cargo.out' # Name of file to keep cargo build -v output.
94
95TARGET_TMP = 'target.tmp' # Name of temporary output directory.
96
97# Message to be displayed when this script is called without the --run flag.
98DRY_RUN_NOTE = (
99 'Dry-run: This script uses ./' + TARGET_TMP + ' for output directory,\n' +
100 'runs cargo clean, runs cargo build -v, saves output to ./cargo.out,\n' +
101 'and writes to Android.bp in the current and subdirectories.\n\n' +
102 'To do do all of the above, use the --run flag.\n' +
103 'See --help for other flags, and more usage notes in this script.\n')
104
105# Cargo -v output of a call to rustc.
106RUSTC_PAT = re.compile('^ +Running `rustc (.*)`$')
107
108# Cargo -vv output of a call to rustc could be split into multiple lines.
109# Assume that the first line will contain some CARGO_* env definition.
110RUSTC_VV_PAT = re.compile('^ +Running `.*CARGO_.*=.*$')
111# The combined -vv output rustc command line pattern.
112RUSTC_VV_CMD_ARGS = re.compile('^ *Running `.*CARGO_.*=.* rustc (.*)`$')
113
114# Cargo -vv output of a "cc" or "ar" command; all in one line.
115CC_AR_VV_PAT = re.compile(r'^\[([^ ]*)[^\]]*\] running:? "(cc|ar)" (.*)$')
116# Some package, such as ring-0.13.5, has pattern '... running "cc"'.
117
118# Rustc output of file location path pattern for a warning message.
119WARNING_FILE_PAT = re.compile('^ *--> ([^:]*):[0-9]+')
120
121# Rust package name with suffix -d1.d2.d3.
122VERSION_SUFFIX_PAT = re.compile(r'^(.*)-[0-9]+\.[0-9]+\.[0-9]+$')
123
124
125def altered_name(name):
126 return RENAME_MAP[name] if (name in RENAME_MAP) else name
127
128
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700129def altered_stem(name):
130 return RENAME_STEM_MAP[name] if (name in RENAME_STEM_MAP) else name
131
132
133def altered_defaults(name):
134 return RENAME_DEFAULTS_MAP[name] if (name in RENAME_DEFAULTS_MAP) else name
135
136
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800137def is_build_crate_name(name):
138 # We added special prefix to build script crate names.
139 return name.startswith('build_script_')
140
141
142def is_dependent_file_path(path):
143 # Absolute or dependent '.../' paths are not main files of this crate.
144 return path.startswith('/') or path.startswith('.../')
145
146
147def get_module_name(crate): # to sort crates in a list
148 return crate.module_name
149
150
151def pkg2crate_name(s):
152 return s.replace('-', '_').replace('.', '_')
153
154
155def file_base_name(path):
156 return os.path.splitext(os.path.basename(path))[0]
157
158
159def test_base_name(path):
160 return pkg2crate_name(file_base_name(path))
161
162
163def unquote(s): # remove quotes around str
164 if s and len(s) > 1 and s[0] == '"' and s[-1] == '"':
165 return s[1:-1]
166 return s
167
168
169def remove_version_suffix(s): # remove -d1.d2.d3 suffix
170 if VERSION_SUFFIX_PAT.match(s):
171 return VERSION_SUFFIX_PAT.match(s).group(1)
172 return s
173
174
175def short_out_name(pkg, s): # replace /.../pkg-*/out/* with .../out/*
176 return re.sub('^/.*/' + pkg + '-[0-9a-f]*/out/', '.../out/', s)
177
178
179def escape_quotes(s): # replace '"' with '\\"'
180 return s.replace('"', '\\"')
181
182
183class Crate(object):
184 """Information of a Rust crate to collect/emit for an Android.bp module."""
185
186 def __init__(self, runner, outf_name):
187 # Remembered global runner and its members.
188 self.runner = runner
189 self.debug = runner.args.debug
190 self.cargo_dir = '' # directory of my Cargo.toml
191 self.outf_name = outf_name # path to Android.bp
192 self.outf = None # open file handle of outf_name during dump*
193 # Variants/results that could be merged from multiple rustc lines.
194 self.host_supported = False
195 self.device_supported = False
196 self.has_warning = False
197 # Android module properties derived from rustc parameters.
198 self.module_name = '' # unique in Android build system
199 self.module_type = '' # rust_{binary,library,test}[_host] etc.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700200 self.defaults = '' # rust_defaults used by rust_test* modules
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800201 self.root_pkg = '' # parent package name of a sub/test packge, from -L
202 self.srcs = list() # main_src or merged multiple source files
203 self.stem = '' # real base name of output file
204 # Kept parsed status
205 self.errors = '' # all errors found during parsing
206 self.line_num = 1 # runner told input source line number
207 self.line = '' # original rustc command line parameters
208 # Parameters collected from rustc command line.
209 self.crate_name = '' # follows --crate-name
210 self.main_src = '' # follows crate_name parameter, shortened
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700211 self.crate_types = list() # follows --crate-type
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800212 self.cfgs = list() # follows --cfg, without feature= prefix
213 self.features = list() # follows --cfg, name in 'feature="..."'
214 self.codegens = list() # follows -C, some ignored
215 self.externs = list() # follows --extern
216 self.core_externs = list() # first part of self.externs elements
217 self.static_libs = list() # e.g. -l static=host_cpuid
218 self.shared_libs = list() # e.g. -l dylib=wayland-client, -l z
219 self.cap_lints = '' # follows --cap-lints
220 self.emit_list = '' # e.g., --emit=dep-info,metadata,link
221 self.edition = '2015' # rustc default, e.g., --edition=2018
222 self.target = '' # follows --target
223
224 def write(self, s):
225 # convenient way to output one line at a time with EOL.
226 self.outf.write(s + '\n')
227
228 def same_flags(self, other):
229 # host_supported, device_supported, has_warning are not compared but merged
230 # target is not compared, to merge different target/host modules
231 # externs is not compared; only core_externs is compared
232 return (not self.errors and not other.errors and
233 self.edition == other.edition and
234 self.cap_lints == other.cap_lints and
235 self.emit_list == other.emit_list and
236 self.core_externs == other.core_externs and
237 self.codegens == other.codegens and
238 self.features == other.features and
239 self.static_libs == other.static_libs and
240 self.shared_libs == other.shared_libs and self.cfgs == other.cfgs)
241
242 def merge_host_device(self, other):
243 """Returns true if attributes are the same except host/device support."""
244 return (self.crate_name == other.crate_name and
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700245 self.crate_types == other.crate_types and
246 self.main_src == other.main_src and
247 # before merge, each test module has an unique module name and stem
248 (self.stem == other.stem or self.crate_types == ['test']) and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800249 self.root_pkg == other.root_pkg and not self.skip_crate() and
250 self.same_flags(other))
251
252 def merge_test(self, other):
253 """Returns true if self and other are tests of same root_pkg."""
254 # Before merger, each test has its own crate_name.
255 # A merged test uses its source file base name as output file name,
256 # so a test is mergeable only if its base name equals to its crate name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700257 return (self.crate_types == other.crate_types and
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700258 self.crate_types == ['test'] and self.root_pkg == other.root_pkg and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800259 not self.skip_crate() and
260 other.crate_name == test_base_name(other.main_src) and
261 (len(self.srcs) > 1 or
262 (self.crate_name == test_base_name(self.main_src)) and
263 self.host_supported == other.host_supported and
264 self.device_supported == other.device_supported) and
265 self.same_flags(other))
266
267 def merge(self, other, outf_name):
268 """Try to merge crate into self."""
269 should_merge_host_device = self.merge_host_device(other)
270 should_merge_test = False
271 if not should_merge_host_device:
272 should_merge_test = self.merge_test(other)
273 # A for-device test crate can be merged with its for-host version,
274 # or merged with a different test for the same host or device.
275 # Since we run cargo once for each device or host, test crates for the
276 # first device or host will be merged first. Then test crates for a
277 # different device or host should be allowed to be merged into a
278 # previously merged one, maybe for a different device or host.
279 if should_merge_host_device or should_merge_test:
280 self.runner.init_bp_file(outf_name)
281 with open(outf_name, 'a') as outf: # to write debug info
282 self.outf = outf
283 other.outf = outf
284 self.do_merge(other, should_merge_test)
285 return True
286 return False
287
288 def do_merge(self, other, should_merge_test):
289 """Merge attributes of other to self."""
290 if self.debug:
291 self.write('\n// Before merge definition (1):')
292 self.dump_debug_info()
293 self.write('\n// Before merge definition (2):')
294 other.dump_debug_info()
295 # Merge properties of other to self.
296 self.host_supported = self.host_supported or other.host_supported
297 self.device_supported = self.device_supported or other.device_supported
298 self.has_warning = self.has_warning or other.has_warning
299 if not self.target: # okay to keep only the first target triple
300 self.target = other.target
301 # decide_module_type sets up default self.stem,
302 # which can be changed if self is a merged test module.
303 self.decide_module_type()
304 if should_merge_test:
305 self.srcs.append(other.main_src)
306 # use a short unique name as the merged module name.
307 prefix = self.root_pkg + '_tests'
308 self.module_name = self.runner.claim_module_name(prefix, self, 0)
309 self.stem = self.module_name
310 # This normalized root_pkg name although might be the same
311 # as other module's crate_name, it is not actually used for
312 # output file name. A merged test module always have multiple
313 # source files and each source file base name is used as
314 # its output file name.
315 self.crate_name = pkg2crate_name(self.root_pkg)
316 if self.debug:
317 self.write('\n// After merge definition (1):')
318 self.dump_debug_info()
319
320 def find_cargo_dir(self):
321 """Deepest directory with Cargo.toml and contains the main_src."""
322 if not is_dependent_file_path(self.main_src):
323 dir_name = os.path.dirname(self.main_src)
324 while dir_name:
325 if os.path.exists(dir_name + '/Cargo.toml'):
326 self.cargo_dir = dir_name
327 return
328 dir_name = os.path.dirname(dir_name)
329
330 def parse(self, line_num, line):
331 """Find important rustc arguments to convert to Android.bp properties."""
332 self.line_num = line_num
333 self.line = line
334 args = line.split() # Loop through every argument of rustc.
335 i = 0
336 while i < len(args):
337 arg = args[i]
338 if arg == '--crate-name':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700339 i += 1
340 self.crate_name = args[i]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800341 elif arg == '--crate-type':
342 i += 1
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700343 # cargo calls rustc with multiple --crate-type flags.
344 # rustc can accept:
345 # --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
346 self.crate_types.append(args[i])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800347 elif arg == '--test':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700348 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800349 elif arg == '--target':
350 i += 1
351 self.target = args[i]
352 elif arg == '--cfg':
353 i += 1
354 if args[i].startswith('\'feature='):
355 self.features.append(unquote(args[i].replace('\'feature=', '')[:-1]))
356 else:
357 self.cfgs.append(args[i])
358 elif arg == '--extern':
359 i += 1
360 extern_names = re.sub('=/[^ ]*/deps/', ' = ', args[i])
361 self.externs.append(extern_names)
362 self.core_externs.append(re.sub(' = .*', '', extern_names))
363 elif arg == '-C': # codegen options
364 i += 1
365 # ignore options not used in Android
366 if not (args[i].startswith('debuginfo=') or
367 args[i].startswith('extra-filename=') or
368 args[i].startswith('incremental=') or
369 args[i].startswith('metadata=')):
370 self.codegens.append(args[i])
371 elif arg == '--cap-lints':
372 i += 1
373 self.cap_lints = args[i]
374 elif arg == '-L':
375 i += 1
376 if args[i].startswith('dependency=') and args[i].endswith('/deps'):
377 if '/' + TARGET_TMP + '/' in args[i]:
378 self.root_pkg = re.sub(
379 '^.*/', '', re.sub('/' + TARGET_TMP + '/.*/deps$', '', args[i]))
380 else:
381 self.root_pkg = re.sub('^.*/', '',
382 re.sub('/[^/]+/[^/]+/deps$', '', args[i]))
383 self.root_pkg = remove_version_suffix(self.root_pkg)
384 elif arg == '-l':
385 i += 1
386 if args[i].startswith('static='):
387 self.static_libs.append(re.sub('static=', '', args[i]))
388 elif args[i].startswith('dylib='):
389 self.shared_libs.append(re.sub('dylib=', '', args[i]))
390 else:
391 self.shared_libs.append(args[i])
392 elif arg == '--out-dir' or arg == '--color': # ignored
393 i += 1
394 elif arg.startswith('--error-format=') or arg.startswith('--json='):
395 _ = arg # ignored
396 elif arg.startswith('--emit='):
397 self.emit_list = arg.replace('--emit=', '')
398 elif arg.startswith('--edition='):
399 self.edition = arg.replace('--edition=', '')
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700400 elif not arg.startswith('-'):
401 # shorten imported crate main source paths like $HOME/.cargo/
402 # registry/src/github.com-1ecc6299db9ec823/memchr-2.3.3/src/lib.rs
403 self.main_src = re.sub(r'^/[^ ]*/registry/src/', '.../', args[i])
404 self.main_src = re.sub(r'^\.\.\./github.com-[0-9a-f]*/', '.../',
405 self.main_src)
406 self.find_cargo_dir()
407 if self.cargo_dir and not self.runner.args.onefile:
408 # Write to Android.bp in the subdirectory with Cargo.toml.
409 self.outf_name = self.cargo_dir + '/Android.bp'
410 self.main_src = self.main_src[len(self.cargo_dir) + 1:]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800411 else:
412 self.errors += 'ERROR: unknown ' + arg + '\n'
413 i += 1
414 if not self.crate_name:
415 self.errors += 'ERROR: missing --crate-name\n'
416 if not self.main_src:
417 self.errors += 'ERROR: missing main source file\n'
418 else:
419 self.srcs.append(self.main_src)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700420 if not self.crate_types:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800421 # Treat "--cfg test" as "--test"
422 if 'test' in self.cfgs:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700423 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800424 else:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700425 self.errors += 'ERROR: missing --crate-type or --test\n'
426 elif len(self.crate_types) > 1:
427 if 'test' in self.crate_types:
428 self.errors += 'ERROR: cannot handle both --crate-type and --test\n'
429 if 'lib' in self.crate_types and 'rlib' in self.crate_types:
430 self.errors += 'ERROR: cannot generate both lib and rlib crate types\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800431 if not self.root_pkg:
432 self.root_pkg = self.crate_name
433 if self.target:
434 self.device_supported = True
435 self.host_supported = True # assume host supported for all builds
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700436 if self.runner.args.no_host: # unless --no-host was specified
437 self.host_supported = False
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800438 self.cfgs = sorted(set(self.cfgs))
439 self.features = sorted(set(self.features))
440 self.codegens = sorted(set(self.codegens))
441 self.externs = sorted(set(self.externs))
442 self.core_externs = sorted(set(self.core_externs))
443 self.static_libs = sorted(set(self.static_libs))
444 self.shared_libs = sorted(set(self.shared_libs))
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700445 self.crate_types = sorted(set(self.crate_types))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800446 self.decide_module_type()
447 self.module_name = altered_name(self.stem)
448 return self
449
450 def dump_line(self):
451 self.write('\n// Line ' + str(self.line_num) + ' ' + self.line)
452
453 def feature_list(self):
454 """Return a string of main_src + "feature_list"."""
455 pkg = self.main_src
456 if pkg.startswith('.../'): # keep only the main package name
457 pkg = re.sub('/.*', '', pkg[4:])
458 if not self.features:
459 return pkg
460 return pkg + ' "' + ','.join(self.features) + '"'
461
462 def dump_skip_crate(self, kind):
463 if self.debug:
464 self.write('\n// IGNORED: ' + kind + ' ' + self.main_src)
465 return self
466
467 def skip_crate(self):
468 """Return crate_name or a message if this crate should be skipped."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700469 if (is_build_crate_name(self.crate_name) or
470 self.crate_name in EXCLUDED_CRATES):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800471 return self.crate_name
472 if is_dependent_file_path(self.main_src):
473 return 'dependent crate'
474 return ''
475
476 def dump(self):
477 """Dump all error/debug/module code to the output .bp file."""
478 self.runner.init_bp_file(self.outf_name)
479 with open(self.outf_name, 'a') as outf:
480 self.outf = outf
481 if self.errors:
482 self.dump_line()
483 self.write(self.errors)
484 elif self.skip_crate():
485 self.dump_skip_crate(self.skip_crate())
486 else:
487 if self.debug:
488 self.dump_debug_info()
489 self.dump_android_module()
490
491 def dump_debug_info(self):
492 """Dump parsed data, when cargo2android is called with --debug."""
493
494 def dump(name, value):
495 self.write('//%12s = %s' % (name, value))
496
497 def opt_dump(name, value):
498 if value:
499 dump(name, value)
500
501 def dump_list(fmt, values):
502 for v in values:
503 self.write(fmt % v)
504
505 self.dump_line()
506 dump('module_name', self.module_name)
507 dump('crate_name', self.crate_name)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700508 dump('crate_types', self.crate_types)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800509 dump('main_src', self.main_src)
510 dump('has_warning', self.has_warning)
511 dump('for_host', self.host_supported)
512 dump('for_device', self.device_supported)
513 dump('module_type', self.module_type)
514 opt_dump('target', self.target)
515 opt_dump('edition', self.edition)
516 opt_dump('emit_list', self.emit_list)
517 opt_dump('cap_lints', self.cap_lints)
518 dump_list('// cfg = %s', self.cfgs)
519 dump_list('// cfg = \'feature "%s"\'', self.features)
520 # TODO(chh): escape quotes in self.features, but not in other dump_list
521 dump_list('// codegen = %s', self.codegens)
522 dump_list('// externs = %s', self.externs)
523 dump_list('// -l static = %s', self.static_libs)
524 dump_list('// -l (dylib) = %s', self.shared_libs)
525
526 def dump_android_module(self):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700527 """Dump one or more Android module definition, depending on crate_types."""
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700528 if len(self.crate_types) == 1:
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700529 self.dump_single_type_android_module()
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700530 return
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700531 if 'test' in self.crate_types:
532 self.write('\nERROR: multiple crate types cannot include test type')
533 return
534 # Dump one Android module per crate_type.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700535 for crate_type in self.crate_types:
536 self.decide_one_module_type(crate_type)
537 self.dump_one_android_module(crate_type)
538
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700539 def build_default_name(self):
540 """Return a short and readable name for the rust_defaults module."""
541 # Choices: (1) root_pkg + '_defaults',
542 # (2) root_pkg + '_defaults_' + crate_name
543 # (3) root_pkg + '_defaults_' + main_src_basename_path
544 # (4) root_pkg + '_defaults_' + a_positive_sequence_number
545 name1 = altered_defaults(self.root_pkg) + '_defaults'
546 if self.runner.try_claim_module_name(name1, self):
547 return name1
548 name2 = name1 + '_' + self.crate_name
549 if self.runner.try_claim_module_name(name2, self):
550 return name2
551 name3 = name1 + '_' + self.main_src_basename_path()
552 if self.runner.try_claim_module_name(name3, self):
553 return name3
554 return self.runner.claim_module_name(name1, self, 0)
555
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700556 def dump_defaults_module(self):
557 """Dump a rust_defaults module to be shared by other modules."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700558 name = self.build_default_name()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700559 self.defaults = name
560 self.write('\nrust_defaults {')
561 self.write(' name: "' + name + '",')
562 self.write(' crate_name: "' + self.crate_name + '",')
563 if 'test' in self.crate_types:
564 self.write(' test_suites: ["general-tests"],')
565 self.write(' auto_gen_config: true,')
566 self.dump_edition_flags_libs()
567 self.write('}')
568
569 def dump_single_type_android_module(self):
570 """Dump one simple Android module, which has only one crate_type."""
571 crate_type = self.crate_types[0]
572 if crate_type != 'test':
573 # do not change self.stem or self.module_name
574 self.dump_one_android_module(crate_type)
575 return
576 # Dump one test module per source file, and separate host and device tests.
577 # crate_type == 'test'
578 if (self.host_supported and self.device_supported) or len(self.srcs) > 1:
579 self.srcs = sorted(set(self.srcs))
580 self.dump_defaults_module()
581 saved_srcs = self.srcs
582 for src in saved_srcs:
583 self.srcs = [src]
584 saved_device_supported = self.device_supported
585 saved_host_supported = self.host_supported
586 saved_main_src = self.main_src
587 self.main_src = src
588 if saved_host_supported:
589 self.device_supported = False
590 self.host_supported = True
591 self.module_name = self.test_module_name()
592 self.decide_one_module_type(crate_type)
593 self.dump_one_android_module(crate_type)
594 if saved_device_supported:
595 self.device_supported = True
596 self.host_supported = False
597 self.module_name = self.test_module_name()
598 self.decide_one_module_type(crate_type)
599 self.dump_one_android_module(crate_type)
600 self.host_supported = saved_host_supported
601 self.device_supported = saved_device_supported
602 self.main_src = saved_main_src
603 self.srcs = saved_srcs
604
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700605 def dump_one_android_module(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800606 """Dump one Android module definition."""
607 if not self.module_type:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700608 self.write('\nERROR: unknown crate_type ' + crate_type)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800609 return
610 self.write('\n' + self.module_type + ' {')
611 self.dump_android_core_properties()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700612 if not self.defaults:
613 self.dump_edition_flags_libs()
614 if self.runner.args.host_first_multilib and self.host_supported and crate_type != 'test':
615 self.write(' compile_multilib: "first",')
616 self.write('}')
617
618 def dump_android_flags(self):
619 """Dump Android module flags property."""
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800620 cfg_fmt = '"--cfg %s"'
621 if self.cap_lints:
622 allowed = '"--cap-lints ' + self.cap_lints + '"'
623 if not self.cfgs:
624 self.write(' flags: [' + allowed + '],')
625 else:
626 self.write(' flags: [\n ' + allowed + ',')
627 self.dump_android_property_list_items(cfg_fmt, self.cfgs)
628 self.write(' ],')
629 else:
630 self.dump_android_property_list('flags', cfg_fmt, self.cfgs)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700631
632 def dump_edition_flags_libs(self):
633 if self.edition:
634 self.write(' edition: "' + self.edition + '",')
635 self.dump_android_property_list('features', '"%s"', self.features)
636 self.dump_android_flags()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800637 if self.externs:
638 self.dump_android_externs()
639 self.dump_android_property_list('static_libs', '"lib%s"', self.static_libs)
640 self.dump_android_property_list('shared_libs', '"lib%s"', self.shared_libs)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800641
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700642 def main_src_basename_path(self):
643 return re.sub('/', '_', re.sub('.rs$', '', self.main_src))
644
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800645 def test_module_name(self):
646 """Return a unique name for a test module."""
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700647 # root_pkg+(_host|_device) + '_test_'+source_file_name
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700648 suffix = self.main_src_basename_path()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700649 host_device = '_host'
650 if self.device_supported:
651 host_device = '_device'
652 return self.root_pkg + host_device + '_test_' + suffix
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800653
654 def decide_module_type(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700655 # Use the first crate type for the default/first module.
656 crate_type = self.crate_types[0] if self.crate_types else ''
657 self.decide_one_module_type(crate_type)
658
659 def decide_one_module_type(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800660 """Decide which Android module type to use."""
661 host = '' if self.device_supported else '_host'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700662 if crate_type == 'bin': # rust_binary[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800663 self.module_type = 'rust_binary' + host
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700664 # In rare cases like protobuf-codegen, the output binary name must
665 # be renamed to use as a plugin for protoc.
666 self.stem = altered_stem(self.crate_name)
667 self.module_name = altered_name(self.crate_name)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700668 elif crate_type == 'lib': # rust_library[_host]
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700669 # TODO(chh): should this be rust_library[_host]?
670 # Assuming that Cargo.toml do not use both 'lib' and 'rlib',
671 # because we map them both to rlib.
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700672 self.module_type = 'rust_library' + host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800673 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700674 self.module_name = altered_name(self.stem)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700675 elif crate_type == 'rlib': # rust_library[_host]
676 self.module_type = 'rust_library' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700677 self.stem = 'lib' + self.crate_name
678 self.module_name = altered_name(self.stem)
679 elif crate_type == 'dylib': # rust_library[_host]_dylib
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800680 self.module_type = 'rust_library' + host + '_dylib'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700681 self.stem = 'lib' + self.crate_name
682 self.module_name = altered_name(self.stem) + '_dylib'
683 elif crate_type == 'cdylib': # rust_library[_host]_shared
684 self.module_type = 'rust_library' + host + '_shared'
685 self.stem = 'lib' + self.crate_name
686 self.module_name = altered_name(self.stem) + '_shared'
687 elif crate_type == 'staticlib': # rust_library[_host]_static
688 self.module_type = 'rust_library' + host + '_static'
689 self.stem = 'lib' + self.crate_name
690 self.module_name = altered_name(self.stem) + '_static'
691 elif crate_type == 'test': # rust_test[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800692 self.module_type = 'rust_test' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700693 # Before do_merge, stem name is based on the --crate-name parameter.
694 # and test module name is based on stem.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800695 self.stem = self.test_module_name()
696 # self.stem will be changed after merging with other tests.
697 # self.stem is NOT used for final test binary name.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700698 # rust_test uses each source file base name as part of output file name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700699 # In do_merge, this function is called again, with a module_name.
700 # We make sure that the module name is unique in each package.
701 if self.module_name:
702 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add
703 # different suffixes and distinguish multiple tests of the same
704 # crate name. We ignore -C and use claim_module_name to get
705 # unique sequential suffix.
706 self.module_name = self.runner.claim_module_name(
707 self.module_name, self, 0)
708 # Now the module name is unique, stem should also match and unique.
709 self.stem = self.module_name
710 elif crate_type == 'proc-macro': # rust_proc_macro
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800711 self.module_type = 'rust_proc_macro'
712 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700713 self.module_name = altered_name(self.stem)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800714 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]?
715 self.module_type = ''
716 self.stem = ''
717
718 def dump_android_property_list_items(self, fmt, values):
719 for v in values:
720 # fmt has quotes, so we need escape_quotes(v)
721 self.write(' ' + (fmt % escape_quotes(v)) + ',')
722
723 def dump_android_property_list(self, name, fmt, values):
724 if values:
725 self.write(' ' + name + ': [')
726 self.dump_android_property_list_items(fmt, values)
727 self.write(' ],')
728
729 def dump_android_core_properties(self):
730 """Dump the module header, name, stem, etc."""
731 self.write(' name: "' + self.module_name + '",')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700732 # see properties shared by dump_defaults_module
733 if self.defaults:
734 self.write(' defaults: ["' + self.defaults + '"],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800735 if self.stem != self.module_name:
736 self.write(' stem: "' + self.stem + '",')
737 if self.has_warning and not self.cap_lints:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700738 self.write(' // has rustc warnings')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800739 if self.host_supported and self.device_supported:
740 self.write(' host_supported: true,')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700741 if not self.defaults:
742 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800743 if len(self.srcs) > 1:
744 self.srcs = sorted(set(self.srcs))
745 self.dump_android_property_list('srcs', '"%s"', self.srcs)
746 else:
747 self.write(' srcs: ["' + self.main_src + '"],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700748 if 'test' in self.crate_types and not self.defaults:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800749 # self.root_pkg can have multiple test modules, with different *_tests[n]
750 # names, but their executables can all be installed under the same _tests
751 # directory. When built from Cargo.toml, all tests should have different
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700752 # file or crate names. So we used (root_pkg + '_tests') name as the
753 # relative_install_path.
754 # However, some package like 'slab' can have non-mergeable tests that
755 # must be separated by different module names. So, here we no longer
756 # emit relative_install_path.
757 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800758 self.write(' test_suites: ["general-tests"],')
759 self.write(' auto_gen_config: true,')
760
761 def dump_android_externs(self):
762 """Dump the dependent rlibs and dylibs property."""
763 so_libs = list()
764 rust_libs = ''
765 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$')
766 for lib in self.externs:
767 # normal value of lib: "libc = liblibc-*.rlib"
768 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib"
769 # we should use "libgetrandom", not "lib" + "getrandom_package"
770 groups = deps_libname.match(lib)
771 if groups is not None:
772 lib_name = groups.group(1)
773 else:
774 lib_name = re.sub(' .*$', '', lib)
775 if lib.endswith('.rlib') or lib.endswith('.rmeta'):
776 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta.
777 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n'
778 elif lib.endswith('.so'):
779 so_libs.append(lib_name)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700780 elif lib != 'proc_macro': # --extern proc_macro is special and ignored
781 rust_libs += ' // ERROR: unknown type of lib ' + lib + '\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800782 if rust_libs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700783 self.write(' rustlibs: [\n' + rust_libs + ' ],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800784 # Are all dependent .so files proc_macros?
785 # TODO(chh): Separate proc_macros and dylib.
786 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs)
787
788
789class ARObject(object):
790 """Information of an "ar" link command."""
791
792 def __init__(self, runner, outf_name):
793 # Remembered global runner and its members.
794 self.runner = runner
795 self.pkg = ''
796 self.outf_name = outf_name # path to Android.bp
797 # "ar" arguments
798 self.line_num = 1
799 self.line = ''
800 self.flags = '' # e.g. "crs"
801 self.lib = '' # e.g. "/.../out/lib*.a"
802 self.objs = list() # e.g. "/.../out/.../*.o"
803
804 def parse(self, pkg, line_num, args_line):
805 """Collect ar obj/lib file names."""
806 self.pkg = pkg
807 self.line_num = line_num
808 self.line = args_line
809 args = args_line.split()
810 num_args = len(args)
811 if num_args < 3:
812 print('ERROR: "ar" command has too few arguments', args_line)
813 else:
814 self.flags = unquote(args[0])
815 self.lib = unquote(args[1])
816 self.objs = sorted(set(map(unquote, args[2:])))
817 return self
818
819 def write(self, s):
820 self.outf.write(s + '\n')
821
822 def dump_debug_info(self):
823 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line)
824 self.write('// ar_object for %12s' % self.pkg)
825 self.write('// flags = %s' % self.flags)
826 self.write('// lib = %s' % short_out_name(self.pkg, self.lib))
827 for o in self.objs:
828 self.write('// obj = %s' % short_out_name(self.pkg, o))
829
830 def dump_android_lib(self):
831 """Write cc_library_static into Android.bp."""
832 self.write('\ncc_library_static {')
833 self.write(' name: "' + file_base_name(self.lib) + '",')
834 self.write(' host_supported: true,')
835 if self.flags != 'crs':
836 self.write(' // ar flags = %s' % self.flags)
837 if self.pkg not in self.runner.pkg_obj2cc:
838 self.write(' ERROR: cannot find source files.\n}')
839 return
840 self.write(' srcs: [')
841 obj2cc = self.runner.pkg_obj2cc[self.pkg]
842 # Note: wflags are ignored.
843 dflags = list()
844 fflags = list()
845 for obj in self.objs:
846 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",')
847 # TODO(chh): union of dflags and flags of all obj
848 # Now, just a temporary hack that uses the last obj's flags
849 dflags = obj2cc[obj].dflags
850 fflags = obj2cc[obj].fflags
851 self.write(' ],')
852 self.write(' cflags: [')
853 self.write(' "-O3",') # TODO(chh): is this default correct?
854 self.write(' "-Wno-error",')
855 for x in fflags:
856 self.write(' "-f' + x + '",')
857 for x in dflags:
858 self.write(' "-D' + x + '",')
859 self.write(' ],')
860 self.write('}')
861
862 def dump(self):
863 """Dump error/debug/module info to the output .bp file."""
864 self.runner.init_bp_file(self.outf_name)
865 with open(self.outf_name, 'a') as outf:
866 self.outf = outf
867 if self.runner.args.debug:
868 self.dump_debug_info()
869 self.dump_android_lib()
870
871
872class CCObject(object):
873 """Information of a "cc" compilation command."""
874
875 def __init__(self, runner, outf_name):
876 # Remembered global runner and its members.
877 self.runner = runner
878 self.pkg = ''
879 self.outf_name = outf_name # path to Android.bp
880 # "cc" arguments
881 self.line_num = 1
882 self.line = ''
883 self.src = ''
884 self.obj = ''
885 self.dflags = list() # -D flags
886 self.fflags = list() # -f flags
887 self.iflags = list() # -I flags
888 self.wflags = list() # -W flags
889 self.other_args = list()
890
891 def parse(self, pkg, line_num, args_line):
892 """Collect cc compilation flags and src/out file names."""
893 self.pkg = pkg
894 self.line_num = line_num
895 self.line = args_line
896 args = args_line.split()
897 i = 0
898 while i < len(args):
899 arg = args[i]
900 if arg == '"-c"':
901 i += 1
902 if args[i].startswith('"-o'):
903 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c"
904 self.obj = unquote(args[i])[2:]
905 i += 1
906 self.src = unquote(args[i])
907 else:
908 self.src = unquote(args[i])
909 elif arg == '"-o"':
910 i += 1
911 self.obj = unquote(args[i])
912 elif arg == '"-I"':
913 i += 1
914 self.iflags.append(unquote(args[i]))
915 elif arg.startswith('"-D'):
916 self.dflags.append(unquote(args[i])[2:])
917 elif arg.startswith('"-f'):
918 self.fflags.append(unquote(args[i])[2:])
919 elif arg.startswith('"-W'):
920 self.wflags.append(unquote(args[i])[2:])
921 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or
922 arg == '"-g3"'):
923 # ignore -O -m64 -g
924 self.other_args.append(unquote(args[i]))
925 i += 1
926 self.dflags = sorted(set(self.dflags))
927 self.fflags = sorted(set(self.fflags))
928 # self.wflags is not sorted because some are order sensitive
929 # and we ignore them anyway.
930 if self.pkg not in self.runner.pkg_obj2cc:
931 self.runner.pkg_obj2cc[self.pkg] = {}
932 self.runner.pkg_obj2cc[self.pkg][self.obj] = self
933 return self
934
935 def write(self, s):
936 self.outf.write(s + '\n')
937
938 def dump_debug_flags(self, name, flags):
939 self.write('// ' + name + ':')
940 for f in flags:
941 self.write('// %s' % f)
942
943 def dump(self):
944 """Dump only error/debug info to the output .bp file."""
945 if not self.runner.args.debug:
946 return
947 self.runner.init_bp_file(self.outf_name)
948 with open(self.outf_name, 'a') as outf:
949 self.outf = outf
950 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line)
951 self.write('// cc_object for %12s' % self.pkg)
952 self.write('// src = %s' % short_out_name(self.pkg, self.src))
953 self.write('// obj = %s' % short_out_name(self.pkg, self.obj))
954 self.dump_debug_flags('-I flags', self.iflags)
955 self.dump_debug_flags('-D flags', self.dflags)
956 self.dump_debug_flags('-f flags', self.fflags)
957 self.dump_debug_flags('-W flags', self.wflags)
958 if self.other_args:
959 self.dump_debug_flags('other args', self.other_args)
960
961
962class Runner(object):
963 """Main class to parse cargo -v output and print Android module definitions."""
964
965 def __init__(self, args):
966 self.bp_files = set() # Remember all output Android.bp files.
967 self.root_pkg = '' # name of package in ./Cargo.toml
968 # Saved flags, modes, and data.
969 self.args = args
970 self.dry_run = not args.run
971 self.skip_cargo = args.skipcargo
972 # All cc/ar objects, crates, dependencies, and warning files
973 self.cc_objects = list()
974 self.pkg_obj2cc = {}
975 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i]
976 self.ar_objects = list()
977 self.crates = list()
978 self.dependencies = list() # dependent and build script crates
979 self.warning_files = set()
980 # Keep a unique mapping from (module name) to crate
981 self.name_owners = {}
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700982 # Save and dump all errors from cargo to Android.bp.
983 self.errors = ''
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800984 # Default action is cargo clean, followed by build or user given actions.
985 if args.cargo:
986 self.cargo = ['clean'] + args.cargo
987 else:
988 self.cargo = ['clean', 'build']
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700989 if args.no_host: # do not run "cargo build" for host
990 self.cargo = ['clean']
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800991 default_target = '--target x86_64-unknown-linux-gnu'
992 if args.device:
993 self.cargo.append('build ' + default_target)
994 if args.tests:
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700995 if not args.no_host:
996 self.cargo.append('build --tests')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800997 self.cargo.append('build --tests ' + default_target)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700998 elif args.tests and not args.no_host:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800999 self.cargo.append('build --tests')
1000
1001 def init_bp_file(self, name):
1002 if name not in self.bp_files:
1003 self.bp_files.add(name)
1004 with open(name, 'w') as outf:
Andrew Walbran80e90be2020-06-09 14:33:18 +01001005 outf.write(ANDROID_BP_HEADER.format(args=' '.join(sys.argv[1:])))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001006
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001007 def try_claim_module_name(self, name, owner):
1008 """Reserve and return True if it has not been reserved yet."""
1009 if name not in self.name_owners or owner == self.name_owners[name]:
1010 self.name_owners[name] = owner
1011 return True
1012 return False
1013
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001014 def claim_module_name(self, prefix, owner, counter):
1015 """Return prefix if not owned yet, otherwise, prefix+str(counter)."""
1016 while True:
1017 name = prefix
1018 if counter > 0:
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001019 name += '_' + str(counter)
1020 if self.try_claim_module_name(name, owner):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001021 return name
1022 counter += 1
1023
1024 def find_root_pkg(self):
1025 """Read name of [package] in ./Cargo.toml."""
1026 if not os.path.exists('./Cargo.toml'):
1027 return
1028 with open('./Cargo.toml', 'r') as inf:
1029 pkg_section = re.compile(r'^ *\[package\]')
1030 name = re.compile('^ *name *= * "([^"]*)"')
1031 in_pkg = False
1032 for line in inf:
1033 if in_pkg:
1034 if name.match(line):
1035 self.root_pkg = name.match(line).group(1)
1036 break
1037 else:
1038 in_pkg = pkg_section.match(line) is not None
1039
1040 def run_cargo(self):
1041 """Calls cargo -v and save its output to ./cargo.out."""
1042 if self.skip_cargo:
1043 return self
1044 cargo = './Cargo.toml'
1045 if not os.access(cargo, os.R_OK):
1046 print('ERROR: Cannot find or read', cargo)
1047 return self
1048 if not self.dry_run and os.path.exists('cargo.out'):
1049 os.remove('cargo.out')
1050 cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> cargo.out 2>&1'
1051 for c in self.cargo:
1052 features = ''
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001053 if c != 'clean':
1054 if self.args.features is not None:
1055 features = ' --no-default-features'
1056 if self.args.features:
1057 features += ' --features ' + self.args.features
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001058 cmd = 'cargo -vv ' if self.args.vv else 'cargo -v '
1059 cmd += c + features + cmd_tail
1060 if self.args.rustflags and c != 'clean':
1061 cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
1062 if self.dry_run:
1063 print('Dry-run skip:', cmd)
1064 else:
1065 if self.args.verbose:
1066 print('Running:', cmd)
1067 with open('cargo.out', 'a') as cargo_out:
1068 cargo_out.write('### Running: ' + cmd + '\n')
1069 os.system(cmd)
1070 return self
1071
1072 def dump_dependencies(self):
1073 """Append dependencies and their features to Android.bp."""
1074 if not self.dependencies:
1075 return
1076 dependent_list = list()
1077 for c in self.dependencies:
1078 dependent_list.append(c.feature_list())
1079 sorted_dependencies = sorted(set(dependent_list))
1080 self.init_bp_file('Android.bp')
1081 with open('Android.bp', 'a') as outf:
1082 outf.write('\n// dependent_library ["feature_list"]\n')
1083 for s in sorted_dependencies:
1084 outf.write('// ' + s + '\n')
1085
1086 def dump_pkg_obj2cc(self):
1087 """Dump debug info of the pkg_obj2cc map."""
1088 if not self.args.debug:
1089 return
1090 self.init_bp_file('Android.bp')
1091 with open('Android.bp', 'a') as outf:
1092 sorted_pkgs = sorted(self.pkg_obj2cc.keys())
1093 for pkg in sorted_pkgs:
1094 if not self.pkg_obj2cc[pkg]:
1095 continue
1096 outf.write('\n// obj => src for %s\n' % pkg)
1097 obj2cc = self.pkg_obj2cc[pkg]
1098 for obj in sorted(obj2cc.keys()):
1099 outf.write('// ' + short_out_name(pkg, obj) + ' => ' +
1100 short_out_name(pkg, obj2cc[obj].src) + '\n')
1101
1102 def gen_bp(self):
1103 """Parse cargo.out and generate Android.bp files."""
1104 if self.dry_run:
1105 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp')
1106 elif os.path.exists(CARGO_OUT):
1107 self.find_root_pkg()
1108 with open(CARGO_OUT, 'r') as cargo_out:
1109 self.parse(cargo_out, 'Android.bp')
1110 self.crates.sort(key=get_module_name)
1111 for obj in self.cc_objects:
1112 obj.dump()
1113 self.dump_pkg_obj2cc()
1114 for crate in self.crates:
1115 crate.dump()
1116 dumped_libs = set()
1117 for lib in self.ar_objects:
1118 if lib.pkg == self.root_pkg:
1119 lib_name = file_base_name(lib.lib)
1120 if lib_name not in dumped_libs:
1121 dumped_libs.add(lib_name)
1122 lib.dump()
1123 if self.args.dependencies and self.dependencies:
1124 self.dump_dependencies()
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001125 if self.errors:
1126 self.append_to_bp('\nErrors in ' + CARGO_OUT + ':\n' + self.errors)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001127 return self
1128
1129 def add_ar_object(self, obj):
1130 self.ar_objects.append(obj)
1131
1132 def add_cc_object(self, obj):
1133 self.cc_objects.append(obj)
1134
1135 def add_crate(self, crate):
1136 """Merge crate with someone in crates, or append to it. Return crates."""
1137 if crate.skip_crate():
1138 if self.args.debug: # include debug info of all crates
1139 self.crates.append(crate)
1140 if self.args.dependencies: # include only dependent crates
1141 if (is_dependent_file_path(crate.main_src) and
1142 not is_build_crate_name(crate.crate_name)):
1143 self.dependencies.append(crate)
1144 else:
1145 for c in self.crates:
1146 if c.merge(crate, 'Android.bp'):
1147 return
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001148 # If not merged, decide module type and name now.
1149 crate.decide_module_type()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001150 self.crates.append(crate)
1151
1152 def find_warning_owners(self):
1153 """For each warning file, find its owner crate."""
1154 missing_owner = False
1155 for f in self.warning_files:
1156 cargo_dir = '' # find lowest crate, with longest path
1157 owner = None # owner crate of this warning
1158 for c in self.crates:
1159 if (f.startswith(c.cargo_dir + '/') and
1160 len(cargo_dir) < len(c.cargo_dir)):
1161 cargo_dir = c.cargo_dir
1162 owner = c
1163 if owner:
1164 owner.has_warning = True
1165 else:
1166 missing_owner = True
1167 if missing_owner and os.path.exists('Cargo.toml'):
1168 # owner is the root cargo, with empty cargo_dir
1169 for c in self.crates:
1170 if not c.cargo_dir:
1171 c.has_warning = True
1172
1173 def rustc_command(self, n, rustc_line, line, outf_name):
1174 """Process a rustc command line from cargo -vv output."""
1175 # cargo build -vv output can have multiple lines for a rustc command
1176 # due to '\n' in strings for environment variables.
1177 # strip removes leading spaces and '\n' at the end
1178 new_rustc = (rustc_line.strip() + line) if rustc_line else line
1179 # Use an heuristic to detect the completions of a multi-line command.
1180 # This might fail for some very rare case, but easy to fix manually.
1181 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0:
1182 return new_rustc
1183 if RUSTC_VV_CMD_ARGS.match(new_rustc):
1184 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1)
1185 self.add_crate(Crate(self, outf_name).parse(n, args))
1186 else:
1187 self.assert_empty_vv_line(new_rustc)
1188 return ''
1189
1190 def cc_ar_command(self, n, groups, outf_name):
1191 pkg = groups.group(1)
1192 line = groups.group(3)
1193 if groups.group(2) == 'cc':
1194 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line))
1195 else:
1196 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line))
1197
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001198 def append_to_bp(self, line):
1199 self.init_bp_file('Android.bp')
1200 with open('Android.bp', 'a') as outf:
1201 outf.write(line)
1202
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001203 def assert_empty_vv_line(self, line):
1204 if line: # report error if line is not empty
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001205 self.append_to_bp('ERROR -vv line: ' + line)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001206 return ''
1207
1208 def parse(self, inf, outf_name):
1209 """Parse rustc and warning messages in inf, return a list of Crates."""
1210 n = 0 # line number
1211 prev_warning = False # true if the previous line was warning: ...
1212 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
1213 for line in inf:
1214 n += 1
1215 if line.startswith('warning: '):
1216 prev_warning = True
1217 rustc_line = self.assert_empty_vv_line(rustc_line)
1218 continue
1219 new_rustc = ''
1220 if RUSTC_PAT.match(line):
1221 args_line = RUSTC_PAT.match(line).group(1)
1222 self.add_crate(Crate(self, outf_name).parse(n, args_line))
1223 self.assert_empty_vv_line(rustc_line)
1224 elif rustc_line or RUSTC_VV_PAT.match(line):
1225 new_rustc = self.rustc_command(n, rustc_line, line, outf_name)
1226 elif CC_AR_VV_PAT.match(line):
1227 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name)
1228 elif prev_warning and WARNING_FILE_PAT.match(line):
1229 self.assert_empty_vv_line(rustc_line)
1230 fpath = WARNING_FILE_PAT.match(line).group(1)
1231 if fpath[0] != '/': # ignore absolute path
1232 self.warning_files.add(fpath)
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001233 elif line.startswith('error: ') or line.startswith('error[E'):
1234 self.errors += line
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001235 prev_warning = False
1236 rustc_line = new_rustc
1237 self.find_warning_owners()
1238
1239
1240def parse_args():
1241 """Parse main arguments."""
1242 parser = argparse.ArgumentParser('cargo2android')
1243 parser.add_argument(
1244 '--cargo',
1245 action='append',
1246 metavar='args_string',
1247 help=('extra cargo build -v args in a string, ' +
1248 'each --cargo flag calls cargo build -v once'))
1249 parser.add_argument(
1250 '--debug',
1251 action='store_true',
1252 default=False,
1253 help='dump debug info into Android.bp')
1254 parser.add_argument(
1255 '--dependencies',
1256 action='store_true',
1257 default=False,
1258 help='dump debug info of dependent crates')
1259 parser.add_argument(
1260 '--device',
1261 action='store_true',
1262 default=False,
1263 help='run cargo also for a default device target')
1264 parser.add_argument(
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001265 '--no-host',
1266 action='store_true',
1267 default=False,
1268 help='do not run cargo for the host; only for the device target')
1269 parser.add_argument(
1270 '--host-first-multilib',
1271 action='store_true',
1272 default=False,
1273 help=('add a compile_multilib:"first" property ' +
1274 'to Android.bp host modules.'))
1275 parser.add_argument(
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001276 '--features',
1277 type=str,
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001278 help=('pass features to cargo build, ' +
1279 'empty string means no default features'))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001280 parser.add_argument(
1281 '--onefile',
1282 action='store_true',
1283 default=False,
1284 help=('output all into one ./Android.bp, default will generate ' +
1285 'one Android.bp per Cargo.toml in subdirectories'))
1286 parser.add_argument(
1287 '--run',
1288 action='store_true',
1289 default=False,
1290 help='run it, default is dry-run')
1291 parser.add_argument('--rustflags', type=str, help='passing flags to rustc')
1292 parser.add_argument(
1293 '--skipcargo',
1294 action='store_true',
1295 default=False,
1296 help='skip cargo command, parse cargo.out, and generate Android.bp')
1297 parser.add_argument(
1298 '--tests',
1299 action='store_true',
1300 default=False,
1301 help='run cargo build --tests after normal build')
1302 parser.add_argument(
1303 '--verbose',
1304 action='store_true',
1305 default=False,
1306 help='echo executed commands')
1307 parser.add_argument(
1308 '--vv',
1309 action='store_true',
1310 default=False,
1311 help='run cargo with -vv instead of default -v')
1312 return parser.parse_args()
1313
1314
1315def main():
1316 args = parse_args()
1317 if not args.run: # default is dry-run
1318 print(DRY_RUN_NOTE)
1319 Runner(args).run_cargo().gen_bp()
1320
1321
1322if __name__ == '__main__':
1323 main()