|  | #!/usr/bin/env python | 
|  |  | 
|  | # ignore-tidy-linelength | 
|  |  | 
|  | from __future__ import absolute_import, division, print_function | 
|  | import shlex | 
|  | import sys | 
|  | import os | 
|  | import re | 
|  |  | 
|  | rust_dir = os.path.dirname(os.path.abspath(__file__)) | 
|  | rust_dir = os.path.dirname(rust_dir) | 
|  | rust_dir = os.path.dirname(rust_dir) | 
|  | sys.path.append(os.path.join(rust_dir, "src", "bootstrap")) | 
|  | import bootstrap  # noqa: E402 | 
|  |  | 
|  |  | 
|  | class Option(object): | 
|  | def __init__(self, name, rustbuild, desc, value): | 
|  | self.name = name | 
|  | self.rustbuild = rustbuild | 
|  | self.desc = desc | 
|  | self.value = value | 
|  |  | 
|  |  | 
|  | options = [] | 
|  |  | 
|  |  | 
|  | def o(*args): | 
|  | options.append(Option(*args, value=False)) | 
|  |  | 
|  |  | 
|  | def v(*args): | 
|  | options.append(Option(*args, value=True)) | 
|  |  | 
|  |  | 
|  | o( | 
|  | "debug", | 
|  | "rust.debug", | 
|  | "enables debugging environment; does not affect optimization of bootstrapped code", | 
|  | ) | 
|  | o("docs", "build.docs", "build standard library documentation") | 
|  | o("compiler-docs", "build.compiler-docs", "build compiler documentation") | 
|  | o("optimize-tests", "rust.optimize-tests", "build tests with optimizations") | 
|  | o("verbose-tests", "rust.verbose-tests", "enable verbose output when running tests") | 
|  | o( | 
|  | "ccache", | 
|  | "build.ccache", | 
|  | "invoke gcc/clang/rustc via ccache to reuse object files between builds", | 
|  | ) | 
|  | o( | 
|  | "sccache", | 
|  | None, | 
|  | "invoke gcc/clang/rustc via sccache to reuse object files between builds", | 
|  | ) | 
|  | o("local-rust", None, "use an installed rustc rather than downloading a snapshot") | 
|  | v("local-rust-root", None, "set prefix for local rust binary") | 
|  | o( | 
|  | "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", | 
|  | ) | 
|  | o( | 
|  | "llvm-static-stdcpp", | 
|  | "llvm.static-libstdcpp", | 
|  | "statically link to libstdc++ for LLVM", | 
|  | ) | 
|  | o( | 
|  | "llvm-link-shared", | 
|  | "llvm.link-shared", | 
|  | "prefer shared linking to LLVM (llvm-config --link-shared)", | 
|  | ) | 
|  | o("rpath", "rust.rpath", "build rpaths into rustc itself") | 
|  | o("codegen-tests", "rust.codegen-tests", "run the tests/codegen tests") | 
|  | o( | 
|  | "ninja", | 
|  | "llvm.ninja", | 
|  | "build LLVM using the Ninja generator (for MSVC, requires building in the correct environment)", | 
|  | ) | 
|  | o("locked-deps", "build.locked-deps", "force Cargo.lock to be up to date") | 
|  | o("vendor", "build.vendor", "enable usage of vendored Rust crates") | 
|  | o( | 
|  | "sanitizers", | 
|  | "build.sanitizers", | 
|  | "build the sanitizer runtimes (asan, dfsan, lsan, msan, tsan, hwasan)", | 
|  | ) | 
|  | o( | 
|  | "dist-src", | 
|  | "rust.dist-src", | 
|  | "when building tarballs enables building a source tarball", | 
|  | ) | 
|  | o( | 
|  | "cargo-native-static", | 
|  | "build.cargo-native-static", | 
|  | "static native libraries in cargo", | 
|  | ) | 
|  | o("profiler", "build.profiler", "build the profiler runtime") | 
|  | o("full-tools", None, "enable all tools") | 
|  | o("lld", "rust.lld", "build lld") | 
|  | o("llvm-bitcode-linker", "rust.llvm-bitcode-linker", "build llvm bitcode linker") | 
|  | o("clang", "llvm.clang", "build clang") | 
|  | o("use-libcxx", "llvm.use-libcxx", "build LLVM with libc++") | 
|  | o("control-flow-guard", "rust.control-flow-guard", "Enable Control Flow Guard") | 
|  | o( | 
|  | "patch-binaries-for-nix", | 
|  | "build.patch-binaries-for-nix", | 
|  | "whether patch binaries for usage with Nix toolchains", | 
|  | ) | 
|  | o("new-symbol-mangling", "rust.new-symbol-mangling", "use symbol-mangling-version v0") | 
|  |  | 
|  | v("llvm-cflags", "llvm.cflags", "build LLVM with these extra compiler flags") | 
|  | v("llvm-cxxflags", "llvm.cxxflags", "build LLVM with these extra compiler flags") | 
|  | v("llvm-ldflags", "llvm.ldflags", "build LLVM with these extra linker flags") | 
|  |  | 
|  | v("llvm-libunwind", "rust.llvm-libunwind", "use LLVM libunwind") | 
|  |  | 
|  | # Optimization and debugging options. These may be overridden by the release | 
|  | # channel, etc. | 
|  | o("optimize-llvm", "llvm.optimize", "build optimized LLVM") | 
|  | o("llvm-assertions", "llvm.assertions", "build LLVM with assertions") | 
|  | o("llvm-enzyme", "llvm.enzyme", "build LLVM with enzyme") | 
|  | o("llvm-offload", "llvm.offload", "build LLVM with gpu offload support") | 
|  | o("llvm-plugins", "llvm.plugins", "build LLVM with plugin interface") | 
|  | o("debug-assertions", "rust.debug-assertions", "build with debugging assertions") | 
|  | o( | 
|  | "debug-assertions-std", | 
|  | "rust.debug-assertions-std", | 
|  | "build the standard library with debugging assertions", | 
|  | ) | 
|  | o("overflow-checks", "rust.overflow-checks", "build with overflow checks") | 
|  | o( | 
|  | "overflow-checks-std", | 
|  | "rust.overflow-checks-std", | 
|  | "build the standard library with overflow checks", | 
|  | ) | 
|  | o( | 
|  | "llvm-release-debuginfo", | 
|  | "llvm.release-debuginfo", | 
|  | "build LLVM with debugger metadata", | 
|  | ) | 
|  | v("debuginfo-level", "rust.debuginfo-level", "debuginfo level for Rust code") | 
|  | v( | 
|  | "debuginfo-level-rustc", | 
|  | "rust.debuginfo-level-rustc", | 
|  | "debuginfo level for the compiler", | 
|  | ) | 
|  | v( | 
|  | "debuginfo-level-std", | 
|  | "rust.debuginfo-level-std", | 
|  | "debuginfo level for the standard library", | 
|  | ) | 
|  | v( | 
|  | "debuginfo-level-tools", | 
|  | "rust.debuginfo-level-tools", | 
|  | "debuginfo level for the tools", | 
|  | ) | 
|  | v( | 
|  | "debuginfo-level-tests", | 
|  | "rust.debuginfo-level-tests", | 
|  | "debuginfo level for the test suites run with compiletest", | 
|  | ) | 
|  | v( | 
|  | "save-toolstates", | 
|  | "rust.save-toolstates", | 
|  | "save build and test status of external tools into this file", | 
|  | ) | 
|  |  | 
|  | v("prefix", "install.prefix", "set installation prefix") | 
|  | v("localstatedir", "install.localstatedir", "local state directory") | 
|  | v("datadir", "install.datadir", "install data") | 
|  | v("sysconfdir", "install.sysconfdir", "install system configuration files") | 
|  | v("infodir", "install.infodir", "install additional info") | 
|  | v("libdir", "install.libdir", "install libraries") | 
|  | v("mandir", "install.mandir", "install man pages in PATH") | 
|  | v("docdir", "install.docdir", "install documentation in PATH") | 
|  | v("bindir", "install.bindir", "install binaries") | 
|  |  | 
|  | v("llvm-root", None, "set LLVM root") | 
|  | v("llvm-config", None, "set path to llvm-config") | 
|  | v("llvm-filecheck", None, "set path to LLVM's FileCheck utility") | 
|  | v("python", "build.python", "set path to python") | 
|  | v("android-ndk", "build.android-ndk", "set path to Android NDK") | 
|  | v( | 
|  | "musl-root", | 
|  | "target.x86_64-unknown-linux-musl.musl-root", | 
|  | "MUSL root installation directory (deprecated)", | 
|  | ) | 
|  | v( | 
|  | "musl-root-x86_64", | 
|  | "target.x86_64-unknown-linux-musl.musl-root", | 
|  | "x86_64-unknown-linux-musl install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-i586", | 
|  | "target.i586-unknown-linux-musl.musl-root", | 
|  | "i586-unknown-linux-musl install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-i686", | 
|  | "target.i686-unknown-linux-musl.musl-root", | 
|  | "i686-unknown-linux-musl install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-arm", | 
|  | "target.arm-unknown-linux-musleabi.musl-root", | 
|  | "arm-unknown-linux-musleabi install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-armhf", | 
|  | "target.arm-unknown-linux-musleabihf.musl-root", | 
|  | "arm-unknown-linux-musleabihf install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-armv5te", | 
|  | "target.armv5te-unknown-linux-musleabi.musl-root", | 
|  | "armv5te-unknown-linux-musleabi install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-armv7", | 
|  | "target.armv7-unknown-linux-musleabi.musl-root", | 
|  | "armv7-unknown-linux-musleabi install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-armv7hf", | 
|  | "target.armv7-unknown-linux-musleabihf.musl-root", | 
|  | "armv7-unknown-linux-musleabihf install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-aarch64", | 
|  | "target.aarch64-unknown-linux-musl.musl-root", | 
|  | "aarch64-unknown-linux-musl install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-mips", | 
|  | "target.mips-unknown-linux-musl.musl-root", | 
|  | "mips-unknown-linux-musl install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-mipsel", | 
|  | "target.mipsel-unknown-linux-musl.musl-root", | 
|  | "mipsel-unknown-linux-musl install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-mips64", | 
|  | "target.mips64-unknown-linux-muslabi64.musl-root", | 
|  | "mips64-unknown-linux-muslabi64 install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-mips64el", | 
|  | "target.mips64el-unknown-linux-muslabi64.musl-root", | 
|  | "mips64el-unknown-linux-muslabi64 install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-powerpc64", | 
|  | "target.powerpc64-unknown-linux-musl.musl-root", | 
|  | "powerpc64-unknown-linux-musl install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-powerpc64le", | 
|  | "target.powerpc64le-unknown-linux-musl.musl-root", | 
|  | "powerpc64le-unknown-linux-musl install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-riscv32gc", | 
|  | "target.riscv32gc-unknown-linux-musl.musl-root", | 
|  | "riscv32gc-unknown-linux-musl install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-riscv64gc", | 
|  | "target.riscv64gc-unknown-linux-musl.musl-root", | 
|  | "riscv64gc-unknown-linux-musl install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-loongarch64", | 
|  | "target.loongarch64-unknown-linux-musl.musl-root", | 
|  | "loongarch64-unknown-linux-musl install directory", | 
|  | ) | 
|  | v( | 
|  | "musl-root-wali-wasm32", | 
|  | "target.wasm32-wali-linux-musl.musl-root", | 
|  | "wasm32-wali-linux-musl install directory", | 
|  | ) | 
|  | v( | 
|  | "qemu-armhf-rootfs", | 
|  | "target.arm-unknown-linux-gnueabihf.qemu-rootfs", | 
|  | "rootfs in qemu testing, you probably don't want to use this", | 
|  | ) | 
|  | v( | 
|  | "qemu-aarch64-rootfs", | 
|  | "target.aarch64-unknown-linux-gnu.qemu-rootfs", | 
|  | "rootfs in qemu testing, you probably don't want to use this", | 
|  | ) | 
|  | v( | 
|  | "qemu-riscv64-rootfs", | 
|  | "target.riscv64gc-unknown-linux-gnu.qemu-rootfs", | 
|  | "rootfs in qemu testing, you probably don't want to use this", | 
|  | ) | 
|  | v( | 
|  | "experimental-targets", | 
|  | "llvm.experimental-targets", | 
|  | "experimental LLVM targets to build", | 
|  | ) | 
|  | v("release-channel", "rust.channel", "the name of the release channel to build") | 
|  | v( | 
|  | "release-description", | 
|  | "build.description", | 
|  | "optional descriptive string for version output", | 
|  | ) | 
|  | v("dist-compression-formats", None, "List of compression formats to use") | 
|  |  | 
|  | # Used on systems where "cc" is unavailable | 
|  | v("default-linker", "rust.default-linker", "the default linker") | 
|  |  | 
|  | # Many of these are saved below during the "writing configuration" step | 
|  | # (others are conditionally saved). | 
|  | o("manage-submodules", "build.submodules", "let the build manage the git submodules") | 
|  | o( | 
|  | "full-bootstrap", | 
|  | "build.full-bootstrap", | 
|  | "build three compilers instead of two (not recommended except for testing reproducible builds)", | 
|  | ) | 
|  | o("extended", "build.extended", "build an extended rust tool set") | 
|  |  | 
|  | v("bootstrap-cache-path", None, "use provided path for the bootstrap cache") | 
|  | v("tools", None, "List of extended tools will be installed") | 
|  | v("codegen-backends", None, "List of codegen backends to build") | 
|  | v("build", "build.build", "GNUs ./configure syntax LLVM build triple") | 
|  | v("host", None, "List of GNUs ./configure syntax LLVM host triples") | 
|  | v("target", None, "List of GNUs ./configure syntax LLVM target triples") | 
|  |  | 
|  | # Options specific to this configure script | 
|  | o( | 
|  | "option-checking", | 
|  | None, | 
|  | "complain about unrecognized options in this configure script", | 
|  | ) | 
|  | o( | 
|  | "verbose-configure", | 
|  | None, | 
|  | "don't truncate options when printing them in this configure script", | 
|  | ) | 
|  | v("set", None, "set arbitrary key/value pairs in TOML configuration") | 
|  |  | 
|  |  | 
|  | def p(msg): | 
|  | print("configure: " + msg) | 
|  |  | 
|  |  | 
|  | def err(msg): | 
|  | print("\nconfigure: ERROR: " + msg + "\n") | 
|  | sys.exit(1) | 
|  |  | 
|  |  | 
|  | def is_value_list(key): | 
|  | for option in options: | 
|  | if option.name == key and option.desc.startswith("List of"): | 
|  | return True | 
|  | return False | 
|  |  | 
|  |  | 
|  | if "--help" in sys.argv or "-h" in sys.argv: | 
|  | print("Usage: ./configure [options]") | 
|  | print("") | 
|  | print("Options") | 
|  | for option in options: | 
|  | if "android" in option.name: | 
|  | # no one needs to know about these obscure options | 
|  | continue | 
|  | if option.value: | 
|  | print("\t{:30} {}".format("--{}=VAL".format(option.name), option.desc)) | 
|  | else: | 
|  | print("\t--enable-{:25} OR --disable-{}".format(option.name, option.name)) | 
|  | print("\t\t" + option.desc) | 
|  | print("") | 
|  | print("This configure script is a thin configuration shim over the true") | 
|  | print("configuration system, `bootstrap.toml`. You can explore the comments") | 
|  | print("in `bootstrap.example.toml` next to this configure script to see") | 
|  | print("more information about what each option is. Additionally you can") | 
|  | print("pass `--set` as an argument to set arbitrary key/value pairs") | 
|  | print("in the TOML configuration if desired") | 
|  | print("") | 
|  | print("Also note that all options which take `--enable` can similarly") | 
|  | print("be passed with `--disable-foo` to forcibly disable the option") | 
|  | sys.exit(0) | 
|  |  | 
|  | VERBOSE = False | 
|  |  | 
|  |  | 
|  | # Parse all command line arguments into one of these three lists, handling | 
|  | # boolean and value-based options separately | 
|  | def parse_args(args): | 
|  | unknown_args = [] | 
|  | need_value_args = [] | 
|  | known_args = {} | 
|  |  | 
|  | i = 0 | 
|  | while i < len(args): | 
|  | arg = args[i] | 
|  | i += 1 | 
|  | if not arg.startswith("--"): | 
|  | unknown_args.append(arg) | 
|  | continue | 
|  |  | 
|  | found = False | 
|  | for option in options: | 
|  | value = None | 
|  | if option.value: | 
|  | keyval = arg[2:].split("=", 1) | 
|  | key = keyval[0] | 
|  | if option.name != key: | 
|  | continue | 
|  |  | 
|  | if len(keyval) > 1: | 
|  | value = keyval[1] | 
|  | elif i < len(args): | 
|  | value = args[i] | 
|  | i += 1 | 
|  | else: | 
|  | need_value_args.append(arg) | 
|  | continue | 
|  | else: | 
|  | if arg[2:] == "enable-" + option.name: | 
|  | value = True | 
|  | elif arg[2:] == "disable-" + option.name: | 
|  | value = False | 
|  | else: | 
|  | continue | 
|  |  | 
|  | found = True | 
|  | if option.name not in known_args: | 
|  | known_args[option.name] = [] | 
|  | known_args[option.name].append((option, value)) | 
|  | break | 
|  |  | 
|  | if not found: | 
|  | unknown_args.append(arg) | 
|  |  | 
|  | # NOTE: here and a few other places, we use [-1] to apply the *last* value | 
|  | # passed.  But if option-checking is enabled, then the known_args loop will | 
|  | # also assert that options are only passed once. | 
|  | option_checking = ( | 
|  | "option-checking" not in known_args or known_args["option-checking"][-1][1] | 
|  | ) | 
|  | if option_checking: | 
|  | if len(unknown_args) > 0: | 
|  | err("Option '" + unknown_args[0] + "' is not recognized") | 
|  | if len(need_value_args) > 0: | 
|  | err("Option '{0}' needs a value ({0}=val)".format(need_value_args[0])) | 
|  |  | 
|  | global VERBOSE | 
|  | VERBOSE = "verbose-configure" in known_args | 
|  |  | 
|  | config = {} | 
|  |  | 
|  | set("build.configure-args", args, config) | 
|  | apply_args(known_args, option_checking, config) | 
|  | return parse_example_config(known_args, config) | 
|  |  | 
|  |  | 
|  | def build(known_args): | 
|  | if "build" in known_args: | 
|  | return known_args["build"][-1][1] | 
|  | return bootstrap.default_build_triple(verbose=False) | 
|  |  | 
|  |  | 
|  | def set(key, value, config): | 
|  | if isinstance(value, list): | 
|  | # Remove empty values, which value.split(',') tends to generate and | 
|  | # replace single quotes for double quotes to ensure correct parsing. | 
|  | value = [v.replace("'", '"') for v in value if v] | 
|  |  | 
|  | s = "{:20} := {}".format(key, value) | 
|  | if len(s) < 70 or VERBOSE: | 
|  | p(s) | 
|  | else: | 
|  | p(s[:70] + " ...") | 
|  |  | 
|  | arr = config | 
|  |  | 
|  | # Split `key` on periods using shell semantics. | 
|  | lexer = shlex.shlex(key, posix=True) | 
|  | lexer.whitespace = "." | 
|  | lexer.wordchars += "-" | 
|  | parts = list(lexer) | 
|  |  | 
|  | for i, part in enumerate(parts): | 
|  | if i == len(parts) - 1: | 
|  | if is_value_list(part) and isinstance(value, str): | 
|  | value = value.split(",") | 
|  | arr[part] = value | 
|  | else: | 
|  | if part not in arr: | 
|  | arr[part] = {} | 
|  | arr = arr[part] | 
|  |  | 
|  |  | 
|  | def apply_args(known_args, option_checking, config): | 
|  | for key in known_args: | 
|  | # The `set` option is special and can be passed a bunch of times | 
|  | if key == "set": | 
|  | for _option, value in known_args[key]: | 
|  | keyval = value.split("=", 1) | 
|  | if len(keyval) == 1 or keyval[1] == "true": | 
|  | value = True | 
|  | elif keyval[1] == "false": | 
|  | value = False | 
|  | else: | 
|  | value = keyval[1] | 
|  | set(keyval[0], value, config) | 
|  | continue | 
|  |  | 
|  | # Ensure each option is only passed once | 
|  | arr = known_args[key] | 
|  | if option_checking and len(arr) > 1: | 
|  | err("Option '{}' provided more than once".format(key)) | 
|  | option, value = arr[-1] | 
|  |  | 
|  | # If we have a clear avenue to set our value in rustbuild, do so | 
|  | if option.rustbuild is not None: | 
|  | set(option.rustbuild, value, config) | 
|  | continue | 
|  |  | 
|  | # Otherwise we're a "special" option and need some extra handling, so do | 
|  | # that here. | 
|  | build_triple = build(known_args) | 
|  |  | 
|  | if option.name == "sccache": | 
|  | set("build.ccache", "sccache", config) | 
|  | elif option.name == "local-rust": | 
|  | for path in os.environ["PATH"].split(os.pathsep): | 
|  | if os.path.exists(path + "/rustc"): | 
|  | set("build.rustc", path + "/rustc", config) | 
|  | break | 
|  | for path in os.environ["PATH"].split(os.pathsep): | 
|  | if os.path.exists(path + "/cargo"): | 
|  | set("build.cargo", path + "/cargo", config) | 
|  | break | 
|  | elif option.name == "local-rust-root": | 
|  | set("build.rustc", value + "/bin/rustc", config) | 
|  | set("build.cargo", value + "/bin/cargo", config) | 
|  | elif option.name == "llvm-root": | 
|  | set( | 
|  | "target.{}.llvm-config".format(build_triple), | 
|  | value + "/bin/llvm-config", | 
|  | config, | 
|  | ) | 
|  | elif option.name == "llvm-config": | 
|  | set("target.{}.llvm-config".format(build_triple), value, config) | 
|  | elif option.name == "llvm-filecheck": | 
|  | set("target.{}.llvm-filecheck".format(build_triple), value, config) | 
|  | elif option.name == "tools": | 
|  | set("build.tools", value.split(","), config) | 
|  | elif option.name == "bootstrap-cache-path": | 
|  | set("build.bootstrap-cache-path", value, config) | 
|  | elif option.name == "codegen-backends": | 
|  | set("rust.codegen-backends", value.split(","), config) | 
|  | elif option.name == "host": | 
|  | set("build.host", value.split(","), config) | 
|  | elif option.name == "target": | 
|  | set("build.target", value.split(","), config) | 
|  | elif option.name == "full-tools": | 
|  | set("rust.codegen-backends", ["llvm"], config) | 
|  | set("rust.lld", True, config) | 
|  | set("rust.llvm-tools", True, config) | 
|  | set("rust.llvm-bitcode-linker", True, config) | 
|  | set("build.extended", True, config) | 
|  | elif option.name in ["option-checking", "verbose-configure"]: | 
|  | # this was handled above | 
|  | pass | 
|  | elif option.name == "dist-compression-formats": | 
|  | set("dist.compression-formats", value.split(","), config) | 
|  | else: | 
|  | raise RuntimeError("unhandled option {}".format(option.name)) | 
|  |  | 
|  |  | 
|  | # "Parse" the `bootstrap.example.toml` file into the various sections, and we'll | 
|  | # use this as a template of a `bootstrap.toml` to write out which preserves | 
|  | # all the various comments and whatnot. | 
|  | # | 
|  | # Note that the `target` section is handled separately as we'll duplicate it | 
|  | # per configured target, so there's a bit of special handling for that here. | 
|  | def parse_example_config(known_args, config): | 
|  | sections = {} | 
|  | cur_section = None | 
|  | sections[None] = [] | 
|  | section_order = [None] | 
|  | targets = {} | 
|  | top_level_keys = [] | 
|  | comment_lines = [] | 
|  |  | 
|  | with open(rust_dir + "/bootstrap.example.toml") as example_config: | 
|  | example_lines = example_config.read().split("\n") | 
|  | for line in example_lines: | 
|  | if line.count("=") >= 1 and not line.startswith("# "): | 
|  | key = line.split("=")[0] | 
|  | key = key.strip(" #") | 
|  | parts = key.split(".") | 
|  | if len(parts) > 1: | 
|  | cur_section = parts[0] | 
|  | if cur_section not in sections: | 
|  | sections[cur_section] = ["[" + cur_section + "]"] | 
|  | section_order.append(cur_section) | 
|  | elif cur_section is None: | 
|  | top_level_keys.append(key) | 
|  | # put the comment lines within the start of | 
|  | # a new section, not outside it. | 
|  | sections[cur_section] += comment_lines | 
|  | comment_lines = [] | 
|  | # remove just the `section.` part from the line, if present. | 
|  | sections[cur_section].append( | 
|  | re.sub("(#?)([a-zA-Z_-]+\\.)?(.*)", "\\1\\3", line) | 
|  | ) | 
|  | elif line.startswith("["): | 
|  | cur_section = line[1:-1] | 
|  | if cur_section.startswith("target"): | 
|  | cur_section = "target" | 
|  | elif "." in cur_section: | 
|  | raise RuntimeError( | 
|  | "don't know how to deal with section: {}".format(cur_section) | 
|  | ) | 
|  | sections[cur_section] = [line] | 
|  | section_order.append(cur_section) | 
|  | else: | 
|  | comment_lines.append(line) | 
|  |  | 
|  | sections[cur_section] += comment_lines | 
|  | # Fill out the `targets` array by giving all configured targets a copy of the | 
|  | # `target` section we just loaded from the example config | 
|  | configured_targets = [build(known_args)] | 
|  | if "build" in config: | 
|  | if "host" in config["build"]: | 
|  | configured_targets += config["build"]["host"] | 
|  | if "target" in config["build"]: | 
|  | configured_targets += config["build"]["target"] | 
|  | if "target" in config: | 
|  | for target in config["target"]: | 
|  | configured_targets.append(target) | 
|  | for target in configured_targets: | 
|  | targets[target] = sections["target"][:] | 
|  | # 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. | 
|  | # Avoid using quotes unless it's necessary. | 
|  | targets[target][0] = targets[target][0].replace( | 
|  | "x86_64-unknown-linux-gnu", | 
|  | "'{}'".format(target) if "." in target else target, | 
|  | ) | 
|  |  | 
|  | if "profile" not in config: | 
|  | set("profile", "dist", config) | 
|  | configure_file(sections, top_level_keys, targets, config) | 
|  | return section_order, sections, targets | 
|  |  | 
|  |  | 
|  | def is_number(value): | 
|  | try: | 
|  | float(value) | 
|  | return True | 
|  | except ValueError: | 
|  | return False | 
|  |  | 
|  |  | 
|  | # Here we walk through the constructed configuration we have from the parsed | 
|  | # command line arguments. We then apply each piece of configuration by | 
|  | # basically just doing a `sed` to change the various configuration line to what | 
|  | # we've got configure. | 
|  | def to_toml(value): | 
|  | if isinstance(value, bool): | 
|  | if value: | 
|  | return "true" | 
|  | else: | 
|  | return "false" | 
|  | elif isinstance(value, list): | 
|  | return "[" + ", ".join(map(to_toml, value)) + "]" | 
|  | elif isinstance(value, str): | 
|  | # Don't put quotes around numeric values | 
|  | if is_number(value): | 
|  | return value | 
|  | else: | 
|  | return "'" + value + "'" | 
|  | elif isinstance(value, dict): | 
|  | return ( | 
|  | "{" | 
|  | + ", ".join( | 
|  | map( | 
|  | lambda a: "{} = {}".format(to_toml(a[0]), to_toml(a[1])), | 
|  | value.items(), | 
|  | ) | 
|  | ) | 
|  | + "}" | 
|  | ) | 
|  | else: | 
|  | raise RuntimeError("no toml") | 
|  |  | 
|  |  | 
|  | def configure_section(lines, config): | 
|  | for key in config: | 
|  | value = config[key] | 
|  | found = False | 
|  | for i, line in enumerate(lines): | 
|  | if not line.startswith("#" + key + " = "): | 
|  | continue | 
|  | found = True | 
|  | lines[i] = "{} = {}".format(key, to_toml(value)) | 
|  | break | 
|  | if not found: | 
|  | # These are used by rpm, but aren't accepted by x.py. | 
|  | # Give a warning that they're ignored, but not a hard error. | 
|  | if key in ["infodir", "localstatedir"]: | 
|  | print("WARNING: {} will be ignored".format(key)) | 
|  | else: | 
|  | raise RuntimeError("failed to find config line for {}".format(key)) | 
|  |  | 
|  |  | 
|  | def configure_top_level_key(lines, top_level_key, value): | 
|  | for i, line in enumerate(lines): | 
|  | if line.startswith("#" + top_level_key + " = ") or line.startswith( | 
|  | top_level_key + " = " | 
|  | ): | 
|  | lines[i] = "{} = {}".format(top_level_key, to_toml(value)) | 
|  | return | 
|  |  | 
|  | raise RuntimeError("failed to find config line for {}".format(top_level_key)) | 
|  |  | 
|  |  | 
|  | # Modify `sections` to reflect the parsed arguments and example configs. | 
|  | def configure_file(sections, top_level_keys, targets, config): | 
|  | for section_key, section_config in config.items(): | 
|  | if section_key not in sections and section_key not in top_level_keys: | 
|  | raise RuntimeError( | 
|  | "config key {} not in sections or top_level_keys".format(section_key) | 
|  | ) | 
|  | if section_key in top_level_keys: | 
|  | configure_top_level_key(sections[None], section_key, section_config) | 
|  |  | 
|  | elif section_key == "target": | 
|  | for target in section_config: | 
|  | configure_section(targets[target], section_config[target]) | 
|  | else: | 
|  | configure_section(sections[section_key], section_config) | 
|  |  | 
|  |  | 
|  | def write_uncommented(target, f): | 
|  | """Writes each block in 'target' that is not composed entirely of comments to 'f'. | 
|  |  | 
|  | A block is a sequence of non-empty lines separated by empty lines. | 
|  | """ | 
|  | block = [] | 
|  |  | 
|  | def flush(last): | 
|  | # If the block is entirely made of comments, ignore it | 
|  | entire_block_comments = all(ln.startswith("#") or ln == "" for ln in block) | 
|  | if not entire_block_comments and len(block) > 0: | 
|  | for line in block: | 
|  | f.write(line + "\n") | 
|  | # Required to output a newline before the start of a new section | 
|  | if last: | 
|  | f.write("\n") | 
|  | block.clear() | 
|  |  | 
|  | for line in target: | 
|  | block.append(line) | 
|  | if len(line) == 0: | 
|  | flush(last=False) | 
|  |  | 
|  | flush(last=True) | 
|  | return f | 
|  |  | 
|  |  | 
|  | def write_config_toml(writer, section_order, targets, sections): | 
|  | for section in section_order: | 
|  | if section == "target": | 
|  | for target in targets: | 
|  | writer = write_uncommented(targets[target], writer) | 
|  | else: | 
|  | writer = write_uncommented(sections[section], writer) | 
|  |  | 
|  |  | 
|  | def quit_if_file_exists(file): | 
|  | if os.path.isfile(file): | 
|  | msg = "Existing '{}' detected. Exiting".format(file) | 
|  |  | 
|  | # If the output object directory isn't empty, we can get these errors | 
|  | host_objdir = os.environ.get("OBJDIR_ON_HOST") | 
|  | if host_objdir is not None: | 
|  | msg += "\nIs objdir '{}' clean?".format(host_objdir) | 
|  |  | 
|  | err(msg) | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | # If 'bootstrap.toml' already exists, exit the script at this point | 
|  | quit_if_file_exists("bootstrap.toml") | 
|  |  | 
|  | if "GITHUB_ACTIONS" in os.environ: | 
|  | print("::group::Configure the build") | 
|  | p("processing command line") | 
|  | # Parse all known arguments into a configuration structure that reflects the | 
|  | # TOML we're going to write out | 
|  | p("") | 
|  | section_order, sections, targets = parse_args(sys.argv[1:]) | 
|  |  | 
|  | # Now that we've built up our `bootstrap.toml`, write it all out in the same | 
|  | # order that we read it in. | 
|  | p("") | 
|  | p("writing `bootstrap.toml` in current directory") | 
|  | with bootstrap.output("bootstrap.toml") as f: | 
|  | write_config_toml(f, section_order, targets, sections) | 
|  |  | 
|  | with bootstrap.output("Makefile") as f: | 
|  | contents = os.path.join(rust_dir, "src", "bootstrap", "mk", "Makefile.in") | 
|  | contents = open(contents).read() | 
|  | contents = contents.replace("$(CFG_SRC_DIR)", rust_dir + "/") | 
|  | contents = contents.replace("$(CFG_PYTHON)", sys.executable) | 
|  | f.write(contents) | 
|  |  | 
|  | p("") | 
|  | p("run `{} {}/x.py --help`".format(os.path.basename(sys.executable), rust_dir)) | 
|  | if "GITHUB_ACTIONS" in os.environ: | 
|  | print("::endgroup::") |