blob: 11b70109113e6905795c880d978ed95199bfccb0 [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
435 # get the package version from running cargo metadata
436 cargo_metadata = subprocess.run(["cargo", "metadata", "--no-deps"],
437 cwd=os.path.abspath(self.cargo_dir), capture_output=True)
438 if cargo_metadata.returncode:
439 self.errors += ("ERROR: unable to get cargo metadata for package version; return code " +
440 cargo_metadata.returncode + "\n")
441 else:
442 metadata_json = json.loads(cargo_metadata.stdout)
443 if len(metadata_json["packages"]) > 1:
444 self.errors += "ERROR: multiple packages defined, will not output package version"
445 else:
446 self.cargo_pkg_version = metadata_json["packages"][0]["version"]
447
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800448 else:
449 self.errors += 'ERROR: unknown ' + arg + '\n'
450 i += 1
451 if not self.crate_name:
452 self.errors += 'ERROR: missing --crate-name\n'
453 if not self.main_src:
454 self.errors += 'ERROR: missing main source file\n'
455 else:
456 self.srcs.append(self.main_src)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700457 if not self.crate_types:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800458 # Treat "--cfg test" as "--test"
459 if 'test' in self.cfgs:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700460 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800461 else:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700462 self.errors += 'ERROR: missing --crate-type or --test\n'
463 elif len(self.crate_types) > 1:
464 if 'test' in self.crate_types:
465 self.errors += 'ERROR: cannot handle both --crate-type and --test\n'
466 if 'lib' in self.crate_types and 'rlib' in self.crate_types:
467 self.errors += 'ERROR: cannot generate both lib and rlib crate types\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800468 if not self.root_pkg:
469 self.root_pkg = self.crate_name
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700470 self.device_supported = self.runner.args.device
471 self.host_supported = not self.runner.args.no_host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800472 self.cfgs = sorted(set(self.cfgs))
473 self.features = sorted(set(self.features))
474 self.codegens = sorted(set(self.codegens))
475 self.externs = sorted(set(self.externs))
476 self.core_externs = sorted(set(self.core_externs))
477 self.static_libs = sorted(set(self.static_libs))
478 self.shared_libs = sorted(set(self.shared_libs))
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700479 self.crate_types = sorted(set(self.crate_types))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800480 self.decide_module_type()
481 self.module_name = altered_name(self.stem)
482 return self
483
484 def dump_line(self):
485 self.write('\n// Line ' + str(self.line_num) + ' ' + self.line)
486
487 def feature_list(self):
488 """Return a string of main_src + "feature_list"."""
489 pkg = self.main_src
490 if pkg.startswith('.../'): # keep only the main package name
491 pkg = re.sub('/.*', '', pkg[4:])
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700492 elif pkg.startswith('/'): # use relative path for a local package
493 pkg = os.path.relpath(pkg)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800494 if not self.features:
495 return pkg
496 return pkg + ' "' + ','.join(self.features) + '"'
497
498 def dump_skip_crate(self, kind):
499 if self.debug:
500 self.write('\n// IGNORED: ' + kind + ' ' + self.main_src)
501 return self
502
503 def skip_crate(self):
504 """Return crate_name or a message if this crate should be skipped."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700505 if (is_build_crate_name(self.crate_name) or
506 self.crate_name in EXCLUDED_CRATES):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800507 return self.crate_name
508 if is_dependent_file_path(self.main_src):
509 return 'dependent crate'
510 return ''
511
512 def dump(self):
513 """Dump all error/debug/module code to the output .bp file."""
514 self.runner.init_bp_file(self.outf_name)
515 with open(self.outf_name, 'a') as outf:
516 self.outf = outf
517 if self.errors:
518 self.dump_line()
519 self.write(self.errors)
520 elif self.skip_crate():
521 self.dump_skip_crate(self.skip_crate())
522 else:
523 if self.debug:
524 self.dump_debug_info()
525 self.dump_android_module()
526
527 def dump_debug_info(self):
528 """Dump parsed data, when cargo2android is called with --debug."""
529
530 def dump(name, value):
531 self.write('//%12s = %s' % (name, value))
532
533 def opt_dump(name, value):
534 if value:
535 dump(name, value)
536
537 def dump_list(fmt, values):
538 for v in values:
539 self.write(fmt % v)
540
541 self.dump_line()
542 dump('module_name', self.module_name)
543 dump('crate_name', self.crate_name)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700544 dump('crate_types', self.crate_types)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800545 dump('main_src', self.main_src)
546 dump('has_warning', self.has_warning)
547 dump('for_host', self.host_supported)
548 dump('for_device', self.device_supported)
549 dump('module_type', self.module_type)
550 opt_dump('target', self.target)
551 opt_dump('edition', self.edition)
552 opt_dump('emit_list', self.emit_list)
553 opt_dump('cap_lints', self.cap_lints)
554 dump_list('// cfg = %s', self.cfgs)
555 dump_list('// cfg = \'feature "%s"\'', self.features)
556 # TODO(chh): escape quotes in self.features, but not in other dump_list
557 dump_list('// codegen = %s', self.codegens)
558 dump_list('// externs = %s', self.externs)
559 dump_list('// -l static = %s', self.static_libs)
560 dump_list('// -l (dylib) = %s', self.shared_libs)
561
562 def dump_android_module(self):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700563 """Dump one or more Android module definition, depending on crate_types."""
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700564 if len(self.crate_types) == 1:
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700565 self.dump_single_type_android_module()
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700566 return
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700567 if 'test' in self.crate_types:
568 self.write('\nERROR: multiple crate types cannot include test type')
569 return
570 # Dump one Android module per crate_type.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700571 for crate_type in self.crate_types:
572 self.decide_one_module_type(crate_type)
573 self.dump_one_android_module(crate_type)
574
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700575 def build_default_name(self):
576 """Return a short and readable name for the rust_defaults module."""
Joel Galensond37d7e62021-07-13 09:03:01 -0700577 # Choices: (1) root_pkg + '_test'? + '_defaults',
578 # (2) root_pkg + '_test'? + '_defaults_' + crate_name
579 # (3) root_pkg + '_test'? + '_defaults_' + main_src_basename_path
580 # (4) root_pkg + '_test'? + '_defaults_' + a_positive_sequence_number
581 test = "_test" if self.crate_types == ['test'] else ""
582 name1 = altered_defaults(self.root_pkg) + test + '_defaults'
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700583 if self.runner.try_claim_module_name(name1, self):
584 return name1
585 name2 = name1 + '_' + self.crate_name
586 if self.runner.try_claim_module_name(name2, self):
587 return name2
588 name3 = name1 + '_' + self.main_src_basename_path()
589 if self.runner.try_claim_module_name(name3, self):
590 return name3
591 return self.runner.claim_module_name(name1, self, 0)
592
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700593 def dump_srcs_list(self):
594 """Dump the srcs list, for defaults or regular modules."""
595 if len(self.srcs) > 1:
596 srcs = sorted(set(self.srcs)) # make a copy and dedup
597 else:
598 srcs = [self.main_src]
599 copy_out = self.runner.copy_out_module_name()
600 if copy_out:
601 srcs.append(':' + copy_out)
602 self.dump_android_property_list('srcs', '"%s"', srcs)
603
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700604 def dump_defaults_module(self):
605 """Dump a rust_defaults module to be shared by other modules."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700606 name = self.build_default_name()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700607 self.defaults = name
608 self.write('\nrust_defaults {')
609 self.write(' name: "' + name + '",')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700610 if self.runner.args.global_defaults:
611 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700612 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700613 if len(self.srcs) == 1: # only one source file; share it in defaults
614 self.default_srcs = True
615 if self.has_warning and not self.cap_lints:
616 self.write(' // has rustc warnings')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700617 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700618 if 'test' in self.crate_types:
619 self.write(' test_suites: ["general-tests"],')
620 self.write(' auto_gen_config: true,')
621 self.dump_edition_flags_libs()
Joel Galensone4f53882021-07-19 11:14:55 -0700622 if 'test' in self.crate_types and len(self.srcs) == 1:
623 self.dump_test_data()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700624 self.write('}')
625
626 def dump_single_type_android_module(self):
627 """Dump one simple Android module, which has only one crate_type."""
628 crate_type = self.crate_types[0]
629 if crate_type != 'test':
630 # do not change self.stem or self.module_name
631 self.dump_one_android_module(crate_type)
632 return
633 # Dump one test module per source file, and separate host and device tests.
634 # crate_type == 'test'
Joel Galensonf6b3c912021-06-03 16:00:54 -0700635 self.srcs = [src for src in self.srcs if not src in self.runner.args.test_blocklist]
636 if ((self.host_supported and self.device_supported and len(self.srcs) > 0) or
637 len(self.srcs) > 1):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700638 self.srcs = sorted(set(self.srcs))
639 self.dump_defaults_module()
640 saved_srcs = self.srcs
641 for src in saved_srcs:
642 self.srcs = [src]
643 saved_device_supported = self.device_supported
644 saved_host_supported = self.host_supported
645 saved_main_src = self.main_src
646 self.main_src = src
647 if saved_host_supported:
648 self.device_supported = False
649 self.host_supported = True
650 self.module_name = self.test_module_name()
651 self.decide_one_module_type(crate_type)
652 self.dump_one_android_module(crate_type)
653 if saved_device_supported:
654 self.device_supported = True
655 self.host_supported = False
656 self.module_name = self.test_module_name()
657 self.decide_one_module_type(crate_type)
658 self.dump_one_android_module(crate_type)
659 self.host_supported = saved_host_supported
660 self.device_supported = saved_device_supported
661 self.main_src = saved_main_src
662 self.srcs = saved_srcs
663
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700664 def dump_one_android_module(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800665 """Dump one Android module definition."""
666 if not self.module_type:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700667 self.write('\nERROR: unknown crate_type ' + crate_type)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800668 return
669 self.write('\n' + self.module_type + ' {')
670 self.dump_android_core_properties()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700671 if not self.defaults:
672 self.dump_edition_flags_libs()
673 if self.runner.args.host_first_multilib and self.host_supported and crate_type != 'test':
674 self.write(' compile_multilib: "first",')
Joel Galensond9c4de62021-04-23 10:26:40 -0700675 if self.runner.args.apex_available and crate_type == 'lib':
676 self.write(' apex_available: [')
677 for apex in self.runner.args.apex_available:
678 self.write(' "%s",' % apex)
679 self.write(' ],')
Ivan Lozano91920862021-07-19 10:49:08 -0400680 if self.runner.args.vendor_available:
681 self.write(' vendor_available: true,')
682 if self.runner.args.vendor_ramdisk_available:
683 self.write(' vendor_ramdisk_available: true,')
Joel Galensond9c4de62021-04-23 10:26:40 -0700684 if self.runner.args.min_sdk_version and crate_type == 'lib':
685 self.write(' min_sdk_version: "%s",' % self.runner.args.min_sdk_version)
Joel Galensone4f53882021-07-19 11:14:55 -0700686 if crate_type == 'test' and not self.default_srcs:
687 self.dump_test_data()
Joel Galenson5664f2a2021-06-10 10:13:49 -0700688 if self.runner.args.add_module_block:
689 with open(self.runner.args.add_module_block, 'r') as f:
690 self.write(' %s,' % f.read().replace('\n', '\n '))
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700691 self.write('}')
692
693 def dump_android_flags(self):
694 """Dump Android module flags property."""
ThiƩbaud Weksteena5a728b2021-04-08 14:23:49 +0200695 if not self.codegens and not self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700696 return
697 self.write(' flags: [')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800698 if self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700699 self.write(' "--cap-lints ' + self.cap_lints + '",')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700700 codegens_fmt = '"-C %s"'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700701 self.dump_android_property_list_items(codegens_fmt, self.codegens)
702 self.write(' ],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700703
704 def dump_edition_flags_libs(self):
705 if self.edition:
706 self.write(' edition: "' + self.edition + '",')
707 self.dump_android_property_list('features', '"%s"', self.features)
Joel Galenson3d6d1e72021-06-07 15:00:24 -0700708 cfgs = [cfg for cfg in self.cfgs if not cfg in self.runner.args.cfg_blocklist]
709 self.dump_android_property_list('cfgs', '"%s"', cfgs)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700710 self.dump_android_flags()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800711 if self.externs:
712 self.dump_android_externs()
Joel Galenson12467e52021-07-12 14:33:28 -0700713 all_static_libs = [lib for lib in self.static_libs if not lib in self.runner.args.lib_blocklist]
714 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 -0700715 self.dump_android_property_list('static_libs', '"lib%s"', static_libs)
Joel Galenson12467e52021-07-12 14:33:28 -0700716 whole_static_libs = [lib for lib in all_static_libs if lib in self.runner.args.whole_static_libs]
717 self.dump_android_property_list('whole_static_libs', '"lib%s"', whole_static_libs)
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700718 shared_libs = [lib for lib in self.shared_libs if not lib in self.runner.args.lib_blocklist]
719 self.dump_android_property_list('shared_libs', '"lib%s"', shared_libs)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800720
Joel Galensone4f53882021-07-19 11:14:55 -0700721 def dump_test_data(self):
722 data = [data for (name, data) in map(lambda kv: kv.split('=', 1), self.runner.args.test_data)
723 if self.srcs == [name]]
724 if data:
725 self.dump_android_property_list('data', '"%s"', data)
726
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700727 def main_src_basename_path(self):
728 return re.sub('/', '_', re.sub('.rs$', '', self.main_src))
729
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800730 def test_module_name(self):
731 """Return a unique name for a test module."""
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700732 # root_pkg+(_host|_device) + '_test_'+source_file_name
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700733 suffix = self.main_src_basename_path()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700734 host_device = '_host'
735 if self.device_supported:
736 host_device = '_device'
737 return self.root_pkg + host_device + '_test_' + suffix
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800738
739 def decide_module_type(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700740 # Use the first crate type for the default/first module.
741 crate_type = self.crate_types[0] if self.crate_types else ''
742 self.decide_one_module_type(crate_type)
743
744 def decide_one_module_type(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800745 """Decide which Android module type to use."""
746 host = '' if self.device_supported else '_host'
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700747 rlib = '_rlib' if self.runner.args.force_rlib else ''
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700748 if crate_type == 'bin': # rust_binary[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800749 self.module_type = 'rust_binary' + host
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700750 # In rare cases like protobuf-codegen, the output binary name must
751 # be renamed to use as a plugin for protoc.
752 self.stem = altered_stem(self.crate_name)
753 self.module_name = altered_name(self.crate_name)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700754 elif crate_type == 'lib': # rust_library[_host]
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700755 # TODO(chh): should this be rust_library[_host]?
756 # Assuming that Cargo.toml do not use both 'lib' and 'rlib',
757 # because we map them both to rlib.
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700758 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800759 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700760 self.module_name = altered_name(self.stem)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700761 elif crate_type == 'rlib': # rust_library[_host]
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700762 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700763 self.stem = 'lib' + self.crate_name
764 self.module_name = altered_name(self.stem)
765 elif crate_type == 'dylib': # rust_library[_host]_dylib
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800766 self.module_type = 'rust_library' + host + '_dylib'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700767 self.stem = 'lib' + self.crate_name
768 self.module_name = altered_name(self.stem) + '_dylib'
769 elif crate_type == 'cdylib': # rust_library[_host]_shared
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500770 self.module_type = 'rust_ffi' + host + '_shared'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700771 self.stem = 'lib' + self.crate_name
772 self.module_name = altered_name(self.stem) + '_shared'
773 elif crate_type == 'staticlib': # rust_library[_host]_static
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500774 self.module_type = 'rust_ffi' + host + '_static'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700775 self.stem = 'lib' + self.crate_name
776 self.module_name = altered_name(self.stem) + '_static'
777 elif crate_type == 'test': # rust_test[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800778 self.module_type = 'rust_test' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700779 # Before do_merge, stem name is based on the --crate-name parameter.
780 # and test module name is based on stem.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800781 self.stem = self.test_module_name()
782 # self.stem will be changed after merging with other tests.
783 # self.stem is NOT used for final test binary name.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700784 # rust_test uses each source file base name as part of output file name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700785 # In do_merge, this function is called again, with a module_name.
786 # We make sure that the module name is unique in each package.
787 if self.module_name:
788 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add
789 # different suffixes and distinguish multiple tests of the same
790 # crate name. We ignore -C and use claim_module_name to get
791 # unique sequential suffix.
792 self.module_name = self.runner.claim_module_name(
793 self.module_name, self, 0)
794 # Now the module name is unique, stem should also match and unique.
795 self.stem = self.module_name
796 elif crate_type == 'proc-macro': # rust_proc_macro
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800797 self.module_type = 'rust_proc_macro'
798 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700799 self.module_name = altered_name(self.stem)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800800 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]?
801 self.module_type = ''
802 self.stem = ''
803
804 def dump_android_property_list_items(self, fmt, values):
805 for v in values:
806 # fmt has quotes, so we need escape_quotes(v)
807 self.write(' ' + (fmt % escape_quotes(v)) + ',')
808
809 def dump_android_property_list(self, name, fmt, values):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700810 if not values:
811 return
812 if len(values) > 1:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800813 self.write(' ' + name + ': [')
814 self.dump_android_property_list_items(fmt, values)
815 self.write(' ],')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700816 else:
817 self.write(' ' + name + ': [' +
818 (fmt % escape_quotes(values[0])) + '],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800819
820 def dump_android_core_properties(self):
821 """Dump the module header, name, stem, etc."""
822 self.write(' name: "' + self.module_name + '",')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700823 # see properties shared by dump_defaults_module
824 if self.defaults:
825 self.write(' defaults: ["' + self.defaults + '"],')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700826 elif self.runner.args.global_defaults:
827 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800828 if self.stem != self.module_name:
829 self.write(' stem: "' + self.stem + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700830 if self.has_warning and not self.cap_lints and not self.default_srcs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700831 self.write(' // has rustc warnings')
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700832 if self.host_supported and self.device_supported and self.module_type != 'rust_proc_macro':
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800833 self.write(' host_supported: true,')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700834 if not self.defaults:
835 self.write(' crate_name: "' + self.crate_name + '",')
Ivan Lozanocc660f12021-08-11 16:49:46 -0400836 if not self.defaults and self.cargo_env_compat:
837 self.write(' cargo_env_compat: true,')
838 self.write(' cargo_pkg_version: "' + self.cargo_pkg_version + '",')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700839 if not self.default_srcs:
840 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700841 if 'test' in self.crate_types and not self.defaults:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800842 # self.root_pkg can have multiple test modules, with different *_tests[n]
843 # names, but their executables can all be installed under the same _tests
844 # directory. When built from Cargo.toml, all tests should have different
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700845 # file or crate names. So we used (root_pkg + '_tests') name as the
846 # relative_install_path.
847 # However, some package like 'slab' can have non-mergeable tests that
848 # must be separated by different module names. So, here we no longer
849 # emit relative_install_path.
850 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800851 self.write(' test_suites: ["general-tests"],')
852 self.write(' auto_gen_config: true,')
Joel Galensone261a152021-01-12 11:31:53 -0800853 if 'test' in self.crate_types and self.host_supported:
854 self.write(' test_options: {')
855 self.write(' unit_test: true,')
856 self.write(' },')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800857
858 def dump_android_externs(self):
859 """Dump the dependent rlibs and dylibs property."""
860 so_libs = list()
861 rust_libs = ''
862 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$')
863 for lib in self.externs:
864 # normal value of lib: "libc = liblibc-*.rlib"
865 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib"
866 # we should use "libgetrandom", not "lib" + "getrandom_package"
867 groups = deps_libname.match(lib)
868 if groups is not None:
869 lib_name = groups.group(1)
870 else:
871 lib_name = re.sub(' .*$', '', lib)
Joel Galenson97e414a2021-05-27 09:42:32 -0700872 if lib_name in self.runner.args.dependency_blocklist:
873 continue
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800874 if lib.endswith('.rlib') or lib.endswith('.rmeta'):
875 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta.
876 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n'
877 elif lib.endswith('.so'):
878 so_libs.append(lib_name)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700879 elif lib != 'proc_macro': # --extern proc_macro is special and ignored
880 rust_libs += ' // ERROR: unknown type of lib ' + lib + '\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800881 if rust_libs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700882 self.write(' rustlibs: [\n' + rust_libs + ' ],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800883 # Are all dependent .so files proc_macros?
884 # TODO(chh): Separate proc_macros and dylib.
885 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs)
886
887
888class ARObject(object):
889 """Information of an "ar" link command."""
890
891 def __init__(self, runner, outf_name):
892 # Remembered global runner and its members.
893 self.runner = runner
894 self.pkg = ''
895 self.outf_name = outf_name # path to Android.bp
896 # "ar" arguments
897 self.line_num = 1
898 self.line = ''
899 self.flags = '' # e.g. "crs"
900 self.lib = '' # e.g. "/.../out/lib*.a"
901 self.objs = list() # e.g. "/.../out/.../*.o"
902
903 def parse(self, pkg, line_num, args_line):
904 """Collect ar obj/lib file names."""
905 self.pkg = pkg
906 self.line_num = line_num
907 self.line = args_line
908 args = args_line.split()
909 num_args = len(args)
910 if num_args < 3:
911 print('ERROR: "ar" command has too few arguments', args_line)
912 else:
913 self.flags = unquote(args[0])
914 self.lib = unquote(args[1])
915 self.objs = sorted(set(map(unquote, args[2:])))
916 return self
917
918 def write(self, s):
919 self.outf.write(s + '\n')
920
921 def dump_debug_info(self):
922 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line)
923 self.write('// ar_object for %12s' % self.pkg)
924 self.write('// flags = %s' % self.flags)
925 self.write('// lib = %s' % short_out_name(self.pkg, self.lib))
926 for o in self.objs:
927 self.write('// obj = %s' % short_out_name(self.pkg, o))
928
929 def dump_android_lib(self):
930 """Write cc_library_static into Android.bp."""
931 self.write('\ncc_library_static {')
932 self.write(' name: "' + file_base_name(self.lib) + '",')
933 self.write(' host_supported: true,')
934 if self.flags != 'crs':
935 self.write(' // ar flags = %s' % self.flags)
936 if self.pkg not in self.runner.pkg_obj2cc:
937 self.write(' ERROR: cannot find source files.\n}')
938 return
939 self.write(' srcs: [')
940 obj2cc = self.runner.pkg_obj2cc[self.pkg]
941 # Note: wflags are ignored.
942 dflags = list()
943 fflags = list()
944 for obj in self.objs:
945 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",')
946 # TODO(chh): union of dflags and flags of all obj
947 # Now, just a temporary hack that uses the last obj's flags
948 dflags = obj2cc[obj].dflags
949 fflags = obj2cc[obj].fflags
950 self.write(' ],')
951 self.write(' cflags: [')
952 self.write(' "-O3",') # TODO(chh): is this default correct?
953 self.write(' "-Wno-error",')
954 for x in fflags:
955 self.write(' "-f' + x + '",')
956 for x in dflags:
957 self.write(' "-D' + x + '",')
958 self.write(' ],')
959 self.write('}')
960
961 def dump(self):
962 """Dump error/debug/module info to the output .bp file."""
963 self.runner.init_bp_file(self.outf_name)
964 with open(self.outf_name, 'a') as outf:
965 self.outf = outf
966 if self.runner.args.debug:
967 self.dump_debug_info()
968 self.dump_android_lib()
969
970
971class CCObject(object):
972 """Information of a "cc" compilation command."""
973
974 def __init__(self, runner, outf_name):
975 # Remembered global runner and its members.
976 self.runner = runner
977 self.pkg = ''
978 self.outf_name = outf_name # path to Android.bp
979 # "cc" arguments
980 self.line_num = 1
981 self.line = ''
982 self.src = ''
983 self.obj = ''
984 self.dflags = list() # -D flags
985 self.fflags = list() # -f flags
986 self.iflags = list() # -I flags
987 self.wflags = list() # -W flags
988 self.other_args = list()
989
990 def parse(self, pkg, line_num, args_line):
991 """Collect cc compilation flags and src/out file names."""
992 self.pkg = pkg
993 self.line_num = line_num
994 self.line = args_line
995 args = args_line.split()
996 i = 0
997 while i < len(args):
998 arg = args[i]
999 if arg == '"-c"':
1000 i += 1
1001 if args[i].startswith('"-o'):
1002 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c"
1003 self.obj = unquote(args[i])[2:]
1004 i += 1
1005 self.src = unquote(args[i])
1006 else:
1007 self.src = unquote(args[i])
1008 elif arg == '"-o"':
1009 i += 1
1010 self.obj = unquote(args[i])
1011 elif arg == '"-I"':
1012 i += 1
1013 self.iflags.append(unquote(args[i]))
1014 elif arg.startswith('"-D'):
1015 self.dflags.append(unquote(args[i])[2:])
1016 elif arg.startswith('"-f'):
1017 self.fflags.append(unquote(args[i])[2:])
1018 elif arg.startswith('"-W'):
1019 self.wflags.append(unquote(args[i])[2:])
1020 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or
1021 arg == '"-g3"'):
1022 # ignore -O -m64 -g
1023 self.other_args.append(unquote(args[i]))
1024 i += 1
1025 self.dflags = sorted(set(self.dflags))
1026 self.fflags = sorted(set(self.fflags))
1027 # self.wflags is not sorted because some are order sensitive
1028 # and we ignore them anyway.
1029 if self.pkg not in self.runner.pkg_obj2cc:
1030 self.runner.pkg_obj2cc[self.pkg] = {}
1031 self.runner.pkg_obj2cc[self.pkg][self.obj] = self
1032 return self
1033
1034 def write(self, s):
1035 self.outf.write(s + '\n')
1036
1037 def dump_debug_flags(self, name, flags):
1038 self.write('// ' + name + ':')
1039 for f in flags:
1040 self.write('// %s' % f)
1041
1042 def dump(self):
1043 """Dump only error/debug info to the output .bp file."""
1044 if not self.runner.args.debug:
1045 return
1046 self.runner.init_bp_file(self.outf_name)
1047 with open(self.outf_name, 'a') as outf:
1048 self.outf = outf
1049 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line)
1050 self.write('// cc_object for %12s' % self.pkg)
1051 self.write('// src = %s' % short_out_name(self.pkg, self.src))
1052 self.write('// obj = %s' % short_out_name(self.pkg, self.obj))
1053 self.dump_debug_flags('-I flags', self.iflags)
1054 self.dump_debug_flags('-D flags', self.dflags)
1055 self.dump_debug_flags('-f flags', self.fflags)
1056 self.dump_debug_flags('-W flags', self.wflags)
1057 if self.other_args:
1058 self.dump_debug_flags('other args', self.other_args)
1059
1060
1061class Runner(object):
1062 """Main class to parse cargo -v output and print Android module definitions."""
1063
1064 def __init__(self, args):
1065 self.bp_files = set() # Remember all output Android.bp files.
1066 self.root_pkg = '' # name of package in ./Cargo.toml
1067 # Saved flags, modes, and data.
1068 self.args = args
1069 self.dry_run = not args.run
1070 self.skip_cargo = args.skipcargo
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001071 self.cargo_path = './cargo' # path to cargo, will be set later
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001072 self.checked_out_files = False # to check only once
1073 self.build_out_files = [] # output files generated by build.rs
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001074 # All cc/ar objects, crates, dependencies, and warning files
1075 self.cc_objects = list()
1076 self.pkg_obj2cc = {}
1077 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i]
1078 self.ar_objects = list()
1079 self.crates = list()
1080 self.dependencies = list() # dependent and build script crates
1081 self.warning_files = set()
1082 # Keep a unique mapping from (module name) to crate
1083 self.name_owners = {}
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001084 # Save and dump all errors from cargo to Android.bp.
1085 self.errors = ''
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001086 self.setup_cargo_path()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001087 # Default action is cargo clean, followed by build or user given actions.
1088 if args.cargo:
1089 self.cargo = ['clean'] + args.cargo
1090 else:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001091 default_target = '--target x86_64-unknown-linux-gnu'
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -07001092 # Use the same target for both host and default device builds.
1093 # Same target is used as default in host x86_64 Android compilation.
1094 # Note: b/169872957, prebuilt cargo failed to build vsock
1095 # on x86_64-unknown-linux-musl systems.
1096 self.cargo = ['clean', 'build ' + default_target]
1097 if args.tests:
1098 self.cargo.append('build --tests ' + default_target)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001099
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001100 def setup_cargo_path(self):
1101 """Find cargo in the --cargo_bin or prebuilt rust bin directory."""
1102 if self.args.cargo_bin:
1103 self.cargo_path = os.path.join(self.args.cargo_bin, 'cargo')
1104 if not os.path.isfile(self.cargo_path):
1105 sys.exit('ERROR: cannot find cargo in ' + self.args.cargo_bin)
1106 print('WARNING: using cargo in ' + self.args.cargo_bin)
1107 return
1108 # We have only tested this on Linux.
1109 if platform.system() != 'Linux':
1110 sys.exit('ERROR: this script has only been tested on Linux with cargo.')
1111 # Assuming that this script is in development/scripts.
1112 my_dir = os.path.dirname(os.path.abspath(__file__))
1113 linux_dir = os.path.join(my_dir, '..', '..',
1114 'prebuilts', 'rust', 'linux-x86')
1115 if not os.path.isdir(linux_dir):
1116 sys.exit('ERROR: cannot find directory ' + linux_dir)
1117 rust_version = self.find_rust_version(my_dir, linux_dir)
1118 cargo_bin = os.path.join(linux_dir, rust_version, 'bin')
1119 self.cargo_path = os.path.join(cargo_bin, 'cargo')
1120 if not os.path.isfile(self.cargo_path):
1121 sys.exit('ERROR: cannot find cargo in ' + cargo_bin
1122 + '; please try --cargo_bin= flag.')
1123 return
1124
1125 def find_rust_version(self, my_dir, linux_dir):
1126 """Use my script directory, find prebuilt rust version."""
1127 # First look up build/soong/rust/config/global.go.
1128 path2global = os.path.join(my_dir, '..', '..',
1129 'build', 'soong', 'rust', 'config', 'global.go')
1130 if os.path.isfile(path2global):
1131 # try to find: RustDefaultVersion = "1.44.0"
1132 version_pat = re.compile(
1133 r'\s*RustDefaultVersion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+)".*$')
1134 with open(path2global, 'r') as inf:
1135 for line in inf:
1136 result = version_pat.match(line)
1137 if result:
1138 return result.group(1)
1139 print('WARNING: cannot find RustDefaultVersion in ' + path2global)
1140 # Otherwise, find the newest (largest) version number in linux_dir.
1141 rust_version = (0, 0, 0) # the prebuilt version to use
1142 version_pat = re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)$')
1143 for dir_name in os.listdir(linux_dir):
1144 result = version_pat.match(dir_name)
1145 if not result:
1146 continue
1147 version = (result.group(1), result.group(2), result.group(3))
1148 if version > rust_version:
1149 rust_version = version
1150 return '.'.join(rust_version)
1151
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001152 def find_out_files(self):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001153 # list1 has build.rs output for normal crates
1154 list1 = glob.glob(TARGET_TMP + '/*/*/build/' + self.root_pkg + '-*/out/*')
1155 # list2 has build.rs output for proc-macro crates
1156 list2 = glob.glob(TARGET_TMP + '/*/build/' + self.root_pkg + '-*/out/*')
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001157 return list1 + list2
1158
1159 def copy_out_files(self):
1160 """Copy build.rs output files to ./out and set up build_out_files."""
1161 if self.checked_out_files:
1162 return
1163 self.checked_out_files = True
1164 cargo_out_files = self.find_out_files()
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001165 out_files = set()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001166 if cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001167 os.makedirs('out', exist_ok=True)
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001168 for path in cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001169 file_name = path.split('/')[-1]
1170 out_files.add(file_name)
1171 shutil.copy(path, 'out/' + file_name)
1172 self.build_out_files = sorted(out_files)
1173
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001174 def has_used_out_dir(self):
1175 """Returns true if env!("OUT_DIR") is found."""
1176 return 0 == os.system('grep -rl --exclude build.rs --include \\*.rs' +
1177 ' \'env!("OUT_DIR")\' * > /dev/null')
1178
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001179 def copy_out_module_name(self):
1180 if self.args.copy_out and self.build_out_files:
1181 return 'copy_' + self.root_pkg + '_build_out'
1182 else:
1183 return ''
1184
Haibo Huang0f72c952021-03-19 11:34:15 -07001185 def read_license(self, name):
1186 if not os.path.isfile(name):
1187 return ''
1188 license = ''
1189 with open(name, 'r') as intf:
1190 line = intf.readline()
1191 # Firstly skip ANDROID_BP_HEADER
1192 while line.startswith('//'):
1193 line = intf.readline()
Joel Galensond9d13b82021-04-05 11:27:55 -07001194 # Read all lines until we see a rust_* or genrule rule.
1195 while line != '' and not (line.startswith('rust_') or line.startswith('genrule {')):
Haibo Huang0f72c952021-03-19 11:34:15 -07001196 license += line
1197 line = intf.readline()
1198 return license.strip()
1199
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001200 def dump_copy_out_module(self, outf):
1201 """Output the genrule module to copy out/* to $(genDir)."""
1202 copy_out = self.copy_out_module_name()
1203 if not copy_out:
1204 return
1205 outf.write('\ngenrule {\n')
1206 outf.write(' name: "' + copy_out + '",\n')
1207 outf.write(' srcs: ["out/*"],\n')
1208 outf.write(' cmd: "cp $(in) $(genDir)",\n')
1209 if len(self.build_out_files) > 1:
1210 outf.write(' out: [\n')
1211 for f in self.build_out_files:
1212 outf.write(' "' + f + '",\n')
1213 outf.write(' ],\n')
1214 else:
1215 outf.write(' out: ["' + self.build_out_files[0] + '"],\n')
1216 outf.write('}\n')
1217
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001218 def init_bp_file(self, name):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001219 # name could be Android.bp or sub_dir_path/Android.bp
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001220 if name not in self.bp_files:
1221 self.bp_files.add(name)
Haibo Huang0f72c952021-03-19 11:34:15 -07001222 license_section = self.read_license(name)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001223 with open(name, 'w') as outf:
Joel Galenson367360c2021-04-29 14:31:43 -07001224 print_args = filter(lambda x: x != "--no-test-mapping", sys.argv[1:])
1225 outf.write(ANDROID_BP_HEADER.format(args=' '.join(print_args)))
Haibo Huang0f72c952021-03-19 11:34:15 -07001226 outf.write('\n')
1227 outf.write(license_section)
1228 outf.write('\n')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001229 # at most one copy_out module per .bp file
1230 self.dump_copy_out_module(outf)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001231
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001232 def try_claim_module_name(self, name, owner):
1233 """Reserve and return True if it has not been reserved yet."""
1234 if name not in self.name_owners or owner == self.name_owners[name]:
1235 self.name_owners[name] = owner
1236 return True
1237 return False
1238
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001239 def claim_module_name(self, prefix, owner, counter):
1240 """Return prefix if not owned yet, otherwise, prefix+str(counter)."""
1241 while True:
1242 name = prefix
1243 if counter > 0:
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001244 name += '_' + str(counter)
1245 if self.try_claim_module_name(name, owner):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001246 return name
1247 counter += 1
1248
1249 def find_root_pkg(self):
1250 """Read name of [package] in ./Cargo.toml."""
1251 if not os.path.exists('./Cargo.toml'):
1252 return
1253 with open('./Cargo.toml', 'r') as inf:
1254 pkg_section = re.compile(r'^ *\[package\]')
1255 name = re.compile('^ *name *= * "([^"]*)"')
1256 in_pkg = False
1257 for line in inf:
1258 if in_pkg:
1259 if name.match(line):
1260 self.root_pkg = name.match(line).group(1)
1261 break
1262 else:
1263 in_pkg = pkg_section.match(line) is not None
1264
1265 def run_cargo(self):
1266 """Calls cargo -v and save its output to ./cargo.out."""
1267 if self.skip_cargo:
1268 return self
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001269 cargo_toml = './Cargo.toml'
1270 cargo_out = './cargo.out'
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001271 # Do not use Cargo.lock, because .bp rules are designed to
1272 # run with "latest" crates avaialable on Android.
1273 cargo_lock = './Cargo.lock'
1274 cargo_lock_saved = './cargo.lock.saved'
1275 had_cargo_lock = os.path.exists(cargo_lock)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001276 if not os.access(cargo_toml, os.R_OK):
1277 print('ERROR: Cannot find or read', cargo_toml)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001278 return self
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001279 if not self.dry_run:
1280 if os.path.exists(cargo_out):
1281 os.remove(cargo_out)
1282 if not self.args.use_cargo_lock and had_cargo_lock: # save it
1283 os.rename(cargo_lock, cargo_lock_saved)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001284 cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> ' + cargo_out + ' 2>&1'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001285 # set up search PATH for cargo to find the correct rustc
1286 saved_path = os.environ['PATH']
1287 os.environ['PATH'] = os.path.dirname(self.cargo_path) + ':' + saved_path
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001288 # Add [workspace] to Cargo.toml if it is not there.
1289 added_workspace = False
1290 if self.args.add_workspace:
1291 with open(cargo_toml, 'r') as in_file:
1292 cargo_toml_lines = in_file.readlines()
1293 found_workspace = '[workspace]\n' in cargo_toml_lines
1294 if found_workspace:
1295 print('### WARNING: found [workspace] in Cargo.toml')
1296 else:
1297 with open(cargo_toml, 'a') as out_file:
1298 out_file.write('[workspace]\n')
1299 added_workspace = True
1300 if self.args.verbose:
1301 print('### INFO: added [workspace] to Cargo.toml')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001302 for c in self.cargo:
1303 features = ''
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001304 if c != 'clean':
1305 if self.args.features is not None:
1306 features = ' --no-default-features'
1307 if self.args.features:
1308 features += ' --features ' + self.args.features
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001309 cmd_v_flag = ' -vv ' if self.args.vv else ' -v '
1310 cmd = self.cargo_path + cmd_v_flag
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001311 cmd += c + features + cmd_tail
1312 if self.args.rustflags and c != 'clean':
1313 cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
1314 if self.dry_run:
1315 print('Dry-run skip:', cmd)
1316 else:
1317 if self.args.verbose:
1318 print('Running:', cmd)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001319 with open(cargo_out, 'a') as out_file:
1320 out_file.write('### Running: ' + cmd + '\n')
Joel Galenson6bf54e32021-05-17 10:54:50 -07001321 ret = os.system(cmd)
1322 if ret != 0:
1323 print('*** There was an error while running cargo. ' +
1324 'See the cargo.out file for details.')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001325 if added_workspace: # restore original Cargo.toml
1326 with open(cargo_toml, 'w') as out_file:
1327 out_file.writelines(cargo_toml_lines)
1328 if self.args.verbose:
1329 print('### INFO: restored original Cargo.toml')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001330 os.environ['PATH'] = saved_path
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001331 if not self.dry_run:
1332 if not had_cargo_lock: # restore to no Cargo.lock state
1333 os.remove(cargo_lock)
1334 elif not self.args.use_cargo_lock: # restore saved Cargo.lock
1335 os.rename(cargo_lock_saved, cargo_lock)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001336 return self
1337
1338 def dump_dependencies(self):
1339 """Append dependencies and their features to Android.bp."""
1340 if not self.dependencies:
1341 return
1342 dependent_list = list()
1343 for c in self.dependencies:
1344 dependent_list.append(c.feature_list())
1345 sorted_dependencies = sorted(set(dependent_list))
1346 self.init_bp_file('Android.bp')
1347 with open('Android.bp', 'a') as outf:
1348 outf.write('\n// dependent_library ["feature_list"]\n')
1349 for s in sorted_dependencies:
1350 outf.write('// ' + s + '\n')
1351
1352 def dump_pkg_obj2cc(self):
1353 """Dump debug info of the pkg_obj2cc map."""
1354 if not self.args.debug:
1355 return
1356 self.init_bp_file('Android.bp')
1357 with open('Android.bp', 'a') as outf:
1358 sorted_pkgs = sorted(self.pkg_obj2cc.keys())
1359 for pkg in sorted_pkgs:
1360 if not self.pkg_obj2cc[pkg]:
1361 continue
1362 outf.write('\n// obj => src for %s\n' % pkg)
1363 obj2cc = self.pkg_obj2cc[pkg]
1364 for obj in sorted(obj2cc.keys()):
1365 outf.write('// ' + short_out_name(pkg, obj) + ' => ' +
1366 short_out_name(pkg, obj2cc[obj].src) + '\n')
1367
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001368 def apply_patch(self):
1369 """Apply local patch file if it is given."""
1370 if self.args.patch:
1371 if self.dry_run:
1372 print('Dry-run skip patch file:', self.args.patch)
1373 else:
1374 if not os.path.exists(self.args.patch):
1375 self.append_to_bp('ERROR cannot find patch file: ' + self.args.patch)
1376 return self
1377 if self.args.verbose:
1378 print('### INFO: applying local patch file:', self.args.patch)
Joel Galenson7e8247e2021-05-20 18:51:42 -07001379 subprocess.run(['patch', '-s', '--no-backup-if-mismatch', './Android.bp',
1380 self.args.patch], check=True)
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001381 return self
1382
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001383 def gen_bp(self):
1384 """Parse cargo.out and generate Android.bp files."""
1385 if self.dry_run:
1386 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp')
1387 elif os.path.exists(CARGO_OUT):
1388 self.find_root_pkg()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001389 if self.args.copy_out:
1390 self.copy_out_files()
1391 elif self.find_out_files() and self.has_used_out_dir():
1392 print('WARNING: ' + self.root_pkg + ' has cargo output files; ' +
1393 'please rerun with the --copy-out flag.')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001394 with open(CARGO_OUT, 'r') as cargo_out:
1395 self.parse(cargo_out, 'Android.bp')
1396 self.crates.sort(key=get_module_name)
1397 for obj in self.cc_objects:
1398 obj.dump()
1399 self.dump_pkg_obj2cc()
1400 for crate in self.crates:
1401 crate.dump()
1402 dumped_libs = set()
1403 for lib in self.ar_objects:
1404 if lib.pkg == self.root_pkg:
1405 lib_name = file_base_name(lib.lib)
1406 if lib_name not in dumped_libs:
1407 dumped_libs.add(lib_name)
1408 lib.dump()
Joel Galenson5664f2a2021-06-10 10:13:49 -07001409 if self.args.add_toplevel_block:
1410 with open(self.args.add_toplevel_block, 'r') as f:
1411 self.append_to_bp('\n' + f.read() + '\n')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001412 if self.args.dependencies and self.dependencies:
1413 self.dump_dependencies()
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001414 if self.errors:
Joel Galenson3f42f802021-04-07 12:42:17 -07001415 self.append_to_bp('\n' + ERRORS_LINE + '\n' + self.errors)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001416 return self
1417
1418 def add_ar_object(self, obj):
1419 self.ar_objects.append(obj)
1420
1421 def add_cc_object(self, obj):
1422 self.cc_objects.append(obj)
1423
1424 def add_crate(self, crate):
1425 """Merge crate with someone in crates, or append to it. Return crates."""
1426 if crate.skip_crate():
1427 if self.args.debug: # include debug info of all crates
1428 self.crates.append(crate)
1429 if self.args.dependencies: # include only dependent crates
1430 if (is_dependent_file_path(crate.main_src) and
1431 not is_build_crate_name(crate.crate_name)):
1432 self.dependencies.append(crate)
1433 else:
1434 for c in self.crates:
1435 if c.merge(crate, 'Android.bp'):
1436 return
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001437 # If not merged, decide module type and name now.
1438 crate.decide_module_type()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001439 self.crates.append(crate)
1440
1441 def find_warning_owners(self):
1442 """For each warning file, find its owner crate."""
1443 missing_owner = False
1444 for f in self.warning_files:
1445 cargo_dir = '' # find lowest crate, with longest path
1446 owner = None # owner crate of this warning
1447 for c in self.crates:
1448 if (f.startswith(c.cargo_dir + '/') and
1449 len(cargo_dir) < len(c.cargo_dir)):
1450 cargo_dir = c.cargo_dir
1451 owner = c
1452 if owner:
1453 owner.has_warning = True
1454 else:
1455 missing_owner = True
1456 if missing_owner and os.path.exists('Cargo.toml'):
1457 # owner is the root cargo, with empty cargo_dir
1458 for c in self.crates:
1459 if not c.cargo_dir:
1460 c.has_warning = True
1461
1462 def rustc_command(self, n, rustc_line, line, outf_name):
1463 """Process a rustc command line from cargo -vv output."""
1464 # cargo build -vv output can have multiple lines for a rustc command
1465 # due to '\n' in strings for environment variables.
1466 # strip removes leading spaces and '\n' at the end
1467 new_rustc = (rustc_line.strip() + line) if rustc_line else line
1468 # Use an heuristic to detect the completions of a multi-line command.
1469 # This might fail for some very rare case, but easy to fix manually.
1470 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0:
1471 return new_rustc
1472 if RUSTC_VV_CMD_ARGS.match(new_rustc):
1473 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1)
1474 self.add_crate(Crate(self, outf_name).parse(n, args))
1475 else:
1476 self.assert_empty_vv_line(new_rustc)
1477 return ''
1478
1479 def cc_ar_command(self, n, groups, outf_name):
1480 pkg = groups.group(1)
1481 line = groups.group(3)
1482 if groups.group(2) == 'cc':
1483 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line))
1484 else:
1485 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line))
1486
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001487 def append_to_bp(self, line):
1488 self.init_bp_file('Android.bp')
1489 with open('Android.bp', 'a') as outf:
1490 outf.write(line)
1491
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001492 def assert_empty_vv_line(self, line):
1493 if line: # report error if line is not empty
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001494 self.append_to_bp('ERROR -vv line: ' + line)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001495 return ''
1496
1497 def parse(self, inf, outf_name):
1498 """Parse rustc and warning messages in inf, return a list of Crates."""
1499 n = 0 # line number
1500 prev_warning = False # true if the previous line was warning: ...
1501 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
1502 for line in inf:
1503 n += 1
1504 if line.startswith('warning: '):
1505 prev_warning = True
1506 rustc_line = self.assert_empty_vv_line(rustc_line)
1507 continue
1508 new_rustc = ''
1509 if RUSTC_PAT.match(line):
1510 args_line = RUSTC_PAT.match(line).group(1)
1511 self.add_crate(Crate(self, outf_name).parse(n, args_line))
1512 self.assert_empty_vv_line(rustc_line)
1513 elif rustc_line or RUSTC_VV_PAT.match(line):
1514 new_rustc = self.rustc_command(n, rustc_line, line, outf_name)
1515 elif CC_AR_VV_PAT.match(line):
1516 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name)
1517 elif prev_warning and WARNING_FILE_PAT.match(line):
1518 self.assert_empty_vv_line(rustc_line)
1519 fpath = WARNING_FILE_PAT.match(line).group(1)
1520 if fpath[0] != '/': # ignore absolute path
1521 self.warning_files.add(fpath)
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001522 elif line.startswith('error: ') or line.startswith('error[E'):
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001523 if not self.args.ignore_cargo_errors:
1524 self.errors += line
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001525 prev_warning = False
1526 rustc_line = new_rustc
1527 self.find_warning_owners()
1528
1529
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001530def get_parser():
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001531 """Parse main arguments."""
1532 parser = argparse.ArgumentParser('cargo2android')
1533 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001534 '--add_workspace',
1535 action='store_true',
1536 default=False,
1537 help=('append [workspace] to Cargo.toml before calling cargo,' +
1538 ' to treat current directory as root of package source;' +
1539 ' otherwise the relative source file path in generated' +
1540 ' .bp file will be from the parent directory.'))
1541 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001542 '--cargo',
1543 action='append',
1544 metavar='args_string',
1545 help=('extra cargo build -v args in a string, ' +
1546 'each --cargo flag calls cargo build -v once'))
1547 parser.add_argument(
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001548 '--cargo_bin',
1549 type=str,
1550 help='use cargo in the cargo_bin directory instead of the prebuilt one')
1551 parser.add_argument(
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001552 '--copy-out',
1553 action='store_true',
1554 default=False,
1555 help=('only for root directory, ' +
1556 'copy build.rs output to ./out/* and add a genrule to copy ' +
1557 './out/* to genrule output; for crates with code pattern: ' +
1558 'include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))'))
1559 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001560 '--debug',
1561 action='store_true',
1562 default=False,
1563 help='dump debug info into Android.bp')
1564 parser.add_argument(
1565 '--dependencies',
1566 action='store_true',
1567 default=False,
1568 help='dump debug info of dependent crates')
1569 parser.add_argument(
1570 '--device',
1571 action='store_true',
1572 default=False,
1573 help='run cargo also for a default device target')
1574 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001575 '--features',
1576 type=str,
1577 help=('pass features to cargo build, ' +
1578 'empty string means no default features'))
1579 parser.add_argument(
1580 '--global_defaults',
1581 type=str,
1582 help='add a defaults name to every module')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001583 parser.add_argument(
1584 '--host-first-multilib',
1585 action='store_true',
1586 default=False,
1587 help=('add a compile_multilib:"first" property ' +
1588 'to Android.bp host modules.'))
1589 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001590 '--ignore-cargo-errors',
1591 action='store_true',
1592 default=False,
1593 help='do not append cargo/rustc error messages to Android.bp')
1594 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001595 '--no-host',
1596 action='store_true',
1597 default=False,
1598 help='do not run cargo for the host; only for the device target')
1599 parser.add_argument(
1600 '--no-subdir',
1601 action='store_true',
1602 default=False,
1603 help='do not output anything for sub-directories')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001604 parser.add_argument(
1605 '--onefile',
1606 action='store_true',
1607 default=False,
1608 help=('output all into one ./Android.bp, default will generate ' +
1609 'one Android.bp per Cargo.toml in subdirectories'))
1610 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001611 '--patch',
1612 type=str,
1613 help='apply the given patch file to generated ./Android.bp')
1614 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001615 '--run',
1616 action='store_true',
1617 default=False,
1618 help='run it, default is dry-run')
1619 parser.add_argument('--rustflags', type=str, help='passing flags to rustc')
1620 parser.add_argument(
1621 '--skipcargo',
1622 action='store_true',
1623 default=False,
1624 help='skip cargo command, parse cargo.out, and generate Android.bp')
1625 parser.add_argument(
1626 '--tests',
1627 action='store_true',
1628 default=False,
1629 help='run cargo build --tests after normal build')
1630 parser.add_argument(
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001631 '--use-cargo-lock',
1632 action='store_true',
1633 default=False,
1634 help=('run cargo build with existing Cargo.lock ' +
1635 '(used when some latest dependent crates failed)'))
1636 parser.add_argument(
Joel Galensond9c4de62021-04-23 10:26:40 -07001637 '--min-sdk-version',
1638 type=str,
1639 help='Minimum SDK version')
1640 parser.add_argument(
1641 '--apex-available',
1642 nargs='*',
1643 help='Mark the main library as apex_available with the given apexes.')
1644 parser.add_argument(
Ivan Lozano91920862021-07-19 10:49:08 -04001645 '--vendor-available',
1646 action='store_true',
1647 default=False,
1648 help='Mark the main library as vendor_available.')
1649 parser.add_argument(
1650 '--vendor-ramdisk-available',
1651 action='store_true',
1652 default=False,
1653 help='Mark the main library as vendor_ramdisk_available.')
1654 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001655 '--force-rlib',
1656 action='store_true',
1657 default=False,
1658 help='Make the main library an rlib.')
1659 parser.add_argument(
Joel Galenson12467e52021-07-12 14:33:28 -07001660 '--whole-static-libs',
1661 nargs='*',
1662 default=[],
1663 help='Make the given libraries (without lib prefixes) whole_static_libs.')
1664 parser.add_argument(
Joel Galensone4f53882021-07-19 11:14:55 -07001665 '--test-data',
1666 nargs='*',
1667 default=[],
1668 help=('Add the given file to the given test\'s data property. ' +
1669 'Usage: test-path=data-path'))
1670 parser.add_argument(
Joel Galenson97e414a2021-05-27 09:42:32 -07001671 '--dependency-blocklist',
1672 nargs='*',
1673 default=[],
Joel Galenson12467e52021-07-12 14:33:28 -07001674 help='Do not emit the given dependencies (without lib prefixes).')
Joel Galenson97e414a2021-05-27 09:42:32 -07001675 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001676 '--lib-blocklist',
1677 nargs='*',
1678 default=[],
Joel Galenson12467e52021-07-12 14:33:28 -07001679 help='Do not emit the given C libraries as dependencies (without lib prefixes).')
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001680 parser.add_argument(
Joel Galensonf6b3c912021-06-03 16:00:54 -07001681 '--test-blocklist',
1682 nargs='*',
1683 default=[],
1684 help=('Do not emit the given tests. ' +
1685 'Pass the path to the test file to exclude.'))
1686 parser.add_argument(
Joel Galenson3d6d1e72021-06-07 15:00:24 -07001687 '--cfg-blocklist',
1688 nargs='*',
1689 default=[],
1690 help='Do not emit the given cfg.')
1691 parser.add_argument(
Joel Galenson5664f2a2021-06-10 10:13:49 -07001692 '--add-toplevel-block',
1693 type=str,
1694 help='Add the contents of the given file to the top level of the Android.bp.')
1695 parser.add_argument(
1696 '--add-module-block',
1697 type=str,
1698 help='Add the contents of the given file to the main module.')
1699 parser.add_argument(
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001700 '--no-test-mapping',
1701 action='store_true',
1702 default=False,
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001703 help='Deprecated. Has no effect.')
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001704 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001705 '--verbose',
1706 action='store_true',
1707 default=False,
1708 help='echo executed commands')
1709 parser.add_argument(
1710 '--vv',
1711 action='store_true',
1712 default=False,
1713 help='run cargo with -vv instead of default -v')
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001714 parser.add_argument(
1715 '--dump-config-and-exit',
1716 type=str,
1717 help=('Dump command-line arguments (minus this flag) to a config file and exit. ' +
1718 'This is intended to help migrate from command line options to config files.'))
1719 parser.add_argument(
1720 '--config',
1721 type=str,
1722 help=('Load command-line options from the given config file. ' +
1723 'Options in this file will override those passed on the command line.'))
1724 return parser
1725
1726
1727def parse_args(parser):
1728 """Parses command-line options."""
1729 args = parser.parse_args()
1730 # Use the values specified in a config file if one was found.
1731 if args.config:
1732 with open(args.config, 'r') as f:
1733 config = json.load(f)
1734 args_dict = vars(args)
1735 for arg in config:
1736 args_dict[arg.replace('-', '_')] = config[arg]
1737 return args
1738
1739
1740def dump_config(parser, args):
1741 """Writes the non-default command-line options to the specified file."""
1742 args_dict = vars(args)
1743 # Filter out the arguments that have their default value.
Joel Galenson367360c2021-04-29 14:31:43 -07001744 # Also filter certain "temporary" arguments.
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001745 non_default_args = {}
1746 for arg in args_dict:
Joel Galenson367360c2021-04-29 14:31:43 -07001747 if args_dict[arg] != parser.get_default(
1748 arg) and arg != 'dump_config_and_exit' and arg != 'no_test_mapping':
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001749 non_default_args[arg.replace('_', '-')] = args_dict[arg]
1750 # Write to the specified file.
1751 with open(args.dump_config_and_exit, 'w') as f:
1752 json.dump(non_default_args, f, indent=2, sort_keys=True)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001753
1754
1755def main():
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001756 parser = get_parser()
1757 args = parse_args(parser)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001758 if not args.run: # default is dry-run
1759 print(DRY_RUN_NOTE)
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001760 if args.dump_config_and_exit:
1761 dump_config(parser, args)
1762 else:
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001763 Runner(args).run_cargo().gen_bp().apply_patch()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001764
1765
1766if __name__ == '__main__':
1767 main()