• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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