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