blob: c5c5d21b4c08d2104808e76b21574b70ba6b1c89 [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
Joel Galenson69ba8072021-08-16 11:31:29 -0700459 if not self.runner.args.no_pkg_vers and not self.skip_crate():
Ivan Lozano26aa1c32021-08-16 11:20:32 -0400460 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 Galenson69ba8072021-08-16 11:31:29 -0700486 cargo_metadata = subprocess.run([self.runner.cargo_path, 'metadata', '--no-deps',
487 '--format-version', '1'],
Joel Galensonc5186502021-08-16 11:22:47 -0700488 cwd=os.path.abspath(self.cargo_dir),
489 stdout=subprocess.PIPE)
Ivan Lozano26aa1c32021-08-16 11:20:32 -0400490 if cargo_metadata.returncode:
491 self.errors += ('ERROR: unable to get cargo metadata for package version; ' +
492 'return code ' + cargo_metadata.returncode + '\n')
493 else:
494 metadata_json = json.loads(cargo_metadata.stdout)
495 if len(metadata_json['packages']) > 1:
496 for package in metadata_json['packages']:
497 # package names may contain '-', but is changed to '_' in the crate_name
498 if package['name'].replace('-','_') == self.crate_name:
499 self.cargo_pkg_version = package['version']
500 break
501 else:
502 self.cargo_pkg_version = metadata_json['packages'][0]['version']
503
504 if not self.cargo_pkg_version:
505 self.errors += ('ERROR: Unable to retrieve package version; ' +
506 'to disable, run with arg "--no-pkg-vers"\n')
507
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800508 def dump_line(self):
509 self.write('\n// Line ' + str(self.line_num) + ' ' + self.line)
510
511 def feature_list(self):
512 """Return a string of main_src + "feature_list"."""
513 pkg = self.main_src
514 if pkg.startswith('.../'): # keep only the main package name
515 pkg = re.sub('/.*', '', pkg[4:])
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700516 elif pkg.startswith('/'): # use relative path for a local package
517 pkg = os.path.relpath(pkg)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800518 if not self.features:
519 return pkg
520 return pkg + ' "' + ','.join(self.features) + '"'
521
522 def dump_skip_crate(self, kind):
523 if self.debug:
524 self.write('\n// IGNORED: ' + kind + ' ' + self.main_src)
525 return self
526
527 def skip_crate(self):
528 """Return crate_name or a message if this crate should be skipped."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700529 if (is_build_crate_name(self.crate_name) or
530 self.crate_name in EXCLUDED_CRATES):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800531 return self.crate_name
532 if is_dependent_file_path(self.main_src):
533 return 'dependent crate'
534 return ''
535
536 def dump(self):
537 """Dump all error/debug/module code to the output .bp file."""
538 self.runner.init_bp_file(self.outf_name)
539 with open(self.outf_name, 'a') as outf:
540 self.outf = outf
541 if self.errors:
542 self.dump_line()
543 self.write(self.errors)
544 elif self.skip_crate():
545 self.dump_skip_crate(self.skip_crate())
546 else:
547 if self.debug:
548 self.dump_debug_info()
549 self.dump_android_module()
550
551 def dump_debug_info(self):
552 """Dump parsed data, when cargo2android is called with --debug."""
553
554 def dump(name, value):
555 self.write('//%12s = %s' % (name, value))
556
557 def opt_dump(name, value):
558 if value:
559 dump(name, value)
560
561 def dump_list(fmt, values):
562 for v in values:
563 self.write(fmt % v)
564
565 self.dump_line()
566 dump('module_name', self.module_name)
567 dump('crate_name', self.crate_name)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700568 dump('crate_types', self.crate_types)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800569 dump('main_src', self.main_src)
570 dump('has_warning', self.has_warning)
571 dump('for_host', self.host_supported)
572 dump('for_device', self.device_supported)
573 dump('module_type', self.module_type)
574 opt_dump('target', self.target)
575 opt_dump('edition', self.edition)
576 opt_dump('emit_list', self.emit_list)
577 opt_dump('cap_lints', self.cap_lints)
578 dump_list('// cfg = %s', self.cfgs)
579 dump_list('// cfg = \'feature "%s"\'', self.features)
580 # TODO(chh): escape quotes in self.features, but not in other dump_list
581 dump_list('// codegen = %s', self.codegens)
582 dump_list('// externs = %s', self.externs)
583 dump_list('// -l static = %s', self.static_libs)
584 dump_list('// -l (dylib) = %s', self.shared_libs)
585
586 def dump_android_module(self):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700587 """Dump one or more Android module definition, depending on crate_types."""
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700588 if len(self.crate_types) == 1:
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700589 self.dump_single_type_android_module()
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700590 return
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700591 if 'test' in self.crate_types:
592 self.write('\nERROR: multiple crate types cannot include test type')
593 return
594 # Dump one Android module per crate_type.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700595 for crate_type in self.crate_types:
596 self.decide_one_module_type(crate_type)
597 self.dump_one_android_module(crate_type)
598
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700599 def build_default_name(self):
600 """Return a short and readable name for the rust_defaults module."""
Joel Galensond37d7e62021-07-13 09:03:01 -0700601 # Choices: (1) root_pkg + '_test'? + '_defaults',
602 # (2) root_pkg + '_test'? + '_defaults_' + crate_name
603 # (3) root_pkg + '_test'? + '_defaults_' + main_src_basename_path
604 # (4) root_pkg + '_test'? + '_defaults_' + a_positive_sequence_number
605 test = "_test" if self.crate_types == ['test'] else ""
606 name1 = altered_defaults(self.root_pkg) + test + '_defaults'
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700607 if self.runner.try_claim_module_name(name1, self):
608 return name1
609 name2 = name1 + '_' + self.crate_name
610 if self.runner.try_claim_module_name(name2, self):
611 return name2
612 name3 = name1 + '_' + self.main_src_basename_path()
613 if self.runner.try_claim_module_name(name3, self):
614 return name3
615 return self.runner.claim_module_name(name1, self, 0)
616
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700617 def dump_srcs_list(self):
618 """Dump the srcs list, for defaults or regular modules."""
619 if len(self.srcs) > 1:
620 srcs = sorted(set(self.srcs)) # make a copy and dedup
621 else:
622 srcs = [self.main_src]
623 copy_out = self.runner.copy_out_module_name()
624 if copy_out:
625 srcs.append(':' + copy_out)
626 self.dump_android_property_list('srcs', '"%s"', srcs)
627
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700628 def dump_defaults_module(self):
629 """Dump a rust_defaults module to be shared by other modules."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700630 name = self.build_default_name()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700631 self.defaults = name
632 self.write('\nrust_defaults {')
633 self.write(' name: "' + name + '",')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700634 if self.runner.args.global_defaults:
635 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700636 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700637 if len(self.srcs) == 1: # only one source file; share it in defaults
638 self.default_srcs = True
639 if self.has_warning and not self.cap_lints:
640 self.write(' // has rustc warnings')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700641 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700642 if 'test' in self.crate_types:
643 self.write(' test_suites: ["general-tests"],')
644 self.write(' auto_gen_config: true,')
645 self.dump_edition_flags_libs()
Joel Galensone4f53882021-07-19 11:14:55 -0700646 if 'test' in self.crate_types and len(self.srcs) == 1:
647 self.dump_test_data()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700648 self.write('}')
649
650 def dump_single_type_android_module(self):
651 """Dump one simple Android module, which has only one crate_type."""
652 crate_type = self.crate_types[0]
653 if crate_type != 'test':
654 # do not change self.stem or self.module_name
655 self.dump_one_android_module(crate_type)
656 return
657 # Dump one test module per source file, and separate host and device tests.
658 # crate_type == 'test'
Joel Galensonf6b3c912021-06-03 16:00:54 -0700659 self.srcs = [src for src in self.srcs if not src in self.runner.args.test_blocklist]
660 if ((self.host_supported and self.device_supported and len(self.srcs) > 0) or
661 len(self.srcs) > 1):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700662 self.srcs = sorted(set(self.srcs))
663 self.dump_defaults_module()
664 saved_srcs = self.srcs
665 for src in saved_srcs:
666 self.srcs = [src]
667 saved_device_supported = self.device_supported
668 saved_host_supported = self.host_supported
669 saved_main_src = self.main_src
670 self.main_src = src
671 if saved_host_supported:
672 self.device_supported = False
673 self.host_supported = True
674 self.module_name = self.test_module_name()
675 self.decide_one_module_type(crate_type)
676 self.dump_one_android_module(crate_type)
677 if saved_device_supported:
678 self.device_supported = True
679 self.host_supported = False
680 self.module_name = self.test_module_name()
681 self.decide_one_module_type(crate_type)
682 self.dump_one_android_module(crate_type)
683 self.host_supported = saved_host_supported
684 self.device_supported = saved_device_supported
685 self.main_src = saved_main_src
686 self.srcs = saved_srcs
687
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700688 def dump_one_android_module(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800689 """Dump one Android module definition."""
690 if not self.module_type:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700691 self.write('\nERROR: unknown crate_type ' + crate_type)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800692 return
693 self.write('\n' + self.module_type + ' {')
694 self.dump_android_core_properties()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700695 if not self.defaults:
696 self.dump_edition_flags_libs()
697 if self.runner.args.host_first_multilib and self.host_supported and crate_type != 'test':
698 self.write(' compile_multilib: "first",')
Joel Galensond9c4de62021-04-23 10:26:40 -0700699 if self.runner.args.apex_available and crate_type == 'lib':
700 self.write(' apex_available: [')
701 for apex in self.runner.args.apex_available:
702 self.write(' "%s",' % apex)
703 self.write(' ],')
Matthew Maurerac677252021-08-13 15:52:52 -0700704 if self.runner.args.native_bridge_supported:
705 self.write(' native_bridge_supported: true,')
706 if self.runner.args.product_available:
707 self.write(' product_available: true,')
708 if self.runner.args.recovery_available:
709 self.write(' recovery_available: true,')
Ivan Lozano91920862021-07-19 10:49:08 -0400710 if self.runner.args.vendor_available:
711 self.write(' vendor_available: true,')
712 if self.runner.args.vendor_ramdisk_available:
713 self.write(' vendor_ramdisk_available: true,')
Matthew Maurerac677252021-08-13 15:52:52 -0700714 if self.runner.args.ramdisk_available:
715 self.write(' ramdisk_available: true,')
Joel Galensond9c4de62021-04-23 10:26:40 -0700716 if self.runner.args.min_sdk_version and crate_type == 'lib':
717 self.write(' min_sdk_version: "%s",' % self.runner.args.min_sdk_version)
Joel Galensone4f53882021-07-19 11:14:55 -0700718 if crate_type == 'test' and not self.default_srcs:
719 self.dump_test_data()
Joel Galenson5664f2a2021-06-10 10:13:49 -0700720 if self.runner.args.add_module_block:
721 with open(self.runner.args.add_module_block, 'r') as f:
722 self.write(' %s,' % f.read().replace('\n', '\n '))
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700723 self.write('}')
724
725 def dump_android_flags(self):
726 """Dump Android module flags property."""
ThiƩbaud Weksteena5a728b2021-04-08 14:23:49 +0200727 if not self.codegens and not self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700728 return
729 self.write(' flags: [')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800730 if self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700731 self.write(' "--cap-lints ' + self.cap_lints + '",')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700732 codegens_fmt = '"-C %s"'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700733 self.dump_android_property_list_items(codegens_fmt, self.codegens)
734 self.write(' ],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700735
736 def dump_edition_flags_libs(self):
737 if self.edition:
738 self.write(' edition: "' + self.edition + '",')
739 self.dump_android_property_list('features', '"%s"', self.features)
Joel Galenson3d6d1e72021-06-07 15:00:24 -0700740 cfgs = [cfg for cfg in self.cfgs if not cfg in self.runner.args.cfg_blocklist]
741 self.dump_android_property_list('cfgs', '"%s"', cfgs)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700742 self.dump_android_flags()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800743 if self.externs:
744 self.dump_android_externs()
Joel Galenson12467e52021-07-12 14:33:28 -0700745 all_static_libs = [lib for lib in self.static_libs if not lib in self.runner.args.lib_blocklist]
746 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 -0700747 self.dump_android_property_list('static_libs', '"lib%s"', static_libs)
Joel Galenson12467e52021-07-12 14:33:28 -0700748 whole_static_libs = [lib for lib in all_static_libs if lib in self.runner.args.whole_static_libs]
749 self.dump_android_property_list('whole_static_libs', '"lib%s"', whole_static_libs)
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700750 shared_libs = [lib for lib in self.shared_libs if not lib in self.runner.args.lib_blocklist]
751 self.dump_android_property_list('shared_libs', '"lib%s"', shared_libs)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800752
Joel Galensone4f53882021-07-19 11:14:55 -0700753 def dump_test_data(self):
754 data = [data for (name, data) in map(lambda kv: kv.split('=', 1), self.runner.args.test_data)
755 if self.srcs == [name]]
756 if data:
757 self.dump_android_property_list('data', '"%s"', data)
758
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700759 def main_src_basename_path(self):
760 return re.sub('/', '_', re.sub('.rs$', '', self.main_src))
761
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800762 def test_module_name(self):
763 """Return a unique name for a test module."""
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700764 # root_pkg+(_host|_device) + '_test_'+source_file_name
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700765 suffix = self.main_src_basename_path()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700766 host_device = '_host'
767 if self.device_supported:
768 host_device = '_device'
769 return self.root_pkg + host_device + '_test_' + suffix
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800770
771 def decide_module_type(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700772 # Use the first crate type for the default/first module.
773 crate_type = self.crate_types[0] if self.crate_types else ''
774 self.decide_one_module_type(crate_type)
775
776 def decide_one_module_type(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800777 """Decide which Android module type to use."""
778 host = '' if self.device_supported else '_host'
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700779 rlib = '_rlib' if self.runner.args.force_rlib else ''
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700780 if crate_type == 'bin': # rust_binary[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800781 self.module_type = 'rust_binary' + host
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700782 # In rare cases like protobuf-codegen, the output binary name must
783 # be renamed to use as a plugin for protoc.
784 self.stem = altered_stem(self.crate_name)
785 self.module_name = altered_name(self.crate_name)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700786 elif crate_type == 'lib': # rust_library[_host]
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700787 # TODO(chh): should this be rust_library[_host]?
788 # Assuming that Cargo.toml do not use both 'lib' and 'rlib',
789 # because we map them both to rlib.
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700790 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800791 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700792 self.module_name = altered_name(self.stem)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700793 elif crate_type == 'rlib': # rust_library[_host]
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700794 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700795 self.stem = 'lib' + self.crate_name
796 self.module_name = altered_name(self.stem)
797 elif crate_type == 'dylib': # rust_library[_host]_dylib
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800798 self.module_type = 'rust_library' + host + '_dylib'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700799 self.stem = 'lib' + self.crate_name
800 self.module_name = altered_name(self.stem) + '_dylib'
801 elif crate_type == 'cdylib': # rust_library[_host]_shared
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500802 self.module_type = 'rust_ffi' + host + '_shared'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700803 self.stem = 'lib' + self.crate_name
804 self.module_name = altered_name(self.stem) + '_shared'
805 elif crate_type == 'staticlib': # rust_library[_host]_static
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500806 self.module_type = 'rust_ffi' + host + '_static'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700807 self.stem = 'lib' + self.crate_name
808 self.module_name = altered_name(self.stem) + '_static'
809 elif crate_type == 'test': # rust_test[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800810 self.module_type = 'rust_test' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700811 # Before do_merge, stem name is based on the --crate-name parameter.
812 # and test module name is based on stem.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800813 self.stem = self.test_module_name()
814 # self.stem will be changed after merging with other tests.
815 # self.stem is NOT used for final test binary name.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700816 # rust_test uses each source file base name as part of output file name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700817 # In do_merge, this function is called again, with a module_name.
818 # We make sure that the module name is unique in each package.
819 if self.module_name:
820 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add
821 # different suffixes and distinguish multiple tests of the same
822 # crate name. We ignore -C and use claim_module_name to get
823 # unique sequential suffix.
824 self.module_name = self.runner.claim_module_name(
825 self.module_name, self, 0)
826 # Now the module name is unique, stem should also match and unique.
827 self.stem = self.module_name
828 elif crate_type == 'proc-macro': # rust_proc_macro
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800829 self.module_type = 'rust_proc_macro'
830 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700831 self.module_name = altered_name(self.stem)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800832 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]?
833 self.module_type = ''
834 self.stem = ''
835
836 def dump_android_property_list_items(self, fmt, values):
837 for v in values:
838 # fmt has quotes, so we need escape_quotes(v)
839 self.write(' ' + (fmt % escape_quotes(v)) + ',')
840
841 def dump_android_property_list(self, name, fmt, values):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700842 if not values:
843 return
844 if len(values) > 1:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800845 self.write(' ' + name + ': [')
846 self.dump_android_property_list_items(fmt, values)
847 self.write(' ],')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700848 else:
849 self.write(' ' + name + ': [' +
850 (fmt % escape_quotes(values[0])) + '],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800851
852 def dump_android_core_properties(self):
853 """Dump the module header, name, stem, etc."""
854 self.write(' name: "' + self.module_name + '",')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700855 # see properties shared by dump_defaults_module
856 if self.defaults:
857 self.write(' defaults: ["' + self.defaults + '"],')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700858 elif self.runner.args.global_defaults:
859 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800860 if self.stem != self.module_name:
861 self.write(' stem: "' + self.stem + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700862 if self.has_warning and not self.cap_lints and not self.default_srcs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700863 self.write(' // has rustc warnings')
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700864 if self.host_supported and self.device_supported and self.module_type != 'rust_proc_macro':
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800865 self.write(' host_supported: true,')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700866 if not self.defaults:
867 self.write(' crate_name: "' + self.crate_name + '",')
Ivan Lozanocc660f12021-08-11 16:49:46 -0400868 if not self.defaults and self.cargo_env_compat:
869 self.write(' cargo_env_compat: true,')
870 self.write(' cargo_pkg_version: "' + self.cargo_pkg_version + '",')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700871 if not self.default_srcs:
872 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700873 if 'test' in self.crate_types and not self.defaults:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800874 # self.root_pkg can have multiple test modules, with different *_tests[n]
875 # names, but their executables can all be installed under the same _tests
876 # directory. When built from Cargo.toml, all tests should have different
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700877 # file or crate names. So we used (root_pkg + '_tests') name as the
878 # relative_install_path.
879 # However, some package like 'slab' can have non-mergeable tests that
880 # must be separated by different module names. So, here we no longer
881 # emit relative_install_path.
882 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800883 self.write(' test_suites: ["general-tests"],')
884 self.write(' auto_gen_config: true,')
Joel Galensone261a152021-01-12 11:31:53 -0800885 if 'test' in self.crate_types and self.host_supported:
886 self.write(' test_options: {')
887 self.write(' unit_test: true,')
888 self.write(' },')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800889
890 def dump_android_externs(self):
891 """Dump the dependent rlibs and dylibs property."""
892 so_libs = list()
893 rust_libs = ''
894 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$')
895 for lib in self.externs:
896 # normal value of lib: "libc = liblibc-*.rlib"
897 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib"
898 # we should use "libgetrandom", not "lib" + "getrandom_package"
899 groups = deps_libname.match(lib)
900 if groups is not None:
901 lib_name = groups.group(1)
902 else:
903 lib_name = re.sub(' .*$', '', lib)
Joel Galenson97e414a2021-05-27 09:42:32 -0700904 if lib_name in self.runner.args.dependency_blocklist:
905 continue
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800906 if lib.endswith('.rlib') or lib.endswith('.rmeta'):
907 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta.
908 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n'
909 elif lib.endswith('.so'):
910 so_libs.append(lib_name)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700911 elif lib != 'proc_macro': # --extern proc_macro is special and ignored
912 rust_libs += ' // ERROR: unknown type of lib ' + lib + '\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800913 if rust_libs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700914 self.write(' rustlibs: [\n' + rust_libs + ' ],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800915 # Are all dependent .so files proc_macros?
916 # TODO(chh): Separate proc_macros and dylib.
917 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs)
918
919
920class ARObject(object):
921 """Information of an "ar" link command."""
922
923 def __init__(self, runner, outf_name):
924 # Remembered global runner and its members.
925 self.runner = runner
926 self.pkg = ''
927 self.outf_name = outf_name # path to Android.bp
928 # "ar" arguments
929 self.line_num = 1
930 self.line = ''
931 self.flags = '' # e.g. "crs"
932 self.lib = '' # e.g. "/.../out/lib*.a"
933 self.objs = list() # e.g. "/.../out/.../*.o"
934
935 def parse(self, pkg, line_num, args_line):
936 """Collect ar obj/lib file names."""
937 self.pkg = pkg
938 self.line_num = line_num
939 self.line = args_line
940 args = args_line.split()
941 num_args = len(args)
942 if num_args < 3:
943 print('ERROR: "ar" command has too few arguments', args_line)
944 else:
945 self.flags = unquote(args[0])
946 self.lib = unquote(args[1])
947 self.objs = sorted(set(map(unquote, args[2:])))
948 return self
949
950 def write(self, s):
951 self.outf.write(s + '\n')
952
953 def dump_debug_info(self):
954 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line)
955 self.write('// ar_object for %12s' % self.pkg)
956 self.write('// flags = %s' % self.flags)
957 self.write('// lib = %s' % short_out_name(self.pkg, self.lib))
958 for o in self.objs:
959 self.write('// obj = %s' % short_out_name(self.pkg, o))
960
961 def dump_android_lib(self):
962 """Write cc_library_static into Android.bp."""
963 self.write('\ncc_library_static {')
964 self.write(' name: "' + file_base_name(self.lib) + '",')
965 self.write(' host_supported: true,')
966 if self.flags != 'crs':
967 self.write(' // ar flags = %s' % self.flags)
968 if self.pkg not in self.runner.pkg_obj2cc:
969 self.write(' ERROR: cannot find source files.\n}')
970 return
971 self.write(' srcs: [')
972 obj2cc = self.runner.pkg_obj2cc[self.pkg]
973 # Note: wflags are ignored.
974 dflags = list()
975 fflags = list()
976 for obj in self.objs:
977 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",')
978 # TODO(chh): union of dflags and flags of all obj
979 # Now, just a temporary hack that uses the last obj's flags
980 dflags = obj2cc[obj].dflags
981 fflags = obj2cc[obj].fflags
982 self.write(' ],')
983 self.write(' cflags: [')
984 self.write(' "-O3",') # TODO(chh): is this default correct?
985 self.write(' "-Wno-error",')
986 for x in fflags:
987 self.write(' "-f' + x + '",')
988 for x in dflags:
989 self.write(' "-D' + x + '",')
990 self.write(' ],')
991 self.write('}')
992
993 def dump(self):
994 """Dump error/debug/module info to the output .bp file."""
995 self.runner.init_bp_file(self.outf_name)
996 with open(self.outf_name, 'a') as outf:
997 self.outf = outf
998 if self.runner.args.debug:
999 self.dump_debug_info()
1000 self.dump_android_lib()
1001
1002
1003class CCObject(object):
1004 """Information of a "cc" compilation command."""
1005
1006 def __init__(self, runner, outf_name):
1007 # Remembered global runner and its members.
1008 self.runner = runner
1009 self.pkg = ''
1010 self.outf_name = outf_name # path to Android.bp
1011 # "cc" arguments
1012 self.line_num = 1
1013 self.line = ''
1014 self.src = ''
1015 self.obj = ''
1016 self.dflags = list() # -D flags
1017 self.fflags = list() # -f flags
1018 self.iflags = list() # -I flags
1019 self.wflags = list() # -W flags
1020 self.other_args = list()
1021
1022 def parse(self, pkg, line_num, args_line):
1023 """Collect cc compilation flags and src/out file names."""
1024 self.pkg = pkg
1025 self.line_num = line_num
1026 self.line = args_line
1027 args = args_line.split()
1028 i = 0
1029 while i < len(args):
1030 arg = args[i]
1031 if arg == '"-c"':
1032 i += 1
1033 if args[i].startswith('"-o'):
1034 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c"
1035 self.obj = unquote(args[i])[2:]
1036 i += 1
1037 self.src = unquote(args[i])
1038 else:
1039 self.src = unquote(args[i])
1040 elif arg == '"-o"':
1041 i += 1
1042 self.obj = unquote(args[i])
1043 elif arg == '"-I"':
1044 i += 1
1045 self.iflags.append(unquote(args[i]))
1046 elif arg.startswith('"-D'):
1047 self.dflags.append(unquote(args[i])[2:])
1048 elif arg.startswith('"-f'):
1049 self.fflags.append(unquote(args[i])[2:])
1050 elif arg.startswith('"-W'):
1051 self.wflags.append(unquote(args[i])[2:])
1052 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or
1053 arg == '"-g3"'):
1054 # ignore -O -m64 -g
1055 self.other_args.append(unquote(args[i]))
1056 i += 1
1057 self.dflags = sorted(set(self.dflags))
1058 self.fflags = sorted(set(self.fflags))
1059 # self.wflags is not sorted because some are order sensitive
1060 # and we ignore them anyway.
1061 if self.pkg not in self.runner.pkg_obj2cc:
1062 self.runner.pkg_obj2cc[self.pkg] = {}
1063 self.runner.pkg_obj2cc[self.pkg][self.obj] = self
1064 return self
1065
1066 def write(self, s):
1067 self.outf.write(s + '\n')
1068
1069 def dump_debug_flags(self, name, flags):
1070 self.write('// ' + name + ':')
1071 for f in flags:
1072 self.write('// %s' % f)
1073
1074 def dump(self):
1075 """Dump only error/debug info to the output .bp file."""
1076 if not self.runner.args.debug:
1077 return
1078 self.runner.init_bp_file(self.outf_name)
1079 with open(self.outf_name, 'a') as outf:
1080 self.outf = outf
1081 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line)
1082 self.write('// cc_object for %12s' % self.pkg)
1083 self.write('// src = %s' % short_out_name(self.pkg, self.src))
1084 self.write('// obj = %s' % short_out_name(self.pkg, self.obj))
1085 self.dump_debug_flags('-I flags', self.iflags)
1086 self.dump_debug_flags('-D flags', self.dflags)
1087 self.dump_debug_flags('-f flags', self.fflags)
1088 self.dump_debug_flags('-W flags', self.wflags)
1089 if self.other_args:
1090 self.dump_debug_flags('other args', self.other_args)
1091
1092
1093class Runner(object):
1094 """Main class to parse cargo -v output and print Android module definitions."""
1095
1096 def __init__(self, args):
1097 self.bp_files = set() # Remember all output Android.bp files.
1098 self.root_pkg = '' # name of package in ./Cargo.toml
1099 # Saved flags, modes, and data.
1100 self.args = args
1101 self.dry_run = not args.run
1102 self.skip_cargo = args.skipcargo
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001103 self.cargo_path = './cargo' # path to cargo, will be set later
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001104 self.checked_out_files = False # to check only once
1105 self.build_out_files = [] # output files generated by build.rs
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001106 # All cc/ar objects, crates, dependencies, and warning files
1107 self.cc_objects = list()
1108 self.pkg_obj2cc = {}
1109 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i]
1110 self.ar_objects = list()
1111 self.crates = list()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001112 self.warning_files = set()
1113 # Keep a unique mapping from (module name) to crate
1114 self.name_owners = {}
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001115 # Save and dump all errors from cargo to Android.bp.
1116 self.errors = ''
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001117 self.setup_cargo_path()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001118 # Default action is cargo clean, followed by build or user given actions.
1119 if args.cargo:
1120 self.cargo = ['clean'] + args.cargo
1121 else:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001122 default_target = '--target x86_64-unknown-linux-gnu'
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -07001123 # Use the same target for both host and default device builds.
1124 # Same target is used as default in host x86_64 Android compilation.
1125 # Note: b/169872957, prebuilt cargo failed to build vsock
1126 # on x86_64-unknown-linux-musl systems.
1127 self.cargo = ['clean', 'build ' + default_target]
1128 if args.tests:
1129 self.cargo.append('build --tests ' + default_target)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001130
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001131 def setup_cargo_path(self):
1132 """Find cargo in the --cargo_bin or prebuilt rust bin directory."""
1133 if self.args.cargo_bin:
1134 self.cargo_path = os.path.join(self.args.cargo_bin, 'cargo')
1135 if not os.path.isfile(self.cargo_path):
1136 sys.exit('ERROR: cannot find cargo in ' + self.args.cargo_bin)
1137 print('WARNING: using cargo in ' + self.args.cargo_bin)
1138 return
1139 # We have only tested this on Linux.
1140 if platform.system() != 'Linux':
1141 sys.exit('ERROR: this script has only been tested on Linux with cargo.')
1142 # Assuming that this script is in development/scripts.
1143 my_dir = os.path.dirname(os.path.abspath(__file__))
1144 linux_dir = os.path.join(my_dir, '..', '..',
1145 'prebuilts', 'rust', 'linux-x86')
1146 if not os.path.isdir(linux_dir):
1147 sys.exit('ERROR: cannot find directory ' + linux_dir)
1148 rust_version = self.find_rust_version(my_dir, linux_dir)
1149 cargo_bin = os.path.join(linux_dir, rust_version, 'bin')
1150 self.cargo_path = os.path.join(cargo_bin, 'cargo')
1151 if not os.path.isfile(self.cargo_path):
1152 sys.exit('ERROR: cannot find cargo in ' + cargo_bin
1153 + '; please try --cargo_bin= flag.')
1154 return
1155
1156 def find_rust_version(self, my_dir, linux_dir):
1157 """Use my script directory, find prebuilt rust version."""
1158 # First look up build/soong/rust/config/global.go.
1159 path2global = os.path.join(my_dir, '..', '..',
1160 'build', 'soong', 'rust', 'config', 'global.go')
1161 if os.path.isfile(path2global):
1162 # try to find: RustDefaultVersion = "1.44.0"
1163 version_pat = re.compile(
1164 r'\s*RustDefaultVersion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+)".*$')
1165 with open(path2global, 'r') as inf:
1166 for line in inf:
1167 result = version_pat.match(line)
1168 if result:
1169 return result.group(1)
1170 print('WARNING: cannot find RustDefaultVersion in ' + path2global)
1171 # Otherwise, find the newest (largest) version number in linux_dir.
1172 rust_version = (0, 0, 0) # the prebuilt version to use
1173 version_pat = re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)$')
1174 for dir_name in os.listdir(linux_dir):
1175 result = version_pat.match(dir_name)
1176 if not result:
1177 continue
1178 version = (result.group(1), result.group(2), result.group(3))
1179 if version > rust_version:
1180 rust_version = version
1181 return '.'.join(rust_version)
1182
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001183 def find_out_files(self):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001184 # list1 has build.rs output for normal crates
1185 list1 = glob.glob(TARGET_TMP + '/*/*/build/' + self.root_pkg + '-*/out/*')
1186 # list2 has build.rs output for proc-macro crates
1187 list2 = glob.glob(TARGET_TMP + '/*/build/' + self.root_pkg + '-*/out/*')
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001188 return list1 + list2
1189
1190 def copy_out_files(self):
1191 """Copy build.rs output files to ./out and set up build_out_files."""
1192 if self.checked_out_files:
1193 return
1194 self.checked_out_files = True
1195 cargo_out_files = self.find_out_files()
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001196 out_files = set()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001197 if cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001198 os.makedirs('out', exist_ok=True)
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001199 for path in cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001200 file_name = path.split('/')[-1]
1201 out_files.add(file_name)
1202 shutil.copy(path, 'out/' + file_name)
1203 self.build_out_files = sorted(out_files)
1204
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001205 def has_used_out_dir(self):
1206 """Returns true if env!("OUT_DIR") is found."""
1207 return 0 == os.system('grep -rl --exclude build.rs --include \\*.rs' +
1208 ' \'env!("OUT_DIR")\' * > /dev/null')
1209
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001210 def copy_out_module_name(self):
1211 if self.args.copy_out and self.build_out_files:
1212 return 'copy_' + self.root_pkg + '_build_out'
1213 else:
1214 return ''
1215
Haibo Huang0f72c952021-03-19 11:34:15 -07001216 def read_license(self, name):
1217 if not os.path.isfile(name):
1218 return ''
1219 license = ''
1220 with open(name, 'r') as intf:
1221 line = intf.readline()
1222 # Firstly skip ANDROID_BP_HEADER
1223 while line.startswith('//'):
1224 line = intf.readline()
Joel Galensond9d13b82021-04-05 11:27:55 -07001225 # Read all lines until we see a rust_* or genrule rule.
1226 while line != '' and not (line.startswith('rust_') or line.startswith('genrule {')):
Haibo Huang0f72c952021-03-19 11:34:15 -07001227 license += line
1228 line = intf.readline()
1229 return license.strip()
1230
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001231 def dump_copy_out_module(self, outf):
1232 """Output the genrule module to copy out/* to $(genDir)."""
1233 copy_out = self.copy_out_module_name()
1234 if not copy_out:
1235 return
1236 outf.write('\ngenrule {\n')
1237 outf.write(' name: "' + copy_out + '",\n')
1238 outf.write(' srcs: ["out/*"],\n')
1239 outf.write(' cmd: "cp $(in) $(genDir)",\n')
1240 if len(self.build_out_files) > 1:
1241 outf.write(' out: [\n')
1242 for f in self.build_out_files:
1243 outf.write(' "' + f + '",\n')
1244 outf.write(' ],\n')
1245 else:
1246 outf.write(' out: ["' + self.build_out_files[0] + '"],\n')
1247 outf.write('}\n')
1248
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001249 def init_bp_file(self, name):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001250 # name could be Android.bp or sub_dir_path/Android.bp
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001251 if name not in self.bp_files:
1252 self.bp_files.add(name)
Haibo Huang0f72c952021-03-19 11:34:15 -07001253 license_section = self.read_license(name)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001254 with open(name, 'w') as outf:
Joel Galenson367360c2021-04-29 14:31:43 -07001255 print_args = filter(lambda x: x != "--no-test-mapping", sys.argv[1:])
1256 outf.write(ANDROID_BP_HEADER.format(args=' '.join(print_args)))
Haibo Huang0f72c952021-03-19 11:34:15 -07001257 outf.write('\n')
1258 outf.write(license_section)
1259 outf.write('\n')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001260 # at most one copy_out module per .bp file
1261 self.dump_copy_out_module(outf)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001262
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001263 def try_claim_module_name(self, name, owner):
1264 """Reserve and return True if it has not been reserved yet."""
1265 if name not in self.name_owners or owner == self.name_owners[name]:
1266 self.name_owners[name] = owner
1267 return True
1268 return False
1269
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001270 def claim_module_name(self, prefix, owner, counter):
1271 """Return prefix if not owned yet, otherwise, prefix+str(counter)."""
1272 while True:
1273 name = prefix
1274 if counter > 0:
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001275 name += '_' + str(counter)
1276 if self.try_claim_module_name(name, owner):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001277 return name
1278 counter += 1
1279
1280 def find_root_pkg(self):
1281 """Read name of [package] in ./Cargo.toml."""
1282 if not os.path.exists('./Cargo.toml'):
1283 return
1284 with open('./Cargo.toml', 'r') as inf:
1285 pkg_section = re.compile(r'^ *\[package\]')
1286 name = re.compile('^ *name *= * "([^"]*)"')
1287 in_pkg = False
1288 for line in inf:
1289 if in_pkg:
1290 if name.match(line):
1291 self.root_pkg = name.match(line).group(1)
1292 break
1293 else:
1294 in_pkg = pkg_section.match(line) is not None
1295
1296 def run_cargo(self):
1297 """Calls cargo -v and save its output to ./cargo.out."""
1298 if self.skip_cargo:
1299 return self
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001300 cargo_toml = './Cargo.toml'
1301 cargo_out = './cargo.out'
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001302 # Do not use Cargo.lock, because .bp rules are designed to
1303 # run with "latest" crates avaialable on Android.
1304 cargo_lock = './Cargo.lock'
1305 cargo_lock_saved = './cargo.lock.saved'
1306 had_cargo_lock = os.path.exists(cargo_lock)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001307 if not os.access(cargo_toml, os.R_OK):
1308 print('ERROR: Cannot find or read', cargo_toml)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001309 return self
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001310 if not self.dry_run:
1311 if os.path.exists(cargo_out):
1312 os.remove(cargo_out)
1313 if not self.args.use_cargo_lock and had_cargo_lock: # save it
1314 os.rename(cargo_lock, cargo_lock_saved)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001315 cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> ' + cargo_out + ' 2>&1'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001316 # set up search PATH for cargo to find the correct rustc
1317 saved_path = os.environ['PATH']
1318 os.environ['PATH'] = os.path.dirname(self.cargo_path) + ':' + saved_path
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001319 # Add [workspace] to Cargo.toml if it is not there.
1320 added_workspace = False
1321 if self.args.add_workspace:
1322 with open(cargo_toml, 'r') as in_file:
1323 cargo_toml_lines = in_file.readlines()
1324 found_workspace = '[workspace]\n' in cargo_toml_lines
1325 if found_workspace:
1326 print('### WARNING: found [workspace] in Cargo.toml')
1327 else:
1328 with open(cargo_toml, 'a') as out_file:
1329 out_file.write('[workspace]\n')
1330 added_workspace = True
1331 if self.args.verbose:
1332 print('### INFO: added [workspace] to Cargo.toml')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001333 for c in self.cargo:
1334 features = ''
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001335 if c != 'clean':
1336 if self.args.features is not None:
1337 features = ' --no-default-features'
1338 if self.args.features:
1339 features += ' --features ' + self.args.features
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001340 cmd_v_flag = ' -vv ' if self.args.vv else ' -v '
1341 cmd = self.cargo_path + cmd_v_flag
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001342 cmd += c + features + cmd_tail
1343 if self.args.rustflags and c != 'clean':
1344 cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
1345 if self.dry_run:
1346 print('Dry-run skip:', cmd)
1347 else:
1348 if self.args.verbose:
1349 print('Running:', cmd)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001350 with open(cargo_out, 'a') as out_file:
1351 out_file.write('### Running: ' + cmd + '\n')
Joel Galenson6bf54e32021-05-17 10:54:50 -07001352 ret = os.system(cmd)
1353 if ret != 0:
1354 print('*** There was an error while running cargo. ' +
1355 'See the cargo.out file for details.')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001356 if added_workspace: # restore original Cargo.toml
1357 with open(cargo_toml, 'w') as out_file:
1358 out_file.writelines(cargo_toml_lines)
1359 if self.args.verbose:
1360 print('### INFO: restored original Cargo.toml')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001361 os.environ['PATH'] = saved_path
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001362 if not self.dry_run:
1363 if not had_cargo_lock: # restore to no Cargo.lock state
1364 os.remove(cargo_lock)
1365 elif not self.args.use_cargo_lock: # restore saved Cargo.lock
1366 os.rename(cargo_lock_saved, cargo_lock)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001367 return self
1368
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001369 def dump_pkg_obj2cc(self):
1370 """Dump debug info of the pkg_obj2cc map."""
1371 if not self.args.debug:
1372 return
1373 self.init_bp_file('Android.bp')
1374 with open('Android.bp', 'a') as outf:
1375 sorted_pkgs = sorted(self.pkg_obj2cc.keys())
1376 for pkg in sorted_pkgs:
1377 if not self.pkg_obj2cc[pkg]:
1378 continue
1379 outf.write('\n// obj => src for %s\n' % pkg)
1380 obj2cc = self.pkg_obj2cc[pkg]
1381 for obj in sorted(obj2cc.keys()):
1382 outf.write('// ' + short_out_name(pkg, obj) + ' => ' +
1383 short_out_name(pkg, obj2cc[obj].src) + '\n')
1384
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001385 def apply_patch(self):
1386 """Apply local patch file if it is given."""
1387 if self.args.patch:
1388 if self.dry_run:
1389 print('Dry-run skip patch file:', self.args.patch)
1390 else:
1391 if not os.path.exists(self.args.patch):
1392 self.append_to_bp('ERROR cannot find patch file: ' + self.args.patch)
1393 return self
1394 if self.args.verbose:
1395 print('### INFO: applying local patch file:', self.args.patch)
Joel Galenson7e8247e2021-05-20 18:51:42 -07001396 subprocess.run(['patch', '-s', '--no-backup-if-mismatch', './Android.bp',
1397 self.args.patch], check=True)
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001398 return self
1399
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001400 def gen_bp(self):
1401 """Parse cargo.out and generate Android.bp files."""
1402 if self.dry_run:
1403 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp')
1404 elif os.path.exists(CARGO_OUT):
1405 self.find_root_pkg()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001406 if self.args.copy_out:
1407 self.copy_out_files()
1408 elif self.find_out_files() and self.has_used_out_dir():
1409 print('WARNING: ' + self.root_pkg + ' has cargo output files; ' +
1410 'please rerun with the --copy-out flag.')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001411 with open(CARGO_OUT, 'r') as cargo_out:
1412 self.parse(cargo_out, 'Android.bp')
1413 self.crates.sort(key=get_module_name)
1414 for obj in self.cc_objects:
1415 obj.dump()
1416 self.dump_pkg_obj2cc()
1417 for crate in self.crates:
1418 crate.dump()
1419 dumped_libs = set()
1420 for lib in self.ar_objects:
1421 if lib.pkg == self.root_pkg:
1422 lib_name = file_base_name(lib.lib)
1423 if lib_name not in dumped_libs:
1424 dumped_libs.add(lib_name)
1425 lib.dump()
Joel Galenson5664f2a2021-06-10 10:13:49 -07001426 if self.args.add_toplevel_block:
1427 with open(self.args.add_toplevel_block, 'r') as f:
1428 self.append_to_bp('\n' + f.read() + '\n')
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001429 if self.errors:
Joel Galenson3f42f802021-04-07 12:42:17 -07001430 self.append_to_bp('\n' + ERRORS_LINE + '\n' + self.errors)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001431 return self
1432
1433 def add_ar_object(self, obj):
1434 self.ar_objects.append(obj)
1435
1436 def add_cc_object(self, obj):
1437 self.cc_objects.append(obj)
1438
1439 def add_crate(self, crate):
1440 """Merge crate with someone in crates, or append to it. Return crates."""
1441 if crate.skip_crate():
1442 if self.args.debug: # include debug info of all crates
1443 self.crates.append(crate)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001444 else:
1445 for c in self.crates:
1446 if c.merge(crate, 'Android.bp'):
1447 return
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001448 # If not merged, decide module type and name now.
1449 crate.decide_module_type()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001450 self.crates.append(crate)
1451
1452 def find_warning_owners(self):
1453 """For each warning file, find its owner crate."""
1454 missing_owner = False
1455 for f in self.warning_files:
1456 cargo_dir = '' # find lowest crate, with longest path
1457 owner = None # owner crate of this warning
1458 for c in self.crates:
1459 if (f.startswith(c.cargo_dir + '/') and
1460 len(cargo_dir) < len(c.cargo_dir)):
1461 cargo_dir = c.cargo_dir
1462 owner = c
1463 if owner:
1464 owner.has_warning = True
1465 else:
1466 missing_owner = True
1467 if missing_owner and os.path.exists('Cargo.toml'):
1468 # owner is the root cargo, with empty cargo_dir
1469 for c in self.crates:
1470 if not c.cargo_dir:
1471 c.has_warning = True
1472
1473 def rustc_command(self, n, rustc_line, line, outf_name):
1474 """Process a rustc command line from cargo -vv output."""
1475 # cargo build -vv output can have multiple lines for a rustc command
1476 # due to '\n' in strings for environment variables.
1477 # strip removes leading spaces and '\n' at the end
1478 new_rustc = (rustc_line.strip() + line) if rustc_line else line
1479 # Use an heuristic to detect the completions of a multi-line command.
1480 # This might fail for some very rare case, but easy to fix manually.
1481 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0:
1482 return new_rustc
1483 if RUSTC_VV_CMD_ARGS.match(new_rustc):
1484 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1)
1485 self.add_crate(Crate(self, outf_name).parse(n, args))
1486 else:
1487 self.assert_empty_vv_line(new_rustc)
1488 return ''
1489
1490 def cc_ar_command(self, n, groups, outf_name):
1491 pkg = groups.group(1)
1492 line = groups.group(3)
1493 if groups.group(2) == 'cc':
1494 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line))
1495 else:
1496 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line))
1497
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001498 def append_to_bp(self, line):
1499 self.init_bp_file('Android.bp')
1500 with open('Android.bp', 'a') as outf:
1501 outf.write(line)
1502
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001503 def assert_empty_vv_line(self, line):
1504 if line: # report error if line is not empty
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001505 self.append_to_bp('ERROR -vv line: ' + line)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001506 return ''
1507
1508 def parse(self, inf, outf_name):
1509 """Parse rustc and warning messages in inf, return a list of Crates."""
1510 n = 0 # line number
1511 prev_warning = False # true if the previous line was warning: ...
1512 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
1513 for line in inf:
1514 n += 1
1515 if line.startswith('warning: '):
1516 prev_warning = True
1517 rustc_line = self.assert_empty_vv_line(rustc_line)
1518 continue
1519 new_rustc = ''
1520 if RUSTC_PAT.match(line):
1521 args_line = RUSTC_PAT.match(line).group(1)
1522 self.add_crate(Crate(self, outf_name).parse(n, args_line))
1523 self.assert_empty_vv_line(rustc_line)
1524 elif rustc_line or RUSTC_VV_PAT.match(line):
1525 new_rustc = self.rustc_command(n, rustc_line, line, outf_name)
1526 elif CC_AR_VV_PAT.match(line):
1527 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name)
1528 elif prev_warning and WARNING_FILE_PAT.match(line):
1529 self.assert_empty_vv_line(rustc_line)
1530 fpath = WARNING_FILE_PAT.match(line).group(1)
1531 if fpath[0] != '/': # ignore absolute path
1532 self.warning_files.add(fpath)
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001533 elif line.startswith('error: ') or line.startswith('error[E'):
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001534 if not self.args.ignore_cargo_errors:
1535 self.errors += line
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001536 prev_warning = False
1537 rustc_line = new_rustc
1538 self.find_warning_owners()
1539
1540
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001541def get_parser():
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001542 """Parse main arguments."""
1543 parser = argparse.ArgumentParser('cargo2android')
1544 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001545 '--add_workspace',
1546 action='store_true',
1547 default=False,
1548 help=('append [workspace] to Cargo.toml before calling cargo,' +
1549 ' to treat current directory as root of package source;' +
1550 ' otherwise the relative source file path in generated' +
1551 ' .bp file will be from the parent directory.'))
1552 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001553 '--cargo',
1554 action='append',
1555 metavar='args_string',
1556 help=('extra cargo build -v args in a string, ' +
1557 'each --cargo flag calls cargo build -v once'))
1558 parser.add_argument(
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001559 '--cargo_bin',
1560 type=str,
1561 help='use cargo in the cargo_bin directory instead of the prebuilt one')
1562 parser.add_argument(
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001563 '--copy-out',
1564 action='store_true',
1565 default=False,
1566 help=('only for root directory, ' +
1567 'copy build.rs output to ./out/* and add a genrule to copy ' +
1568 './out/* to genrule output; for crates with code pattern: ' +
1569 'include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))'))
1570 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001571 '--debug',
1572 action='store_true',
1573 default=False,
1574 help='dump debug info into Android.bp')
1575 parser.add_argument(
1576 '--dependencies',
1577 action='store_true',
1578 default=False,
Joel Galenson833848c2021-08-17 10:50:42 -07001579 help='Deprecated. Has no effect.')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001580 parser.add_argument(
1581 '--device',
1582 action='store_true',
1583 default=False,
1584 help='run cargo also for a default device target')
1585 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001586 '--features',
1587 type=str,
1588 help=('pass features to cargo build, ' +
1589 'empty string means no default features'))
1590 parser.add_argument(
1591 '--global_defaults',
1592 type=str,
1593 help='add a defaults name to every module')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001594 parser.add_argument(
1595 '--host-first-multilib',
1596 action='store_true',
1597 default=False,
1598 help=('add a compile_multilib:"first" property ' +
1599 'to Android.bp host modules.'))
1600 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001601 '--ignore-cargo-errors',
1602 action='store_true',
1603 default=False,
1604 help='do not append cargo/rustc error messages to Android.bp')
1605 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001606 '--no-host',
1607 action='store_true',
1608 default=False,
1609 help='do not run cargo for the host; only for the device target')
1610 parser.add_argument(
1611 '--no-subdir',
1612 action='store_true',
1613 default=False,
1614 help='do not output anything for sub-directories')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001615 parser.add_argument(
1616 '--onefile',
1617 action='store_true',
1618 default=False,
1619 help=('output all into one ./Android.bp, default will generate ' +
1620 'one Android.bp per Cargo.toml in subdirectories'))
1621 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001622 '--patch',
1623 type=str,
1624 help='apply the given patch file to generated ./Android.bp')
1625 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001626 '--run',
1627 action='store_true',
1628 default=False,
1629 help='run it, default is dry-run')
1630 parser.add_argument('--rustflags', type=str, help='passing flags to rustc')
1631 parser.add_argument(
1632 '--skipcargo',
1633 action='store_true',
1634 default=False,
1635 help='skip cargo command, parse cargo.out, and generate Android.bp')
1636 parser.add_argument(
1637 '--tests',
1638 action='store_true',
1639 default=False,
1640 help='run cargo build --tests after normal build')
1641 parser.add_argument(
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001642 '--use-cargo-lock',
1643 action='store_true',
1644 default=False,
1645 help=('run cargo build with existing Cargo.lock ' +
1646 '(used when some latest dependent crates failed)'))
1647 parser.add_argument(
Joel Galensond9c4de62021-04-23 10:26:40 -07001648 '--min-sdk-version',
1649 type=str,
1650 help='Minimum SDK version')
1651 parser.add_argument(
1652 '--apex-available',
1653 nargs='*',
1654 help='Mark the main library as apex_available with the given apexes.')
1655 parser.add_argument(
Matthew Maurerac677252021-08-13 15:52:52 -07001656 '--native-bridge-supported',
1657 action='store_true',
1658 default=False,
1659 help='Mark the main library as native_bridge_supported.')
1660 parser.add_argument(
1661 '--product-available',
1662 action='store_true',
1663 default=False,
1664 help='Mark the main library as product_available.')
1665 parser.add_argument(
1666 '--recovery-available',
1667 action='store_true',
1668 default=False,
1669 help='Mark the main library as recovery_available.')
1670 parser.add_argument(
Ivan Lozano91920862021-07-19 10:49:08 -04001671 '--vendor-available',
1672 action='store_true',
1673 default=False,
1674 help='Mark the main library as vendor_available.')
1675 parser.add_argument(
1676 '--vendor-ramdisk-available',
1677 action='store_true',
1678 default=False,
1679 help='Mark the main library as vendor_ramdisk_available.')
1680 parser.add_argument(
Matthew Maurerac677252021-08-13 15:52:52 -07001681 '--ramdisk-available',
1682 action='store_true',
1683 default=False,
1684 help='Mark the main library as ramdisk_available.')
1685 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001686 '--force-rlib',
1687 action='store_true',
1688 default=False,
1689 help='Make the main library an rlib.')
1690 parser.add_argument(
Joel Galenson12467e52021-07-12 14:33:28 -07001691 '--whole-static-libs',
1692 nargs='*',
1693 default=[],
1694 help='Make the given libraries (without lib prefixes) whole_static_libs.')
1695 parser.add_argument(
Ivan Lozano26aa1c32021-08-16 11:20:32 -04001696 '--no-pkg-vers',
1697 action='store_true',
1698 default=False,
1699 help='Do not attempt to determine the package version automatically.')
1700 parser.add_argument(
Joel Galensone4f53882021-07-19 11:14:55 -07001701 '--test-data',
1702 nargs='*',
1703 default=[],
1704 help=('Add the given file to the given test\'s data property. ' +
1705 'Usage: test-path=data-path'))
1706 parser.add_argument(
Joel Galenson97e414a2021-05-27 09:42:32 -07001707 '--dependency-blocklist',
1708 nargs='*',
1709 default=[],
Joel Galenson12467e52021-07-12 14:33:28 -07001710 help='Do not emit the given dependencies (without lib prefixes).')
Joel Galenson97e414a2021-05-27 09:42:32 -07001711 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001712 '--lib-blocklist',
1713 nargs='*',
1714 default=[],
Joel Galenson12467e52021-07-12 14:33:28 -07001715 help='Do not emit the given C libraries as dependencies (without lib prefixes).')
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001716 parser.add_argument(
Joel Galensonf6b3c912021-06-03 16:00:54 -07001717 '--test-blocklist',
1718 nargs='*',
1719 default=[],
1720 help=('Do not emit the given tests. ' +
1721 'Pass the path to the test file to exclude.'))
1722 parser.add_argument(
Joel Galenson3d6d1e72021-06-07 15:00:24 -07001723 '--cfg-blocklist',
1724 nargs='*',
1725 default=[],
1726 help='Do not emit the given cfg.')
1727 parser.add_argument(
Joel Galenson5664f2a2021-06-10 10:13:49 -07001728 '--add-toplevel-block',
1729 type=str,
1730 help='Add the contents of the given file to the top level of the Android.bp.')
1731 parser.add_argument(
1732 '--add-module-block',
1733 type=str,
1734 help='Add the contents of the given file to the main module.')
1735 parser.add_argument(
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001736 '--no-test-mapping',
1737 action='store_true',
1738 default=False,
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001739 help='Deprecated. Has no effect.')
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001740 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001741 '--verbose',
1742 action='store_true',
1743 default=False,
1744 help='echo executed commands')
1745 parser.add_argument(
1746 '--vv',
1747 action='store_true',
1748 default=False,
1749 help='run cargo with -vv instead of default -v')
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001750 parser.add_argument(
1751 '--dump-config-and-exit',
1752 type=str,
1753 help=('Dump command-line arguments (minus this flag) to a config file and exit. ' +
1754 'This is intended to help migrate from command line options to config files.'))
1755 parser.add_argument(
1756 '--config',
1757 type=str,
1758 help=('Load command-line options from the given config file. ' +
1759 'Options in this file will override those passed on the command line.'))
1760 return parser
1761
1762
1763def parse_args(parser):
1764 """Parses command-line options."""
1765 args = parser.parse_args()
1766 # Use the values specified in a config file if one was found.
1767 if args.config:
1768 with open(args.config, 'r') as f:
1769 config = json.load(f)
1770 args_dict = vars(args)
1771 for arg in config:
1772 args_dict[arg.replace('-', '_')] = config[arg]
1773 return args
1774
1775
1776def dump_config(parser, args):
1777 """Writes the non-default command-line options to the specified file."""
1778 args_dict = vars(args)
1779 # Filter out the arguments that have their default value.
Joel Galenson367360c2021-04-29 14:31:43 -07001780 # Also filter certain "temporary" arguments.
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001781 non_default_args = {}
1782 for arg in args_dict:
Joel Galenson367360c2021-04-29 14:31:43 -07001783 if args_dict[arg] != parser.get_default(
1784 arg) and arg != 'dump_config_and_exit' and arg != 'no_test_mapping':
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001785 non_default_args[arg.replace('_', '-')] = args_dict[arg]
1786 # Write to the specified file.
1787 with open(args.dump_config_and_exit, 'w') as f:
1788 json.dump(non_default_args, f, indent=2, sort_keys=True)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001789
1790
1791def main():
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001792 parser = get_parser()
1793 args = parse_args(parser)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001794 if not args.run: # default is dry-run
1795 print(DRY_RUN_NOTE)
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001796 if args.dump_config_and_exit:
1797 dump_config(parser, args)
1798 else:
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001799 Runner(args).run_cargo().gen_bp().apply_patch()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001800
1801
1802if __name__ == '__main__':
1803 main()