Skip to content

Commit c5820b5

Browse files
committed
Test cargo arguments passed by bootstrap.py
This moves a lot of code around, but the logic itself is not too terribly complicated. - Move almost all logic in `def bootstrap` to the `RustBuild` class, to avoid mixing setting configuration with running commands - Update various doctests to the new (more complete) RustBuild config. In particular, don't pretend that `bin_root` supports `build` being unset. - Change `parse_args` not to use a global, to allow testing it - Set BUILD_DIR appropriately so bootstrap.py doesn't panic because cargo isn't found
1 parent 3508cde commit c5820b5

File tree

3 files changed

+118
-88
lines changed

3 files changed

+118
-88
lines changed

src/bootstrap/bootstrap.py

+64-61
Original file line numberDiff line numberDiff line change
@@ -458,23 +458,52 @@ def unpack_component(download_info):
458458
verbose=download_info.verbose,
459459
)
460460

461-
class RustBuild(object):
462-
"""Provide all the methods required to build Rust"""
461+
class FakeArgs:
462+
"""Used for unit tests to avoid updating all call sites"""
463463
def __init__(self):
464-
self.checksums_sha256 = {}
465-
self.stage0_compiler = None
466-
self.download_url = ''
467464
self.build = ''
468465
self.build_dir = ''
469466
self.clean = False
470-
self.config_toml = ''
471-
self.rust_root = ''
472-
self.use_locked_deps = False
473-
self.use_vendored_sources = False
474467
self.verbose = False
468+
self.json_output = False
469+
470+
class RustBuild(object):
471+
"""Provide all the methods required to build Rust"""
472+
def __init__(self, config_toml="", args=FakeArgs()):
475473
self.git_version = None
476474
self.nix_deps_dir = None
477475
self._should_fix_bins_and_dylibs = None
476+
self.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
477+
478+
self.config_toml = config_toml
479+
480+
self.verbose = args.verbose != 0
481+
self.clean = args.clean
482+
self.json_output = args.json_output
483+
484+
profile = self.get_toml('profile')
485+
if profile is not None:
486+
include_file = 'config.{}.toml'.format(profile)
487+
include_dir = os.path.join(self.rust_root, 'src', 'bootstrap', 'defaults')
488+
include_path = os.path.join(include_dir, include_file)
489+
# HACK: This works because `self.get_toml()` returns the first match it finds for a
490+
# specific key, so appending our defaults at the end allows the user to override them
491+
with open(include_path) as included_toml:
492+
self.config_toml += os.linesep + included_toml.read()
493+
494+
self.use_vendored_sources = self.get_toml('vendor', 'build') == 'true'
495+
self.use_locked_deps = self.get_toml('locked-deps', 'build') == 'true'
496+
497+
build_dir = args.build_dir or self.get_toml('build-dir', 'build') or 'build'
498+
self.build_dir = os.path.abspath(build_dir)
499+
500+
with open(os.path.join(self.rust_root, "src", "stage0.json")) as f:
501+
data = json.load(f)
502+
self.checksums_sha256 = data["checksums_sha256"]
503+
self.stage0_compiler = Stage0Toolchain(data["compiler"])
504+
self.download_url = os.getenv("RUSTUP_DIST_SERVER") or data["config"]["dist_server"]
505+
506+
self.build = args.build or self.build_triple()
478507

