blob: 1c01fc80e95bda4e12dd9e0c395bafab0f662e14 [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',
Jooyung Hana427c9b2021-07-16 08:53:14 +090075 'libxml': 'libxml_rust',
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -070076 'protoc_gen_rust': 'protoc-gen-rust',
77}
78
79RENAME_STEM_MAP = {
80 # This map includes all changes to the default rust module stem names,
81 # which is used for output files when different from the module name.
82 'protoc_gen_rust': 'protoc-gen-rust',
83}
84
85RENAME_DEFAULTS_MAP = {
86 # This map includes all changes to the default prefix of rust_default
87 # module names, to avoid conflict with existing Android modules.
88 'libc': 'rust_libc',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080089}
90
91# Header added to all generated Android.bp files.
Joel Galenson56446742021-02-18 08:27:48 -080092ANDROID_BP_HEADER = (
93 '// This file is generated by cargo2android.py {args}.\n' +
94 '// Do not modify this file as changes will be overridden on upgrade.\n')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080095
96CARGO_OUT = 'cargo.out' # Name of file to keep cargo build -v output.
97
Joel Galenson3f42f802021-04-07 12:42:17 -070098# This should be kept in sync with tools/external_updater/crates_updater.py.
99ERRORS_LINE = 'Errors in ' + CARGO_OUT + ':'
100
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800101TARGET_TMP = 'target.tmp' # Name of temporary output directory.
102
103# Message to be displayed when this script is called without the --run flag.
104DRY_RUN_NOTE = (
105 'Dry-run: This script uses ./' + TARGET_TMP + ' for output directory,\n' +
106 'runs cargo clean, runs cargo build -v, saves output to ./cargo.out,\n' +
107 'and writes to Android.bp in the current and subdirectories.\n\n' +
108 'To do do all of the above, use the --run flag.\n' +
109 'See --help for other flags, and more usage notes in this script.\n')
110
111# Cargo -v output of a call to rustc.
112RUSTC_PAT = re.compile('^ +Running `rustc (.*)`$')
113
114# Cargo -vv output of a call to rustc could be split into multiple lines.
115# Assume that the first line will contain some CARGO_* env definition.
116RUSTC_VV_PAT = re.compile('^ +Running `.*CARGO_.*=.*$')
117# The combined -vv output rustc command line pattern.
118RUSTC_VV_CMD_ARGS = re.compile('^ *Running `.*CARGO_.*=.* rustc (.*)`$')
119
120# Cargo -vv output of a "cc" or "ar" command; all in one line.
121CC_AR_VV_PAT = re.compile(r'^\[([^ ]*)[^\]]*\] running:? "(cc|ar)" (.*)$')
122# Some package, such as ring-0.13.5, has pattern '... running "cc"'.
123
124# Rustc output of file location path pattern for a warning message.
125WARNING_FILE_PAT = re.compile('^ *--> ([^:]*):[0-9]+')
126
127# Rust package name with suffix -d1.d2.d3.
128VERSION_SUFFIX_PAT = re.compile(r'^(.*)-[0-9]+\.[0-9]+\.[0-9]+$')
129
130
131def altered_name(name):
132 return RENAME_MAP[name] if (name in RENAME_MAP) else name
133
134
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700135def altered_stem(name):
136 return RENAME_STEM_MAP[name] if (name in RENAME_STEM_MAP) else name
137
138
139def altered_defaults(name):
140 return RENAME_DEFAULTS_MAP[name] if (name in RENAME_DEFAULTS_MAP) else name
141
142
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800143def is_build_crate_name(name):
144 # We added special prefix to build script crate names.
145 return name.startswith('build_script_')
146
147
148def is_dependent_file_path(path):
149 # Absolute or dependent '.../' paths are not main files of this crate.
150 return path.startswith('/') or path.startswith('.../')
151
152
153def get_module_name(crate): # to sort crates in a list
154 return crate.module_name
155
156
157def pkg2crate_name(s):
158 return s.replace('-', '_').replace('.', '_')
159
160
161def file_base_name(path):
162 return os.path.splitext(os.path.basename(path))[0]
163
164
165def test_base_name(path):
166 return pkg2crate_name(file_base_name(path))
167
168
169def unquote(s): # remove quotes around str
170 if s and len(s) > 1 and s[0] == '"' and s[-1] == '"':
171 return s[1:-1]
172 return s
173
174
175def remove_version_suffix(s): # remove -d1.d2.d3 suffix
176 if VERSION_SUFFIX_PAT.match(s):
177 return VERSION_SUFFIX_PAT.match(s).group(1)
178 return s
179
180
181def short_out_name(pkg, s): # replace /.../pkg-*/out/* with .../out/*
182 return re.sub('^/.*/' + pkg + '-[0-9a-f]*/out/', '.../out/', s)
183
184
185def escape_quotes(s): # replace '"' with '\\"'
186 return s.replace('"', '\\"')
187
188
189class Crate(object):
190 """Information of a Rust crate to collect/emit for an Android.bp module."""
191
192 def __init__(self, runner, outf_name):
193 # Remembered global runner and its members.
194 self.runner = runner
195 self.debug = runner.args.debug
196 self.cargo_dir = '' # directory of my Cargo.toml
197 self.outf_name = outf_name # path to Android.bp
198 self.outf = None # open file handle of outf_name during dump*
199 # Variants/results that could be merged from multiple rustc lines.
200 self.host_supported = False
201 self.device_supported = False
202 self.has_warning = False
203 # Android module properties derived from rustc parameters.
204 self.module_name = '' # unique in Android build system
205 self.module_type = '' # rust_{binary,library,test}[_host] etc.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700206 self.defaults = '' # rust_defaults used by rust_test* modules
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700207 self.default_srcs = False # use 'srcs' defined in self.defaults
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800208 self.root_pkg = '' # parent package name of a sub/test packge, from -L
209 self.srcs = list() # main_src or merged multiple source files
210 self.stem = '' # real base name of output file
211 # Kept parsed status
212 self.errors = '' # all errors found during parsing
213 self.line_num = 1 # runner told input source line number
214 self.line = '' # original rustc command line parameters
215 # Parameters collected from rustc command line.
216 self.crate_name = '' # follows --crate-name
217 self.main_src = '' # follows crate_name parameter, shortened
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700218 self.crate_types = list() # follows --crate-type
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800219 self.cfgs = list() # follows --cfg, without feature= prefix
220 self.features = list() # follows --cfg, name in 'feature="..."'
221 self.codegens = list() # follows -C, some ignored
222 self.externs = list() # follows --extern
223 self.core_externs = list() # first part of self.externs elements
224 self.static_libs = list() # e.g. -l static=host_cpuid
225 self.shared_libs = list() # e.g. -l dylib=wayland-client, -l z
226 self.cap_lints = '' # follows --cap-lints
227 self.emit_list = '' # e.g., --emit=dep-info,metadata,link
228 self.edition = '2015' # rustc default, e.g., --edition=2018
229 self.target = '' # follows --target
Ivan Lozanocc660f12021-08-11 16:49:46 -0400230 self.cargo_env_compat = True
231 self.cargo_pkg_version = '' # value extracted from Cargo.toml version field
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800232
233 def write(self, s):
234 # convenient way to output one line at a time with EOL.
235 self.outf.write(s + '\n')
236
237 def same_flags(self, other):
238 # host_supported, device_supported, has_warning are not compared but merged
239 # target is not compared, to merge different target/host modules
240 # externs is not compared; only core_externs is compared
241 return (not self.errors and not other.errors and
242 self.edition == other.edition and
243 self.cap_lints == other.cap_lints and
244 self.emit_list == other.emit_list and
245 self.core_externs == other.core_externs and
246 self.codegens == other.codegens and
247 self.features == other.features and
248 self.static_libs == other.static_libs and
249 self.shared_libs == other.shared_libs and self.cfgs == other.cfgs)
250
251 def merge_host_device(self, other):
252 """Returns true if attributes are the same except host/device support."""
253 return (self.crate_name == other.crate_name and
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700254 self.crate_types == other.crate_types and
255 self.main_src == other.main_src and
256 # before merge, each test module has an unique module name and stem
257 (self.stem == other.stem or self.crate_types == ['test']) and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800258 self.root_pkg == other.root_pkg and not self.skip_crate() and
259 self.same_flags(other))
260
261 def merge_test(self, other):
262 """Returns true if self and other are tests of same root_pkg."""
263 # Before merger, each test has its own crate_name.
264 # A merged test uses its source file base name as output file name,
265 # so a test is mergeable only if its base name equals to its crate name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700266 return (self.crate_types == other.crate_types and
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700267 self.crate_types == ['test'] and self.root_pkg == other.root_pkg and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800268 not self.skip_crate() and
269 other.crate_name == test_base_name(other.main_src) and
270 (len(self.srcs) > 1 or
271 (self.crate_name == test_base_name(self.main_src)) and
272 self.host_supported == other.host_supported and
273 self.device_supported == other.device_supported) and
274 self.same_flags(other))
275
276 def merge(self, other, outf_name):
277 """Try to merge crate into self."""
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700278 # Cargo build --tests could recompile a library for tests.
279 # We need to merge such duplicated calls to rustc, with
280 # the algorithm in merge_host_device.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800281 should_merge_host_device = self.merge_host_device(other)
282 should_merge_test = False
283 if not should_merge_host_device:
284 should_merge_test = self.merge_test(other)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800285 if should_merge_host_device or should_merge_test:
286 self.runner.init_bp_file(outf_name)
287 with open(outf_name, 'a') as outf: # to write debug info
288 self.outf = outf
289 other.outf = outf
290 self.do_merge(other, should_merge_test)
291 return True
292 return False
293
294 def do_merge(self, other, should_merge_test):
295 """Merge attributes of other to self."""
296 if self.debug:
297 self.write('\n// Before merge definition (1):')
298 self.dump_debug_info()
299 self.write('\n// Before merge definition (2):')
300 other.dump_debug_info()
301 # Merge properties of other to self.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800302 self.has_warning = self.has_warning or other.has_warning
303 if not self.target: # okay to keep only the first target triple
304 self.target = other.target
305 # decide_module_type sets up default self.stem,
306 # which can be changed if self is a merged test module.
307 self.decide_module_type()
308 if should_merge_test:
Joel Galenson57fa23a2021-07-15 10:47:35 -0700309 if (self.main_src in self.runner.args.test_blocklist and
310 not other.main_src in self.runner.args.test_blocklist):
311 self.main_src = other.main_src
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800312 self.srcs.append(other.main_src)
313 # use a short unique name as the merged module name.
314 prefix = self.root_pkg + '_tests'
315 self.module_name = self.runner.claim_module_name(prefix, self, 0)
316 self.stem = self.module_name
317 # This normalized root_pkg name although might be the same
318 # as other module's crate_name, it is not actually used for
319 # output file name. A merged test module always have multiple
320 # source files and each source file base name is used as
321 # its output file name.
322 self.crate_name = pkg2crate_name(self.root_pkg)
323 if self.debug:
324 self.write('\n// After merge definition (1):')
325 self.dump_debug_info()
326
327 def find_cargo_dir(self):
328 """Deepest directory with Cargo.toml and contains the main_src."""
329 if not is_dependent_file_path(self.main_src):
330 dir_name = os.path.dirname(self.main_src)
331 while dir_name:
332 if os.path.exists(dir_name + '/Cargo.toml'):
333 self.cargo_dir = dir_name
334 return
335 dir_name = os.path.dirname(dir_name)
336
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700337 def add_codegens_flag(self, flag):
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700338 """Ignore options not used in Android."""
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700339 # 'prefer-dynamic' does not work with common flag -C lto
Chih-Hung Hsieh63459ed2020-08-26 11:51:15 -0700340 # 'embed-bitcode' is ignored; we might control LTO with other .bp flag
Chih-Hung Hsieh6c13b722020-09-11 21:24:03 -0700341 # 'codegen-units' is set in Android global config or by default
342 if not (flag.startswith('codegen-units=') or
343 flag.startswith('debuginfo=') or
Chih-Hung Hsieh63459ed2020-08-26 11:51:15 -0700344 flag.startswith('embed-bitcode=') or
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700345 flag.startswith('extra-filename=') or
346 flag.startswith('incremental=') or
347 flag.startswith('metadata=') or
348 flag == 'prefer-dynamic'):
349 self.codegens.append(flag)
350
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800351 def parse(self, line_num, line):
352 """Find important rustc arguments to convert to Android.bp properties."""
353 self.line_num = line_num
354 self.line = line
355 args = line.split() # Loop through every argument of rustc.
356 i = 0
357 while i < len(args):
358 arg = args[i]
359 if arg == '--crate-name':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700360 i += 1
361 self.crate_name = args[i]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800362 elif arg == '--crate-type':
363 i += 1
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700364 # cargo calls rustc with multiple --crate-type flags.
365 # rustc can accept:
366 # --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
367 self.crate_types.append(args[i])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800368 elif arg == '--test':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700369 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800370 elif arg == '--target':
371 i += 1
372 self.target = args[i]
373 elif arg == '--cfg':
374 i += 1
375 if args[i].startswith('\'feature='):
376 self.features.append(unquote(args[i].replace('\'feature=', '')[:-1]))
377 else:
378 self.cfgs.append(args[i])
379 elif arg == '--extern':
380 i += 1
381 extern_names = re.sub('=/[^ ]*/deps/', ' = ', args[i])
382 self.externs.append(extern_names)
383 self.core_externs.append(re.sub(' = .*', '', extern_names))
384 elif arg == '-C': # codegen options
385 i += 1
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700386 self.add_codegens_flag(args[i])
387 elif arg.startswith('-C'):
388 # cargo has been passing "-C <xyz>" flag to rustc,
389 # but newer cargo could pass '-Cembed-bitcode=no' to rustc.
390 self.add_codegens_flag(arg[2:])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800391 elif arg == '--cap-lints':
392 i += 1
393 self.cap_lints = args[i]
394 elif arg == '-L':
395 i += 1
396 if args[i].startswith('dependency=') and args[i].endswith('/deps'):
397 if '/' + TARGET_TMP + '/' in args[i]:
398 self.root_pkg = re.sub(
399 '^.*/', '', re.sub('/' + TARGET_TMP + '/.*/deps$', '', args[i]))
400 else:
401 self.root_pkg = re.sub('^.*/', '',
402 re.sub('/[^/]+/[^/]+/deps$', '', args[i]))
403 self.root_pkg = remove_version_suffix(self.root_pkg)
404 elif arg == '-l':
405 i += 1
406 if args[i].startswith('static='):
407 self.static_libs.append(re.sub('static=', '', args[i]))
408 elif args[i].startswith('dylib='):
409 self.shared_libs.append(re.sub('dylib=', '', args[i]))
410 else:
411 self.shared_libs.append(args[i])
412 elif arg == '--out-dir' or arg == '--color': # ignored
413 i += 1
414 elif arg.startswith('--error-format=') or arg.startswith('--json='):
415 _ = arg # ignored
416 elif arg.startswith('--emit='):
417 self.emit_list = arg.replace('--emit=', '')
418 elif arg.startswith('--edition='):
419 self.edition = arg.replace('--edition=', '')
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700420 elif not arg.startswith('-'):
421 # shorten imported crate main source paths like $HOME/.cargo/
422 # registry/src/github.com-1ecc6299db9ec823/memchr-2.3.3/src/lib.rs
423 self.main_src = re.sub(r'^/[^ ]*/registry/src/', '.../', args[i])
424 self.main_src = re.sub(r'^\.\.\./github.com-[0-9a-f]*/', '.../',
425 self.main_src)
426 self.find_cargo_dir()
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700427 if self.cargo_dir: # for a subdirectory
428 if self.runner.args.no_subdir: # all .bp content to /dev/null
429 self.outf_name = '/dev/null'
430 elif not self.runner.args.onefile:
431 # Write to Android.bp in the subdirectory with Cargo.toml.
432 self.outf_name = self.cargo_dir + '/Android.bp'
433 self.main_src = self.main_src[len(self.cargo_dir) + 1:]
Ivan Lozanocc660f12021-08-11 16:49:46 -0400434
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800435 else:
436 self.errors += 'ERROR: unknown ' + arg + '\n'
437 i += 1
438 if not self.crate_name:
439 self.errors += 'ERROR: missing --crate-name\n'
440 if not self.main_src:
441 self.errors += 'ERROR: missing main source file\n'
442 else:
443 self.srcs.append(self.main_src)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700444 if not self.crate_types:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800445 # Treat "--cfg test" as "--test"
446 if 'test' in self.cfgs:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700447 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800448 else:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700449 self.errors += 'ERROR: missing --crate-type or --test\n'
450 elif len(self.crate_types) > 1:
451 if 'test' in self.crate_types:
452 self.errors += 'ERROR: cannot handle both --crate-type and --test\n'
453 if 'lib' in self.crate_types and 'rlib' in self.crate_types:
454 self.errors += 'ERROR: cannot generate both lib and rlib crate types\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800455 if not self.root_pkg:
456 self.root_pkg = self.crate_name
Ivan Lozano26aa1c32021-08-16 11:20:32 -0400457
458 # get the package version from running cargo metadata
459 if not self.runner.args.no_pkg_vers:
460 self.get_pkg_version()
461
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700462 self.device_supported = self.runner.args.device
463 self.host_supported = not self.runner.args.no_host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800464 self.cfgs = sorted(set(self.cfgs))
465 self.features = sorted(set(self.features))
466 self.codegens = sorted(set(self.codegens))
467 self.externs = sorted(set(self.externs))
468 self.core_externs = sorted(set(self.core_externs))
469 self.static_libs = sorted(set(self.static_libs))
470 self.shared_libs = sorted(set(self.shared_libs))
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700471 self.crate_types = sorted(set(self.crate_types))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800472 self.decide_module_type()
473 self.module_name = altered_name(self.stem)
474 return self
475
Ivan Lozano26aa1c32021-08-16 11:20:32 -0400476 def get_pkg_version(self):
477 """Attempt to retrieve the package version from the Cargo.toml
478
479 If there is only one package, use its version. Otherwise, try to
480 match the emitted `--crate_name` arg against the package name.
481
482 This may fail in cases where multiple packages are defined (workspaces)
483 and where the package name does not match the emitted crate_name
484 (e.g. [lib.name] is set).
485 """
486 cargo_metadata = subprocess.run(['cargo', 'metadata', '--no-deps'],
487 cwd=os.path.abspath(self.cargo_dir), capture_output=True)
488 if cargo_metadata.returncode:
489 self.errors += ('ERROR: unable to get cargo metadata for package version; ' +
490 'return code ' + cargo_metadata.returncode + '\n')
491 else:
492 metadata_json = json.loads(cargo_metadata.stdout)
493 if len(metadata_json['packages']) > 1:
494 for package in metadata_json['packages']:
495 # package names may contain '-', but is changed to '_' in the crate_name
496 if package['name'].replace('-','_') == self.crate_name:
497 self.cargo_pkg_version = package['version']
498 break
499 else:
500 self.cargo_pkg_version = metadata_json['packages'][0]['version']
501
502 if not self.cargo_pkg_version:
503 self.errors += ('ERROR: Unable to retrieve package version; ' +
504 'to disable, run with arg "--no-pkg-vers"\n')
505
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800506 def dump_line(self):
507 self.write('\n// Line ' + str(self.line_num) + ' ' + self.line)
508
509 def feature_list(self):
510 """Return a string of main_src + "feature_list"."""
511 pkg = self.main_src
512 if pkg.startswith('.../'): # keep only the main package name
513 pkg = re.sub('/.*', '', pkg[4:])
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700514 elif pkg.startswith('/'): # use relative path for a local package
515 pkg = os.path.relpath(pkg)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800516 if not self.features:
517 return pkg
518 return pkg + ' "' + ','.join(self.features) + '"'
519
520 def dump_skip_crate(self, kind):
521 if self.debug:
522 self.write('\n// IGNORED: ' + kind + ' ' + self.main_src)
523 return self
524
525 def skip_crate(self):
526 """Return crate_name or a message if this crate should be skipped."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700527 if (is_build_crate_name(self.crate_name) or
528 self.crate_name in EXCLUDED_CRATES):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800529 return self.crate_name
530 if is_dependent_file_path(self.main_src):
531 return 'dependent crate'
532 return ''
533
534 def dump(self):
535 """Dump all error/debug/module code to the output .bp file."""
536 self.runner.init_bp_file(self.outf_name)
537 with open(self.outf_name, 'a') as outf:
538 self.outf = outf
539 if self.errors:
540 self.dump_line()
541 self.write(self.errors)
542 elif self.skip_crate():
543 self.dump_skip_crate(self.skip_crate())
544 else:
545 if self.debug:
546 self.dump_debug_info()
547 self.dump_android_module()
548
549 def dump_debug_info(self):
550 """Dump parsed data, when cargo2android is called with --debug."""
551
552 def dump(name, value):
553 self.write('//%12s = %s' % (name, value))
554
555 def opt_dump(name, value):
556 if value:
557 dump(name, value)
558
559 def dump_list(fmt, values):
560 for v in values:
561 self.write(fmt % v)
562
563 self.dump_line()
564 dump('module_name', self.module_name)
565 dump('crate_name', self.crate_name)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700566 dump('crate_types', self.crate_types)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800567 dump('main_src', self.main_src)
568 dump('has_warning', self.has_warning)
569 dump('for_host', self.host_supported)
570 dump('for_device', self.device_supported)
571 dump('module_type', self.module_type)
572 opt_dump('target', self.target)
573 opt_dump('edition', self.edition)
574 opt_dump('emit_list', self.emit_list)
575 opt_dump('cap_lints', self.cap_lints)
576 dump_list('// cfg = %s', self.cfgs)
577 dump_list('// cfg = \'feature "%s"\'', self.features)
578 # TODO(chh): escape quotes in self.features, but not in other dump_list
579 dump_list('// codegen = %s', self.codegens)
580 dump_list('// externs = %s', self.externs)
581 dump_list('// -l static = %s', self.static_libs)
582 dump_list('// -l (dylib) = %s', self.shared_libs)
583
584 def dump_android_module(self):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700585 """Dump one or more Android module definition, depending on crate_types."""
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700586 if len(self.crate_types) == 1:
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700587 self.dump_single_type_android_module()
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700588 return
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700589 if 'test' in self.crate_types:
590 self.write('\nERROR: multiple crate types cannot include test type')
591 return
592 # Dump one Android module per crate_type.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700593 for crate_type in self.crate_types:
594 self.decide_one_module_type(crate_type)
595 self.dump_one_android_module(crate_type)
596
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700597 def build_default_name(self):
598 """Return a short and readable name for the rust_defaults module."""
Joel Galensond37d7e62021-07-13 09:03:01 -0700599 # Choices: (1) root_pkg + '_test'? + '_defaults',
600 # (2) root_pkg + '_test'? + '_defaults_' + crate_name
601 # (3) root_pkg + '_test'? + '_defaults_' + main_src_basename_path
602 # (4) root_pkg + '_test'? + '_defaults_' + a_positive_sequence_number
603 test = "_test" if self.crate_types == ['test'] else ""
604 name1 = altered_defaults(self.root_pkg) + test + '_defaults'
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700605 if self.runner.try_claim_module_name(name1, self):
606 return name1
607 name2 = name1 + '_' + self.crate_name
608 if self.runner.try_claim_module_name(name2, self):
609 return name2
610 name3 = name1 + '_' + self.main_src_basename_path()
611 if self.runner.try_claim_module_name(name3, self):
612 return name3
613 return self.runner.claim_module_name(name1, self, 0)
614
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700615 def dump_srcs_list(self):
616 """Dump the srcs list, for defaults or regular modules."""
617 if len(self.srcs) > 1:
618 srcs = sorted(set(self.srcs)) # make a copy and dedup
619 else:
620 srcs = [self.main_src]
621 copy_out = self.runner.copy_out_module_name()
622 if copy_out:
623 srcs.append(':' + copy_out)
624 self.dump_android_property_list('srcs', '"%s"', srcs)
625
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700626 def dump_defaults_module(self):
627 """Dump a rust_defaults module to be shared by other modules."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700628 name = self.build_default_name()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700629 self.defaults = name
630 self.write('\nrust_defaults {')
631 self.write(' name: "' + name + '",')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700632 if self.runner.args.global_defaults:
633 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700634 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700635 if len(self.srcs) == 1: # only one source file; share it in defaults
636 self.default_srcs = True
637 if self.has_warning and not self.cap_lints:
638 self.write(' // has rustc warnings')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700639 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700640 if 'test' in self.crate_types:
641 self.write(' test_suites: ["general-tests"],')
642 self.write(' auto_gen_config: true,')
643 self.dump_edition_flags_libs()
Joel Galensone4f53882021-07-19 11:14:55 -0700644 if 'test' in self.crate_types and len(self.srcs) == 1:
645 self.dump_test_data()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700646 self.write('}')
647
648 def dump_single_type_android_module(self):
649 """Dump one simple Android module, which has only one crate_type."""
650 crate_type = self.crate_types[0]
651 if crate_type != 'test':
652 # do not change self.stem or self.module_name
653 self.dump_one_android_module(crate_type)
654 return
655 # Dump one test module per source file, and separate host and device tests.
656 # crate_type == 'test'
Joel Galensonf6b3c912021-06-03 16:00:54 -0700657 self.srcs = [src for src in self.srcs if not src in self.runner.args.test_blocklist]
658 if ((self.host_supported and self.device_supported and len(self.srcs) > 0) or
659 len(self.srcs) > 1):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700660 self.srcs = sorted(set(self.srcs))
661 self.dump_defaults_module()
662 saved_srcs = self.srcs
663 for src in saved_srcs:
664 self.srcs = [src]
665 saved_device_supported = self.device_supported
666 saved_host_supported = self.host_supported
667 saved_main_src = self.main_src
668 self.main_src = src
669 if saved_host_supported:
670 self.device_supported = False
671 self.host_supported = True
672 self.module_name = self.test_module_name()
673 self.decide_one_module_type(crate_type)
674 self.dump_one_android_module(crate_type)
675 if saved_device_supported:
676 self.device_supported = True
677 self.host_supported = False
678 self.module_name = self.test_module_name()
679 self.decide_one_module_type(crate_type)
680 self.dump_one_android_module(crate_type)
681 self.host_supported = saved_host_supported
682 self.device_supported = saved_device_supported
683 self.main_src = saved_main_src
684 self.srcs = saved_srcs
685
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700686 def dump_one_android_module(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800687 """Dump one Android module definition."""
688 if not self.module_type:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700689 self.write('\nERROR: unknown crate_type ' + crate_type)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800690 return
691 self.write('\n' + self.module_type + ' {')
692 self.dump_android_core_properties()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700693 if not self.defaults:
694 self.dump_edition_flags_libs()
695 if self.runner.args.host_first_multilib and self.host_supported and crate_type != 'test':
696 self.write(' compile_multilib: "first",')
Joel Galensond9c4de62021-04-23 10:26:40 -0700697 if self.runner.args.apex_available and crate_type == 'lib':
698 self.write(' apex_available: [')
699 for apex in self.runner.args.apex_available:
700 self.write(' "%s",' % apex)
701 self.write(' ],')
Ivan Lozano91920862021-07-19 10:49:08 -0400702 if self.runner.args.vendor_available:
703 self.write(' vendor_available: true,')
704 if self.runner.args.vendor_ramdisk_available:
705 self.write(' vendor_ramdisk_available: true,')
Joel Galensond9c4de62021-04-23 10:26:40 -0700706 if self.runner.args.min_sdk_version and crate_type == 'lib':
707 self.write(' min_sdk_version: "%s",' % self.runner.args.min_sdk_version)
Joel Galensone4f53882021-07-19 11:14:55 -0700708 if crate_type == 'test' and not self.default_srcs:
709 self.dump_test_data()
Joel Galenson5664f2a2021-06-10 10:13:49 -0700710 if self.runner.args.add_module_block:
711 with open(self.runner.args.add_module_block, 'r') as f:
712 self.write(' %s,' % f.read().replace('\n', '\n '))
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700713 self.write('}')
714
715 def dump_android_flags(self):
716 """Dump Android module flags property."""
ThiƩbaud Weksteena5a728b2021-04-08 14:23:49 +0200717 if not self.codegens and not self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700718 return
719 self.write(' flags: [')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800720 if self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700721 self.write(' "--cap-lints ' + self.cap_lints + '",')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700722 codegens_fmt = '"-C %s"'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700723 self.dump_android_property_list_items(codegens_fmt, self.codegens)
724 self.write(' ],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700725
726 def dump_edition_flags_libs(self):
727 if self.edition:
728 self.write(' edition: "' + self.edition + '",')
729 self.dump_android_property_list('features', '"%s"', self.features)
Joel Galenson3d6d1e72021-06-07 15:00:24 -0700730 cfgs = [cfg for cfg in self.cfgs if not cfg in self.runner.args.cfg_blocklist]
731 self.dump_android_property_list('cfgs', '"%s"', cfgs)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700732 self.dump_android_flags()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800733 if self.externs:
734 self.dump_android_externs()
Joel Galenson12467e52021-07-12 14:33:28 -0700735 all_static_libs = [lib for lib in self.static_libs if not lib in self.runner.args.lib_blocklist]
736 static_libs = [lib for lib in all_static_libs if not lib in self.runner.args.whole_static_libs]
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700737 self.dump_android_property_list('static_libs', '"lib%s"', static_libs)
Joel Galenson12467e52021-07-12 14:33:28 -0700738 whole_static_libs = [lib for lib in all_static_libs if lib in self.runner.args.whole_static_libs]
739 self.dump_android_property_list('whole_static_libs', '"lib%s"', whole_static_libs)
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700740 shared_libs = [lib for lib in self.shared_libs if not lib in self.runner.args.lib_blocklist]
741 self.dump_android_property_list('shared_libs', '"lib%s"', shared_libs)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800742
Joel Galensone4f53882021-07-19 11:14:55 -0700743 def dump_test_data(self):
744 data = [data for (name, data) in map(lambda kv: kv.split('=', 1), self.runner.args.test_data)
745 if self.srcs == [name]]
746 if data:
747 self.dump_android_property_list('data', '"%s"', data)
748
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700749 def main_src_basename_path(self):
750 return re.sub('/', '_', re.sub('.rs$', '', self.main_src))
751
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800752 def test_module_name(self):
753 """Return a unique name for a test module."""
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700754 # root_pkg+(_host|_device) + '_test_'+source_file_name
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700755 suffix = self.main_src_basename_path()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700756 host_device = '_host'
757 if self.device_supported:
758 host_device = '_device'
759 return self.root_pkg + host_device + '_test_' + suffix
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800760
761 def decide_module_type(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700762 # Use the first crate type for the default/first module.
763 crate_type = self.crate_types[0] if self.crate_types else ''
764 self.decide_one_module_type(crate_type)
765
766 def decide_one_module_type(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800767 """Decide which Android module type to use."""
768 host = '' if self.device_supported else '_host'
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700769 rlib = '_rlib' if self.runner.args.force_rlib else ''
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700770 if crate_type == 'bin': # rust_binary[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800771 self.module_type = 'rust_binary' + host
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700772 # In rare cases like protobuf-codegen, the output binary name must
773 # be renamed to use as a plugin for protoc.
774 self.stem = altered_stem(self.crate_name)
775 self.module_name = altered_name(self.crate_name)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700776 elif crate_type == 'lib': # rust_library[_host]
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700777 # TODO(chh): should this be rust_library[_host]?
778 # Assuming that Cargo.toml do not use both 'lib' and 'rlib',
779 # because we map them both to rlib.
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700780 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800781 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700782 self.module_name = altered_name(self.stem)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700783 elif crate_type == 'rlib': # rust_library[_host]
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700784 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700785 self.stem = 'lib' + self.crate_name
786 self.module_name = altered_name(self.stem)
787 elif crate_type == 'dylib': # rust_library[_host]_dylib
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800788 self.module_type = 'rust_library' + host + '_dylib'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700789 self.stem = 'lib' + self.crate_name
790 self.module_name = altered_name(self.stem) + '_dylib'
791 elif crate_type == 'cdylib': # rust_library[_host]_shared
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500792 self.module_type = 'rust_ffi' + host + '_shared'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700793 self.stem = 'lib' + self.crate_name
794 self.module_name = altered_name(self.stem) + '_shared'
795 elif crate_type == 'staticlib': # rust_library[_host]_static
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500796 self.module_type = 'rust_ffi' + host + '_static'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700797 self.stem = 'lib' + self.crate_name
798 self.module_name = altered_name(self.stem) + '_static'
799 elif crate_type == 'test': # rust_test[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800800 self.module_type = 'rust_test' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700801 # Before do_merge, stem name is based on the --crate-name parameter.
802 # and test module name is based on stem.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800803 self.stem = self.test_module_name()
804 # self.stem will be changed after merging with other tests.
805 # self.stem is NOT used for final test binary name.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700806 # rust_test uses each source file base name as part of output file name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700807 # In do_merge, this function is called again, with a module_name.
808 # We make sure that the module name is unique in each package.
809 if self.module_name:
810 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add
811 # different suffixes and distinguish multiple tests of the same
812 # crate name. We ignore -C and use claim_module_name to get
813 # unique sequential suffix.
814 self.module_name = self.runner.claim_module_name(
815 self.module_name, self, 0)
816 # Now the module name is unique, stem should also match and unique.
817 self.stem = self.module_name
818 elif crate_type == 'proc-macro': # rust_proc_macro
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800819 self.module_type = 'rust_proc_macro'
820 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700821 self.module_name = altered_name(self.stem)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800822 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]?
823 self.module_type = ''
824 self.stem = ''
825
826 def dump_android_property_list_items(self, fmt, values):
827 for v in values:
828 # fmt has quotes, so we need escape_quotes(v)
829 self.write(' ' + (fmt % escape_quotes(v)) + ',')
830
831 def dump_android_property_list(self, name, fmt, values):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700832 if not values:
833 return
834 if len(values) > 1:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800835 self.write(' ' + name + ': [')
836 self.dump_android_property_list_items(fmt, values)
837 self.write(' ],')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700838 else:
839 self.write(' ' + name + ': [' +
840 (fmt % escape_quotes(values[0])) + '],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800841
842 def dump_android_core_properties(self):
843 """Dump the module header, name, stem, etc."""
844 self.write(' name: "' + self.module_name + '",')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700845 # see properties shared by dump_defaults_module
846 if self.defaults:
847 self.write(' defaults: ["' + self.defaults + '"],')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700848 elif self.runner.args.global_defaults:
849 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800850 if self.stem != self.module_name:
851 self.write(' stem: "' + self.stem + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700852 if self.has_warning and not self.cap_lints and not self.default_srcs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700853 self.write(' // has rustc warnings')
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700854 if self.host_supported and self.device_supported and self.module_type != 'rust_proc_macro':
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800855 self.write(' host_supported: true,')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700856 if not self.defaults:
857 self.write(' crate_name: "' + self.crate_name + '",')
Ivan Lozanocc660f12021-08-11 16:49:46 -0400858 if not self.defaults and self.cargo_env_compat:
859 self.write(' cargo_env_compat: true,')
860 self.write(' cargo_pkg_version: "' + self.cargo_pkg_version + '",')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700861 if not self.default_srcs:
862 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700863 if 'test' in self.crate_types and not self.defaults:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800864 # self.root_pkg can have multiple test modules, with different *_tests[n]
865 # names, but their executables can all be installed under the same _tests
866 # directory. When built from Cargo.toml, all tests should have different
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700867 # file or crate names. So we used (root_pkg + '_tests') name as the
868 # relative_install_path.
869 # However, some package like 'slab' can have non-mergeable tests that
870 # must be separated by different module names. So, here we no longer
871 # emit relative_install_path.
872 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800873 self.write(' test_suites: ["general-tests"],')
874 self.write(' auto_gen_config: true,')
Joel Galensone261a152021-01-12 11:31:53 -0800875 if 'test' in self.crate_types and self.host_supported:
876 self.write(' test_options: {')
877 self.write(' unit_test: true,')
878 self.write(' },')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800879
880 def dump_android_externs(self):
881 """Dump the dependent rlibs and dylibs property."""
882 so_libs = list()
883 rust_libs = ''
884 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$')
885 for lib in self.externs:
886 # normal value of lib: "libc = liblibc-*.rlib"
887 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib"
888 # we should use "libgetrandom", not "lib" + "getrandom_package"
889 groups = deps_libname.match(lib)
890 if groups is not None:
891 lib_name = groups.group(1)
892 else:
893 lib_name = re.sub(' .*$', '', lib)
Joel Galenson97e414a2021-05-27 09:42:32 -0700894 if lib_name in self.runner.args.dependency_blocklist:
895 continue
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800896 if lib.endswith('.rlib') or lib.endswith('.rmeta'):
897 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta.
898 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n'
899 elif lib.endswith('.so'):
900 so_libs.append(lib_name)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700901 elif lib != 'proc_macro': # --extern proc_macro is special and ignored
902 rust_libs += ' // ERROR: unknown type of lib ' + lib + '\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800903 if rust_libs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700904 self.write(' rustlibs: [\n' + rust_libs + ' ],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800905 # Are all dependent .so files proc_macros?
906 # TODO(chh): Separate proc_macros and dylib.
907 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs)
908
909
910class ARObject(object):
911 """Information of an "ar" link command."""
912
913 def __init__(self, runner, outf_name):
914 # Remembered global runner and its members.
915 self.runner = runner
916 self.pkg = ''
917 self.outf_name = outf_name # path to Android.bp
918 # "ar" arguments
919 self.line_num = 1
920 self.line = ''
921 self.flags = '' # e.g. "crs"
922 self.lib = '' # e.g. "/.../out/lib*.a"
923 self.objs = list() # e.g. "/.../out/.../*.o"
924
925 def parse(self, pkg, line_num, args_line):
926 """Collect ar obj/lib file names."""
927 self.pkg = pkg
928 self.line_num = line_num
929 self.line = args_line
930 args = args_line.split()
931 num_args = len(args)
932 if num_args < 3:
933 print('ERROR: "ar" command has too few arguments', args_line)
934 else:
935 self.flags = unquote(args[0])
936 self.lib = unquote(args[1])
937 self.objs = sorted(set(map(unquote, args[2:])))
938 return self
939
940 def write(self, s):
941 self.outf.write(s + '\n')
942
943 def dump_debug_info(self):
944 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line)
945 self.write('// ar_object for %12s' % self.pkg)
946 self.write('// flags = %s' % self.flags)
947 self.write('// lib = %s' % short_out_name(self.pkg, self.lib))
948 for o in self.objs:
949 self.write('// obj = %s' % short_out_name(self.pkg, o))
950
951 def dump_android_lib(self):
952 """Write cc_library_static into Android.bp."""
953 self.write('\ncc_library_static {')
954 self.write(' name: "' + file_base_name(self.lib) + '",')
955 self.write(' host_supported: true,')
956 if self.flags != 'crs':
957 self.write(' // ar flags = %s' % self.flags)
958 if self.pkg not in self.runner.pkg_obj2cc:
959 self.write(' ERROR: cannot find source files.\n}')
960 return
961 self.write(' srcs: [')
962 obj2cc = self.runner.pkg_obj2cc[self.pkg]
963 # Note: wflags are ignored.
964 dflags = list()
965 fflags = list()
966 for obj in self.objs:
967 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",')
968 # TODO(chh): union of dflags and flags of all obj
969 # Now, just a temporary hack that uses the last obj's flags
970 dflags = obj2cc[obj].dflags
971 fflags = obj2cc[obj].fflags
972 self.write(' ],')
973 self.write(' cflags: [')
974 self.write(' "-O3",') # TODO(chh): is this default correct?
975 self.write(' "-Wno-error",')
976 for x in fflags:
977 self.write(' "-f' + x + '",')
978 for x in dflags:
979 self.write(' "-D' + x + '",')
980 self.write(' ],')
981 self.write('}')
982
983 def dump(self):
984 """Dump error/debug/module info to the output .bp file."""
985 self.runner.init_bp_file(self.outf_name)
986 with open(self.outf_name, 'a') as outf:
987 self.outf = outf
988 if self.runner.args.debug:
989 self.dump_debug_info()
990 self.dump_android_lib()
991
992
993class CCObject(object):
994 """Information of a "cc" compilation command."""
995
996 def __init__(self, runner, outf_name):
997 # Remembered global runner and its members.
998 self.runner = runner
999 self.pkg = ''
1000 self.outf_name = outf_name # path to Android.bp
1001 # "cc" arguments
1002 self.line_num = 1
1003 self.line = ''
1004 self.src = ''
1005 self.obj = ''
1006 self.dflags = list() # -D flags
1007 self.fflags = list() # -f flags
1008 self.iflags = list() # -I flags
1009 self.wflags = list() # -W flags
1010 self.other_args = list()
1011
1012 def parse(self, pkg, line_num, args_line):
1013 """Collect cc compilation flags and src/out file names."""
1014 self.pkg = pkg
1015 self.line_num = line_num
1016 self.line = args_line
1017 args = args_line.split()
1018 i = 0
1019 while i < len(args):
1020 arg = args[i]
1021 if arg == '"-c"':
1022 i += 1
1023 if args[i].startswith('"-o'):
1024 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c"
1025 self.obj = unquote(args[i])[2:]
1026 i += 1
1027 self.src = unquote(args[i])
1028 else:
1029 self.src = unquote(args[i])
1030 elif arg == '"-o"':
1031 i += 1
1032 self.obj = unquote(args[i])
1033 elif arg == '"-I"':
1034 i += 1
1035 self.iflags.append(unquote(args[i]))
1036 elif arg.startswith('"-D'):
1037 self.dflags.append(unquote(args[i])[2:])
1038 elif arg.startswith('"-f'):
1039 self.fflags.append(unquote(args[i])[2:])
1040 elif arg.startswith('"-W'):
1041 self.wflags.append(unquote(args[i])[2:])
1042 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or
1043 arg == '"-g3"'):
1044 # ignore -O -m64 -g
1045 self.other_args.append(unquote(args[i]))
1046 i += 1
1047 self.dflags = sorted(set(self.dflags))
1048 self.fflags = sorted(set(self.fflags))
1049 # self.wflags is not sorted because some are order sensitive
1050 # and we ignore them anyway.
1051 if self.pkg not in self.runner.pkg_obj2cc:
1052 self.runner.pkg_obj2cc[self.pkg] = {}
1053 self.runner.pkg_obj2cc[self.pkg][self.obj] = self
1054 return self
1055
1056 def write(self, s):
1057 self.outf.write(s + '\n')
1058
1059 def dump_debug_flags(self, name, flags):
1060 self.write('// ' + name + ':')
1061 for f in flags:
1062 self.write('// %s' % f)
1063
1064 def dump(self):
1065 """Dump only error/debug info to the output .bp file."""
1066 if not self.runner.args.debug:
1067 return
1068 self.runner.init_bp_file(self.outf_name)
1069 with open(self.outf_name, 'a') as outf:
1070 self.outf = outf
1071 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line)
1072 self.write('// cc_object for %12s' % self.pkg)
1073 self.write('// src = %s' % short_out_name(self.pkg, self.src))
1074 self.write('// obj = %s' % short_out_name(self.pkg, self.obj))
1075 self.dump_debug_flags('-I flags', self.iflags)
1076 self.dump_debug_flags('-D flags', self.dflags)
1077 self.dump_debug_flags('-f flags', self.fflags)
1078 self.dump_debug_flags('-W flags', self.wflags)
1079 if self.other_args:
1080 self.dump_debug_flags('other args', self.other_args)
1081
1082
1083class Runner(object):
1084 """Main class to parse cargo -v output and print Android module definitions."""
1085
1086 def __init__(self, args):
1087 self.bp_files = set() # Remember all output Android.bp files.
1088 self.root_pkg = '' # name of package in ./Cargo.toml
1089 # Saved flags, modes, and data.
1090 self.args = args
1091 self.dry_run = not args.run
1092 self.skip_cargo = args.skipcargo
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001093 self.cargo_path = './cargo' # path to cargo, will be set later
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001094 self.checked_out_files = False # to check only once
1095 self.build_out_files = [] # output files generated by build.rs
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001096 # All cc/ar objects, crates, dependencies, and warning files
1097 self.cc_objects = list()
1098 self.pkg_obj2cc = {}
1099 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i]
1100 self.ar_objects = list()
1101 self.crates = list()
1102 self.dependencies = list() # dependent and build script crates
1103 self.warning_files = set()
1104 # Keep a unique mapping from (module name) to crate
1105 self.name_owners = {}
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001106 # Save and dump all errors from cargo to Android.bp.
1107 self.errors = ''
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001108 self.setup_cargo_path()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001109 # Default action is cargo clean, followed by build or user given actions.
1110 if args.cargo:
1111 self.cargo = ['clean'] + args.cargo
1112 else:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001113 default_target = '--target x86_64-unknown-linux-gnu'
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -07001114 # Use the same target for both host and default device builds.
1115 # Same target is used as default in host x86_64 Android compilation.
1116 # Note: b/169872957, prebuilt cargo failed to build vsock
1117 # on x86_64-unknown-linux-musl systems.
1118 self.cargo = ['clean', 'build ' + default_target]
1119 if args.tests:
1120 self.cargo.append('build --tests ' + default_target)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001121
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001122 def setup_cargo_path(self):
1123 """Find cargo in the --cargo_bin or prebuilt rust bin directory."""
1124 if self.args.cargo_bin:
1125 self.cargo_path = os.path.join(self.args.cargo_bin, 'cargo')
1126 if not os.path.isfile(self.cargo_path):
1127 sys.exit('ERROR: cannot find cargo in ' + self.args.cargo_bin)
1128 print('WARNING: using cargo in ' + self.args.cargo_bin)
1129 return
1130 # We have only tested this on Linux.
1131 if platform.system() != 'Linux':
1132 sys.exit('ERROR: this script has only been tested on Linux with cargo.')
1133 # Assuming that this script is in development/scripts.
1134 my_dir = os.path.dirname(os.path.abspath(__file__))
1135 linux_dir = os.path.join(my_dir, '..', '..',
1136 'prebuilts', 'rust', 'linux-x86')
1137 if not os.path.isdir(linux_dir):
1138 sys.exit('ERROR: cannot find directory ' + linux_dir)
1139 rust_version = self.find_rust_version(my_dir, linux_dir)
1140 cargo_bin = os.path.join(linux_dir, rust_version, 'bin')
1141 self.cargo_path = os.path.join(cargo_bin, 'cargo')
1142 if not os.path.isfile(self.cargo_path):
1143 sys.exit('ERROR: cannot find cargo in ' + cargo_bin
1144 + '; please try --cargo_bin= flag.')
1145 return
1146
1147 def find_rust_version(self, my_dir, linux_dir):
1148 """Use my script directory, find prebuilt rust version."""
1149 # First look up build/soong/rust/config/global.go.
1150 path2global = os.path.join(my_dir, '..', '..',
1151 'build', 'soong', 'rust', 'config', 'global.go')
1152 if os.path.isfile(path2global):
1153 # try to find: RustDefaultVersion = "1.44.0"
1154 version_pat = re.compile(
1155 r'\s*RustDefaultVersion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+)".*$')
1156 with open(path2global, 'r') as inf:
1157 for line in inf:
1158 result = version_pat.match(line)
1159 if result:
1160 return result.group(1)
1161 print('WARNING: cannot find RustDefaultVersion in ' + path2global)
1162 # Otherwise, find the newest (largest) version number in linux_dir.
1163 rust_version = (0, 0, 0) # the prebuilt version to use
1164 version_pat = re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)$')
1165 for dir_name in os.listdir(linux_dir):
1166 result = version_pat.match(dir_name)
1167 if not result:
1168 continue
1169 version = (result.group(1), result.group(2), result.group(3))
1170 if version > rust_version:
1171 rust_version = version
1172 return '.'.join(rust_version)
1173
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001174 def find_out_files(self):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001175 # list1 has build.rs output for normal crates
1176 list1 = glob.glob(TARGET_TMP + '/*/*/build/' + self.root_pkg + '-*/out/*')
1177 # list2 has build.rs output for proc-macro crates
1178 list2 = glob.glob(TARGET_TMP + '/*/build/' + self.root_pkg + '-*/out/*')
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001179 return list1 + list2
1180
1181 def copy_out_files(self):
1182 """Copy build.rs output files to ./out and set up build_out_files."""
1183 if self.checked_out_files:
1184 return
1185 self.checked_out_files = True
1186 cargo_out_files = self.find_out_files()
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001187 out_files = set()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001188 if cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001189 os.makedirs('out', exist_ok=True)
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001190 for path in cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001191 file_name = path.split('/')[-1]
1192 out_files.add(file_name)
1193 shutil.copy(path, 'out/' + file_name)
1194 self.build_out_files = sorted(out_files)
1195
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001196 def has_used_out_dir(self):
1197 """Returns true if env!("OUT_DIR") is found."""
1198 return 0 == os.system('grep -rl --exclude build.rs --include \\*.rs' +
1199 ' \'env!("OUT_DIR")\' * > /dev/null')
1200
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001201 def copy_out_module_name(self):
1202 if self.args.copy_out and self.build_out_files:
1203 return 'copy_' + self.root_pkg + '_build_out'
1204 else:
1205 return ''
1206
Haibo Huang0f72c952021-03-19 11:34:15 -07001207 def read_license(self, name):
1208 if not os.path.isfile(name):
1209 return ''
1210 license = ''
1211 with open(name, 'r') as intf:
1212 line = intf.readline()
1213 # Firstly skip ANDROID_BP_HEADER
1214 while line.startswith('//'):
1215 line = intf.readline()
Joel Galensond9d13b82021-04-05 11:27:55 -07001216 # Read all lines until we see a rust_* or genrule rule.
1217 while line != '' and not (line.startswith('rust_') or line.startswith('genrule {')):
Haibo Huang0f72c952021-03-19 11:34:15 -07001218 license += line
1219 line = intf.readline()
1220 return license.strip()
1221
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001222 def dump_copy_out_module(self, outf):
1223 """Output the genrule module to copy out/* to $(genDir)."""
1224 copy_out = self.copy_out_module_name()
1225 if not copy_out:
1226 return
1227 outf.write('\ngenrule {\n')
1228 outf.write(' name: "' + copy_out + '",\n')
1229 outf.write(' srcs: ["out/*"],\n')
1230 outf.write(' cmd: "cp $(in) $(genDir)",\n')
1231 if len(self.build_out_files) > 1:
1232 outf.write(' out: [\n')
1233 for f in self.build_out_files:
1234 outf.write(' "' + f + '",\n')
1235 outf.write(' ],\n')
1236 else:
1237 outf.write(' out: ["' + self.build_out_files[0] + '"],\n')
1238 outf.write('}\n')
1239
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001240 def init_bp_file(self, name):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001241 # name could be Android.bp or sub_dir_path/Android.bp
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001242 if name not in self.bp_files:
1243 self.bp_files.add(name)
Haibo Huang0f72c952021-03-19 11:34:15 -07001244 license_section = self.read_license(name)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001245 with open(name, 'w') as outf:
Joel Galenson367360c2021-04-29 14:31:43 -07001246 print_args = filter(lambda x: x != "--no-test-mapping", sys.argv[1:])
1247 outf.write(ANDROID_BP_HEADER.format(args=' '.join(print_args)))
Haibo Huang0f72c952021-03-19 11:34:15 -07001248 outf.write('\n')
1249 outf.write(license_section)
1250 outf.write('\n')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001251 # at most one copy_out module per .bp file
1252 self.dump_copy_out_module(outf)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001253
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001254 def try_claim_module_name(self, name, owner):
1255 """Reserve and return True if it has not been reserved yet."""
1256 if name not in self.name_owners or owner == self.name_owners[name]:
1257 self.name_owners[name] = owner
1258 return True
1259 return False
1260
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001261 def claim_module_name(self, prefix, owner, counter):
1262 """Return prefix if not owned yet, otherwise, prefix+str(counter)."""
1263 while True:
1264 name = prefix
1265 if counter > 0:
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001266 name += '_' + str(counter)
1267 if self.try_claim_module_name(name, owner):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001268 return name
1269 counter += 1
1270
1271 def find_root_pkg(self):
1272 """Read name of [package] in ./Cargo.toml."""
1273 if not os.path.exists('./Cargo.toml'):
1274 return
1275 with open('./Cargo.toml', 'r') as inf:
1276 pkg_section = re.compile(r'^ *\[package\]')
1277 name = re.compile('^ *name *= * "([^"]*)"')
1278 in_pkg = False
1279 for line in inf:
1280 if in_pkg:
1281 if name.match(line):
1282 self.root_pkg = name.match(line).group(1)
1283 break
1284 else:
1285 in_pkg = pkg_section.match(line) is not None
1286
1287 def run_cargo(self):
1288 """Calls cargo -v and save its output to ./cargo.out."""
1289 if self.skip_cargo:
1290 return self
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001291 cargo_toml = './Cargo.toml'
1292 cargo_out = './cargo.out'
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001293 # Do not use Cargo.lock, because .bp rules are designed to
1294 # run with "latest" crates avaialable on Android.
1295 cargo_lock = './Cargo.lock'
1296 cargo_lock_saved = './cargo.lock.saved'
1297 had_cargo_lock = os.path.exists(cargo_lock)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001298 if not os.access(cargo_toml, os.R_OK):
1299 print('ERROR: Cannot find or read', cargo_toml)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001300 return self
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001301 if not self.dry_run:
1302 if os.path.exists(cargo_out):
1303 os.remove(cargo_out)
1304 if not self.args.use_cargo_lock and had_cargo_lock: # save it
1305 os.rename(cargo_lock, cargo_lock_saved)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001306 cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> ' + cargo_out + ' 2>&1'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001307 # set up search PATH for cargo to find the correct rustc
1308 saved_path = os.environ['PATH']
1309 os.environ['PATH'] = os.path.dirname(self.cargo_path) + ':' + saved_path
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001310 # Add [workspace] to Cargo.toml if it is not there.
1311 added_workspace = False
1312 if self.args.add_workspace:
1313 with open(cargo_toml, 'r') as in_file:
1314 cargo_toml_lines = in_file.readlines()
1315 found_workspace = '[workspace]\n' in cargo_toml_lines
1316 if found_workspace:
1317 print('### WARNING: found [workspace] in Cargo.toml')
1318 else:
1319 with open(cargo_toml, 'a') as out_file:
1320 out_file.write('[workspace]\n')
1321 added_workspace = True
1322 if self.args.verbose:
1323 print('### INFO: added [workspace] to Cargo.toml')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001324 for c in self.cargo:
1325 features = ''
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001326 if c != 'clean':
1327 if self.args.features is not None:
1328 features = ' --no-default-features'
1329 if self.args.features:
1330 features += ' --features ' + self.args.features
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001331 cmd_v_flag = ' -vv ' if self.args.vv else ' -v '
1332 cmd = self.cargo_path + cmd_v_flag
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001333 cmd += c + features + cmd_tail
1334 if self.args.rustflags and c != 'clean':
1335 cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
1336 if self.dry_run:
1337 print('Dry-run skip:', cmd)
1338 else:
1339 if self.args.verbose:
1340 print('Running:', cmd)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001341 with open(cargo_out, 'a') as out_file:
1342 out_file.write('### Running: ' + cmd + '\n')
Joel Galenson6bf54e32021-05-17 10:54:50 -07001343 ret = os.system(cmd)
1344 if ret != 0:
1345 print('*** There was an error while running cargo. ' +
1346 'See the cargo.out file for details.')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001347 if added_workspace: # restore original Cargo.toml
1348 with open(cargo_toml, 'w') as out_file:
1349 out_file.writelines(cargo_toml_lines)
1350 if self.args.verbose:
1351 print('### INFO: restored original Cargo.toml')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001352 os.environ['PATH'] = saved_path
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001353 if not self.dry_run:
1354 if not had_cargo_lock: # restore to no Cargo.lock state
1355 os.remove(cargo_lock)
1356 elif not self.args.use_cargo_lock: # restore saved Cargo.lock
1357 os.rename(cargo_lock_saved, cargo_lock)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001358 return self
1359
1360 def dump_dependencies(self):
1361 """Append dependencies and their features to Android.bp."""
1362 if not self.dependencies:
1363 return
1364 dependent_list = list()
1365 for c in self.dependencies:
1366 dependent_list.append(c.feature_list())
1367 sorted_dependencies = sorted(set(dependent_list))
1368 self.init_bp_file('Android.bp')
1369 with open('Android.bp', 'a') as outf:
1370 outf.write('\n// dependent_library ["feature_list"]\n')
1371 for s in sorted_dependencies:
1372 outf.write('// ' + s + '\n')
1373
1374 def dump_pkg_obj2cc(self):
1375 """Dump debug info of the pkg_obj2cc map."""
1376 if not self.args.debug:
1377 return
1378 self.init_bp_file('Android.bp')
1379 with open('Android.bp', 'a') as outf:
1380 sorted_pkgs = sorted(self.pkg_obj2cc.keys())
1381 for pkg in sorted_pkgs:
1382 if not self.pkg_obj2cc[pkg]:
1383 continue
1384 outf.write('\n// obj => src for %s\n' % pkg)
1385 obj2cc = self.pkg_obj2cc[pkg]
1386 for obj in sorted(obj2cc.keys()):
1387 outf.write('// ' + short_out_name(pkg, obj) + ' => ' +
1388 short_out_name(pkg, obj2cc[obj].src) + '\n')
1389
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001390 def apply_patch(self):
1391 """Apply local patch file if it is given."""
1392 if self.args.patch:
1393 if self.dry_run:
1394 print('Dry-run skip patch file:', self.args.patch)
1395 else:
1396 if not os.path.exists(self.args.patch):
1397 self.append_to_bp('ERROR cannot find patch file: ' + self.args.patch)
1398 return self
1399 if self.args.verbose:
1400 print('### INFO: applying local patch file:', self.args.patch)
Joel Galenson7e8247e2021-05-20 18:51:42 -07001401 subprocess.run(['patch', '-s', '--no-backup-if-mismatch', './Android.bp',
1402 self.args.patch], check=True)
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001403 return self
1404
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001405 def gen_bp(self):
1406 """Parse cargo.out and generate Android.bp files."""
1407 if self.dry_run:
1408 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp')
1409 elif os.path.exists(CARGO_OUT):
1410 self.find_root_pkg()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001411 if self.args.copy_out:
1412 self.copy_out_files()
1413 elif self.find_out_files() and self.has_used_out_dir():
1414 print('WARNING: ' + self.root_pkg + ' has cargo output files; ' +
1415 'please rerun with the --copy-out flag.')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001416 with open(CARGO_OUT, 'r') as cargo_out:
1417 self.parse(cargo_out, 'Android.bp')
1418 self.crates.sort(key=get_module_name)
1419 for obj in self.cc_objects:
1420 obj.dump()
1421 self.dump_pkg_obj2cc()
1422 for crate in self.crates:
1423 crate.dump()
1424 dumped_libs = set()
1425 for lib in self.ar_objects:
1426 if lib.pkg == self.root_pkg:
1427 lib_name = file_base_name(lib.lib)
1428 if lib_name not in dumped_libs:
1429 dumped_libs.add(lib_name)
1430 lib.dump()
Joel Galenson5664f2a2021-06-10 10:13:49 -07001431 if self.args.add_toplevel_block:
1432 with open(self.args.add_toplevel_block, 'r') as f:
1433 self.append_to_bp('\n' + f.read() + '\n')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001434 if self.args.dependencies and self.dependencies:
1435 self.dump_dependencies()
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001436 if self.errors:
Joel Galenson3f42f802021-04-07 12:42:17 -07001437 self.append_to_bp('\n' + ERRORS_LINE + '\n' + self.errors)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001438 return self
1439
1440 def add_ar_object(self, obj):
1441 self.ar_objects.append(obj)
1442
1443 def add_cc_object(self, obj):
1444 self.cc_objects.append(obj)
1445
1446 def add_crate(self, crate):
1447 """Merge crate with someone in crates, or append to it. Return crates."""
1448 if crate.skip_crate():
1449 if self.args.debug: # include debug info of all crates
1450 self.crates.append(crate)
1451 if self.args.dependencies: # include only dependent crates
1452 if (is_dependent_file_path(crate.main_src) and
1453 not is_build_crate_name(crate.crate_name)):
1454 self.dependencies.append(crate)
1455 else:
1456 for c in self.crates:
1457 if c.merge(crate, 'Android.bp'):
1458 return
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001459 # If not merged, decide module type and name now.
1460 crate.decide_module_type()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001461 self.crates.append(crate)
1462
1463 def find_warning_owners(self):
1464 """For each warning file, find its owner crate."""
1465 missing_owner = False
1466 for f in self.warning_files:
1467 cargo_dir = '' # find lowest crate, with longest path
1468 owner = None # owner crate of this warning
1469 for c in self.crates:
1470 if (f.startswith(c.cargo_dir + '/') and
1471 len(cargo_dir) < len(c.cargo_dir)):
1472 cargo_dir = c.cargo_dir
1473 owner = c
1474 if owner:
1475 owner.has_warning = True
1476 else:
1477 missing_owner = True
1478 if missing_owner and os.path.exists('Cargo.toml'):
1479 # owner is the root cargo, with empty cargo_dir
1480 for c in self.crates:
1481 if not c.cargo_dir:
1482 c.has_warning = True
1483
1484 def rustc_command(self, n, rustc_line, line, outf_name):
1485 """Process a rustc command line from cargo -vv output."""
1486 # cargo build -vv output can have multiple lines for a rustc command
1487 # due to '\n' in strings for environment variables.
1488 # strip removes leading spaces and '\n' at the end
1489 new_rustc = (rustc_line.strip() + line) if rustc_line else line
1490 # Use an heuristic to detect the completions of a multi-line command.
1491 # This might fail for some very rare case, but easy to fix manually.
1492 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0:
1493 return new_rustc
1494 if RUSTC_VV_CMD_ARGS.match(new_rustc):
1495 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1)
1496 self.add_crate(Crate(self, outf_name).parse(n, args))
1497 else:
1498 self.assert_empty_vv_line(new_rustc)
1499 return ''
1500
1501 def cc_ar_command(self, n, groups, outf_name):
1502 pkg = groups.group(1)
1503 line = groups.group(3)
1504 if groups.group(2) == 'cc':
1505 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line))
1506 else:
1507 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line))
1508
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001509 def append_to_bp(self, line):
1510 self.init_bp_file('Android.bp')
1511 with open('Android.bp', 'a') as outf:
1512 outf.write(line)
1513
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001514 def assert_empty_vv_line(self, line):
1515 if line: # report error if line is not empty
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001516 self.append_to_bp('ERROR -vv line: ' + line)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001517 return ''
1518
1519 def parse(self, inf, outf_name):
1520 """Parse rustc and warning messages in inf, return a list of Crates."""
1521 n = 0 # line number
1522 prev_warning = False # true if the previous line was warning: ...
1523 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
1524 for line in inf:
1525 n += 1
1526 if line.startswith('warning: '):
1527 prev_warning = True
1528 rustc_line = self.assert_empty_vv_line(rustc_line)
1529 continue
1530 new_rustc = ''
1531 if RUSTC_PAT.match(line):
1532 args_line = RUSTC_PAT.match(line).group(1)
1533 self.add_crate(Crate(self, outf_name).parse(n, args_line))
1534 self.assert_empty_vv_line(rustc_line)
1535 elif rustc_line or RUSTC_VV_PAT.match(line):
1536 new_rustc = self.rustc_command(n, rustc_line, line, outf_name)
1537 elif CC_AR_VV_PAT.match(line):
1538 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name)
1539 elif prev_warning and WARNING_FILE_PAT.match(line):
1540 self.assert_empty_vv_line(rustc_line)
1541 fpath = WARNING_FILE_PAT.match(line).group(1)
1542 if fpath[0] != '/': # ignore absolute path
1543 self.warning_files.add(fpath)
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001544 elif line.startswith('error: ') or line.startswith('error[E'):
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001545 if not self.args.ignore_cargo_errors:
1546 self.errors += line
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001547 prev_warning = False
1548 rustc_line = new_rustc
1549 self.find_warning_owners()
1550
1551
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001552def get_parser():
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001553 """Parse main arguments."""
1554 parser = argparse.ArgumentParser('cargo2android')
1555 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001556 '--add_workspace',
1557 action='store_true',
1558 default=False,
1559 help=('append [workspace] to Cargo.toml before calling cargo,' +
1560 ' to treat current directory as root of package source;' +
1561 ' otherwise the relative source file path in generated' +
1562 ' .bp file will be from the parent directory.'))
1563 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001564 '--cargo',
1565 action='append',
1566 metavar='args_string',
1567 help=('extra cargo build -v args in a string, ' +
1568 'each --cargo flag calls cargo build -v once'))
1569 parser.add_argument(
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001570 '--cargo_bin',
1571 type=str,
1572 help='use cargo in the cargo_bin directory instead of the prebuilt one')
1573 parser.add_argument(
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001574 '--copy-out',
1575 action='store_true',
1576 default=False,
1577 help=('only for root directory, ' +
1578 'copy build.rs output to ./out/* and add a genrule to copy ' +
1579 './out/* to genrule output; for crates with code pattern: ' +
1580 'include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))'))
1581 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001582 '--debug',
1583 action='store_true',
1584 default=False,
1585 help='dump debug info into Android.bp')
1586 parser.add_argument(
1587 '--dependencies',
1588 action='store_true',
1589 default=False,
1590 help='dump debug info of dependent crates')
1591 parser.add_argument(
1592 '--device',
1593 action='store_true',
1594 default=False,
1595 help='run cargo also for a default device target')
1596 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001597 '--features',
1598 type=str,
1599 help=('pass features to cargo build, ' +
1600 'empty string means no default features'))
1601 parser.add_argument(
1602 '--global_defaults',
1603 type=str,
1604 help='add a defaults name to every module')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001605 parser.add_argument(
1606 '--host-first-multilib',
1607 action='store_true',
1608 default=False,
1609 help=('add a compile_multilib:"first" property ' +
1610 'to Android.bp host modules.'))
1611 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001612 '--ignore-cargo-errors',
1613 action='store_true',
1614 default=False,
1615 help='do not append cargo/rustc error messages to Android.bp')
1616 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001617 '--no-host',
1618 action='store_true',
1619 default=False,
1620 help='do not run cargo for the host; only for the device target')
1621 parser.add_argument(
1622 '--no-subdir',
1623 action='store_true',
1624 default=False,
1625 help='do not output anything for sub-directories')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001626 parser.add_argument(
1627 '--onefile',
1628 action='store_true',
1629 default=False,
1630 help=('output all into one ./Android.bp, default will generate ' +
1631 'one Android.bp per Cargo.toml in subdirectories'))
1632 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001633 '--patch',
1634 type=str,
1635 help='apply the given patch file to generated ./Android.bp')
1636 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001637 '--run',
1638 action='store_true',
1639 default=False,
1640 help='run it, default is dry-run')
1641 parser.add_argument('--rustflags', type=str, help='passing flags to rustc')
1642 parser.add_argument(
1643 '--skipcargo',
1644 action='store_true',
1645 default=False,
1646 help='skip cargo command, parse cargo.out, and generate Android.bp')
1647 parser.add_argument(
1648 '--tests',
1649 action='store_true',
1650 default=False,
1651 help='run cargo build --tests after normal build')
1652 parser.add_argument(
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001653 '--use-cargo-lock',
1654 action='store_true',
1655 default=False,
1656 help=('run cargo build with existing Cargo.lock ' +
1657 '(used when some latest dependent crates failed)'))
1658 parser.add_argument(
Joel Galensond9c4de62021-04-23 10:26:40 -07001659 '--min-sdk-version',
1660 type=str,
1661 help='Minimum SDK version')
1662 parser.add_argument(
1663 '--apex-available',
1664 nargs='*',
1665 help='Mark the main library as apex_available with the given apexes.')
1666 parser.add_argument(
Ivan Lozano91920862021-07-19 10:49:08 -04001667 '--vendor-available',
1668 action='store_true',
1669 default=False,
1670 help='Mark the main library as vendor_available.')
1671 parser.add_argument(
1672 '--vendor-ramdisk-available',
1673 action='store_true',
1674 default=False,
1675 help='Mark the main library as vendor_ramdisk_available.')
1676 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001677 '--force-rlib',
1678 action='store_true',
1679 default=False,
1680 help='Make the main library an rlib.')
1681 parser.add_argument(
Joel Galenson12467e52021-07-12 14:33:28 -07001682 '--whole-static-libs',
1683 nargs='*',
1684 default=[],
1685 help='Make the given libraries (without lib prefixes) whole_static_libs.')
1686 parser.add_argument(
Ivan Lozano26aa1c32021-08-16 11:20:32 -04001687 '--no-pkg-vers',
1688 action='store_true',
1689 default=False,
1690 help='Do not attempt to determine the package version automatically.')
1691 parser.add_argument(
Joel Galensone4f53882021-07-19 11:14:55 -07001692 '--test-data',
1693 nargs='*',
1694 default=[],
1695 help=('Add the given file to the given test\'s data property. ' +
1696 'Usage: test-path=data-path'))
1697 parser.add_argument(
Joel Galenson97e414a2021-05-27 09:42:32 -07001698 '--dependency-blocklist',
1699 nargs='*',
1700 default=[],
Joel Galenson12467e52021-07-12 14:33:28 -07001701 help='Do not emit the given dependencies (without lib prefixes).')
Joel Galenson97e414a2021-05-27 09:42:32 -07001702 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001703 '--lib-blocklist',
1704 nargs='*',
1705 default=[],
Joel Galenson12467e52021-07-12 14:33:28 -07001706 help='Do not emit the given C libraries as dependencies (without lib prefixes).')
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001707 parser.add_argument(
Joel Galensonf6b3c912021-06-03 16:00:54 -07001708 '--test-blocklist',
1709 nargs='*',
1710 default=[],
1711 help=('Do not emit the given tests. ' +
1712 'Pass the path to the test file to exclude.'))
1713 parser.add_argument(
Joel Galenson3d6d1e72021-06-07 15:00:24 -07001714 '--cfg-blocklist',
1715 nargs='*',
1716 default=[],
1717 help='Do not emit the given cfg.')
1718 parser.add_argument(
Joel Galenson5664f2a2021-06-10 10:13:49 -07001719 '--add-toplevel-block',
1720 type=str,
1721 help='Add the contents of the given file to the top level of the Android.bp.')
1722 parser.add_argument(
1723 '--add-module-block',
1724 type=str,
1725 help='Add the contents of the given file to the main module.')
1726 parser.add_argument(
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001727 '--no-test-mapping',
1728 action='store_true',
1729 default=False,
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001730 help='Deprecated. Has no effect.')
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001731 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001732 '--verbose',
1733 action='store_true',
1734 default=False,
1735 help='echo executed commands')
1736 parser.add_argument(
1737 '--vv',
1738 action='store_true',
1739 default=False,
1740 help='run cargo with -vv instead of default -v')
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001741 parser.add_argument(
1742 '--dump-config-and-exit',
1743 type=str,
1744 help=('Dump command-line arguments (minus this flag) to a config file and exit. ' +
1745 'This is intended to help migrate from command line options to config files.'))
1746 parser.add_argument(
1747 '--config',
1748 type=str,
1749 help=('Load command-line options from the given config file. ' +
1750 'Options in this file will override those passed on the command line.'))
1751 return parser
1752
1753
1754def parse_args(parser):
1755 """Parses command-line options."""
1756 args = parser.parse_args()
1757 # Use the values specified in a config file if one was found.
1758 if args.config:
1759 with open(args.config, 'r') as f:
1760 config = json.load(f)
1761 args_dict = vars(args)
1762 for arg in config:
1763 args_dict[arg.replace('-', '_')] = config[arg]
1764 return args
1765
1766
1767def dump_config(parser, args):
1768 """Writes the non-default command-line options to the specified file."""
1769 args_dict = vars(args)
1770 # Filter out the arguments that have their default value.
Joel Galenson367360c2021-04-29 14:31:43 -07001771 # Also filter certain "temporary" arguments.
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001772 non_default_args = {}
1773 for arg in args_dict:
Joel Galenson367360c2021-04-29 14:31:43 -07001774 if args_dict[arg] != parser.get_default(
1775 arg) and arg != 'dump_config_and_exit' and arg != 'no_test_mapping':
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001776 non_default_args[arg.replace('_', '-')] = args_dict[arg]
1777 # Write to the specified file.
1778 with open(args.dump_config_and_exit, 'w') as f:
1779 json.dump(non_default_args, f, indent=2, sort_keys=True)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001780
1781
1782def main():
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001783 parser = get_parser()
1784 args = parse_args(parser)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001785 if not args.run: # default is dry-run
1786 print(DRY_RUN_NOTE)
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001787 if args.dump_config_and_exit:
1788 dump_config(parser, args)
1789 else:
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001790 Runner(args).run_cargo().gen_bp().apply_patch()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001791
1792
1793if __name__ == '__main__':
1794 main()