1#!/usr/bin/env python3 2# 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 30 Note that cargo build is only called once with the default target 31 x86_64-unknown-linux-gnu. 32 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 39 --cargo "build --target x86_64-unknown-linux-gnu" 40 --cargo "build --tests --target x86_64-unknown-linux-gnu" 41 42(4) To build variants of the same crate include a list of variant 43 entries under the 'variants' key inside cargo2android.json 44 config file. Each entry should contain a 'suffix' key along with 45 any required keys. The 'suffix' key must be a unique suffix to 46 be concatenated to the stem/module name. 47 48 Note that keys specified inside each variant entry will overwrite the 49 values on the existing keys in cargo2android.json. 50 51If there are rustc warning messages, this script will add 52a warning comment to the owner crate module in Android.bp. 53""" 54 55import argparse 56import glob 57import json 58import os 59import os.path 60import platform 61import re 62import shutil 63import subprocess 64import sys 65 66# Some Rust packages include extra unwanted crates. 67# This set contains all such excluded crate names. 68EXCLUDED_CRATES = {'protobuf_bin_gen_rust_do_not_use'} 69 70RENAME_MAP = { 71 # This map includes all changes to the default rust module names 72 # to resolve name conflicts, avoid confusion, or work as plugin. 73 'libash': 'libash_rust', 74 'libatomic': 'libatomic_rust', 75 'libbacktrace': 'libbacktrace_rust', 76 'libbase': 'libbase_rust', 77 'libbase64': 'libbase64_rust', 78 'libfuse': 'libfuse_rust', 79 'libgcc': 'libgcc_rust', 80 'liblog': 'liblog_rust', 81 'libminijail': 'libminijail_rust', 82 'libsync': 'libsync_rust', 83 'libx86_64': 'libx86_64_rust', 84 'libxml': 'libxml_rust', 85 'protoc_gen_rust': 'protoc-gen-rust', 86} 87 88RENAME_STEM_MAP = { 89 # This map includes all changes to the default rust module stem names, 90 # which is used for output files when different from the module name. 91 'protoc_gen_rust': 'protoc-gen-rust', 92} 93 94RENAME_DEFAULTS_MAP = { 95 # This map includes all changes to the default prefix of rust_default 96 # module names, to avoid conflict with existing Android modules. 97 'libc': 'rust_libc', 98} 99 100# Header added to all generated Android.bp files. 101ANDROID_BP_HEADER = ( 102 '// This file is generated by cargo2android.py {args}.\n' + 103 '// Do not modify this file as changes will be overridden on upgrade.\n') 104 105CARGO_OUT = 'cargo.out' # Name of file to keep cargo build -v output. 106 107# This should be kept in sync with tools/external_updater/crates_updater.py. 108ERRORS_LINE = 'Errors in ' + CARGO_OUT + ':' 109 110TARGET_TMP = 'target.tmp' # Name of temporary output directory. 111 112# Message to be displayed when this script is called without the --run flag. 113DRY_RUN_NOTE = ( 114 'Dry-run: This script uses ./' + TARGET_TMP + ' for output directory,\n' + 115 'runs cargo clean, runs cargo build -v, saves output to ./cargo.out,\n' + 116 'and writes to Android.bp in the current and subdirectories.\n\n' + 117 'To do do all of the above, use the --run flag.\n' + 118 'See --help for other flags, and more usage notes in this script.\n') 119 120# Cargo -v output of a call to rustc. 121RUSTC_PAT = re.compile('^ +Running `rustc (.*)`$') 122 123# Cargo -vv output of a call to rustc could be split into multiple lines. 124# Assume that the first line will contain some CARGO_* env definition. 125RUSTC_VV_PAT = re.compile('^ +Running `.*CARGO_.*=.*$') 126# The combined -vv output rustc command line pattern. 127RUSTC_VV_CMD_ARGS = re.compile('^ *Running `.*CARGO_.*=.* rustc (.*)`$') 128 129# Cargo -vv output of a "cc" or "ar" command; all in one line. 130CC_AR_VV_PAT = re.compile(r'^\[([^ ]*)[^\]]*\] running:? "(cc|ar)" (.*)$') 131# Some package, such as ring-0.13.5, has pattern '... running "cc"'. 132 133# Rustc output of file location path pattern for a warning message. 134WARNING_FILE_PAT = re.compile('^ *--> ([^:]*):[0-9]+') 135 136# cargo test --list output of the start of running a binary. 137CARGO_TEST_LIST_START_PAT = re.compile('^\s*Running (.*) \(.*\)$') 138 139# cargo test --list output of the end of running a binary. 140CARGO_TEST_LIST_END_PAT = re.compile('^(\d+) tests?, (\d+) benchmarks$') 141 142CARGO2ANDROID_RUNNING_PAT = re.compile('^### Running: .*$') 143 144# Rust package name with suffix -d1.d2.d3(+.*)?. 145VERSION_SUFFIX_PAT = re.compile(r'^(.*)-[0-9]+\.[0-9]+\.[0-9]+(?:-(alpha|beta)\.[0-9]+)?(?:\+.*)?$') 146 147# Crate types corresponding to a C ABI library 148C_LIBRARY_CRATE_TYPES = ['staticlib', 'cdylib'] 149# Crate types corresponding to a Rust ABI library 150RUST_LIBRARY_CRATE_TYPES = ['lib', 'rlib', 'dylib'] 151# Crate types corresponding to a library 152LIBRARY_CRATE_TYPES = C_LIBRARY_CRATE_TYPES + RUST_LIBRARY_CRATE_TYPES 153 154def altered_name(name): 155 return RENAME_MAP[name] if (name in RENAME_MAP) else name 156 157 158def altered_stem(name): 159 return RENAME_STEM_MAP[name] if (name in RENAME_STEM_MAP) else name 160 161 162def altered_defaults(name): 163 return RENAME_DEFAULTS_MAP[name] if (name in RENAME_DEFAULTS_MAP) else name 164 165 166def is_build_crate_name(name): 167 # We added special prefix to build script crate names. 168 return name.startswith('build_script_') 169 170 171def is_dependent_file_path(path): 172 # Absolute or dependent '.../' paths are not main files of this crate. 173 return path.startswith('/') or path.startswith('.../') 174 175 176def get_module_name(crate): # to sort crates in a list 177 return crate.module_name 178 179 180def pkg2crate_name(s): 181 return s.replace('-', '_').replace('.', '_') 182 183 184def file_base_name(path): 185 return os.path.splitext(os.path.basename(path))[0] 186 187 188def test_base_name(path): 189 return pkg2crate_name(file_base_name(path)) 190 191 192def unquote(s): # remove quotes around str 193 if s and len(s) > 1 and s[0] == s[-1] and s[0] in ('"', "'"): 194 return s[1:-1] 195 return s 196 197 198def remove_version_suffix(s): # remove -d1.d2.d3 suffix 199 if VERSION_SUFFIX_PAT.match(s): 200 return VERSION_SUFFIX_PAT.match(s).group(1) 201 return s 202 203 204def short_out_name(pkg, s): # replace /.../pkg-*/out/* with .../out/* 205 return re.sub('^/.*/' + pkg + '-[0-9a-f]*/out/', '.../out/', s) 206 207 208def escape_quotes(s): # replace '"' with '\\"' 209 return s.replace('"', '\\"') 210 211 212class Crate(object): 213 """Information of a Rust crate to collect/emit for an Android.bp module.""" 214 215 def __init__(self, runner, outf_name): 216 # Remembered global runner and its members. 217 self.runner = runner 218 self.debug = runner.args.debug 219 self.cargo_dir = '' # directory of my Cargo.toml 220 self.outf_name = outf_name # path to Android.bp 221 self.outf = None # open file handle of outf_name during dump* 222 # Variants/results that could be merged from multiple rustc lines. 223 self.host_supported = False 224 self.device_supported = False 225 self.has_warning = False 226 # Android module properties derived from rustc parameters. 227 self.module_name = '' # unique in Android build system 228 self.module_type = '' # rust_{binary,library,test}[_host] etc. 229 self.defaults = '' # rust_defaults used by rust_test* modules 230 self.default_srcs = False # use 'srcs' defined in self.defaults 231 self.root_pkg = '' # parent package name of a sub/test packge, from -L 232 self.srcs = list() # main_src or merged multiple source files 233 self.stem = '' # real base name of output file 234 # Kept parsed status 235 self.errors = '' # all errors found during parsing 236 self.line_num = 1 # runner told input source line number 237 self.line = '' # original rustc command line parameters 238 # Parameters collected from rustc command line. 239 self.crate_name = '' # follows --crate-name 240 self.main_src = '' # follows crate_name parameter, shortened 241 self.crate_types = list() # follows --crate-type 242 self.cfgs = list() # follows --cfg, without feature= prefix 243 self.features = list() # follows --cfg, name in 'feature="..."' 244 self.codegens = list() # follows -C, some ignored 245 self.externs = list() # follows --extern 246 self.core_externs = list() # first part of self.externs elements 247 self.static_libs = list() # e.g. -l static=host_cpuid 248 self.shared_libs = list() # e.g. -l dylib=wayland-client, -l z 249 self.cap_lints = '' # follows --cap-lints 250 self.emit_list = '' # e.g., --emit=dep-info,metadata,link 251 self.edition = '2015' # rustc default, e.g., --edition=2018 252 self.target = '' # follows --target 253 self.cargo_env_compat = True 254 self.cargo_pkg_version = '' # value extracted from Cargo.toml version field 255 self.variant_num = int(runner.variant_num) 256 257 def write(self, s): 258 # convenient way to output one line at a time with EOL. 259 self.outf.write(s + '\n') 260 261 def same_flags(self, other): 262 # host_supported, device_supported, has_warning are not compared but merged 263 # target is not compared, to merge different target/host modules 264 # externs is not compared; only core_externs is compared 265 return (not self.errors and not other.errors and 266 self.edition == other.edition and 267 self.cap_lints == other.cap_lints and 268 self.emit_list == other.emit_list and 269 self.core_externs == other.core_externs and 270 self.codegens == other.codegens and 271 self.features == other.features and 272 self.static_libs == other.static_libs and 273 self.shared_libs == other.shared_libs and self.cfgs == other.cfgs) 274 275 def merge_host_device(self, other): 276 """Returns true if attributes are the same except host/device support.""" 277 return (self.crate_name == other.crate_name and 278 self.crate_types == other.crate_types and 279 self.main_src == other.main_src and 280 # before merge, each test module has an unique module name and stem 281 (self.stem == other.stem or self.crate_types == ['test']) and 282 self.root_pkg == other.root_pkg and not self.skip_crate() and 283 self.same_flags(other)) 284 285 def merge_test(self, other): 286 """Returns true if self and other are tests of same root_pkg.""" 287 # Before merger, each test has its own crate_name. 288 # A merged test uses its source file base name as output file name, 289 # so a test is mergeable only if its base name equals to its crate name. 290 return (self.crate_types == other.crate_types and 291 self.crate_types == ['test'] and self.root_pkg == other.root_pkg and 292 not self.skip_crate() and 293 other.crate_name == test_base_name(other.main_src) and 294 (len(self.srcs) > 1 or 295 (self.crate_name == test_base_name(self.main_src)) and 296 self.host_supported == other.host_supported and 297 self.device_supported == other.device_supported) and 298 self.same_flags(other)) 299 300 def merge(self, other, outf_name): 301 """Try to merge crate into self.""" 302 # Cargo build --tests could recompile a library for tests. 303 # We need to merge such duplicated calls to rustc, with 304 # the algorithm in merge_host_device. 305 should_merge_host_device = self.merge_host_device(other) 306 should_merge_test = False 307 if not should_merge_host_device: 308 should_merge_test = self.merge_test(other) 309 if should_merge_host_device or should_merge_test: 310 self.runner.init_bp_file(outf_name) 311 with open(outf_name, 'a') as outf: # to write debug info 312 self.outf = outf 313 other.outf = outf 314 self.do_merge(other, should_merge_test) 315 return True 316 return False 317 318 def do_merge(self, other, should_merge_test): 319 """Merge attributes of other to self.""" 320 if self.debug: 321 self.write('\n// Before merge definition (1):') 322 self.dump_debug_info() 323 self.write('\n// Before merge definition (2):') 324 other.dump_debug_info() 325 # Merge properties of other to self. 326 self.has_warning = self.has_warning or other.has_warning 327 if not self.target: # okay to keep only the first target triple 328 self.target = other.target 329 # decide_module_type sets up default self.stem, 330 # which can be changed if self is a merged test module. 331 self.decide_module_type() 332 if should_merge_test: 333 if (self.runner.should_ignore_test(self.main_src) 334 and not self.runner.should_ignore_test(other.main_src)): 335 self.main_src = other.main_src 336 self.srcs.append(other.main_src) 337 # use a short unique name as the merged module name. 338 prefix = self.root_pkg + '_tests' 339 self.module_name = self.runner.claim_module_name(prefix, self, 0) 340 self.stem = self.module_name 341 # This normalized root_pkg name although might be the same 342 # as other module's crate_name, it is not actually used for 343 # output file name. A merged test module always have multiple 344 # source files and each source file base name is used as 345 # its output file name. 346 self.crate_name = pkg2crate_name(self.root_pkg) 347 if self.debug: 348 self.write('\n// After merge definition (1):') 349 self.dump_debug_info() 350 351 def find_cargo_dir(self): 352 """Deepest directory with Cargo.toml and contains the main_src.""" 353 if not is_dependent_file_path(self.main_src): 354 dir_name = os.path.dirname(self.main_src) 355 while dir_name: 356 if os.path.exists(dir_name + '/Cargo.toml'): 357 self.cargo_dir = dir_name 358 return 359 dir_name = os.path.dirname(dir_name) 360 361 def add_codegens_flag(self, flag): 362 """Ignore options not used in Android.""" 363 # 'prefer-dynamic' does not work with common flag -C lto 364 # 'embed-bitcode' is ignored; we might control LTO with other .bp flag 365 # 'codegen-units' is set in Android global config or by default 366 if not (flag.startswith('codegen-units=') or 367 flag.startswith('debuginfo=') or 368 flag.startswith('embed-bitcode=') or 369 flag.startswith('extra-filename=') or 370 flag.startswith('incremental=') or 371 flag.startswith('metadata=') or 372 flag == 'prefer-dynamic'): 373 self.codegens.append(flag) 374 375 def parse(self, line_num, line): 376 """Find important rustc arguments to convert to Android.bp properties.""" 377 self.line_num = line_num 378 self.line = line 379 args = list(map(unquote, line.split())) 380 i = 0 381 # Loop through every argument of rustc. 382 while i < len(args): 383 arg = args[i] 384 if arg == '--crate-name': 385 i += 1 386 self.crate_name = args[i] 387 elif arg == '--crate-type': 388 i += 1 389 # cargo calls rustc with multiple --crate-type flags. 390 # rustc can accept: 391 # --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro] 392 self.crate_types.append(args[i]) 393 elif arg == '--test': 394 self.crate_types.append('test') 395 elif arg == '--target': 396 i += 1 397 self.target = args[i] 398 elif arg == '--cfg': 399 i += 1 400 if args[i].startswith('feature='): 401 self.features.append(unquote(args[i].replace('feature=', ''))) 402 else: 403 self.cfgs.append(args[i]) 404 elif arg == '--extern': 405 i += 1 406 extern_names = re.sub('=/[^ ]*/deps/', ' = ', args[i]) 407 self.externs.append(extern_names) 408 self.core_externs.append(re.sub(' = .*', '', extern_names)) 409 elif arg == '-C': # codegen options 410 i += 1 411 self.add_codegens_flag(args[i]) 412 elif arg.startswith('-C'): 413 # cargo has been passing "-C <xyz>" flag to rustc, 414 # but newer cargo could pass '-Cembed-bitcode=no' to rustc. 415 self.add_codegens_flag(arg[2:]) 416 elif arg == '--cap-lints': 417 i += 1 418 self.cap_lints = args[i] 419 elif arg == '-L': 420 i += 1 421 if args[i].startswith('dependency=') and args[i].endswith('/deps'): 422 if '/' + TARGET_TMP + '/' in args[i]: 423 self.root_pkg = re.sub( 424 '^.*/', '', re.sub('/' + TARGET_TMP + '/.*/deps$', '', args[i])) 425 else: 426 self.root_pkg = re.sub('^.*/', '', 427 re.sub('/[^/]+/[^/]+/deps$', '', args[i])) 428 self.root_pkg = remove_version_suffix(self.root_pkg) 429 elif arg == '-l': 430 i += 1 431 if args[i].startswith('static='): 432 self.static_libs.append(re.sub('static=', '', args[i])) 433 elif args[i].startswith('dylib='): 434 self.shared_libs.append(re.sub('dylib=', '', args[i])) 435 else: 436 self.shared_libs.append(args[i]) 437 elif arg == '--out-dir' or arg == '--color': # ignored 438 i += 1 439 elif arg.startswith('--error-format=') or arg.startswith('--json='): 440 pass # ignored 441 elif arg.startswith('--emit='): 442 self.emit_list = arg.replace('--emit=', '') 443 elif arg.startswith('--edition='): 444 self.edition = arg.replace('--edition=', '') 445 elif arg.startswith('-Aclippy') or arg.startswith('-Wclippy'): 446 pass # TODO: Consider storing these to include in the Android.bp. 447 elif arg.startswith('-W'): 448 pass # ignored 449 elif arg.startswith('-D'): 450 pass # TODO: Consider storing these to include in the Android.bp. 451 elif not arg.startswith('-'): 452 # shorten imported crate main source paths like $HOME/.cargo/ 453 # registry/src/github.com-1ecc6299db9ec823/memchr-2.3.3/src/lib.rs 454 self.main_src = re.sub(r'^/[^ ]*/registry/src/', '.../', args[i]) 455 self.main_src = re.sub(r'^\.\.\./github.com-[0-9a-f]*/', '.../', 456 self.main_src) 457 self.find_cargo_dir() 458 if self.cargo_dir: # for a subdirectory 459 if self.runner.variant_args.no_subdir: # all .bp content to /dev/null 460 self.outf_name = '/dev/null' 461 elif not self.runner.variant_args.onefile: 462 # Write to Android.bp in the subdirectory with Cargo.toml. 463 self.outf_name = self.cargo_dir + '/Android.bp' 464 self.main_src = self.main_src[len(self.cargo_dir) + 1:] 465 466 else: 467 self.errors += 'ERROR: unknown ' + arg + '\n' 468 i += 1 469 if not self.crate_name: 470 self.errors += 'ERROR: missing --crate-name\n' 471 if not self.main_src: 472 self.errors += 'ERROR: missing main source file\n' 473 else: 474 self.srcs.append(self.main_src) 475 if not self.crate_types: 476 # Treat "--cfg test" as "--test" 477 if 'test' in self.cfgs: 478 self.crate_types.append('test') 479 else: 480 self.errors += 'ERROR: missing --crate-type or --test\n' 481 elif len(self.crate_types) > 1: 482 if 'test' in self.crate_types: 483 self.errors += 'ERROR: cannot handle both --crate-type and --test\n' 484 if 'lib' in self.crate_types and 'rlib' in self.crate_types: 485 self.errors += 'ERROR: cannot generate both lib and rlib crate types\n' 486 if not self.root_pkg: 487 self.root_pkg = self.crate_name 488 489 # get the package version from running cargo metadata 490 if not self.runner.variant_args.no_pkg_vers and not self.skip_crate(): 491 self.get_pkg_version() 492 493 self.device_supported = self.runner.variant_args.device 494 self.host_supported = not self.runner.variant_args.no_host 495 self.cfgs = sorted(set(self.cfgs)) 496 self.features = sorted(set(self.features)) 497 self.codegens = sorted(set(self.codegens)) 498 self.externs = sorted(set(self.externs)) 499 self.core_externs = sorted(set(self.core_externs)) 500 self.static_libs = sorted(set(self.static_libs)) 501 self.shared_libs = sorted(set(self.shared_libs)) 502 self.crate_types = sorted(set(self.crate_types)) 503 self.decide_module_type() 504 self.module_name = altered_name(self.stem) 505 return self 506 507 def get_pkg_version(self): 508 """Attempt to retrieve the package version from the Cargo.toml 509 510 If there is only one package, use its version. Otherwise, try to 511 match the emitted `--crate_name` arg against the package name. 512 513 This may fail in cases where multiple packages are defined (workspaces) 514 and where the package name does not match the emitted crate_name 515 (e.g. [lib.name] is set). 516 """ 517 cargo_metadata = subprocess.run([self.runner.cargo_path, 'metadata', '--no-deps', 518 '--format-version', '1'], 519 cwd=os.path.abspath(self.cargo_dir), 520 stdout=subprocess.PIPE) 521 if cargo_metadata.returncode: 522 self.errors += ('ERROR: unable to get cargo metadata for package version; ' + 523 'return code ' + cargo_metadata.returncode + '\n') 524 else: 525 metadata_json = json.loads(cargo_metadata.stdout) 526 if len(metadata_json['packages']) > 1: 527 for package in metadata_json['packages']: 528 # package names may contain '-', but is changed to '_' in the crate_name 529 if package['name'].replace('-','_') == self.crate_name: 530 self.cargo_pkg_version = package['version'] 531 break 532 else: 533 self.cargo_pkg_version = metadata_json['packages'][0]['version'] 534 535 if not self.cargo_pkg_version: 536 self.errors += ('ERROR: Unable to retrieve package version; ' + 537 'to disable, run with arg "--no-pkg-vers"\n') 538 539 def dump_line(self): 540 self.write('\n// Line ' + str(self.line_num) + ' ' + self.line) 541 542 def feature_list(self): 543 """Return a string of main_src + "feature_list".""" 544 pkg = self.main_src 545 if pkg.startswith('.../'): # keep only the main package name 546 pkg = re.sub('/.*', '', pkg[4:]) 547 elif pkg.startswith('/'): # use relative path for a local package 548 pkg = os.path.relpath(pkg) 549 if not self.features: 550 return pkg 551 return pkg + ' "' + ','.join(self.features) + '"' 552 553 def dump_skip_crate(self, kind): 554 if self.debug: 555 self.write('\n// IGNORED: ' + kind + ' ' + self.main_src) 556 return self 557 558 def skip_crate(self): 559 """Return crate_name or a message if this crate should be skipped.""" 560 if (is_build_crate_name(self.crate_name) or 561 self.crate_name in EXCLUDED_CRATES): 562 return self.crate_name 563 if is_dependent_file_path(self.main_src): 564 return 'dependent crate' 565 return '' 566 567 def dump(self): 568 """Dump all error/debug/module code to the output .bp file.""" 569 self.runner.init_bp_file(self.outf_name) 570 with open(self.outf_name, 'a') as outf: 571 self.outf = outf 572 if self.errors: 573 self.dump_line() 574 self.write(self.errors) 575 elif self.skip_crate(): 576 self.dump_skip_crate(self.skip_crate()) 577 else: 578 if self.debug: 579 self.dump_debug_info() 580 self.dump_android_module() 581 582 def dump_debug_info(self): 583 """Dump parsed data, when cargo2android is called with --debug.""" 584 585 def dump(name, value): 586 self.write('//%12s = %s' % (name, value)) 587 588 def opt_dump(name, value): 589 if value: 590 dump(name, value) 591 592 def dump_list(fmt, values): 593 for v in values: 594 self.write(fmt % v) 595 596 self.dump_line() 597 dump('module_name', self.module_name) 598 dump('crate_name', self.crate_name) 599 dump('crate_types', self.crate_types) 600 dump('main_src', self.main_src) 601 dump('has_warning', self.has_warning) 602 dump('for_host', self.host_supported) 603 dump('for_device', self.device_supported) 604 dump('module_type', self.module_type) 605 opt_dump('target', self.target) 606 opt_dump('edition', self.edition) 607 opt_dump('emit_list', self.emit_list) 608 opt_dump('cap_lints', self.cap_lints) 609 dump_list('// cfg = %s', self.cfgs) 610 dump_list('// cfg = \'feature "%s"\'', self.features) 611 # TODO(chh): escape quotes in self.features, but not in other dump_list 612 dump_list('// codegen = %s', self.codegens) 613 dump_list('// externs = %s', self.externs) 614 dump_list('// -l static = %s', self.static_libs) 615 dump_list('// -l (dylib) = %s', self.shared_libs) 616 617 def dump_android_module(self): 618 """Dump one or more Android module definition, depending on crate_types.""" 619 if len(self.crate_types) == 1: 620 self.dump_single_type_android_module() 621 return 622 if 'test' in self.crate_types: 623 self.write('\nERROR: multiple crate types cannot include test type') 624 return 625 # Dump one Android module per crate_type. 626 for crate_type in self.crate_types: 627 self.decide_one_module_type(crate_type) 628 self.dump_one_android_module(crate_type) 629 630 def build_default_name(self): 631 """Return a short and readable name for the rust_defaults module.""" 632 # Choices: (1) root_pkg + '_test'? + '_defaults', 633 # (2) root_pkg + '_test'? + '_defaults_' + crate_name 634 # (3) root_pkg + '_test'? + '_defaults_' + main_src_basename_path 635 # (4) root_pkg + '_test'? + '_defaults_' + a_positive_sequence_number 636 test = "_test" if self.crate_types == ['test'] else "" 637 name1 = altered_defaults(self.root_pkg) + test + '_defaults' 638 if self.runner.try_claim_module_name(name1, self): 639 return name1 640 name2 = name1 + '_' + self.crate_name 641 if self.runner.try_claim_module_name(name2, self): 642 return name2 643 name3 = name1 + '_' + self.main_src_basename_path() 644 if self.runner.try_claim_module_name(name3, self): 645 return name3 646 return self.runner.claim_module_name(name1, self, 0) 647 648 def dump_srcs_list(self): 649 """Dump the srcs list, for defaults or regular modules.""" 650 if len(self.srcs) > 1: 651 srcs = sorted(set(self.srcs)) # make a copy and dedup 652 else: 653 srcs = [self.main_src] 654 copy_out = self.runner.copy_out_module_name() 655 if copy_out: 656 srcs.append(':' + copy_out) 657 self.dump_android_property_list('srcs', '"%s"', srcs) 658 659 def dump_defaults_module(self): 660 """Dump a rust_defaults module to be shared by other modules.""" 661 name = self.build_default_name() 662 self.defaults = name 663 self.write('\nrust_defaults {') 664 self.write(' name: "' + name + '",') 665 if self.runner.variant_args.global_defaults: 666 self.write(' defaults: ["' + self.runner.variant_args.global_defaults + '"],') 667 self.write(' crate_name: "' + self.crate_name + '",') 668 if len(self.srcs) == 1: # only one source file; share it in defaults 669 self.default_srcs = True 670 if self.has_warning and not self.cap_lints: 671 self.write(' // has rustc warnings') 672 self.dump_srcs_list() 673 if self.cargo_env_compat: 674 self.write(' cargo_env_compat: true,') 675 if not self.runner.variant_args.no_pkg_vers: 676 self.write(' cargo_pkg_version: "' + self.cargo_pkg_version + '",') 677 if 'test' in self.crate_types: 678 self.write(' test_suites: ["general-tests"],') 679 self.write(' auto_gen_config: true,') 680 self.dump_edition_flags_libs() 681 if 'test' in self.crate_types and len(self.srcs) == 1: 682 self.dump_test_data() 683 self.write('}') 684 685 def dump_single_type_android_module(self): 686 """Dump one simple Android module, which has only one crate_type.""" 687 crate_type = self.crate_types[0] 688 if crate_type != 'test': 689 # do not change self.stem or self.module_name 690 self.dump_one_android_module(crate_type) 691 return 692 # Dump one test module per source file. 693 # crate_type == 'test' 694 self.srcs = [src for src in self.srcs if not self.runner.should_ignore_test(src)] 695 if len(self.srcs) > 1: 696 self.srcs = sorted(set(self.srcs)) 697 self.dump_defaults_module() 698 saved_srcs = self.srcs 699 for src in saved_srcs: 700 self.srcs = [src] 701 saved_main_src = self.main_src 702 self.main_src = src 703 self.module_name = self.test_module_name() 704 self.decide_one_module_type(crate_type) 705 self.dump_one_android_module(crate_type) 706 self.main_src = saved_main_src 707 self.srcs = saved_srcs 708 709 def dump_one_android_module(self, crate_type): 710 """Dump one Android module definition.""" 711 if not self.module_type: 712 self.write('\nERROR: unknown crate_type ' + crate_type) 713 return 714 self.write('\n' + self.module_type + ' {') 715 self.dump_android_core_properties() 716 if not self.defaults: 717 self.dump_edition_flags_libs() 718 if self.runner.variant_args.host_first_multilib and self.host_supported and crate_type != 'test': 719 self.write(' compile_multilib: "first",') 720 if self.runner.variant_args.exported_c_header_dir and crate_type in C_LIBRARY_CRATE_TYPES: 721 self.write(' include_dirs: [') 722 for header_dir in self.runner.variant_args.exported_c_header_dir: 723 self.write(' "%s",' % header_dir) 724 self.write(' ],') 725 if crate_type in LIBRARY_CRATE_TYPES and self.device_supported: 726 self.write(' apex_available: [') 727 if self.runner.variant_args.apex_available is None: 728 # If apex_available is not explicitly set, make it available to all 729 # apexes. 730 self.write(' "//apex_available:platform",') 731 self.write(' "//apex_available:anyapex",') 732 else: 733 for apex in self.runner.variant_args.apex_available: 734 self.write(' "%s",' % apex) 735 self.write(' ],') 736 if crate_type != 'test': 737 if self.runner.variant_args.native_bridge_supported: 738 self.write(' native_bridge_supported: true,') 739 if self.runner.variant_args.product_available: 740 self.write(' product_available: true,') 741 if self.runner.variant_args.recovery_available: 742 self.write(' recovery_available: true,') 743 if self.runner.variant_args.vendor_available: 744 self.write(' vendor_available: true,') 745 if self.runner.variant_args.vendor_ramdisk_available: 746 self.write(' vendor_ramdisk_available: true,') 747 if self.runner.variant_args.ramdisk_available: 748 self.write(' ramdisk_available: true,') 749 if self.runner.variant_args.min_sdk_version and crate_type in LIBRARY_CRATE_TYPES and self.device_supported: 750 self.write(' min_sdk_version: "%s",' % self.runner.variant_args.min_sdk_version) 751 if crate_type == 'test' and not self.default_srcs: 752 self.dump_test_data() 753 if self.runner.variant_args.add_module_block: 754 with open(self.runner.variant_args.add_module_block, 'r') as f: 755 self.write(' %s,' % f.read().replace('\n', '\n ')) 756 self.write('}') 757 758 def dump_android_flags(self): 759 """Dump Android module flags property.""" 760 if not self.codegens and not self.cap_lints: 761 return 762 self.write(' flags: [') 763 if self.cap_lints: 764 self.write(' "--cap-lints ' + self.cap_lints + '",') 765 codegens_fmt = '"-C %s"' 766 self.dump_android_property_list_items(codegens_fmt, self.codegens) 767 self.write(' ],') 768 769 def dump_edition_flags_libs(self): 770 if self.edition: 771 self.write(' edition: "' + self.edition + '",') 772 self.dump_android_property_list('features', '"%s"', self.features) 773 cfgs = [cfg for cfg in self.cfgs if not cfg in self.runner.variant_args.cfg_blocklist] 774 self.dump_android_property_list('cfgs', '"%s"', cfgs) 775 self.dump_android_flags() 776 if self.externs: 777 self.dump_android_externs() 778 all_static_libs = [lib for lib in self.static_libs if not lib in self.runner.variant_args.lib_blocklist] 779 static_libs = [lib for lib in all_static_libs if not lib in self.runner.variant_args.whole_static_libs] 780 self.dump_android_property_list('static_libs', '"lib%s"', static_libs) 781 whole_static_libs = [lib for lib in all_static_libs if lib in self.runner.variant_args.whole_static_libs] 782 self.dump_android_property_list('whole_static_libs', '"lib%s"', whole_static_libs) 783 shared_libs = [lib for lib in self.shared_libs if not lib in self.runner.variant_args.lib_blocklist] 784 self.dump_android_property_list('shared_libs', '"lib%s"', shared_libs) 785 786 def dump_test_data(self): 787 data = [data for (name, data) in map(lambda kv: kv.split('=', 1), self.runner.variant_args.test_data) 788 if self.srcs == [name]] 789 if data: 790 self.dump_android_property_list('data', '"%s"', data) 791 792 def main_src_basename_path(self): 793 return re.sub('/', '_', re.sub('.rs$', '', self.main_src)) 794 795 def test_module_name(self): 796 """Return a unique name for a test module.""" 797 # root_pkg+(_host|_device) + '_test_'+source_file_name 798 suffix = self.main_src_basename_path() 799 return self.root_pkg + '_test_' + suffix 800 801 def decide_module_type(self): 802 # Use the first crate type for the default/first module. 803 crate_type = self.crate_types[0] if self.crate_types else '' 804 self.decide_one_module_type(crate_type) 805 806 def decide_one_module_type(self, crate_type): 807 """Decide which Android module type to use.""" 808 host = '' if self.device_supported else '_host' 809 rlib = '_rlib' if self.runner.variant_args.force_rlib else '' 810 suffix = self.runner.variant_args.suffix if 'suffix' in self.runner.variant_args else '' 811 if crate_type == 'bin': # rust_binary[_host] 812 self.module_type = 'rust_binary' + host 813 # In rare cases like protobuf-codegen, the output binary name must 814 # be renamed to use as a plugin for protoc. 815 self.stem = altered_stem(self.crate_name) + suffix 816 self.module_name = altered_name(self.stem) 817 elif crate_type == 'lib': # rust_library[_host] 818 # TODO(chh): should this be rust_library[_host]? 819 # Assuming that Cargo.toml do not use both 'lib' and 'rlib', 820 # because we map them both to rlib. 821 self.module_type = 'rust_library' + rlib + host 822 self.stem = 'lib' + self.crate_name + suffix 823 self.module_name = altered_name(self.stem) 824 elif crate_type == 'rlib': # rust_library[_host] 825 self.module_type = 'rust_library' + rlib + host 826 self.stem = 'lib' + self.crate_name + suffix 827 self.module_name = altered_name(self.stem) 828 elif crate_type == 'dylib': # rust_library[_host]_dylib 829 self.module_type = 'rust_library' + host + '_dylib' 830 self.stem = 'lib' + self.crate_name + suffix 831 self.module_name = altered_name(self.stem) + '_dylib' 832 elif crate_type == 'cdylib': # rust_library[_host]_shared 833 self.module_type = 'rust_ffi' + host + '_shared' 834 self.stem = 'lib' + self.crate_name + suffix 835 self.module_name = altered_name(self.stem) + '_shared' 836 elif crate_type == 'staticlib': # rust_library[_host]_static 837 self.module_type = 'rust_ffi' + host + '_static' 838 self.stem = 'lib' + self.crate_name + suffix 839 self.module_name = altered_name(self.stem) + '_static' 840 elif crate_type == 'test': # rust_test[_host] 841 self.module_type = 'rust_test' + host 842 # Before do_merge, stem name is based on the --crate-name parameter. 843 # and test module name is based on stem. 844 self.stem = self.test_module_name() 845 # self.stem will be changed after merging with other tests. 846 # self.stem is NOT used for final test binary name. 847 # rust_test uses each source file base name as part of output file name. 848 # In do_merge, this function is called again, with a module_name. 849 # We make sure that the module name is unique in each package. 850 if self.module_name: 851 # Cargo uses "-C extra-filename=..." and "-C metadata=..." to add 852 # different suffixes and distinguish multiple tests of the same 853 # crate name. We ignore -C and use claim_module_name to get 854 # unique sequential suffix. 855 self.module_name = self.runner.claim_module_name( 856 self.module_name, self, 0) 857 # Now the module name is unique, stem should also match and unique. 858 self.stem = self.module_name 859 elif crate_type == 'proc-macro': # rust_proc_macro 860 self.module_type = 'rust_proc_macro' 861 self.stem = 'lib' + self.crate_name + suffix 862 self.module_name = altered_name(self.stem) 863 else: # unknown module type, rust_prebuilt_dylib? rust_library[_host]? 864 self.module_type = '' 865 self.stem = '' 866 867 def dump_android_property_list_items(self, fmt, values): 868 for v in values: 869 # fmt has quotes, so we need escape_quotes(v) 870 self.write(' ' + (fmt % escape_quotes(v)) + ',') 871 872 def dump_android_property_list(self, name, fmt, values): 873 if not values: 874 return 875 if len(values) > 1: 876 self.write(' ' + name + ': [') 877 self.dump_android_property_list_items(fmt, values) 878 self.write(' ],') 879 else: 880 self.write(' ' + name + ': [' + 881 (fmt % escape_quotes(values[0])) + '],') 882 883 def dump_android_core_properties(self): 884 """Dump the module header, name, stem, etc.""" 885 self.write(' name: "' + self.module_name + '",') 886 # see properties shared by dump_defaults_module 887 if self.defaults: 888 self.write(' defaults: ["' + self.defaults + '"],') 889 elif self.runner.variant_args.global_defaults: 890 self.write(' defaults: ["' + self.runner.variant_args.global_defaults + '"],') 891 if self.stem != self.module_name: 892 self.write(' stem: "' + self.stem + '",') 893 if self.has_warning and not self.cap_lints and not self.default_srcs: 894 self.write(' // has rustc warnings') 895 if self.host_supported and self.device_supported and self.module_type != 'rust_proc_macro': 896 self.write(' host_supported: true,') 897 if not self.defaults: 898 self.write(' crate_name: "' + self.crate_name + '",') 899 if not self.defaults and self.cargo_env_compat: 900 self.write(' cargo_env_compat: true,') 901 if not self.runner.variant_args.no_pkg_vers: 902 self.write(' cargo_pkg_version: "' + self.cargo_pkg_version + '",') 903 if not self.default_srcs: 904 self.dump_srcs_list() 905 if 'test' in self.crate_types and not self.defaults: 906 # self.root_pkg can have multiple test modules, with different *_tests[n] 907 # names, but their executables can all be installed under the same _tests 908 # directory. When built from Cargo.toml, all tests should have different 909 # file or crate names. So we used (root_pkg + '_tests') name as the 910 # relative_install_path. 911 # However, some package like 'slab' can have non-mergeable tests that 912 # must be separated by different module names. So, here we no longer 913 # emit relative_install_path. 914 # self.write(' relative_install_path: "' + self.root_pkg + '_tests",') 915 self.write(' test_suites: ["general-tests"],') 916 self.write(' auto_gen_config: true,') 917 if 'test' in self.crate_types and self.host_supported: 918 self.write(' test_options: {') 919 if self.runner.variant_args.no_presubmit: 920 self.write(' unit_test: false,') 921 else: 922 self.write(' unit_test: true,') 923 self.write(' },') 924 925 def dump_android_externs(self): 926 """Dump the dependent rlibs and dylibs property.""" 927 so_libs = list() 928 rust_libs = '' 929 deps_libname = re.compile('^.* = lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$') 930 for lib in self.externs: 931 # normal value of lib: "libc = liblibc-*.rlib" 932 # strange case in rand crate: "getrandom_package = libgetrandom-*.rlib" 933 # we should use "libgetrandom", not "lib" + "getrandom_package" 934 groups = deps_libname.match(lib) 935 if groups is not None: 936 lib_name = groups.group(1) 937 else: 938 lib_name = re.sub(' .*$', '', lib) 939 if lib_name in self.runner.variant_args.dependency_blocklist: 940 continue 941 if lib.endswith('.rlib') or lib.endswith('.rmeta'): 942 # On MacOS .rmeta is used when Linux uses .rlib or .rmeta. 943 rust_libs += ' "' + altered_name('lib' + lib_name) + '",\n' 944 elif lib.endswith('.so'): 945 so_libs.append(lib_name) 946 elif lib != 'proc_macro': # --extern proc_macro is special and ignored 947 rust_libs += ' // ERROR: unknown type of lib ' + lib + '\n' 948 if rust_libs: 949 self.write(' rustlibs: [\n' + rust_libs + ' ],') 950 # Are all dependent .so files proc_macros? 951 # TODO(chh): Separate proc_macros and dylib. 952 self.dump_android_property_list('proc_macros', '"lib%s"', so_libs) 953 954 955class ARObject(object): 956 """Information of an "ar" link command.""" 957 958 def __init__(self, runner, outf_name): 959 # Remembered global runner and its members. 960 self.runner = runner 961 self.pkg = '' 962 self.outf_name = outf_name # path to Android.bp 963 # "ar" arguments 964 self.line_num = 1 965 self.line = '' 966 self.flags = '' # e.g. "crs" 967 self.lib = '' # e.g. "/.../out/lib*.a" 968 self.objs = list() # e.g. "/.../out/.../*.o" 969 970 def parse(self, pkg, line_num, args_line): 971 """Collect ar obj/lib file names.""" 972 self.pkg = pkg 973 self.line_num = line_num 974 self.line = args_line 975 args = args_line.split() 976 num_args = len(args) 977 if num_args < 3: 978 print('ERROR: "ar" command has too few arguments', args_line) 979 else: 980 self.flags = unquote(args[0]) 981 self.lib = unquote(args[1]) 982 self.objs = sorted(set(map(unquote, args[2:]))) 983 return self 984 985 def write(self, s): 986 self.outf.write(s + '\n') 987 988 def dump_debug_info(self): 989 self.write('\n// Line ' + str(self.line_num) + ' "ar" ' + self.line) 990 self.write('// ar_object for %12s' % self.pkg) 991 self.write('// flags = %s' % self.flags) 992 self.write('// lib = %s' % short_out_name(self.pkg, self.lib)) 993 for o in self.objs: 994 self.write('// obj = %s' % short_out_name(self.pkg, o)) 995 996 def dump_android_lib(self): 997 """Write cc_library_static into Android.bp.""" 998 self.write('\ncc_library_static {') 999 self.write(' name: "' + file_base_name(self.lib) + '",') 1000 self.write(' host_supported: true,') 1001 if self.flags != 'crs': 1002 self.write(' // ar flags = %s' % self.flags) 1003 if self.pkg not in self.runner.pkg_obj2cc: 1004 self.write(' ERROR: cannot find source files.\n}') 1005 return 1006 self.write(' srcs: [') 1007 obj2cc = self.runner.pkg_obj2cc[self.pkg] 1008 # Note: wflags are ignored. 1009 dflags = list() 1010 fflags = list() 1011 for obj in self.objs: 1012 self.write(' "' + short_out_name(self.pkg, obj2cc[obj].src) + '",') 1013 # TODO(chh): union of dflags and flags of all obj 1014 # Now, just a temporary hack that uses the last obj's flags 1015 dflags = obj2cc[obj].dflags 1016 fflags = obj2cc[obj].fflags 1017 self.write(' ],') 1018 self.write(' cflags: [') 1019 self.write(' "-O3",') # TODO(chh): is this default correct? 1020 self.write(' "-Wno-error",') 1021 for x in fflags: 1022 self.write(' "-f' + x + '",') 1023 for x in dflags: 1024 self.write(' "-D' + x + '",') 1025 self.write(' ],') 1026 self.write('}') 1027 1028 def dump(self): 1029 """Dump error/debug/module info to the output .bp file.""" 1030 self.runner.init_bp_file(self.outf_name) 1031 with open(self.outf_name, 'a') as outf: 1032 self.outf = outf 1033 if self.runner.variant_args.debug: 1034 self.dump_debug_info() 1035 self.dump_android_lib() 1036 1037 1038class CCObject(object): 1039 """Information of a "cc" compilation command.""" 1040 1041 def __init__(self, runner, outf_name): 1042 # Remembered global runner and its members. 1043 self.runner = runner 1044 self.pkg = '' 1045 self.outf_name = outf_name # path to Android.bp 1046 # "cc" arguments 1047 self.line_num = 1 1048 self.line = '' 1049 self.src = '' 1050 self.obj = '' 1051 self.dflags = list() # -D flags 1052 self.fflags = list() # -f flags 1053 self.iflags = list() # -I flags 1054 self.wflags = list() # -W flags 1055 self.other_args = list() 1056 1057 def parse(self, pkg, line_num, args_line): 1058 """Collect cc compilation flags and src/out file names.""" 1059 self.pkg = pkg 1060 self.line_num = line_num 1061 self.line = args_line 1062 args = args_line.split() 1063 i = 0 1064 while i < len(args): 1065 arg = args[i] 1066 if arg == '"-c"': 1067 i += 1 1068 if args[i].startswith('"-o'): 1069 # ring-0.13.5 dumps: ... "-c" "-o/.../*.o" ".../*.c" 1070 self.obj = unquote(args[i])[2:] 1071 i += 1 1072 self.src = unquote(args[i]) 1073 else: 1074 self.src = unquote(args[i]) 1075 elif arg == '"-o"': 1076 i += 1 1077 self.obj = unquote(args[i]) 1078 elif arg == '"-I"': 1079 i += 1 1080 self.iflags.append(unquote(args[i])) 1081 elif arg.startswith('"-D'): 1082 self.dflags.append(unquote(args[i])[2:]) 1083 elif arg.startswith('"-f'): 1084 self.fflags.append(unquote(args[i])[2:]) 1085 elif arg.startswith('"-W'): 1086 self.wflags.append(unquote(args[i])[2:]) 1087 elif not (arg.startswith('"-O') or arg == '"-m64"' or arg == '"-g"' or 1088 arg == '"-g3"'): 1089 # ignore -O -m64 -g 1090 self.other_args.append(unquote(args[i])) 1091 i += 1 1092 self.dflags = sorted(set(self.dflags)) 1093 self.fflags = sorted(set(self.fflags)) 1094 # self.wflags is not sorted because some are order sensitive 1095 # and we ignore them anyway. 1096 if self.pkg not in self.runner.pkg_obj2cc: 1097 self.runner.pkg_obj2cc[self.pkg] = {} 1098 self.runner.pkg_obj2cc[self.pkg][self.obj] = self 1099 return self 1100 1101 def write(self, s): 1102 self.outf.write(s + '\n') 1103 1104 def dump_debug_flags(self, name, flags): 1105 self.write('// ' + name + ':') 1106 for f in flags: 1107 self.write('// %s' % f) 1108 1109 def dump(self): 1110 """Dump only error/debug info to the output .bp file.""" 1111 if not self.runner.variant_args.debug: 1112 return 1113 self.runner.init_bp_file(self.outf_name) 1114 with open(self.outf_name, 'a') as outf: 1115 self.outf = outf 1116 self.write('\n// Line ' + str(self.line_num) + ' "cc" ' + self.line) 1117 self.write('// cc_object for %12s' % self.pkg) 1118 self.write('// src = %s' % short_out_name(self.pkg, self.src)) 1119 self.write('// obj = %s' % short_out_name(self.pkg, self.obj)) 1120 self.dump_debug_flags('-I flags', self.iflags) 1121 self.dump_debug_flags('-D flags', self.dflags) 1122 self.dump_debug_flags('-f flags', self.fflags) 1123 self.dump_debug_flags('-W flags', self.wflags) 1124 if self.other_args: 1125 self.dump_debug_flags('other args', self.other_args) 1126 1127 1128class Runner(object): 1129 """Main class to parse cargo -v output and print Android module definitions.""" 1130 1131 def __init__(self, args): 1132 self.bp_files = set() # Remember all output Android.bp files. 1133 self.root_pkg = '' # name of package in ./Cargo.toml 1134 # Saved flags, modes, and data. 1135 self.args = args 1136 self.variant_args = args 1137 self.variant_num = 0 1138 self.dry_run = not args.run 1139 self.skip_cargo = args.skipcargo 1140 self.cargo_path = './cargo' # path to cargo, will be set later 1141 self.checked_out_files = False # to check only once 1142 self.build_out_files = [] # output files generated by build.rs 1143 # All cc/ar objects, crates, dependencies, and warning files 1144 self.cc_objects = list() 1145 self.pkg_obj2cc = {} 1146 # pkg_obj2cc[cc_object[i].pkg][cc_objects[i].obj] = cc_objects[i] 1147 self.ar_objects = list() 1148 self.crates = list() 1149 self.warning_files = set() 1150 # Keep a unique mapping from (module name) to crate 1151 self.name_owners = {} 1152 # Save and dump all errors from cargo to Android.bp. 1153 self.errors = '' 1154 self.test_errors = '' 1155 self.setup_cargo_path() 1156 # Default action is cargo clean, followed by build or user given actions. 1157 if args.cargo: 1158 self.cargo = ['clean'] + args.cargo 1159 else: 1160 default_target = '--target x86_64-unknown-linux-gnu' 1161 # Use the same target for both host and default device builds. 1162 # Same target is used as default in host x86_64 Android compilation. 1163 # Note: b/169872957, prebuilt cargo failed to build vsock 1164 # on x86_64-unknown-linux-musl systems. 1165 self.cargo = ['clean', 'build ' + default_target] 1166 if args.tests: 1167 self.cargo.append('build --tests ' + default_target) 1168 self.empty_tests = set() 1169 self.empty_unittests = False 1170 1171 def setup_cargo_path(self): 1172 """Find cargo in the --cargo_bin or prebuilt rust bin directory.""" 1173 if self.args.cargo_bin: 1174 self.cargo_path = os.path.join(self.args.cargo_bin, 'cargo') 1175 if not os.path.isfile(self.cargo_path): 1176 sys.exit('ERROR: cannot find cargo in ' + self.args.cargo_bin) 1177 print('INFO: using cargo in ' + self.args.cargo_bin) 1178 return 1179 # We have only tested this on Linux. 1180 if platform.system() != 'Linux': 1181 sys.exit('ERROR: this script has only been tested on Linux with cargo.') 1182 # Assuming that this script is in development/scripts. 1183 my_dir = os.path.dirname(os.path.abspath(__file__)) 1184 linux_dir = os.path.join(my_dir, '..', '..', 1185 'prebuilts', 'rust', 'linux-x86') 1186 if not os.path.isdir(linux_dir): 1187 sys.exit('ERROR: cannot find directory ' + linux_dir) 1188 rust_version = self.find_rust_version(my_dir, linux_dir) 1189 cargo_bin = os.path.join(linux_dir, rust_version, 'bin') 1190 self.cargo_path = os.path.join(cargo_bin, 'cargo') 1191 if not os.path.isfile(self.cargo_path): 1192 sys.exit('ERROR: cannot find cargo in ' + cargo_bin 1193 + '; please try --cargo_bin= flag.') 1194 return 1195 1196 def find_rust_version(self, my_dir, linux_dir): 1197 """Use my script directory, find prebuilt rust version.""" 1198 # First look up build/soong/rust/config/global.go. 1199 path2global = os.path.join(my_dir, '..', '..', 1200 'build', 'soong', 'rust', 'config', 'global.go') 1201 if os.path.isfile(path2global): 1202 # try to find: RustDefaultVersion = "1.44.0" 1203 version_pat = re.compile( 1204 r'\s*RustDefaultVersion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+).*"') 1205 with open(path2global, 'r') as inf: 1206 for line in inf: 1207 result = version_pat.match(line) 1208 if result: 1209 return result.group(1) 1210 print('WARNING: cannot find RustDefaultVersion in ' + path2global) 1211 # Otherwise, find the newest (largest) version number in linux_dir. 1212 rust_version = (0, 0, 0) # the prebuilt version to use 1213 version_pat = re.compile(r'([0-9]+)\.([0-9]+)\.([0-9]+)$') 1214 for dir_name in os.listdir(linux_dir): 1215 result = version_pat.match(dir_name) 1216 if not result: 1217 continue 1218 version = (int(result.group(1)), int(result.group(2)), int(result.group(3))) 1219 if version > rust_version: 1220 rust_version = version 1221 return '.'.join(map(str, rust_version)) 1222 1223 def find_out_files(self): 1224 # list1 has build.rs output for normal crates 1225 list1 = glob.glob(TARGET_TMP + '/*/*/build/' + self.root_pkg + '-*/out/*') 1226 # list2 has build.rs output for proc-macro crates 1227 list2 = glob.glob(TARGET_TMP + '/*/build/' + self.root_pkg + '-*/out/*') 1228 return list1 + list2 1229 1230 def copy_out_files(self): 1231 """Copy build.rs output files to ./out and set up build_out_files.""" 1232 if self.checked_out_files: 1233 return 1234 self.checked_out_files = True 1235 cargo_out_files = self.find_out_files() 1236 out_files = set() 1237 if cargo_out_files: 1238 os.makedirs('out', exist_ok=True) 1239 for path in cargo_out_files: 1240 file_name = path.split('/')[-1] 1241 out_files.add(file_name) 1242 shutil.copy(path, 'out/' + file_name) 1243 self.build_out_files = sorted(out_files) 1244 1245 def has_used_out_dir(self): 1246 """Returns true if env!("OUT_DIR") is found.""" 1247 return 0 == os.system('grep -rl --exclude build.rs --include \\*.rs' + 1248 ' \'env!("OUT_DIR")\' * > /dev/null') 1249 1250 def copy_out_module_name(self): 1251 if self.args.copy_out and self.build_out_files: 1252 return 'copy_' + self.root_pkg + '_build_out' 1253 else: 1254 return '' 1255 1256 def read_license(self, name): 1257 if not os.path.isfile(name): 1258 return '' 1259 license = '' 1260 with open(name, 'r') as intf: 1261 line = intf.readline() 1262 # Firstly skip ANDROID_BP_HEADER 1263 while line.startswith('//'): 1264 line = intf.readline() 1265 # Read all lines until we see a rust_* or genrule rule. 1266 while line != '' and not (line.startswith('rust_') or line.startswith('genrule {')): 1267 license += line 1268 line = intf.readline() 1269 return license.strip() 1270 1271 def dump_copy_out_module(self, outf): 1272 """Output the genrule module to copy out/* to $(genDir).""" 1273 copy_out = self.copy_out_module_name() 1274 if not copy_out: 1275 return 1276 outf.write('\ngenrule {\n') 1277 outf.write(' name: "' + copy_out + '",\n') 1278 outf.write(' srcs: ["out/*"],\n') 1279 outf.write(' cmd: "cp $(in) $(genDir)",\n') 1280 if len(self.build_out_files) > 1: 1281 outf.write(' out: [\n') 1282 for f in self.build_out_files: 1283 outf.write(' "' + f + '",\n') 1284 outf.write(' ],\n') 1285 else: 1286 outf.write(' out: ["' + self.build_out_files[0] + '"],\n') 1287 outf.write('}\n') 1288 1289 def init_bp_file(self, name): 1290 # name could be Android.bp or sub_dir_path/Android.bp 1291 if name not in self.bp_files: 1292 self.bp_files.add(name) 1293 license_section = self.read_license(name) 1294 with open(name, 'w') as outf: 1295 print_args = sys.argv[1:].copy() 1296 if '--cargo_bin' in print_args: 1297 index = print_args.index('--cargo_bin') 1298 del print_args[index:index+2] 1299 outf.write(ANDROID_BP_HEADER.format(args=' '.join(print_args))) 1300 outf.write('\n') 1301 outf.write(license_section) 1302 outf.write('\n') 1303 # at most one copy_out module per .bp file 1304 self.dump_copy_out_module(outf) 1305 1306 def try_claim_module_name(self, name, owner): 1307 """Reserve and return True if it has not been reserved yet.""" 1308 if name not in self.name_owners or owner == self.name_owners[name]: 1309 self.name_owners[name] = owner 1310 return True 1311 return False 1312 1313 def claim_module_name(self, prefix, owner, counter): 1314 """Return prefix if not owned yet, otherwise, prefix+str(counter).""" 1315 while True: 1316 name = prefix 1317 if counter > 0: 1318 name += '_' + str(counter) 1319 if self.try_claim_module_name(name, owner): 1320 return name 1321 counter += 1 1322 1323 def find_root_pkg(self): 1324 """Read name of [package] in ./Cargo.toml.""" 1325 if not os.path.exists('./Cargo.toml'): 1326 return 1327 with open('./Cargo.toml', 'r') as inf: 1328 pkg_section = re.compile(r'^ *\[package\]') 1329 name = re.compile('^ *name *= * "([^"]*)"') 1330 in_pkg = False 1331 for line in inf: 1332 if in_pkg: 1333 if name.match(line): 1334 self.root_pkg = name.match(line).group(1) 1335 break 1336 else: 1337 in_pkg = pkg_section.match(line) is not None 1338 1339 def update_variant_args(self, variant_num): 1340 if 'variants' in self.args: 1341 # Resolve - and _ for Namespace usage 1342 variant_data = {k.replace('-', '_') : v for k, v in self.args.variants[variant_num].items()} 1343 # Merge and overwrite variant args 1344 self.variant_args = argparse.Namespace(**vars(self.args) | variant_data) 1345 1346 def run_cargo(self): 1347 """Calls cargo -v and save its output to ./cargo{_variant_num}.out.""" 1348 if self.skip_cargo: 1349 return self 1350 num_variants = len(self.args.variants) if 'variants' in self.args else 1 1351 for variant_num in range(num_variants): 1352 cargo_toml = './Cargo.toml' 1353 cargo_out = './cargo.out' 1354 if variant_num > 0: 1355 cargo_out = f'./cargo_{variant_num}.out' 1356 self.variant_num = variant_num 1357 self.update_variant_args(variant_num=variant_num) 1358 1359 # Do not use Cargo.lock, because .bp rules are designed to 1360 # run with "latest" crates available on Android. 1361 cargo_lock = './Cargo.lock' 1362 cargo_lock_saved = './cargo.lock.saved' 1363 had_cargo_lock = os.path.exists(cargo_lock) 1364 if not os.access(cargo_toml, os.R_OK): 1365 print('ERROR: Cannot find or read', cargo_toml) 1366 return self 1367 if not self.dry_run: 1368 if os.path.exists(cargo_out): 1369 os.remove(cargo_out) 1370 if not self.variant_args.use_cargo_lock and had_cargo_lock: # save it 1371 os.rename(cargo_lock, cargo_lock_saved) 1372 cmd_tail_target = ' --target-dir ' + TARGET_TMP 1373 cmd_tail_redir = ' >> ' + cargo_out + ' 2>&1' 1374 # set up search PATH for cargo to find the correct rustc 1375 saved_path = os.environ['PATH'] 1376 os.environ['PATH'] = os.path.dirname(self.cargo_path) + ':' + saved_path 1377 # Add [workspace] to Cargo.toml if it is not there. 1378 added_workspace = False 1379 if self.variant_args.add_workspace: 1380 with open(cargo_toml, 'r') as in_file: 1381 cargo_toml_lines = in_file.readlines() 1382 found_workspace = '[workspace]\n' in cargo_toml_lines 1383 if found_workspace: 1384 print('### WARNING: found [workspace] in Cargo.toml') 1385 else: 1386 with open(cargo_toml, 'a') as out_file: 1387 out_file.write('\n\n[workspace]\n') 1388 added_workspace = True 1389 if self.variant_args.verbose: 1390 print('### INFO: added [workspace] to Cargo.toml') 1391 for c in self.cargo: 1392 features = '' 1393 if c != 'clean': 1394 if self.variant_args.features is not None: 1395 features = ' --no-default-features' 1396 if self.variant_args.features: 1397 features += ' --features ' + self.variant_args.features 1398 cmd_v_flag = ' -vv ' if self.variant_args.vv else ' -v ' 1399 cmd = self.cargo_path + cmd_v_flag 1400 cmd += c + features + cmd_tail_target + cmd_tail_redir 1401 if self.variant_args.rustflags and c != 'clean': 1402 cmd = 'RUSTFLAGS="' + self.variant_args.rustflags + '" ' + cmd 1403 self.run_cmd(cmd, cargo_out) 1404 if self.variant_args.tests: 1405 cmd = self.cargo_path + ' test' + features + cmd_tail_target + ' -- --list' + cmd_tail_redir 1406 self.run_cmd(cmd, cargo_out) 1407 if added_workspace: # restore original Cargo.toml 1408 with open(cargo_toml, 'w') as out_file: 1409 out_file.writelines(cargo_toml_lines) 1410 if self.variant_args.verbose: 1411 print('### INFO: restored original Cargo.toml') 1412 os.environ['PATH'] = saved_path 1413 if not self.dry_run: 1414 if not had_cargo_lock: # restore to no Cargo.lock state 1415 if os.path.exists(cargo_lock): 1416 os.remove(cargo_lock) 1417 elif not self.variant_args.use_cargo_lock: # restore saved Cargo.lock 1418 os.rename(cargo_lock_saved, cargo_lock) 1419 return self 1420 1421 def run_cmd(self, cmd, cargo_out): 1422 if self.dry_run: 1423 print('Dry-run skip:', cmd) 1424 else: 1425 if self.variant_args.verbose: 1426 print('Running:', cmd) 1427 with open(cargo_out, 'a+') as out_file: 1428 out_file.write('### Running: ' + cmd + '\n') 1429 ret = os.system(cmd) 1430 if ret != 0: 1431 print('*** There was an error while running cargo. ' + 1432 'See the cargo.out file for details.') 1433 1434 def dump_pkg_obj2cc(self): 1435 """Dump debug info of the pkg_obj2cc map.""" 1436 if not self.variant_args.debug: 1437 return 1438 self.init_bp_file('Android.bp') 1439 with open('Android.bp', 'a') as outf: 1440 sorted_pkgs = sorted(self.pkg_obj2cc.keys()) 1441 for pkg in sorted_pkgs: 1442 if not self.pkg_obj2cc[pkg]: 1443 continue 1444 outf.write('\n// obj => src for %s\n' % pkg) 1445 obj2cc = self.pkg_obj2cc[pkg] 1446 for obj in sorted(obj2cc.keys()): 1447 outf.write('// ' + short_out_name(pkg, obj) + ' => ' + 1448 short_out_name(pkg, obj2cc[obj].src) + '\n') 1449 1450 def apply_patch(self): 1451 """Apply local patch file if it is given.""" 1452 if self.args.patch: 1453 if self.dry_run: 1454 print('Dry-run skip patch file:', self.args.patch) 1455 else: 1456 if not os.path.exists(self.args.patch): 1457 self.append_to_bp('ERROR cannot find patch file: ' + self.args.patch) 1458 return self 1459 if self.args.verbose: 1460 print('### INFO: applying local patch file:', self.args.patch) 1461 subprocess.run(['patch', '-s', '--no-backup-if-mismatch', './Android.bp', 1462 self.args.patch], check=True) 1463 return self 1464 1465 def gen_bp(self): 1466 """Parse cargo.out and generate Android.bp files.""" 1467 if self.dry_run: 1468 print('Dry-run skip: read', CARGO_OUT, 'write Android.bp') 1469 elif os.path.exists(CARGO_OUT): 1470 self.find_root_pkg() 1471 if self.args.copy_out: 1472 self.copy_out_files() 1473 elif self.find_out_files() and self.has_used_out_dir(): 1474 print('WARNING: ' + self.root_pkg + ' has cargo output files; ' + 1475 'please rerun with the --copy-out flag.') 1476 num_variants = len(self.args.variants) if 'variants' in self.args else 1 1477 for variant_num in range(num_variants): 1478 cargo_out_path = CARGO_OUT 1479 if variant_num > 0: 1480 cargo_out_path = f'./cargo_{variant_num}.out' 1481 self.variant_num = variant_num 1482 self.update_variant_args(variant_num=variant_num) 1483 with open(cargo_out_path, 'r') as cargo_out: 1484 self.parse(cargo_out, 'Android.bp') 1485 self.crates.sort(key=get_module_name) 1486 for obj in self.cc_objects: 1487 obj.dump() 1488 self.dump_pkg_obj2cc() 1489 for crate in self.crates: 1490 self.update_variant_args(variant_num=crate.variant_num) 1491 crate.dump() 1492 dumped_libs = set() 1493 for lib in self.ar_objects: 1494 if lib.pkg == self.root_pkg: 1495 lib_name = file_base_name(lib.lib) 1496 if lib_name not in dumped_libs: 1497 dumped_libs.add(lib_name) 1498 lib.dump() 1499 if self.args.add_toplevel_block: 1500 with open(self.args.add_toplevel_block, 'r') as f: 1501 self.append_to_bp('\n' + f.read() + '\n') 1502 if self.errors: 1503 self.append_to_bp('\n' + ERRORS_LINE + '\n' + self.errors) 1504 if self.test_errors: 1505 self.append_to_bp('\n// Errors when listing tests:\n' + self.test_errors) 1506 return self 1507 1508 def add_ar_object(self, obj): 1509 self.ar_objects.append(obj) 1510 1511 def add_cc_object(self, obj): 1512 self.cc_objects.append(obj) 1513 1514 def add_crate(self, crate): 1515 """Merge crate with someone in crates, or append to it. Return crates.""" 1516 if crate.skip_crate(): 1517 if self.args.debug: # include debug info of all crates 1518 self.crates.append(crate) 1519 else: 1520 for c in self.crates: 1521 if c.merge(crate, 'Android.bp'): 1522 return 1523 # If not merged, decide module type and name now. 1524 crate.decide_module_type() 1525 self.crates.append(crate) 1526 1527 def find_warning_owners(self): 1528 """For each warning file, find its owner crate.""" 1529 missing_owner = False 1530 for f in self.warning_files: 1531 cargo_dir = '' # find lowest crate, with longest path 1532 owner = None # owner crate of this warning 1533 for c in self.crates: 1534 if (f.startswith(c.cargo_dir + '/') and 1535 len(cargo_dir) < len(c.cargo_dir)): 1536 cargo_dir = c.cargo_dir 1537 owner = c 1538 if owner: 1539 owner.has_warning = True 1540 else: 1541 missing_owner = True 1542 if missing_owner and os.path.exists('Cargo.toml'): 1543 # owner is the root cargo, with empty cargo_dir 1544 for c in self.crates: 1545 if not c.cargo_dir: 1546 c.has_warning = True 1547 1548 def rustc_command(self, n, rustc_line, line, outf_name): 1549 """Process a rustc command line from cargo -vv output.""" 1550 # cargo build -vv output can have multiple lines for a rustc command 1551 # due to '\n' in strings for environment variables. 1552 # strip removes leading spaces and '\n' at the end 1553 new_rustc = (rustc_line.strip() + line) if rustc_line else line 1554 # Use an heuristic to detect the completions of a multi-line command. 1555 # This might fail for some very rare case, but easy to fix manually. 1556 if not line.endswith('`\n') or (new_rustc.count('`') % 2) != 0: 1557 return new_rustc 1558 if RUSTC_VV_CMD_ARGS.match(new_rustc): 1559 args = RUSTC_VV_CMD_ARGS.match(new_rustc).group(1) 1560 self.add_crate(Crate(self, outf_name).parse(n, args)) 1561 else: 1562 self.assert_empty_vv_line(new_rustc) 1563 return '' 1564 1565 def cc_ar_command(self, n, groups, outf_name): 1566 pkg = groups.group(1) 1567 line = groups.group(3) 1568 if groups.group(2) == 'cc': 1569 self.add_cc_object(CCObject(self, outf_name).parse(pkg, n, line)) 1570 else: 1571 self.add_ar_object(ARObject(self, outf_name).parse(pkg, n, line)) 1572 1573 def append_to_bp(self, line): 1574 self.init_bp_file('Android.bp') 1575 with open('Android.bp', 'a') as outf: 1576 outf.write(line) 1577 1578 def assert_empty_vv_line(self, line): 1579 if line: # report error if line is not empty 1580 self.append_to_bp('ERROR -vv line: ' + line) 1581 return '' 1582 1583 def add_empty_test(self, name): 1584 if name.startswith('unittests'): 1585 self.empty_unittests = True 1586 else: 1587 self.empty_tests.add(name) 1588 1589 def should_ignore_test(self, src): 1590 # cargo test outputs the source file for integration tests but "unittests" 1591 # for unit tests. To figure out to which crate this corresponds, we check 1592 # if the current source file is the main source of a non-test crate, e.g., 1593 # a library or a binary. 1594 return (src in self.args.test_blocklist or src in self.empty_tests 1595 or (self.empty_unittests 1596 and src in [c.main_src for c in self.crates if c.crate_types != ['test']])) 1597 1598 def parse(self, inf, outf_name): 1599 """Parse rustc, test, and warning messages in inf, return a list of Crates.""" 1600 n = 0 # line number 1601 # We read the file in two passes, where the first simply checks for empty tests. 1602 # Otherwise we would add and merge tests before seeing they're empty. 1603 cur_test_name = None 1604 for line in inf: 1605 if CARGO_TEST_LIST_START_PAT.match(line): 1606 cur_test_name = CARGO_TEST_LIST_START_PAT.match(line).group(1) 1607 elif cur_test_name and CARGO_TEST_LIST_END_PAT.match(line): 1608 match = CARGO_TEST_LIST_END_PAT.match(line) 1609 if int(match.group(1)) + int(match.group(2)) == 0: 1610 self.add_empty_test(cur_test_name) 1611 cur_test_name = None 1612 inf.seek(0) 1613 prev_warning = False # true if the previous line was warning: ... 1614 rustc_line = '' # previous line(s) matching RUSTC_VV_PAT 1615 in_tests = False 1616 for line in inf: 1617 n += 1 1618 if line.startswith('warning: '): 1619 prev_warning = True 1620 rustc_line = self.assert_empty_vv_line(rustc_line) 1621 continue 1622 new_rustc = '' 1623 if RUSTC_PAT.match(line): 1624 args_line = RUSTC_PAT.match(line).group(1) 1625 self.add_crate(Crate(self, outf_name).parse(n, args_line)) 1626 self.assert_empty_vv_line(rustc_line) 1627 elif rustc_line or RUSTC_VV_PAT.match(line): 1628 new_rustc = self.rustc_command(n, rustc_line, line, outf_name) 1629 elif CC_AR_VV_PAT.match(line): 1630 self.cc_ar_command(n, CC_AR_VV_PAT.match(line), outf_name) 1631 elif prev_warning and WARNING_FILE_PAT.match(line): 1632 self.assert_empty_vv_line(rustc_line) 1633 fpath = WARNING_FILE_PAT.match(line).group(1) 1634 if fpath[0] != '/': # ignore absolute path 1635 self.warning_files.add(fpath) 1636 elif line.startswith('error: ') or line.startswith('error[E'): 1637 if not self.args.ignore_cargo_errors: 1638 if in_tests: 1639 self.test_errors += '// ' + line 1640 else: 1641 self.errors += line 1642 elif CARGO2ANDROID_RUNNING_PAT.match(line): 1643 in_tests = "cargo test" in line and "--list" in line 1644 prev_warning = False 1645 rustc_line = new_rustc 1646 self.find_warning_owners() 1647 1648 1649def get_parser(): 1650 """Parse main arguments.""" 1651 parser = argparse.ArgumentParser('cargo2android') 1652 parser.add_argument( 1653 '--add_workspace', 1654 action='store_true', 1655 default=False, 1656 help=('append [workspace] to Cargo.toml before calling cargo,' + 1657 ' to treat current directory as root of package source;' + 1658 ' otherwise the relative source file path in generated' + 1659 ' .bp file will be from the parent directory.')) 1660 parser.add_argument( 1661 '--cargo', 1662 action='append', 1663 metavar='args_string', 1664 help=('extra cargo build -v args in a string, ' + 1665 'each --cargo flag calls cargo build -v once')) 1666 parser.add_argument( 1667 '--cargo_bin', 1668 type=str, 1669 help='use cargo in the cargo_bin directory instead of the prebuilt one') 1670 parser.add_argument( 1671 '--copy-out', 1672 action='store_true', 1673 default=False, 1674 help=('only for root directory, ' + 1675 'copy build.rs output to ./out/* and add a genrule to copy ' + 1676 './out/* to genrule output; for crates with code pattern: ' + 1677 'include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))')) 1678 parser.add_argument( 1679 '--debug', 1680 action='store_true', 1681 default=False, 1682 help='dump debug info into Android.bp') 1683 parser.add_argument( 1684 '--dependencies', 1685 action='store_true', 1686 default=False, 1687 help='Deprecated. Has no effect.') 1688 parser.add_argument( 1689 '--device', 1690 action='store_true', 1691 default=False, 1692 help='run cargo also for a default device target') 1693 parser.add_argument( 1694 '--features', 1695 type=str, 1696 help=('pass features to cargo build, ' + 1697 'empty string means no default features')) 1698 parser.add_argument( 1699 '--global_defaults', 1700 type=str, 1701 help='add a defaults name to every module') 1702 parser.add_argument( 1703 '--host-first-multilib', 1704 action='store_true', 1705 default=False, 1706 help=('add a compile_multilib:"first" property ' + 1707 'to Android.bp host modules.')) 1708 parser.add_argument( 1709 '--ignore-cargo-errors', 1710 action='store_true', 1711 default=False, 1712 help='do not append cargo/rustc error messages to Android.bp') 1713 parser.add_argument( 1714 '--no-host', 1715 action='store_true', 1716 default=False, 1717 help='do not run cargo for the host; only for the device target') 1718 parser.add_argument( 1719 '--no-presubmit', 1720 action='store_true', 1721 default=False, 1722 help='set unit_test to false for test targets, to avoid host tests running in presubmit') 1723 parser.add_argument( 1724 '--no-subdir', 1725 action='store_true', 1726 default=False, 1727 help='do not output anything for sub-directories') 1728 parser.add_argument( 1729 '--onefile', 1730 action='store_true', 1731 default=False, 1732 help=('output all into one ./Android.bp, default will generate ' + 1733 'one Android.bp per Cargo.toml in subdirectories')) 1734 parser.add_argument( 1735 '--patch', 1736 type=str, 1737 help='apply the given patch file to generated ./Android.bp') 1738 parser.add_argument( 1739 '--run', 1740 action='store_true', 1741 default=False, 1742 help='run it, default is dry-run') 1743 parser.add_argument('--rustflags', type=str, help='passing flags to rustc') 1744 parser.add_argument( 1745 '--skipcargo', 1746 action='store_true', 1747 default=False, 1748 help='skip cargo command, parse cargo.out, and generate Android.bp') 1749 parser.add_argument( 1750 '--tests', 1751 action='store_true', 1752 default=False, 1753 help='run cargo build --tests after normal build') 1754 parser.add_argument( 1755 '--use-cargo-lock', 1756 action='store_true', 1757 default=False, 1758 help=('run cargo build with existing Cargo.lock ' + 1759 '(used when some latest dependent crates failed)')) 1760 parser.add_argument( 1761 '--exported_c_header_dir', 1762 nargs='*', 1763 help='Directories with headers to export for C usage' 1764 ) 1765 parser.add_argument( 1766 '--min-sdk-version', 1767 type=str, 1768 help='Minimum SDK version') 1769 parser.add_argument( 1770 '--apex-available', 1771 nargs='*', 1772 help='Mark the main library as apex_available with the given apexes.') 1773 parser.add_argument( 1774 '--native-bridge-supported', 1775 action='store_true', 1776 default=False, 1777 help='Mark the main library as native_bridge_supported.') 1778 parser.add_argument( 1779 '--product-available', 1780 action='store_true', 1781 default=True, 1782 help='Mark the main library as product_available.') 1783 parser.add_argument( 1784 '--recovery-available', 1785 action='store_true', 1786 default=False, 1787 help='Mark the main library as recovery_available.') 1788 parser.add_argument( 1789 '--vendor-available', 1790 action='store_true', 1791 default=True, 1792 help='Mark the main library as vendor_available.') 1793 parser.add_argument( 1794 '--vendor-ramdisk-available', 1795 action='store_true', 1796 default=False, 1797 help='Mark the main library as vendor_ramdisk_available.') 1798 parser.add_argument( 1799 '--ramdisk-available', 1800 action='store_true', 1801 default=False, 1802 help='Mark the main library as ramdisk_available.') 1803 parser.add_argument( 1804 '--force-rlib', 1805 action='store_true', 1806 default=False, 1807 help='Make the main library an rlib.') 1808 parser.add_argument( 1809 '--whole-static-libs', 1810 nargs='*', 1811 default=[], 1812 help='Make the given libraries (without lib prefixes) whole_static_libs.') 1813 parser.add_argument( 1814 '--no-pkg-vers', 1815 action='store_true', 1816 default=False, 1817 help='Do not attempt to determine the package version automatically.') 1818 parser.add_argument( 1819 '--test-data', 1820 nargs='*', 1821 default=[], 1822 help=('Add the given file to the given test\'s data property. ' + 1823 'Usage: test-path=data-path')) 1824 parser.add_argument( 1825 '--dependency-blocklist', 1826 nargs='*', 1827 default=[], 1828 help='Do not emit the given dependencies (without lib prefixes).') 1829 parser.add_argument( 1830 '--lib-blocklist', 1831 nargs='*', 1832 default=[], 1833 help='Do not emit the given C libraries as dependencies (without lib prefixes).') 1834 parser.add_argument( 1835 '--test-blocklist', 1836 nargs='*', 1837 default=[], 1838 help=('Do not emit the given tests. ' + 1839 'Pass the path to the test file to exclude.')) 1840 parser.add_argument( 1841 '--cfg-blocklist', 1842 nargs='*', 1843 default=[], 1844 help='Do not emit the given cfg.') 1845 parser.add_argument( 1846 '--add-toplevel-block', 1847 type=str, 1848 help=('Add the contents of the given file to the top level of the Android.bp. ' + 1849 'The filename should start with cargo2android to work with the updater.')) 1850 parser.add_argument( 1851 '--add-module-block', 1852 type=str, 1853 help=('Add the contents of the given file to the main module. '+ 1854 'The filename should start with cargo2android to work with the updater.')) 1855 parser.add_argument( 1856 '--verbose', 1857 action='store_true', 1858 default=False, 1859 help='echo executed commands') 1860 parser.add_argument( 1861 '--vv', 1862 action='store_true', 1863 default=False, 1864 help='run cargo with -vv instead of default -v') 1865 parser.add_argument( 1866 '--dump-config-and-exit', 1867 type=str, 1868 help=('Dump command-line arguments (minus this flag) to a config file and exit. ' + 1869 'This is intended to help migrate from command line options to config files.')) 1870 parser.add_argument( 1871 '--config', 1872 type=str, 1873 help=('Load command-line options from the given config file. ' + 1874 'Options in this file will override those passed on the command line.')) 1875 return parser 1876 1877 1878def parse_args(parser): 1879 """Parses command-line options.""" 1880 args = parser.parse_args() 1881 # Use the values specified in a config file if one was found. 1882 if args.config: 1883 with open(args.config, 'r') as f: 1884 config = json.load(f) 1885 args_dict = vars(args) 1886 for arg in config: 1887 args_dict[arg.replace('-', '_')] = config[arg] 1888 return args 1889 1890 1891def dump_config(parser, args): 1892 """Writes the non-default command-line options to the specified file.""" 1893 args_dict = vars(args) 1894 # Filter out the arguments that have their default value. 1895 # Also filter certain "temporary" arguments. 1896 non_default_args = {} 1897 for arg in args_dict: 1898 if (args_dict[arg] != parser.get_default(arg) and arg != 'dump_config_and_exit' 1899 and arg != 'config' and arg != 'cargo_bin'): 1900 non_default_args[arg.replace('_', '-')] = args_dict[arg] 1901 # Write to the specified file. 1902 with open(args.dump_config_and_exit, 'w') as f: 1903 json.dump(non_default_args, f, indent=2, sort_keys=True) 1904 1905 1906def main(): 1907 parser = get_parser() 1908 args = parse_args(parser) 1909 if not args.run: # default is dry-run 1910 print(DRY_RUN_NOTE) 1911 if args.dump_config_and_exit: 1912 dump_config(parser, args) 1913 else: 1914 Runner(args).run_cargo().gen_bp().apply_patch() 1915 1916 1917if __name__ == '__main__': 1918 main() 1919