479508
def download_toolchain(self):
480509
"""Fetch the build system for Rust, written in Rust
@@ -704,9 +733,10 @@ def rustc_stamp(self):
704733
"""Return the path for .rustc-stamp at the given stage
705734
706735
>>> rb = RustBuild()
736+
>>> rb.build = "host"
707737
>>> rb.build_dir = "build"
708-
>>> rb.rustc_stamp() == os.path.join("build", "stage0", ".rustc-stamp")
709-
True
738+
>>> expected = os.path.join("build", "host", "stage0", ".rustc-stamp")
739+
>>> assert rb.rustc_stamp() == expected, rb.rustc_stamp()
710740
"""
711741
return os.path.join(self.bin_root(), '.rustc-stamp')
712742

@@ -721,15 +751,9 @@ def bin_root(self):
721751
"""Return the binary root directory for the given stage
722752
723753
>>> rb = RustBuild()
724-
>>> rb.build_dir = "build"
725-
>>> rb.bin_root() == os.path.join("build", "stage0")
726-
True
727-
728-
When the 'build' property is given should be a nested directory:
729-
730754
>>> rb.build = "devel"
731-
>>> rb.bin_root() == os.path.join("build", "devel", "stage0")
732-
True
755+
>>> expected = os.path.abspath(os.path.join("build", "devel", "stage0"))
756+
>>> assert rb.bin_root() == expected, rb.bin_root()
733757
"""
734758
subdir = "stage0"
735759
return os.path.join(self.build_dir, self.build, subdir)
@@ -842,6 +866,16 @@ def build_bootstrap(self, color, warnings, verbose_count):
842866
print("::group::Building bootstrap")
843867
else:
844868
print("Building bootstrap", file=sys.stderr)
869+
870+
args = self.build_bootstrap_cmd(env, color, warnings, verbose_count)
871+
# Run this from the source directory so cargo finds .cargo/config
872+
run(args, env=env, verbose=self.verbose, cwd=self.rust_root)
873+
874+
if "GITHUB_ACTIONS" in env:
875+
print("::endgroup::")
876+
877+
def build_bootstrap_cmd(self, env, color, warnings, verbose_count):
878+
"""For tests."""
845879
build_dir = os.path.join(self.build_dir, "bootstrap")
846880
if self.clean and os.path.exists(build_dir):
847881
shutil.rmtree(build_dir)
@@ -927,11 +961,7 @@ def build_bootstrap(self, color, warnings, verbose_count):
927961
except KeyError:
928962
pass
929963

930-
# Run this from the source directory so cargo finds .cargo/config
931-
run(args, env=env, verbose=self.verbose, cwd=self.rust_root)
932-
933-
if "GITHUB_ACTIONS" in env:
934-
print("::endgroup::")
964+
return args
935965

936966
def build_triple(self):
937967
"""Build triple as in LLVM
@@ -981,7 +1011,7 @@ def check_vendored_status(self):
9811011
if os.path.exists(cargo_dir):
9821012
shutil.rmtree(cargo_dir)
9831013

984-
def parse_args():
1014+
def parse_args(args):
9851015
"""Parse the command line arguments that the python script needs."""
9861016
parser = argparse.ArgumentParser(add_help=False)
9871017
parser.add_argument('-h', '--help', action='store_true')
@@ -994,16 +1024,11 @@ def parse_args():
9941024
parser.add_argument('--warnings', choices=['deny', 'warn', 'default'], default='default')
9951025
parser.add_argument('-v', '--verbose', action='count', default=0)
9961026

997-
return parser.parse_known_args(sys.argv)[0]
1027+
return parser.parse_known_args(args)[0]
9981028

9991029
def bootstrap(args):
10001030
"""Configure, fetch, build and run the initial bootstrap"""
1001-
# Configure initial bootstrap
1002-
build = RustBuild()
1003-
build.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
1004-
build.verbose = args.verbose != 0
1005-
build.clean = args.clean
1006-
build.json_output = args.json_output
1031+
rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
10071032

10081033
# Read from `--config`, then `RUST_BOOTSTRAP_CONFIG`, then `./config.toml`,
10091034
# then `config.toml` in the root directory.
@@ -1012,45 +1037,23 @@ def bootstrap(args):
10121037
if using_default_path:
10131038
toml_path = 'config.toml'
10141039
if not os.path.exists(toml_path):
1015-
toml_path = os.path.join(build.rust_root, toml_path)
1040+
toml_path = os.path.join(rust_root, toml_path)
10161041

