Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 37 additions & 5 deletions src/bootstrap/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,21 @@ def get_toml(self, key, section=None):
>>> rb.get_toml('key', 'c') is None
True

A dotted key names a table relative to its enclosing section, so the
full table path must match for the key to be found:

>>> rb.config_toml = 'build.cargo = "/path/to/cargo"'
>>> rb.get_toml('cargo', 'build')
'/path/to/cargo'
>>> rb.get_toml('cargo', 'other') is None
True

A dotted key inside a section composes with that section's name:

>>> rb.config_toml = '[target]\\nx86_64-unknown-linux-gnu.cc = "gcc"'
>>> rb.get_toml('cc', 'target.x86_64-unknown-linux-gnu')
'gcc'

>>> rb.config_toml = 'key1 = true'
>>> rb.get_toml("key1")
'true'
Expand All @@ -940,10 +955,24 @@ def get_toml_static(config_toml, key, section=None):
if section_match is not None:
cur_section = section_match.group(1)

match = re.match(r"^{}\s*=(.*)$".format(key), line)
# Match the key, optionally preceded by a dotted-table prefix (the
# `build.` in `build.cargo`), which names a table relative to the
# current `[section]` and is appended to `cur_section`. This is a
# subset parser, not full TOML: quoted names (e.g. the `'a.b'` that
# configure.py emits for dotted targets) are not matched here.
match = re.match(
r"^\s*(?:([\w.-]+)\.)?{}\s*=(.*)$".format(re.escape(key)), line
)
if match is not None:
value = match.group(1)
if section is None or section == cur_section:
prefix = match.group(1)
if prefix is None:
line_section = cur_section
elif cur_section is None:
line_section = prefix
else:
line_section = "{}.{}".format(cur_section, prefix)
value = match.group(2)
if section is None or section == line_section:
return RustBuild.get_string(value) or value.strip()
return None

Expand All @@ -959,7 +988,10 @@ def program_config(self, program):
"""Return config path for the given program at the given stage

>>> rb = RustBuild()
>>> rb.config_toml = 'rustc = "rustc"\\n'
>>> rb.config_toml = 'build.rustc = "rustc"\\n'
>>> rb.program_config('rustc')
'rustc'
>>> rb.config_toml = '[build]\\nrustc = "rustc"\\n'
>>> rb.program_config('rustc')
'rustc'
>>> rb.config_toml = ''
Expand All @@ -968,7 +1000,7 @@ def program_config(self, program):
... "bin", "cargo")
True
"""
config = self.get_toml(program)
config = self.get_toml(program, "build")
if config:
return os.path.expanduser(config)
return os.path.join(self.bin_root(), "bin", "{}{}".format(program, EXE_SUFFIX))
Expand Down
67 changes: 67 additions & 0 deletions src/bootstrap/bootstrap_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,73 @@ def test_set_codegen_backends(self):
self.assertNotEqual(build.config_toml.find("codegen-backends = ['llvm']"), -1)


class GetTomlDottedKeys(unittest.TestCase):
"""Test that get_toml understands the dotted-table key syntax that a
hand-written bootstrap.toml may use (e.g. `build.cargo = "..."`), and that
it checks the table a key belongs to. See issue #156948."""

def parse(self, config_toml):
return bootstrap.RustBuild(config_toml=config_toml, args=bootstrap.FakeArgs())

def test_dotted_key(self):
build = self.parse('build.cargo = "/path/to/cargo"')
self.assertEqual(build.get_toml("cargo", "build"), "/path/to/cargo")

def test_dotted_key_matches_section_form(self):
dotted = self.parse('build.cargo = "/path/to/cargo"')
section = self.parse('[build]\ncargo = "/path/to/cargo"')
self.assertEqual(
dotted.get_toml("cargo", "build"), section.get_toml("cargo", "build")
)

def test_dotted_key_wrong_section(self):
# A `cargo` key in some other table must not be picked up for `build`.
build = self.parse('[foo]\ncargo = "false"')
self.assertIsNone(build.get_toml("cargo", "build"))

def test_program_config_requires_build_table(self):
# A cargo/rustc key outside [build] must be ignored; program_config
# falls back to the stage0 default path rather than the misplaced value.
wrong = self.parse('[foo]\ncargo = "/wrong/cargo"')
cargo_default = os.path.join(
wrong.bin_root(), "bin", "cargo" + bootstrap.EXE_SUFFIX
)
self.assertEqual(wrong.cargo(), cargo_default)

wrong_rustc = self.parse('[foo]\nrustc = "/wrong/rustc"')
rustc_default = os.path.join(
wrong_rustc.bin_root(), "bin", "rustc" + bootstrap.EXE_SUFFIX
)
self.assertEqual(wrong_rustc.rustc(), rustc_default)

# A dotted key in the build table is honored.
right = self.parse('build.cargo = "/path/to/cargo"')
self.assertEqual(right.cargo(), "/path/to/cargo")

def test_dotted_target_key(self):
build = self.parse('target.x86_64-unknown-linux-gnu.cc = "gcc"')
self.assertEqual(build.get_toml("cc", "target.x86_64-unknown-linux-gnu"), "gcc")

def test_dotted_key_inside_section(self):
# A dotted key nested under a `[section]` header composes with that
# section's name; it is equivalent to the fully top-level dotted form.
nested = self.parse('[target]\nx86_64-unknown-linux-gnu.cc = "gcc"')
self.assertEqual(
nested.get_toml("cc", "target.x86_64-unknown-linux-gnu"), "gcc"
)
top_level = self.parse('target.x86_64-unknown-linux-gnu.cc = "gcc"')
self.assertEqual(
nested.get_toml("cc", "target.x86_64-unknown-linux-gnu"),
top_level.get_toml("cc", "target.x86_64-unknown-linux-gnu"),
)

def test_dotted_prefix_does_not_alias_other_section(self):
# `build.cargo` inside `[foo]` is `foo.build.cargo`, not the top-level
# `[build]` table, so it must not be returned for section "build".
build = self.parse('[foo]\nbuild.cargo = "false"')
self.assertIsNone(build.get_toml("cargo", "build"))


class BuildBootstrap(unittest.TestCase):
"""Test that we generate the appropriate arguments when building bootstrap"""

Expand Down
Loading