blob: 9dd5f90d0aeca63dc575412f24c144e808b9dfe4 [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 """
Joel Galensonc5186502021-08-16 11:22:47 -0700486 cargo_metadata = subprocess.run([self.runner.cargo_path, 'metadata', '--no-deps'],
487 cwd=os.path.abspath(self.cargo_dir),
488 stdout=subprocess.PIPE)
Ivan Lozano26aa1c32021-08-16 11:20:32 -0400489 if cargo_metadata.returncode:
490 self.errors += ('ERROR: unable to get cargo metadata for package version; ' +
491 'return code ' + cargo_metadata.returncode + '\n')
492 else:
493 metadata_json = json.loads(cargo_metadata.stdout)
494 if len(metadata_json['packages']) > 1:
495 for package in metadata_json['packages']:
496 # package names may contain '-', but is changed to '_' in the crate_name
497 if package['name'].replace('-','_') == self.crate_name:
498 self.cargo_pkg_version = package['version']
499 break
500 else:
501 self.cargo_pkg_version = metadata_json['packages'][0]['version']
502
503 if not self.cargo_pkg_version:
504 self.errors += ('ERROR: Unable to retrieve package version; ' +
505 'to disable, run with arg "--no-pkg-vers"\n')
506
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800507 def dump_line(self):
508 self.write('\n// Line ' + str(self.line_num) + ' ' + self.line)
509
510 def feature_list(self):
511 """Return a string of main_src + "feature_list"."""
512 pkg = self.main_src
513 if pkg.startswith('.../'): # keep only the main package name
514 pkg = re.sub('/.*', '', pkg[4:])
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700515 elif pkg.startswith('/'): # use relative path for a local package
516 pkg = os.path.relpath(pkg)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800517 if not self.features:
518 return pkg
519 return pkg + ' "' + ','.join(self.features) + '"'
520
521 def dump_skip_crate(self, kind):
522 if self.debug:
523 self.write('\n// IGNORED: ' + kind + ' ' + self.main_src)
524 return self
525
526 def skip_crate(self):
527 """Return crate_name or a message if this crate should be skipped."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700528 if (is_build_crate_name(self.crate_name) or
529 self.crate_name in EXCLUDED_CRATES):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800530 return self.crate_name
531 if is_dependent_file_path(self.main_src):
532 return 'dependent crate'
533 return ''
534
535 def dump(self):
536 """Dump all error/debug/module code to the output .bp file."""
537 self.runner.init_bp_file(self.outf_name)
538 with open(self.outf_name, 'a') as outf:
539 self.outf = outf
540 if self.errors:
541 self.dump_line()
542 self.write(self.errors)
543 elif self.skip_crate():
544 self.dump_skip_crate(self.skip_crate())
545 else:
546 if self.debug:
547 self.dump_debug_info()
548 self.dump_android_module()
549
550 def dump_debug_info(self):
551 """Dump parsed data, when cargo2android is called with --debug."""
552
553 def dump(name, value):
554 self.write('//%12s = %s' % (name, value))
555
556 def opt_dump(name, value):
557 if value:
558 dump(name, value)
559
560 def dump_list(fmt, values):
561 for v in values:
562 self.write(fmt % v)
563
564 self.dump_line()
565 dump('module_name', self.module_name)
566 dump('crate_name', self.crate_name)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700567 dump('crate_types', self.crate_types)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800568 dump('main_src', self.main_src)
569 dump('has_warning', self.has_warning)
570 dump('for_host', self.host_supported)
571 dump('for_device', self.device_supported)
572 dump('module_type', self.module_type)
573 opt_dump('target', self.target)
574 opt_dump('edition', self.edition)
575 opt_dump('emit_list', self.emit_list)
576 opt_dump('cap_lints', self.cap_lints)
577 dump_list('// cfg = %s', self.cfgs)
578 dump_list('// cfg = \'feature "%s"\'', self.features)
579 # TODO(chh): escape quotes in self.features, but not in other dump_list
580 dump_list('// codegen = %s', self.codegens)
581 dump_list('// externs = %s', self.externs)
582 dump_list('// -l static = %s', self.static_libs)
583 dump_list('// -l (dylib) = %s', self.shared_libs)
584
585 def dump_android_module(self):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700586 """Dump one or more Android module definition, depending on crate_types."""
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700587 if len(self.crate_types) == 1:
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700588 self.dump_single_type_android_module()
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700589 return
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700590 if 'test' in self.crate_types:
591 self.write('\nERROR: multiple crate types cannot include test type')
592 return
593 # Dump one Android module per crate_type.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700594 for crate_type in self.crate_types:
595 self.decide_one_module_type(crate_type)
596 self.dump_one_android_module(crate_type)
597
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700598 def build_default_name(self):
599 """Return a short and readable name for the rust_defaults module."""
Joel Galensond37d7e62021-07-13 09:03:01 -0700600 # Choices: (1) root_pkg + '_test'? + '_defaults',
601 # (2) root_pkg + '_test'? + '_defaults_' + crate_name
602 # (3) root_pkg + '_test'? + '_defaults_' + main_src_basename_path
603 # (4) root_pkg + '_test'? + '_defaults_' + a_positive_sequence_number
604 test = "_test" if self.crate_types == ['test'] else ""
605 name1 = altered_defaults(self.root_pkg) + test + '_defaults'
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700606 if self.runner.try_claim_module_name(name1, self):
607 return name1
608 name2 = name1 + '_' + self.crate_name
609 if self.runner.try_claim_module_name(name2, self):
610 return name2
611 name3 = name1 + '_' + self.main_src_basename_path()
612 if self.runner.try_claim_module_name(name3, self):
613 return name3
614 return self.runner.claim_module_name(name1, self, 0)
615
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700616 def dump_srcs_list(self):
617 """Dump the srcs list, for defaults or regular modules."""
618 if len(self.srcs) > 1:
619 srcs = sorted(set(self.srcs)) # make a copy and dedup
620 else:
621 srcs = [self.main_src]
622 copy_out = self.runner.copy_out_module_name()
623 if copy_out:
624 srcs.append(':' + copy_out)
625 self.dump_android_property_list('srcs', '"%s"', srcs)
626
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700627 def dump_defaults_module(self):
628 """Dump a rust_defaults module to be shared by other modules."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700629 name = self.build_default_name()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700630 self.defaults = name
631 self.write('\nrust_defaults {')
632 self.write(' name: "' + name + '",')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700633 if self.runner.args.global_defaults:
634 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700635 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700636 if len(self.srcs) == 1: # only one source file; share it in defaults
637 self.default_srcs = True
638 if self.has_warning and not self.cap_lints:
639 self.write(' // has rustc warnings')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700640 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700641 if 'test' in self.crate_types:
642 self.write(' test_suites: ["general-tests"],')
643 self.write(' auto_gen_config: true,')
644 self.dump_edition_flags_libs()
Joel Galensone4f53882021-07-19 11:14:55 -0700645 if 'test' in self.crate_types and len(self.srcs) == 1:
646 self.dump_test_data()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700647 self.write('}')
648
649 def dump_single_type_android_module(self):
650 """Dump one simple Android module, which has only one crate_type."""
651 crate_type = self.crate_types[0]
652 if crate_type != 'test':
653 # do not change self.stem or self.module_name
654 self.dump_one_android_module(crate_type)
655 return
656 # Dump one test module per source file, and separate host and device tests.
657 # crate_type == 'test'
Joel Galensonf6b3c912021-06-03 16:00:54 -0700658 self.srcs = [src for src in self.srcs if not src in self.runner.args.test_blocklist]
659 if ((self.host_supported and self.device_supported and len(self.srcs) > 0) or
660 len(self.srcs) > 1):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700661 self.srcs = sorted(set(self.srcs))
662 self.dump_defaults_module()
663 saved_srcs = self.srcs
664 for src in saved_srcs:
665 self.srcs = [src]
666 saved_device_supported = self.device_supported
667 saved_host_supported = self.host_supported
668 saved_main_src = self.main_src
669 self.main_src = src
670 if saved_host_supported:
671 self.device_supported = False
672 self.host_supported = True
673 self.module_name = self.test_module_name()
674 self.decide_one_module_type(crate_type)
675 self.dump_one_android_module(crate_type)
676 if saved_device_supported:
677 self.device_supported = True
678 self.host_supported = False
679 self.module_name = self.test_module_name()
680 self.decide_one_module_type(crate_type)
681 self.dump_one_android_module(crate_type)
682 self.host_supported = saved_host_supported
683 self.device_supported = saved_device_supported
684 self.main_src = saved_main_src
685 self.srcs = saved_srcs
686
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700687 def dump_one_android_module(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800688 """Dump one Android module definition."""
689 if not self.module_type:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700690 self.write('\nERROR: unknown crate_type ' + crate_type)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800691 return
692 self.write('\n' + self.module_type + ' {')
693 self.dump_android_core_properties()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700694 if not self.defaults:
695 self.dump_edition_flags_libs()
696 if self.runner.args.host_first_multilib and self.host_supported and crate_type != 'test':
697 self.write(' compile_multilib: "first",')
Joel Galensond9c4de62021-04-23 10:26:40 -0700698 if self.runner.args.apex_available and crate_type == 'lib':
699 self.write(' apex_available: [')
700 for apex in self.runner.args.apex_available:
701 self.write(' "%s",' % apex)
702 self.write(' ],')
Ivan Lozano91920862021-07-19 10:49:08 -0400703 if self.runner.args.vendor_available:
704 self.write(' vendor_available: true,')
705 if self.runner.args.vendor_ramdisk_available:
706 self.write(' vendor_ramdisk_available: true,')
Joel Galensond9c4de62021-04-23 10:26:40 -0700707 if self.runner.args.min_sdk_version and crate_type == 'lib':
708 self.write(' min_sdk_version: "%s",' % self.runner.args.min_sdk_version)
Joel Galensone4f53882021-07-19 11:14:55 -0700709 if crate_type == 'test' and not self.default_srcs:
710 self.dump_test_data()
Joel Galenson5664f2a2021-06-10 10:13:49 -0700711 if self.runner.args.add_module_block:
712 with open(self.runner.args.add_module_block, 'r') as f:
713 self.write(' %s,' % f.read().replace('\n', '\n '))
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700714 self.write('}')
715
716 def dump_android_flags(self):
717 """Dump Android module flags property."""
ThiƩbaud Weksteena5a728b2021-04-08 14:23:49 +0200718 if not self.codegens and not self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700719 return
720 self.write(' flags: [')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800721 if self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700722 self.write(' "--cap-lints ' + self.cap_lints + '",')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700723 codegens_fmt = '"-C %s"'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700724 self.dump_android_property_list_items(codegens_fmt, self.codegens)
725 self.write(' ],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700726
727 def dump_edition_flags_libs(self):
728 if self.edition:
729 self.write(' edition: "' + self.edition + '",')
730 self.dump_android_property_list('features', '"%s"', self.features)
Joel Galenson3d6d1e72021-06-07 15:00:24 -0700731 cfgs = [cfg for cfg in self.cfgs if not cfg in self.runner.args.cfg_blocklist]
732 self.dump_android_property_list('cfgs', '"%s"', cfgs)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700733 self.dump_android_flags()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800734 if self.externs:
735 self.dump_android_externs()
Joel Galenson12467e52021-07-12 14:33:28 -0700736 all_static_libs = [lib for lib in self.static_libs if not lib in self.runner.args.lib_blocklist]
737 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 -0700738 self.dump_android_property_list('static_libs', '"lib%s"', static_libs)
Joel Galenson12467e52021-07-12 14:33:28 -0700739 whole_static_libs = [lib for lib in all_static_libs if lib in self.runner.args.whole_static_libs]
740 self.dump_android_property_list('whole_static_libs', '"lib%s"', whole_static_libs)
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700741 shared_libs = [lib for lib in self.shared_libs if not lib in self.runner.args.lib_blocklist]
742 self.dump_android_property_list('shared_libs', '"lib%s"', shared_libs)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800743
Joel Galensone4f53882021-07-19 11:14:55 -0700744 def dump_test_data(self):
745 data = [data for (name, data) in map(lambda kv: kv.split('=', 1), self.runner.args.test_data)
746 if self.srcs == [name]]
747 if data:
748 self.dump_android_property_list('data', '"%s"', data)
749
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700750 def main_src_basename_path(self):
751 return re.sub('/', '_', re.sub('.rs$', '', self.main_src))
752
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800753 def test_module_name(self):
754 """Return a unique name for a test module."""
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700755 # root_pkg+(_host|_device) + '_test_'+source_file_name
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700756 suffix = self.main_src_basename_path()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700757 host_device = '_host'
758 if self.device_supported:
759 host_device = '_device'
760 return self.root_pkg + host_device + '_test_' + suffix
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800761
762 def decide_module_type(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700763 # Use the first crate type for the default/first module.
764 crate_type = self.crate_types[0] if self.crate_types else ''
765 self.decide_one_module_type(crate_type)
766
767 def decide_one_module_type(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800768 """Decide which Android module type to use."""
769 host = '' if self.device_supported else '_host'
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700770 rlib = '_rlib' if self.runner.args.force_rlib else ''
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700771 if crate_type == 'bin': # rust_binary[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800772 self.module_type = 'rust_binary' + host
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700773 # In rare cases like protobuf-codegen, the output binary name must
774 # be renamed to use as a plugin for protoc.
775 self.stem = altered_stem(self.crate_name)
776 self.module_name = altered_name(self.crate_name)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700777 elif crate_type == 'lib': # rust_library[_host]
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700778 # TODO(chh): should this be rust_library[_host]?
779 # Assuming that Cargo.toml do not use both 'lib' and 'rlib',
780 # because we map them both to rlib.
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700781 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800782 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700783 self.module_name = altered_name(self.stem)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700784 elif crate_type == 'rlib': # rust_library[_host]
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700785 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700786 self.stem = 'lib' + self.crate_name
787 self.module_name = altered_name(self.stem)
788 elif crate_type == 'dylib': # rust_library[_host]_dylib
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800789 self.module_type = 'rust_library' + host + '_dylib'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700790 self.stem = 'lib' + self.crate_name
791 self.module_name = altered_name(self.stem) + '_dylib'
792 elif crate_type == 'cdylib': # rust_library[_host]_shared
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500793 self.module_type = 'rust_ffi' + host + '_shared'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700794 self.stem = 'lib' + self.crate_name
795 self.module_name = altered_name(self.stem) + '_shared'
796 elif crate_type == 'staticlib': # rust_library[_host]_static
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500797 self.module_type = 'rust_ffi' + host + '_static'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700798 self.stem = 'lib' + self.crate_name
799 self.module_name = altered_name(self.stem) + '_static'
800 elif crate_type == 'test': # rust_test[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800801 self.module_type = 'rust_test' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700802 # Before do_merge, stem name is based on the --crate-name parameter.
803 # and test module name is based on stem.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800804 self.stem = self.test_module_name()
805 # self.stem will be changed after merging with other tests.
806 # self.stem is NOT used for final test binary name.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700807 # rust_test uses each source file base name as part of output file name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700808 # In do_merge, this function is called again, with a module_name.
809 # We make sure that the module name is unique in each package.
810 if self.module_name:
811 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add
812 # different suffixes and distinguish multiple tests of the same
813 # crate name. We ignore -C and use claim_module_name to get
814 # unique sequential suffix.
815 self.module_name = self.runner.claim_module_name(
816 self.module_name, self, 0)
817 # Now the module name is unique, stem should also match and unique.
818 self.stem = self.module_name
819 elif crate_type == 'proc-macro': # rust_proc_macro
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800820 self.module_type = 'rust_proc_macro'
821 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700822 self.module_name = altered_name(self.stem)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800823 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]?
824 self.module_type = ''
825 self.stem = ''
826
827 def dump_android_property_list_items(self, fmt, values):
828 for v in values:
829 # fmt has quotes, so we need escape_quotes(v)
830 self.write(' ' + (fmt % escape_quotes(v)) + ',')
831
832 def dump_android_property_list(self, name, fmt, values):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700833 if not values:
834 return
835 if len(values) > 1:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800836 self.write(' ' + name + ': [')
837 self.dump_android_property_list_items(fmt, values)
838 self.write(' ],')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700839 else:
840 self.write(' ' + name + ': [' +
841 (fmt % escape_quotes(values[0])) + '],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800842
843 def dump_android_core_properties(self):
844 """Dump the module header, name, stem, etc."""
845 self.write(' name: "' + self.module_name + '",')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700846 # see properties shared by dump_defaults_module
847 if self.defaults:
848 self.write(' defaults: ["' + self.defaults + '"],')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700849 elif self.runner.args.global_defaults:
850 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800851 if self.stem != self.module_name:
852 self.write(' stem: "' + self.stem + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700853 if self.has_warning and not self.cap_lints and not self.default_srcs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700854 self.write(' // has rustc warnings')
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700855 if self.host_supported and self.device_supported and self.module_type != 'rust_proc_macro':
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800856 self.write(' host_supported: true,')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700857 if not self.defaults:
858 self.write(' crate_name: "' + self.crate_name + '",')
Ivan Lozanocc660f12021-08-11 16:49:46 -0400859 if not self.defaults and self.cargo_env_compat:
860 self.write(' cargo_env_compat: true,')
861 self.write(' cargo_pkg_version: "' + self.cargo_pkg_version + '",')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700862 if not self.default_srcs:
863 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700864 if 'test' in self.crate_types and not self.defaults:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800865 # self.root_pkg can have multiple test modules, with different *_tests[n]
866 # names, but their executables can all be installed under the same _tests
867 # directory. When built from Cargo.toml, all tests should have different
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700868 # file or crate names. So we used (root_pkg + '_tests') name as the
869 # relative_install_path.
870 # However, some package like 'slab' can have non-mergeable tests that
871 # must be separated by different module names. So, here we no longer
872 # emit relative_install_path.
873 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800874 self.write(' test_suites: ["general-tests"],')
875 self.write(' auto_gen_config: true,')
Joel Galensone261a152021-01-12 11:31:53 -0800876 if 'test' in self.crate_types and self.host_supported:
877 self.write(' test_options: {')
878 self.write(' unit_test: true,')
879 self.write(' },')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800880
881 def dump_android_externs(self):
882 """Dump the dependent rlibs and dylibs property."""
883 so_libs = list()
884 rust_libs = ''
885 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$')
886 for lib in self.externs:
887 # normal value of lib: "libc = liblibc-*.rlib"
888 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib"
889 # we should use "libgetrandom", not "lib" + "getrandom_package"
890 groups = deps_libname.match(lib)
891 if groups is not None:
892 lib_name = groups.group(1)
893 else:
894 lib_name = re.sub(' .*$', '', lib)
Joel Galenson97e414a2021-05-27 09:42:32 -0700895 if lib_name in self.runner.args.dependency_blocklist:
896 continue
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800897 if lib.endswith('.rlib') or lib.endswith('.rmeta'):
898 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta.
899 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n'
900 elif lib.endswith('.so'):
901 so_libs.append(lib_name)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700902 elif lib != 'proc_macro': # --extern proc_macro is special and ignored
903 rust_libs += ' // ERROR: unknown type of lib ' + lib + '\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800904 if rust_libs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700905 self.write(' rustlibs: [\n' + rust_libs + ' ],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800906 # Are all dependent .so files proc_macros?
907 # TODO(chh): Separate proc_macros and dylib.
908 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs)
909
910
911class ARObject(object):
912 """Information of an "ar" link command."""
913
914 def __init__(self, runner, outf_name):
915 # Remembered global runner and its members.
916 self.runner = runner
917 self.pkg = ''
918 self.outf_name = outf_name # path to Android.bp
919 # "ar" arguments
920 self.line_num = 1
921 self.line = ''
922 self.flags = '' # e.g. "crs"
923 self.lib = '' # e.g. "/.../out/lib*.a"
924 self.objs = list() # e.g. "/.../out/.../*.o"
925
926 def parse(self, pkg, line_num, args_line):
927 """Collect ar obj/lib file names."""
928 self.pkg = pkg
929 self.line_num = line_num
930 self.line = args_line
931 args = args_line.split()
932 num_args = len(args)
933 if num_args < 3:
934 print('ERROR: "ar" command has too few arguments', args_line)
935 else:
936 self.flags = unquote(args[0])
937 self.lib = unquote(args[1])
938 self.objs = sorted(set(map(unquote, args[2:])))
939 return self
940
941 def write(self, s):
942 self.outf.write(s + '\n')
943
944 def dump_debug_info(self):
945 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line)
946 self.write('// ar_object for %12s' % self.pkg)
947 self.write('// flags = %s' % self.flags)
948 self.write('// lib = %s' % short_out_name(self.pkg, self.lib))
949 for o in self.objs:
950 self.write('// obj = %s' % short_out_name(self.pkg, o))
951
952 def dump_android_lib(self):
953 """Write cc_library_static into Android.bp."""
954 self.write('\ncc_library_static {')
955 self.write(' name: "' + file_base_name(self.lib) + '",')
956 self.write(' host_supported: true,')
957 if self.flags != 'crs':
958 self.write(' // ar flags = %s' % self.flags)
959 if self.pkg not in self.runner.pkg_obj2cc:
960 self.write(' ERROR: cannot find source files.\n}')
961 return
962 self.write(' srcs: [')
963 obj2cc = self.runner.pkg_obj2cc[self.pkg]
964 # Note: wflags are ignored.
965 dflags = list()
966 fflags = list()
967 for obj in self.objs:
968 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",')
969 # TODO(chh): union of dflags and flags of all obj
970 # Now, just a temporary hack that uses the last obj's flags
971 dflags = obj2cc[obj].dflags
972 fflags = obj2cc[obj].fflags
973 self.write(' ],')
974 self.write(' cflags: [')
975 self.write(' "-O3",') # TODO(chh): is this default correct?
976 self.write(' "-Wno-error",')
977 for x in fflags:
978 self.write(' "-f' + x + '",')
979 for x in dflags:
980 self.write(' "-D' + x + '",')
981 self.write(' ],')
982 self.write('}')
983
984 def dump(self):
985 """Dump error/debug/module info to the output .bp file."""
986 self.runner.init_bp_file(self.outf_name)
987 with open(self.outf_name, 'a') as outf:
988 self.outf = outf
989 if self.runner.args.debug:
990 self.dump_debug_info()
991 self.dump_android_lib()
992
993
994class CCObject(object):
995 """Information of a "cc" compilation command."""
996
997 def __init__(self, runner, outf_name):
998 # Remembered global runner and its members.
999 self.runner = runner
1000 self.pkg = ''
1001 self.outf_name = outf_name # path to Android.bp
1002 # "cc" arguments
1003 self.line_num = 1
1004 self.line = ''
1005 self.src = ''
1006 self.obj = ''
1007 self.dflags = list() # -D flags
1008 self.fflags = list() # -f flags
1009 self.iflags = list() # -I flags
1010 self.wflags = list() # -W flags
1011 self.other_args = list()
1012
1013 def parse(self, pkg, line_num, args_line):
1014 """Collect cc compilation flags and src/out file names."""
1015 self.pkg = pkg
1016 self.line_num = line_num
1017 self.line = args_line
1018 args = args_line.split()
1019 i = 0
1020 while i < len(args):
1021 arg = args[i]
1022 if arg == '"-c"':
1023 i += 1
1024 if args[i].startswith('"-o'):
1025 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c"
1026 self.obj = unquote(args[i])[2:]
1027 i += 1
1028 self.src = unquote(args[i])
1029 else:
1030 self.src = unquote(args[i])
1031 elif arg == '"-o"':
1032 i += 1
1033 self.obj = unquote(args[i])
1034 elif arg == '"-I"':
1035 i += 1
1036 self.iflags.append(unquote(args[i]))
1037 elif arg.startswith('"-D'):
1038 self.dflags.append(unquote(args[i])[2:])
1039 elif arg.startswith('"-f'):
1040 self.fflags.append(unquote(args[i])[2:])
1041 elif arg.startswith('"-W'):
1042 self.wflags.append(unquote(args[i])[2:])
1043 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or
1044 arg == '"-g3"'):
1045 # ignore -O -m64 -g
1046 self.other_args.append(unquote(args[i]))
1047 i += 1
1048 self.dflags = sorted(set(self.dflags))
1049 self.fflags = sorted(set(self.fflags))
1050 # self.wflags is not sorted because some are order sensitive
1051 # and we ignore them anyway.
1052 if self.pkg not in self.runner.pkg_obj2cc:
1053 self.runner.pkg_obj2cc[self.pkg] = {}
1054 self.runner.pkg_obj2cc[self.pkg][self.obj] = self
1055 return self
1056
1057 def write(self, s):
1058 self.outf.write(s + '\n')
1059
1060 def dump_debug_flags(self, name, flags):
1061 self.write('// ' + name + ':')
1062 for f in flags:
1063 self.write('// %s' % f)
1064
1065 def dump(self):
1066 """Dump only error/debug info to the output .bp file."""
1067 if not self.runner.args.debug:
1068 return
1069 self.runner.init_bp_file(self.outf_name)
1070 with open(self.outf_name, 'a') as outf:
1071 self.outf = outf
1072 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line)
1073 self.write('// cc_object for %12s' % self.pkg)
1074 self.write('// src = %s' % short_out_name(self.pkg, self.src))
1075 self.write('// obj = %s' % short_out_name(self.pkg, self.obj))
1076 self.dump_debug_flags('-I flags', self.iflags)
1077 self.dump_debug_flags('-D flags', self.dflags)
1078 self.dump_debug_flags('-f flags', self.fflags)
1079 self.dump_debug_flags('-W flags', self.wflags)
1080 if self.other_args:
1081 self.dump_debug_flags('other args', self.other_args)
1082
1083
1084class Runner(object):
1085 """Main class to parse cargo -v output and print Android module definitions."""
1086
1087 def __init__(self, args):
1088 self.bp_files = set() # Remember all output Android.bp files.
1089 self.root_pkg = '' # name of package in ./Cargo.toml
1090 # Saved flags, modes, and data.
1091 self.args = args
1092 self.dry_run = not args.run
1093 self.skip_cargo = args.skipcargo
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001094 self.cargo_path = './cargo' # path to cargo, will be set later
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001095 self.checked_out_files = False # to check only once
1096 self.build_out_files = [] # output files generated by build.rs
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001097 # All cc/ar objects, crates, dependencies, and warning files
1098 self.cc_objects = list()
1099 self.pkg_obj2cc = {}
1100 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i]
1101 self.ar_objects = list()
1102 self.crates = list()
1103 self.dependencies = list() # dependent and build script crates
1104 self.warning_files = set()
1105 # Keep a unique mapping from (module name) to crate
1106 self.name_owners = {}
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001107 # Save and dump all errors from cargo to Android.bp.
1108 self.errors = ''
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001109 self.setup_cargo_path()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001110 # Default action is cargo clean, followed by build or user given actions.
1111 if args.cargo:
1112 self.cargo = ['clean'] + args.cargo
1113 else:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001114 default_target = '--target x86_64-unknown-linux-gnu'
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -07001115 # Use the same target for both host and default device builds.
1116 # Same target is used as default in host x86_64 Android compilation.
1117 # Note: b/169872957, prebuilt cargo failed to build vsock
1118 # on x86_64-unknown-linux-musl systems.
1119 self.cargo = ['clean', 'build ' + default_target]
1120 if args.tests:
1121 self.cargo.append('build --tests ' + default_target)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001122
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001123 def setup_cargo_path(self):
1124 """Find cargo in the --cargo_bin or prebuilt rust bin directory."""
1125 if self.args.cargo_bin:
1126 self.cargo_path = os.path.join(self.args.cargo_bin, 'cargo')
1127 if not os.path.isfile(self.cargo_path):
1128 sys.exit('ERROR: cannot find cargo in ' + self.args.cargo_bin)
1129 print('WARNING: using cargo in ' + self.args.cargo_bin)
1130 return
1131 # We have only tested this on Linux.
1132 if platform.system() != 'Linux':
1133 sys.exit('ERROR: this script has only been tested on Linux with cargo.')
1134 # Assuming that this script is in development/scripts.
1135 my_dir = os.path.dirname(os.path.abspath(__file__))
1136 linux_dir = os.path.join(my_dir, '..', '..',
1137 'prebuilts', 'rust', 'linux-x86')
1138 if not os.path.isdir(linux_dir):
1139 sys.exit('ERROR: cannot find directory ' + linux_dir)
1140 rust_version = self.find_rust_version(my_dir, linux_dir)
1141 cargo_bin = os.path.join(linux_dir, rust_version, 'bin')
1142 self.cargo_path = os.path.join(cargo_bin, 'cargo')
1143 if not os.path.isfile(self.cargo_path):
1144 sys.exit('ERROR: cannot find cargo in ' + cargo_bin
1145 + '; please try --cargo_bin= flag.')
1146 return
1147
1148 def find_rust_version(self, my_dir, linux_dir):
1149 """Use my script directory, find prebuilt rust version."""
1150 # First look up build/soong/rust/config/global.go.
1151 path2global = os.path.join(my_dir, '..', '..',
1152 'build', 'soong', 'rust', 'config', 'global.go')
1153 if os.path.isfile(path2global):
1154 # try to find: RustDefaultVersion = "1.44.0"
1155 version_pat = re.compile(
1156 r'\s*RustDefaultVersion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+)".*$')
1157 with open(path2global, 'r') as inf:
1158 for line in inf:
1159 result = version_pat.match(line)
1160 if result:
1161 return result.group(1)
1162 print('WARNING: cannot find RustDefaultVersion in ' + path2global)
1163 # Otherwise, find the newest (largest) version number in linux_dir.
1164 rust_version = (0, 0, 0) # the prebuilt version to use
1165 version_pat = re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)$')
1166 for dir_name in os.listdir(linux_dir):
1167 result = version_pat.match(dir_name)
1168 if not result:
1169 continue
1170 version = (result.group(1), result.group(2), result.group(3))
1171 if version > rust_version:
1172 rust_version = version
1173 return '.'.join(rust_version)
1174
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001175 def find_out_files(self):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001176 # list1 has build.rs output for normal crates
1177 list1 = glob.glob(TARGET_TMP + '/*/*/build/' + self.root_pkg + '-*/out/*')
1178 # list2 has build.rs output for proc-macro crates
1179 list2 = glob.glob(TARGET_TMP + '/*/build/' + self.root_pkg + '-*/out/*')
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001180 return list1 + list2
1181
1182 def copy_out_files(self):
1183 """Copy build.rs output files to ./out and set up build_out_files."""
1184 if self.checked_out_files:
1185 return
1186 self.checked_out_files = True
1187 cargo_out_files = self.find_out_files()
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001188 out_files = set()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001189 if cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001190 os.makedirs('out', exist_ok=True)
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001191 for path in cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001192 file_name = path.split('/')[-1]
1193 out_files.add(file_name)
1194 shutil.copy(path, 'out/' + file_name)
1195 self.build_out_files = sorted(out_files)
1196
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001197 def has_used_out_dir(self):
1198 """Returns true if env!("OUT_DIR") is found."""
1199 return 0 == os.system('grep -rl --exclude build.rs --include \\*.rs' +
1200 ' \'env!("OUT_DIR")\' * > /dev/null')
1201
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001202 def copy_out_module_name(self):
1203 if self.args.copy_out and self.build_out_files:
1204 return 'copy_' + self.root_pkg + '_build_out'
1205 else:
1206 return ''
1207
Haibo Huang0f72c952021-03-19 11:34:15 -07001208 def read_license(self, name):
1209 if not os.path.isfile(name):
1210 return ''
1211 license = ''
1212 with open(name, 'r') as intf:
1213 line = intf.readline()
1214 # Firstly skip ANDROID_BP_HEADER
1215 while line.startswith('//'):
1216 line = intf.readline()
Joel Galensond9d13b82021-04-05 11:27:55 -07001217 # Read all lines until we see a rust_* or genrule rule.
1218 while line != '' and not (line.startswith('rust_') or line.startswith('genrule {')):
Haibo Huang0f72c952021-03-19 11:34:15 -07001219 license += line
1220 line = intf.readline()
1221 return license.strip()
1222
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001223 def dump_copy_out_module(self, outf):
1224 """Output the genrule module to copy out/* to $(genDir)."""
1225 copy_out = self.copy_out_module_name()
1226 if not copy_out:
1227 return
1228 outf.write('\ngenrule {\n')
1229 outf.write(' name: "' + copy_out + '",\n')
1230 outf.write(' srcs: ["out/*"],\n')
1231 outf.write(' cmd: "cp $(in) $(genDir)",\n')
1232 if len(self.build_out_files) > 1:
1233 outf.write(' out: [\n')
1234 for f in self.build_out_files:
1235 outf.write(' "' + f + '",\n')
1236 outf.write(' ],\n')
1237 else:
1238 outf.write(' out: ["' + self.build_out_files[0] + '"],\n')
1239 outf.write('}\n')
1240
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001241 def init_bp_file(self, name):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001242 # name could be Android.bp or sub_dir_path/Android.bp
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001243 if name not in self.bp_files:
1244 self.bp_files.add(name)
Haibo Huang0f72c952021-03-19 11:34:15 -07001245 license_section = self.read_license(name)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001246 with open(name, 'w') as outf:
Joel Galenson367360c2021-04-29 14:31:43 -07001247 print_args = filter(lambda x: x != "--no-test-mapping", sys.argv[1:])
1248 outf.write(ANDROID_BP_HEADER.format(args=' '.join(print_args)))
Haibo Huang0f72c952021-03-19 11:34:15 -07001249 outf.write('\n')
1250 outf.write(license_section)
1251 outf.write('\n')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001252 # at most one copy_out module per .bp file
1253 self.dump_copy_out_module(outf)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001254
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001255 def try_claim_module_name(self, name, owner):
1256 """Reserve and return True if it has not been reserved yet."""
1257 if name not in self.name_owners or owner == self.name_owners[name]:
1258 self.name_owners[name] = owner
1259 return True
1260 return False
1261
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001262 def claim_module_name(self, prefix, owner, counter):
1263 """Return prefix if not owned yet, otherwise, prefix+str(counter)."""
1264 while True:
1265 name = prefix
1266 if counter > 0:
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001267 name += '_' + str(counter)
1268 if self.try_claim_module_name(name, owner):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001269 return name
1270 counter += 1
1271
1272 def find_root_pkg(self):
1273 """Read name of [package] in ./Cargo.toml."""
1274 if not os.path.exists('./Cargo.toml'):
1275 return
1276 with open('./Cargo.toml', 'r') as inf:
1277 pkg_section = re.compile(r'^ *\[package\]')
1278 name = re.compile('^ *name *= * "([^"]*)"')
1279 in_pkg = False
1280 for line in inf:
1281 if in_pkg:
1282 if name.match(line):
1283 self.root_pkg = name.match(line).group(1)
1284 break
1285 else:
1286 in_pkg = pkg_section.match(line) is not None
1287
1288 def run_cargo(self):
1289 """Calls cargo -v and save its output to ./cargo.out."""
1290 if self.skip_cargo:
1291 return self
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001292 cargo_toml = './Cargo.toml'
1293 cargo_out = './cargo.out'
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001294 # Do not use Cargo.lock, because .bp rules are designed to
1295 # run with "latest" crates avaialable on Android.
1296 cargo_lock = './Cargo.lock'
1297 cargo_lock_saved = './cargo.lock.saved'
1298 had_cargo_lock = os.path.exists(cargo_lock)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001299 if not os.access(cargo_toml, os.R_OK):
1300 print('ERROR: Cannot find or read', cargo_toml)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001301 return self
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001302 if not self.dry_run:
1303 if os.path.exists(cargo_out):
1304 os.remove(cargo_out)
1305 if not self.args.use_cargo_lock and had_cargo_lock: # save it
1306 os.rename(cargo_lock, cargo_lock_saved)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001307 cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> ' + cargo_out + ' 2>&1'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001308 # set up search PATH for cargo to find the correct rustc
1309 saved_path = os.environ['PATH']
1310 os.environ['PATH'] = os.path.dirname(self.cargo_path) + ':' + saved_path
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001311 # Add [workspace] to Cargo.toml if it is not there.
1312 added_workspace = False
1313 if self.args.add_workspace:
1314 with open(cargo_toml, 'r') as in_file:
1315 cargo_toml_lines = in_file.readlines()
1316 found_workspace = '[workspace]\n' in cargo_toml_lines
1317 if found_workspace:
1318 print('### WARNING: found [workspace] in Cargo.toml')
1319 else:
1320 with open(cargo_toml, 'a') as out_file:
1321 out_file.write('[workspace]\n')
1322 added_workspace = True
1323 if self.args.verbose:
1324 print('### INFO: added [workspace] to Cargo.toml')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001325 for c in self.cargo:
1326 features = ''
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001327 if c != 'clean':
1328 if self.args.features is not None:
1329 features = ' --no-default-features'
1330 if self.args.features:
1331 features += ' --features ' + self.args.features
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001332 cmd_v_flag = ' -vv ' if self.args.vv else ' -v '
1333 cmd = self.cargo_path + cmd_v_flag
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001334 cmd += c + features + cmd_tail
1335 if self.args.rustflags and c != 'clean':
1336 cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
1337 if self.dry_run:
1338 print('Dry-run skip:', cmd)
1339 else:
1340 if self.args.verbose:
1341 print('Running:', cmd)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001342 with open(cargo_out, 'a') as out_file:
1343 out_file.write('### Running: ' + cmd + '\n')
Joel Galenson6bf54e32021-05-17 10:54:50 -07001344 ret = os.system(cmd)
1345 if ret != 0:
1346 print('*** There was an error while running cargo. ' +
1347 'See the cargo.out file for details.')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001348 if added_workspace: # restore original Cargo.toml
1349 with open(cargo_toml, 'w') as out_file:
1350 out_file.writelines(cargo_toml_lines)
1351 if self.args.verbose:
1352 print('### INFO: restored original Cargo.toml')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001353 os.environ['PATH'] = saved_path
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001354 if not self.dry_run:
1355 if not had_cargo_lock: # restore to no Cargo.lock state
1356 os.remove(cargo_lock)
1357 elif not self.args.use_cargo_lock: # restore saved Cargo.lock
1358 os.rename(cargo_lock_saved, cargo_lock)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001359 return self
1360
1361 def dump_dependencies(self):
1362 """Append dependencies and their features to Android.bp."""
1363 if not self.dependencies:
1364 return
1365 dependent_list = list()
1366 for c in self.dependencies:
1367 dependent_list.append(c.feature_list())
1368 sorted_dependencies = sorted(set(dependent_list))
1369 self.init_bp_file('Android.bp')
1370 with open('Android.bp', 'a') as outf:
1371 outf.write('\n// dependent_library ["feature_list"]\n')
1372 for s in sorted_dependencies:
1373 outf.write('// ' + s + '\n')
1374
1375 def dump_pkg_obj2cc(self):
1376 """Dump debug info of the pkg_obj2cc map."""
1377 if not self.args.debug:
1378 return
1379 self.init_bp_file('Android.bp')
1380 with open('Android.bp', 'a') as outf:
1381 sorted_pkgs = sorted(self.pkg_obj2cc.keys())
1382 for pkg in sorted_pkgs:
1383 if not self.pkg_obj2cc[pkg]:
1384 continue
1385 outf.write('\n// obj => src for %s\n' % pkg)
1386 obj2cc = self.pkg_obj2cc[pkg]
1387 for obj in sorted(obj2cc.keys()):
1388 outf.write('// ' + short_out_name(pkg, obj) + ' => ' +
1389 short_out_name(pkg, obj2cc[obj].src) + '\n')
1390
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001391 def apply_patch(self):
1392 """Apply local patch file if it is given."""
1393 if self.args.patch:
1394 if self.dry_run:
1395 print('Dry-run skip patch file:', self.args.patch)
1396 else:
1397 if not os.path.exists(self.args.patch):
1398 self.append_to_bp('ERROR cannot find patch file: ' + self.args.patch)
1399 return self
1400 if self.args.verbose:
1401 print('### INFO: applying local patch file:', self.args.patch)
Joel Galenson7e8247e2021-05-20 18:51:42 -07001402 subprocess.run(['patch', '-s', '--no-backup-if-mismatch', './Android.bp',
1403 self.args.patch], check=True)
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001404 return self
1405
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001406 def gen_bp(self):
1407 """Parse cargo.out and generate Android.bp files."""
1408 if self.dry_run:
1409 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp')
1410 elif os.path.exists(CARGO_OUT):
1411 self.find_root_pkg()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001412 if self.args.copy_out:
1413 self.copy_out_files()
1414 elif self.find_out_files() and self.has_used_out_dir():
1415 print('WARNING: ' + self.root_pkg + ' has cargo output files; ' +
1416 'please rerun with the --copy-out flag.')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001417 with open(CARGO_OUT, 'r') as cargo_out:
1418 self.parse(cargo_out, 'Android.bp')
1419 self.crates.sort(key=get_module_name)
1420 for obj in self.cc_objects:
1421 obj.dump()
1422 self.dump_pkg_obj2cc()
1423 for crate in self.crates:
1424 crate.dump()
1425 dumped_libs = set()
1426 for lib in self.ar_objects:
1427 if lib.pkg == self.root_pkg:
1428 lib_name = file_base_name(lib.lib)
1429 if lib_name not in dumped_libs:
1430 dumped_libs.add(lib_name)
1431 lib.dump()
Joel Galenson5664f2a2021-06-10 10:13:49 -07001432 if self.args.add_toplevel_block:
1433 with open(self.args.add_toplevel_block, 'r') as f:
1434 self.append_to_bp('\n' + f.read() + '\n')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001435 if self.args.dependencies and self.dependencies:
1436 self.dump_dependencies()
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001437 if self.errors:
Joel Galenson3f42f802021-04-07 12:42:17 -07001438 self.append_to_bp('\n' + ERRORS_LINE + '\n' + self.errors)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001439 return self
1440
1441 def add_ar_object(self, obj):
1442 self.ar_objects.append(obj)
1443
1444 def add_cc_object(self, obj):
1445 self.cc_objects.append(obj)
1446
1447 def add_crate(self, crate):
1448 """Merge crate with someone in crates, or append to it. Return crates."""
1449 if crate.skip_crate():
1450 if self.args.debug: # include debug info of all crates
1451 self.crates.append(crate)
1452 if self.args.dependencies: # include only dependent crates
1453 if (is_dependent_file_path(crate.main_src) and
1454 not is_build_crate_name(crate.crate_name)):
1455 self.dependencies.append(crate)
1456 else:
1457 for c in self.crates:
1458 if c.merge(crate, 'Android.bp'):
1459 return
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001460 # If not merged, decide module type and name now.
1461 crate.decide_module_type()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001462 self.crates.append(crate)
1463
1464 def find_warning_owners(self):
1465 """For each warning file, find its owner crate."""
1466 missing_owner = False
1467 for f in self.warning_files:
1468 cargo_dir = '' # find lowest crate, with longest path
1469 owner = None # owner crate of this warning
1470 for c in self.crates:
1471 if (f.startswith(c.cargo_dir + '/') and
1472 len(cargo_dir) < len(c.cargo_dir)):
1473 cargo_dir = c.cargo_dir
1474 owner = c
1475 if owner:
1476 owner.has_warning = True
1477 else:
1478 missing_owner = True
1479 if missing_owner and os.path.exists('Cargo.toml'):
1480 # owner is the root cargo, with empty cargo_dir
1481 for c in self.crates:
1482 if not c.cargo_dir:
1483 c.has_warning = True
1484
1485 def rustc_command(self, n, rustc_line, line, outf_name):
1486 """Process a rustc command line from cargo -vv output."""
1487 # cargo build -vv output can have multiple lines for a rustc command
1488 # due to '\n' in strings for environment variables.
1489 # strip removes leading spaces and '\n' at the end
1490 new_rustc = (rustc_line.strip() + line) if rustc_line else line
1491 # Use an heuristic to detect the completions of a multi-line command.
1492 # This might fail for some very rare case, but easy to fix manually.
1493 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0:
1494 return new_rustc
1495 if RUSTC_VV_CMD_ARGS.match(new_rustc):
1496 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1)
1497 self.add_crate(Crate(self, outf_name).parse(n, args))
1498 else:
1499 self.assert_empty_vv_line(new_rustc)
1500 return ''
1501
1502 def cc_ar_command(self, n, groups, outf_name):
1503 pkg = groups.group(1)
1504 line = groups.group(3)
1505 if groups.group(2) == 'cc':
1506 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line))
1507 else:
1508 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line))
1509
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001510 def append_to_bp(self, line):
1511 self.init_bp_file('Android.bp')
1512 with open('Android.bp', 'a') as outf:
1513 outf.write(line)
1514
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001515 def assert_empty_vv_line(self, line):
1516 if line: # report error if line is not empty
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001517 self.append_to_bp('ERROR -vv line: ' + line)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001518 return ''
1519
1520 def parse(self, inf, outf_name):
1521 """Parse rustc and warning messages in inf, return a list of Crates."""
1522 n = 0 # line number
1523 prev_warning = False # true if the previous line was warning: ...
1524 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
1525 for line in inf:
1526 n += 1
1527 if line.startswith('warning: '):
1528 prev_warning = True
1529 rustc_line = self.assert_empty_vv_line(rustc_line)
1530 continue
1531 new_rustc = ''
1532 if RUSTC_PAT.match(line):
1533 args_line = RUSTC_PAT.match(line).group(1)
1534 self.add_crate(Crate(self, outf_name).parse(n, args_line))
1535 self.assert_empty_vv_line(rustc_line)
1536 elif rustc_line or RUSTC_VV_PAT.match(line):
1537 new_rustc = self.rustc_command(n, rustc_line, line, outf_name)
1538 elif CC_AR_VV_PAT.match(line):
1539 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name)
1540 elif prev_warning and WARNING_FILE_PAT.match(line):
1541 self.assert_empty_vv_line(rustc_line)
1542 fpath = WARNING_FILE_PAT.match(line).group(1)
1543 if fpath[0] != '/': # ignore absolute path
1544 self.warning_files.add(fpath)
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001545 elif line.startswith('error: ') or line.startswith('error[E'):
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001546 if not self.args.ignore_cargo_errors:
1547 self.errors += line
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001548 prev_warning = False
1549 rustc_line = new_rustc
1550 self.find_warning_owners()
1551
1552
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001553def get_parser():
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001554 """Parse main arguments."""
1555 parser = argparse.ArgumentParser('cargo2android')
1556 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001557 '--add_workspace',
1558 action='store_true',
1559 default=False,
1560 help=('append [workspace] to Cargo.toml before calling cargo,' +
1561 ' to treat current directory as root of package source;' +
1562 ' otherwise the relative source file path in generated' +
1563 ' .bp file will be from the parent directory.'))
1564 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001565 '--cargo',
1566 action='append',
1567 metavar='args_string',
1568 help=('extra cargo build -v args in a string, ' +
1569 'each --cargo flag calls cargo build -v once'))
1570 parser.add_argument(
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001571 '--cargo_bin',
1572 type=str,
1573 help='use cargo in the cargo_bin directory instead of the prebuilt one')
1574 parser.add_argument(
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001575 '--copy-out',
1576 action='store_true',
1577 default=False,
1578 help=('only for root directory, ' +
1579 'copy build.rs output to ./out/* and add a genrule to copy ' +
1580 './out/* to genrule output; for crates with code pattern: ' +
1581 'include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))'))
1582 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001583 '--debug',
1584 action='store_true',
1585 default=False,
1586 help='dump debug info into Android.bp')
1587 parser.add_argument(
1588 '--dependencies',
1589 action='store_true',
1590 default=False,
1591 help='dump debug info of dependent crates')
1592 parser.add_argument(
1593 '--device',
1594 action='store_true',
1595 default=False,
1596 help='run cargo also for a default device target')
1597 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001598 '--features',
1599 type=str,
1600 help=('pass features to cargo build, ' +
1601 'empty string means no default features'))
1602 parser.add_argument(
1603 '--global_defaults',
1604 type=str,
1605 help='add a defaults name to every module')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001606 parser.add_argument(
1607 '--host-first-multilib',
1608 action='store_true',
1609 default=False,
1610 help=('add a compile_multilib:"first" property ' +
1611 'to Android.bp host modules.'))
1612 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001613 '--ignore-cargo-errors',
1614 action='store_true',
1615 default=False,
1616 help='do not append cargo/rustc error messages to Android.bp')
1617 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001618 '--no-host',
1619 action='store_true',
1620 default=False,
1621 help='do not run cargo for the host; only for the device target')
1622 parser.add_argument(
1623 '--no-subdir',
1624 action='store_true',
1625 default=False,
1626 help='do not output anything for sub-directories')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001627 parser.add_argument(
1628 '--onefile',
1629 action='store_true',
1630 default=False,
1631 help=('output all into one ./Android.bp, default will generate ' +
1632 'one Android.bp per Cargo.toml in subdirectories'))
1633 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001634 '--patch',
1635 type=str,
1636 help='apply the given patch file to generated ./Android.bp')
1637 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001638 '--run',
1639 action='store_true',
1640 default=False,
1641 help='run it, default is dry-run')
1642 parser.add_argument('--rustflags', type=str, help='passing flags to rustc')
1643 parser.add_argument(
1644 '--skipcargo',
1645 action='store_true',
1646 default=False,
1647 help='skip cargo command, parse cargo.out, and generate Android.bp')
1648 parser.add_argument(
1649 '--tests',
1650 action='store_true',
1651 default=False,
1652 help='run cargo build --tests after normal build')
1653 parser.add_argument(
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001654 '--use-cargo-lock',
1655 action='store_true',
1656 default=False,
1657 help=('run cargo build with existing Cargo.lock ' +
1658 '(used when some latest dependent crates failed)'))
1659 parser.add_argument(
Joel Galensond9c4de62021-04-23 10:26:40 -07001660 '--min-sdk-version',
1661 type=str,
1662 help='Minimum SDK version')
1663 parser.add_argument(
1664 '--apex-available',
1665 nargs='*',
1666 help='Mark the main library as apex_available with the given apexes.')
1667 parser.add_argument(
Ivan Lozano91920862021-07-19 10:49:08 -04001668 '--vendor-available',
1669 action='store_true',
1670 default=False,
1671 help='Mark the main library as vendor_available.')
1672 parser.add_argument(
1673 '--vendor-ramdisk-available',
1674 action='store_true',
1675 default=False,
1676 help='Mark the main library as vendor_ramdisk_available.')
1677 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001678 '--force-rlib',
1679 action='store_true',
1680 default=False,
1681 help='Make the main library an rlib.')
1682 parser.add_argument(
Joel Galenson12467e52021-07-12 14:33:28 -07001683 '--whole-static-libs',
1684 nargs='*',
1685 default=[],
1686 help='Make the given libraries (without lib prefixes) whole_static_libs.')
1687 parser.add_argument(
Ivan Lozano26aa1c32021-08-16 11:20:32 -04001688 '--no-pkg-vers',
1689 action='store_true',
1690 default=False,
1691 help='Do not attempt to determine the package version automatically.')
1692 parser.add_argument(
Joel Galensone4f53882021-07-19 11:14:55 -07001693 '--test-data',
1694 nargs='*',
1695 default=[],
1696 help=('Add the given file to the given test\'s data property. ' +
1697 'Usage: test-path=data-path'))
1698 parser.add_argument(
Joel Galenson97e414a2021-05-27 09:42:32 -07001699 '--dependency-blocklist',
1700 nargs='*',
1701 default=[],
Joel Galenson12467e52021-07-12 14:33:28 -07001702 help='Do not emit the given dependencies (without lib prefixes).')
Joel Galenson97e414a2021-05-27 09:42:32 -07001703 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001704 '--lib-blocklist',
1705 nargs='*',
1706 default=[],
Joel Galenson12467e52021-07-12 14:33:28 -07001707 help='Do not emit the given C libraries as dependencies (without lib prefixes).')
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001708 parser.add_argument(
Joel Galensonf6b3c912021-06-03 16:00:54 -07001709 '--test-blocklist',
1710 nargs='*',
1711 default=[],
1712 help=('Do not emit the given tests. ' +
1713 'Pass the path to the test file to exclude.'))
1714 parser.add_argument(
Joel Galenson3d6d1e72021-06-07 15:00:24 -07001715 '--cfg-blocklist',
1716 nargs='*',
1717 default=[],
1718 help='Do not emit the given cfg.')
1719 parser.add_argument(
Joel Galenson5664f2a2021-06-10 10:13:49 -07001720 '--add-toplevel-block',
1721 type=str,
1722 help='Add the contents of the given file to the top level of the Android.bp.')
1723 parser.add_argument(
1724 '--add-module-block',
1725 type=str,
1726 help='Add the contents of the given file to the main module.')
1727 parser.add_argument(
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001728 '--no-test-mapping',
1729 action='store_true',
1730 default=False,
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001731 help='Deprecated. Has no effect.')
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001732 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001733 '--verbose',
1734 action='store_true',
1735 default=False,
1736 help='echo executed commands')
1737 parser.add_argument(
1738 '--vv',
1739 action='store_true',
1740 default=False,
1741 help='run cargo with -vv instead of default -v')
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001742 parser.add_argument(
1743 '--dump-config-and-exit',
1744 type=str,
1745 help=('Dump command-line arguments (minus this flag) to a config file and exit. ' +
1746 'This is intended to help migrate from command line options to config files.'))
1747 parser.add_argument(
1748 '--config',
1749 type=str,
1750 help=('Load command-line options from the given config file. ' +
1751 'Options in this file will override those passed on the command line.'))
1752 return parser
1753
1754
1755def parse_args(parser):
1756 """Parses command-line options."""
1757 args = parser.parse_args()
1758 # Use the values specified in a config file if one was found.
1759 if args.config:
1760 with open(args.config, 'r') as f:
1761 config = json.load(f)
1762 args_dict = vars(args)
1763 for arg in config:
1764 args_dict[arg.replace('-', '_')] = config[arg]
1765 return args
1766
1767
1768def dump_config(parser, args):
1769 """Writes the non-default command-line options to the specified file."""
1770 args_dict = vars(args)
1771 # Filter out the arguments that have their default value.
Joel Galenson367360c2021-04-29 14:31:43 -07001772 # Also filter certain "temporary" arguments.
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001773 non_default_args = {}
1774 for arg in args_dict:
Joel Galenson367360c2021-04-29 14:31:43 -07001775 if args_dict[arg] != parser.get_default(
1776 arg) and arg != 'dump_config_and_exit' and arg != 'no_test_mapping':
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001777 non_default_args[arg.replace('_', '-')] = args_dict[arg]
1778 # Write to the specified file.
1779 with open(args.dump_config_and_exit, 'w') as f:
1780 json.dump(non_default_args, f, indent=2, sort_keys=True)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001781
1782
1783def main():
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001784 parser = get_parser()
1785 args = parse_args(parser)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001786 if not args.run: # default is dry-run
1787 print(DRY_RUN_NOTE)
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001788 if args.dump_config_and_exit:
1789 dump_config(parser, args)
1790 else:
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001791 Runner(args).run_cargo().gen_bp().apply_patch()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001792
1793
1794if __name__ == '__main__':
1795 main()