1#!/usr/bin/env python 2 3# ignore-tidy-linelength 4 5from __future__ import absolute_import, division, print_function 6import sys 7import os 8rust_dir = os.path.dirname(os.path.abspath(__file__)) 9rust_dir = os.path.dirname(rust_dir) 10rust_dir = os.path.dirname(rust_dir) 11sys.path.append(os.path.join(rust_dir, "src", "bootstrap")) 12import bootstrap # noqa: E402 13 14 15class Option(object): 16 def __init__(self, name, rustbuild, desc, value): 17 self.name = name 18 self.rustbuild = rustbuild 19 self.desc = desc 20 self.value = value 21 22 23options = [] 24 25 26def o(*args): 27 options.append(Option(*args, value=False)) 28 29 30def v(*args): 31 options.append(Option(*args, value=True)) 32 33 34o("debug", "rust.debug", "enables debugging environment; does not affect optimization of bootstrapped code") 35o("docs", "build.docs", "build standard library documentation") 36o("compiler-docs", "build.compiler-docs", "build compiler documentation") 37o("optimize-tests", "rust.optimize-tests", "build tests with optimizations") 38o("verbose-tests", "rust.verbose-tests", "enable verbose output when running tests") 39o("ccache", "llvm.ccache", "invoke gcc/clang via ccache to reuse object files between builds") 40o("sccache", None, "invoke gcc/clang via sccache to reuse object files between builds") 41o("local-rust", None, "use an installed rustc rather than downloading a snapshot") 42v("local-rust-root", None, "set prefix for local rust binary") 43o("local-rebuild", "build.local-rebuild", "assume local-rust matches the current version, for rebuilds; implies local-rust, and is implied if local-rust already matches the current version") 44o("llvm-static-stdcpp", "llvm.static-libstdcpp", "statically link to libstdc++ for LLVM") 45o("llvm-link-shared", "llvm.link-shared", "prefer shared linking to LLVM (llvm-config --link-shared)") 46o("rpath", "rust.rpath", "build rpaths into rustc itself") 47o("codegen-tests", "rust.codegen-tests", "run the tests/codegen tests") 48o("ninja", "llvm.ninja", "build LLVM using the Ninja generator (for MSVC, requires building in the correct environment)") 49o("locked-deps", "build.locked-deps", "force Cargo.lock to be up to date") 50o("vendor", "build.vendor", "enable usage of vendored Rust crates") 51o("sanitizers", "build.sanitizers", "build the sanitizer runtimes (asan, lsan, msan, tsan, hwasan)") 52o("dist-src", "rust.dist-src", "when building tarballs enables building a source tarball") 53o("cargo-native-static", "build.cargo-native-static", "static native libraries in cargo") 54o("profiler", "build.profiler", "build the profiler runtime") 55o("full-tools", None, "enable all tools") 56o("lld", "rust.lld", "build lld") 57o("clang", "llvm.clang", "build clang") 58o("missing-tools", "dist.missing-tools", "allow failures when building tools") 59o("use-libcxx", "llvm.use-libcxx", "build LLVM with libc++") 60o("control-flow-guard", "rust.control-flow-guard", "Enable Control Flow Guard") 61 62v("llvm-cflags", "llvm.cflags", "build LLVM with these extra compiler flags") 63v("llvm-cxxflags", "llvm.cxxflags", "build LLVM with these extra compiler flags") 64v("llvm-ldflags", "llvm.ldflags", "build LLVM with these extra linker flags") 65 66v("llvm-libunwind", "rust.llvm-libunwind", "use LLVM libunwind") 67 68# Optimization and debugging options. These may be overridden by the release 69# channel, etc. 70o("optimize-llvm", "llvm.optimize", "build optimized LLVM") 71o("llvm-assertions", "llvm.assertions", "build LLVM with assertions") 72o("llvm-plugins", "llvm.plugins", "build LLVM with plugin interface") 73o("debug-assertions", "rust.debug-assertions", "build with debugging assertions") 74o("debug-assertions-std", "rust.debug-assertions-std", "build the standard library with debugging assertions") 75o("overflow-checks", "rust.overflow-checks", "build with overflow checks") 76o("overflow-checks-std", "rust.overflow-checks-std", "build the standard library with overflow checks") 77o("llvm-release-debuginfo", "llvm.release-debuginfo", "build LLVM with debugger metadata") 78v("debuginfo-level", "rust.debuginfo-level", "debuginfo level for Rust code") 79v("debuginfo-level-rustc", "rust.debuginfo-level-rustc", "debuginfo level for the compiler") 80v("debuginfo-level-std", "rust.debuginfo-level-std", "debuginfo level for the standard library") 81v("debuginfo-level-tools", "rust.debuginfo-level-tools", "debuginfo level for the tools") 82v("debuginfo-level-tests", "rust.debuginfo-level-tests", "debuginfo level for the test suites run with compiletest") 83v("save-toolstates", "rust.save-toolstates", "save build and test status of external tools into this file") 84 85v("prefix", "install.prefix", "set installation prefix") 86v("localstatedir", "install.localstatedir", "local state directory") 87v("datadir", "install.datadir", "install data") 88v("sysconfdir", "install.sysconfdir", "install system configuration files") 89v("infodir", "install.infodir", "install additional info") 90v("libdir", "install.libdir", "install libraries") 91v("mandir", "install.mandir", "install man pages in PATH") 92v("docdir", "install.docdir", "install documentation in PATH") 93v("bindir", "install.bindir", "install binaries") 94 95v("llvm-root", None, "set LLVM root") 96v("llvm-config", None, "set path to llvm-config") 97v("llvm-filecheck", None, "set path to LLVM's FileCheck utility") 98v("python", "build.python", "set path to python") 99v("android-cross-path", "target.arm-linux-androideabi.android-ndk", 100 "Android NDK standalone path (deprecated)") 101v("i686-linux-android-ndk", "target.i686-linux-android.android-ndk", 102 "i686-linux-android NDK standalone path") 103v("arm-linux-androideabi-ndk", "target.arm-linux-androideabi.android-ndk", 104 "arm-linux-androideabi NDK standalone path") 105v("armv7-linux-androideabi-ndk", "target.armv7-linux-androideabi.android-ndk", 106 "armv7-linux-androideabi NDK standalone path") 107v("thumbv7neon-linux-androideabi-ndk", "target.thumbv7neon-linux-androideabi.android-ndk", 108 "thumbv7neon-linux-androideabi NDK standalone path") 109v("aarch64-linux-android-ndk", "target.aarch64-linux-android.android-ndk", 110 "aarch64-linux-android NDK standalone path") 111v("x86_64-linux-android-ndk", "target.x86_64-linux-android.android-ndk", 112 "x86_64-linux-android NDK standalone path") 113v("musl-root", "target.x86_64-unknown-linux-musl.musl-root", 114 "MUSL root installation directory (deprecated)") 115v("musl-root-x86_64", "target.x86_64-unknown-linux-musl.musl-root", 116 "x86_64-unknown-linux-musl install directory") 117v("musl-root-i586", "target.i586-unknown-linux-musl.musl-root", 118 "i586-unknown-linux-musl install directory") 119v("musl-root-i686", "target.i686-unknown-linux-musl.musl-root", 120 "i686-unknown-linux-musl install directory") 121v("musl-root-arm", "target.arm-unknown-linux-musleabi.musl-root", 122 "arm-unknown-linux-musleabi install directory") 123v("musl-root-armhf", "target.arm-unknown-linux-musleabihf.musl-root", 124 "arm-unknown-linux-musleabihf install directory") 125v("musl-root-armv5te", "target.armv5te-unknown-linux-musleabi.musl-root", 126 "armv5te-unknown-linux-musleabi install directory") 127v("musl-root-armv7", "target.armv7-unknown-linux-musleabi.musl-root", 128 "armv7-unknown-linux-musleabi install directory") 129v("musl-root-armv7hf", "target.armv7-unknown-linux-musleabihf.musl-root", 130 "armv7-unknown-linux-musleabihf install directory") 131v("musl-root-aarch64", "target.aarch64-unknown-linux-musl.musl-root", 132 "aarch64-unknown-linux-musl install directory") 133v("musl-root-mips", "target.mips-unknown-linux-musl.musl-root", 134 "mips-unknown-linux-musl install directory") 135v("musl-root-mipsel", "target.mipsel-unknown-linux-musl.musl-root", 136 "mipsel-unknown-linux-musl install directory") 137v("musl-root-mips64", "target.mips64-unknown-linux-muslabi64.musl-root", 138 "mips64-unknown-linux-muslabi64 install directory") 139v("musl-root-mips64el", "target.mips64el-unknown-linux-muslabi64.musl-root", 140 "mips64el-unknown-linux-muslabi64 install directory") 141v("musl-root-riscv32gc", "target.riscv32gc-unknown-linux-musl.musl-root", 142 "riscv32gc-unknown-linux-musl install directory") 143v("musl-root-riscv64gc", "target.riscv64gc-unknown-linux-musl.musl-root", 144 "riscv64gc-unknown-linux-musl install directory") 145v("qemu-armhf-rootfs", "target.arm-unknown-linux-gnueabihf.qemu-rootfs", 146 "rootfs in qemu testing, you probably don't want to use this") 147v("qemu-aarch64-rootfs", "target.aarch64-unknown-linux-gnu.qemu-rootfs", 148 "rootfs in qemu testing, you probably don't want to use this") 149v("qemu-riscv64-rootfs", "target.riscv64gc-unknown-linux-gnu.qemu-rootfs", 150 "rootfs in qemu testing, you probably don't want to use this") 151v("experimental-targets", "llvm.experimental-targets", 152 "experimental LLVM targets to build") 153v("release-channel", "rust.channel", "the name of the release channel to build") 154v("release-description", "rust.description", "optional descriptive string for version output") 155v("dist-compression-formats", None, "List of compression formats to use") 156 157# Used on systems where "cc" is unavailable 158v("default-linker", "rust.default-linker", "the default linker") 159 160# Many of these are saved below during the "writing configuration" step 161# (others are conditionally saved). 162o("manage-submodules", "build.submodules", "let the build manage the git submodules") 163o("full-bootstrap", "build.full-bootstrap", "build three compilers instead of two (not recommended except for testing reproducible builds)") 164o("extended", "build.extended", "build an extended rust tool set") 165 166v("tools", None, "List of extended tools will be installed") 167v("codegen-backends", None, "List of codegen backends to build") 168v("build", "build.build", "GNUs ./configure syntax LLVM build triple") 169v("host", None, "List of GNUs ./configure syntax LLVM host triples") 170v("target", None, "List of GNUs ./configure syntax LLVM target triples") 171 172# Options specific to this configure script 173o("option-checking", None, "complain about unrecognized options in this configure script") 174o("verbose-configure", None, "don't truncate options when printing them in this configure script") 175v("set", None, "set arbitrary key/value pairs in TOML configuration") 176 177 178def p(msg): 179 print("configure: " + msg) 180 181 182def err(msg): 183 print("configure: error: " + msg) 184 sys.exit(1) 185 186def is_value_list(key): 187 for option in options: 188 if option.name == key and option.desc.startswith('List of'): 189 return True 190 return False 191 192if '--help' in sys.argv or '-h' in sys.argv: 193 print('Usage: ./configure [options]') 194 print('') 195 print('Options') 196 for option in options: 197 if 'android' in option.name: 198 # no one needs to know about these obscure options 199 continue 200 if option.value: 201 print('\t{:30} {}'.format('--{}=VAL'.format(option.name), option.desc)) 202 else: 203 print('\t{:30} {}'.format('--enable-{}'.format(option.name), option.desc)) 204 print('') 205 print('This configure script is a thin configuration shim over the true') 206 print('configuration system, `config.toml`. You can explore the comments') 207 print('in `config.example.toml` next to this configure script to see') 208 print('more information about what each option is. Additionally you can') 209 print('pass `--set` as an argument to set arbitrary key/value pairs') 210 print('in the TOML configuration if desired') 211 print('') 212 print('Also note that all options which take `--enable` can similarly') 213 print('be passed with `--disable-foo` to forcibly disable the option') 214 sys.exit(0) 215 216VERBOSE = False 217 218# Parse all command line arguments into one of these three lists, handling 219# boolean and value-based options separately 220def parse_args(args): 221 unknown_args = [] 222 need_value_args = [] 223 known_args = {} 224 225 i = 0 226 while i < len(args): 227 arg = args[i] 228 i += 1 229 if not arg.startswith('--'): 230 unknown_args.append(arg) 231 continue 232 233 found = False 234 for option in options: 235 value = None 236 if option.value: 237 keyval = arg[2:].split('=', 1) 238 key = keyval[0] 239 if option.name != key: 240 continue 241 242 if len(keyval) > 1: 243 value = keyval[1] 244 elif i < len(args): 245 value = args[i] 246 i += 1 247 else: 248 need_value_args.append(arg) 249 continue 250 else: 251 if arg[2:] == 'enable-' + option.name: 252 value = True 253 elif arg[2:] == 'disable-' + option.name: 254 value = False 255 else: 256 continue 257 258 found = True 259 if option.name not in known_args: 260 known_args[option.name] = [] 261 known_args[option.name].append((option, value)) 262 break 263 264 if not found: 265 unknown_args.append(arg) 266 267 # Note: here and a few other places, we use [-1] to apply the *last* value 268 # passed. But if option-checking is enabled, then the known_args loop will 269 # also assert that options are only passed once. 270 option_checking = ('option-checking' not in known_args 271 or known_args['option-checking'][-1][1]) 272 if option_checking: 273 if len(unknown_args) > 0: 274 err("Option '" + unknown_args[0] + "' is not recognized") 275 if len(need_value_args) > 0: 276 err("Option '{0}' needs a value ({0}=val)".format(need_value_args[0])) 277 278 global VERBOSE 279 VERBOSE = 'verbose-configure' in known_args 280 281 config = {} 282 283 set('build.configure-args', args, config) 284 apply_args(known_args, option_checking, config) 285 return parse_example_config(known_args, config) 286 287 288def build(known_args): 289 if 'build' in known_args: 290 return known_args['build'][-1][1] 291 return bootstrap.default_build_triple(verbose=False) 292 293 294def set(key, value, config): 295 if isinstance(value, list): 296 # Remove empty values, which value.split(',') tends to generate. 297 value = [v for v in value if v] 298 299 s = "{:20} := {}".format(key, value) 300 if len(s) < 70 or VERBOSE: 301 p(s) 302 else: 303 p(s[:70] + " ...") 304 305 arr = config 306 parts = key.split('.') 307 for i, part in enumerate(parts): 308 if i == len(parts) - 1: 309 if is_value_list(part) and isinstance(value, str): 310 value = value.split(',') 311 arr[part] = value 312 else: 313 if part not in arr: 314 arr[part] = {} 315 arr = arr[part] 316 317 318def apply_args(known_args, option_checking, config): 319 for key in known_args: 320 # The `set` option is special and can be passed a bunch of times 321 if key == 'set': 322 for _option, value in known_args[key]: 323 keyval = value.split('=', 1) 324 if len(keyval) == 1 or keyval[1] == "true": 325 value = True 326 elif keyval[1] == "false": 327 value = False 328 else: 329 value = keyval[1] 330 set(keyval[0], value, config) 331 continue 332 333 # Ensure each option is only passed once 334 arr = known_args[key] 335 if option_checking and len(arr) > 1: 336 err("Option '{}' provided more than once".format(key)) 337 option, value = arr[-1] 338 339 # If we have a clear avenue to set our value in rustbuild, do so 340 if option.rustbuild is not None: 341 set(option.rustbuild, value, config) 342 continue 343 344 # Otherwise we're a "special" option and need some extra handling, so do 345 # that here. 346 build_triple = build(known_args) 347 348 if option.name == 'sccache': 349 set('llvm.ccache', 'sccache', config) 350 elif option.name == 'local-rust': 351 for path in os.environ['PATH'].split(os.pathsep): 352 if os.path.exists(path + '/rustc'): 353 set('build.rustc', path + '/rustc', config) 354 break 355 for path in os.environ['PATH'].split(os.pathsep): 356 if os.path.exists(path + '/cargo'): 357 set('build.cargo', path + '/cargo', config) 358 break 359 elif option.name == 'local-rust-root': 360 set('build.rustc', value + '/bin/rustc', config) 361 set('build.cargo', value + '/bin/cargo', config) 362 elif option.name == 'llvm-root': 363 set('target.{}.llvm-config'.format(build_triple), value + '/bin/llvm-config', config) 364 elif option.name == 'llvm-config': 365 set('target.{}.llvm-config'.format(build_triple), value, config) 366 elif option.name == 'llvm-filecheck': 367 set('target.{}.llvm-filecheck'.format(build_triple), value, config) 368 elif option.name == 'tools': 369 set('build.tools', value.split(','), config) 370 elif option.name == 'codegen-backends': 371 set('rust.codegen-backends', value.split(','), config) 372 elif option.name == 'host': 373 set('build.host', value.split(','), config) 374 elif option.name == 'target': 375 set('build.target', value.split(','), config) 376 elif option.name == 'full-tools': 377 set('rust.codegen-backends', ['llvm'], config) 378 set('rust.lld', True, config) 379 set('rust.llvm-tools', True, config) 380 set('build.extended', True, config) 381 elif option.name in ['option-checking', 'verbose-configure']: 382 # this was handled above 383 pass 384 elif option.name == 'dist-compression-formats': 385 set('dist.compression-formats', value.split(','), config) 386 else: 387 raise RuntimeError("unhandled option {}".format(option.name)) 388 389# "Parse" the `config.example.toml` file into the various sections, and we'll 390# use this as a template of a `config.toml` to write out which preserves 391# all the various comments and whatnot. 392# 393# Note that the `target` section is handled separately as we'll duplicate it 394# per configured target, so there's a bit of special handling for that here. 395def parse_example_config(known_args, config): 396 sections = {} 397 cur_section = None 398 sections[None] = [] 399 section_order = [None] 400 targets = {} 401 top_level_keys = [] 402 403 with open(rust_dir + '/config.example.toml') as example_config: 404 example_lines = example_config.read().split("\n") 405 for line in example_lines: 406 if cur_section is None: 407 if line.count('=') == 1: 408 top_level_key = line.split('=')[0] 409 top_level_key = top_level_key.strip(' #') 410 top_level_keys.append(top_level_key) 411 if line.startswith('['): 412 cur_section = line[1:-1] 413 if cur_section.startswith('target'): 414 cur_section = 'target' 415 elif '.' in cur_section: 416 raise RuntimeError("don't know how to deal with section: {}".format(cur_section)) 417 sections[cur_section] = [line] 418 section_order.append(cur_section) 419 else: 420 sections[cur_section].append(line) 421 422 # Fill out the `targets` array by giving all configured targets a copy of the 423 # `target` section we just loaded from the example config 424 configured_targets = [build(known_args)] 425 if 'build' in config: 426 if 'host' in config['build']: 427 configured_targets += config['build']['host'] 428 if 'target' in config['build']: 429 configured_targets += config['build']['target'] 430 if 'target' in config: 431 for target in config['target']: 432 configured_targets.append(target) 433 for target in configured_targets: 434 targets[target] = sections['target'][:] 435 # For `.` to be valid TOML, it needs to be quoted. But `bootstrap.py` doesn't use a proper TOML parser and fails to parse the target. 436 # Avoid using quotes unless it's necessary. 437 targets[target][0] = targets[target][0].replace("x86_64-unknown-linux-gnu", "'{}'".format(target) if "." in target else target) 438 439 if 'profile' not in config: 440 set('profile', 'dist', config) 441 configure_file(sections, top_level_keys, targets, config) 442 return section_order, sections, targets 443 444 445def is_number(value): 446 try: 447 float(value) 448 return True 449 except ValueError: 450 return False 451 452 453# Here we walk through the constructed configuration we have from the parsed 454# command line arguments. We then apply each piece of configuration by 455# basically just doing a `sed` to change the various configuration line to what 456# we've got configure. 457def to_toml(value): 458 if isinstance(value, bool): 459 if value: 460 return "true" 461 else: 462 return "false" 463 elif isinstance(value, list): 464 return '[' + ', '.join(map(to_toml, value)) + ']' 465 elif isinstance(value, str): 466 # Don't put quotes around numeric values 467 if is_number(value): 468 return value 469 else: 470 return "'" + value + "'" 471 elif isinstance(value, dict): 472 return "{" + ", ".join(map(lambda a: "{} = {}".format(to_toml(a[0]), to_toml(a[1])), value.items())) + "}" 473 else: 474 raise RuntimeError('no toml') 475 476 477def configure_section(lines, config): 478 for key in config: 479 value = config[key] 480 found = False 481 for i, line in enumerate(lines): 482 if not line.startswith('#' + key + ' = '): 483 continue 484 found = True 485 lines[i] = "{} = {}".format(key, to_toml(value)) 486 break 487 if not found: 488 # These are used by rpm, but aren't accepted by x.py. 489 # Give a warning that they're ignored, but not a hard error. 490 if key in ["infodir", "localstatedir"]: 491 print("warning: {} will be ignored".format(key)) 492 else: 493 raise RuntimeError("failed to find config line for {}".format(key)) 494 495 496def configure_top_level_key(lines, top_level_key, value): 497 for i, line in enumerate(lines): 498 if line.startswith('#' + top_level_key + ' = ') or line.startswith(top_level_key + ' = '): 499 lines[i] = "{} = {}".format(top_level_key, to_toml(value)) 500 return 501 502 raise RuntimeError("failed to find config line for {}".format(top_level_key)) 503 504 505# Modify `sections` to reflect the parsed arguments and example configs. 506def configure_file(sections, top_level_keys, targets, config): 507 for section_key, section_config in config.items(): 508 if section_key not in sections and section_key not in top_level_keys: 509 raise RuntimeError("config key {} not in sections or top_level_keys".format(section_key)) 510 if section_key in top_level_keys: 511 configure_top_level_key(sections[None], section_key, section_config) 512 513 elif section_key == 'target': 514 for target in section_config: 515 configure_section(targets[target], section_config[target]) 516 else: 517 configure_section(sections[section_key], section_config) 518 519 520def write_uncommented(target, f): 521 block = [] 522 is_comment = True 523 524 for line in target: 525 block.append(line) 526 if len(line) == 0: 527 if not is_comment: 528 for ln in block: 529 f.write(ln + "\n") 530 block = [] 531 is_comment = True 532 continue 533 is_comment = is_comment and line.startswith('#') 534 return f 535 536 537def write_config_toml(writer, section_order, targets, sections): 538 for section in section_order: 539 if section == 'target': 540 for target in targets: 541 writer = write_uncommented(targets[target], writer) 542 else: 543 writer = write_uncommented(sections[section], writer) 544 545def quit_if_file_exists(file): 546 if os.path.isfile(file): 547 err("Existing '" + file + "' detected.") 548 549if __name__ == "__main__": 550 # If 'config.toml' already exists, exit the script at this point 551 quit_if_file_exists('config.toml') 552 553 p("processing command line") 554 # Parse all known arguments into a configuration structure that reflects the 555 # TOML we're going to write out 556 p("") 557 section_order, sections, targets = parse_args(sys.argv[1:]) 558 559 # Now that we've built up our `config.toml`, write it all out in the same 560 # order that we read it in. 561 p("") 562 p("writing `config.toml` in current directory") 563 with bootstrap.output('config.toml') as f: 564 write_config_toml(f, section_order, targets, sections) 565 566 with bootstrap.output('Makefile') as f: 567 contents = os.path.join(rust_dir, 'src', 'bootstrap', 'mk', 'Makefile.in') 568 contents = open(contents).read() 569 contents = contents.replace("$(CFG_SRC_DIR)", rust_dir + '/') 570 contents = contents.replace("$(CFG_PYTHON)", sys.executable) 571 f.write(contents) 572 573 p("") 574 p("run `python {}/x.py --help`".format(rust_dir)) 575