10171042
# Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path,
10181043
# but not if `config.toml` hasn't been created.
10191044
if not using_default_path or os.path.exists(toml_path):
10201045
with open(toml_path) as config:
1021-
build.config_toml = config.read()
1022-
1023-
profile = build.get_toml('profile')
1024-
if profile is not None:
1025-
include_file = 'config.{}.toml'.format(profile)
1026-
include_dir = os.path.join(build.rust_root, 'src', 'bootstrap', 'defaults')
1027-
include_path = os.path.join(include_dir, include_file)
1028-
# HACK: This works because `build.get_toml()` returns the first match it finds for a
1029-
# specific key, so appending our defaults at the end allows the user to override them
1030-
with open(include_path) as included_toml:
1031-
build.config_toml += os.linesep + included_toml.read()
1046+
config_toml = config.read()
1047+
1048+
# Configure initial bootstrap
1049+
build = RustBuild(config_toml, args)
1050+
build.check_vendored_status()
10321051

10331052
verbose_count = args.verbose
10341053
config_verbose_count = build.get_toml('verbose', 'build')
10351054
if config_verbose_count is not None:
10361055
verbose_count = max(args.verbose, int(config_verbose_count))
10371056

1038-
build.use_vendored_sources = build.get_toml('vendor', 'build') == 'true'
1039-
build.use_locked_deps = build.get_toml('locked-deps', 'build') == 'true'
1040-
1041-
build.check_vendored_status()
1042-
1043-
build_dir = args.build_dir or build.get_toml('build-dir', 'build') or 'build'
1044-
build.build_dir = os.path.abspath(build_dir)
1045-
1046-
with open(os.path.join(build.rust_root, "src", "stage0.json")) as f:
1047-
data = json.load(f)
1048-
build.checksums_sha256 = data["checksums_sha256"]
1049-
build.stage0_compiler = Stage0Toolchain(data["compiler"])
1050-
build.download_url = os.getenv("RUSTUP_DIST_SERVER") or data["config"]["dist_server"]
1051-
1052-
build.build = args.build or build.build_triple()
1053-
10541057
if not os.path.exists(build.build_dir):
10551058
os.makedirs(build.build_dir)
10561059

@@ -1077,7 +1080,7 @@ def main():
10771080
if len(sys.argv) > 1 and sys.argv[1] == 'help':
10781081
sys.argv[1] = '-h'
10791082

1080-
args = parse_args()
1083+
args = parse_args(sys.argv)
10811084
help_triggered = args.help or len(sys.argv) == 1
10821085

10831086
# If the user is asking for help, let them know that the whole download-and-build

src/bootstrap/bootstrap_test.py

+52-24
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,23 @@
1515
import bootstrap
1616
import configure
1717

18+
def serialize_and_parse(args):
19+
from io import StringIO
20+
21+
section_order, sections, targets = configure.parse_args(args)
22+
buffer = StringIO()
23+
configure.write_config_toml(buffer, section_order, targets, sections)
24+
build = bootstrap.RustBuild()
25+
build.config_toml = buffer.getvalue()
26+
27+
try:
28+
import tomllib
29+
# Verify this is actually valid TOML.
30+
tomllib.loads(build.config_toml)
31+
except ImportError:
32+
print("warning: skipping TOML validation, need at least python 3.11", file=sys.stderr)
33+
return build
34+
1835

1936
class VerifyTestCase(unittest.TestCase):
2037
"""Test Case for verify"""
@@ -79,46 +96,57 @@ def test_same_dates(self):
7996

8097
class GenerateAndParseConfig(unittest.TestCase):
8198
"""Test that we can serialize and deserialize a config.toml file"""
82-
def serialize_and_parse(self, args):
83-
from io import StringIO
84-
85-
section_order, sections, targets = configure.parse_args(args)
86-
buffer = StringIO()
87-
configure.write_config_toml(buffer, section_order, targets, sections)
88-
build = bootstrap.RustBuild()
89-
build.config_toml = buffer.getvalue()
90-
91-
try:
92-
import tomllib
93-
# Verify this is actually valid TOML.
94-
tomllib.loads(build.config_toml)
95-
except ImportError:
96-
print("warning: skipping TOML validation, need at least python 3.11", file=sys.stderr)
97-
return build
98-
9999
def test_no_args(self):
100-
build = self.serialize_and_parse([])
100+
build = serialize_and_parse([])
101101
self.assertEqual(build.get_toml("changelog-seen"), '2')
102102
self.assertEqual(build.get_toml("profile"), 'user')
103103
self.assertIsNone(build.get_toml("llvm.download-ci-llvm"))
104104

