blob: 4810cad6853feeaf61060d7964b76d562e00f68d [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',
Victor Hsieh21bea792020-12-04 10:59:16 -080068 'libfuse': 'libfuse_rust',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080069 'libgcc': 'libgcc_rust',
70 'liblog': 'liblog_rust',
Chih-Hung Hsieh07119862020-07-24 15:34:06 -070071 'libminijail': 'libminijail_rust',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080072 'libsync': 'libsync_rust',
73 'libx86_64': 'libx86_64_rust',
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -070074 'protoc_gen_rust': 'protoc-gen-rust',
75}
76
77RENAME_STEM_MAP = {
78 # This map includes all changes to the default rust module stem names,
79 # which is used for output files when different from the module name.
80 'protoc_gen_rust': 'protoc-gen-rust',
81}
82
83RENAME_DEFAULTS_MAP = {
84 # This map includes all changes to the default prefix of rust_default
85 # module names, to avoid conflict with existing Android modules.
86 'libc': 'rust_libc',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080087}
88
89# Header added to all generated Android.bp files.
Joel Galenson56446742021-02-18 08:27:48 -080090ANDROID_BP_HEADER = (
91 '// This file is generated by cargo2android.py {args}.\n' +
92 '// Do not modify this file as changes will be overridden on upgrade.\n')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080093
94CARGO_OUT = 'cargo.out' # Name of file to keep cargo build -v output.
95
Joel Galenson3f42f802021-04-07 12:42:17 -070096# This should be kept in sync with tools/external_updater/crates_updater.py.
97ERRORS_LINE = 'Errors in ' + CARGO_OUT + ':'
98
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080099TARGET_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
187class Crate(object):
188 """Information of a Rust crate to collect/emit for an Android.bp module."""
189
190 def __init__(self, runner, outf_name):
191 # Remembered global runner and its members.
192 self.runner = runner
193 self.debug = runner.args.debug
194 self.cargo_dir = '' # directory of my Cargo.toml
195 self.outf_name = outf_name # path to Android.bp
196 self.outf = None # open file handle of outf_name during dump*
197 # Variants/results that could be merged from multiple rustc lines.
198 self.host_supported = False
199 self.device_supported = False
200 self.has_warning = False
201 # Android module properties derived from rustc parameters.
202 self.module_name = '' # unique in Android build system
203 self.module_type = '' # rust_{binary,library,test}[_host] etc.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700204 self.defaults = '' # rust_defaults used by rust_test* modules
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700205 self.default_srcs = False # use 'srcs' defined in self.defaults
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800206 self.root_pkg = '' # parent package name of a sub/test packge, from -L
207 self.srcs = list() # main_src or merged multiple source files
208 self.stem = '' # real base name of output file
209 # Kept parsed status
210 self.errors = '' # all errors found during parsing
211 self.line_num = 1 # runner told input source line number
212 self.line = '' # original rustc command line parameters
213 # Parameters collected from rustc command line.
214 self.crate_name = '' # follows --crate-name
215 self.main_src = '' # follows crate_name parameter, shortened
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700216 self.crate_types = list() # follows --crate-type
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800217 self.cfgs = list() # follows --cfg, without feature= prefix
218 self.features = list() # follows --cfg, name in 'feature="..."'
219 self.codegens = list() # follows -C, some ignored
220 self.externs = list() # follows --extern
221 self.core_externs = list() # first part of self.externs elements
222 self.static_libs = list() # e.g. -l static=host_cpuid
223 self.shared_libs = list() # e.g. -l dylib=wayland-client, -l z
224 self.cap_lints = '' # follows --cap-lints
225 self.emit_list = '' # e.g., --emit=dep-info,metadata,link
226 self.edition = '2015' # rustc default, e.g., --edition=2018
227 self.target = '' # follows --target
228
229 def write(self, s):
230 # convenient way to output one line at a time with EOL.
231 self.outf.write(s + '\n')
232
233 def same_flags(self, other):
234 # host_supported, device_supported, has_warning are not compared but merged
235 # target is not compared, to merge different target/host modules
236 # externs is not compared; only core_externs is compared
237 return (not self.errors and not other.errors and
238 self.edition == other.edition and
239 self.cap_lints == other.cap_lints and
240 self.emit_list == other.emit_list and
241 self.core_externs == other.core_externs and
242 self.codegens == other.codegens and
243 self.features == other.features and
244 self.static_libs == other.static_libs and
245 self.shared_libs == other.shared_libs and self.cfgs == other.cfgs)
246
247 def merge_host_device(self, other):
248 """Returns true if attributes are the same except host/device support."""
249 return (self.crate_name == other.crate_name and
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700250 self.crate_types == other.crate_types and
251 self.main_src == other.main_src and
252 # before merge, each test module has an unique module name and stem
253 (self.stem == other.stem or self.crate_types == ['test']) and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800254 self.root_pkg == other.root_pkg and not self.skip_crate() and
255 self.same_flags(other))
256
257 def merge_test(self, other):
258 """Returns true if self and other are tests of same root_pkg."""
259 # Before merger, each test has its own crate_name.
260 # A merged test uses its source file base name as output file name,
261 # so a test is mergeable only if its base name equals to its crate name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700262 return (self.crate_types == other.crate_types and
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700263 self.crate_types == ['test'] and self.root_pkg == other.root_pkg and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800264 not self.skip_crate() and
265 other.crate_name == test_base_name(other.main_src) and
266 (len(self.srcs) > 1 or
267 (self.crate_name == test_base_name(self.main_src)) and
268 self.host_supported == other.host_supported and
269 self.device_supported == other.device_supported) and
270 self.same_flags(other))
271
272 def merge(self, other, outf_name):
273 """Try to merge crate into self."""
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700274 # Cargo build --tests could recompile a library for tests.
275 # We need to merge such duplicated calls to rustc, with
276 # the algorithm in merge_host_device.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800277 should_merge_host_device = self.merge_host_device(other)
278 should_merge_test = False
279 if not should_merge_host_device:
280 should_merge_test = self.merge_test(other)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800281 if should_merge_host_device or should_merge_test:
282 self.runner.init_bp_file(outf_name)
283 with open(outf_name, 'a') as outf: # to write debug info
284 self.outf = outf
285 other.outf = outf
286 self.do_merge(other, should_merge_test)
287 return True
288 return False
289
290 def do_merge(self, other, should_merge_test):
291 """Merge attributes of other to self."""
292 if self.debug:
293 self.write('\n// Before merge definition (1):')
294 self.dump_debug_info()
295 self.write('\n// Before merge definition (2):')
296 other.dump_debug_info()
297 # Merge properties of other to self.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800298 self.has_warning = self.has_warning or other.has_warning
299 if not self.target: # okay to keep only the first target triple
300 self.target = other.target
301 # decide_module_type sets up default self.stem,
302 # which can be changed if self is a merged test module.
303 self.decide_module_type()
304 if should_merge_test:
305 self.srcs.append(other.main_src)
306 # use a short unique name as the merged module name.
307 prefix = self.root_pkg + '_tests'
308 self.module_name = self.runner.claim_module_name(prefix, self, 0)
309 self.stem = self.module_name
310 # This normalized root_pkg name although might be the same
311 # as other module's crate_name, it is not actually used for
312 # output file name. A merged test module always have multiple
313 # source files and each source file base name is used as
314 # its output file name.
315 self.crate_name = pkg2crate_name(self.root_pkg)
316 if self.debug:
317 self.write('\n// After merge definition (1):')
318 self.dump_debug_info()
319
320 def find_cargo_dir(self):
321 """Deepest directory with Cargo.toml and contains the main_src."""
322 if not is_dependent_file_path(self.main_src):
323 dir_name = os.path.dirname(self.main_src)
324 while dir_name:
325 if os.path.exists(dir_name + '/Cargo.toml'):
326 self.cargo_dir = dir_name
327 return
328 dir_name = os.path.dirname(dir_name)
329
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700330 def add_codegens_flag(self, flag):
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700331 """Ignore options not used in Android."""
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700332 # 'prefer-dynamic' does not work with common flag -C lto
Chih-Hung Hsieh63459ed2020-08-26 11:51:15 -0700333 # 'embed-bitcode' is ignored; we might control LTO with other .bp flag
Chih-Hung Hsieh6c13b722020-09-11 21:24:03 -0700334 # 'codegen-units' is set in Android global config or by default
335 if not (flag.startswith('codegen-units=') or
336 flag.startswith('debuginfo=') or
Chih-Hung Hsieh63459ed2020-08-26 11:51:15 -0700337 flag.startswith('embed-bitcode=') or
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700338 flag.startswith('extra-filename=') or
339 flag.startswith('incremental=') or
340 flag.startswith('metadata=') or
341 flag == 'prefer-dynamic'):
342 self.codegens.append(flag)
343
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800344 def parse(self, line_num, line):
345 """Find important rustc arguments to convert to Android.bp properties."""
346 self.line_num = line_num
347 self.line = line
348 args = line.split() # Loop through every argument of rustc.
349 i = 0
350 while i < len(args):
351 arg = args[i]
352 if arg == '--crate-name':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700353 i += 1
354 self.crate_name = args[i]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800355 elif arg == '--crate-type':
356 i += 1
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700357 # cargo calls rustc with multiple --crate-type flags.
358 # rustc can accept:
359 # --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
360 self.crate_types.append(args[i])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800361 elif arg == '--test':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700362 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800363 elif arg == '--target':
364 i += 1
365 self.target = args[i]
366 elif arg == '--cfg':
367 i += 1
368 if args[i].startswith('\'feature='):
369 self.features.append(unquote(args[i].replace('\'feature=', '')[:-1]))
370 else:
371 self.cfgs.append(args[i])
372 elif arg == '--extern':
373 i += 1
374 extern_names = re.sub('=/[^ ]*/deps/', ' = ', args[i])
375 self.externs.append(extern_names)
376 self.core_externs.append(re.sub(' = .*', '', extern_names))
377 elif arg == '-C': # codegen options
378 i += 1
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700379 self.add_codegens_flag(args[i])
380 elif arg.startswith('-C'):
381 # cargo has been passing "-C <xyz>" flag to rustc,
382 # but newer cargo could pass '-Cembed-bitcode=no' to rustc.
383 self.add_codegens_flag(arg[2:])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800384 elif arg == '--cap-lints':
385 i += 1
386 self.cap_lints = args[i]
387 elif arg == '-L':
388 i += 1
389 if args[i].startswith('dependency=') and args[i].endswith('/deps'):
390 if '/' + TARGET_TMP + '/' in args[i]:
391 self.root_pkg = re.sub(
392 '^.*/', '', re.sub('/' + TARGET_TMP + '/.*/deps$', '', args[i]))
393 else:
394 self.root_pkg = re.sub('^.*/', '',
395 re.sub('/[^/]+/[^/]+/deps$', '', args[i]))
396 self.root_pkg = remove_version_suffix(self.root_pkg)
397 elif arg == '-l':
398 i += 1
399 if args[i].startswith('static='):
400 self.static_libs.append(re.sub('static=', '', args[i]))
401 elif args[i].startswith('dylib='):
402 self.shared_libs.append(re.sub('dylib=', '', args[i]))
403 else:
404 self.shared_libs.append(args[i])
405 elif arg == '--out-dir' or arg == '--color': # ignored
406 i += 1
407 elif arg.startswith('--error-format=') or arg.startswith('--json='):
408 _ = arg # ignored
409 elif arg.startswith('--emit='):
410 self.emit_list = arg.replace('--emit=', '')
411 elif arg.startswith('--edition='):
412 self.edition = arg.replace('--edition=', '')
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700413 elif not arg.startswith('-'):
414 # shorten imported crate main source paths like $HOME/.cargo/
415 # registry/src/github.com-1ecc6299db9ec823/memchr-2.3.3/src/lib.rs
416 self.main_src = re.sub(r'^/[^ ]*/registry/src/', '.../', args[i])
417 self.main_src = re.sub(r'^\.\.\./github.com-[0-9a-f]*/', '.../',
418 self.main_src)
419 self.find_cargo_dir()
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700420 if self.cargo_dir: # for a subdirectory
421 if self.runner.args.no_subdir: # all .bp content to /dev/null
422 self.outf_name = '/dev/null'
423 elif not self.runner.args.onefile:
424 # Write to Android.bp in the subdirectory with Cargo.toml.
425 self.outf_name = self.cargo_dir + '/Android.bp'
426 self.main_src = self.main_src[len(self.cargo_dir) + 1:]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800427 else:
428 self.errors += 'ERROR: unknown ' + arg + '\n'
429 i += 1
430 if not self.crate_name:
431 self.errors += 'ERROR: missing --crate-name\n'
432 if not self.main_src:
433 self.errors += 'ERROR: missing main source file\n'
434 else:
435 self.srcs.append(self.main_src)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700436 if not self.crate_types:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800437 # Treat "--cfg test" as "--test"
438 if 'test' in self.cfgs:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700439 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800440 else:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700441 self.errors += 'ERROR: missing --crate-type or --test\n'
442 elif len(self.crate_types) > 1:
443 if 'test' in self.crate_types:
444 self.errors += 'ERROR: cannot handle both --crate-type and --test\n'
445 if 'lib' in self.crate_types and 'rlib' in self.crate_types:
446 self.errors += 'ERROR: cannot generate both lib and rlib crate types\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800447 if not self.root_pkg:
448 self.root_pkg = self.crate_name
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700449 self.device_supported = self.runner.args.device
450 self.host_supported = not self.runner.args.no_host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800451 self.cfgs = sorted(set(self.cfgs))
452 self.features = sorted(set(self.features))
453 self.codegens = sorted(set(self.codegens))
454 self.externs = sorted(set(self.externs))
455 self.core_externs = sorted(set(self.core_externs))
456 self.static_libs = sorted(set(self.static_libs))
457 self.shared_libs = sorted(set(self.shared_libs))
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700458 self.crate_types = sorted(set(self.crate_types))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800459 self.decide_module_type()
460 self.module_name = altered_name(self.stem)
461 return self
462
463 def dump_line(self):
464 self.write('\n// Line ' + str(self.line_num) + ' ' + self.line)
465
466 def feature_list(self):
467 """Return a string of main_src + "feature_list"."""
468 pkg = self.main_src
469 if pkg.startswith('.../'): # keep only the main package name
470 pkg = re.sub('/.*', '', pkg[4:])
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700471 elif pkg.startswith('/'): # use relative path for a local package
472 pkg = os.path.relpath(pkg)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800473 if not self.features:
474 return pkg
475 return pkg + ' "' + ','.join(self.features) + '"'
476
477 def dump_skip_crate(self, kind):
478 if self.debug:
479 self.write('\n// IGNORED: ' + kind + ' ' + self.main_src)
480 return self
481
482 def skip_crate(self):
483 """Return crate_name or a message if this crate should be skipped."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700484 if (is_build_crate_name(self.crate_name) or
485 self.crate_name in EXCLUDED_CRATES):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800486 return self.crate_name
487 if is_dependent_file_path(self.main_src):
488 return 'dependent crate'
489 return ''
490
491 def dump(self):
492 """Dump all error/debug/module code to the output .bp file."""
493 self.runner.init_bp_file(self.outf_name)
494 with open(self.outf_name, 'a') as outf:
495 self.outf = outf
496 if self.errors:
497 self.dump_line()
498 self.write(self.errors)
499 elif self.skip_crate():
500 self.dump_skip_crate(self.skip_crate())
501 else:
502 if self.debug:
503 self.dump_debug_info()
504 self.dump_android_module()
505
506 def dump_debug_info(self):
507 """Dump parsed data, when cargo2android is called with --debug."""
508
509 def dump(name, value):
510 self.write('//%12s = %s' % (name, value))
511
512 def opt_dump(name, value):
513 if value:
514 dump(name, value)
515
516 def dump_list(fmt, values):
517 for v in values:
518 self.write(fmt % v)
519
520 self.dump_line()
521 dump('module_name', self.module_name)
522 dump('crate_name', self.crate_name)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700523 dump('crate_types', self.crate_types)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800524 dump('main_src', self.main_src)
525 dump('has_warning', self.has_warning)
526 dump('for_host', self.host_supported)
527 dump('for_device', self.device_supported)
528 dump('module_type', self.module_type)
529 opt_dump('target', self.target)
530 opt_dump('edition', self.edition)
531 opt_dump('emit_list', self.emit_list)
532 opt_dump('cap_lints', self.cap_lints)
533 dump_list('// cfg = %s', self.cfgs)
534 dump_list('// cfg = \'feature "%s"\'', self.features)
535 # TODO(chh): escape quotes in self.features, but not in other dump_list
536 dump_list('// codegen = %s', self.codegens)
537 dump_list('// externs = %s', self.externs)
538 dump_list('// -l static = %s', self.static_libs)
539 dump_list('// -l (dylib) = %s', self.shared_libs)
540
541 def dump_android_module(self):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700542 """Dump one or more Android module definition, depending on crate_types."""
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700543 if len(self.crate_types) == 1:
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700544 self.dump_single_type_android_module()
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700545 return
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700546 if 'test' in self.crate_types:
547 self.write('\nERROR: multiple crate types cannot include test type')
548 return
549 # Dump one Android module per crate_type.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700550 for crate_type in self.crate_types:
551 self.decide_one_module_type(crate_type)
552 self.dump_one_android_module(crate_type)
553
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700554 def build_default_name(self):
555 """Return a short and readable name for the rust_defaults module."""
556 # Choices: (1) root_pkg + '_defaults',
557 # (2) root_pkg + '_defaults_' + crate_name
558 # (3) root_pkg + '_defaults_' + main_src_basename_path
559 # (4) root_pkg + '_defaults_' + a_positive_sequence_number
560 name1 = altered_defaults(self.root_pkg) + '_defaults'
561 if self.runner.try_claim_module_name(name1, self):
562 return name1
563 name2 = name1 + '_' + self.crate_name
564 if self.runner.try_claim_module_name(name2, self):
565 return name2
566 name3 = name1 + '_' + self.main_src_basename_path()
567 if self.runner.try_claim_module_name(name3, self):
568 return name3
569 return self.runner.claim_module_name(name1, self, 0)
570
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700571 def dump_srcs_list(self):
572 """Dump the srcs list, for defaults or regular modules."""
573 if len(self.srcs) > 1:
574 srcs = sorted(set(self.srcs)) # make a copy and dedup
575 else:
576 srcs = [self.main_src]
577 copy_out = self.runner.copy_out_module_name()
578 if copy_out:
579 srcs.append(':' + copy_out)
580 self.dump_android_property_list('srcs', '"%s"', srcs)
581
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700582 def dump_defaults_module(self):
583 """Dump a rust_defaults module to be shared by other modules."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700584 name = self.build_default_name()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700585 self.defaults = name
586 self.write('\nrust_defaults {')
587 self.write(' name: "' + name + '",')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700588 if self.runner.args.global_defaults:
589 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700590 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700591 if len(self.srcs) == 1: # only one source file; share it in defaults
592 self.default_srcs = True
593 if self.has_warning and not self.cap_lints:
594 self.write(' // has rustc warnings')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700595 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700596 if 'test' in self.crate_types:
597 self.write(' test_suites: ["general-tests"],')
598 self.write(' auto_gen_config: true,')
599 self.dump_edition_flags_libs()
600 self.write('}')
601
602 def dump_single_type_android_module(self):
603 """Dump one simple Android module, which has only one crate_type."""
604 crate_type = self.crate_types[0]
605 if crate_type != 'test':
606 # do not change self.stem or self.module_name
607 self.dump_one_android_module(crate_type)
608 return
609 # Dump one test module per source file, and separate host and device tests.
610 # crate_type == 'test'
Joel Galensonf6b3c912021-06-03 16:00:54 -0700611 self.srcs = [src for src in self.srcs if not src in self.runner.args.test_blocklist]
612 if ((self.host_supported and self.device_supported and len(self.srcs) > 0) or
613 len(self.srcs) > 1):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700614 self.srcs = sorted(set(self.srcs))
615 self.dump_defaults_module()
616 saved_srcs = self.srcs
617 for src in saved_srcs:
618 self.srcs = [src]
619 saved_device_supported = self.device_supported
620 saved_host_supported = self.host_supported
621 saved_main_src = self.main_src
622 self.main_src = src
623 if saved_host_supported:
624 self.device_supported = False
625 self.host_supported = True
626 self.module_name = self.test_module_name()
627 self.decide_one_module_type(crate_type)
628 self.dump_one_android_module(crate_type)
629 if saved_device_supported:
630 self.device_supported = True
631 self.host_supported = False
632 self.module_name = self.test_module_name()
633 self.decide_one_module_type(crate_type)
634 self.dump_one_android_module(crate_type)
635 self.host_supported = saved_host_supported
636 self.device_supported = saved_device_supported
637 self.main_src = saved_main_src
638 self.srcs = saved_srcs
639
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700640 def dump_one_android_module(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800641 """Dump one Android module definition."""
642 if not self.module_type:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700643 self.write('\nERROR: unknown crate_type ' + crate_type)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800644 return
645 self.write('\n' + self.module_type + ' {')
646 self.dump_android_core_properties()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700647 if not self.defaults:
648 self.dump_edition_flags_libs()
649 if self.runner.args.host_first_multilib and self.host_supported and crate_type != 'test':
650 self.write(' compile_multilib: "first",')
Joel Galensond9c4de62021-04-23 10:26:40 -0700651 if self.runner.args.apex_available and crate_type == 'lib':
652 self.write(' apex_available: [')
653 for apex in self.runner.args.apex_available:
654 self.write(' "%s",' % apex)
655 self.write(' ],')
656 if self.runner.args.min_sdk_version and crate_type == 'lib':
657 self.write(' min_sdk_version: "%s",' % self.runner.args.min_sdk_version)
Joel Galenson5664f2a2021-06-10 10:13:49 -0700658 if self.runner.args.add_module_block:
659 with open(self.runner.args.add_module_block, 'r') as f:
660 self.write(' %s,' % f.read().replace('\n', '\n '))
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700661 self.write('}')
662
663 def dump_android_flags(self):
664 """Dump Android module flags property."""
ThiƩbaud Weksteena5a728b2021-04-08 14:23:49 +0200665 if not self.codegens and not self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700666 return
667 self.write(' flags: [')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800668 if self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700669 self.write(' "--cap-lints ' + self.cap_lints + '",')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700670 codegens_fmt = '"-C %s"'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700671 self.dump_android_property_list_items(codegens_fmt, self.codegens)
672 self.write(' ],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700673
674 def dump_edition_flags_libs(self):
675 if self.edition:
676 self.write(' edition: "' + self.edition + '",')
677 self.dump_android_property_list('features', '"%s"', self.features)
Joel Galenson3d6d1e72021-06-07 15:00:24 -0700678 cfgs = [cfg for cfg in self.cfgs if not cfg in self.runner.args.cfg_blocklist]
679 self.dump_android_property_list('cfgs', '"%s"', cfgs)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700680 self.dump_android_flags()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800681 if self.externs:
682 self.dump_android_externs()
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700683 static_libs = [lib for lib in self.static_libs if not lib in self.runner.args.lib_blocklist]
684 self.dump_android_property_list('static_libs', '"lib%s"', static_libs)
685 shared_libs = [lib for lib in self.shared_libs if not lib in self.runner.args.lib_blocklist]
686 self.dump_android_property_list('shared_libs', '"lib%s"', shared_libs)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800687
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700688 def main_src_basename_path(self):
689 return re.sub('/', '_', re.sub('.rs$', '', self.main_src))
690
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800691 def test_module_name(self):
692 """Return a unique name for a test module."""
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700693 # root_pkg+(_host|_device) + '_test_'+source_file_name
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700694 suffix = self.main_src_basename_path()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700695 host_device = '_host'
696 if self.device_supported:
697 host_device = '_device'
698 return self.root_pkg + host_device + '_test_' + suffix
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800699
700 def decide_module_type(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700701 # Use the first crate type for the default/first module.
702 crate_type = self.crate_types[0] if self.crate_types else ''
703 self.decide_one_module_type(crate_type)
704
705 def decide_one_module_type(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800706 """Decide which Android module type to use."""
707 host = '' if self.device_supported else '_host'
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700708 rlib = '_rlib' if self.runner.args.force_rlib else ''
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700709 if crate_type == 'bin': # rust_binary[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800710 self.module_type = 'rust_binary' + host
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700711 # In rare cases like protobuf-codegen, the output binary name must
712 # be renamed to use as a plugin for protoc.
713 self.stem = altered_stem(self.crate_name)
714 self.module_name = altered_name(self.crate_name)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700715 elif crate_type == 'lib': # rust_library[_host]
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700716 # TODO(chh): should this be rust_library[_host]?
717 # Assuming that Cargo.toml do not use both 'lib' and 'rlib',
718 # because we map them both to rlib.
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700719 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800720 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700721 self.module_name = altered_name(self.stem)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700722 elif crate_type == 'rlib': # rust_library[_host]
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700723 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700724 self.stem = 'lib' + self.crate_name
725 self.module_name = altered_name(self.stem)
726 elif crate_type == 'dylib': # rust_library[_host]_dylib
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800727 self.module_type = 'rust_library' + host + '_dylib'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700728 self.stem = 'lib' + self.crate_name
729 self.module_name = altered_name(self.stem) + '_dylib'
730 elif crate_type == 'cdylib': # rust_library[_host]_shared
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500731 self.module_type = 'rust_ffi' + host + '_shared'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700732 self.stem = 'lib' + self.crate_name
733 self.module_name = altered_name(self.stem) + '_shared'
734 elif crate_type == 'staticlib': # rust_library[_host]_static
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500735 self.module_type = 'rust_ffi' + host + '_static'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700736 self.stem = 'lib' + self.crate_name
737 self.module_name = altered_name(self.stem) + '_static'
738 elif crate_type == 'test': # rust_test[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800739 self.module_type = 'rust_test' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700740 # Before do_merge, stem name is based on the --crate-name parameter.
741 # and test module name is based on stem.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800742 self.stem = self.test_module_name()
743 # self.stem will be changed after merging with other tests.
744 # self.stem is NOT used for final test binary name.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700745 # rust_test uses each source file base name as part of output file name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700746 # In do_merge, this function is called again, with a module_name.
747 # We make sure that the module name is unique in each package.
748 if self.module_name:
749 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add
750 # different suffixes and distinguish multiple tests of the same
751 # crate name. We ignore -C and use claim_module_name to get
752 # unique sequential suffix.
753 self.module_name = self.runner.claim_module_name(
754 self.module_name, self, 0)
755 # Now the module name is unique, stem should also match and unique.
756 self.stem = self.module_name
757 elif crate_type == 'proc-macro': # rust_proc_macro
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800758 self.module_type = 'rust_proc_macro'
759 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700760 self.module_name = altered_name(self.stem)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800761 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]?
762 self.module_type = ''
763 self.stem = ''
764
765 def dump_android_property_list_items(self, fmt, values):
766 for v in values:
767 # fmt has quotes, so we need escape_quotes(v)
768 self.write(' ' + (fmt % escape_quotes(v)) + ',')
769
770 def dump_android_property_list(self, name, fmt, values):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700771 if not values:
772 return
773 if len(values) > 1:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800774 self.write(' ' + name + ': [')
775 self.dump_android_property_list_items(fmt, values)
776 self.write(' ],')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700777 else:
778 self.write(' ' + name + ': [' +
779 (fmt % escape_quotes(values[0])) + '],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800780
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 Hsieh07119862020-07-24 15:34:06 -0700787 elif self.runner.args.global_defaults:
788 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800789 if self.stem != self.module_name:
790 self.write(' stem: "' + self.stem + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700791 if self.has_warning and not self.cap_lints and not self.default_srcs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700792 self.write(' // has rustc warnings')
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700793 if self.host_supported and self.device_supported and self.module_type != 'rust_proc_macro':
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800794 self.write(' host_supported: true,')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700795 if not self.defaults:
796 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700797 if not self.default_srcs:
798 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700799 if 'test' in self.crate_types and not self.defaults:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800800 # self.root_pkg can have multiple test modules, with different *_tests[n]
801 # names, but their executables can all be installed under the same _tests
802 # directory. When built from Cargo.toml, all tests should have different
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700803 # file or crate names. So we used (root_pkg + '_tests') name as the
804 # relative_install_path.
805 # However, some package like 'slab' can have non-mergeable tests that
806 # must be separated by different module names. So, here we no longer
807 # emit relative_install_path.
808 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800809 self.write(' test_suites: ["general-tests"],')
810 self.write(' auto_gen_config: true,')
Joel Galensone261a152021-01-12 11:31:53 -0800811 if 'test' in self.crate_types and self.host_supported:
812 self.write(' test_options: {')
813 self.write(' unit_test: true,')
814 self.write(' },')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800815
816 def dump_android_externs(self):
817 """Dump the dependent rlibs and dylibs property."""
818 so_libs = list()
819 rust_libs = ''
820 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$')
821 for lib in self.externs:
822 # normal value of lib: "libc = liblibc-*.rlib"
823 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib"
824 # we should use "libgetrandom", not "lib" + "getrandom_package"
825 groups = deps_libname.match(lib)
826 if groups is not None:
827 lib_name = groups.group(1)
828 else:
829 lib_name = re.sub(' .*$', '', lib)
Joel Galenson97e414a2021-05-27 09:42:32 -0700830 if lib_name in self.runner.args.dependency_blocklist:
831 continue
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800832 if lib.endswith('.rlib') or lib.endswith('.rmeta'):
833 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta.
834 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n'
835 elif lib.endswith('.so'):
836 so_libs.append(lib_name)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700837 elif lib != 'proc_macro': # --extern proc_macro is special and ignored
838 rust_libs += ' // ERROR: unknown type of lib ' + lib + '\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800839 if rust_libs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700840 self.write(' rustlibs: [\n' + rust_libs + ' ],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800841 # Are all dependent .so files proc_macros?
842 # TODO(chh): Separate proc_macros and dylib.
843 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs)
844
845
846class ARObject(object):
847 """Information of an "ar" link command."""
848
849 def __init__(self, runner, outf_name):
850 # Remembered global runner and its members.
851 self.runner = runner
852 self.pkg = ''
853 self.outf_name = outf_name # path to Android.bp
854 # "ar" arguments
855 self.line_num = 1
856 self.line = ''
857 self.flags = '' # e.g. "crs"
858 self.lib = '' # e.g. "/.../out/lib*.a"
859 self.objs = list() # e.g. "/.../out/.../*.o"
860
861 def parse(self, pkg, line_num, args_line):
862 """Collect ar obj/lib file names."""
863 self.pkg = pkg
864 self.line_num = line_num
865 self.line = args_line
866 args = args_line.split()
867 num_args = len(args)
868 if num_args < 3:
869 print('ERROR: "ar" command has too few arguments', args_line)
870 else:
871 self.flags = unquote(args[0])
872 self.lib = unquote(args[1])
873 self.objs = sorted(set(map(unquote, args[2:])))
874 return self
875
876 def write(self, s):
877 self.outf.write(s + '\n')
878
879 def dump_debug_info(self):
880 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line)
881 self.write('// ar_object for %12s' % self.pkg)
882 self.write('// flags = %s' % self.flags)
883 self.write('// lib = %s' % short_out_name(self.pkg, self.lib))
884 for o in self.objs:
885 self.write('// obj = %s' % short_out_name(self.pkg, o))
886
887 def dump_android_lib(self):
888 """Write cc_library_static into Android.bp."""
889 self.write('\ncc_library_static {')
890 self.write(' name: "' + file_base_name(self.lib) + '",')
891 self.write(' host_supported: true,')
892 if self.flags != 'crs':
893 self.write(' // ar flags = %s' % self.flags)
894 if self.pkg not in self.runner.pkg_obj2cc:
895 self.write(' ERROR: cannot find source files.\n}')
896 return
897 self.write(' srcs: [')
898 obj2cc = self.runner.pkg_obj2cc[self.pkg]
899 # Note: wflags are ignored.
900 dflags = list()
901 fflags = list()
902 for obj in self.objs:
903 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",')
904 # TODO(chh): union of dflags and flags of all obj
905 # Now, just a temporary hack that uses the last obj's flags
906 dflags = obj2cc[obj].dflags
907 fflags = obj2cc[obj].fflags
908 self.write(' ],')
909 self.write(' cflags: [')
910 self.write(' "-O3",') # TODO(chh): is this default correct?
911 self.write(' "-Wno-error",')
912 for x in fflags:
913 self.write(' "-f' + x + '",')
914 for x in dflags:
915 self.write(' "-D' + x + '",')
916 self.write(' ],')
917 self.write('}')
918
919 def dump(self):
920 """Dump error/debug/module info to the output .bp file."""
921 self.runner.init_bp_file(self.outf_name)
922 with open(self.outf_name, 'a') as outf:
923 self.outf = outf
924 if self.runner.args.debug:
925 self.dump_debug_info()
926 self.dump_android_lib()
927
928
929class CCObject(object):
930 """Information of a "cc" compilation command."""
931
932 def __init__(self, runner, outf_name):
933 # Remembered global runner and its members.
934 self.runner = runner
935 self.pkg = ''
936 self.outf_name = outf_name # path to Android.bp
937 # "cc" arguments
938 self.line_num = 1
939 self.line = ''
940 self.src = ''
941 self.obj = ''
942 self.dflags = list() # -D flags
943 self.fflags = list() # -f flags
944 self.iflags = list() # -I flags
945 self.wflags = list() # -W flags
946 self.other_args = list()
947
948 def parse(self, pkg, line_num, args_line):
949 """Collect cc compilation flags and src/out file names."""
950 self.pkg = pkg
951 self.line_num = line_num
952 self.line = args_line
953 args = args_line.split()
954 i = 0
955 while i < len(args):
956 arg = args[i]
957 if arg == '"-c"':
958 i += 1
959 if args[i].startswith('"-o'):
960 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c"
961 self.obj = unquote(args[i])[2:]
962 i += 1
963 self.src = unquote(args[i])
964 else:
965 self.src = unquote(args[i])
966 elif arg == '"-o"':
967 i += 1
968 self.obj = unquote(args[i])
969 elif arg == '"-I"':
970 i += 1
971 self.iflags.append(unquote(args[i]))
972 elif arg.startswith('"-D'):
973 self.dflags.append(unquote(args[i])[2:])
974 elif arg.startswith('"-f'):
975 self.fflags.append(unquote(args[i])[2:])
976 elif arg.startswith('"-W'):
977 self.wflags.append(unquote(args[i])[2:])
978 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or
979 arg == '"-g3"'):
980 # ignore -O -m64 -g
981 self.other_args.append(unquote(args[i]))
982 i += 1
983 self.dflags = sorted(set(self.dflags))
984 self.fflags = sorted(set(self.fflags))
985 # self.wflags is not sorted because some are order sensitive
986 # and we ignore them anyway.
987 if self.pkg not in self.runner.pkg_obj2cc:
988 self.runner.pkg_obj2cc[self.pkg] = {}
989 self.runner.pkg_obj2cc[self.pkg][self.obj] = self
990 return self
991
992 def write(self, s):
993 self.outf.write(s + '\n')
994
995 def dump_debug_flags(self, name, flags):
996 self.write('// ' + name + ':')
997 for f in flags:
998 self.write('// %s' % f)
999
1000 def dump(self):
1001 """Dump only error/debug info to the output .bp file."""
1002 if not self.runner.args.debug:
1003 return
1004 self.runner.init_bp_file(self.outf_name)
1005 with open(self.outf_name, 'a') as outf:
1006 self.outf = outf
1007 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line)
1008 self.write('// cc_object for %12s' % self.pkg)
1009 self.write('// src = %s' % short_out_name(self.pkg, self.src))
1010 self.write('// obj = %s' % short_out_name(self.pkg, self.obj))
1011 self.dump_debug_flags('-I flags', self.iflags)
1012 self.dump_debug_flags('-D flags', self.dflags)
1013 self.dump_debug_flags('-f flags', self.fflags)
1014 self.dump_debug_flags('-W flags', self.wflags)
1015 if self.other_args:
1016 self.dump_debug_flags('other args', self.other_args)
1017
1018
1019class Runner(object):
1020 """Main class to parse cargo -v output and print Android module definitions."""
1021
1022 def __init__(self, args):
1023 self.bp_files = set() # Remember all output Android.bp files.
1024 self.root_pkg = '' # name of package in ./Cargo.toml
1025 # Saved flags, modes, and data.
1026 self.args = args
1027 self.dry_run = not args.run
1028 self.skip_cargo = args.skipcargo
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001029 self.cargo_path = './cargo' # path to cargo, will be set later
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001030 self.checked_out_files = False # to check only once
1031 self.build_out_files = [] # output files generated by build.rs
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001032 # All cc/ar objects, crates, dependencies, and warning files
1033 self.cc_objects = list()
1034 self.pkg_obj2cc = {}
1035 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i]
1036 self.ar_objects = list()
1037 self.crates = list()
1038 self.dependencies = list() # dependent and build script crates
1039 self.warning_files = set()
1040 # Keep a unique mapping from (module name) to crate
1041 self.name_owners = {}
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001042 # Save and dump all errors from cargo to Android.bp.
1043 self.errors = ''
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001044 self.setup_cargo_path()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001045 # Default action is cargo clean, followed by build or user given actions.
1046 if args.cargo:
1047 self.cargo = ['clean'] + args.cargo
1048 else:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001049 default_target = '--target x86_64-unknown-linux-gnu'
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -07001050 # Use the same target for both host and default device builds.
1051 # Same target is used as default in host x86_64 Android compilation.
1052 # Note: b/169872957, prebuilt cargo failed to build vsock
1053 # on x86_64-unknown-linux-musl systems.
1054 self.cargo = ['clean', 'build ' + default_target]
1055 if args.tests:
1056 self.cargo.append('build --tests ' + default_target)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001057
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001058 def setup_cargo_path(self):
1059 """Find cargo in the --cargo_bin or prebuilt rust bin directory."""
1060 if self.args.cargo_bin:
1061 self.cargo_path = os.path.join(self.args.cargo_bin, 'cargo')
1062 if not os.path.isfile(self.cargo_path):
1063 sys.exit('ERROR: cannot find cargo in ' + self.args.cargo_bin)
1064 print('WARNING: using cargo in ' + self.args.cargo_bin)
1065 return
1066 # We have only tested this on Linux.
1067 if platform.system() != 'Linux':
1068 sys.exit('ERROR: this script has only been tested on Linux with cargo.')
1069 # Assuming that this script is in development/scripts.
1070 my_dir = os.path.dirname(os.path.abspath(__file__))
1071 linux_dir = os.path.join(my_dir, '..', '..',
1072 'prebuilts', 'rust', 'linux-x86')
1073 if not os.path.isdir(linux_dir):
1074 sys.exit('ERROR: cannot find directory ' + linux_dir)
1075 rust_version = self.find_rust_version(my_dir, linux_dir)
1076 cargo_bin = os.path.join(linux_dir, rust_version, 'bin')
1077 self.cargo_path = os.path.join(cargo_bin, 'cargo')
1078 if not os.path.isfile(self.cargo_path):
1079 sys.exit('ERROR: cannot find cargo in ' + cargo_bin
1080 + '; please try --cargo_bin= flag.')
1081 return
1082
1083 def find_rust_version(self, my_dir, linux_dir):
1084 """Use my script directory, find prebuilt rust version."""
1085 # First look up build/soong/rust/config/global.go.
1086 path2global = os.path.join(my_dir, '..', '..',
1087 'build', 'soong', 'rust', 'config', 'global.go')
1088 if os.path.isfile(path2global):
1089 # try to find: RustDefaultVersion = "1.44.0"
1090 version_pat = re.compile(
1091 r'\s*RustDefaultVersion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+)".*$')
1092 with open(path2global, 'r') as inf:
1093 for line in inf:
1094 result = version_pat.match(line)
1095 if result:
1096 return result.group(1)
1097 print('WARNING: cannot find RustDefaultVersion in ' + path2global)
1098 # Otherwise, find the newest (largest) version number in linux_dir.
1099 rust_version = (0, 0, 0) # the prebuilt version to use
1100 version_pat = re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)$')
1101 for dir_name in os.listdir(linux_dir):
1102 result = version_pat.match(dir_name)
1103 if not result:
1104 continue
1105 version = (result.group(1), result.group(2), result.group(3))
1106 if version > rust_version:
1107 rust_version = version
1108 return '.'.join(rust_version)
1109
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001110 def find_out_files(self):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001111 # list1 has build.rs output for normal crates
1112 list1 = glob.glob(TARGET_TMP + '/*/*/build/' + self.root_pkg + '-*/out/*')
1113 # list2 has build.rs output for proc-macro crates
1114 list2 = glob.glob(TARGET_TMP + '/*/build/' + self.root_pkg + '-*/out/*')
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001115 return list1 + list2
1116
1117 def copy_out_files(self):
1118 """Copy build.rs output files to ./out and set up build_out_files."""
1119 if self.checked_out_files:
1120 return
1121 self.checked_out_files = True
1122 cargo_out_files = self.find_out_files()
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001123 out_files = set()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001124 if cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001125 os.makedirs('out', exist_ok=True)
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001126 for path in cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001127 file_name = path.split('/')[-1]
1128 out_files.add(file_name)
1129 shutil.copy(path, 'out/' + file_name)
1130 self.build_out_files = sorted(out_files)
1131
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001132 def has_used_out_dir(self):
1133 """Returns true if env!("OUT_DIR") is found."""
1134 return 0 == os.system('grep -rl --exclude build.rs --include \\*.rs' +
1135 ' \'env!("OUT_DIR")\' * > /dev/null')
1136
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001137 def copy_out_module_name(self):
1138 if self.args.copy_out and self.build_out_files:
1139 return 'copy_' + self.root_pkg + '_build_out'
1140 else:
1141 return ''
1142
Haibo Huang0f72c952021-03-19 11:34:15 -07001143 def read_license(self, name):
1144 if not os.path.isfile(name):
1145 return ''
1146 license = ''
1147 with open(name, 'r') as intf:
1148 line = intf.readline()
1149 # Firstly skip ANDROID_BP_HEADER
1150 while line.startswith('//'):
1151 line = intf.readline()
Joel Galensond9d13b82021-04-05 11:27:55 -07001152 # Read all lines until we see a rust_* or genrule rule.
1153 while line != '' and not (line.startswith('rust_') or line.startswith('genrule {')):
Haibo Huang0f72c952021-03-19 11:34:15 -07001154 license += line
1155 line = intf.readline()
1156 return license.strip()
1157
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001158 def dump_copy_out_module(self, outf):
1159 """Output the genrule module to copy out/* to $(genDir)."""
1160 copy_out = self.copy_out_module_name()
1161 if not copy_out:
1162 return
1163 outf.write('\ngenrule {\n')
1164 outf.write(' name: "' + copy_out + '",\n')
1165 outf.write(' srcs: ["out/*"],\n')
1166 outf.write(' cmd: "cp $(in) $(genDir)",\n')
1167 if len(self.build_out_files) > 1:
1168 outf.write(' out: [\n')
1169 for f in self.build_out_files:
1170 outf.write(' "' + f + '",\n')
1171 outf.write(' ],\n')
1172 else:
1173 outf.write(' out: ["' + self.build_out_files[0] + '"],\n')
1174 outf.write('}\n')
1175
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001176 def init_bp_file(self, name):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001177 # name could be Android.bp or sub_dir_path/Android.bp
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001178 if name not in self.bp_files:
1179 self.bp_files.add(name)
Haibo Huang0f72c952021-03-19 11:34:15 -07001180 license_section = self.read_license(name)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001181 with open(name, 'w') as outf:
Joel Galenson367360c2021-04-29 14:31:43 -07001182 print_args = filter(lambda x: x != "--no-test-mapping", sys.argv[1:])
1183 outf.write(ANDROID_BP_HEADER.format(args=' '.join(print_args)))
Haibo Huang0f72c952021-03-19 11:34:15 -07001184 outf.write('\n')
1185 outf.write(license_section)
1186 outf.write('\n')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001187 # at most one copy_out module per .bp file
1188 self.dump_copy_out_module(outf)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001189
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001190 def try_claim_module_name(self, name, owner):
1191 """Reserve and return True if it has not been reserved yet."""
1192 if name not in self.name_owners or owner == self.name_owners[name]:
1193 self.name_owners[name] = owner
1194 return True
1195 return False
1196
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001197 def claim_module_name(self, prefix, owner, counter):
1198 """Return prefix if not owned yet, otherwise, prefix+str(counter)."""
1199 while True:
1200 name = prefix
1201 if counter > 0:
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001202 name += '_' + str(counter)
1203 if self.try_claim_module_name(name, owner):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001204 return name
1205 counter += 1
1206
1207 def find_root_pkg(self):
1208 """Read name of [package] in ./Cargo.toml."""
1209 if not os.path.exists('./Cargo.toml'):
1210 return
1211 with open('./Cargo.toml', 'r') as inf:
1212 pkg_section = re.compile(r'^ *\[package\]')
1213 name = re.compile('^ *name *= * "([^"]*)"')
1214 in_pkg = False
1215 for line in inf:
1216 if in_pkg:
1217 if name.match(line):
1218 self.root_pkg = name.match(line).group(1)
1219 break
1220 else:
1221 in_pkg = pkg_section.match(line) is not None
1222
1223 def run_cargo(self):
1224 """Calls cargo -v and save its output to ./cargo.out."""
1225 if self.skip_cargo:
1226 return self
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001227 cargo_toml = './Cargo.toml'
1228 cargo_out = './cargo.out'
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001229 # Do not use Cargo.lock, because .bp rules are designed to
1230 # run with "latest" crates avaialable on Android.
1231 cargo_lock = './Cargo.lock'
1232 cargo_lock_saved = './cargo.lock.saved'
1233 had_cargo_lock = os.path.exists(cargo_lock)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001234 if not os.access(cargo_toml, os.R_OK):
1235 print('ERROR: Cannot find or read', cargo_toml)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001236 return self
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001237 if not self.dry_run:
1238 if os.path.exists(cargo_out):
1239 os.remove(cargo_out)
1240 if not self.args.use_cargo_lock and had_cargo_lock: # save it
1241 os.rename(cargo_lock, cargo_lock_saved)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001242 cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> ' + cargo_out + ' 2>&1'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001243 # set up search PATH for cargo to find the correct rustc
1244 saved_path = os.environ['PATH']
1245 os.environ['PATH'] = os.path.dirname(self.cargo_path) + ':' + saved_path
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001246 # Add [workspace] to Cargo.toml if it is not there.
1247 added_workspace = False
1248 if self.args.add_workspace:
1249 with open(cargo_toml, 'r') as in_file:
1250 cargo_toml_lines = in_file.readlines()
1251 found_workspace = '[workspace]\n' in cargo_toml_lines
1252 if found_workspace:
1253 print('### WARNING: found [workspace] in Cargo.toml')
1254 else:
1255 with open(cargo_toml, 'a') as out_file:
1256 out_file.write('[workspace]\n')
1257 added_workspace = True
1258 if self.args.verbose:
1259 print('### INFO: added [workspace] to Cargo.toml')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001260 for c in self.cargo:
1261 features = ''
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001262 if c != 'clean':
1263 if self.args.features is not None:
1264 features = ' --no-default-features'
1265 if self.args.features:
1266 features += ' --features ' + self.args.features
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001267 cmd_v_flag = ' -vv ' if self.args.vv else ' -v '
1268 cmd = self.cargo_path + cmd_v_flag
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001269 cmd += c + features + cmd_tail
1270 if self.args.rustflags and c != 'clean':
1271 cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
1272 if self.dry_run:
1273 print('Dry-run skip:', cmd)
1274 else:
1275 if self.args.verbose:
1276 print('Running:', cmd)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001277 with open(cargo_out, 'a') as out_file:
1278 out_file.write('### Running: ' + cmd + '\n')
Joel Galenson6bf54e32021-05-17 10:54:50 -07001279 ret = os.system(cmd)
1280 if ret != 0:
1281 print('*** There was an error while running cargo. ' +
1282 'See the cargo.out file for details.')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001283 if added_workspace: # restore original Cargo.toml
1284 with open(cargo_toml, 'w') as out_file:
1285 out_file.writelines(cargo_toml_lines)
1286 if self.args.verbose:
1287 print('### INFO: restored original Cargo.toml')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001288 os.environ['PATH'] = saved_path
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001289 if not self.dry_run:
1290 if not had_cargo_lock: # restore to no Cargo.lock state
1291 os.remove(cargo_lock)
1292 elif not self.args.use_cargo_lock: # restore saved Cargo.lock
1293 os.rename(cargo_lock_saved, cargo_lock)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001294 return self
1295
1296 def dump_dependencies(self):
1297 """Append dependencies and their features to Android.bp."""
1298 if not self.dependencies:
1299 return
1300 dependent_list = list()
1301 for c in self.dependencies:
1302 dependent_list.append(c.feature_list())
1303 sorted_dependencies = sorted(set(dependent_list))
1304 self.init_bp_file('Android.bp')
1305 with open('Android.bp', 'a') as outf:
1306 outf.write('\n// dependent_library ["feature_list"]\n')
1307 for s in sorted_dependencies:
1308 outf.write('// ' + s + '\n')
1309
1310 def dump_pkg_obj2cc(self):
1311 """Dump debug info of the pkg_obj2cc map."""
1312 if not self.args.debug:
1313 return
1314 self.init_bp_file('Android.bp')
1315 with open('Android.bp', 'a') as outf:
1316 sorted_pkgs = sorted(self.pkg_obj2cc.keys())
1317 for pkg in sorted_pkgs:
1318 if not self.pkg_obj2cc[pkg]:
1319 continue
1320 outf.write('\n// obj => src for %s\n' % pkg)
1321 obj2cc = self.pkg_obj2cc[pkg]
1322 for obj in sorted(obj2cc.keys()):
1323 outf.write('// ' + short_out_name(pkg, obj) + ' => ' +
1324 short_out_name(pkg, obj2cc[obj].src) + '\n')
1325
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001326 def apply_patch(self):
1327 """Apply local patch file if it is given."""
1328 if self.args.patch:
1329 if self.dry_run:
1330 print('Dry-run skip patch file:', self.args.patch)
1331 else:
1332 if not os.path.exists(self.args.patch):
1333 self.append_to_bp('ERROR cannot find patch file: ' + self.args.patch)
1334 return self
1335 if self.args.verbose:
1336 print('### INFO: applying local patch file:', self.args.patch)
Joel Galenson7e8247e2021-05-20 18:51:42 -07001337 subprocess.run(['patch', '-s', '--no-backup-if-mismatch', './Android.bp',
1338 self.args.patch], check=True)
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001339 return self
1340
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001341 def gen_bp(self):
1342 """Parse cargo.out and generate Android.bp files."""
1343 if self.dry_run:
1344 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp')
1345 elif os.path.exists(CARGO_OUT):
1346 self.find_root_pkg()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001347 if self.args.copy_out:
1348 self.copy_out_files()
1349 elif self.find_out_files() and self.has_used_out_dir():
1350 print('WARNING: ' + self.root_pkg + ' has cargo output files; ' +
1351 'please rerun with the --copy-out flag.')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001352 with open(CARGO_OUT, 'r') as cargo_out:
1353 self.parse(cargo_out, 'Android.bp')
1354 self.crates.sort(key=get_module_name)
1355 for obj in self.cc_objects:
1356 obj.dump()
1357 self.dump_pkg_obj2cc()
1358 for crate in self.crates:
1359 crate.dump()
1360 dumped_libs = set()
1361 for lib in self.ar_objects:
1362 if lib.pkg == self.root_pkg:
1363 lib_name = file_base_name(lib.lib)
1364 if lib_name not in dumped_libs:
1365 dumped_libs.add(lib_name)
1366 lib.dump()
Joel Galenson5664f2a2021-06-10 10:13:49 -07001367 if self.args.add_toplevel_block:
1368 with open(self.args.add_toplevel_block, 'r') as f:
1369 self.append_to_bp('\n' + f.read() + '\n')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001370 if self.args.dependencies and self.dependencies:
1371 self.dump_dependencies()
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001372 if self.errors:
Joel Galenson3f42f802021-04-07 12:42:17 -07001373 self.append_to_bp('\n' + ERRORS_LINE + '\n' + self.errors)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001374 return self
1375
1376 def add_ar_object(self, obj):
1377 self.ar_objects.append(obj)
1378
1379 def add_cc_object(self, obj):
1380 self.cc_objects.append(obj)
1381
1382 def add_crate(self, crate):
1383 """Merge crate with someone in crates, or append to it. Return crates."""
1384 if crate.skip_crate():
1385 if self.args.debug: # include debug info of all crates
1386 self.crates.append(crate)
1387 if self.args.dependencies: # include only dependent crates
1388 if (is_dependent_file_path(crate.main_src) and
1389 not is_build_crate_name(crate.crate_name)):
1390 self.dependencies.append(crate)
1391 else:
1392 for c in self.crates:
1393 if c.merge(crate, 'Android.bp'):
1394 return
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001395 # If not merged, decide module type and name now.
1396 crate.decide_module_type()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001397 self.crates.append(crate)
1398
1399 def find_warning_owners(self):
1400 """For each warning file, find its owner crate."""
1401 missing_owner = False
1402 for f in self.warning_files:
1403 cargo_dir = '' # find lowest crate, with longest path
1404 owner = None # owner crate of this warning
1405 for c in self.crates:
1406 if (f.startswith(c.cargo_dir + '/') and
1407 len(cargo_dir) < len(c.cargo_dir)):
1408 cargo_dir = c.cargo_dir
1409 owner = c
1410 if owner:
1411 owner.has_warning = True
1412 else:
1413 missing_owner = True
1414 if missing_owner and os.path.exists('Cargo.toml'):
1415 # owner is the root cargo, with empty cargo_dir
1416 for c in self.crates:
1417 if not c.cargo_dir:
1418 c.has_warning = True
1419
1420 def rustc_command(self, n, rustc_line, line, outf_name):
1421 """Process a rustc command line from cargo -vv output."""
1422 # cargo build -vv output can have multiple lines for a rustc command
1423 # due to '\n' in strings for environment variables.
1424 # strip removes leading spaces and '\n' at the end
1425 new_rustc = (rustc_line.strip() + line) if rustc_line else line
1426 # Use an heuristic to detect the completions of a multi-line command.
1427 # This might fail for some very rare case, but easy to fix manually.
1428 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0:
1429 return new_rustc
1430 if RUSTC_VV_CMD_ARGS.match(new_rustc):
1431 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1)
1432 self.add_crate(Crate(self, outf_name).parse(n, args))
1433 else:
1434 self.assert_empty_vv_line(new_rustc)
1435 return ''
1436
1437 def cc_ar_command(self, n, groups, outf_name):
1438 pkg = groups.group(1)
1439 line = groups.group(3)
1440 if groups.group(2) == 'cc':
1441 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line))
1442 else:
1443 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line))
1444
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001445 def append_to_bp(self, line):
1446 self.init_bp_file('Android.bp')
1447 with open('Android.bp', 'a') as outf:
1448 outf.write(line)
1449
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001450 def assert_empty_vv_line(self, line):
1451 if line: # report error if line is not empty
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001452 self.append_to_bp('ERROR -vv line: ' + line)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001453 return ''
1454
1455 def parse(self, inf, outf_name):
1456 """Parse rustc and warning messages in inf, return a list of Crates."""
1457 n = 0 # line number
1458 prev_warning = False # true if the previous line was warning: ...
1459 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
1460 for line in inf:
1461 n += 1
1462 if line.startswith('warning: '):
1463 prev_warning = True
1464 rustc_line = self.assert_empty_vv_line(rustc_line)
1465 continue
1466 new_rustc = ''
1467 if RUSTC_PAT.match(line):
1468 args_line = RUSTC_PAT.match(line).group(1)
1469 self.add_crate(Crate(self, outf_name).parse(n, args_line))
1470 self.assert_empty_vv_line(rustc_line)
1471 elif rustc_line or RUSTC_VV_PAT.match(line):
1472 new_rustc = self.rustc_command(n, rustc_line, line, outf_name)
1473 elif CC_AR_VV_PAT.match(line):
1474 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name)
1475 elif prev_warning and WARNING_FILE_PAT.match(line):
1476 self.assert_empty_vv_line(rustc_line)
1477 fpath = WARNING_FILE_PAT.match(line).group(1)
1478 if fpath[0] != '/': # ignore absolute path
1479 self.warning_files.add(fpath)
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001480 elif line.startswith('error: ') or line.startswith('error[E'):
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001481 if not self.args.ignore_cargo_errors:
1482 self.errors += line
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001483 prev_warning = False
1484 rustc_line = new_rustc
1485 self.find_warning_owners()
1486
1487
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001488def get_parser():
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001489 """Parse main arguments."""
1490 parser = argparse.ArgumentParser('cargo2android')
1491 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001492 '--add_workspace',
1493 action='store_true',
1494 default=False,
1495 help=('append [workspace] to Cargo.toml before calling cargo,' +
1496 ' to treat current directory as root of package source;' +
1497 ' otherwise the relative source file path in generated' +
1498 ' .bp file will be from the parent directory.'))
1499 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001500 '--cargo',
1501 action='append',
1502 metavar='args_string',
1503 help=('extra cargo build -v args in a string, ' +
1504 'each --cargo flag calls cargo build -v once'))
1505 parser.add_argument(
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001506 '--cargo_bin',
1507 type=str,
1508 help='use cargo in the cargo_bin directory instead of the prebuilt one')
1509 parser.add_argument(
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001510 '--copy-out',
1511 action='store_true',
1512 default=False,
1513 help=('only for root directory, ' +
1514 'copy build.rs output to ./out/* and add a genrule to copy ' +
1515 './out/* to genrule output; for crates with code pattern: ' +
1516 'include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))'))
1517 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001518 '--debug',
1519 action='store_true',
1520 default=False,
1521 help='dump debug info into Android.bp')
1522 parser.add_argument(
1523 '--dependencies',
1524 action='store_true',
1525 default=False,
1526 help='dump debug info of dependent crates')
1527 parser.add_argument(
1528 '--device',
1529 action='store_true',
1530 default=False,
1531 help='run cargo also for a default device target')
1532 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001533 '--features',
1534 type=str,
1535 help=('pass features to cargo build, ' +
1536 'empty string means no default features'))
1537 parser.add_argument(
1538 '--global_defaults',
1539 type=str,
1540 help='add a defaults name to every module')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001541 parser.add_argument(
1542 '--host-first-multilib',
1543 action='store_true',
1544 default=False,
1545 help=('add a compile_multilib:"first" property ' +
1546 'to Android.bp host modules.'))
1547 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001548 '--ignore-cargo-errors',
1549 action='store_true',
1550 default=False,
1551 help='do not append cargo/rustc error messages to Android.bp')
1552 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001553 '--no-host',
1554 action='store_true',
1555 default=False,
1556 help='do not run cargo for the host; only for the device target')
1557 parser.add_argument(
1558 '--no-subdir',
1559 action='store_true',
1560 default=False,
1561 help='do not output anything for sub-directories')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001562 parser.add_argument(
1563 '--onefile',
1564 action='store_true',
1565 default=False,
1566 help=('output all into one ./Android.bp, default will generate ' +
1567 'one Android.bp per Cargo.toml in subdirectories'))
1568 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001569 '--patch',
1570 type=str,
1571 help='apply the given patch file to generated ./Android.bp')
1572 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001573 '--run',
1574 action='store_true',
1575 default=False,
1576 help='run it, default is dry-run')
1577 parser.add_argument('--rustflags', type=str, help='passing flags to rustc')
1578 parser.add_argument(
1579 '--skipcargo',
1580 action='store_true',
1581 default=False,
1582 help='skip cargo command, parse cargo.out, and generate Android.bp')
1583 parser.add_argument(
1584 '--tests',
1585 action='store_true',
1586 default=False,
1587 help='run cargo build --tests after normal build')
1588 parser.add_argument(
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001589 '--use-cargo-lock',
1590 action='store_true',
1591 default=False,
1592 help=('run cargo build with existing Cargo.lock ' +
1593 '(used when some latest dependent crates failed)'))
1594 parser.add_argument(
Joel Galensond9c4de62021-04-23 10:26:40 -07001595 '--min-sdk-version',
1596 type=str,
1597 help='Minimum SDK version')
1598 parser.add_argument(
1599 '--apex-available',
1600 nargs='*',
1601 help='Mark the main library as apex_available with the given apexes.')
1602 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001603 '--force-rlib',
1604 action='store_true',
1605 default=False,
1606 help='Make the main library an rlib.')
1607 parser.add_argument(
Joel Galenson97e414a2021-05-27 09:42:32 -07001608 '--dependency-blocklist',
1609 nargs='*',
1610 default=[],
1611 help='Do not emit the given dependencies.')
1612 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001613 '--lib-blocklist',
1614 nargs='*',
1615 default=[],
1616 help='Do not emit the given C libraries as dependencies.')
1617 parser.add_argument(
Joel Galensonf6b3c912021-06-03 16:00:54 -07001618 '--test-blocklist',
1619 nargs='*',
1620 default=[],
1621 help=('Do not emit the given tests. ' +
1622 'Pass the path to the test file to exclude.'))
1623 parser.add_argument(
Joel Galenson3d6d1e72021-06-07 15:00:24 -07001624 '--cfg-blocklist',
1625 nargs='*',
1626 default=[],
1627 help='Do not emit the given cfg.')
1628 parser.add_argument(
Joel Galenson5664f2a2021-06-10 10:13:49 -07001629 '--add-toplevel-block',
1630 type=str,
1631 help='Add the contents of the given file to the top level of the Android.bp.')
1632 parser.add_argument(
1633 '--add-module-block',
1634 type=str,
1635 help='Add the contents of the given file to the main module.')
1636 parser.add_argument(
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001637 '--no-test-mapping',
1638 action='store_true',
1639 default=False,
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001640 help='Deprecated. Has no effect.')
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001641 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001642 '--verbose',
1643 action='store_true',
1644 default=False,
1645 help='echo executed commands')
1646 parser.add_argument(
1647 '--vv',
1648 action='store_true',
1649 default=False,
1650 help='run cargo with -vv instead of default -v')
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001651 parser.add_argument(
1652 '--dump-config-and-exit',
1653 type=str,
1654 help=('Dump command-line arguments (minus this flag) to a config file and exit. ' +
1655 'This is intended to help migrate from command line options to config files.'))
1656 parser.add_argument(
1657 '--config',
1658 type=str,
1659 help=('Load command-line options from the given config file. ' +
1660 'Options in this file will override those passed on the command line.'))
1661 return parser
1662
1663
1664def parse_args(parser):
1665 """Parses command-line options."""
1666 args = parser.parse_args()
1667 # Use the values specified in a config file if one was found.
1668 if args.config:
1669 with open(args.config, 'r') as f:
1670 config = json.load(f)
1671 args_dict = vars(args)
1672 for arg in config:
1673 args_dict[arg.replace('-', '_')] = config[arg]
1674 return args
1675
1676
1677def dump_config(parser, args):
1678 """Writes the non-default command-line options to the specified file."""
1679 args_dict = vars(args)
1680 # Filter out the arguments that have their default value.
Joel Galenson367360c2021-04-29 14:31:43 -07001681 # Also filter certain "temporary" arguments.
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001682 non_default_args = {}
1683 for arg in args_dict:
Joel Galenson367360c2021-04-29 14:31:43 -07001684 if args_dict[arg] != parser.get_default(
1685 arg) and arg != 'dump_config_and_exit' and arg != 'no_test_mapping':
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001686 non_default_args[arg.replace('_', '-')] = args_dict[arg]
1687 # Write to the specified file.
1688 with open(args.dump_config_and_exit, 'w') as f:
1689 json.dump(non_default_args, f, indent=2, sort_keys=True)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001690
1691
1692def main():
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001693 parser = get_parser()
1694 args = parse_args(parser)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001695 if not args.run: # default is dry-run
1696 print(DRY_RUN_NOTE)
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001697 if args.dump_config_and_exit:
1698 dump_config(parser, args)
1699 else:
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001700 Runner(args).run_cargo().gen_bp().apply_patch()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001701
1702
1703if __name__ == '__main__':
1704 main()