blob: 2e93521935ba300658ec64b1975869dd5d05cdf9 [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 Hsiehf7eff152020-07-16 15:36:22 -070051 Note that when there are test modules generated into Android.bp,
52 corresponding test entries will also be added into the TEST_MAPPING file.
53
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -070054If there are rustc warning messages, this script will add
55a warning comment to the owner crate module in Android.bp.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080056"""
57
58from __future__ import print_function
59
60import argparse
61import os
62import os.path
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -070063import platform
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080064import re
Andrew Walbran80e90be2020-06-09 14:33:18 +010065import sys
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080066
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -070067# Some Rust packages include extra unwanted crates.
68# This set contains all such excluded crate names.
69EXCLUDED_CRATES = set(['protobuf_bin_gen_rust_do_not_use'])
70
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080071RENAME_MAP = {
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -070072 # This map includes all changes to the default rust module names
73 # to resolve name conflicts, avoid confusion, or work as plugin.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080074 'libbacktrace': 'libbacktrace_rust',
75 'libgcc': 'libgcc_rust',
76 'liblog': 'liblog_rust',
77 'libsync': 'libsync_rust',
78 'libx86_64': 'libx86_64_rust',
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -070079 'protoc_gen_rust': 'protoc-gen-rust',
80}
81
82RENAME_STEM_MAP = {
83 # This map includes all changes to the default rust module stem names,
84 # which is used for output files when different from the module name.
85 'protoc_gen_rust': 'protoc-gen-rust',
86}
87
88RENAME_DEFAULTS_MAP = {
89 # This map includes all changes to the default prefix of rust_default
90 # module names, to avoid conflict with existing Android modules.
91 'libc': 'rust_libc',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080092}
93
94# Header added to all generated Android.bp files.
Andrew Walbran80e90be2020-06-09 14:33:18 +010095ANDROID_BP_HEADER = '// This file is generated by cargo2android.py {args}.\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080096
97CARGO_OUT = 'cargo.out' # Name of file to keep cargo build -v output.
98
99TARGET_TMP = 'target.tmp' # Name of temporary output directory.
100
101# Message to be displayed when this script is called without the --run flag.
102DRY_RUN_NOTE = (
103 'Dry-run: This script uses ./' + TARGET_TMP + ' for output directory,\n' +
104 'runs cargo clean, runs cargo build -v, saves output to ./cargo.out,\n' +
105 'and writes to Android.bp in the current and subdirectories.\n\n' +
106 'To do do all of the above, use the --run flag.\n' +
107 'See --help for other flags, and more usage notes in this script.\n')
108
109# Cargo -v output of a call to rustc.
110RUSTC_PAT = re.compile('^ +Running `rustc (.*)`$')
111
112# Cargo -vv output of a call to rustc could be split into multiple lines.
113# Assume that the first line will contain some CARGO_* env definition.
114RUSTC_VV_PAT = re.compile('^ +Running `.*CARGO_.*=.*$')
115# The combined -vv output rustc command line pattern.
116RUSTC_VV_CMD_ARGS = re.compile('^ *Running `.*CARGO_.*=.* rustc (.*)`$')
117
118# Cargo -vv output of a "cc" or "ar" command; all in one line.
119CC_AR_VV_PAT = re.compile(r'^\[([^ ]*)[^\]]*\] running:? "(cc|ar)" (.*)$')
120# Some package, such as ring-0.13.5, has pattern '... running "cc"'.
121
122# Rustc output of file location path pattern for a warning message.
123WARNING_FILE_PAT = re.compile('^ *--> ([^:]*):[0-9]+')
124
125# Rust package name with suffix -d1.d2.d3.
126VERSION_SUFFIX_PAT = re.compile(r'^(.*)-[0-9]+\.[0-9]+\.[0-9]+$')
127
128
129def altered_name(name):
130 return RENAME_MAP[name] if (name in RENAME_MAP) else name
131
132
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700133def altered_stem(name):
134 return RENAME_STEM_MAP[name] if (name in RENAME_STEM_MAP) else name
135
136
137def altered_defaults(name):
138 return RENAME_DEFAULTS_MAP[name] if (name in RENAME_DEFAULTS_MAP) else name
139
140
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800141def is_build_crate_name(name):
142 # We added special prefix to build script crate names.
143 return name.startswith('build_script_')
144
145
146def is_dependent_file_path(path):
147 # Absolute or dependent '.../' paths are not main files of this crate.
148 return path.startswith('/') or path.startswith('.../')
149
150
151def get_module_name(crate): # to sort crates in a list
152 return crate.module_name
153
154
155def pkg2crate_name(s):
156 return s.replace('-', '_').replace('.', '_')
157
158
159def file_base_name(path):
160 return os.path.splitext(os.path.basename(path))[0]
161
162
163def test_base_name(path):
164 return pkg2crate_name(file_base_name(path))
165
166
167def unquote(s): # remove quotes around str
168 if s and len(s) > 1 and s[0] == '"' and s[-1] == '"':
169 return s[1:-1]
170 return s
171
172
173def remove_version_suffix(s): # remove -d1.d2.d3 suffix
174 if VERSION_SUFFIX_PAT.match(s):
175 return VERSION_SUFFIX_PAT.match(s).group(1)
176 return s
177
178
179def short_out_name(pkg, s): # replace /.../pkg-*/out/* with .../out/*
180 return re.sub('^/.*/' + pkg + '-[0-9a-f]*/out/', '.../out/', s)
181
182
183def escape_quotes(s): # replace '"' with '\\"'
184 return s.replace('"', '\\"')
185
186
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700187class TestMapping(object):
188 """Entries for a TEST_MAPPING file."""
189
190 def __init__(self):
191 self.entries = []
192
193 def add_test(self, name, host):
194 self.entries.append((name, host))
195
196 def is_empty(self):
197 return not self.entries
198
199 def dump(self, outf_name):
200 """Append all entries into the output file."""
201 if self.is_empty():
202 return
203 with open(outf_name, 'w') as outf:
204 outf.write('// Generated by cargo2android.py for tests in Android.bp\n')
205 outf.write('{\n "presubmit": [\n')
206 is_first = True
207 for (name, host) in self.entries:
208 if not is_first: # add comma and '\n' after the previous entry
209 outf.write(',\n')
210 is_first = False
211 outf.write(' {\n "name": "' + name + '"')
212 if host:
213 outf.write(',\n "host": true\n }')
214 else:
215 outf.write('\n }')
216 outf.write('\n ]\n}\n')
217
218
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800219class Crate(object):
220 """Information of a Rust crate to collect/emit for an Android.bp module."""
221
222 def __init__(self, runner, outf_name):
223 # Remembered global runner and its members.
224 self.runner = runner
225 self.debug = runner.args.debug
226 self.cargo_dir = '' # directory of my Cargo.toml
227 self.outf_name = outf_name # path to Android.bp
228 self.outf = None # open file handle of outf_name during dump*
229 # Variants/results that could be merged from multiple rustc lines.
230 self.host_supported = False
231 self.device_supported = False
232 self.has_warning = False
233 # Android module properties derived from rustc parameters.
234 self.module_name = '' # unique in Android build system
235 self.module_type = '' # rust_{binary,library,test}[_host] etc.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700236 self.defaults = '' # rust_defaults used by rust_test* modules
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700237 self.default_srcs = False # use 'srcs' defined in self.defaults
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800238 self.root_pkg = '' # parent package name of a sub/test packge, from -L
239 self.srcs = list() # main_src or merged multiple source files
240 self.stem = '' # real base name of output file
241 # Kept parsed status
242 self.errors = '' # all errors found during parsing
243 self.line_num = 1 # runner told input source line number
244 self.line = '' # original rustc command line parameters
245 # Parameters collected from rustc command line.
246 self.crate_name = '' # follows --crate-name
247 self.main_src = '' # follows crate_name parameter, shortened
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700248 self.crate_types = list() # follows --crate-type
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800249 self.cfgs = list() # follows --cfg, without feature= prefix
250 self.features = list() # follows --cfg, name in 'feature="..."'
251 self.codegens = list() # follows -C, some ignored
252 self.externs = list() # follows --extern
253 self.core_externs = list() # first part of self.externs elements
254 self.static_libs = list() # e.g. -l static=host_cpuid
255 self.shared_libs = list() # e.g. -l dylib=wayland-client, -l z
256 self.cap_lints = '' # follows --cap-lints
257 self.emit_list = '' # e.g., --emit=dep-info,metadata,link
258 self.edition = '2015' # rustc default, e.g., --edition=2018
259 self.target = '' # follows --target
260
261 def write(self, s):
262 # convenient way to output one line at a time with EOL.
263 self.outf.write(s + '\n')
264
265 def same_flags(self, other):
266 # host_supported, device_supported, has_warning are not compared but merged
267 # target is not compared, to merge different target/host modules
268 # externs is not compared; only core_externs is compared
269 return (not self.errors and not other.errors and
270 self.edition == other.edition and
271 self.cap_lints == other.cap_lints and
272 self.emit_list == other.emit_list and
273 self.core_externs == other.core_externs and
274 self.codegens == other.codegens and
275 self.features == other.features and
276 self.static_libs == other.static_libs and
277 self.shared_libs == other.shared_libs and self.cfgs == other.cfgs)
278
279 def merge_host_device(self, other):
280 """Returns true if attributes are the same except host/device support."""
281 return (self.crate_name == other.crate_name and
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700282 self.crate_types == other.crate_types and
283 self.main_src == other.main_src and
284 # before merge, each test module has an unique module name and stem
285 (self.stem == other.stem or self.crate_types == ['test']) and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800286 self.root_pkg == other.root_pkg and not self.skip_crate() and
287 self.same_flags(other))
288
289 def merge_test(self, other):
290 """Returns true if self and other are tests of same root_pkg."""
291 # Before merger, each test has its own crate_name.
292 # A merged test uses its source file base name as output file name,
293 # so a test is mergeable only if its base name equals to its crate name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700294 return (self.crate_types == other.crate_types and
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700295 self.crate_types == ['test'] and self.root_pkg == other.root_pkg and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800296 not self.skip_crate() and
297 other.crate_name == test_base_name(other.main_src) and
298 (len(self.srcs) > 1 or
299 (self.crate_name == test_base_name(self.main_src)) and
300 self.host_supported == other.host_supported and
301 self.device_supported == other.device_supported) and
302 self.same_flags(other))
303
304 def merge(self, other, outf_name):
305 """Try to merge crate into self."""
306 should_merge_host_device = self.merge_host_device(other)
307 should_merge_test = False
308 if not should_merge_host_device:
309 should_merge_test = self.merge_test(other)
310 # A for-device test crate can be merged with its for-host version,
311 # or merged with a different test for the same host or device.
312 # Since we run cargo once for each device or host, test crates for the
313 # first device or host will be merged first. Then test crates for a
314 # different device or host should be allowed to be merged into a
315 # previously merged one, maybe for a different device or host.
316 if should_merge_host_device or should_merge_test:
317 self.runner.init_bp_file(outf_name)
318 with open(outf_name, 'a') as outf: # to write debug info
319 self.outf = outf
320 other.outf = outf
321 self.do_merge(other, should_merge_test)
322 return True
323 return False
324
325 def do_merge(self, other, should_merge_test):
326 """Merge attributes of other to self."""
327 if self.debug:
328 self.write('\n// Before merge definition (1):')
329 self.dump_debug_info()
330 self.write('\n// Before merge definition (2):')
331 other.dump_debug_info()
332 # Merge properties of other to self.
333 self.host_supported = self.host_supported or other.host_supported
334 self.device_supported = self.device_supported or other.device_supported
335 self.has_warning = self.has_warning or other.has_warning
336 if not self.target: # okay to keep only the first target triple
337 self.target = other.target
338 # decide_module_type sets up default self.stem,
339 # which can be changed if self is a merged test module.
340 self.decide_module_type()
341 if should_merge_test:
342 self.srcs.append(other.main_src)
343 # use a short unique name as the merged module name.
344 prefix = self.root_pkg + '_tests'
345 self.module_name = self.runner.claim_module_name(prefix, self, 0)
346 self.stem = self.module_name
347 # This normalized root_pkg name although might be the same
348 # as other module's crate_name, it is not actually used for
349 # output file name. A merged test module always have multiple
350 # source files and each source file base name is used as
351 # its output file name.
352 self.crate_name = pkg2crate_name(self.root_pkg)
353 if self.debug:
354 self.write('\n// After merge definition (1):')
355 self.dump_debug_info()
356
357 def find_cargo_dir(self):
358 """Deepest directory with Cargo.toml and contains the main_src."""
359 if not is_dependent_file_path(self.main_src):
360 dir_name = os.path.dirname(self.main_src)
361 while dir_name:
362 if os.path.exists(dir_name + '/Cargo.toml'):
363 self.cargo_dir = dir_name
364 return
365 dir_name = os.path.dirname(dir_name)
366
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700367 def add_codegens_flag(self, flag):
368 # ignore options not used in Android
369 # 'prefer-dynamic' does not work with common flag -C lto
370 if not (flag.startswith('debuginfo=') or
371 flag.startswith('extra-filename=') or
372 flag.startswith('incremental=') or
373 flag.startswith('metadata=') or
374 flag == 'prefer-dynamic'):
375 self.codegens.append(flag)
376
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800377 def parse(self, line_num, line):
378 """Find important rustc arguments to convert to Android.bp properties."""
379 self.line_num = line_num
380 self.line = line
381 args = line.split() # Loop through every argument of rustc.
382 i = 0
383 while i < len(args):
384 arg = args[i]
385 if arg == '--crate-name':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700386 i += 1
387 self.crate_name = args[i]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800388 elif arg == '--crate-type':
389 i += 1
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700390 # cargo calls rustc with multiple --crate-type flags.
391 # rustc can accept:
392 # --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
393 self.crate_types.append(args[i])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800394 elif arg == '--test':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700395 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800396 elif arg == '--target':
397 i += 1
398 self.target = args[i]
399 elif arg == '--cfg':
400 i += 1
401 if args[i].startswith('\'feature='):
402 self.features.append(unquote(args[i].replace('\'feature=', '')[:-1]))
403 else:
404 self.cfgs.append(args[i])
405 elif arg == '--extern':
406 i += 1
407 extern_names = re.sub('=/[^ ]*/deps/', ' = ', args[i])
408 self.externs.append(extern_names)
409 self.core_externs.append(re.sub(' = .*', '', extern_names))
410 elif arg == '-C': # codegen options
411 i += 1
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700412 self.add_codegens_flag(args[i])
413 elif arg.startswith('-C'):
414 # cargo has been passing "-C <xyz>" flag to rustc,
415 # but newer cargo could pass '-Cembed-bitcode=no' to rustc.
416 self.add_codegens_flag(arg[2:])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800417 elif arg == '--cap-lints':
418 i += 1
419 self.cap_lints = args[i]
420 elif arg == '-L':
421 i += 1
422 if args[i].startswith('dependency=') and args[i].endswith('/deps'):
423 if '/' + TARGET_TMP + '/' in args[i]:
424 self.root_pkg = re.sub(
425 '^.*/', '', re.sub('/' + TARGET_TMP + '/.*/deps$', '', args[i]))
426 else:
427 self.root_pkg = re.sub('^.*/', '',
428 re.sub('/[^/]+/[^/]+/deps$', '', args[i]))
429 self.root_pkg = remove_version_suffix(self.root_pkg)
430 elif arg == '-l':
431 i += 1
432 if args[i].startswith('static='):
433 self.static_libs.append(re.sub('static=', '', args[i]))
434 elif args[i].startswith('dylib='):
435 self.shared_libs.append(re.sub('dylib=', '', args[i]))
436 else:
437 self.shared_libs.append(args[i])
438 elif arg == '--out-dir' or arg == '--color': # ignored
439 i += 1
440 elif arg.startswith('--error-format=') or arg.startswith('--json='):
441 _ = arg # ignored
442 elif arg.startswith('--emit='):
443 self.emit_list = arg.replace('--emit=', '')
444 elif arg.startswith('--edition='):
445 self.edition = arg.replace('--edition=', '')
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700446 elif not arg.startswith('-'):
447 # shorten imported crate main source paths like $HOME/.cargo/
448 # registry/src/github.com-1ecc6299db9ec823/memchr-2.3.3/src/lib.rs
449 self.main_src = re.sub(r'^/[^ ]*/registry/src/', '.../', args[i])
450 self.main_src = re.sub(r'^\.\.\./github.com-[0-9a-f]*/', '.../',
451 self.main_src)
452 self.find_cargo_dir()
453 if self.cargo_dir and not self.runner.args.onefile:
454 # Write to Android.bp in the subdirectory with Cargo.toml.
455 self.outf_name = self.cargo_dir + '/Android.bp'
456 self.main_src = self.main_src[len(self.cargo_dir) + 1:]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800457 else:
458 self.errors += 'ERROR: unknown ' + arg + '\n'
459 i += 1
460 if not self.crate_name:
461 self.errors += 'ERROR: missing --crate-name\n'
462 if not self.main_src:
463 self.errors += 'ERROR: missing main source file\n'
464 else:
465 self.srcs.append(self.main_src)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700466 if not self.crate_types:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800467 # Treat "--cfg test" as "--test"
468 if 'test' in self.cfgs:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700469 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800470 else:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700471 self.errors += 'ERROR: missing --crate-type or --test\n'
472 elif len(self.crate_types) > 1:
473 if 'test' in self.crate_types:
474 self.errors += 'ERROR: cannot handle both --crate-type and --test\n'
475 if 'lib' in self.crate_types and 'rlib' in self.crate_types:
476 self.errors += 'ERROR: cannot generate both lib and rlib crate types\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800477 if not self.root_pkg:
478 self.root_pkg = self.crate_name
479 if self.target:
480 self.device_supported = True
481 self.host_supported = True # assume host supported for all builds
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700482 if self.runner.args.no_host: # unless --no-host was specified
483 self.host_supported = False
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800484 self.cfgs = sorted(set(self.cfgs))
485 self.features = sorted(set(self.features))
486 self.codegens = sorted(set(self.codegens))
487 self.externs = sorted(set(self.externs))
488 self.core_externs = sorted(set(self.core_externs))
489 self.static_libs = sorted(set(self.static_libs))
490 self.shared_libs = sorted(set(self.shared_libs))
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700491 self.crate_types = sorted(set(self.crate_types))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800492 self.decide_module_type()
493 self.module_name = altered_name(self.stem)
494 return self
495
496 def dump_line(self):
497 self.write('\n// Line ' + str(self.line_num) + ' ' + self.line)
498
499 def feature_list(self):
500 """Return a string of main_src + "feature_list"."""
501 pkg = self.main_src
502 if pkg.startswith('.../'): # keep only the main package name
503 pkg = re.sub('/.*', '', pkg[4:])
504 if not self.features:
505 return pkg
506 return pkg + ' "' + ','.join(self.features) + '"'
507
508 def dump_skip_crate(self, kind):
509 if self.debug:
510 self.write('\n// IGNORED: ' + kind + ' ' + self.main_src)
511 return self
512
513 def skip_crate(self):
514 """Return crate_name or a message if this crate should be skipped."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700515 if (is_build_crate_name(self.crate_name) or
516 self.crate_name in EXCLUDED_CRATES):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800517 return self.crate_name
518 if is_dependent_file_path(self.main_src):
519 return 'dependent crate'
520 return ''
521
522 def dump(self):
523 """Dump all error/debug/module code to the output .bp file."""
524 self.runner.init_bp_file(self.outf_name)
525 with open(self.outf_name, 'a') as outf:
526 self.outf = outf
527 if self.errors:
528 self.dump_line()
529 self.write(self.errors)
530 elif self.skip_crate():
531 self.dump_skip_crate(self.skip_crate())
532 else:
533 if self.debug:
534 self.dump_debug_info()
535 self.dump_android_module()
536
537 def dump_debug_info(self):
538 """Dump parsed data, when cargo2android is called with --debug."""
539
540 def dump(name, value):
541 self.write('//%12s = %s' % (name, value))
542
543 def opt_dump(name, value):
544 if value:
545 dump(name, value)
546
547 def dump_list(fmt, values):
548 for v in values:
549 self.write(fmt % v)
550
551 self.dump_line()
552 dump('module_name', self.module_name)
553 dump('crate_name', self.crate_name)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700554 dump('crate_types', self.crate_types)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800555 dump('main_src', self.main_src)
556 dump('has_warning', self.has_warning)
557 dump('for_host', self.host_supported)
558 dump('for_device', self.device_supported)
559 dump('module_type', self.module_type)
560 opt_dump('target', self.target)
561 opt_dump('edition', self.edition)
562 opt_dump('emit_list', self.emit_list)
563 opt_dump('cap_lints', self.cap_lints)
564 dump_list('// cfg = %s', self.cfgs)
565 dump_list('// cfg = \'feature "%s"\'', self.features)
566 # TODO(chh): escape quotes in self.features, but not in other dump_list
567 dump_list('// codegen = %s', self.codegens)
568 dump_list('// externs = %s', self.externs)
569 dump_list('// -l static = %s', self.static_libs)
570 dump_list('// -l (dylib) = %s', self.shared_libs)
571
572 def dump_android_module(self):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700573 """Dump one or more Android module definition, depending on crate_types."""
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700574 if len(self.crate_types) == 1:
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700575 self.dump_single_type_android_module()
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700576 return
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700577 if 'test' in self.crate_types:
578 self.write('\nERROR: multiple crate types cannot include test type')
579 return
580 # Dump one Android module per crate_type.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700581 for crate_type in self.crate_types:
582 self.decide_one_module_type(crate_type)
583 self.dump_one_android_module(crate_type)
584
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700585 def build_default_name(self):
586 """Return a short and readable name for the rust_defaults module."""
587 # Choices: (1) root_pkg + '_defaults',
588 # (2) root_pkg + '_defaults_' + crate_name
589 # (3) root_pkg + '_defaults_' + main_src_basename_path
590 # (4) root_pkg + '_defaults_' + a_positive_sequence_number
591 name1 = altered_defaults(self.root_pkg) + '_defaults'
592 if self.runner.try_claim_module_name(name1, self):
593 return name1
594 name2 = name1 + '_' + self.crate_name
595 if self.runner.try_claim_module_name(name2, self):
596 return name2
597 name3 = name1 + '_' + self.main_src_basename_path()
598 if self.runner.try_claim_module_name(name3, self):
599 return name3
600 return self.runner.claim_module_name(name1, self, 0)
601
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700602 def dump_defaults_module(self):
603 """Dump a rust_defaults module to be shared by other modules."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700604 name = self.build_default_name()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700605 self.defaults = name
606 self.write('\nrust_defaults {')
607 self.write(' name: "' + name + '",')
608 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700609 if len(self.srcs) == 1: # only one source file; share it in defaults
610 self.default_srcs = True
611 if self.has_warning and not self.cap_lints:
612 self.write(' // has rustc warnings')
613 self.write(' srcs: ["' + self.main_src + '"],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700614 if 'test' in self.crate_types:
615 self.write(' test_suites: ["general-tests"],')
616 self.write(' auto_gen_config: true,')
617 self.dump_edition_flags_libs()
618 self.write('}')
619
620 def dump_single_type_android_module(self):
621 """Dump one simple Android module, which has only one crate_type."""
622 crate_type = self.crate_types[0]
623 if crate_type != 'test':
624 # do not change self.stem or self.module_name
625 self.dump_one_android_module(crate_type)
626 return
627 # Dump one test module per source file, and separate host and device tests.
628 # crate_type == 'test'
629 if (self.host_supported and self.device_supported) or len(self.srcs) > 1:
630 self.srcs = sorted(set(self.srcs))
631 self.dump_defaults_module()
632 saved_srcs = self.srcs
633 for src in saved_srcs:
634 self.srcs = [src]
635 saved_device_supported = self.device_supported
636 saved_host_supported = self.host_supported
637 saved_main_src = self.main_src
638 self.main_src = src
639 if saved_host_supported:
640 self.device_supported = False
641 self.host_supported = True
642 self.module_name = self.test_module_name()
643 self.decide_one_module_type(crate_type)
644 self.dump_one_android_module(crate_type)
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700645 self.runner.add_test(self.outf_name, self.module_name, True)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700646 if saved_device_supported:
647 self.device_supported = True
648 self.host_supported = False
649 self.module_name = self.test_module_name()
650 self.decide_one_module_type(crate_type)
651 self.dump_one_android_module(crate_type)
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700652 self.runner.add_test(self.outf_name, self.module_name, False)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700653 self.host_supported = saved_host_supported
654 self.device_supported = saved_device_supported
655 self.main_src = saved_main_src
656 self.srcs = saved_srcs
657
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700658 def dump_one_android_module(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800659 """Dump one Android module definition."""
660 if not self.module_type:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700661 self.write('\nERROR: unknown crate_type ' + crate_type)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800662 return
663 self.write('\n' + self.module_type + ' {')
664 self.dump_android_core_properties()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700665 if not self.defaults:
666 self.dump_edition_flags_libs()
667 if self.runner.args.host_first_multilib and self.host_supported and crate_type != 'test':
668 self.write(' compile_multilib: "first",')
669 self.write('}')
670
671 def dump_android_flags(self):
672 """Dump Android module flags property."""
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700673 if not self.cfgs and not self.codegens and not self.cap_lints:
674 return
675 self.write(' flags: [')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800676 if self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700677 self.write(' "--cap-lints ' + self.cap_lints + '",')
678 cfg_fmt = '"--cfg %s"'
679 codegens_fmt = '"-C %s"'
680 self.dump_android_property_list_items(cfg_fmt, self.cfgs)
681 self.dump_android_property_list_items(codegens_fmt, self.codegens)
682 self.write(' ],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700683
684 def dump_edition_flags_libs(self):
685 if self.edition:
686 self.write(' edition: "' + self.edition + '",')
687 self.dump_android_property_list('features', '"%s"', self.features)
688 self.dump_android_flags()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800689 if self.externs:
690 self.dump_android_externs()
691 self.dump_android_property_list('static_libs', '"lib%s"', self.static_libs)
692 self.dump_android_property_list('shared_libs', '"lib%s"', self.shared_libs)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800693
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700694 def main_src_basename_path(self):
695 return re.sub('/', '_', re.sub('.rs$', '', self.main_src))
696
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800697 def test_module_name(self):
698 """Return a unique name for a test module."""
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700699 # root_pkg+(_host|_device) + '_test_'+source_file_name
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700700 suffix = self.main_src_basename_path()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700701 host_device = '_host'
702 if self.device_supported:
703 host_device = '_device'
704 return self.root_pkg + host_device + '_test_' + suffix
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800705
706 def decide_module_type(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700707 # Use the first crate type for the default/first module.
708 crate_type = self.crate_types[0] if self.crate_types else ''
709 self.decide_one_module_type(crate_type)
710
711 def decide_one_module_type(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800712 """Decide which Android module type to use."""
713 host = '' if self.device_supported else '_host'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700714 if crate_type == 'bin': # rust_binary[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800715 self.module_type = 'rust_binary' + host
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700716 # In rare cases like protobuf-codegen, the output binary name must
717 # be renamed to use as a plugin for protoc.
718 self.stem = altered_stem(self.crate_name)
719 self.module_name = altered_name(self.crate_name)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700720 elif crate_type == 'lib': # rust_library[_host]
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700721 # TODO(chh): should this be rust_library[_host]?
722 # Assuming that Cargo.toml do not use both 'lib' and 'rlib',
723 # because we map them both to rlib.
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700724 self.module_type = 'rust_library' + host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800725 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700726 self.module_name = altered_name(self.stem)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700727 elif crate_type == 'rlib': # rust_library[_host]
728 self.module_type = 'rust_library' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700729 self.stem = 'lib' + self.crate_name
730 self.module_name = altered_name(self.stem)
731 elif crate_type == 'dylib': # rust_library[_host]_dylib
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800732 self.module_type = 'rust_library' + host + '_dylib'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700733 self.stem = 'lib' + self.crate_name
734 self.module_name = altered_name(self.stem) + '_dylib'
735 elif crate_type == 'cdylib': # rust_library[_host]_shared
736 self.module_type = 'rust_library' + host + '_shared'
737 self.stem = 'lib' + self.crate_name
738 self.module_name = altered_name(self.stem) + '_shared'
739 elif crate_type == 'staticlib': # rust_library[_host]_static
740 self.module_type = 'rust_library' + host + '_static'
741 self.stem = 'lib' + self.crate_name
742 self.module_name = altered_name(self.stem) + '_static'
743 elif crate_type == 'test': # rust_test[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800744 self.module_type = 'rust_test' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700745 # Before do_merge, stem name is based on the --crate-name parameter.
746 # and test module name is based on stem.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800747 self.stem = self.test_module_name()
748 # self.stem will be changed after merging with other tests.
749 # self.stem is NOT used for final test binary name.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700750 # rust_test uses each source file base name as part of output file name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700751 # In do_merge, this function is called again, with a module_name.
752 # We make sure that the module name is unique in each package.
753 if self.module_name:
754 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add
755 # different suffixes and distinguish multiple tests of the same
756 # crate name. We ignore -C and use claim_module_name to get
757 # unique sequential suffix.
758 self.module_name = self.runner.claim_module_name(
759 self.module_name, self, 0)
760 # Now the module name is unique, stem should also match and unique.
761 self.stem = self.module_name
762 elif crate_type == 'proc-macro': # rust_proc_macro
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800763 self.module_type = 'rust_proc_macro'
764 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700765 self.module_name = altered_name(self.stem)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800766 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]?
767 self.module_type = ''
768 self.stem = ''
769
770 def dump_android_property_list_items(self, fmt, values):
771 for v in values:
772 # fmt has quotes, so we need escape_quotes(v)
773 self.write(' ' + (fmt % escape_quotes(v)) + ',')
774
775 def dump_android_property_list(self, name, fmt, values):
776 if values:
777 self.write(' ' + name + ': [')
778 self.dump_android_property_list_items(fmt, values)
779 self.write(' ],')
780
781 def dump_android_core_properties(self):
782 """Dump the module header, name, stem, etc."""
783 self.write(' name: "' + self.module_name + '",')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700784 # see properties shared by dump_defaults_module
785 if self.defaults:
786 self.write(' defaults: ["' + self.defaults + '"],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800787 if self.stem != self.module_name:
788 self.write(' stem: "' + self.stem + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700789 if self.has_warning and not self.cap_lints and not self.default_srcs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700790 self.write(' // has rustc warnings')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800791 if self.host_supported and self.device_supported:
792 self.write(' host_supported: true,')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700793 if not self.defaults:
794 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800795 if len(self.srcs) > 1:
796 self.srcs = sorted(set(self.srcs))
797 self.dump_android_property_list('srcs', '"%s"', self.srcs)
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700798 elif not self.default_srcs:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800799 self.write(' srcs: ["' + self.main_src + '"],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700800 if 'test' in self.crate_types and not self.defaults:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800801 # self.root_pkg can have multiple test modules, with different *_tests[n]
802 # names, but their executables can all be installed under the same _tests
803 # directory. When built from Cargo.toml, all tests should have different
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700804 # file or crate names. So we used (root_pkg + '_tests') name as the
805 # relative_install_path.
806 # However, some package like 'slab' can have non-mergeable tests that
807 # must be separated by different module names. So, here we no longer
808 # emit relative_install_path.
809 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800810 self.write(' test_suites: ["general-tests"],')
811 self.write(' auto_gen_config: true,')
812
813 def dump_android_externs(self):
814 """Dump the dependent rlibs and dylibs property."""
815 so_libs = list()
816 rust_libs = ''
817 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$')
818 for lib in self.externs:
819 # normal value of lib: "libc = liblibc-*.rlib"
820 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib"
821 # we should use "libgetrandom", not "lib" + "getrandom_package"
822 groups = deps_libname.match(lib)
823 if groups is not None:
824 lib_name = groups.group(1)
825 else:
826 lib_name = re.sub(' .*$', '', lib)
827 if lib.endswith('.rlib') or lib.endswith('.rmeta'):
828 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta.
829 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n'
830 elif lib.endswith('.so'):
831 so_libs.append(lib_name)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700832 elif lib != 'proc_macro': # --extern proc_macro is special and ignored
833 rust_libs += ' // ERROR: unknown type of lib ' + lib + '\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800834 if rust_libs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700835 self.write(' rustlibs: [\n' + rust_libs + ' ],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800836 # Are all dependent .so files proc_macros?
837 # TODO(chh): Separate proc_macros and dylib.
838 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs)
839
840
841class ARObject(object):
842 """Information of an "ar" link command."""
843
844 def __init__(self, runner, outf_name):
845 # Remembered global runner and its members.
846 self.runner = runner
847 self.pkg = ''
848 self.outf_name = outf_name # path to Android.bp
849 # "ar" arguments
850 self.line_num = 1
851 self.line = ''
852 self.flags = '' # e.g. "crs"
853 self.lib = '' # e.g. "/.../out/lib*.a"
854 self.objs = list() # e.g. "/.../out/.../*.o"
855
856 def parse(self, pkg, line_num, args_line):
857 """Collect ar obj/lib file names."""
858 self.pkg = pkg
859 self.line_num = line_num
860 self.line = args_line
861 args = args_line.split()
862 num_args = len(args)
863 if num_args < 3:
864 print('ERROR: "ar" command has too few arguments', args_line)
865 else:
866 self.flags = unquote(args[0])
867 self.lib = unquote(args[1])
868 self.objs = sorted(set(map(unquote, args[2:])))
869 return self
870
871 def write(self, s):
872 self.outf.write(s + '\n')
873
874 def dump_debug_info(self):
875 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line)
876 self.write('// ar_object for %12s' % self.pkg)
877 self.write('// flags = %s' % self.flags)
878 self.write('// lib = %s' % short_out_name(self.pkg, self.lib))
879 for o in self.objs:
880 self.write('// obj = %s' % short_out_name(self.pkg, o))
881
882 def dump_android_lib(self):
883 """Write cc_library_static into Android.bp."""
884 self.write('\ncc_library_static {')
885 self.write(' name: "' + file_base_name(self.lib) + '",')
886 self.write(' host_supported: true,')
887 if self.flags != 'crs':
888 self.write(' // ar flags = %s' % self.flags)
889 if self.pkg not in self.runner.pkg_obj2cc:
890 self.write(' ERROR: cannot find source files.\n}')
891 return
892 self.write(' srcs: [')
893 obj2cc = self.runner.pkg_obj2cc[self.pkg]
894 # Note: wflags are ignored.
895 dflags = list()
896 fflags = list()
897 for obj in self.objs:
898 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",')
899 # TODO(chh): union of dflags and flags of all obj
900 # Now, just a temporary hack that uses the last obj's flags
901 dflags = obj2cc[obj].dflags
902 fflags = obj2cc[obj].fflags
903 self.write(' ],')
904 self.write(' cflags: [')
905 self.write(' "-O3",') # TODO(chh): is this default correct?
906 self.write(' "-Wno-error",')
907 for x in fflags:
908 self.write(' "-f' + x + '",')
909 for x in dflags:
910 self.write(' "-D' + x + '",')
911 self.write(' ],')
912 self.write('}')
913
914 def dump(self):
915 """Dump error/debug/module info to the output .bp file."""
916 self.runner.init_bp_file(self.outf_name)
917 with open(self.outf_name, 'a') as outf:
918 self.outf = outf
919 if self.runner.args.debug:
920 self.dump_debug_info()
921 self.dump_android_lib()
922
923
924class CCObject(object):
925 """Information of a "cc" compilation command."""
926
927 def __init__(self, runner, outf_name):
928 # Remembered global runner and its members.
929 self.runner = runner
930 self.pkg = ''
931 self.outf_name = outf_name # path to Android.bp
932 # "cc" arguments
933 self.line_num = 1
934 self.line = ''
935 self.src = ''
936 self.obj = ''
937 self.dflags = list() # -D flags
938 self.fflags = list() # -f flags
939 self.iflags = list() # -I flags
940 self.wflags = list() # -W flags
941 self.other_args = list()
942
943 def parse(self, pkg, line_num, args_line):
944 """Collect cc compilation flags and src/out file names."""
945 self.pkg = pkg
946 self.line_num = line_num
947 self.line = args_line
948 args = args_line.split()
949 i = 0
950 while i < len(args):
951 arg = args[i]
952 if arg == '"-c"':
953 i += 1
954 if args[i].startswith('"-o'):
955 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c"
956 self.obj = unquote(args[i])[2:]
957 i += 1
958 self.src = unquote(args[i])
959 else:
960 self.src = unquote(args[i])
961 elif arg == '"-o"':
962 i += 1
963 self.obj = unquote(args[i])
964 elif arg == '"-I"':
965 i += 1
966 self.iflags.append(unquote(args[i]))
967 elif arg.startswith('"-D'):
968 self.dflags.append(unquote(args[i])[2:])
969 elif arg.startswith('"-f'):
970 self.fflags.append(unquote(args[i])[2:])
971 elif arg.startswith('"-W'):
972 self.wflags.append(unquote(args[i])[2:])
973 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or
974 arg == '"-g3"'):
975 # ignore -O -m64 -g
976 self.other_args.append(unquote(args[i]))
977 i += 1
978 self.dflags = sorted(set(self.dflags))
979 self.fflags = sorted(set(self.fflags))
980 # self.wflags is not sorted because some are order sensitive
981 # and we ignore them anyway.
982 if self.pkg not in self.runner.pkg_obj2cc:
983 self.runner.pkg_obj2cc[self.pkg] = {}
984 self.runner.pkg_obj2cc[self.pkg][self.obj] = self
985 return self
986
987 def write(self, s):
988 self.outf.write(s + '\n')
989
990 def dump_debug_flags(self, name, flags):
991 self.write('// ' + name + ':')
992 for f in flags:
993 self.write('// %s' % f)
994
995 def dump(self):
996 """Dump only error/debug info to the output .bp file."""
997 if not self.runner.args.debug:
998 return
999 self.runner.init_bp_file(self.outf_name)
1000 with open(self.outf_name, 'a') as outf:
1001 self.outf = outf
1002 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line)
1003 self.write('// cc_object for %12s' % self.pkg)
1004 self.write('// src = %s' % short_out_name(self.pkg, self.src))
1005 self.write('// obj = %s' % short_out_name(self.pkg, self.obj))
1006 self.dump_debug_flags('-I flags', self.iflags)
1007 self.dump_debug_flags('-D flags', self.dflags)
1008 self.dump_debug_flags('-f flags', self.fflags)
1009 self.dump_debug_flags('-W flags', self.wflags)
1010 if self.other_args:
1011 self.dump_debug_flags('other args', self.other_args)
1012
1013
1014class Runner(object):
1015 """Main class to parse cargo -v output and print Android module definitions."""
1016
1017 def __init__(self, args):
1018 self.bp_files = set() # Remember all output Android.bp files.
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -07001019 self.test_mappings = {} # Map from Android.bp file path to TestMapping.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001020 self.root_pkg = '' # name of package in ./Cargo.toml
1021 # Saved flags, modes, and data.
1022 self.args = args
1023 self.dry_run = not args.run
1024 self.skip_cargo = args.skipcargo
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001025 self.cargo_path = './cargo' # path to cargo, will be set later
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001026 # All cc/ar objects, crates, dependencies, and warning files
1027 self.cc_objects = list()
1028 self.pkg_obj2cc = {}
1029 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i]
1030 self.ar_objects = list()
1031 self.crates = list()
1032 self.dependencies = list() # dependent and build script crates
1033 self.warning_files = set()
1034 # Keep a unique mapping from (module name) to crate
1035 self.name_owners = {}
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001036 # Save and dump all errors from cargo to Android.bp.
1037 self.errors = ''
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001038 self.setup_cargo_path()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001039 # Default action is cargo clean, followed by build or user given actions.
1040 if args.cargo:
1041 self.cargo = ['clean'] + args.cargo
1042 else:
1043 self.cargo = ['clean', 'build']
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001044 if args.no_host: # do not run "cargo build" for host
1045 self.cargo = ['clean']
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001046 default_target = '--target x86_64-unknown-linux-gnu'
1047 if args.device:
1048 self.cargo.append('build ' + default_target)
1049 if args.tests:
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001050 if not args.no_host:
1051 self.cargo.append('build --tests')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001052 self.cargo.append('build --tests ' + default_target)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001053 elif args.tests and not args.no_host:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001054 self.cargo.append('build --tests')
1055
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001056 def setup_cargo_path(self):
1057 """Find cargo in the --cargo_bin or prebuilt rust bin directory."""
1058 if self.args.cargo_bin:
1059 self.cargo_path = os.path.join(self.args.cargo_bin, 'cargo')
1060 if not os.path.isfile(self.cargo_path):
1061 sys.exit('ERROR: cannot find cargo in ' + self.args.cargo_bin)
1062 print('WARNING: using cargo in ' + self.args.cargo_bin)
1063 return
1064 # We have only tested this on Linux.
1065 if platform.system() != 'Linux':
1066 sys.exit('ERROR: this script has only been tested on Linux with cargo.')
1067 # Assuming that this script is in development/scripts.
1068 my_dir = os.path.dirname(os.path.abspath(__file__))
1069 linux_dir = os.path.join(my_dir, '..', '..',
1070 'prebuilts', 'rust', 'linux-x86')
1071 if not os.path.isdir(linux_dir):
1072 sys.exit('ERROR: cannot find directory ' + linux_dir)
1073 rust_version = self.find_rust_version(my_dir, linux_dir)
1074 cargo_bin = os.path.join(linux_dir, rust_version, 'bin')
1075 self.cargo_path = os.path.join(cargo_bin, 'cargo')
1076 if not os.path.isfile(self.cargo_path):
1077 sys.exit('ERROR: cannot find cargo in ' + cargo_bin
1078 + '; please try --cargo_bin= flag.')
1079 return
1080
1081 def find_rust_version(self, my_dir, linux_dir):
1082 """Use my script directory, find prebuilt rust version."""
1083 # First look up build/soong/rust/config/global.go.
1084 path2global = os.path.join(my_dir, '..', '..',
1085 'build', 'soong', 'rust', 'config', 'global.go')
1086 if os.path.isfile(path2global):
1087 # try to find: RustDefaultVersion = "1.44.0"
1088 version_pat = re.compile(
1089 r'\s*RustDefaultVersion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+)".*$')
1090 with open(path2global, 'r') as inf:
1091 for line in inf:
1092 result = version_pat.match(line)
1093 if result:
1094 return result.group(1)
1095 print('WARNING: cannot find RustDefaultVersion in ' + path2global)
1096 # Otherwise, find the newest (largest) version number in linux_dir.
1097 rust_version = (0, 0, 0) # the prebuilt version to use
1098 version_pat = re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)$')
1099 for dir_name in os.listdir(linux_dir):
1100 result = version_pat.match(dir_name)
1101 if not result:
1102 continue
1103 version = (result.group(1), result.group(2), result.group(3))
1104 if version > rust_version:
1105 rust_version = version
1106 return '.'.join(rust_version)
1107
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001108 def init_bp_file(self, name):
1109 if name not in self.bp_files:
1110 self.bp_files.add(name)
1111 with open(name, 'w') as outf:
Andrew Walbran80e90be2020-06-09 14:33:18 +01001112 outf.write(ANDROID_BP_HEADER.format(args=' '.join(sys.argv[1:])))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001113
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -07001114 def dump_test_mapping_files(self):
1115 if self.dry_run:
1116 print('Dry-run skip dump of TEST_MAPPING')
1117 else:
1118 for bp_file_name in self.test_mappings:
1119 name = os.path.join(os.path.dirname(bp_file_name), 'TEST_MAPPING')
1120 self.test_mappings[bp_file_name].dump(name)
1121 return self
1122
1123 def add_test(self, bp_file_name, test_name, host):
1124 if bp_file_name not in self.test_mappings:
1125 self.test_mappings[bp_file_name] = TestMapping()
1126 mapping = self.test_mappings[bp_file_name]
1127 mapping.add_test(test_name, host)
1128
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001129 def try_claim_module_name(self, name, owner):
1130 """Reserve and return True if it has not been reserved yet."""
1131 if name not in self.name_owners or owner == self.name_owners[name]:
1132 self.name_owners[name] = owner
1133 return True
1134 return False
1135
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001136 def claim_module_name(self, prefix, owner, counter):
1137 """Return prefix if not owned yet, otherwise, prefix+str(counter)."""
1138 while True:
1139 name = prefix
1140 if counter > 0:
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001141 name += '_' + str(counter)
1142 if self.try_claim_module_name(name, owner):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001143 return name
1144 counter += 1
1145
1146 def find_root_pkg(self):
1147 """Read name of [package] in ./Cargo.toml."""
1148 if not os.path.exists('./Cargo.toml'):
1149 return
1150 with open('./Cargo.toml', 'r') as inf:
1151 pkg_section = re.compile(r'^ *\[package\]')
1152 name = re.compile('^ *name *= * "([^"]*)"')
1153 in_pkg = False
1154 for line in inf:
1155 if in_pkg:
1156 if name.match(line):
1157 self.root_pkg = name.match(line).group(1)
1158 break
1159 else:
1160 in_pkg = pkg_section.match(line) is not None
1161
1162 def run_cargo(self):
1163 """Calls cargo -v and save its output to ./cargo.out."""
1164 if self.skip_cargo:
1165 return self
1166 cargo = './Cargo.toml'
1167 if not os.access(cargo, os.R_OK):
1168 print('ERROR: Cannot find or read', cargo)
1169 return self
1170 if not self.dry_run and os.path.exists('cargo.out'):
1171 os.remove('cargo.out')
1172 cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> cargo.out 2>&1'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001173 # set up search PATH for cargo to find the correct rustc
1174 saved_path = os.environ['PATH']
1175 os.environ['PATH'] = os.path.dirname(self.cargo_path) + ':' + saved_path
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001176 for c in self.cargo:
1177 features = ''
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001178 if c != 'clean':
1179 if self.args.features is not None:
1180 features = ' --no-default-features'
1181 if self.args.features:
1182 features += ' --features ' + self.args.features
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001183 cmd_v_flag = ' -vv ' if self.args.vv else ' -v '
1184 cmd = self.cargo_path + cmd_v_flag
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001185 cmd += c + features + cmd_tail
1186 if self.args.rustflags and c != 'clean':
1187 cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
1188 if self.dry_run:
1189 print('Dry-run skip:', cmd)
1190 else:
1191 if self.args.verbose:
1192 print('Running:', cmd)
1193 with open('cargo.out', 'a') as cargo_out:
1194 cargo_out.write('### Running: ' + cmd + '\n')
1195 os.system(cmd)
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001196 os.environ['PATH'] = saved_path
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001197 return self
1198
1199 def dump_dependencies(self):
1200 """Append dependencies and their features to Android.bp."""
1201 if not self.dependencies:
1202 return
1203 dependent_list = list()
1204 for c in self.dependencies:
1205 dependent_list.append(c.feature_list())
1206 sorted_dependencies = sorted(set(dependent_list))
1207 self.init_bp_file('Android.bp')
1208 with open('Android.bp', 'a') as outf:
1209 outf.write('\n// dependent_library ["feature_list"]\n')
1210 for s in sorted_dependencies:
1211 outf.write('// ' + s + '\n')
1212
1213 def dump_pkg_obj2cc(self):
1214 """Dump debug info of the pkg_obj2cc map."""
1215 if not self.args.debug:
1216 return
1217 self.init_bp_file('Android.bp')
1218 with open('Android.bp', 'a') as outf:
1219 sorted_pkgs = sorted(self.pkg_obj2cc.keys())
1220 for pkg in sorted_pkgs:
1221 if not self.pkg_obj2cc[pkg]:
1222 continue
1223 outf.write('\n// obj => src for %s\n' % pkg)
1224 obj2cc = self.pkg_obj2cc[pkg]
1225 for obj in sorted(obj2cc.keys()):
1226 outf.write('// ' + short_out_name(pkg, obj) + ' => ' +
1227 short_out_name(pkg, obj2cc[obj].src) + '\n')
1228
1229 def gen_bp(self):
1230 """Parse cargo.out and generate Android.bp files."""
1231 if self.dry_run:
1232 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp')
1233 elif os.path.exists(CARGO_OUT):
1234 self.find_root_pkg()
1235 with open(CARGO_OUT, 'r') as cargo_out:
1236 self.parse(cargo_out, 'Android.bp')
1237 self.crates.sort(key=get_module_name)
1238 for obj in self.cc_objects:
1239 obj.dump()
1240 self.dump_pkg_obj2cc()
1241 for crate in self.crates:
1242 crate.dump()
1243 dumped_libs = set()
1244 for lib in self.ar_objects:
1245 if lib.pkg == self.root_pkg:
1246 lib_name = file_base_name(lib.lib)
1247 if lib_name not in dumped_libs:
1248 dumped_libs.add(lib_name)
1249 lib.dump()
1250 if self.args.dependencies and self.dependencies:
1251 self.dump_dependencies()
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001252 if self.errors:
1253 self.append_to_bp('\nErrors in ' + CARGO_OUT + ':\n' + self.errors)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001254 return self
1255
1256 def add_ar_object(self, obj):
1257 self.ar_objects.append(obj)
1258
1259 def add_cc_object(self, obj):
1260 self.cc_objects.append(obj)
1261
1262 def add_crate(self, crate):
1263 """Merge crate with someone in crates, or append to it. Return crates."""
1264 if crate.skip_crate():
1265 if self.args.debug: # include debug info of all crates
1266 self.crates.append(crate)
1267 if self.args.dependencies: # include only dependent crates
1268 if (is_dependent_file_path(crate.main_src) and
1269 not is_build_crate_name(crate.crate_name)):
1270 self.dependencies.append(crate)
1271 else:
1272 for c in self.crates:
1273 if c.merge(crate, 'Android.bp'):
1274 return
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001275 # If not merged, decide module type and name now.
1276 crate.decide_module_type()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001277 self.crates.append(crate)
1278
1279 def find_warning_owners(self):
1280 """For each warning file, find its owner crate."""
1281 missing_owner = False
1282 for f in self.warning_files:
1283 cargo_dir = '' # find lowest crate, with longest path
1284 owner = None # owner crate of this warning
1285 for c in self.crates:
1286 if (f.startswith(c.cargo_dir + '/') and
1287 len(cargo_dir) < len(c.cargo_dir)):
1288 cargo_dir = c.cargo_dir
1289 owner = c
1290 if owner:
1291 owner.has_warning = True
1292 else:
1293 missing_owner = True
1294 if missing_owner and os.path.exists('Cargo.toml'):
1295 # owner is the root cargo, with empty cargo_dir
1296 for c in self.crates:
1297 if not c.cargo_dir:
1298 c.has_warning = True
1299
1300 def rustc_command(self, n, rustc_line, line, outf_name):
1301 """Process a rustc command line from cargo -vv output."""
1302 # cargo build -vv output can have multiple lines for a rustc command
1303 # due to '\n' in strings for environment variables.
1304 # strip removes leading spaces and '\n' at the end
1305 new_rustc = (rustc_line.strip() + line) if rustc_line else line
1306 # Use an heuristic to detect the completions of a multi-line command.
1307 # This might fail for some very rare case, but easy to fix manually.
1308 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0:
1309 return new_rustc
1310 if RUSTC_VV_CMD_ARGS.match(new_rustc):
1311 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1)
1312 self.add_crate(Crate(self, outf_name).parse(n, args))
1313 else:
1314 self.assert_empty_vv_line(new_rustc)
1315 return ''
1316
1317 def cc_ar_command(self, n, groups, outf_name):
1318 pkg = groups.group(1)
1319 line = groups.group(3)
1320 if groups.group(2) == 'cc':
1321 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line))
1322 else:
1323 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line))
1324
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001325 def append_to_bp(self, line):
1326 self.init_bp_file('Android.bp')
1327 with open('Android.bp', 'a') as outf:
1328 outf.write(line)
1329
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001330 def assert_empty_vv_line(self, line):
1331 if line: # report error if line is not empty
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001332 self.append_to_bp('ERROR -vv line: ' + line)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001333 return ''
1334
1335 def parse(self, inf, outf_name):
1336 """Parse rustc and warning messages in inf, return a list of Crates."""
1337 n = 0 # line number
1338 prev_warning = False # true if the previous line was warning: ...
1339 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
1340 for line in inf:
1341 n += 1
1342 if line.startswith('warning: '):
1343 prev_warning = True
1344 rustc_line = self.assert_empty_vv_line(rustc_line)
1345 continue
1346 new_rustc = ''
1347 if RUSTC_PAT.match(line):
1348 args_line = RUSTC_PAT.match(line).group(1)
1349 self.add_crate(Crate(self, outf_name).parse(n, args_line))
1350 self.assert_empty_vv_line(rustc_line)
1351 elif rustc_line or RUSTC_VV_PAT.match(line):
1352 new_rustc = self.rustc_command(n, rustc_line, line, outf_name)
1353 elif CC_AR_VV_PAT.match(line):
1354 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name)
1355 elif prev_warning and WARNING_FILE_PAT.match(line):
1356 self.assert_empty_vv_line(rustc_line)
1357 fpath = WARNING_FILE_PAT.match(line).group(1)
1358 if fpath[0] != '/': # ignore absolute path
1359 self.warning_files.add(fpath)
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001360 elif line.startswith('error: ') or line.startswith('error[E'):
1361 self.errors += line
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001362 prev_warning = False
1363 rustc_line = new_rustc
1364 self.find_warning_owners()
1365
1366
1367def parse_args():
1368 """Parse main arguments."""
1369 parser = argparse.ArgumentParser('cargo2android')
1370 parser.add_argument(
1371 '--cargo',
1372 action='append',
1373 metavar='args_string',
1374 help=('extra cargo build -v args in a string, ' +
1375 'each --cargo flag calls cargo build -v once'))
1376 parser.add_argument(
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001377 '--cargo_bin',
1378 type=str,
1379 help='use cargo in the cargo_bin directory instead of the prebuilt one')
1380 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001381 '--debug',
1382 action='store_true',
1383 default=False,
1384 help='dump debug info into Android.bp')
1385 parser.add_argument(
1386 '--dependencies',
1387 action='store_true',
1388 default=False,
1389 help='dump debug info of dependent crates')
1390 parser.add_argument(
1391 '--device',
1392 action='store_true',
1393 default=False,
1394 help='run cargo also for a default device target')
1395 parser.add_argument(
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001396 '--no-host',
1397 action='store_true',
1398 default=False,
1399 help='do not run cargo for the host; only for the device target')
1400 parser.add_argument(
1401 '--host-first-multilib',
1402 action='store_true',
1403 default=False,
1404 help=('add a compile_multilib:"first" property ' +
1405 'to Android.bp host modules.'))
1406 parser.add_argument(
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001407 '--features',
1408 type=str,
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001409 help=('pass features to cargo build, ' +
1410 'empty string means no default features'))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001411 parser.add_argument(
1412 '--onefile',
1413 action='store_true',
1414 default=False,
1415 help=('output all into one ./Android.bp, default will generate ' +
1416 'one Android.bp per Cargo.toml in subdirectories'))
1417 parser.add_argument(
1418 '--run',
1419 action='store_true',
1420 default=False,
1421 help='run it, default is dry-run')
1422 parser.add_argument('--rustflags', type=str, help='passing flags to rustc')
1423 parser.add_argument(
1424 '--skipcargo',
1425 action='store_true',
1426 default=False,
1427 help='skip cargo command, parse cargo.out, and generate Android.bp')
1428 parser.add_argument(
1429 '--tests',
1430 action='store_true',
1431 default=False,
1432 help='run cargo build --tests after normal build')
1433 parser.add_argument(
1434 '--verbose',
1435 action='store_true',
1436 default=False,
1437 help='echo executed commands')
1438 parser.add_argument(
1439 '--vv',
1440 action='store_true',
1441 default=False,
1442 help='run cargo with -vv instead of default -v')
1443 return parser.parse_args()
1444
1445
1446def main():
1447 args = parse_args()
1448 if not args.run: # default is dry-run
1449 print(DRY_RUN_NOTE)
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -07001450 Runner(args).run_cargo().gen_bp().dump_test_mapping_files()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001451
1452
1453if __name__ == '__main__':
1454 main()