105105
def test_set_section(self):
106-
build = self.serialize_and_parse(["--set", "llvm.download-ci-llvm"])
106+
build = serialize_and_parse(["--set", "llvm.download-ci-llvm"])
107107
self.assertEqual(build.get_toml("download-ci-llvm", section="llvm"), 'true')
108108

109109
def test_set_target(self):
110-
build = self.serialize_and_parse(["--set", "target.x86_64-unknown-linux-gnu.cc=gcc"])
110+
build = serialize_and_parse(["--set", "target.x86_64-unknown-linux-gnu.cc=gcc"])
111111
self.assertEqual(build.get_toml("cc", section="target.x86_64-unknown-linux-gnu"), 'gcc')
112112

113113
def test_set_top_level(self):
114-
build = self.serialize_and_parse(["--set", "profile=compiler"])
114+
build = serialize_and_parse(["--set", "profile=compiler"])
115115
self.assertEqual(build.get_toml("profile"), 'compiler')
116116

117117
def test_set_codegen_backends(self):
118-
build = self.serialize_and_parse(["--set", "rust.codegen-backends=cranelift"])
118+
build = serialize_and_parse(["--set", "rust.codegen-backends=cranelift"])
119119
self.assertNotEqual(build.config_toml.find("codegen-backends = ['cranelift']"), -1)
120-
build = self.serialize_and_parse(["--set", "rust.codegen-backends=cranelift,llvm"])
120+
build = serialize_and_parse(["--set", "rust.codegen-backends=cranelift,llvm"])
121121
self.assertNotEqual(build.config_toml.find("codegen-backends = ['cranelift', 'llvm']"), -1)
122-
build = self.serialize_and_parse(["--enable-full-tools"])
122+
build = serialize_and_parse(["--enable-full-tools"])
123123
self.assertNotEqual(build.config_toml.find("codegen-backends = ['llvm']"), -1)
124124

125+
126+
class BuildBootstrap(unittest.TestCase):
127+
"""Test that we generate the appropriate arguments when building bootstrap"""
128+
129+
def build_args(self, configure_args=[], args=[], env={}):
130+
env = env.copy()
131+
env["PATH"] = os.environ["PATH"]
132+
133+
build = serialize_and_parse(configure_args)
134+
build.build_dir = os.environ["BUILD_DIR"]
135+
parsed = bootstrap.parse_args(args)
136+
return build.build_bootstrap_cmd(env, parsed.color, parsed.warnings, parsed.verbose), env
137+
138+
def test_cargoflags(self):
139+
args, _ = self.build_args(env={"CARGOFLAGS": "--timings"})
140+
self.assertTrue("--timings" in args)
141+
142+
def test_warnings(self):
143+
for toml_warnings in ['false', 'true', None]:
144+
configure_args = []
145+
if toml_warnings is not None:
146+
configure_args = ["--set", "rust.deny-warnings=" + toml_warnings]
147+
148+
_, env = self.build_args(configure_args, args=["--warnings=warn"])
149+
self.assertFalse("-Dwarnings" in env["RUSTFLAGS"])
150+
151+
_, env = self.build_args(configure_args, args=["--warnings=deny"])
152+
self.assertTrue("-Dwarnings" in env["RUSTFLAGS"])

src/bootstrap/test.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -2665,9 +2665,8 @@ impl Step for Bootstrap {
26652665
fn run(self, builder: &Builder<'_>) {
26662666
let mut check_bootstrap = Command::new(&builder.python());
26672667
check_bootstrap
2668-
.arg("-m")
2669-
.arg("unittest")
2670-
.arg("bootstrap_test.py")
2668+
.args(["-m", "unittest", "bootstrap_test.py"])
2669+
.env("BUILD_DIR", &builder.out)
26712670
.current_dir(builder.src.join("src/bootstrap/"))
26722671
.args(builder.config.test_args());
26732672
try_run(builder, &mut check_bootstrap).unwrap();

0 commit comments

Comments
 (0)