blob: 6e63ca115aa3fc731ed9f76ed0370094703b2cd2 [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(' ],')
Ivan Lozano91920862021-07-19 10:49:08 -0400704 if self.runner.args.vendor_available:
705 self.write(' vendor_available: true,')
706 if self.runner.args.vendor_ramdisk_available:
707 self.write(' vendor_ramdisk_available: true,')
Joel Galensond9c4de62021-04-23 10:26:40 -0700708 if self.runner.args.min_sdk_version and crate_type == 'lib':
709 self.write(' min_sdk_version: "%s",' % self.runner.args.min_sdk_version)
Joel Galensone4f53882021-07-19 11:14:55 -0700710 if crate_type == 'test' and not self.default_srcs:
711 self.dump_test_data()
Joel Galenson5664f2a2021-06-10 10:13:49 -0700712 if self.runner.args.add_module_block:
713 with open(self.runner.args.add_module_block, 'r') as f:
714 self.write(' %s,' % f.read().replace('\n', '\n '))
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700715 self.write('}')
716
717 def dump_android_flags(self):
718 """Dump Android module flags property."""
ThiƩbaud Weksteena5a728b2021-04-08 14:23:49 +0200719 if not self.codegens and not self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700720 return
721 self.write(' flags: [')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800722 if self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700723 self.write(' "--cap-lints ' + self.cap_lints + '",')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700724 codegens_fmt = '"-C %s"'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700725 self.dump_android_property_list_items(codegens_fmt, self.codegens)
726 self.write(' ],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700727
728 def dump_edition_flags_libs(self):
729 if self.edition:
730 self.write(' edition: "' + self.edition + '",')
731 self.dump_android_property_list('features', '"%s"', self.features)
Joel Galenson3d6d1e72021-06-07 15:00:24 -0700732 cfgs = [cfg for cfg in self.cfgs if not cfg in self.runner.args.cfg_blocklist]
733 self.dump_android_property_list('cfgs', '"%s"', cfgs)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700734 self.dump_android_flags()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800735 if self.externs:
736 self.dump_android_externs()
Joel Galenson12467e52021-07-12 14:33:28 -0700737 all_static_libs = [lib for lib in self.static_libs if not lib in self.runner.args.lib_blocklist]
738 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 -0700739 self.dump_android_property_list('static_libs', '"lib%s"', static_libs)
Joel Galenson12467e52021-07-12 14:33:28 -0700740 whole_static_libs = [lib for lib in all_static_libs if lib in self.runner.args.whole_static_libs]
741 self.dump_android_property_list('whole_static_libs', '"lib%s"', whole_static_libs)
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700742 shared_libs = [lib for lib in self.shared_libs if not lib in self.runner.args.lib_blocklist]
743 self.dump_android_property_list('shared_libs', '"lib%s"', shared_libs)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800744
Joel Galensone4f53882021-07-19 11:14:55 -0700745 def dump_test_data(self):
746 data = [data for (name, data) in map(lambda kv: kv.split('=', 1), self.runner.args.test_data)
747 if self.srcs == [name]]
748 if data:
749 self.dump_android_property_list('data', '"%s"', data)
750
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700751 def main_src_basename_path(self):
752 return re.sub('/', '_', re.sub('.rs$', '', self.main_src))
753
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800754 def test_module_name(self):
755 """Return a unique name for a test module."""
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700756 # root_pkg+(_host|_device) + '_test_'+source_file_name
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700757 suffix = self.main_src_basename_path()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700758 host_device = '_host'
759 if self.device_supported:
760 host_device = '_device'
761 return self.root_pkg + host_device + '_test_' + suffix
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800762
763 def decide_module_type(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700764 # Use the first crate type for the default/first module.
765 crate_type = self.crate_types[0] if self.crate_types else ''
766 self.decide_one_module_type(crate_type)
767
768 def decide_one_module_type(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800769 """Decide which Android module type to use."""
770 host = '' if self.device_supported else '_host'
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700771 rlib = '_rlib' if self.runner.args.force_rlib else ''
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700772 if crate_type == 'bin': # rust_binary[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800773 self.module_type = 'rust_binary' + host
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700774 # In rare cases like protobuf-codegen, the output binary name must
775 # be renamed to use as a plugin for protoc.
776 self.stem = altered_stem(self.crate_name)
777 self.module_name = altered_name(self.crate_name)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700778 elif crate_type == 'lib': # rust_library[_host]
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700779 # TODO(chh): should this be rust_library[_host]?
780 # Assuming that Cargo.toml do not use both 'lib' and 'rlib',
781 # because we map them both to rlib.
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700782 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800783 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700784 self.module_name = altered_name(self.stem)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700785 elif crate_type == 'rlib': # rust_library[_host]
Joel Galensoncb5f2f02021-06-08 14:47:55 -0700786 self.module_type = 'rust_library' + rlib + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700787 self.stem = 'lib' + self.crate_name
788 self.module_name = altered_name(self.stem)
789 elif crate_type == 'dylib': # rust_library[_host]_dylib
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800790 self.module_type = 'rust_library' + host + '_dylib'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700791 self.stem = 'lib' + self.crate_name
792 self.module_name = altered_name(self.stem) + '_dylib'
793 elif crate_type == 'cdylib': # rust_library[_host]_shared
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500794 self.module_type = 'rust_ffi' + host + '_shared'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700795 self.stem = 'lib' + self.crate_name
796 self.module_name = altered_name(self.stem) + '_shared'
797 elif crate_type == 'staticlib': # rust_library[_host]_static
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500798 self.module_type = 'rust_ffi' + host + '_static'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700799 self.stem = 'lib' + self.crate_name
800 self.module_name = altered_name(self.stem) + '_static'
801 elif crate_type == 'test': # rust_test[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800802 self.module_type = 'rust_test' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700803 # Before do_merge, stem name is based on the --crate-name parameter.
804 # and test module name is based on stem.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800805 self.stem = self.test_module_name()
806 # self.stem will be changed after merging with other tests.
807 # self.stem is NOT used for final test binary name.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700808 # rust_test uses each source file base name as part of output file name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700809 # In do_merge, this function is called again, with a module_name.
810 # We make sure that the module name is unique in each package.
811 if self.module_name:
812 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add
813 # different suffixes and distinguish multiple tests of the same
814 # crate name. We ignore -C and use claim_module_name to get
815 # unique sequential suffix.
816 self.module_name = self.runner.claim_module_name(
817 self.module_name, self, 0)
818 # Now the module name is unique, stem should also match and unique.
819 self.stem = self.module_name
820 elif crate_type == 'proc-macro': # rust_proc_macro
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800821 self.module_type = 'rust_proc_macro'
822 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700823 self.module_name = altered_name(self.stem)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800824 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]?
825 self.module_type = ''
826 self.stem = ''
827
828 def dump_android_property_list_items(self, fmt, values):
829 for v in values:
830 # fmt has quotes, so we need escape_quotes(v)
831 self.write(' ' + (fmt % escape_quotes(v)) + ',')
832
833 def dump_android_property_list(self, name, fmt, values):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700834 if not values:
835 return
836 if len(values) > 1:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800837 self.write(' ' + name + ': [')
838 self.dump_android_property_list_items(fmt, values)
839 self.write(' ],')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700840 else:
841 self.write(' ' + name + ': [' +
842 (fmt % escape_quotes(values[0])) + '],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800843
844 def dump_android_core_properties(self):
845 """Dump the module header, name, stem, etc."""
846 self.write(' name: "' + self.module_name + '",')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700847 # see properties shared by dump_defaults_module
848 if self.defaults:
849 self.write(' defaults: ["' + self.defaults + '"],')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700850 elif self.runner.args.global_defaults:
851 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800852 if self.stem != self.module_name:
853 self.write(' stem: "' + self.stem + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700854 if self.has_warning and not self.cap_lints and not self.default_srcs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700855 self.write(' // has rustc warnings')
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700856 if self.host_supported and self.device_supported and self.module_type != 'rust_proc_macro':
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800857 self.write(' host_supported: true,')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700858 if not self.defaults:
859 self.write(' crate_name: "' + self.crate_name + '",')
Ivan Lozanocc660f12021-08-11 16:49:46 -0400860 if not self.defaults and self.cargo_env_compat:
861 self.write(' cargo_env_compat: true,')
862 self.write(' cargo_pkg_version: "' + self.cargo_pkg_version + '",')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700863 if not self.default_srcs:
864 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700865 if 'test' in self.crate_types and not self.defaults:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800866 # self.root_pkg can have multiple test modules, with different *_tests[n]
867 # names, but their executables can all be installed under the same _tests
868 # directory. When built from Cargo.toml, all tests should have different
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700869 # file or crate names. So we used (root_pkg + '_tests') name as the
870 # relative_install_path.
871 # However, some package like 'slab' can have non-mergeable tests that
872 # must be separated by different module names. So, here we no longer
873 # emit relative_install_path.
874 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800875 self.write(' test_suites: ["general-tests"],')
876 self.write(' auto_gen_config: true,')
Joel Galensone261a152021-01-12 11:31:53 -0800877 if 'test' in self.crate_types and self.host_supported:
878 self.write(' test_options: {')
879 self.write(' unit_test: true,')
880 self.write(' },')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800881
882 def dump_android_externs(self):
883 """Dump the dependent rlibs and dylibs property."""
884 so_libs = list()
885 rust_libs = ''
886 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$')
887 for lib in self.externs:
888 # normal value of lib: "libc = liblibc-*.rlib"
889 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib"
890 # we should use "libgetrandom", not "lib" + "getrandom_package"
891 groups = deps_libname.match(lib)
892 if groups is not None:
893 lib_name = groups.group(1)
894 else:
895 lib_name = re.sub(' .*$', '', lib)
Joel Galenson97e414a2021-05-27 09:42:32 -0700896 if lib_name in self.runner.args.dependency_blocklist:
897 continue
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800898 if lib.endswith('.rlib') or lib.endswith('.rmeta'):
899 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta.
900 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n'
901 elif lib.endswith('.so'):
902 so_libs.append(lib_name)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700903 elif lib != 'proc_macro': # --extern proc_macro is special and ignored
904 rust_libs += ' // ERROR: unknown type of lib ' + lib + '\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800905 if rust_libs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700906 self.write(' rustlibs: [\n' + rust_libs + ' ],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800907 # Are all dependent .so files proc_macros?
908 # TODO(chh): Separate proc_macros and dylib.
909 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs)
910
911
912class ARObject(object):
913 """Information of an "ar" link command."""
914
915 def __init__(self, runner, outf_name):
916 # Remembered global runner and its members.
917 self.runner = runner
918 self.pkg = ''
919 self.outf_name = outf_name # path to Android.bp
920 # "ar" arguments
921 self.line_num = 1
922 self.line = ''
923 self.flags = '' # e.g. "crs"
924 self.lib = '' # e.g. "/.../out/lib*.a"
925 self.objs = list() # e.g. "/.../out/.../*.o"
926
927 def parse(self, pkg, line_num, args_line):
928 """Collect ar obj/lib file names."""
929 self.pkg = pkg
930 self.line_num = line_num
931 self.line = args_line
932 args = args_line.split()
933 num_args = len(args)
934 if num_args < 3:
935 print('ERROR: "ar" command has too few arguments', args_line)
936 else:
937 self.flags = unquote(args[0])
938 self.lib = unquote(args[1])
939 self.objs = sorted(set(map(unquote, args[2:])))
940 return self
941
942 def write(self, s):
943 self.outf.write(s + '\n')
944
945 def dump_debug_info(self):
946 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line)
947 self.write('// ar_object for %12s' % self.pkg)
948 self.write('// flags = %s' % self.flags)
949 self.write('// lib = %s' % short_out_name(self.pkg, self.lib))
950 for o in self.objs:
951 self.write('// obj = %s' % short_out_name(self.pkg, o))
952
953 def dump_android_lib(self):
954 """Write cc_library_static into Android.bp."""
955 self.write('\ncc_library_static {')
956 self.write(' name: "' + file_base_name(self.lib) + '",')
957 self.write(' host_supported: true,')
958 if self.flags != 'crs':
959 self.write(' // ar flags = %s' % self.flags)
960 if self.pkg not in self.runner.pkg_obj2cc:
961 self.write(' ERROR: cannot find source files.\n}')
962 return
963 self.write(' srcs: [')
964 obj2cc = self.runner.pkg_obj2cc[self.pkg]
965 # Note: wflags are ignored.
966 dflags = list()
967 fflags = list()
968 for obj in self.objs:
969 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",')
970 # TODO(chh): union of dflags and flags of all obj
971 # Now, just a temporary hack that uses the last obj's flags
972 dflags = obj2cc[obj].dflags
973 fflags = obj2cc[obj].fflags
974 self.write(' ],')
975 self.write(' cflags: [')
976 self.write(' "-O3",') # TODO(chh): is this default correct?
977 self.write(' "-Wno-error",')
978 for x in fflags:
979 self.write(' "-f' + x + '",')
980 for x in dflags:
981 self.write(' "-D' + x + '",')
982 self.write(' ],')
983 self.write('}')
984
985 def dump(self):
986 """Dump error/debug/module info to the output .bp file."""
987 self.runner.init_bp_file(self.outf_name)
988 with open(self.outf_name, 'a') as outf:
989 self.outf = outf
990 if self.runner.args.debug:
991 self.dump_debug_info()
992 self.dump_android_lib()
993
994
995class CCObject(object):
996 """Information of a "cc" compilation command."""
997
998 def __init__(self, runner, outf_name):
999 # Remembered global runner and its members.
1000 self.runner = runner
1001 self.pkg = ''
1002 self.outf_name = outf_name # path to Android.bp
1003 # "cc" arguments
1004 self.line_num = 1
1005 self.line = ''
1006 self.src = ''
1007 self.obj = ''
1008 self.dflags = list() # -D flags
1009 self.fflags = list() # -f flags
1010 self.iflags = list() # -I flags
1011 self.wflags = list() # -W flags
1012 self.other_args = list()
1013
1014 def parse(self, pkg, line_num, args_line):
1015 """Collect cc compilation flags and src/out file names."""
1016 self.pkg = pkg
1017 self.line_num = line_num
1018 self.line = args_line
1019 args = args_line.split()
1020 i = 0
1021 while i < len(args):
1022 arg = args[i]
1023 if arg == '"-c"':
1024 i += 1
1025 if args[i].startswith('"-o'):
1026 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c"
1027 self.obj = unquote(args[i])[2:]
1028 i += 1
1029 self.src = unquote(args[i])
1030 else:
1031 self.src = unquote(args[i])
1032 elif arg == '"-o"':
1033 i += 1
1034 self.obj = unquote(args[i])
1035 elif arg == '"-I"':
1036 i += 1
1037 self.iflags.append(unquote(args[i]))
1038 elif arg.startswith('"-D'):
1039 self.dflags.append(unquote(args[i])[2:])
1040 elif arg.startswith('"-f'):
1041 self.fflags.append(unquote(args[i])[2:])
1042 elif arg.startswith('"-W'):
1043 self.wflags.append(unquote(args[i])[2:])
1044 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or
1045 arg == '"-g3"'):
1046 # ignore -O -m64 -g
1047 self.other_args.append(unquote(args[i]))
1048 i += 1
1049 self.dflags = sorted(set(self.dflags))
1050 self.fflags = sorted(set(self.fflags))
1051 # self.wflags is not sorted because some are order sensitive
1052 # and we ignore them anyway.
1053 if self.pkg not in self.runner.pkg_obj2cc:
1054 self.runner.pkg_obj2cc[self.pkg] = {}
1055 self.runner.pkg_obj2cc[self.pkg][self.obj] = self
1056 return self
1057
1058 def write(self, s):
1059 self.outf.write(s + '\n')
1060
1061 def dump_debug_flags(self, name, flags):
1062 self.write('// ' + name + ':')
1063 for f in flags:
1064 self.write('// %s' % f)
1065
1066 def dump(self):
1067 """Dump only error/debug info to the output .bp file."""
1068 if not self.runner.args.debug:
1069 return
1070 self.runner.init_bp_file(self.outf_name)
1071 with open(self.outf_name, 'a') as outf:
1072 self.outf = outf
1073 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line)
1074 self.write('// cc_object for %12s' % self.pkg)
1075 self.write('// src = %s' % short_out_name(self.pkg, self.src))
1076 self.write('// obj = %s' % short_out_name(self.pkg, self.obj))
1077 self.dump_debug_flags('-I flags', self.iflags)
1078 self.dump_debug_flags('-D flags', self.dflags)
1079 self.dump_debug_flags('-f flags', self.fflags)
1080 self.dump_debug_flags('-W flags', self.wflags)
1081 if self.other_args:
1082 self.dump_debug_flags('other args', self.other_args)
1083
1084
1085class Runner(object):
1086 """Main class to parse cargo -v output and print Android module definitions."""
1087
1088 def __init__(self, args):
1089 self.bp_files = set() # Remember all output Android.bp files.
1090 self.root_pkg = '' # name of package in ./Cargo.toml
1091 # Saved flags, modes, and data.
1092 self.args = args
1093 self.dry_run = not args.run
1094 self.skip_cargo = args.skipcargo
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001095 self.cargo_path = './cargo' # path to cargo, will be set later
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001096 self.checked_out_files = False # to check only once
1097 self.build_out_files = [] # output files generated by build.rs
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001098 # All cc/ar objects, crates, dependencies, and warning files
1099 self.cc_objects = list()
1100 self.pkg_obj2cc = {}
1101 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i]
1102 self.ar_objects = list()
1103 self.crates = list()
1104 self.dependencies = list() # dependent and build script crates
1105 self.warning_files = set()
1106 # Keep a unique mapping from (module name) to crate
1107 self.name_owners = {}
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001108 # Save and dump all errors from cargo to Android.bp.
1109 self.errors = ''
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001110 self.setup_cargo_path()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001111 # Default action is cargo clean, followed by build or user given actions.
1112 if args.cargo:
1113 self.cargo = ['clean'] + args.cargo
1114 else:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001115 default_target = '--target x86_64-unknown-linux-gnu'
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -07001116 # Use the same target for both host and default device builds.
1117 # Same target is used as default in host x86_64 Android compilation.
1118 # Note: b/169872957, prebuilt cargo failed to build vsock
1119 # on x86_64-unknown-linux-musl systems.
1120 self.cargo = ['clean', 'build ' + default_target]
1121 if args.tests:
1122 self.cargo.append('build --tests ' + default_target)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001123
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001124 def setup_cargo_path(self):
1125 """Find cargo in the --cargo_bin or prebuilt rust bin directory."""
1126 if self.args.cargo_bin:
1127 self.cargo_path = os.path.join(self.args.cargo_bin, 'cargo')
1128 if not os.path.isfile(self.cargo_path):
1129 sys.exit('ERROR: cannot find cargo in ' + self.args.cargo_bin)
1130 print('WARNING: using cargo in ' + self.args.cargo_bin)
1131 return
1132 # We have only tested this on Linux.
1133 if platform.system() != 'Linux':
1134 sys.exit('ERROR: this script has only been tested on Linux with cargo.')
1135 # Assuming that this script is in development/scripts.
1136 my_dir = os.path.dirname(os.path.abspath(__file__))
1137 linux_dir = os.path.join(my_dir, '..', '..',
1138 'prebuilts', 'rust', 'linux-x86')
1139 if not os.path.isdir(linux_dir):
1140 sys.exit('ERROR: cannot find directory ' + linux_dir)
1141 rust_version = self.find_rust_version(my_dir, linux_dir)
1142 cargo_bin = os.path.join(linux_dir, rust_version, 'bin')
1143 self.cargo_path = os.path.join(cargo_bin, 'cargo')
1144 if not os.path.isfile(self.cargo_path):
1145 sys.exit('ERROR: cannot find cargo in ' + cargo_bin
1146 + '; please try --cargo_bin= flag.')
1147 return
1148
1149 def find_rust_version(self, my_dir, linux_dir):
1150 """Use my script directory, find prebuilt rust version."""
1151 # First look up build/soong/rust/config/global.go.
1152 path2global = os.path.join(my_dir, '..', '..',
1153 'build', 'soong', 'rust', 'config', 'global.go')
1154 if os.path.isfile(path2global):
1155 # try to find: RustDefaultVersion = "1.44.0"
1156 version_pat = re.compile(
1157 r'\s*RustDefaultVersion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+)".*$')
1158 with open(path2global, 'r') as inf:
1159 for line in inf:
1160 result = version_pat.match(line)
1161 if result:
1162 return result.group(1)
1163 print('WARNING: cannot find RustDefaultVersion in ' + path2global)
1164 # Otherwise, find the newest (largest) version number in linux_dir.
1165 rust_version = (0, 0, 0) # the prebuilt version to use
1166 version_pat = re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)$')
1167 for dir_name in os.listdir(linux_dir):
1168 result = version_pat.match(dir_name)
1169 if not result:
1170 continue
1171 version = (result.group(1), result.group(2), result.group(3))
1172 if version > rust_version:
1173 rust_version = version
1174 return '.'.join(rust_version)
1175
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001176 def find_out_files(self):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001177 # list1 has build.rs output for normal crates
1178 list1 = glob.glob(TARGET_TMP + '/*/*/build/' + self.root_pkg + '-*/out/*')
1179 # list2 has build.rs output for proc-macro crates
1180 list2 = glob.glob(TARGET_TMP + '/*/build/' + self.root_pkg + '-*/out/*')
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001181 return list1 + list2
1182
1183 def copy_out_files(self):
1184 """Copy build.rs output files to ./out and set up build_out_files."""
1185 if self.checked_out_files:
1186 return
1187 self.checked_out_files = True
1188 cargo_out_files = self.find_out_files()
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001189 out_files = set()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001190 if cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001191 os.makedirs('out', exist_ok=True)
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001192 for path in cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001193 file_name = path.split('/')[-1]
1194 out_files.add(file_name)
1195 shutil.copy(path, 'out/' + file_name)
1196 self.build_out_files = sorted(out_files)
1197
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001198 def has_used_out_dir(self):
1199 """Returns true if env!("OUT_DIR") is found."""
1200 return 0 == os.system('grep -rl --exclude build.rs --include \\*.rs' +
1201 ' \'env!("OUT_DIR")\' * > /dev/null')
1202
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001203 def copy_out_module_name(self):
1204 if self.args.copy_out and self.build_out_files:
1205 return 'copy_' + self.root_pkg + '_build_out'
1206 else:
1207 return ''
1208
Haibo Huang0f72c952021-03-19 11:34:15 -07001209 def read_license(self, name):
1210 if not os.path.isfile(name):
1211 return ''
1212 license = ''
1213 with open(name, 'r') as intf:
1214 line = intf.readline()
1215 # Firstly skip ANDROID_BP_HEADER
1216 while line.startswith('//'):
1217 line = intf.readline()
Joel Galensond9d13b82021-04-05 11:27:55 -07001218 # Read all lines until we see a rust_* or genrule rule.
1219 while line != '' and not (line.startswith('rust_') or line.startswith('genrule {')):
Haibo Huang0f72c952021-03-19 11:34:15 -07001220 license += line
1221 line = intf.readline()
1222 return license.strip()
1223
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001224 def dump_copy_out_module(self, outf):
1225 """Output the genrule module to copy out/* to $(genDir)."""
1226 copy_out = self.copy_out_module_name()
1227 if not copy_out:
1228 return
1229 outf.write('\ngenrule {\n')
1230 outf.write(' name: "' + copy_out + '",\n')
1231 outf.write(' srcs: ["out/*"],\n')
1232 outf.write(' cmd: "cp $(in) $(genDir)",\n')
1233 if len(self.build_out_files) > 1:
1234 outf.write(' out: [\n')
1235 for f in self.build_out_files:
1236 outf.write(' "' + f + '",\n')
1237 outf.write(' ],\n')
1238 else:
1239 outf.write(' out: ["' + self.build_out_files[0] + '"],\n')
1240 outf.write('}\n')
1241
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001242 def init_bp_file(self, name):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001243 # name could be Android.bp or sub_dir_path/Android.bp
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001244 if name not in self.bp_files:
1245 self.bp_files.add(name)
Haibo Huang0f72c952021-03-19 11:34:15 -07001246 license_section = self.read_license(name)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001247 with open(name, 'w') as outf:
Joel Galenson367360c2021-04-29 14:31:43 -07001248 print_args = filter(lambda x: x != "--no-test-mapping", sys.argv[1:])
1249 outf.write(ANDROID_BP_HEADER.format(args=' '.join(print_args)))
Haibo Huang0f72c952021-03-19 11:34:15 -07001250 outf.write('\n')
1251 outf.write(license_section)
1252 outf.write('\n')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001253 # at most one copy_out module per .bp file
1254 self.dump_copy_out_module(outf)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001255
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001256 def try_claim_module_name(self, name, owner):
1257 """Reserve and return True if it has not been reserved yet."""
1258 if name not in self.name_owners or owner == self.name_owners[name]:
1259 self.name_owners[name] = owner
1260 return True
1261 return False
1262
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001263 def claim_module_name(self, prefix, owner, counter):
1264 """Return prefix if not owned yet, otherwise, prefix+str(counter)."""
1265 while True:
1266 name = prefix
1267 if counter > 0:
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001268 name += '_' + str(counter)
1269 if self.try_claim_module_name(name, owner):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001270 return name
1271 counter += 1
1272
1273 def find_root_pkg(self):
1274 """Read name of [package] in ./Cargo.toml."""
1275 if not os.path.exists('./Cargo.toml'):
1276 return
1277 with open('./Cargo.toml', 'r') as inf:
1278 pkg_section = re.compile(r'^ *\[package\]')
1279 name = re.compile('^ *name *= * "([^"]*)"')
1280 in_pkg = False
1281 for line in inf:
1282 if in_pkg:
1283 if name.match(line):
1284 self.root_pkg = name.match(line).group(1)
1285 break
1286 else:
1287 in_pkg = pkg_section.match(line) is not None
1288
1289 def run_cargo(self):
1290 """Calls cargo -v and save its output to ./cargo.out."""
1291 if self.skip_cargo:
1292 return self
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001293 cargo_toml = './Cargo.toml'
1294 cargo_out = './cargo.out'
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001295 # Do not use Cargo.lock, because .bp rules are designed to
1296 # run with "latest" crates avaialable on Android.
1297 cargo_lock = './Cargo.lock'
1298 cargo_lock_saved = './cargo.lock.saved'
1299 had_cargo_lock = os.path.exists(cargo_lock)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001300 if not os.access(cargo_toml, os.R_OK):
1301 print('ERROR: Cannot find or read', cargo_toml)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001302 return self
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001303 if not self.dry_run:
1304 if os.path.exists(cargo_out):
1305 os.remove(cargo_out)
1306 if not self.args.use_cargo_lock and had_cargo_lock: # save it
1307 os.rename(cargo_lock, cargo_lock_saved)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001308 cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> ' + cargo_out + ' 2>&1'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001309 # set up search PATH for cargo to find the correct rustc
1310 saved_path = os.environ['PATH']
1311 os.environ['PATH'] = os.path.dirname(self.cargo_path) + ':' + saved_path
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001312 # Add [workspace] to Cargo.toml if it is not there.
1313 added_workspace = False
1314 if self.args.add_workspace:
1315 with open(cargo_toml, 'r') as in_file:
1316 cargo_toml_lines = in_file.readlines()
1317 found_workspace = '[workspace]\n' in cargo_toml_lines
1318 if found_workspace:
1319 print('### WARNING: found [workspace] in Cargo.toml')
1320 else:
1321 with open(cargo_toml, 'a') as out_file:
1322 out_file.write('[workspace]\n')
1323 added_workspace = True
1324 if self.args.verbose:
1325 print('### INFO: added [workspace] to Cargo.toml')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001326 for c in self.cargo:
1327 features = ''
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001328 if c != 'clean':
1329 if self.args.features is not None:
1330 features = ' --no-default-features'
1331 if self.args.features:
1332 features += ' --features ' + self.args.features
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001333 cmd_v_flag = ' -vv ' if self.args.vv else ' -v '
1334 cmd = self.cargo_path + cmd_v_flag
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001335 cmd += c + features + cmd_tail
1336 if self.args.rustflags and c != 'clean':
1337 cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
1338 if self.dry_run:
1339 print('Dry-run skip:', cmd)
1340 else:
1341 if self.args.verbose:
1342 print('Running:', cmd)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001343 with open(cargo_out, 'a') as out_file:
1344 out_file.write('### Running: ' + cmd + '\n')
Joel Galenson6bf54e32021-05-17 10:54:50 -07001345 ret = os.system(cmd)
1346 if ret != 0:
1347 print('*** There was an error while running cargo. ' +
1348 'See the cargo.out file for details.')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001349 if added_workspace: # restore original Cargo.toml
1350 with open(cargo_toml, 'w') as out_file:
1351 out_file.writelines(cargo_toml_lines)
1352 if self.args.verbose:
1353 print('### INFO: restored original Cargo.toml')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001354 os.environ['PATH'] = saved_path
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001355 if not self.dry_run:
1356 if not had_cargo_lock: # restore to no Cargo.lock state
1357 os.remove(cargo_lock)
1358 elif not self.args.use_cargo_lock: # restore saved Cargo.lock
1359 os.rename(cargo_lock_saved, cargo_lock)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001360 return self
1361
1362 def dump_dependencies(self):
1363 """Append dependencies and their features to Android.bp."""
1364 if not self.dependencies:
1365 return
1366 dependent_list = list()
1367 for c in self.dependencies:
1368 dependent_list.append(c.feature_list())
1369 sorted_dependencies = sorted(set(dependent_list))
1370 self.init_bp_file('Android.bp')
1371 with open('Android.bp', 'a') as outf:
1372 outf.write('\n// dependent_library ["feature_list"]\n')
1373 for s in sorted_dependencies:
1374 outf.write('// ' + s + '\n')
1375
1376 def dump_pkg_obj2cc(self):
1377 """Dump debug info of the pkg_obj2cc map."""
1378 if not self.args.debug:
1379 return
1380 self.init_bp_file('Android.bp')
1381 with open('Android.bp', 'a') as outf:
1382 sorted_pkgs = sorted(self.pkg_obj2cc.keys())
1383 for pkg in sorted_pkgs:
1384 if not self.pkg_obj2cc[pkg]:
1385 continue
1386 outf.write('\n// obj => src for %s\n' % pkg)
1387 obj2cc = self.pkg_obj2cc[pkg]
1388 for obj in sorted(obj2cc.keys()):
1389 outf.write('// ' + short_out_name(pkg, obj) + ' => ' +
1390 short_out_name(pkg, obj2cc[obj].src) + '\n')
1391
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001392 def apply_patch(self):
1393 """Apply local patch file if it is given."""
1394 if self.args.patch:
1395 if self.dry_run:
1396 print('Dry-run skip patch file:', self.args.patch)
1397 else:
1398 if not os.path.exists(self.args.patch):
1399 self.append_to_bp('ERROR cannot find patch file: ' + self.args.patch)
1400 return self
1401 if self.args.verbose:
1402 print('### INFO: applying local patch file:', self.args.patch)
Joel Galenson7e8247e2021-05-20 18:51:42 -07001403 subprocess.run(['patch', '-s', '--no-backup-if-mismatch', './Android.bp',
1404 self.args.patch], check=True)
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001405 return self
1406
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001407 def gen_bp(self):
1408 """Parse cargo.out and generate Android.bp files."""
1409 if self.dry_run:
1410 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp')
1411 elif os.path.exists(CARGO_OUT):
1412 self.find_root_pkg()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001413 if self.args.copy_out:
1414 self.copy_out_files()
1415 elif self.find_out_files() and self.has_used_out_dir():
1416 print('WARNING: ' + self.root_pkg + ' has cargo output files; ' +
1417 'please rerun with the --copy-out flag.')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001418 with open(CARGO_OUT, 'r') as cargo_out:
1419 self.parse(cargo_out, 'Android.bp')
1420 self.crates.sort(key=get_module_name)
1421 for obj in self.cc_objects:
1422 obj.dump()
1423 self.dump_pkg_obj2cc()
1424 for crate in self.crates:
1425 crate.dump()
1426 dumped_libs = set()
1427 for lib in self.ar_objects:
1428 if lib.pkg == self.root_pkg:
1429 lib_name = file_base_name(lib.lib)
1430 if lib_name not in dumped_libs:
1431 dumped_libs.add(lib_name)
1432 lib.dump()
Joel Galenson5664f2a2021-06-10 10:13:49 -07001433 if self.args.add_toplevel_block:
1434 with open(self.args.add_toplevel_block, 'r') as f:
1435 self.append_to_bp('\n' + f.read() + '\n')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001436 if self.args.dependencies and self.dependencies:
1437 self.dump_dependencies()
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001438 if self.errors:
Joel Galenson3f42f802021-04-07 12:42:17 -07001439 self.append_to_bp('\n' + ERRORS_LINE + '\n' + self.errors)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001440 return self
1441
1442 def add_ar_object(self, obj):
1443 self.ar_objects.append(obj)
1444
1445 def add_cc_object(self, obj):
1446 self.cc_objects.append(obj)
1447
1448 def add_crate(self, crate):
1449 """Merge crate with someone in crates, or append to it. Return crates."""
1450 if crate.skip_crate():
1451 if self.args.debug: # include debug info of all crates
1452 self.crates.append(crate)
1453 if self.args.dependencies: # include only dependent crates
1454 if (is_dependent_file_path(crate.main_src) and
1455 not is_build_crate_name(crate.crate_name)):
1456 self.dependencies.append(crate)
1457 else:
1458 for c in self.crates:
1459 if c.merge(crate, 'Android.bp'):
1460 return
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001461 # If not merged, decide module type and name now.
1462 crate.decide_module_type()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001463 self.crates.append(crate)
1464
1465 def find_warning_owners(self):
1466 """For each warning file, find its owner crate."""
1467 missing_owner = False
1468 for f in self.warning_files:
1469 cargo_dir = '' # find lowest crate, with longest path
1470 owner = None # owner crate of this warning
1471 for c in self.crates:
1472 if (f.startswith(c.cargo_dir + '/') and
1473 len(cargo_dir) < len(c.cargo_dir)):
1474 cargo_dir = c.cargo_dir
1475 owner = c
1476 if owner:
1477 owner.has_warning = True
1478 else:
1479 missing_owner = True
1480 if missing_owner and os.path.exists('Cargo.toml'):
1481 # owner is the root cargo, with empty cargo_dir
1482 for c in self.crates:
1483 if not c.cargo_dir:
1484 c.has_warning = True
1485
1486 def rustc_command(self, n, rustc_line, line, outf_name):
1487 """Process a rustc command line from cargo -vv output."""
1488 # cargo build -vv output can have multiple lines for a rustc command
1489 # due to '\n' in strings for environment variables.
1490 # strip removes leading spaces and '\n' at the end
1491 new_rustc = (rustc_line.strip() + line) if rustc_line else line
1492 # Use an heuristic to detect the completions of a multi-line command.
1493 # This might fail for some very rare case, but easy to fix manually.
1494 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0:
1495 return new_rustc
1496 if RUSTC_VV_CMD_ARGS.match(new_rustc):
1497 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1)
1498 self.add_crate(Crate(self, outf_name).parse(n, args))
1499 else:
1500 self.assert_empty_vv_line(new_rustc)
1501 return ''
1502
1503 def cc_ar_command(self, n, groups, outf_name):
1504 pkg = groups.group(1)
1505 line = groups.group(3)
1506 if groups.group(2) == 'cc':
1507 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line))
1508 else:
1509 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line))
1510
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001511 def append_to_bp(self, line):
1512 self.init_bp_file('Android.bp')
1513 with open('Android.bp', 'a') as outf:
1514 outf.write(line)
1515
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001516 def assert_empty_vv_line(self, line):
1517 if line: # report error if line is not empty
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001518 self.append_to_bp('ERROR -vv line: ' + line)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001519 return ''
1520
1521 def parse(self, inf, outf_name):
1522 """Parse rustc and warning messages in inf, return a list of Crates."""
1523 n = 0 # line number
1524 prev_warning = False # true if the previous line was warning: ...
1525 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
1526 for line in inf:
1527 n += 1
1528 if line.startswith('warning: '):
1529 prev_warning = True
1530 rustc_line = self.assert_empty_vv_line(rustc_line)
1531 continue
1532 new_rustc = ''
1533 if RUSTC_PAT.match(line):
1534 args_line = RUSTC_PAT.match(line).group(1)
1535 self.add_crate(Crate(self, outf_name).parse(n, args_line))
1536 self.assert_empty_vv_line(rustc_line)
1537 elif rustc_line or RUSTC_VV_PAT.match(line):
1538 new_rustc = self.rustc_command(n, rustc_line, line, outf_name)
1539 elif CC_AR_VV_PAT.match(line):
1540 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name)
1541 elif prev_warning and WARNING_FILE_PAT.match(line):
1542 self.assert_empty_vv_line(rustc_line)
1543 fpath = WARNING_FILE_PAT.match(line).group(1)
1544 if fpath[0] != '/': # ignore absolute path
1545 self.warning_files.add(fpath)
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001546 elif line.startswith('error: ') or line.startswith('error[E'):
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001547 if not self.args.ignore_cargo_errors:
1548 self.errors += line
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001549 prev_warning = False
1550 rustc_line = new_rustc
1551 self.find_warning_owners()
1552
1553
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001554def get_parser():
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001555 """Parse main arguments."""
1556 parser = argparse.ArgumentParser('cargo2android')
1557 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001558 '--add_workspace',
1559 action='store_true',
1560 default=False,
1561 help=('append [workspace] to Cargo.toml before calling cargo,' +
1562 ' to treat current directory as root of package source;' +
1563 ' otherwise the relative source file path in generated' +
1564 ' .bp file will be from the parent directory.'))
1565 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001566 '--cargo',
1567 action='append',
1568 metavar='args_string',
1569 help=('extra cargo build -v args in a string, ' +
1570 'each --cargo flag calls cargo build -v once'))
1571 parser.add_argument(
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001572 '--cargo_bin',
1573 type=str,
1574 help='use cargo in the cargo_bin directory instead of the prebuilt one')
1575 parser.add_argument(
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001576 '--copy-out',
1577 action='store_true',
1578 default=False,
1579 help=('only for root directory, ' +
1580 'copy build.rs output to ./out/* and add a genrule to copy ' +
1581 './out/* to genrule output; for crates with code pattern: ' +
1582 'include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))'))
1583 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001584 '--debug',
1585 action='store_true',
1586 default=False,
1587 help='dump debug info into Android.bp')
1588 parser.add_argument(
1589 '--dependencies',
1590 action='store_true',
1591 default=False,
1592 help='dump debug info of dependent crates')
1593 parser.add_argument(
1594 '--device',
1595 action='store_true',
1596 default=False,
1597 help='run cargo also for a default device target')
1598 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001599 '--features',
1600 type=str,
1601 help=('pass features to cargo build, ' +
1602 'empty string means no default features'))
1603 parser.add_argument(
1604 '--global_defaults',
1605 type=str,
1606 help='add a defaults name to every module')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001607 parser.add_argument(
1608 '--host-first-multilib',
1609 action='store_true',
1610 default=False,
1611 help=('add a compile_multilib:"first" property ' +
1612 'to Android.bp host modules.'))
1613 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001614 '--ignore-cargo-errors',
1615 action='store_true',
1616 default=False,
1617 help='do not append cargo/rustc error messages to Android.bp')
1618 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001619 '--no-host',
1620 action='store_true',
1621 default=False,
1622 help='do not run cargo for the host; only for the device target')
1623 parser.add_argument(
1624 '--no-subdir',
1625 action='store_true',
1626 default=False,
1627 help='do not output anything for sub-directories')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001628 parser.add_argument(
1629 '--onefile',
1630 action='store_true',
1631 default=False,
1632 help=('output all into one ./Android.bp, default will generate ' +
1633 'one Android.bp per Cargo.toml in subdirectories'))
1634 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001635 '--patch',
1636 type=str,
1637 help='apply the given patch file to generated ./Android.bp')
1638 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001639 '--run',
1640 action='store_true',
1641 default=False,
1642 help='run it, default is dry-run')
1643 parser.add_argument('--rustflags', type=str, help='passing flags to rustc')
1644 parser.add_argument(
1645 '--skipcargo',
1646 action='store_true',
1647 default=False,
1648 help='skip cargo command, parse cargo.out, and generate Android.bp')
1649 parser.add_argument(
1650 '--tests',
1651 action='store_true',
1652 default=False,
1653 help='run cargo build --tests after normal build')
1654 parser.add_argument(
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001655 '--use-cargo-lock',
1656 action='store_true',
1657 default=False,
1658 help=('run cargo build with existing Cargo.lock ' +
1659 '(used when some latest dependent crates failed)'))
1660 parser.add_argument(
Joel Galensond9c4de62021-04-23 10:26:40 -07001661 '--min-sdk-version',
1662 type=str,
1663 help='Minimum SDK version')
1664 parser.add_argument(
1665 '--apex-available',
1666 nargs='*',
1667 help='Mark the main library as apex_available with the given apexes.')
1668 parser.add_argument(
Ivan Lozano91920862021-07-19 10:49:08 -04001669 '--vendor-available',
1670 action='store_true',
1671 default=False,
1672 help='Mark the main library as vendor_available.')
1673 parser.add_argument(
1674 '--vendor-ramdisk-available',
1675 action='store_true',
1676 default=False,
1677 help='Mark the main library as vendor_ramdisk_available.')
1678 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001679 '--force-rlib',
1680 action='store_true',
1681 default=False,
1682 help='Make the main library an rlib.')
1683 parser.add_argument(
Joel Galenson12467e52021-07-12 14:33:28 -07001684 '--whole-static-libs',
1685 nargs='*',
1686 default=[],
1687 help='Make the given libraries (without lib prefixes) whole_static_libs.')
1688 parser.add_argument(
Ivan Lozano26aa1c32021-08-16 11:20:32 -04001689 '--no-pkg-vers',
1690 action='store_true',
1691 default=False,
1692 help='Do not attempt to determine the package version automatically.')
1693 parser.add_argument(
Joel Galensone4f53882021-07-19 11:14:55 -07001694 '--test-data',
1695 nargs='*',
1696 default=[],
1697 help=('Add the given file to the given test\'s data property. ' +
1698 'Usage: test-path=data-path'))
1699 parser.add_argument(
Joel Galenson97e414a2021-05-27 09:42:32 -07001700 '--dependency-blocklist',
1701 nargs='*',
1702 default=[],
Joel Galenson12467e52021-07-12 14:33:28 -07001703 help='Do not emit the given dependencies (without lib prefixes).')
Joel Galenson97e414a2021-05-27 09:42:32 -07001704 parser.add_argument(
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001705 '--lib-blocklist',
1706 nargs='*',
1707 default=[],
Joel Galenson12467e52021-07-12 14:33:28 -07001708 help='Do not emit the given C libraries as dependencies (without lib prefixes).')
Joel Galensoncb5f2f02021-06-08 14:47:55 -07001709 parser.add_argument(
Joel Galensonf6b3c912021-06-03 16:00:54 -07001710 '--test-blocklist',
1711 nargs='*',
1712 default=[],
1713 help=('Do not emit the given tests. ' +
1714 'Pass the path to the test file to exclude.'))
1715 parser.add_argument(
Joel Galenson3d6d1e72021-06-07 15:00:24 -07001716 '--cfg-blocklist',
1717 nargs='*',
1718 default=[],
1719 help='Do not emit the given cfg.')
1720 parser.add_argument(
Joel Galenson5664f2a2021-06-10 10:13:49 -07001721 '--add-toplevel-block',
1722 type=str,
1723 help='Add the contents of the given file to the top level of the Android.bp.')
1724 parser.add_argument(
1725 '--add-module-block',
1726 type=str,
1727 help='Add the contents of the given file to the main module.')
1728 parser.add_argument(
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001729 '--no-test-mapping',
1730 action='store_true',
1731 default=False,
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001732 help='Deprecated. Has no effect.')
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001733 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001734 '--verbose',
1735 action='store_true',
1736 default=False,
1737 help='echo executed commands')
1738 parser.add_argument(
1739 '--vv',
1740 action='store_true',
1741 default=False,
1742 help='run cargo with -vv instead of default -v')
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001743 parser.add_argument(
1744 '--dump-config-and-exit',
1745 type=str,
1746 help=('Dump command-line arguments (minus this flag) to a config file and exit. ' +
1747 'This is intended to help migrate from command line options to config files.'))
1748 parser.add_argument(
1749 '--config',
1750 type=str,
1751 help=('Load command-line options from the given config file. ' +
1752 'Options in this file will override those passed on the command line.'))
1753 return parser
1754
1755
1756def parse_args(parser):
1757 """Parses command-line options."""
1758 args = parser.parse_args()
1759 # Use the values specified in a config file if one was found.
1760 if args.config:
1761 with open(args.config, 'r') as f:
1762 config = json.load(f)
1763 args_dict = vars(args)
1764 for arg in config:
1765 args_dict[arg.replace('-', '_')] = config[arg]
1766 return args
1767
1768
1769def dump_config(parser, args):
1770 """Writes the non-default command-line options to the specified file."""
1771 args_dict = vars(args)
1772 # Filter out the arguments that have their default value.
Joel Galenson367360c2021-04-29 14:31:43 -07001773 # Also filter certain "temporary" arguments.
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001774 non_default_args = {}
1775 for arg in args_dict:
Joel Galenson367360c2021-04-29 14:31:43 -07001776 if args_dict[arg] != parser.get_default(
1777 arg) and arg != 'dump_config_and_exit' and arg != 'no_test_mapping':
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001778 non_default_args[arg.replace('_', '-')] = args_dict[arg]
1779 # Write to the specified file.
1780 with open(args.dump_config_and_exit, 'w') as f:
1781 json.dump(non_default_args, f, indent=2, sort_keys=True)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001782
1783
1784def main():
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001785 parser = get_parser()
1786 args = parse_args(parser)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001787 if not args.run: # default is dry-run
1788 print(DRY_RUN_NOTE)
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001789 if args.dump_config_and_exit:
1790 dump_config(parser, args)
1791 else:
ThiƩbaud Weksteen198e93f2021-07-02 14:49:19 +02001792 Runner(args).run_cargo().gen_bp().apply_patch()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001793
1794
1795if __name__ == '__main__':
1796 main()