blob: 780a09b19e460a795ebd6218954fe3a2e6a6299f [file] [log] [blame]
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001#!/usr/bin/env python3
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08002#
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
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -070030 Note that cargo build is only called once with the default target
31 x86_64-unknown-linux-gnu.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080032
33(3) To build default and test crates, for host and device, use both
34 --device and --tests flags:
35 cargo2android.py --run --device --tests
36
37 This is equivalent to using the --cargo flag to add extra builds:
38 cargo2android.py --run
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080039 --cargo "build --target x86_64-unknown-linux-gnu"
40 --cargo "build --tests --target x86_64-unknown-linux-gnu"
41
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -070042If there are rustc warning messages, this script will add
43a warning comment to the owner crate module in Android.bp.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080044"""
45
46from __future__ import print_function
47
48import argparse
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -070049import glob
Joel Galenson0fbdafe2021-04-21 16:33:33 -070050import json
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080051import os
52import os.path
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -070053import platform
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080054import re
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -070055import shutil
Joel Galenson7e8247e2021-05-20 18:51:42 -070056import subprocess
Andrew Walbran80e90be2020-06-09 14:33:18 +010057import sys
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080058
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -070059# Some Rust packages include extra unwanted crates.
60# This set contains all such excluded crate names.
61EXCLUDED_CRATES = set(['protobuf_bin_gen_rust_do_not_use'])
62
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080063RENAME_MAP = {
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -070064 # This map includes all changes to the default rust module names
65 # to resolve name conflicts, avoid confusion, or work as plugin.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080066 'libbacktrace': 'libbacktrace_rust',
Andrew Walbrane51f1042020-08-11 16:42:48 +010067 'libbase': 'libbase_rust',
Luke Huanga1371af2021-06-29 18:04:40 +080068 'libbase64': 'libbase64_rust',
Victor Hsieh21bea792020-12-04 10:59:16 -080069 'libfuse': 'libfuse_rust',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080070 'libgcc': 'libgcc_rust',
71 'liblog': 'liblog_rust',
Chih-Hung Hsieh07119862020-07-24 15:34:06 -070072 'libminijail': 'libminijail_rust',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080073 '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.
Joel Galenson56446742021-02-18 08:27:48 -080091ANDROID_BP_HEADER = (
92 '// This file is generated by cargo2android.py {args}.\n' +
93 '// Do not modify this file as changes will be overridden on upgrade.\n')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080094
95CARGO_OUT = 'cargo.out' # Name of file to keep cargo build -v output.
96
Joel Galenson3f42f802021-04-07 12:42:17 -070097# This should be kept in sync with tools/external_updater/crates_updater.py.
98ERRORS_LINE = 'Errors in ' + CARGO_OUT + ':'
99
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800100TARGET_TMP = 'target.tmp' # Name of temporary output directory.
101
102# Message to be displayed when this script is called without the --run flag.
103DRY_RUN_NOTE = (
104 'Dry-run: This script uses ./' + TARGET_TMP + ' for output directory,\n' +
105 'runs cargo clean, runs cargo build -v, saves output to ./cargo.out,\n' +
106 'and writes to Android.bp in the current and subdirectories.\n\n' +
107 'To do do all of the above, use the --run flag.\n' +
108 'See --help for other flags, and more usage notes in this script.\n')
109
110# Cargo -v output of a call to rustc.
111RUSTC_PAT = re.compile('^ +Running `rustc (.*)`$')
112
113# Cargo -vv output of a call to rustc could be split into multiple lines.
114# Assume that the first line will contain some CARGO_* env definition.
115RUSTC_VV_PAT = re.compile('^ +Running `.*CARGO_.*=.*$')
116# The combined -vv output rustc command line pattern.
117RUSTC_VV_CMD_ARGS = re.compile('^ *Running `.*CARGO_.*=.* rustc (.*)`$')
118
119# Cargo -vv output of a "cc" or "ar" command; all in one line.
120CC_AR_VV_PAT = re.compile(r'^\[([^ ]*)[^\]]*\] running:? "(cc|ar)" (.*)$')
121# Some package, such as ring-0.13.5, has pattern '... running "cc"'.
122
123# Rustc output of file location path pattern for a warning message.
124WARNING_FILE_PAT = re.compile('^ *--> ([^:]*):[0-9]+')
125
126# Rust package name with suffix -d1.d2.d3.
127VERSION_SUFFIX_PAT = re.compile(r'^(.*)-[0-9]+\.[0-9]+\.[0-9]+$')
128
129
130def altered_name(name):
131 return RENAME_MAP[name] if (name in RENAME_MAP) else name
132
133
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700134def altered_stem(name):
135 return RENAME_STEM_MAP[name] if (name in RENAME_STEM_MAP) else name
136
137
138def altered_defaults(name):
139 return RENAME_DEFAULTS_MAP[name] if (name in RENAME_DEFAULTS_MAP) else name
140
141
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800142def is_build_crate_name(name):
143 # We added special prefix to build script crate names.
144 return name.startswith('build_script_')
145
146
147def is_dependent_file_path(path):
148 # Absolute or dependent '.../' paths are not main files of this crate.
149 return path.startswith('/') or path.startswith('.../')
150
151
152def get_module_name(crate): # to sort crates in a list
153 return crate.module_name
154
155
156def pkg2crate_name(s):
157 return s.replace('-', '_').replace('.', '_')
158
159
160def file_base_name(path):
161 return os.path.splitext(os.path.basename(path))[0]
162
163
164def test_base_name(path):
165 return pkg2crate_name(file_base_name(path))
166
167
168def unquote(s): # remove quotes around str
169 if s and len(s) > 1 and s[0] == '"' and s[-1] == '"':
170 return s[1:-1]
171 return s
172
173
174def remove_version_suffix(s): # remove -d1.d2.d3 suffix
175 if VERSION_SUFFIX_PAT.match(s):
176 return VERSION_SUFFIX_PAT.match(s).group(1)
177 return s
178
179
180def short_out_name(pkg, s): # replace /.../pkg-*/out/* with .../out/*
181 return re.sub('^/.*/' + pkg + '-[0-9a-f]*/out/', '.../out/', s)
182
183
184def escape_quotes(s): # replace '"' with '\\"'
185 return s.replace('"', '\\"')
186
187
188class Crate(object):
189 """Information of a Rust crate to collect/emit for an Android.bp module."""
190
191 def __init__(self, runner, outf_name):
192 # Remembered global runner and its members.
193 self.runner = runner
194 self.debug = runner.args.debug
195 self.cargo_dir = '' # directory of my Cargo.toml
196 self.outf_name = outf_name # path to Android.bp
197 self.outf = None # open file handle of outf_name during dump*
198 # Variants/results that could be merged from multiple rustc lines.
199 self.host_supported = False
200 self.device_supported = False
201 self.has_warning = False
202 # Android module properties derived from rustc parameters.
203 self.module_name = '' # unique in Android build system
204 self.module_type = '' # rust_{binary,library,test}[_host] etc.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700205 self.defaults = '' # rust_defaults used by rust_test* modules
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700206 self.default_srcs = False # use 'srcs' defined in self.defaults
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800207 self.root_pkg = '' # parent package name of a sub/test packge, from -L
208 self.srcs = list() # main_src or merged multiple source files
209 self.stem = '' # real base name of output file
210 # Kept parsed status
211 self.errors = '' # all errors found during parsing
212 self.line_num = 1 # runner told input source line number
213 self.line = '' # original rustc command line parameters
214 # Parameters collected from rustc command line.
215 self.crate_name = '' # follows --crate-name
216 self.main_src = '' # follows crate_name parameter, shortened
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700217 self.crate_types = list() # follows --crate-type
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800218 self.cfgs = list() # follows --cfg, without feature= prefix
219 self.features = list() # follows --cfg, name in 'feature="..."'
220 self.codegens = list() # follows -C, some ignored
221 self.externs = list() # follows --extern
222 self.core_externs = list() # first part of self.externs elements
223 self.static_libs = list() # e.g. -l static=host_cpuid
224 self.shared_libs = list() # e.g. -l dylib=wayland-client, -l z
225 self.cap_lints = '' # follows --cap-lints
226 self.emit_list = '' # e.g., --emit=dep-info,metadata,link
227 self.edition = '2015' # rustc default, e.g., --edition=2018
228 self.target = '' # follows --target
229
230 def write(self, s):
231 # convenient way to output one line at a time with EOL.
232 self.outf.write(s + '\n')
233
234 def same_flags(self, other):
235 # host_supported, device_supported, has_warning are not compared but merged
236 # target is not compared, to merge different target/host modules
237 # externs is not compared; only core_externs is compared
238 return (not self.errors and not other.errors and
239 self.edition == other.edition and
240 self.cap_lints == other.cap_lints and
241 self.emit_list == other.emit_list and
242 self.core_externs == other.core_externs and
243 self.codegens == other.codegens and
244 self.features == other.features and
245 self.static_libs == other.static_libs and
246 self.shared_libs == other.shared_libs and self.cfgs == other.cfgs)
247
248 def merge_host_device(self, other):
249 """Returns true if attributes are the same except host/device support."""
250 return (self.crate_name == other.crate_name and
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700251 self.crate_types == other.crate_types and
252 self.main_src == other.main_src and
253 # before merge, each test module has an unique module name and stem
254 (self.stem == other.stem or self.crate_types == ['test']) and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800255 self.root_pkg == other.root_pkg and not self.skip_crate() and
256 self.same_flags(other))
257
258 def merge_test(self, other):
259 """Returns true if self and other are tests of same root_pkg."""
260 # Before merger, each test has its own crate_name.
261 # A merged test uses its source file base name as output file name,
262 # so a test is mergeable only if its base name equals to its crate name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700263 return (self.crate_types == other.crate_types and
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700264 self.crate_types == ['test'] and self.root_pkg == other.root_pkg and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800265 not self.skip_crate() and
266 other.crate_name == test_base_name(other.main_src) and
267 (len(self.srcs) > 1 or
268 (self.crate_name == test_base_name(self.main_src)) and
269 self.host_supported == other.host_supported and
270 self.device_supported == other.device_supported) and
271 self.same_flags(other))
272
273 def merge(self, other, outf_name):
274 """Try to merge crate into self."""
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700275 # Cargo build --tests could recompile a library for tests.
276 # We need to merge such duplicated calls to rustc, with
277 # the algorithm in merge_host_device.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800278 should_merge_host_device = self.merge_host_device(other)
279 should_merge_test = False
280 if not should_merge_host_device:
281 should_merge_test = self.merge_test(other)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800282 if should_merge_host_device or should_merge_test:
283 self.runner.init_bp_file(outf_name)
284 with open(outf_name, 'a') as outf: # to write debug info
285 self.outf = outf
286 other.outf = outf
287 self.do_merge(other, should_merge_test)
288 return True
289 return False
290
291 def do_merge(self, other, should_merge_test):
292 """Merge attributes of other to self."""
293 if self.debug:
294 self.write('\n// Before merge definition (1):')
295 self.dump_debug_info()
296 self.write('\n// Before merge definition (2):')
297 other.dump_debug_info()
298 # Merge properties of other to self.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800299 self.has_warning = self.has_warning or other.has_warning
300 if not self.target: # okay to keep only the first target triple
301 self.target = other.target
302 # decide_module_type sets up default self.stem,
303 # which can be changed if self is a merged test module.
304 self.decide_module_type()
305 if should_merge_test:
306 self.srcs.append(other.main_src)
307 # use a short unique name as the merged module name.
308 prefix = self.root_pkg + '_tests'
309 self.module_name = self.runner.claim_module_name(prefix, self, 0)
310 self.stem = self.module_name
311 # This normalized root_pkg name although might be the same
312 # as other module's crate_name, it is not actually used for
313 # output file name. A merged test module always have multiple
314 # source files and each source file base name is used as
315 # its output file name.
316 self.crate_name = pkg2crate_name(self.root_pkg)
317 if self.debug:
318 self.write('\n// After merge definition (1):')
319 self.dump_debug_info()
320
321 def find_cargo_dir(self):
322 """Deepest directory with Cargo.toml and contains the main_src."""
323 if not is_dependent_file_path(self.main_src):
324 dir_name = os.path.dirname(self.main_src)
325 while dir_name:
326 if os.path.exists(dir_name + '/Cargo.toml'):
327 self.cargo_dir = dir_name
328 return
329 dir_name = os.path.dirname(dir_name)
330
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700331 def add_codegens_flag(self, flag):
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700332 """Ignore options not used in Android."""
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700333 # 'prefer-dynamic' does not work with common flag -C lto
Chih-Hung Hsieh63459ed2020-08-26 11:51:15 -0700334 # 'embed-bitcode' is ignored; we might control LTO with other .bp flag
Chih-Hung Hsieh6c13b722020-09-11 21:24:03 -0700335 # 'codegen-units' is set in Android global config or by default
336 if not (flag.startswith('codegen-units=') or
337 flag.startswith('debuginfo=') or
Chih-Hung Hsieh63459ed2020-08-26 11:51:15 -0700338 flag.startswith('embed-bitcode=') or
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700339 flag.startswith('extra-filename=') or
340 flag.startswith('incremental=') or
341 flag.startswith('metadata=') or
342 flag == 'prefer-dynamic'):
343 self.codegens.append(flag)
344
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800345 def parse(self, line_num, line):
346 """Find important rustc arguments to convert to Android.bp properties."""
347 self.line_num = line_num
348 self.line = line
349 args = line.split() # Loop through every argument of rustc.
350 i = 0
351 while i < len(args):
352 arg = args[i]
353 if arg == '--crate-name':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700354 i += 1
355 self.crate_name = args[i]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800356 elif arg == '--crate-type':
357 i += 1
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700358 # cargo calls rustc with multiple --crate-type flags.
359 # rustc can accept:
360 # --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
361 self.crate_types.append(args[i])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800362 elif arg == '--test':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700363 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800364 elif arg == '--target':
365 i += 1
366 self.target = args[i]
367 elif arg == '--cfg':
368 i += 1
369 if args[i].startswith('\'feature='):
370 self.features.append(unquote(args[i].replace('\'feature=', '')[:-1]))
371 else:
372 self.cfgs.append(args[i])
373 elif arg == '--extern':
374 i += 1
375 extern_names = re.sub('=/[^ ]*/deps/', ' = ', args[i])
376 self.externs.append(extern_names)
377 self.core_externs.append(re.sub(' = .*', '', extern_names))
378 elif arg == '-C': # codegen options
379 i += 1
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700380 self.add_codegens_flag(args[i])
381 elif arg.startswith('-C'):
382 # cargo has been passing "-C <xyz>" flag to rustc,
383 # but newer cargo could pass '-Cembed-bitcode=no' to rustc.
384 self.add_codegens_flag(arg[2:])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800385 elif arg == '--cap-lints':
386 i += 1
387 self.cap_lints = args[i]
388 elif arg == '-L':
389 i += 1
390 if args[i].startswith('dependency=') and args[i].endswith('/deps'):
391 if '/' + TARGET_TMP + '/' in args[i]:
392 self.root_pkg = re.sub(
393 '^.*/', '', re.sub('/' + TARGET_TMP + '/.*/deps$', '', args[i]))
394 else:
395 self.root_pkg = re.sub('^.*/', '',
396 re.sub('/[^/]+/[^/]+/deps$', '', args[i]))
397 self.root_pkg = remove_version_suffix(self.root_pkg)
398 elif arg == '-l':
399 i += 1
400 if args[i].startswith('static='):
401 self.static_libs.append(re.sub('static=', '', args[i]))
402 elif args[i].startswith('dylib='):
403 self.shared_libs.append(re.sub('dylib=', '', args[i]))
404 else:
405 self.shared_libs.append(args[i])
406 elif arg == '--out-dir' or arg == '--color': # ignored
407 i += 1
408 elif arg.startswith('--error-format=') or arg.startswith('--json='):
409 _ = arg # ignored
410 elif arg.startswith('--emit='):
411 self.emit_list = arg.replace('--emit=', '')
412 elif arg.startswith('--edition='):
413 self.edition = arg.replace('--edition=', '')
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700414 elif not arg.startswith('-'):
415 # shorten imported crate main source paths like $HOME/.cargo/
416 # registry/src/github.com-1ecc6299db9ec823/memchr-2.3.3/src/lib.rs
417 self.main_src = re.sub(r'^/[^ ]*/registry/src/', '.../', args[i])
418 self.main_src = re.sub(r'^\.\.\./github.com-[0-9a-f]*/', '.../',
419 self.main_src)
420 self.find_cargo_dir()
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700421 if self.cargo_dir: # for a subdirectory
422 if self.runner.args.no_subdir: # all .bp content to /dev/null
423 self.outf_name = '/dev/null'
424 elif not self.runner.args.onefile:
425 # Write to Android.bp in the subdirectory with Cargo.toml.
426 self.outf_name = self.cargo_dir + '/Android.bp'
427 self.main_src = self.main_src[len(self.cargo_dir) + 1:]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800428 else:
429 self.errors += 'ERROR: unknown ' + arg + '\n'
430 i += 1
431 if not self.crate_name:
432 self.errors += 'ERROR: missing --crate-name\n'
433 if not self.main_src:
434 self.errors += 'ERROR: missing main source file\n'
435 else:
436 self.srcs.append(self.main_src)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700437 if not self.crate_types:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800438 # Treat "--cfg test" as "--test"
439 if 'test' in self.cfgs:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700440 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800441 else:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700442 self.errors += 'ERROR: missing --crate-type or --test\n'
443 elif len(self.crate_types) > 1:
444 if 'test' in self.crate_types:
445 self.errors += 'ERROR: cannot handle both --crate-type and --test\n'
446 if 'lib' in self.crate_types and 'rlib' in self.crate_types:
447 self.errors += 'ERROR: cannot generate both lib and rlib crate types\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800448 if not self.root_pkg:
449 self.root_pkg = self.crate_name
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700450 self.device_supported = self.runner.args.device
451 self.host_supported = not self.runner.args.no_host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800452 self.cfgs = sorted(set(self.cfgs))
453 self.features = sorted(set(self.features))
454 self.codegens = sorted(set(self.codegens))
455 self.externs = sorted(set(self.externs))
456 self.core_externs = sorted(set(self.core_externs))
457 self.static_libs = sorted(set(self.static_libs))
458 self.shared_libs = sorted(set(self.shared_libs))
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700459 self.crate_types = sorted(set(self.crate_types))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800460 self.decide_module_type()
461 self.module_name = altered_name(self.stem)
462 return self
463
464 def dump_line(self):
465 self.write('\n// Line ' + str(self.line_num) + ' ' + self.line)
466
467 def feature_list(self):
468 """Return a string of main_src + "feature_list"."""
469 pkg = self.main_src
470 if pkg.startswith('.../'): # keep only the main package name
471 pkg = re.sub('/.*', '', pkg[4:])
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700472 elif pkg.startswith('/'): # use relative path for a local package
473 pkg = os.path.relpath(pkg)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800474 if not self.features:
475 return pkg
476 return pkg + ' "' + ','.join(self.features) + '"'
477
478 def dump_skip_crate(self, kind):
479 if self.debug:
480 self.write('\n// IGNORED: ' + kind + ' ' + self.main_src)
481 return self
482
483 def skip_crate(self):
484 """Return crate_name or a message if this crate should be skipped."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700485 if (is_build_crate_name(self.crate_name) or
486 self.crate_name in EXCLUDED_CRATES):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800487 return self.crate_name
488 if is_dependent_file_path(self.main_src):
489 return 'dependent crate'
490 return ''
491
492 def dump(self):
493 """Dump all error/debug/module code to the output .bp file."""
494 self.runner.init_bp_file(self.outf_name)
495 with open(self.outf_name, 'a') as outf:
496 self.outf = outf
497 if self.errors:
498 self.dump_line()
499 self.write(self.errors)
500 elif self.skip_crate():
501 self.dump_skip_crate(self.skip_crate())
502 else:
503 if self.debug:
504 self.dump_debug_info()
505 self.dump_android_module()
506
507 def dump_debug_info(self):
508 """Dump parsed data, when cargo2android is called with --debug."""
509
510 def dump(name, value):
511 self.write('//%12s = %s' % (name, value))
512
513 def opt_dump(name, value):
514 if value:
515 dump(name, value)
516
517 def dump_list(fmt, values):
518 for v in values:
519 self.write(fmt % v)
520
521 self.dump_line()
522 dump('module_name', self.module_name)
523 dump('crate_name', self.crate_name)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700524 dump('crate_types', self.crate_types)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800525 dump('main_src', self.main_src)
526 dump('has_warning', self.has_warning)
527 dump('for_host', self.host_supported)
528 dump('for_device', self.device_supported)
529 dump('module_type', self.module_type)
530 opt_dump('target', self.target)
531 opt_dump('edition', self.edition)
532 opt_dump('emit_list', self.emit_list)
533 opt_dump('cap_lints', self.cap_lints)
534 dump_list('// cfg = %s', self.cfgs)
535 dump_list('// cfg = \'feature "%s"\'', self.features)
536 # TODO(chh): escape quotes in self.features, but not in other dump_list
537 dump_list('// codegen = %s', self.codegens)
538 dump_list('// externs = %s', self.externs)
539 dump_list('// -l static = %s', self.static_libs)
540 dump_list('// -l (dylib) = %s', self.shared_libs)
541
542 def dump_android_module(self):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700543 """Dump one or more Android module definition, depending on crate_types."""
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700544 if len(self.crate_types) == 1:
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700545 self.dump_single_type_android_module()
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700546 return
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700547 if 'test' in self.crate_types:
548 self.write('\nERROR: multiple crate types cannot include test type')
549 return
550 # Dump one Android module per crate_type.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700551 for crate_type in self.crate_types:
552 self.decide_one_module_type(crate_type)
553 self.dump_one_android_module(crate_type)
554
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700555 def build_default_name(self):
556 """Return a short and readable name for the rust_defaults module."""
Joel Galensond37d7e62021-07-13 09:03:01 -0700557 # Choices: (1) root_pkg + '_test'? + '_defaults',
558 # (2) root_pkg + '_test'? + '_defaults_' + crate_name
559 # (3) root_pkg + '_test'? + '_defaults_' + main_src_basename_path
560 # (4) root_pkg + '_test'? + '_defaults_' + a_positive_sequence_number
561 test = "_test" if self.crate_types == ['test'] else ""
562 name1 = altered_defaults(self.root_pkg) + test + '_defaults'
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700563 if self.runner.try_claim_module_name(name1, self):
564 return name1
565 name2 = name1 + '_' + self.crate_name
566 if self.runner.try_claim_module_name(name2, self):
567 return name2
568 name3 = name1 + '_' + self.main_src_basename_path()
569 if self.runner.try_claim_module_name(name3, self):
570 return name3
571 return self.runner.claim_module_name(name1, self, 0)
572
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700573 def dump_srcs_list(self):
574 """Dump the srcs list, for defaults or regular modules."""
575 if len(self.srcs) > 1:
576 srcs = sorted(set(self.srcs)) # make a copy and dedup
577 else:
578 srcs = [self.main_src]
579 copy_out = self.runner.copy_out_module_name()
580 if copy_out:
581 srcs.append(':' + copy_out)
582 self.dump_android_property_list('srcs', '"%s"', srcs)
583
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700584 def dump_defaults_module(self):
585 """Dump a rust_defaults module to be shared by other modules."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700586 name = self.build_default_name()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700587 self.defaults = name
588 self.write('\nrust_defaults {')
589 self.write(' name: "' + name + '",')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700590 if self.runner.args.global_defaults:
591 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700592 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700593 if len(self.srcs) == 1: # only one source file; share it in defaults
594 self.default_srcs = True
595 if self.has_warning and not self.cap_lints:
596 self.write(' // has rustc warnings')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700597 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700598 if 'test' in self.crate_types:
599 self.write(' test_suites: ["general-tests"],')
600 self.write(' auto_gen_config: true,')
601 self.dump_edition_flags_libs()
602 self.write('}')
603
604 def dump_single_type_android_module(self):
605 """Dump one simple Android module, which has only one crate_type."""
606 crate_type = self.crate_types[0]
607 if crate_type != 'test':
608 # do not change self.stem or self.module_name
609 self.dump_one_android_module(crate_type)
610 return
611 # Dump one test module per source file, and separate host and device tests.
612 # crate_type == 'test'
Joel Galensonf6b3c912021-06-03 16:00:54 -0700613 self.srcs = [src for src in self.srcs if not src in self.runner.args.test_blocklist]
614 if ((self.host_supported and self.device_supported and len(self.srcs) > 0) or
615 len(self.srcs) > 1):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700616 self.srcs = sorted(set(self.srcs))
617 self.dump_defaults_module()
618 saved_srcs = self.srcs
619 for src in saved_srcs:
620 self.srcs = [src]
621 saved_device_supported = self.device_supported
622 saved_host_supported = self.host_supported
623 saved_main_src = self.main_src
624 self.main_src = src
625 if saved_host_supported:
626 self.device_supported = False
627 self.host_supported = True
628 self.module_name = self.test_module_name()
629 self.decide_one_module_type(crate_type)
630 self.dump_one_android_module(crate_type)
631 if saved_device_supported:
632 self.device_supported = True
633 self.host_supported = False
634 self.module_name = self.test_module_name()
635 self.decide_one_module_type(crate_type)
636 self.dump_one_android_module(crate_type)
637 self.host_supported = saved_host_supported
638 self.device_supported = saved_device_supported
639 self.main_src = saved_main_src
640 self.srcs = saved_srcs
641
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700642 def dump_one_android_module(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800643 """Dump one Android module definition."""
644 if not self.module_type:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700645 self.write('\nERROR: unknown crate_type ' + crate_type)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800646 return
647 self.write('\n' + self.module_type + ' {')
648 self.dump_android_core_properties()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700649 if not self.defaults:
650 self.dump_edition_flags_libs()
651 if self.runner.args.host_first_multilib and self.host_supported and crate_type != 'test':
652 self.write(' compile_multilib: "first",')
Joel Galensond9c4de62021-04-23 10:26:40 -0700653 if self.runner.args.apex_available and crate_type == 'lib':
654 self.write(' apex_available: [')
655 for apex in self.runner.args.apex_available:
656 self.write(' "%s",' % apex)
657 self.write(' ],')
658 if self.runner.args.min_sdk_version and crate_type == 'lib':
659 self.write(' min_sdk_version: "%s",' % self.runner.args.min_sdk_version)
Joel Galenson5664f2a2021-06-10 10:13:49 -0700660 if self.runner.args.add_module_block:
661 with open(self.runner.args.add_module_block, 'r') as f:
662 self.write(' %s,' % f.read().replace('\n', '\n '))
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700663 self.write('}')
664
665 def dump_android_flags(self):
666 """Dump Android module flags property."""
ThiƩbaud Weksteena5a728b2021-04-08 14:23:49 +0200667 if not self.codegens and not self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700668 return
669 self.write(' flags: [')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800670 if self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700671 self.write(' "--cap-lints ' + self.cap_lints + '",')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700672 codegens_fmt = '"-C %s"'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700673 self.dump_android_property_list_items(codegens_fmt, self.codegens)
674 self.write(' ],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700675
676 def dump_edition_flags_libs(self):
677 if self.edition:
678 self.write(' edition: "' + self.edition + '",')
679 self.dump_android_property_list('features', '"%s"', self.features)
Joel Galenson3d6d1e72021-06-07 15:00:24 -0700680 cfgs = [cfg for cfg in self.cfgs if not cfg in self.runner.args.cfg_blocklist]
681 self.dump_android_property_list('cfgs', '"%s"', cfgs)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700682 self.dump_android_flags()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800683 if self.externs:
684 self.dump_android_externs()
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700685 static_libs = [lib for lib in self.static_libs if not lib in self.runner.args.lib_blocklist]
686 self.dump_android_property_list('static_libs', '"lib%s"', static_libs)
687 shared_libs = [lib for lib in self.shared_libs if not lib in self.runner.args.lib_blocklist]
688 self.dump_android_property_list('shared_libs', '"lib%s"', shared_libs)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800689
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700690 def main_src_basename_path(self):
691 return re.sub('/', '_', re.sub('.rs$', '', self.main_src))
692
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800693 def test_module_name(self):
694 """Return a unique name for a test module."""
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700695 # root_pkg+(_host|_device) + '_test_'+source_file_name
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700696 suffix = self.main_src_basename_path()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700697 host_device = '_host'
698 if self.device_supported:
699 host_device = '_device'
700 return self.root_pkg + host_device + '_test_' + suffix
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800701
702 def decide_module_type(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700703 # Use the first crate type for the default/first module.
704 crate_type = self.crate_types[0] if self.crate_types else ''
705 self.decide_one_module_type(crate_type)
706
707 def decide_one_module_type(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800708 """Decide which Android module type to use."""
709 host = '' if self.device_supported else '_host'
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700710 rlib = '_rlib' if self.runner.args.force_rlib else ''
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700711 if crate_type == 'bin': # rust_binary[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800712 self.module_type = 'rust_binary' + host
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700713 # In rare cases like protobuf-codegen, the output binary name must
714 # be renamed to use as a plugin for protoc.
715 self.stem = altered_stem(self.crate_name)
716 self.module_name = altered_name(self.crate_name)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700717 elif crate_type == 'lib': # rust_library[_host]
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700718 # TODO(chh): should this be rust_library[_host]?
719 # Assuming that Cargo.toml do not use both 'lib' and 'rlib',
720 # because we map them both to rlib.
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700721 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800722 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700723 self.module_name = altered_name(self.stem)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700724 elif crate_type == 'rlib': # rust_library[_host]
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700725 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700726 self.stem = 'lib' + self.crate_name
727 self.module_name = altered_name(self.stem)
728 elif crate_type == 'dylib': # rust_library[_host]_dylib
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800729 self.module_type = 'rust_library' + host + '_dylib'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700730 self.stem = 'lib' + self.crate_name
731 self.module_name = altered_name(self.stem) + '_dylib'
732 elif crate_type == 'cdylib': # rust_library[_host]_shared
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500733 self.module_type = 'rust_ffi' + host + '_shared'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700734 self.stem = 'lib' + self.crate_name
735 self.module_name = altered_name(self.stem) + '_shared'
736 elif crate_type == 'staticlib': # rust_library[_host]_static
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500737 self.module_type = 'rust_ffi' + host + '_static'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700738 self.stem = 'lib' + self.crate_name
739 self.module_name = altered_name(self.stem) + '_static'
740 elif crate_type == 'test': # rust_test[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800741 self.module_type = 'rust_test' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700742 # Before do_merge, stem name is based on the --crate-name parameter.
743 # and test module name is based on stem.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800744 self.stem = self.test_module_name()
745 # self.stem will be changed after merging with other tests.
746 # self.stem is NOT used for final test binary name.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700747 # rust_test uses each source file base name as part of output file name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700748 # In do_merge, this function is called again, with a module_name.
749 # We make sure that the module name is unique in each package.
750 if self.module_name:
751 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add
752 # different suffixes and distinguish multiple tests of the same
753 # crate name. We ignore -C and use claim_module_name to get
754 # unique sequential suffix.
755 self.module_name = self.runner.claim_module_name(
756 self.module_name, self, 0)
757 # Now the module name is unique, stem should also match and unique.
758 self.stem = self.module_name
759 elif crate_type == 'proc-macro': # rust_proc_macro
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800760 self.module_type = 'rust_proc_macro'
761 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700762 self.module_name = altered_name(self.stem)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800763 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]?
764 self.module_type = ''
765 self.stem = ''
766
767 def dump_android_property_list_items(self, fmt, values):
768 for v in values:
769 # fmt has quotes, so we need escape_quotes(v)
770 self.write(' ' + (fmt % escape_quotes(v)) + ',')
771
772 def dump_android_property_list(self, name, fmt, values):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700773 if not values:
774 return
775 if len(values) > 1:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800776 self.write(' ' + name + ': [')
777 self.dump_android_property_list_items(fmt, values)
778 self.write(' ],')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700779 else:
780 self.write(' ' + name + ': [' +
781 (fmt % escape_quotes(values[0])) + '],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800782
783 def dump_android_core_properties(self):
784 """Dump the module header, name, stem, etc."""
785 self.write(' name: "' + self.module_name + '",')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700786 # see properties shared by dump_defaults_module
787 if self.defaults:
788 self.write(' defaults: ["' + self.defaults + '"],')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700789 elif self.runner.args.global_defaults:
790 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800791 if self.stem != self.module_name:
792 self.write(' stem: "' + self.stem + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700793 if self.has_warning and not self.cap_lints and not self.default_srcs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700794 self.write(' // has rustc warnings')
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700795 if self.host_supported and self.device_supported and self.module_type != 'rust_proc_macro':
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800796 self.write(' host_supported: true,')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700797 if not self.defaults:
798 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700799 if not self.default_srcs:
800 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700801 if 'test' in self.crate_types and not self.defaults:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800802 # self.root_pkg can have multiple test modules, with different *_tests[n]
803 # names, but their executables can all be installed under the same _tests
804 # directory. When built from Cargo.toml, all tests should have different
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700805 # file or crate names. So we used (root_pkg + '_tests') name as the
806 # relative_install_path.
807 # However, some package like 'slab' can have non-mergeable tests that
808 # must be separated by different module names. So, here we no longer
809 # emit relative_install_path.
810 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800811 self.write(' test_suites: ["general-tests"],')
812 self.write(' auto_gen_config: true,')
Joel Galensone261a152021-01-12 11:31:53 -0800813 if 'test' in self.crate_types and self.host_supported:
814 self.write(' test_options: {')
815 self.write(' unit_test: true,')
816 self.write(' },')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800817
818 def dump_android_externs(self):
819 """Dump the dependent rlibs and dylibs property."""
820 so_libs = list()
821 rust_libs = ''
822 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$')
823 for lib in self.externs:
824 # normal value of lib: "libc = liblibc-*.rlib"
825 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib"
826 # we should use "libgetrandom", not "lib" + "getrandom_package"
827 groups = deps_libname.match(lib)
828 if groups is not None:
829 lib_name = groups.group(1)
830 else:
831 lib_name = re.sub(' .*$', '', lib)
Joel Galenson97e414a2021-05-27 09:42:32 -0700832 if lib_name in self.runner.args.dependency_blocklist:
833 continue
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800834 if lib.endswith('.rlib') or lib.endswith('.rmeta'):
835 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta.
836 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n'
837 elif lib.endswith('.so'):
838 so_libs.append(lib_name)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700839 elif lib != 'proc_macro': # --extern proc_macro is special and ignored
840 rust_libs += ' // ERROR: unknown type of lib ' + lib + '\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800841 if rust_libs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700842 self.write(' rustlibs: [\n' + rust_libs + ' ],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800843 # Are all dependent .so files proc_macros?
844 # TODO(chh): Separate proc_macros and dylib.
845 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs)
846
847
848class ARObject(object):
849 """Information of an "ar" link command."""
850
851 def __init__(self, runner, outf_name):
852 # Remembered global runner and its members.
853 self.runner = runner
854 self.pkg = ''
855 self.outf_name = outf_name # path to Android.bp
856 # "ar" arguments
857 self.line_num = 1
858 self.line = ''
859 self.flags = '' # e.g. "crs"
860 self.lib = '' # e.g. "/.../out/lib*.a"
861 self.objs = list() # e.g. "/.../out/.../*.o"
862
863 def parse(self, pkg, line_num, args_line):
864 """Collect ar obj/lib file names."""
865 self.pkg = pkg
866 self.line_num = line_num
867 self.line = args_line
868 args = args_line.split()
869 num_args = len(args)
870 if num_args < 3:
871 print('ERROR: "ar" command has too few arguments', args_line)
872 else:
873 self.flags = unquote(args[0])
874 self.lib = unquote(args[1])
875 self.objs = sorted(set(map(unquote, args[2:])))
876 return self
877
878 def write(self, s):
879 self.outf.write(s + '\n')
880
881 def dump_debug_info(self):
882 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line)
883 self.write('// ar_object for %12s' % self.pkg)
884 self.write('// flags = %s' % self.flags)
885 self.write('// lib = %s' % short_out_name(self.pkg, self.lib))
886 for o in self.objs:
887 self.write('// obj = %s' % short_out_name(self.pkg, o))
888
889 def dump_android_lib(self):
890 """Write cc_library_static into Android.bp."""
891 self.write('\ncc_library_static {')
892 self.write(' name: "' + file_base_name(self.lib) + '",')
893 self.write(' host_supported: true,')
894 if self.flags != 'crs':
895 self.write(' // ar flags = %s' % self.flags)
896 if self.pkg not in self.runner.pkg_obj2cc:
897 self.write(' ERROR: cannot find source files.\n}')
898 return
899 self.write(' srcs: [')
900 obj2cc = self.runner.pkg_obj2cc[self.pkg]
901 # Note: wflags are ignored.
902 dflags = list()
903 fflags = list()
904 for obj in self.objs:
905 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",')
906 # TODO(chh): union of dflags and flags of all obj
907 # Now, just a temporary hack that uses the last obj's flags
908 dflags = obj2cc[obj].dflags
909 fflags = obj2cc[obj].fflags
910 self.write(' ],')
911 self.write(' cflags: [')
912 self.write(' "-O3",') # TODO(chh): is this default correct?
913 self.write(' "-Wno-error",')
914 for x in fflags:
915 self.write(' "-f' + x + '",')
916 for x in dflags:
917 self.write(' "-D' + x + '",')
918 self.write(' ],')
919 self.write('}')
920
921 def dump(self):
922 """Dump error/debug/module info to the output .bp file."""
923 self.runner.init_bp_file(self.outf_name)
924 with open(self.outf_name, 'a') as outf:
925 self.outf = outf
926 if self.runner.args.debug:
927 self.dump_debug_info()
928 self.dump_android_lib()
929
930
931class CCObject(object):
932 """Information of a "cc" compilation command."""
933
934 def __init__(self, runner, outf_name):
935 # Remembered global runner and its members.
936 self.runner = runner
937 self.pkg = ''
938 self.outf_name = outf_name # path to Android.bp
939 # "cc" arguments
940 self.line_num = 1
941 self.line = ''
942 self.src = ''
943 self.obj = ''
944 self.dflags = list() # -D flags
945 self.fflags = list() # -f flags
946 self.iflags = list() # -I flags
947 self.wflags = list() # -W flags
948 self.other_args = list()
949
950 def parse(self, pkg, line_num, args_line):
951 """Collect cc compilation flags and src/out file names."""
952 self.pkg = pkg
953 self.line_num = line_num
954 self.line = args_line
955 args = args_line.split()
956 i = 0
957 while i < len(args):
958 arg = args[i]
959 if arg == '"-c"':
960 i += 1
961 if args[i].startswith('"-o'):
962 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c"
963 self.obj = unquote(args[i])[2:]
964 i += 1
965 self.src = unquote(args[i])
966 else:
967 self.src = unquote(args[i])
968 elif arg == '"-o"':
969 i += 1
970 self.obj = unquote(args[i])
971 elif arg == '"-I"':
972 i += 1
973 self.iflags.append(unquote(args[i]))
974 elif arg.startswith('"-D'):
975 self.dflags.append(unquote(args[i])[2:])
976 elif arg.startswith('"-f'):
977 self.fflags.append(unquote(args[i])[2:])
978 elif arg.startswith('"-W'):
979 self.wflags.append(unquote(args[i])[2:])
980 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or
981 arg == '"-g3"'):
982 # ignore -O -m64 -g
983 self.other_args.append(unquote(args[i]))
984 i += 1
985 self.dflags = sorted(set(self.dflags))
986 self.fflags = sorted(set(self.fflags))
987 # self.wflags is not sorted because some are order sensitive
988 # and we ignore them anyway.
989 if self.pkg not in self.runner.pkg_obj2cc:
990 self.runner.pkg_obj2cc[self.pkg] = {}
991 self.runner.pkg_obj2cc[self.pkg][self.obj] = self
992 return self
993
994 def write(self, s):
995 self.outf.write(s + '\n')
996
997 def dump_debug_flags(self, name, flags):
998 self.write('// ' + name + ':')
999 for f in flags:
1000 self.write('// %s' % f)
1001
1002 def dump(self):
1003 """Dump only error/debug info to the output .bp file."""
1004 if not self.runner.args.debug:
1005 return
1006 self.runner.init_bp_file(self.outf_name)
1007 with open(self.outf_name, 'a') as outf:
1008 self.outf = outf
1009 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line)
1010 self.write('// cc_object for %12s' % self.pkg)
1011 self.write('// src = %s' % short_out_name(self.pkg, self.src))
1012 self.write('// obj = %s' % short_out_name(self.pkg, self.obj))
1013 self.dump_debug_flags('-I flags', self.iflags)
1014 self.dump_debug_flags('-D flags', self.dflags)
1015 self.dump_debug_flags('-f flags', self.fflags)
1016 self.dump_debug_flags('-W flags', self.wflags)
1017 if self.other_args:
1018 self.dump_debug_flags('other args', self.other_args)
1019
1020
1021class Runner(object):
1022 """Main class to parse cargo -v output and print Android module definitions."""
1023
1024 def __init__(self, args):
1025 self.bp_files = set() # Remember all output Android.bp files.
1026 self.root_pkg = '' # name of package in ./Cargo.toml
1027 # Saved flags, modes, and data.
1028 self.args = args
1029 self.dry_run = not args.run
1030 self.skip_cargo = args.skipcargo
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001031 self.cargo_path = './cargo' # path to cargo, will be set later
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001032 self.checked_out_files = False # to check only once
1033 self.build_out_files = [] # output files generated by build.rs
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001034 # All cc/ar objects, crates, dependencies, and warning files
1035 self.cc_objects = list()
1036 self.pkg_obj2cc = {}
1037 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i]
1038 self.ar_objects = list()
1039 self.crates = list()
1040 self.dependencies = list() # dependent and build script crates
1041 self.warning_files = set()
1042 # Keep a unique mapping from (module name) to crate
1043 self.name_owners = {}
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001044 # Save and dump all errors from cargo to Android.bp.
1045 self.errors = ''
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001046 self.setup_cargo_path()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001047 # Default action is cargo clean, followed by build or user given actions.
1048 if args.cargo:
1049 self.cargo = ['clean'] + args.cargo
1050 else:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001051 default_target = '--target x86_64-unknown-linux-gnu'
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -07001052 # Use the same target for both host and default device builds.
1053 # Same target is used as default in host x86_64 Android compilation.
1054 # Note: b/169872957, prebuilt cargo failed to build vsock
1055 # on x86_64-unknown-linux-musl systems.
1056 self.cargo = ['clean', 'build ' + default_target]
1057 if args.tests:
1058 self.cargo.append('build --tests ' + default_target)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001059
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001060 def setup_cargo_path(self):
1061 """Find cargo in the --cargo_bin or prebuilt rust bin directory."""
1062 if self.args.cargo_bin:
1063 self.cargo_path = os.path.join(self.args.cargo_bin, 'cargo')
1064 if not os.path.isfile(self.cargo_path):
1065 sys.exit('ERROR: cannot find cargo in ' + self.args.cargo_bin)
1066 print('WARNING: using cargo in ' + self.args.cargo_bin)
1067 return
1068 # We have only tested this on Linux.
1069 if platform.system() != 'Linux':
1070 sys.exit('ERROR: this script has only been tested on Linux with cargo.')
1071 # Assuming that this script is in development/scripts.
1072 my_dir = os.path.dirname(os.path.abspath(__file__))
1073 linux_dir = os.path.join(my_dir, '..', '..',
1074 'prebuilts', 'rust', 'linux-x86')
1075 if not os.path.isdir(linux_dir):
1076 sys.exit('ERROR: cannot find directory ' + linux_dir)
1077 rust_version = self.find_rust_version(my_dir, linux_dir)
1078 cargo_bin = os.path.join(linux_dir, rust_version, 'bin')
1079 self.cargo_path = os.path.join(cargo_bin, 'cargo')
1080 if not os.path.isfile(self.cargo_path):
1081 sys.exit('ERROR: cannot find cargo in ' + cargo_bin
1082 + '; please try --cargo_bin= flag.')
1083 return
1084
1085 def find_rust_version(self, my_dir, linux_dir):
1086 """Use my script directory, find prebuilt rust version."""
1087 # First look up build/soong/rust/config/global.go.
1088 path2global = os.path.join(my_dir, '..', '..',
1089 'build', 'soong', 'rust', 'config', 'global.go')
1090 if os.path.isfile(path2global):
1091 # try to find: RustDefaultVersion = "1.44.0"
1092 version_pat = re.compile(
1093 r'\s*RustDefaultVersion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+)".*$')
1094 with open(path2global, 'r') as inf:
1095 for line in inf:
1096 result = version_pat.match(line)
1097 if result:
1098 return result.group(1)
1099 print('WARNING: cannot find RustDefaultVersion in ' + path2global)
1100 # Otherwise, find the newest (largest) version number in linux_dir.
1101 rust_version = (0, 0, 0) # the prebuilt version to use
1102 version_pat = re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)$')
1103 for dir_name in os.listdir(linux_dir):
1104 result = version_pat.match(dir_name)
1105 if not result:
1106 continue
1107 version = (result.group(1), result.group(2), result.group(3))
1108 if version > rust_version:
1109 rust_version = version
1110 return '.'.join(rust_version)
1111
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001112 def find_out_files(self):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001113 # list1 has build.rs output for normal crates
1114 list1 = glob.glob(TARGET_TMP + '/*/*/build/' + self.root_pkg + '-*/out/*')
1115 # list2 has build.rs output for proc-macro crates
1116 list2 = glob.glob(TARGET_TMP + '/*/build/' + self.root_pkg + '-*/out/*')
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001117 return list1 + list2
1118
1119 def copy_out_files(self):
1120 """Copy build.rs output files to ./out and set up build_out_files."""
1121 if self.checked_out_files:
1122 return
1123 self.checked_out_files = True
1124 cargo_out_files = self.find_out_files()
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001125 out_files = set()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001126 if cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001127 os.makedirs('out', exist_ok=True)
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001128 for path in cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001129 file_name = path.split('/')[-1]
1130 out_files.add(file_name)
1131 shutil.copy(path, 'out/' + file_name)
1132 self.build_out_files = sorted(out_files)
1133
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001134 def has_used_out_dir(self):
1135 """Returns true if env!("OUT_DIR") is found."""
1136 return 0 == os.system('grep -rl --exclude build.rs --include \\*.rs' +
1137 ' \'env!("OUT_DIR")\' * > /dev/null')
1138
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001139 def copy_out_module_name(self):
1140 if self.args.copy_out and self.build_out_files:
1141 return 'copy_' + self.root_pkg + '_build_out'
1142 else:
1143 return ''
1144
Haibo Huang0f72c952021-03-19 11:34:15 -07001145 def read_license(self, name):
1146 if not os.path.isfile(name):
1147 return ''
1148 license = ''
1149 with open(name, 'r') as intf:
1150 line = intf.readline()
1151 # Firstly skip ANDROID_BP_HEADER
1152 while line.startswith('//'):
1153 line = intf.readline()
Joel Galensond9d13b82021-04-05 11:27:55 -07001154 # Read all lines until we see a rust_* or genrule rule.
1155 while line != '' and not (line.startswith('rust_') or line.startswith('genrule {')):
Haibo Huang0f72c952021-03-19 11:34:15 -07001156 license += line
1157 line = intf.readline()
1158 return license.strip()
1159
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001160 def dump_copy_out_module(self, outf):
1161 """Output the genrule module to copy out/* to $(genDir)."""
1162 copy_out = self.copy_out_module_name()
1163 if not copy_out:
1164 return
1165 outf.write('\ngenrule {\n')
1166 outf.write(' name: "' + copy_out + '",\n')
1167 outf.write(' srcs: ["out/*"],\n')
1168 outf.write(' cmd: "cp $(in) $(genDir)",\n')
1169 if len(self.build_out_files) > 1:
1170 outf.write(' out: [\n')
1171 for f in self.build_out_files:
1172 outf.write(' "' + f + '",\n')
1173 outf.write(' ],\n')
1174 else:
1175 outf.write(' out: ["' + self.build_out_files[0] + '"],\n')
1176 outf.write('}\n')
1177
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001178 def init_bp_file(self, name):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001179 # name could be Android.bp or sub_dir_path/Android.bp
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001180 if name not in self.bp_files:
1181 self.bp_files.add(name)
Haibo Huang0f72c952021-03-19 11:34:15 -07001182 license_section = self.read_license(name)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001183 with open(name, 'w') as outf:
Joel Galenson367360c2021-04-29 14:31:43 -07001184 print_args = filter(lambda x: x != "--no-test-mapping", sys.argv[1:])
1185 outf.write(ANDROID_BP_HEADER.format(args=' '.join(print_args)))
Haibo Huang0f72c952021-03-19 11:34:15 -07001186 outf.write('\n')
1187 outf.write(license_section)
1188 outf.write('\n')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001189 # at most one copy_out module per .bp file
1190 self.dump_copy_out_module(outf)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001191
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001192 def try_claim_module_name(self, name, owner):
1193 """Reserve and return True if it has not been reserved yet."""
1194 if name not in self.name_owners or owner == self.name_owners[name]:
1195 self.name_owners[name] = owner
1196 return True
1197 return False
1198
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001199 def claim_module_name(self, prefix, owner, counter):
1200 """Return prefix if not owned yet, otherwise, prefix+str(counter)."""
1201 while True:
1202 name = prefix
1203 if counter > 0:
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001204 name += '_' + str(counter)
1205 if self.try_claim_module_name(name, owner):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001206 return name
1207 counter += 1
1208
1209 def find_root_pkg(self):
1210 """Read name of [package] in ./Cargo.toml."""
1211 if not os.path.exists('./Cargo.toml'):
1212 return
1213 with open('./Cargo.toml', 'r') as inf:
1214 pkg_section = re.compile(r'^ *\[package\]')
1215 name = re.compile('^ *name *= * "([^"]*)"')
1216 in_pkg = False
1217 for line in inf:
1218 if in_pkg:
1219 if name.match(line):
1220 self.root_pkg = name.match(line).group(1)
1221 break
1222 else:
1223 in_pkg = pkg_section.match(line) is not None
1224
1225 def run_cargo(self):
1226 """Calls cargo -v and save its output to ./cargo.out."""
1227 if self.skip_cargo:
1228 return self
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001229 cargo_toml = './Cargo.toml'
1230 cargo_out = './cargo.out'
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001231 # Do not use Cargo.lock, because .bp rules are designed to
1232 # run with "latest" crates avaialable on Android.
1233 cargo_lock = './Cargo.lock'
1234 cargo_lock_saved = './cargo.lock.saved'
1235 had_cargo_lock = os.path.exists(cargo_lock)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001236 if not os.access(cargo_toml, os.R_OK):
1237 print('ERROR: Cannot find or read', cargo_toml)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001238 return self
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001239 if not self.dry_run:
1240 if os.path.exists(cargo_out):
1241 os.remove(cargo_out)
1242 if not self.args.use_cargo_lock and had_cargo_lock: # save it
1243 os.rename(cargo_lock, cargo_lock_saved)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001244 cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> ' + cargo_out + ' 2>&1'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001245 # set up search PATH for cargo to find the correct rustc
1246 saved_path = os.environ['PATH']
1247 os.environ['PATH'] = os.path.dirname(self.cargo_path) + ':' + saved_path
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001248 # Add [workspace] to Cargo.toml if it is not there.
1249 added_workspace = False
1250 if self.args.add_workspace:
1251 with open(cargo_toml, 'r') as in_file:
1252 cargo_toml_lines = in_file.readlines()
1253 found_workspace = '[workspace]\n' in cargo_toml_lines
1254 if found_workspace:
1255 print('### WARNING: found [workspace] in Cargo.toml')
1256 else:
1257 with open(cargo_toml, 'a') as out_file:
1258 out_file.write('[workspace]\n')
1259 added_workspace = True
1260 if self.args.verbose:
1261 print('### INFO: added [workspace] to Cargo.toml')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001262 for c in self.cargo:
1263 features = ''
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001264 if c != 'clean':
1265 if self.args.features is not None:
1266 features = ' --no-default-features'
1267 if self.args.features:
1268 features += ' --features ' + self.args.features
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001269 cmd_v_flag = ' -vv ' if self.args.vv else ' -v '
1270 cmd = self.cargo_path + cmd_v_flag
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001271 cmd += c + features + cmd_tail
1272 if self.args.rustflags and c != 'clean':
1273 cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
1274 if self.dry_run:
1275 print('Dry-run skip:', cmd)
1276 else:
1277 if self.args.verbose:
1278 print('Running:', cmd)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001279 with open(cargo_out, 'a') as out_file:
1280 out_file.write('### Running: ' + cmd + '\n')
Joel Galenson6bf54e32021-05-17 10:54:50 -07001281 ret = os.system(cmd)
1282 if ret != 0:
1283 print('*** There was an error while running cargo. ' +
1284 'See the cargo.out file for details.')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001285 if added_workspace: # restore original Cargo.toml
1286 with open(cargo_toml, 'w') as out_file:
1287 out_file.writelines(cargo_toml_lines)
1288 if self.args.verbose:
1289 print('### INFO: restored original Cargo.toml')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001290 os.environ['PATH'] = saved_path
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001291 if not self.dry_run:
1292 if not had_cargo_lock: # restore to no Cargo.lock state
1293 os.remove(cargo_lock)
1294 elif not self.args.use_cargo_lock: # restore saved Cargo.lock
1295 os.rename(cargo_lock_saved, cargo_lock)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001296 return self
1297
1298 def dump_dependencies(self):
1299 """Append dependencies and their features to Android.bp."""
1300 if not self.dependencies:
1301 return
1302 dependent_list = list()
1303 for c in self.dependencies:
1304 dependent_list.append(c.feature_list())
1305 sorted_dependencies = sorted(set(dependent_list))
1306 self.init_bp_file('Android.bp')
1307 with open('Android.bp', 'a') as outf:
1308 outf.write('\n// dependent_library ["feature_list"]\n')
1309 for s in sorted_dependencies:
1310 outf.write('// ' + s + '\n')
1311
1312 def dump_pkg_obj2cc(self):
1313 """Dump debug info of the pkg_obj2cc map."""
1314 if not self.args.debug:
1315 return
1316 self.init_bp_file('Android.bp')
1317 with open('Android.bp', 'a') as outf:
1318 sorted_pkgs = sorted(self.pkg_obj2cc.keys())
1319 for pkg in sorted_pkgs:
1320 if not self.pkg_obj2cc[pkg]:
1321 continue
1322 outf.write('\n// obj => src for %s\n' % pkg)
1323 obj2cc = self.pkg_obj2cc[pkg]
1324 for obj in sorted(obj2cc.keys()):
1325 outf.write('// ' + short_out_name(pkg, obj) + ' => ' +
1326 short_out_name(pkg, obj2cc[obj].src) + '\n')
1327
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001328 def apply_patch(self):
1329 """Apply local patch file if it is given."""
1330 if self.args.patch:
1331 if self.dry_run:
1332 print('Dry-run skip patch file:', self.args.patch)
1333 else:
1334 if not os.path.exists(self.args.patch):
1335 self.append_to_bp('ERROR cannot find patch file: ' + self.args.patch)
1336 return self
1337 if self.args.verbose:
1338 print('### INFO: applying local patch file:', self.args.patch)
Joel Galenson7e8247e2021-05-20 18:51:42 -07001339 subprocess.run(['patch', '-s', '--no-backup-if-mismatch', './Android.bp',
1340 self.args.patch], check=True)
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001341 return self
1342
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001343 def gen_bp(self):
1344 """Parse cargo.out and generate Android.bp files."""
1345 if self.dry_run:
1346 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp')
1347 elif os.path.exists(CARGO_OUT):
1348 self.find_root_pkg()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001349 if self.args.copy_out:
1350 self.copy_out_files()
1351 elif self.find_out_files() and self.has_used_out_dir():
1352 print('WARNING: ' + self.root_pkg + ' has cargo output files; ' +
1353 'please rerun with the --copy-out flag.')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001354 with open(CARGO_OUT, 'r') as cargo_out:
1355 self.parse(cargo_out, 'Android.bp')
1356 self.crates.sort(key=get_module_name)
1357 for obj in self.cc_objects:
1358 obj.dump()
1359 self.dump_pkg_obj2cc()
1360 for crate in self.crates:
1361 crate.dump()
1362 dumped_libs = set()
1363 for lib in self.ar_objects:
1364 if lib.pkg == self.root_pkg:
1365 lib_name = file_base_name(lib.lib)
1366 if lib_name not in dumped_libs:
1367 dumped_libs.add(lib_name)
1368 lib.dump()
Joel Galenson5664f2a2021-06-10 10:13:49 -07001369 if self.args.add_toplevel_block:
1370 with open(self.args.add_toplevel_block, 'r') as f:
1371 self.append_to_bp('\n' + f.read() + '\n')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001372 if self.args.dependencies and self.dependencies:
1373 self.dump_dependencies()
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001374 if self.errors:
Joel Galenson3f42f802021-04-07 12:42:17 -07001375 self.append_to_bp('\n' + ERRORS_LINE + '\n' + self.errors)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001376 return self
1377
1378 def add_ar_object(self, obj):
1379 self.ar_objects.append(obj)
1380
1381 def add_cc_object(self, obj):
1382 self.cc_objects.append(obj)
1383
1384 def add_crate(self, crate):
1385 """Merge crate with someone in crates, or append to it. Return crates."""
1386 if crate.skip_crate():
1387 if self.args.debug: # include debug info of all crates
1388 self.crates.append(crate)
1389 if self.args.dependencies: # include only dependent crates
1390 if (is_dependent_file_path(crate.main_src) and
1391 not is_build_crate_name(crate.crate_name)):
1392 self.dependencies.append(crate)
1393 else:
1394 for c in self.crates:
1395 if c.merge(crate, 'Android.bp'):
1396 return
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001397 # If not merged, decide module type and name now.
1398 crate.decide_module_type()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001399 self.crates.append(crate)
1400
1401 def find_warning_owners(self):
1402 """For each warning file, find its owner crate."""
1403 missing_owner = False
1404 for f in self.warning_files:
1405 cargo_dir = '' # find lowest crate, with longest path
1406 owner = None # owner crate of this warning
1407 for c in self.crates:
1408 if (f.startswith(c.cargo_dir + '/') and
1409 len(cargo_dir) < len(c.cargo_dir)):
1410 cargo_dir = c.cargo_dir
1411 owner = c
1412 if owner:
1413 owner.has_warning = True
1414 else:
1415 missing_owner = True
1416 if missing_owner and os.path.exists('Cargo.toml'):
1417 # owner is the root cargo, with empty cargo_dir
1418 for c in self.crates:
1419 if not c.cargo_dir:
1420 c.has_warning = True
1421
1422 def rustc_command(self, n, rustc_line, line, outf_name):
1423 """Process a rustc command line from cargo -vv output."""
1424 # cargo build -vv output can have multiple lines for a rustc command
1425 # due to '\n' in strings for environment variables.
1426 # strip removes leading spaces and '\n' at the end
1427 new_rustc = (rustc_line.strip() + line) if rustc_line else line
1428 # Use an heuristic to detect the completions of a multi-line command.
1429 # This might fail for some very rare case, but easy to fix manually.
1430 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0:
1431 return new_rustc
1432 if RUSTC_VV_CMD_ARGS.match(new_rustc):
1433 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1)
1434 self.add_crate(Crate(self, outf_name).parse(n, args))
1435 else:
1436 self.assert_empty_vv_line(new_rustc)
1437 return ''
1438
1439 def cc_ar_command(self, n, groups, outf_name):
1440 pkg = groups.group(1)
1441 line = groups.group(3)
1442 if groups.group(2) == 'cc':
1443 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line))
1444 else:
1445 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line))
1446
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001447 def append_to_bp(self, line):
1448 self.init_bp_file('Android.bp')
1449 with open('Android.bp', 'a') as outf:
1450 outf.write(line)
1451
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001452 def assert_empty_vv_line(self, line):
1453 if line: # report error if line is not empty
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001454 self.append_to_bp('ERROR -vv line: ' + line)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001455 return ''
1456
1457 def parse(self, inf, outf_name):
1458 """Parse rustc and warning messages in inf, return a list of Crates."""
1459 n = 0 # line number
1460 prev_warning = False # true if the previous line was warning: ...
1461 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
1462 for line in inf:
1463 n += 1
1464 if line.startswith('warning: '):
1465 prev_warning = True
1466 rustc_line = self.assert_empty_vv_line(rustc_line)
1467 continue
1468 new_rustc = ''
1469 if RUSTC_PAT.match(line):
1470 args_line = RUSTC_PAT.match(line).group(1)
1471 self.add_crate(Crate(self, outf_name).parse(n, args_line))
1472 self.assert_empty_vv_line(rustc_line)
1473 elif rustc_line or RUSTC_VV_PAT.match(line):
1474 new_rustc = self.rustc_command(n, rustc_line, line, outf_name)
1475 elif CC_AR_VV_PAT.match(line):
1476 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name)
1477 elif prev_warning and WARNING_FILE_PAT.match(line):
1478 self.assert_empty_vv_line(rustc_line)
1479 fpath = WARNING_FILE_PAT.match(line).group(1)
1480 if fpath[0] != '/': # ignore absolute path
1481 self.warning_files.add(fpath)
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001482 elif line.startswith('error: ') or line.startswith('error[E'):
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001483 if not self.args.ignore_cargo_errors:
1484 self.errors += line
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001485 prev_warning = False
1486 rustc_line = new_rustc
1487 self.find_warning_owners()
1488
1489
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001490def get_parser():
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001491 """Parse main arguments."""
1492 parser = argparse.ArgumentParser('cargo2android')
1493 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001494 '--add_workspace',
1495 action='store_true',
1496 default=False,
1497 help=('append [workspace] to Cargo.toml before calling cargo,' +
1498 ' to treat current directory as root of package source;' +
1499 ' otherwise the relative source file path in generated' +
1500 ' .bp file will be from the parent directory.'))
1501 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001502 '--cargo',
1503 action='append',
1504 metavar='args_string',
1505 help=('extra cargo build -v args in a string, ' +
1506 'each --cargo flag calls cargo build -v once'))
1507 parser.add_argument(
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001508 '--cargo_bin',
1509 type=str,
1510 help='use cargo in the cargo_bin directory instead of the prebuilt one')
1511 parser.add_argument(
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001512 '--copy-out',
1513 action='store_true',
1514 default=False,
1515 help=('only for root directory, ' +
1516 'copy build.rs output to ./out/* and add a genrule to copy ' +
1517 './out/* to genrule output; for crates with code pattern: ' +
1518 'include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))'))
1519 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001520 '--debug',
1521 action='store_true',
1522 default=False,
1523 help='dump debug info into Android.bp')
1524 parser.add_argument(
1525 '--dependencies',
1526 action='store_true',
1527 default=False,
1528 help='dump debug info of dependent crates')
1529 parser.add_argument(
1530 '--device',
1531 action='store_true',
1532 default=False,
1533 help='run cargo also for a default device target')
1534 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001535 '--features',
1536 type=str,
1537 help=('pass features to cargo build, ' +
1538 'empty string means no default features'))
1539 parser.add_argument(
1540 '--global_defaults',
1541 type=str,
1542 help='add a defaults name to every module')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001543 parser.add_argument(
1544 '--host-first-multilib',
1545 action='store_true',
1546 default=False,
1547 help=('add a compile_multilib:"first" property ' +
1548 'to Android.bp host modules.'))
1549 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001550 '--ignore-cargo-errors',
1551 action='store_true',
1552 default=False,
1553 help='do not append cargo/rustc error messages to Android.bp')
1554 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001555 '--no-host',
1556 action='store_true',
1557 default=False,
1558 help='do not run cargo for the host; only for the device target')
1559 parser.add_argument(
1560 '--no-subdir',
1561 action='store_true',
1562 default=False,
1563 help='do not output anything for sub-directories')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001564 parser.add_argument(
1565 '--onefile',
1566 action='store_true',
1567 default=False,
1568 help=('output all into one ./Android.bp, default will generate ' +
1569 'one Android.bp per Cargo.toml in subdirectories'))
1570 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001571 '--patch',
1572 type=str,
1573 help='apply the given patch file to generated ./Android.bp')
1574 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001575 '--run',
1576 action='store_true',
1577 default=False,
1578 help='run it, default is dry-run')
1579 parser.add_argument('--rustflags', type=str, help='passing flags to rustc')
1580 parser.add_argument(
1581 '--skipcargo',
1582 action='store_true',
1583 default=False,
1584 help='skip cargo command, parse cargo.out, and generate Android.bp')
1585 parser.add_argument(
1586 '--tests',
1587 action='store_true',
1588 default=False,
1589 help='run cargo build --tests after normal build')
1590 parser.add_argument(
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001591 '--use-cargo-lock',
1592 action='store_true',
1593 default=False,
1594 help=('run cargo build with existing Cargo.lock ' +
1595 '(used when some latest dependent crates failed)'))
1596 parser.add_argument(
Joel Galensond9c4de62021-04-23 10:26:40 -07001597 '--min-sdk-version',
1598 type=str,
1599 help='Minimum SDK version')
1600 parser.add_argument(
1601 '--apex-available',
1602 nargs='*',
1603 help='Mark the main library as apex_available with the given apexes.')
1604 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001605 '--force-rlib',
1606 action='store_true',
1607 default=False,
1608 help='Make the main library an rlib.')
1609 parser.add_argument(
Joel Galenson97e414a2021-05-27 09:42:32 -07001610 '--dependency-blocklist',
1611 nargs='*',
1612 default=[],
1613 help='Do not emit the given dependencies.')
1614 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001615 '--lib-blocklist',
1616 nargs='*',
1617 default=[],
1618 help='Do not emit the given C libraries as dependencies.')
1619 parser.add_argument(
Joel Galensonf6b3c912021-06-03 16:00:54 -07001620 '--test-blocklist',
1621 nargs='*',
1622 default=[],
1623 help=('Do not emit the given tests. ' +
1624 'Pass the path to the test file to exclude.'))
1625 parser.add_argument(
Joel Galenson3d6d1e72021-06-07 15:00:24 -07001626 '--cfg-blocklist',
1627 nargs='*',
1628 default=[],
1629 help='Do not emit the given cfg.')
1630 parser.add_argument(
Joel Galenson5664f2a2021-06-10 10:13:49 -07001631 '--add-toplevel-block',
1632 type=str,
1633 help='Add the contents of the given file to the top level of the Android.bp.')
1634 parser.add_argument(
1635 '--add-module-block',
1636 type=str,
1637 help='Add the contents of the given file to the main module.')
1638 parser.add_argument(
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001639 '--no-test-mapping',
1640 action='store_true',
1641 default=False,
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001642 help='Deprecated. Has no effect.')
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001643 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001644 '--verbose',
1645 action='store_true',
1646 default=False,
1647 help='echo executed commands')
1648 parser.add_argument(
1649 '--vv',
1650 action='store_true',
1651 default=False,
1652 help='run cargo with -vv instead of default -v')
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001653 parser.add_argument(
1654 '--dump-config-and-exit',
1655 type=str,
1656 help=('Dump command-line arguments (minus this flag) to a config file and exit. ' +
1657 'This is intended to help migrate from command line options to config files.'))
1658 parser.add_argument(
1659 '--config',
1660 type=str,
1661 help=('Load command-line options from the given config file. ' +
1662 'Options in this file will override those passed on the command line.'))
1663 return parser
1664
1665
1666def parse_args(parser):
1667 """Parses command-line options."""
1668 args = parser.parse_args()
1669 # Use the values specified in a config file if one was found.
1670 if args.config:
1671 with open(args.config, 'r') as f:
1672 config = json.load(f)
1673 args_dict = vars(args)
1674 for arg in config:
1675 args_dict[arg.replace('-', '_')] = config[arg]
1676 return args
1677
1678
1679def dump_config(parser, args):
1680 """Writes the non-default command-line options to the specified file."""
1681 args_dict = vars(args)
1682 # Filter out the arguments that have their default value.
Joel Galenson367360c2021-04-29 14:31:43 -07001683 # Also filter certain "temporary" arguments.
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001684 non_default_args = {}
1685 for arg in args_dict:
Joel Galenson367360c2021-04-29 14:31:43 -07001686 if args_dict[arg] != parser.get_default(
1687 arg) and arg != 'dump_config_and_exit' and arg != 'no_test_mapping':
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001688 non_default_args[arg.replace('_', '-')] = args_dict[arg]
1689 # Write to the specified file.
1690 with open(args.dump_config_and_exit, 'w') as f:
1691 json.dump(non_default_args, f, indent=2, sort_keys=True)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001692
1693
1694def main():
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001695 parser = get_parser()
1696 args = parse_args(parser)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001697 if not args.run: # default is dry-run
1698 print(DRY_RUN_NOTE)
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001699 if args.dump_config_and_exit:
1700 dump_config(parser, args)
1701 else:
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001702 Runner(args).run_cargo().gen_bp().apply_patch()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001703
1704
1705if __name__ == '__main__':
1706 main()