blob: 7c43aad40349684d8442e0c652ee7cf27fca23cc [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
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +010042 Note that when there are tests for this module or for its reverse
43 dependencies, these tests will be added to the TEST_MAPPING file.
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -070044
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -070045If there are rustc warning messages, this script will add
46a warning comment to the owner crate module in Android.bp.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080047"""
48
49from __future__ import print_function
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +010050from update_crate_tests import TestMapping
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080051
52import argparse
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -070053import glob
Joel Galenson0fbdafe2021-04-21 16:33:33 -070054import json
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080055import os
56import os.path
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -070057import platform
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080058import re
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -070059import shutil
Andrew Walbran80e90be2020-06-09 14:33:18 +010060import sys
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080061
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -070062# Some Rust packages include extra unwanted crates.
63# This set contains all such excluded crate names.
64EXCLUDED_CRATES = set(['protobuf_bin_gen_rust_do_not_use'])
65
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080066RENAME_MAP = {
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -070067 # This map includes all changes to the default rust module names
68 # to resolve name conflicts, avoid confusion, or work as plugin.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080069 'libbacktrace': 'libbacktrace_rust',
Andrew Walbrane51f1042020-08-11 16:42:48 +010070 'libbase': 'libbase_rust',
Victor Hsieh21bea792020-12-04 10:59:16 -080071 'libfuse': 'libfuse_rust',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080072 'libgcc': 'libgcc_rust',
73 'liblog': 'liblog_rust',
Chih-Hung Hsieh07119862020-07-24 15:34:06 -070074 'libminijail': 'libminijail_rust',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080075 'libsync': 'libsync_rust',
76 'libx86_64': 'libx86_64_rust',
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -070077 'protoc_gen_rust': 'protoc-gen-rust',
78}
79
80RENAME_STEM_MAP = {
81 # This map includes all changes to the default rust module stem names,
82 # which is used for output files when different from the module name.
83 'protoc_gen_rust': 'protoc-gen-rust',
84}
85
86RENAME_DEFAULTS_MAP = {
87 # This map includes all changes to the default prefix of rust_default
88 # module names, to avoid conflict with existing Android modules.
89 'libc': 'rust_libc',
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080090}
91
92# Header added to all generated Android.bp files.
Joel Galenson56446742021-02-18 08:27:48 -080093ANDROID_BP_HEADER = (
94 '// This file is generated by cargo2android.py {args}.\n' +
95 '// Do not modify this file as changes will be overridden on upgrade.\n')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -080096
97CARGO_OUT = 'cargo.out' # Name of file to keep cargo build -v output.
98
Joel Galenson3f42f802021-04-07 12:42:17 -070099# This should be kept in sync with tools/external_updater/crates_updater.py.
100ERRORS_LINE = 'Errors in ' + CARGO_OUT + ':'
101
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800102TARGET_TMP = 'target.tmp' # Name of temporary output directory.
103
104# Message to be displayed when this script is called without the --run flag.
105DRY_RUN_NOTE = (
106 'Dry-run: This script uses ./' + TARGET_TMP + ' for output directory,\n' +
107 'runs cargo clean, runs cargo build -v, saves output to ./cargo.out,\n' +
108 'and writes to Android.bp in the current and subdirectories.\n\n' +
109 'To do do all of the above, use the --run flag.\n' +
110 'See --help for other flags, and more usage notes in this script.\n')
111
112# Cargo -v output of a call to rustc.
113RUSTC_PAT = re.compile('^ +Running `rustc (.*)`$')
114
115# Cargo -vv output of a call to rustc could be split into multiple lines.
116# Assume that the first line will contain some CARGO_* env definition.
117RUSTC_VV_PAT = re.compile('^ +Running `.*CARGO_.*=.*$')
118# The combined -vv output rustc command line pattern.
119RUSTC_VV_CMD_ARGS = re.compile('^ *Running `.*CARGO_.*=.* rustc (.*)`$')
120
121# Cargo -vv output of a "cc" or "ar" command; all in one line.
122CC_AR_VV_PAT = re.compile(r'^\[([^ ]*)[^\]]*\] running:? "(cc|ar)" (.*)$')
123# Some package, such as ring-0.13.5, has pattern '... running "cc"'.
124
125# Rustc output of file location path pattern for a warning message.
126WARNING_FILE_PAT = re.compile('^ *--> ([^:]*):[0-9]+')
127
128# Rust package name with suffix -d1.d2.d3.
129VERSION_SUFFIX_PAT = re.compile(r'^(.*)-[0-9]+\.[0-9]+\.[0-9]+$')
130
131
132def altered_name(name):
133 return RENAME_MAP[name] if (name in RENAME_MAP) else name
134
135
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700136def altered_stem(name):
137 return RENAME_STEM_MAP[name] if (name in RENAME_STEM_MAP) else name
138
139
140def altered_defaults(name):
141 return RENAME_DEFAULTS_MAP[name] if (name in RENAME_DEFAULTS_MAP) else name
142
143
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800144def is_build_crate_name(name):
145 # We added special prefix to build script crate names.
146 return name.startswith('build_script_')
147
148
149def is_dependent_file_path(path):
150 # Absolute or dependent '.../' paths are not main files of this crate.
151 return path.startswith('/') or path.startswith('.../')
152
153
154def get_module_name(crate): # to sort crates in a list
155 return crate.module_name
156
157
158def pkg2crate_name(s):
159 return s.replace('-', '_').replace('.', '_')
160
161
162def file_base_name(path):
163 return os.path.splitext(os.path.basename(path))[0]
164
165
166def test_base_name(path):
167 return pkg2crate_name(file_base_name(path))
168
169
170def unquote(s): # remove quotes around str
171 if s and len(s) > 1 and s[0] == '"' and s[-1] == '"':
172 return s[1:-1]
173 return s
174
175
176def remove_version_suffix(s): # remove -d1.d2.d3 suffix
177 if VERSION_SUFFIX_PAT.match(s):
178 return VERSION_SUFFIX_PAT.match(s).group(1)
179 return s
180
181
182def short_out_name(pkg, s): # replace /.../pkg-*/out/* with .../out/*
183 return re.sub('^/.*/' + pkg + '-[0-9a-f]*/out/', '.../out/', s)
184
185
186def escape_quotes(s): # replace '"' with '\\"'
187 return s.replace('"', '\\"')
188
189
190class Crate(object):
191 """Information of a Rust crate to collect/emit for an Android.bp module."""
192
193 def __init__(self, runner, outf_name):
194 # Remembered global runner and its members.
195 self.runner = runner
196 self.debug = runner.args.debug
197 self.cargo_dir = '' # directory of my Cargo.toml
198 self.outf_name = outf_name # path to Android.bp
199 self.outf = None # open file handle of outf_name during dump*
200 # Variants/results that could be merged from multiple rustc lines.
201 self.host_supported = False
202 self.device_supported = False
203 self.has_warning = False
204 # Android module properties derived from rustc parameters.
205 self.module_name = '' # unique in Android build system
206 self.module_type = '' # rust_{binary,library,test}[_host] etc.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700207 self.defaults = '' # rust_defaults used by rust_test* modules
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700208 self.default_srcs = False # use 'srcs' defined in self.defaults
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800209 self.root_pkg = '' # parent package name of a sub/test packge, from -L
210 self.srcs = list() # main_src or merged multiple source files
211 self.stem = '' # real base name of output file
212 # Kept parsed status
213 self.errors = '' # all errors found during parsing
214 self.line_num = 1 # runner told input source line number
215 self.line = '' # original rustc command line parameters
216 # Parameters collected from rustc command line.
217 self.crate_name = '' # follows --crate-name
218 self.main_src = '' # follows crate_name parameter, shortened
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700219 self.crate_types = list() # follows --crate-type
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800220 self.cfgs = list() # follows --cfg, without feature= prefix
221 self.features = list() # follows --cfg, name in 'feature="..."'
222 self.codegens = list() # follows -C, some ignored
223 self.externs = list() # follows --extern
224 self.core_externs = list() # first part of self.externs elements
225 self.static_libs = list() # e.g. -l static=host_cpuid
226 self.shared_libs = list() # e.g. -l dylib=wayland-client, -l z
227 self.cap_lints = '' # follows --cap-lints
228 self.emit_list = '' # e.g., --emit=dep-info,metadata,link
229 self.edition = '2015' # rustc default, e.g., --edition=2018
230 self.target = '' # follows --target
231
232 def write(self, s):
233 # convenient way to output one line at a time with EOL.
234 self.outf.write(s + '\n')
235
236 def same_flags(self, other):
237 # host_supported, device_supported, has_warning are not compared but merged
238 # target is not compared, to merge different target/host modules
239 # externs is not compared; only core_externs is compared
240 return (not self.errors and not other.errors and
241 self.edition == other.edition and
242 self.cap_lints == other.cap_lints and
243 self.emit_list == other.emit_list and
244 self.core_externs == other.core_externs and
245 self.codegens == other.codegens and
246 self.features == other.features and
247 self.static_libs == other.static_libs and
248 self.shared_libs == other.shared_libs and self.cfgs == other.cfgs)
249
250 def merge_host_device(self, other):
251 """Returns true if attributes are the same except host/device support."""
252 return (self.crate_name == other.crate_name and
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700253 self.crate_types == other.crate_types and
254 self.main_src == other.main_src and
255 # before merge, each test module has an unique module name and stem
256 (self.stem == other.stem or self.crate_types == ['test']) and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800257 self.root_pkg == other.root_pkg and not self.skip_crate() and
258 self.same_flags(other))
259
260 def merge_test(self, other):
261 """Returns true if self and other are tests of same root_pkg."""
262 # Before merger, each test has its own crate_name.
263 # A merged test uses its source file base name as output file name,
264 # so a test is mergeable only if its base name equals to its crate name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700265 return (self.crate_types == other.crate_types and
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700266 self.crate_types == ['test'] and self.root_pkg == other.root_pkg and
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800267 not self.skip_crate() and
268 other.crate_name == test_base_name(other.main_src) and
269 (len(self.srcs) > 1 or
270 (self.crate_name == test_base_name(self.main_src)) and
271 self.host_supported == other.host_supported and
272 self.device_supported == other.device_supported) and
273 self.same_flags(other))
274
275 def merge(self, other, outf_name):
276 """Try to merge crate into self."""
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700277 # Cargo build --tests could recompile a library for tests.
278 # We need to merge such duplicated calls to rustc, with
279 # the algorithm in merge_host_device.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800280 should_merge_host_device = self.merge_host_device(other)
281 should_merge_test = False
282 if not should_merge_host_device:
283 should_merge_test = self.merge_test(other)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800284 if should_merge_host_device or should_merge_test:
285 self.runner.init_bp_file(outf_name)
286 with open(outf_name, 'a') as outf: # to write debug info
287 self.outf = outf
288 other.outf = outf
289 self.do_merge(other, should_merge_test)
290 return True
291 return False
292
293 def do_merge(self, other, should_merge_test):
294 """Merge attributes of other to self."""
295 if self.debug:
296 self.write('\n// Before merge definition (1):')
297 self.dump_debug_info()
298 self.write('\n// Before merge definition (2):')
299 other.dump_debug_info()
300 # Merge properties of other to self.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800301 self.has_warning = self.has_warning or other.has_warning
302 if not self.target: # okay to keep only the first target triple
303 self.target = other.target
304 # decide_module_type sets up default self.stem,
305 # which can be changed if self is a merged test module.
306 self.decide_module_type()
307 if should_merge_test:
308 self.srcs.append(other.main_src)
309 # use a short unique name as the merged module name.
310 prefix = self.root_pkg + '_tests'
311 self.module_name = self.runner.claim_module_name(prefix, self, 0)
312 self.stem = self.module_name
313 # This normalized root_pkg name although might be the same
314 # as other module's crate_name, it is not actually used for
315 # output file name. A merged test module always have multiple
316 # source files and each source file base name is used as
317 # its output file name.
318 self.crate_name = pkg2crate_name(self.root_pkg)
319 if self.debug:
320 self.write('\n// After merge definition (1):')
321 self.dump_debug_info()
322
323 def find_cargo_dir(self):
324 """Deepest directory with Cargo.toml and contains the main_src."""
325 if not is_dependent_file_path(self.main_src):
326 dir_name = os.path.dirname(self.main_src)
327 while dir_name:
328 if os.path.exists(dir_name + '/Cargo.toml'):
329 self.cargo_dir = dir_name
330 return
331 dir_name = os.path.dirname(dir_name)
332
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700333 def add_codegens_flag(self, flag):
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700334 """Ignore options not used in Android."""
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700335 # 'prefer-dynamic' does not work with common flag -C lto
Chih-Hung Hsieh63459ed2020-08-26 11:51:15 -0700336 # 'embed-bitcode' is ignored; we might control LTO with other .bp flag
Chih-Hung Hsieh6c13b722020-09-11 21:24:03 -0700337 # 'codegen-units' is set in Android global config or by default
338 if not (flag.startswith('codegen-units=') or
339 flag.startswith('debuginfo=') or
Chih-Hung Hsieh63459ed2020-08-26 11:51:15 -0700340 flag.startswith('embed-bitcode=') or
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700341 flag.startswith('extra-filename=') or
342 flag.startswith('incremental=') or
343 flag.startswith('metadata=') or
344 flag == 'prefer-dynamic'):
345 self.codegens.append(flag)
346
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800347 def parse(self, line_num, line):
348 """Find important rustc arguments to convert to Android.bp properties."""
349 self.line_num = line_num
350 self.line = line
351 args = line.split() # Loop through every argument of rustc.
352 i = 0
353 while i < len(args):
354 arg = args[i]
355 if arg == '--crate-name':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700356 i += 1
357 self.crate_name = args[i]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800358 elif arg == '--crate-type':
359 i += 1
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700360 # cargo calls rustc with multiple --crate-type flags.
361 # rustc can accept:
362 # --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
363 self.crate_types.append(args[i])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800364 elif arg == '--test':
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700365 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800366 elif arg == '--target':
367 i += 1
368 self.target = args[i]
369 elif arg == '--cfg':
370 i += 1
371 if args[i].startswith('\'feature='):
372 self.features.append(unquote(args[i].replace('\'feature=', '')[:-1]))
373 else:
374 self.cfgs.append(args[i])
375 elif arg == '--extern':
376 i += 1
377 extern_names = re.sub('=/[^ ]*/deps/', ' = ', args[i])
378 self.externs.append(extern_names)
379 self.core_externs.append(re.sub(' = .*', '', extern_names))
380 elif arg == '-C': # codegen options
381 i += 1
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700382 self.add_codegens_flag(args[i])
383 elif arg.startswith('-C'):
384 # cargo has been passing "-C <xyz>" flag to rustc,
385 # but newer cargo could pass '-Cembed-bitcode=no' to rustc.
386 self.add_codegens_flag(arg[2:])
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800387 elif arg == '--cap-lints':
388 i += 1
389 self.cap_lints = args[i]
390 elif arg == '-L':
391 i += 1
392 if args[i].startswith('dependency=') and args[i].endswith('/deps'):
393 if '/' + TARGET_TMP + '/' in args[i]:
394 self.root_pkg = re.sub(
395 '^.*/', '', re.sub('/' + TARGET_TMP + '/.*/deps$', '', args[i]))
396 else:
397 self.root_pkg = re.sub('^.*/', '',
398 re.sub('/[^/]+/[^/]+/deps$', '', args[i]))
399 self.root_pkg = remove_version_suffix(self.root_pkg)
400 elif arg == '-l':
401 i += 1
402 if args[i].startswith('static='):
403 self.static_libs.append(re.sub('static=', '', args[i]))
404 elif args[i].startswith('dylib='):
405 self.shared_libs.append(re.sub('dylib=', '', args[i]))
406 else:
407 self.shared_libs.append(args[i])
408 elif arg == '--out-dir' or arg == '--color': # ignored
409 i += 1
410 elif arg.startswith('--error-format=') or arg.startswith('--json='):
411 _ = arg # ignored
412 elif arg.startswith('--emit='):
413 self.emit_list = arg.replace('--emit=', '')
414 elif arg.startswith('--edition='):
415 self.edition = arg.replace('--edition=', '')
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700416 elif not arg.startswith('-'):
417 # shorten imported crate main source paths like $HOME/.cargo/
418 # registry/src/github.com-1ecc6299db9ec823/memchr-2.3.3/src/lib.rs
419 self.main_src = re.sub(r'^/[^ ]*/registry/src/', '.../', args[i])
420 self.main_src = re.sub(r'^\.\.\./github.com-[0-9a-f]*/', '.../',
421 self.main_src)
422 self.find_cargo_dir()
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700423 if self.cargo_dir: # for a subdirectory
424 if self.runner.args.no_subdir: # all .bp content to /dev/null
425 self.outf_name = '/dev/null'
426 elif not self.runner.args.onefile:
427 # Write to Android.bp in the subdirectory with Cargo.toml.
428 self.outf_name = self.cargo_dir + '/Android.bp'
429 self.main_src = self.main_src[len(self.cargo_dir) + 1:]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800430 else:
431 self.errors += 'ERROR: unknown ' + arg + '\n'
432 i += 1
433 if not self.crate_name:
434 self.errors += 'ERROR: missing --crate-name\n'
435 if not self.main_src:
436 self.errors += 'ERROR: missing main source file\n'
437 else:
438 self.srcs.append(self.main_src)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700439 if not self.crate_types:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800440 # Treat "--cfg test" as "--test"
441 if 'test' in self.cfgs:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700442 self.crate_types.append('test')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800443 else:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700444 self.errors += 'ERROR: missing --crate-type or --test\n'
445 elif len(self.crate_types) > 1:
446 if 'test' in self.crate_types:
447 self.errors += 'ERROR: cannot handle both --crate-type and --test\n'
448 if 'lib' in self.crate_types and 'rlib' in self.crate_types:
449 self.errors += 'ERROR: cannot generate both lib and rlib crate types\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800450 if not self.root_pkg:
451 self.root_pkg = self.crate_name
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700452 self.device_supported = self.runner.args.device
453 self.host_supported = not self.runner.args.no_host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800454 self.cfgs = sorted(set(self.cfgs))
455 self.features = sorted(set(self.features))
456 self.codegens = sorted(set(self.codegens))
457 self.externs = sorted(set(self.externs))
458 self.core_externs = sorted(set(self.core_externs))
459 self.static_libs = sorted(set(self.static_libs))
460 self.shared_libs = sorted(set(self.shared_libs))
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700461 self.crate_types = sorted(set(self.crate_types))
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800462 self.decide_module_type()
463 self.module_name = altered_name(self.stem)
464 return self
465
466 def dump_line(self):
467 self.write('\n// Line ' + str(self.line_num) + ' ' + self.line)
468
469 def feature_list(self):
470 """Return a string of main_src + "feature_list"."""
471 pkg = self.main_src
472 if pkg.startswith('.../'): # keep only the main package name
473 pkg = re.sub('/.*', '', pkg[4:])
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700474 elif pkg.startswith('/'): # use relative path for a local package
475 pkg = os.path.relpath(pkg)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800476 if not self.features:
477 return pkg
478 return pkg + ' "' + ','.join(self.features) + '"'
479
480 def dump_skip_crate(self, kind):
481 if self.debug:
482 self.write('\n// IGNORED: ' + kind + ' ' + self.main_src)
483 return self
484
485 def skip_crate(self):
486 """Return crate_name or a message if this crate should be skipped."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700487 if (is_build_crate_name(self.crate_name) or
488 self.crate_name in EXCLUDED_CRATES):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800489 return self.crate_name
490 if is_dependent_file_path(self.main_src):
491 return 'dependent crate'
492 return ''
493
494 def dump(self):
495 """Dump all error/debug/module code to the output .bp file."""
496 self.runner.init_bp_file(self.outf_name)
497 with open(self.outf_name, 'a') as outf:
498 self.outf = outf
499 if self.errors:
500 self.dump_line()
501 self.write(self.errors)
502 elif self.skip_crate():
503 self.dump_skip_crate(self.skip_crate())
504 else:
505 if self.debug:
506 self.dump_debug_info()
507 self.dump_android_module()
508
509 def dump_debug_info(self):
510 """Dump parsed data, when cargo2android is called with --debug."""
511
512 def dump(name, value):
513 self.write('//%12s = %s' % (name, value))
514
515 def opt_dump(name, value):
516 if value:
517 dump(name, value)
518
519 def dump_list(fmt, values):
520 for v in values:
521 self.write(fmt % v)
522
523 self.dump_line()
524 dump('module_name', self.module_name)
525 dump('crate_name', self.crate_name)
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700526 dump('crate_types', self.crate_types)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800527 dump('main_src', self.main_src)
528 dump('has_warning', self.has_warning)
529 dump('for_host', self.host_supported)
530 dump('for_device', self.device_supported)
531 dump('module_type', self.module_type)
532 opt_dump('target', self.target)
533 opt_dump('edition', self.edition)
534 opt_dump('emit_list', self.emit_list)
535 opt_dump('cap_lints', self.cap_lints)
536 dump_list('// cfg = %s', self.cfgs)
537 dump_list('// cfg = \'feature "%s"\'', self.features)
538 # TODO(chh): escape quotes in self.features, but not in other dump_list
539 dump_list('// codegen = %s', self.codegens)
540 dump_list('// externs = %s', self.externs)
541 dump_list('// -l static = %s', self.static_libs)
542 dump_list('// -l (dylib) = %s', self.shared_libs)
543
544 def dump_android_module(self):
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700545 """Dump one or more Android module definition, depending on crate_types."""
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700546 if len(self.crate_types) == 1:
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700547 self.dump_single_type_android_module()
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700548 return
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700549 if 'test' in self.crate_types:
550 self.write('\nERROR: multiple crate types cannot include test type')
551 return
552 # Dump one Android module per crate_type.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700553 for crate_type in self.crate_types:
554 self.decide_one_module_type(crate_type)
555 self.dump_one_android_module(crate_type)
556
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700557 def build_default_name(self):
558 """Return a short and readable name for the rust_defaults module."""
559 # Choices: (1) root_pkg + '_defaults',
560 # (2) root_pkg + '_defaults_' + crate_name
561 # (3) root_pkg + '_defaults_' + main_src_basename_path
562 # (4) root_pkg + '_defaults_' + a_positive_sequence_number
563 name1 = altered_defaults(self.root_pkg) + '_defaults'
564 if self.runner.try_claim_module_name(name1, self):
565 return name1
566 name2 = name1 + '_' + self.crate_name
567 if self.runner.try_claim_module_name(name2, self):
568 return name2
569 name3 = name1 + '_' + self.main_src_basename_path()
570 if self.runner.try_claim_module_name(name3, self):
571 return name3
572 return self.runner.claim_module_name(name1, self, 0)
573
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700574 def dump_srcs_list(self):
575 """Dump the srcs list, for defaults or regular modules."""
576 if len(self.srcs) > 1:
577 srcs = sorted(set(self.srcs)) # make a copy and dedup
578 else:
579 srcs = [self.main_src]
580 copy_out = self.runner.copy_out_module_name()
581 if copy_out:
582 srcs.append(':' + copy_out)
583 self.dump_android_property_list('srcs', '"%s"', srcs)
584
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700585 def dump_defaults_module(self):
586 """Dump a rust_defaults module to be shared by other modules."""
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700587 name = self.build_default_name()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700588 self.defaults = name
589 self.write('\nrust_defaults {')
590 self.write(' name: "' + name + '",')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700591 if self.runner.args.global_defaults:
592 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700593 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700594 if len(self.srcs) == 1: # only one source file; share it in defaults
595 self.default_srcs = True
596 if self.has_warning and not self.cap_lints:
597 self.write(' // has rustc warnings')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700598 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700599 if 'test' in self.crate_types:
600 self.write(' test_suites: ["general-tests"],')
601 self.write(' auto_gen_config: true,')
602 self.dump_edition_flags_libs()
603 self.write('}')
604
605 def dump_single_type_android_module(self):
606 """Dump one simple Android module, which has only one crate_type."""
607 crate_type = self.crate_types[0]
608 if crate_type != 'test':
609 # do not change self.stem or self.module_name
610 self.dump_one_android_module(crate_type)
611 return
612 # Dump one test module per source file, and separate host and device tests.
613 # crate_type == 'test'
614 if (self.host_supported and self.device_supported) or len(self.srcs) > 1:
615 self.srcs = sorted(set(self.srcs))
616 self.dump_defaults_module()
617 saved_srcs = self.srcs
618 for src in saved_srcs:
619 self.srcs = [src]
620 saved_device_supported = self.device_supported
621 saved_host_supported = self.host_supported
622 saved_main_src = self.main_src
623 self.main_src = src
624 if saved_host_supported:
625 self.device_supported = False
626 self.host_supported = True
627 self.module_name = self.test_module_name()
628 self.decide_one_module_type(crate_type)
629 self.dump_one_android_module(crate_type)
630 if saved_device_supported:
631 self.device_supported = True
632 self.host_supported = False
633 self.module_name = self.test_module_name()
634 self.decide_one_module_type(crate_type)
635 self.dump_one_android_module(crate_type)
636 self.host_supported = saved_host_supported
637 self.device_supported = saved_device_supported
638 self.main_src = saved_main_src
639 self.srcs = saved_srcs
640
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700641 def dump_one_android_module(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800642 """Dump one Android module definition."""
643 if not self.module_type:
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700644 self.write('\nERROR: unknown crate_type ' + crate_type)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800645 return
646 self.write('\n' + self.module_type + ' {')
647 self.dump_android_core_properties()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700648 if not self.defaults:
649 self.dump_edition_flags_libs()
650 if self.runner.args.host_first_multilib and self.host_supported and crate_type != 'test':
651 self.write(' compile_multilib: "first",')
Joel Galensond9c4de62021-04-23 10:26:40 -0700652 if self.runner.args.apex_available and crate_type == 'lib':
653 self.write(' apex_available: [')
654 for apex in self.runner.args.apex_available:
655 self.write(' "%s",' % apex)
656 self.write(' ],')
657 if self.runner.args.min_sdk_version and crate_type == 'lib':
658 self.write(' min_sdk_version: "%s",' % self.runner.args.min_sdk_version)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700659 self.write('}')
660
661 def dump_android_flags(self):
662 """Dump Android module flags property."""
Thiébaud Weksteena5a728b2021-04-08 14:23:49 +0200663 if not self.codegens and not self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700664 return
665 self.write(' flags: [')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800666 if self.cap_lints:
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700667 self.write(' "--cap-lints ' + self.cap_lints + '",')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700668 codegens_fmt = '"-C %s"'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -0700669 self.dump_android_property_list_items(codegens_fmt, self.codegens)
670 self.write(' ],')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700671
672 def dump_edition_flags_libs(self):
673 if self.edition:
674 self.write(' edition: "' + self.edition + '",')
675 self.dump_android_property_list('features', '"%s"', self.features)
Thiébaud Weksteena5a728b2021-04-08 14:23:49 +0200676 self.dump_android_property_list('cfgs', '"%s"', self.cfgs)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700677 self.dump_android_flags()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800678 if self.externs:
679 self.dump_android_externs()
680 self.dump_android_property_list('static_libs', '"lib%s"', self.static_libs)
681 self.dump_android_property_list('shared_libs', '"lib%s"', self.shared_libs)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800682
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700683 def main_src_basename_path(self):
684 return re.sub('/', '_', re.sub('.rs$', '', self.main_src))
685
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800686 def test_module_name(self):
687 """Return a unique name for a test module."""
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700688 # root_pkg+(_host|_device) + '_test_'+source_file_name
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -0700689 suffix = self.main_src_basename_path()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700690 host_device = '_host'
691 if self.device_supported:
692 host_device = '_device'
693 return self.root_pkg + host_device + '_test_' + suffix
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800694
695 def decide_module_type(self):
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700696 # Use the first crate type for the default/first module.
697 crate_type = self.crate_types[0] if self.crate_types else ''
698 self.decide_one_module_type(crate_type)
699
700 def decide_one_module_type(self, crate_type):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800701 """Decide which Android module type to use."""
702 host = '' if self.device_supported else '_host'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700703 if crate_type == 'bin': # rust_binary[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800704 self.module_type = 'rust_binary' + host
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700705 # In rare cases like protobuf-codegen, the output binary name must
706 # be renamed to use as a plugin for protoc.
707 self.stem = altered_stem(self.crate_name)
708 self.module_name = altered_name(self.crate_name)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700709 elif crate_type == 'lib': # rust_library[_host]
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700710 # TODO(chh): should this be rust_library[_host]?
711 # Assuming that Cargo.toml do not use both 'lib' and 'rlib',
712 # because we map them both to rlib.
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700713 self.module_type = 'rust_library' + host
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800714 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700715 self.module_name = altered_name(self.stem)
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700716 elif crate_type == 'rlib': # rust_library[_host]
717 self.module_type = 'rust_library' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700718 self.stem = 'lib' + self.crate_name
719 self.module_name = altered_name(self.stem)
720 elif crate_type == 'dylib': # rust_library[_host]_dylib
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800721 self.module_type = 'rust_library' + host + '_dylib'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700722 self.stem = 'lib' + self.crate_name
723 self.module_name = altered_name(self.stem) + '_dylib'
724 elif crate_type == 'cdylib': # rust_library[_host]_shared
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500725 self.module_type = 'rust_ffi' + host + '_shared'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700726 self.stem = 'lib' + self.crate_name
727 self.module_name = altered_name(self.stem) + '_shared'
728 elif crate_type == 'staticlib': # rust_library[_host]_static
Ivan Lozano0c057ad2020-12-15 10:41:26 -0500729 self.module_type = 'rust_ffi' + host + '_static'
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700730 self.stem = 'lib' + self.crate_name
731 self.module_name = altered_name(self.stem) + '_static'
732 elif crate_type == 'test': # rust_test[_host]
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800733 self.module_type = 'rust_test' + host
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700734 # Before do_merge, stem name is based on the --crate-name parameter.
735 # and test module name is based on stem.
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800736 self.stem = self.test_module_name()
737 # self.stem will be changed after merging with other tests.
738 # self.stem is NOT used for final test binary name.
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700739 # rust_test uses each source file base name as part of output file name.
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700740 # In do_merge, this function is called again, with a module_name.
741 # We make sure that the module name is unique in each package.
742 if self.module_name:
743 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add
744 # different suffixes and distinguish multiple tests of the same
745 # crate name. We ignore -C and use claim_module_name to get
746 # unique sequential suffix.
747 self.module_name = self.runner.claim_module_name(
748 self.module_name, self, 0)
749 # Now the module name is unique, stem should also match and unique.
750 self.stem = self.module_name
751 elif crate_type == 'proc-macro': # rust_proc_macro
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800752 self.module_type = 'rust_proc_macro'
753 self.stem = 'lib' + self.crate_name
Chih-Hung Hsieh8a1a2302020-04-03 14:33:33 -0700754 self.module_name = altered_name(self.stem)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800755 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]?
756 self.module_type = ''
757 self.stem = ''
758
759 def dump_android_property_list_items(self, fmt, values):
760 for v in values:
761 # fmt has quotes, so we need escape_quotes(v)
762 self.write(' ' + (fmt % escape_quotes(v)) + ',')
763
764 def dump_android_property_list(self, name, fmt, values):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700765 if not values:
766 return
767 if len(values) > 1:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800768 self.write(' ' + name + ': [')
769 self.dump_android_property_list_items(fmt, values)
770 self.write(' ],')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700771 else:
772 self.write(' ' + name + ': [' +
773 (fmt % escape_quotes(values[0])) + '],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800774
775 def dump_android_core_properties(self):
776 """Dump the module header, name, stem, etc."""
777 self.write(' name: "' + self.module_name + '",')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700778 # see properties shared by dump_defaults_module
779 if self.defaults:
780 self.write(' defaults: ["' + self.defaults + '"],')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -0700781 elif self.runner.args.global_defaults:
782 self.write(' defaults: ["' + self.runner.args.global_defaults + '"],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800783 if self.stem != self.module_name:
784 self.write(' stem: "' + self.stem + '",')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -0700785 if self.has_warning and not self.cap_lints and not self.default_srcs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700786 self.write(' // has rustc warnings')
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -0700787 if self.host_supported and self.device_supported and self.module_type != 'rust_proc_macro':
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800788 self.write(' host_supported: true,')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700789 if not self.defaults:
790 self.write(' crate_name: "' + self.crate_name + '",')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -0700791 if not self.default_srcs:
792 self.dump_srcs_list()
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700793 if 'test' in self.crate_types and not self.defaults:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800794 # self.root_pkg can have multiple test modules, with different *_tests[n]
795 # names, but their executables can all be installed under the same _tests
796 # directory. When built from Cargo.toml, all tests should have different
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -0700797 # file or crate names. So we used (root_pkg + '_tests') name as the
798 # relative_install_path.
799 # However, some package like 'slab' can have non-mergeable tests that
800 # must be separated by different module names. So, here we no longer
801 # emit relative_install_path.
802 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800803 self.write(' test_suites: ["general-tests"],')
804 self.write(' auto_gen_config: true,')
Joel Galensone261a152021-01-12 11:31:53 -0800805 if 'test' in self.crate_types and self.host_supported:
806 self.write(' test_options: {')
807 self.write(' unit_test: true,')
808 self.write(' },')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800809
810 def dump_android_externs(self):
811 """Dump the dependent rlibs and dylibs property."""
812 so_libs = list()
813 rust_libs = ''
814 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$')
815 for lib in self.externs:
816 # normal value of lib: "libc = liblibc-*.rlib"
817 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib"
818 # we should use "libgetrandom", not "lib" + "getrandom_package"
819 groups = deps_libname.match(lib)
820 if groups is not None:
821 lib_name = groups.group(1)
822 else:
823 lib_name = re.sub(' .*$', '', lib)
824 if lib.endswith('.rlib') or lib.endswith('.rmeta'):
825 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta.
826 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n'
827 elif lib.endswith('.so'):
828 so_libs.append(lib_name)
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -0700829 elif lib != 'proc_macro': # --extern proc_macro is special and ignored
830 rust_libs += ' // ERROR: unknown type of lib ' + lib + '\n'
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800831 if rust_libs:
Chih-Hung Hsieh35ca4bc2020-07-10 16:49:51 -0700832 self.write(' rustlibs: [\n' + rust_libs + ' ],')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -0800833 # Are all dependent .so files proc_macros?
834 # TODO(chh): Separate proc_macros and dylib.
835 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs)
836
837
838class ARObject(object):
839 """Information of an "ar" link command."""
840
841 def __init__(self, runner, outf_name):
842 # Remembered global runner and its members.
843 self.runner = runner
844 self.pkg = ''
845 self.outf_name = outf_name # path to Android.bp
846 # "ar" arguments
847 self.line_num = 1
848 self.line = ''
849 self.flags = '' # e.g. "crs"
850 self.lib = '' # e.g. "/.../out/lib*.a"
851 self.objs = list() # e.g. "/.../out/.../*.o"
852
853 def parse(self, pkg, line_num, args_line):
854 """Collect ar obj/lib file names."""
855 self.pkg = pkg
856 self.line_num = line_num
857 self.line = args_line
858 args = args_line.split()
859 num_args = len(args)
860 if num_args < 3:
861 print('ERROR: "ar" command has too few arguments', args_line)
862 else:
863 self.flags = unquote(args[0])
864 self.lib = unquote(args[1])
865 self.objs = sorted(set(map(unquote, args[2:])))
866 return self
867
868 def write(self, s):
869 self.outf.write(s + '\n')
870
871 def dump_debug_info(self):
872 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line)
873 self.write('// ar_object for %12s' % self.pkg)
874 self.write('// flags = %s' % self.flags)
875 self.write('// lib = %s' % short_out_name(self.pkg, self.lib))
876 for o in self.objs:
877 self.write('// obj = %s' % short_out_name(self.pkg, o))
878
879 def dump_android_lib(self):
880 """Write cc_library_static into Android.bp."""
881 self.write('\ncc_library_static {')
882 self.write(' name: "' + file_base_name(self.lib) + '",')
883 self.write(' host_supported: true,')
884 if self.flags != 'crs':
885 self.write(' // ar flags = %s' % self.flags)
886 if self.pkg not in self.runner.pkg_obj2cc:
887 self.write(' ERROR: cannot find source files.\n}')
888 return
889 self.write(' srcs: [')
890 obj2cc = self.runner.pkg_obj2cc[self.pkg]
891 # Note: wflags are ignored.
892 dflags = list()
893 fflags = list()
894 for obj in self.objs:
895 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",')
896 # TODO(chh): union of dflags and flags of all obj
897 # Now, just a temporary hack that uses the last obj's flags
898 dflags = obj2cc[obj].dflags
899 fflags = obj2cc[obj].fflags
900 self.write(' ],')
901 self.write(' cflags: [')
902 self.write(' "-O3",') # TODO(chh): is this default correct?
903 self.write(' "-Wno-error",')
904 for x in fflags:
905 self.write(' "-f' + x + '",')
906 for x in dflags:
907 self.write(' "-D' + x + '",')
908 self.write(' ],')
909 self.write('}')
910
911 def dump(self):
912 """Dump error/debug/module info to the output .bp file."""
913 self.runner.init_bp_file(self.outf_name)
914 with open(self.outf_name, 'a') as outf:
915 self.outf = outf
916 if self.runner.args.debug:
917 self.dump_debug_info()
918 self.dump_android_lib()
919
920
921class CCObject(object):
922 """Information of a "cc" compilation command."""
923
924 def __init__(self, runner, outf_name):
925 # Remembered global runner and its members.
926 self.runner = runner
927 self.pkg = ''
928 self.outf_name = outf_name # path to Android.bp
929 # "cc" arguments
930 self.line_num = 1
931 self.line = ''
932 self.src = ''
933 self.obj = ''
934 self.dflags = list() # -D flags
935 self.fflags = list() # -f flags
936 self.iflags = list() # -I flags
937 self.wflags = list() # -W flags
938 self.other_args = list()
939
940 def parse(self, pkg, line_num, args_line):
941 """Collect cc compilation flags and src/out file names."""
942 self.pkg = pkg
943 self.line_num = line_num
944 self.line = args_line
945 args = args_line.split()
946 i = 0
947 while i < len(args):
948 arg = args[i]
949 if arg == '"-c"':
950 i += 1
951 if args[i].startswith('"-o'):
952 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c"
953 self.obj = unquote(args[i])[2:]
954 i += 1
955 self.src = unquote(args[i])
956 else:
957 self.src = unquote(args[i])
958 elif arg == '"-o"':
959 i += 1
960 self.obj = unquote(args[i])
961 elif arg == '"-I"':
962 i += 1
963 self.iflags.append(unquote(args[i]))
964 elif arg.startswith('"-D'):
965 self.dflags.append(unquote(args[i])[2:])
966 elif arg.startswith('"-f'):
967 self.fflags.append(unquote(args[i])[2:])
968 elif arg.startswith('"-W'):
969 self.wflags.append(unquote(args[i])[2:])
970 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or
971 arg == '"-g3"'):
972 # ignore -O -m64 -g
973 self.other_args.append(unquote(args[i]))
974 i += 1
975 self.dflags = sorted(set(self.dflags))
976 self.fflags = sorted(set(self.fflags))
977 # self.wflags is not sorted because some are order sensitive
978 # and we ignore them anyway.
979 if self.pkg not in self.runner.pkg_obj2cc:
980 self.runner.pkg_obj2cc[self.pkg] = {}
981 self.runner.pkg_obj2cc[self.pkg][self.obj] = self
982 return self
983
984 def write(self, s):
985 self.outf.write(s + '\n')
986
987 def dump_debug_flags(self, name, flags):
988 self.write('// ' + name + ':')
989 for f in flags:
990 self.write('// %s' % f)
991
992 def dump(self):
993 """Dump only error/debug info to the output .bp file."""
994 if not self.runner.args.debug:
995 return
996 self.runner.init_bp_file(self.outf_name)
997 with open(self.outf_name, 'a') as outf:
998 self.outf = outf
999 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line)
1000 self.write('// cc_object for %12s' % self.pkg)
1001 self.write('// src = %s' % short_out_name(self.pkg, self.src))
1002 self.write('// obj = %s' % short_out_name(self.pkg, self.obj))
1003 self.dump_debug_flags('-I flags', self.iflags)
1004 self.dump_debug_flags('-D flags', self.dflags)
1005 self.dump_debug_flags('-f flags', self.fflags)
1006 self.dump_debug_flags('-W flags', self.wflags)
1007 if self.other_args:
1008 self.dump_debug_flags('other args', self.other_args)
1009
1010
1011class Runner(object):
1012 """Main class to parse cargo -v output and print Android module definitions."""
1013
1014 def __init__(self, args):
1015 self.bp_files = set() # Remember all output Android.bp files.
1016 self.root_pkg = '' # name of package in ./Cargo.toml
1017 # Saved flags, modes, and data.
1018 self.args = args
1019 self.dry_run = not args.run
1020 self.skip_cargo = args.skipcargo
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001021 self.cargo_path = './cargo' # path to cargo, will be set later
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001022 self.checked_out_files = False # to check only once
1023 self.build_out_files = [] # output files generated by build.rs
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001024 # All cc/ar objects, crates, dependencies, and warning files
1025 self.cc_objects = list()
1026 self.pkg_obj2cc = {}
1027 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i]
1028 self.ar_objects = list()
1029 self.crates = list()
1030 self.dependencies = list() # dependent and build script crates
1031 self.warning_files = set()
1032 # Keep a unique mapping from (module name) to crate
1033 self.name_owners = {}
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001034 # Save and dump all errors from cargo to Android.bp.
1035 self.errors = ''
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001036 self.setup_cargo_path()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001037 # Default action is cargo clean, followed by build or user given actions.
1038 if args.cargo:
1039 self.cargo = ['clean'] + args.cargo
1040 else:
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001041 default_target = '--target x86_64-unknown-linux-gnu'
Chih-Hung Hsiehe1b7bb62020-09-30 13:09:30 -07001042 # Use the same target for both host and default device builds.
1043 # Same target is used as default in host x86_64 Android compilation.
1044 # Note: b/169872957, prebuilt cargo failed to build vsock
1045 # on x86_64-unknown-linux-musl systems.
1046 self.cargo = ['clean', 'build ' + default_target]
1047 if args.tests:
1048 self.cargo.append('build --tests ' + default_target)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001049
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001050 def setup_cargo_path(self):
1051 """Find cargo in the --cargo_bin or prebuilt rust bin directory."""
1052 if self.args.cargo_bin:
1053 self.cargo_path = os.path.join(self.args.cargo_bin, 'cargo')
1054 if not os.path.isfile(self.cargo_path):
1055 sys.exit('ERROR: cannot find cargo in ' + self.args.cargo_bin)
1056 print('WARNING: using cargo in ' + self.args.cargo_bin)
1057 return
1058 # We have only tested this on Linux.
1059 if platform.system() != 'Linux':
1060 sys.exit('ERROR: this script has only been tested on Linux with cargo.')
1061 # Assuming that this script is in development/scripts.
1062 my_dir = os.path.dirname(os.path.abspath(__file__))
1063 linux_dir = os.path.join(my_dir, '..', '..',
1064 'prebuilts', 'rust', 'linux-x86')
1065 if not os.path.isdir(linux_dir):
1066 sys.exit('ERROR: cannot find directory ' + linux_dir)
1067 rust_version = self.find_rust_version(my_dir, linux_dir)
1068 cargo_bin = os.path.join(linux_dir, rust_version, 'bin')
1069 self.cargo_path = os.path.join(cargo_bin, 'cargo')
1070 if not os.path.isfile(self.cargo_path):
1071 sys.exit('ERROR: cannot find cargo in ' + cargo_bin
1072 + '; please try --cargo_bin= flag.')
1073 return
1074
1075 def find_rust_version(self, my_dir, linux_dir):
1076 """Use my script directory, find prebuilt rust version."""
1077 # First look up build/soong/rust/config/global.go.
1078 path2global = os.path.join(my_dir, '..', '..',
1079 'build', 'soong', 'rust', 'config', 'global.go')
1080 if os.path.isfile(path2global):
1081 # try to find: RustDefaultVersion = "1.44.0"
1082 version_pat = re.compile(
1083 r'\s*RustDefaultVersion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+)".*$')
1084 with open(path2global, 'r') as inf:
1085 for line in inf:
1086 result = version_pat.match(line)
1087 if result:
1088 return result.group(1)
1089 print('WARNING: cannot find RustDefaultVersion in ' + path2global)
1090 # Otherwise, find the newest (largest) version number in linux_dir.
1091 rust_version = (0, 0, 0) # the prebuilt version to use
1092 version_pat = re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)$')
1093 for dir_name in os.listdir(linux_dir):
1094 result = version_pat.match(dir_name)
1095 if not result:
1096 continue
1097 version = (result.group(1), result.group(2), result.group(3))
1098 if version > rust_version:
1099 rust_version = version
1100 return '.'.join(rust_version)
1101
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001102 def find_out_files(self):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001103 # list1 has build.rs output for normal crates
1104 list1 = glob.glob(TARGET_TMP + '/*/*/build/' + self.root_pkg + '-*/out/*')
1105 # list2 has build.rs output for proc-macro crates
1106 list2 = glob.glob(TARGET_TMP + '/*/build/' + self.root_pkg + '-*/out/*')
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001107 return list1 + list2
1108
1109 def copy_out_files(self):
1110 """Copy build.rs output files to ./out and set up build_out_files."""
1111 if self.checked_out_files:
1112 return
1113 self.checked_out_files = True
1114 cargo_out_files = self.find_out_files()
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001115 out_files = set()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001116 if cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001117 os.makedirs('out', exist_ok=True)
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001118 for path in cargo_out_files:
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001119 file_name = path.split('/')[-1]
1120 out_files.add(file_name)
1121 shutil.copy(path, 'out/' + file_name)
1122 self.build_out_files = sorted(out_files)
1123
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001124 def has_used_out_dir(self):
1125 """Returns true if env!("OUT_DIR") is found."""
1126 return 0 == os.system('grep -rl --exclude build.rs --include \\*.rs' +
1127 ' \'env!("OUT_DIR")\' * > /dev/null')
1128
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001129 def copy_out_module_name(self):
1130 if self.args.copy_out and self.build_out_files:
1131 return 'copy_' + self.root_pkg + '_build_out'
1132 else:
1133 return ''
1134
Haibo Huang0f72c952021-03-19 11:34:15 -07001135 def read_license(self, name):
1136 if not os.path.isfile(name):
1137 return ''
1138 license = ''
1139 with open(name, 'r') as intf:
1140 line = intf.readline()
1141 # Firstly skip ANDROID_BP_HEADER
1142 while line.startswith('//'):
1143 line = intf.readline()
Joel Galensond9d13b82021-04-05 11:27:55 -07001144 # Read all lines until we see a rust_* or genrule rule.
1145 while line != '' and not (line.startswith('rust_') or line.startswith('genrule {')):
Haibo Huang0f72c952021-03-19 11:34:15 -07001146 license += line
1147 line = intf.readline()
1148 return license.strip()
1149
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001150 def dump_copy_out_module(self, outf):
1151 """Output the genrule module to copy out/* to $(genDir)."""
1152 copy_out = self.copy_out_module_name()
1153 if not copy_out:
1154 return
1155 outf.write('\ngenrule {\n')
1156 outf.write(' name: "' + copy_out + '",\n')
1157 outf.write(' srcs: ["out/*"],\n')
1158 outf.write(' cmd: "cp $(in) $(genDir)",\n')
1159 if len(self.build_out_files) > 1:
1160 outf.write(' out: [\n')
1161 for f in self.build_out_files:
1162 outf.write(' "' + f + '",\n')
1163 outf.write(' ],\n')
1164 else:
1165 outf.write(' out: ["' + self.build_out_files[0] + '"],\n')
1166 outf.write('}\n')
1167
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001168 def init_bp_file(self, name):
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001169 # name could be Android.bp or sub_dir_path/Android.bp
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001170 if name not in self.bp_files:
1171 self.bp_files.add(name)
Haibo Huang0f72c952021-03-19 11:34:15 -07001172 license_section = self.read_license(name)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001173 with open(name, 'w') as outf:
Joel Galenson367360c2021-04-29 14:31:43 -07001174 print_args = filter(lambda x: x != "--no-test-mapping", sys.argv[1:])
1175 outf.write(ANDROID_BP_HEADER.format(args=' '.join(print_args)))
Haibo Huang0f72c952021-03-19 11:34:15 -07001176 outf.write('\n')
1177 outf.write(license_section)
1178 outf.write('\n')
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001179 # at most one copy_out module per .bp file
1180 self.dump_copy_out_module(outf)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001181
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -07001182 def dump_test_mapping_files(self):
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001183 """Dump all TEST_MAPPING files."""
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -07001184 if self.dry_run:
1185 print('Dry-run skip dump of TEST_MAPPING')
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001186 elif self.args.no_test_mapping:
1187 print('Skipping generation of TEST_MAPPING')
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -07001188 else:
Jeff Vander Stoep1b24dc32021-02-03 18:52:42 +01001189 test_mapping = TestMapping(None)
Jeff Vander Stoepcec8bac2021-01-15 14:15:37 +01001190 for bp_file_name in self.bp_files:
1191 test_mapping.create_test_mapping(os.path.dirname(bp_file_name))
Chih-Hung Hsiehf7eff152020-07-16 15:36:22 -07001192 return self
1193
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001194 def try_claim_module_name(self, name, owner):
1195 """Reserve and return True if it has not been reserved yet."""
1196 if name not in self.name_owners or owner == self.name_owners[name]:
1197 self.name_owners[name] = owner
1198 return True
1199 return False
1200
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001201 def claim_module_name(self, prefix, owner, counter):
1202 """Return prefix if not owned yet, otherwise, prefix+str(counter)."""
1203 while True:
1204 name = prefix
1205 if counter > 0:
Chih-Hung Hsiehe02dce12020-07-14 16:05:21 -07001206 name += '_' + str(counter)
1207 if self.try_claim_module_name(name, owner):
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001208 return name
1209 counter += 1
1210
1211 def find_root_pkg(self):
1212 """Read name of [package] in ./Cargo.toml."""
1213 if not os.path.exists('./Cargo.toml'):
1214 return
1215 with open('./Cargo.toml', 'r') as inf:
1216 pkg_section = re.compile(r'^ *\[package\]')
1217 name = re.compile('^ *name *= * "([^"]*)"')
1218 in_pkg = False
1219 for line in inf:
1220 if in_pkg:
1221 if name.match(line):
1222 self.root_pkg = name.match(line).group(1)
1223 break
1224 else:
1225 in_pkg = pkg_section.match(line) is not None
1226
1227 def run_cargo(self):
1228 """Calls cargo -v and save its output to ./cargo.out."""
1229 if self.skip_cargo:
1230 return self
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001231 cargo_toml = './Cargo.toml'
1232 cargo_out = './cargo.out'
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001233 # Do not use Cargo.lock, because .bp rules are designed to
1234 # run with "latest" crates avaialable on Android.
1235 cargo_lock = './Cargo.lock'
1236 cargo_lock_saved = './cargo.lock.saved'
1237 had_cargo_lock = os.path.exists(cargo_lock)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001238 if not os.access(cargo_toml, os.R_OK):
1239 print('ERROR: Cannot find or read', cargo_toml)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001240 return self
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001241 if not self.dry_run:
1242 if os.path.exists(cargo_out):
1243 os.remove(cargo_out)
1244 if not self.args.use_cargo_lock and had_cargo_lock: # save it
1245 os.rename(cargo_lock, cargo_lock_saved)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001246 cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> ' + cargo_out + ' 2>&1'
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001247 # set up search PATH for cargo to find the correct rustc
1248 saved_path = os.environ['PATH']
1249 os.environ['PATH'] = os.path.dirname(self.cargo_path) + ':' + saved_path
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001250 # Add [workspace] to Cargo.toml if it is not there.
1251 added_workspace = False
1252 if self.args.add_workspace:
1253 with open(cargo_toml, 'r') as in_file:
1254 cargo_toml_lines = in_file.readlines()
1255 found_workspace = '[workspace]\n' in cargo_toml_lines
1256 if found_workspace:
1257 print('### WARNING: found [workspace] in Cargo.toml')
1258 else:
1259 with open(cargo_toml, 'a') as out_file:
1260 out_file.write('[workspace]\n')
1261 added_workspace = True
1262 if self.args.verbose:
1263 print('### INFO: added [workspace] to Cargo.toml')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001264 for c in self.cargo:
1265 features = ''
Chih-Hung Hsieh6c8d52f2020-03-30 18:28:52 -07001266 if c != 'clean':
1267 if self.args.features is not None:
1268 features = ' --no-default-features'
1269 if self.args.features:
1270 features += ' --features ' + self.args.features
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001271 cmd_v_flag = ' -vv ' if self.args.vv else ' -v '
1272 cmd = self.cargo_path + cmd_v_flag
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001273 cmd += c + features + cmd_tail
1274 if self.args.rustflags and c != 'clean':
1275 cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
1276 if self.dry_run:
1277 print('Dry-run skip:', cmd)
1278 else:
1279 if self.args.verbose:
1280 print('Running:', cmd)
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001281 with open(cargo_out, 'a') as out_file:
1282 out_file.write('### Running: ' + cmd + '\n')
Joel Galenson6bf54e32021-05-17 10:54:50 -07001283 ret = os.system(cmd)
1284 if ret != 0:
1285 print('*** There was an error while running cargo. ' +
1286 'See the cargo.out file for details.')
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001287 if added_workspace: # restore original Cargo.toml
1288 with open(cargo_toml, 'w') as out_file:
1289 out_file.writelines(cargo_toml_lines)
1290 if self.args.verbose:
1291 print('### INFO: restored original Cargo.toml')
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001292 os.environ['PATH'] = saved_path
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001293 if not self.dry_run:
1294 if not had_cargo_lock: # restore to no Cargo.lock state
1295 os.remove(cargo_lock)
1296 elif not self.args.use_cargo_lock: # restore saved Cargo.lock
1297 os.rename(cargo_lock_saved, cargo_lock)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001298 return self
1299
1300 def dump_dependencies(self):
1301 """Append dependencies and their features to Android.bp."""
1302 if not self.dependencies:
1303 return
1304 dependent_list = list()
1305 for c in self.dependencies:
1306 dependent_list.append(c.feature_list())
1307 sorted_dependencies = sorted(set(dependent_list))
1308 self.init_bp_file('Android.bp')
1309 with open('Android.bp', 'a') as outf:
1310 outf.write('\n// dependent_library ["feature_list"]\n')
1311 for s in sorted_dependencies:
1312 outf.write('// ' + s + '\n')
1313
1314 def dump_pkg_obj2cc(self):
1315 """Dump debug info of the pkg_obj2cc map."""
1316 if not self.args.debug:
1317 return
1318 self.init_bp_file('Android.bp')
1319 with open('Android.bp', 'a') as outf:
1320 sorted_pkgs = sorted(self.pkg_obj2cc.keys())
1321 for pkg in sorted_pkgs:
1322 if not self.pkg_obj2cc[pkg]:
1323 continue
1324 outf.write('\n// obj => src for %s\n' % pkg)
1325 obj2cc = self.pkg_obj2cc[pkg]
1326 for obj in sorted(obj2cc.keys()):
1327 outf.write('// ' + short_out_name(pkg, obj) + ' => ' +
1328 short_out_name(pkg, obj2cc[obj].src) + '\n')
1329
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001330 def apply_patch(self):
1331 """Apply local patch file if it is given."""
1332 if self.args.patch:
1333 if self.dry_run:
1334 print('Dry-run skip patch file:', self.args.patch)
1335 else:
1336 if not os.path.exists(self.args.patch):
1337 self.append_to_bp('ERROR cannot find patch file: ' + self.args.patch)
1338 return self
1339 if self.args.verbose:
1340 print('### INFO: applying local patch file:', self.args.patch)
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001341 os.system('patch -s --no-backup-if-mismatch ./Android.bp ' +
1342 self.args.patch)
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001343 return self
1344
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001345 def gen_bp(self):
1346 """Parse cargo.out and generate Android.bp files."""
1347 if self.dry_run:
1348 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp')
1349 elif os.path.exists(CARGO_OUT):
1350 self.find_root_pkg()
Chih-Hung Hsieh60140752020-11-03 15:05:58 -08001351 if self.args.copy_out:
1352 self.copy_out_files()
1353 elif self.find_out_files() and self.has_used_out_dir():
1354 print('WARNING: ' + self.root_pkg + ' has cargo output files; ' +
1355 'please rerun with the --copy-out flag.')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001356 with open(CARGO_OUT, 'r') as cargo_out:
1357 self.parse(cargo_out, 'Android.bp')
1358 self.crates.sort(key=get_module_name)
1359 for obj in self.cc_objects:
1360 obj.dump()
1361 self.dump_pkg_obj2cc()
1362 for crate in self.crates:
1363 crate.dump()
1364 dumped_libs = set()
1365 for lib in self.ar_objects:
1366 if lib.pkg == self.root_pkg:
1367 lib_name = file_base_name(lib.lib)
1368 if lib_name not in dumped_libs:
1369 dumped_libs.add(lib_name)
1370 lib.dump()
1371 if self.args.dependencies and self.dependencies:
1372 self.dump_dependencies()
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001373 if self.errors:
Joel Galenson3f42f802021-04-07 12:42:17 -07001374 self.append_to_bp('\n' + ERRORS_LINE + '\n' + self.errors)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001375 return self
1376
1377 def add_ar_object(self, obj):
1378 self.ar_objects.append(obj)
1379
1380 def add_cc_object(self, obj):
1381 self.cc_objects.append(obj)
1382
1383 def add_crate(self, crate):
1384 """Merge crate with someone in crates, or append to it. Return crates."""
1385 if crate.skip_crate():
1386 if self.args.debug: # include debug info of all crates
1387 self.crates.append(crate)
1388 if self.args.dependencies: # include only dependent crates
1389 if (is_dependent_file_path(crate.main_src) and
1390 not is_build_crate_name(crate.crate_name)):
1391 self.dependencies.append(crate)
1392 else:
1393 for c in self.crates:
1394 if c.merge(crate, 'Android.bp'):
1395 return
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001396 # If not merged, decide module type and name now.
1397 crate.decide_module_type()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001398 self.crates.append(crate)
1399
1400 def find_warning_owners(self):
1401 """For each warning file, find its owner crate."""
1402 missing_owner = False
1403 for f in self.warning_files:
1404 cargo_dir = '' # find lowest crate, with longest path
1405 owner = None # owner crate of this warning
1406 for c in self.crates:
1407 if (f.startswith(c.cargo_dir + '/') and
1408 len(cargo_dir) < len(c.cargo_dir)):
1409 cargo_dir = c.cargo_dir
1410 owner = c
1411 if owner:
1412 owner.has_warning = True
1413 else:
1414 missing_owner = True
1415 if missing_owner and os.path.exists('Cargo.toml'):
1416 # owner is the root cargo, with empty cargo_dir
1417 for c in self.crates:
1418 if not c.cargo_dir:
1419 c.has_warning = True
1420
1421 def rustc_command(self, n, rustc_line, line, outf_name):
1422 """Process a rustc command line from cargo -vv output."""
1423 # cargo build -vv output can have multiple lines for a rustc command
1424 # due to '\n' in strings for environment variables.
1425 # strip removes leading spaces and '\n' at the end
1426 new_rustc = (rustc_line.strip() + line) if rustc_line else line
1427 # Use an heuristic to detect the completions of a multi-line command.
1428 # This might fail for some very rare case, but easy to fix manually.
1429 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0:
1430 return new_rustc
1431 if RUSTC_VV_CMD_ARGS.match(new_rustc):
1432 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1)
1433 self.add_crate(Crate(self, outf_name).parse(n, args))
1434 else:
1435 self.assert_empty_vv_line(new_rustc)
1436 return ''
1437
1438 def cc_ar_command(self, n, groups, outf_name):
1439 pkg = groups.group(1)
1440 line = groups.group(3)
1441 if groups.group(2) == 'cc':
1442 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line))
1443 else:
1444 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line))
1445
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001446 def append_to_bp(self, line):
1447 self.init_bp_file('Android.bp')
1448 with open('Android.bp', 'a') as outf:
1449 outf.write(line)
1450
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001451 def assert_empty_vv_line(self, line):
1452 if line: # report error if line is not empty
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001453 self.append_to_bp('ERROR -vv line: ' + line)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001454 return ''
1455
1456 def parse(self, inf, outf_name):
1457 """Parse rustc and warning messages in inf, return a list of Crates."""
1458 n = 0 # line number
1459 prev_warning = False # true if the previous line was warning: ...
1460 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
1461 for line in inf:
1462 n += 1
1463 if line.startswith('warning: '):
1464 prev_warning = True
1465 rustc_line = self.assert_empty_vv_line(rustc_line)
1466 continue
1467 new_rustc = ''
1468 if RUSTC_PAT.match(line):
1469 args_line = RUSTC_PAT.match(line).group(1)
1470 self.add_crate(Crate(self, outf_name).parse(n, args_line))
1471 self.assert_empty_vv_line(rustc_line)
1472 elif rustc_line or RUSTC_VV_PAT.match(line):
1473 new_rustc = self.rustc_command(n, rustc_line, line, outf_name)
1474 elif CC_AR_VV_PAT.match(line):
1475 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name)
1476 elif prev_warning and WARNING_FILE_PAT.match(line):
1477 self.assert_empty_vv_line(rustc_line)
1478 fpath = WARNING_FILE_PAT.match(line).group(1)
1479 if fpath[0] != '/': # ignore absolute path
1480 self.warning_files.add(fpath)
Chih-Hung Hsieh185052a2020-05-07 14:48:57 -07001481 elif line.startswith('error: ') or line.startswith('error[E'):
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001482 if not self.args.ignore_cargo_errors:
1483 self.errors += line
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001484 prev_warning = False
1485 rustc_line = new_rustc
1486 self.find_warning_owners()
1487
1488
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001489def get_parser():
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001490 """Parse main arguments."""
1491 parser = argparse.ArgumentParser('cargo2android')
1492 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001493 '--add_workspace',
1494 action='store_true',
1495 default=False,
1496 help=('append [workspace] to Cargo.toml before calling cargo,' +
1497 ' to treat current directory as root of package source;' +
1498 ' otherwise the relative source file path in generated' +
1499 ' .bp file will be from the parent directory.'))
1500 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001501 '--cargo',
1502 action='append',
1503 metavar='args_string',
1504 help=('extra cargo build -v args in a string, ' +
1505 'each --cargo flag calls cargo build -v once'))
1506 parser.add_argument(
Chih-Hung Hsieh776f6a12020-07-22 14:16:54 -07001507 '--cargo_bin',
1508 type=str,
1509 help='use cargo in the cargo_bin directory instead of the prebuilt one')
1510 parser.add_argument(
Chih-Hung Hsiehe2342ba2020-10-25 03:51:24 -07001511 '--copy-out',
1512 action='store_true',
1513 default=False,
1514 help=('only for root directory, ' +
1515 'copy build.rs output to ./out/* and add a genrule to copy ' +
1516 './out/* to genrule output; for crates with code pattern: ' +
1517 'include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))'))
1518 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001519 '--debug',
1520 action='store_true',
1521 default=False,
1522 help='dump debug info into Android.bp')
1523 parser.add_argument(
1524 '--dependencies',
1525 action='store_true',
1526 default=False,
1527 help='dump debug info of dependent crates')
1528 parser.add_argument(
1529 '--device',
1530 action='store_true',
1531 default=False,
1532 help='run cargo also for a default device target')
1533 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001534 '--features',
1535 type=str,
1536 help=('pass features to cargo build, ' +
1537 'empty string means no default features'))
1538 parser.add_argument(
1539 '--global_defaults',
1540 type=str,
1541 help='add a defaults name to every module')
Chih-Hung Hsieh3725e082020-07-12 00:51:20 -07001542 parser.add_argument(
1543 '--host-first-multilib',
1544 action='store_true',
1545 default=False,
1546 help=('add a compile_multilib:"first" property ' +
1547 'to Android.bp host modules.'))
1548 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001549 '--ignore-cargo-errors',
1550 action='store_true',
1551 default=False,
1552 help='do not append cargo/rustc error messages to Android.bp')
1553 parser.add_argument(
Chih-Hung Hsieh07119862020-07-24 15:34:06 -07001554 '--no-host',
1555 action='store_true',
1556 default=False,
1557 help='do not run cargo for the host; only for the device target')
1558 parser.add_argument(
1559 '--no-subdir',
1560 action='store_true',
1561 default=False,
1562 help='do not output anything for sub-directories')
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001563 parser.add_argument(
1564 '--onefile',
1565 action='store_true',
1566 default=False,
1567 help=('output all into one ./Android.bp, default will generate ' +
1568 'one Android.bp per Cargo.toml in subdirectories'))
1569 parser.add_argument(
Chih-Hung Hsiehec8846b2020-10-30 17:03:47 -07001570 '--patch',
1571 type=str,
1572 help='apply the given patch file to generated ./Android.bp')
1573 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001574 '--run',
1575 action='store_true',
1576 default=False,
1577 help='run it, default is dry-run')
1578 parser.add_argument('--rustflags', type=str, help='passing flags to rustc')
1579 parser.add_argument(
1580 '--skipcargo',
1581 action='store_true',
1582 default=False,
1583 help='skip cargo command, parse cargo.out, and generate Android.bp')
1584 parser.add_argument(
1585 '--tests',
1586 action='store_true',
1587 default=False,
1588 help='run cargo build --tests after normal build')
1589 parser.add_argument(
Chih-Hung Hsieh610a8942020-10-29 17:21:35 -07001590 '--use-cargo-lock',
1591 action='store_true',
1592 default=False,
1593 help=('run cargo build with existing Cargo.lock ' +
1594 '(used when some latest dependent crates failed)'))
1595 parser.add_argument(
Joel Galensond9c4de62021-04-23 10:26:40 -07001596 '--min-sdk-version',
1597 type=str,
1598 help='Minimum SDK version')
1599 parser.add_argument(
1600 '--apex-available',
1601 nargs='*',
1602 help='Mark the main library as apex_available with the given apexes.')
1603 parser.add_argument(
Joel Galensoncf4ebb02021-04-07 08:39:51 -07001604 '--no-test-mapping',
1605 action='store_true',
1606 default=False,
1607 help='Do not generate a TEST_MAPPING file. Use only to speed up debugging.')
1608 parser.add_argument(
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001609 '--verbose',
1610 action='store_true',
1611 default=False,
1612 help='echo executed commands')
1613 parser.add_argument(
1614 '--vv',
1615 action='store_true',
1616 default=False,
1617 help='run cargo with -vv instead of default -v')
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001618 parser.add_argument(
1619 '--dump-config-and-exit',
1620 type=str,
1621 help=('Dump command-line arguments (minus this flag) to a config file and exit. ' +
1622 'This is intended to help migrate from command line options to config files.'))
1623 parser.add_argument(
1624 '--config',
1625 type=str,
1626 help=('Load command-line options from the given config file. ' +
1627 'Options in this file will override those passed on the command line.'))
1628 return parser
1629
1630
1631def parse_args(parser):
1632 """Parses command-line options."""
1633 args = parser.parse_args()
1634 # Use the values specified in a config file if one was found.
1635 if args.config:
1636 with open(args.config, 'r') as f:
1637 config = json.load(f)
1638 args_dict = vars(args)
1639 for arg in config:
1640 args_dict[arg.replace('-', '_')] = config[arg]
1641 return args
1642
1643
1644def dump_config(parser, args):
1645 """Writes the non-default command-line options to the specified file."""
1646 args_dict = vars(args)
1647 # Filter out the arguments that have their default value.
Joel Galenson367360c2021-04-29 14:31:43 -07001648 # Also filter certain "temporary" arguments.
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001649 non_default_args = {}
1650 for arg in args_dict:
Joel Galenson367360c2021-04-29 14:31:43 -07001651 if args_dict[arg] != parser.get_default(
1652 arg) and arg != 'dump_config_and_exit' and arg != 'no_test_mapping':
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001653 non_default_args[arg.replace('_', '-')] = args_dict[arg]
1654 # Write to the specified file.
1655 with open(args.dump_config_and_exit, 'w') as f:
1656 json.dump(non_default_args, f, indent=2, sort_keys=True)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001657
1658
1659def main():
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001660 parser = get_parser()
1661 args = parse_args(parser)
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001662 if not args.run: # default is dry-run
1663 print(DRY_RUN_NOTE)
Joel Galenson0fbdafe2021-04-21 16:33:33 -07001664 if args.dump_config_and_exit:
1665 dump_config(parser, args)
1666 else:
1667 Runner(args).run_cargo().gen_bp().apply_patch().dump_test_mapping_files()
Chih-Hung Hsiehe8887372019-11-05 10:34:17 -08001668
1669
1670if __name__ == '__main__':
1671 main()