about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/bootstrap/bin/main.rs2
-rw-r--r--src/bootstrap/builder.rs6
-rw-r--r--src/bootstrap/builder/tests.rs2
-rw-r--r--src/bootstrap/config.rs25
-rw-r--r--src/bootstrap/flags.rs2
-rw-r--r--src/bootstrap/lib.rs16
-rw-r--r--src/bootstrap/test.rs2
-rw-r--r--src/bootstrap/util.rs109
8 files changed, 138 insertions, 26 deletions
diff --git a/src/bootstrap/bin/main.rs b/src/bootstrap/bin/main.rs
index 6494e5b113b..9c41ab69c8b 100644
--- a/src/bootstrap/bin/main.rs
+++ b/src/bootstrap/bin/main.rs
@@ -11,7 +11,7 @@ use bootstrap::{Build, Config, Subcommand, VERSION};
 
 fn main() {
     let args = env::args().skip(1).collect::<Vec<_>>();
-    let config = Config::parse(&args, false);
+    let config = Config::parse(&args);
 
     // check_version warnings are not printed during setup
     let changelog_suggestion =
diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs
index fc55c8626d9..1903f0baef1 100644
--- a/src/bootstrap/builder.rs
+++ b/src/bootstrap/builder.rs
@@ -883,7 +883,7 @@ impl<'a> Builder<'a> {
     }
 
     pub fn rustdoc_cmd(&self, compiler: Compiler) -> Command {
-        let mut cmd = Command::new(&self.out.join("bootstrap/debug/rustdoc"));
+        let mut cmd = Command::new(&self.bootstrap_out.join("rustdoc"));
         cmd.env("RUSTC_STAGE", compiler.stage.to_string())
             .env("RUSTC_SYSROOT", self.sysroot(compiler))
             // Note that this is *not* the sysroot_libdir because rustdoc must be linked
@@ -1249,7 +1249,7 @@ impl<'a> Builder<'a> {
             .env("RUSTC_STAGE", stage.to_string())
             .env("RUSTC_SYSROOT", &sysroot)
             .env("RUSTC_LIBDIR", &libdir)
-            .env("RUSTDOC", self.out.join("bootstrap/debug/rustdoc"))
+            .env("RUSTDOC", self.bootstrap_out.join("rustdoc"))
             .env(
                 "RUSTDOC_REAL",
                 if cmd == "doc" || cmd == "rustdoc" || (cmd == "test" && want_rustdoc) {
@@ -1263,7 +1263,7 @@ impl<'a> Builder<'a> {
         // Clippy support is a hack and uses the default `cargo-clippy` in path.
         // Don't override RUSTC so that the `cargo-clippy` in path will be run.
         if cmd != "clippy" {
-            cargo.env("RUSTC", self.out.join("bootstrap/debug/rustc"));
+            cargo.env("RUSTC", self.bootstrap_out.join("rustc"));
         }
 
         // Dealing with rpath here is a little special, so let's go into some
diff --git a/src/bootstrap/builder/tests.rs b/src/bootstrap/builder/tests.rs
index bd0be6da0b1..b76bb569852 100644
--- a/src/bootstrap/builder/tests.rs
+++ b/src/bootstrap/builder/tests.rs
@@ -3,7 +3,7 @@ use crate::config::{Config, TargetSelection};
 use std::thread;
 
 fn configure(cmd: &str, host: &[&str], target: &[&str]) -> Config {
-    let mut config = Config::parse(&[cmd.to_owned()], true);
+    let mut config = Config::parse(&[cmd.to_owned()]);
     // don't save toolstates
     config.save_toolstates = None;
     config.dry_run = true;
diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs
index fb387f94086..73a855ae4d7 100644
--- a/src/bootstrap/config.rs
+++ b/src/bootstrap/config.rs
@@ -619,7 +619,7 @@ impl Config {
         config
     }
 
-    pub fn parse(args: &[String], unit_test: bool) -> Config {
+    pub fn parse(args: &[String]) -> Config {
         let flags = Flags::parse(&args);
 
         let mut config = Config::default_opts();
@@ -681,26 +681,13 @@ impl Config {
 
         let build = toml.build.unwrap_or_default();
 
-        set(&mut config.out, build.build_dir.map(String::into));
+        set(&mut config.initial_rustc, build.rustc.map(PathBuf::from));
+        set(&mut config.out, build.build_dir.map(PathBuf::from));
         // NOTE: Bootstrap spawns various commands with different working directories.
         // To avoid writing to random places on the file system, `config.out` needs to be an absolute path.
-
-        // FIXME: using `canonicalize()` makes this a lot more complicated than it needs to be -
-        // if/when `std::path::absolute` lands, we should use that instead.
-
-        // HACK: in tests, we override the build directory manually.
-        // Avoid creating a directory we won't actually need.
-        // (The original motivation for this is that CI uses read-only directories.)
-        if !config.out.is_absolute() && !unit_test {
-            // canonicalize() gives a hard error if the directory doesn't exist
-            t!(
-                fs::create_dir_all(&config.out),
-                format!("failed to create build dir: {}", config.out.display())
-            );
-            config.out = t!(
-                config.out.canonicalize(),
-                format!("failed to canonicalize {}", config.out.display())
-            );
+        if !config.out.is_absolute() {
+            // `canonicalize` requires the path to already exist. Use our vendored copy of `absolute` instead.
+            config.out = crate::util::absolute(&config.out);
         }
 
         if config.dry_run {
diff --git a/src/bootstrap/flags.rs b/src/bootstrap/flags.rs
index b12ba1e89e0..1a4e6a96888 100644
--- a/src/bootstrap/flags.rs
+++ b/src/bootstrap/flags.rs
@@ -343,7 +343,7 @@ To learn more about a subcommand, run `./x.py <subcommand> -h`",
 
             // All subcommands except `clean` can have an optional "Available paths" section
             if verbose {
-                let config = Config::parse(&["build".to_string()], false);
+                let config = Config::parse(&["build".to_string()]);
                 let build = Build::new(config);
 
                 let maybe_rules_help = Builder::get_help(&build, subcommand.as_str());
diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs
index c2780a232ed..2ae63858ff6 100644
--- a/src/bootstrap/lib.rs
+++ b/src/bootstrap/lib.rs
@@ -261,6 +261,7 @@ pub struct Build {
     // Properties derived from the above configuration
     src: PathBuf,
     out: PathBuf,
+    bootstrap_out: PathBuf,
     rust_info: channel::GitInfo,
     cargo_info: channel::GitInfo,
     rls_info: channel::GitInfo,
@@ -435,6 +436,20 @@ impl Build {
             .expect("failed to read src/version");
         let version = version.trim();
 
+        let bootstrap_out = if std::env::var("BOOTSTRAP_PYTHON").is_ok() {
+            out.join("bootstrap").join("debug")
+        } else {
+            let workspace_target_dir = std::env::var("CARGO_TARGET_DIR")
+                .map(PathBuf::from)
+                .unwrap_or_else(|_| src.join("target"));
+            let bootstrap_out = workspace_target_dir.join("debug");
+            if !bootstrap_out.join("rustc").exists() {
+                // this restriction can be lifted whenever https://github.com/rust-lang/rfcs/pull/3028 is implemented
+                panic!("run `cargo build --bins` before `cargo run`")
+            }
+            bootstrap_out
+        };
+
         let mut build = Build {
             initial_rustc: config.initial_rustc.clone(),
             initial_cargo: config.initial_cargo.clone(),
@@ -453,6 +468,7 @@ impl Build {
             version: version.to_string(),
             src,
             out,
+            bootstrap_out,
 
             rust_info,
             cargo_info,
diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs
index 1665affcf8f..58b73ebed50 100644
--- a/src/bootstrap/test.rs
+++ b/src/bootstrap/test.rs
@@ -730,7 +730,7 @@ impl Step for RustdocTheme {
     }
 
     fn run(self, builder: &Builder<'_>) {
-        let rustdoc = builder.out.join("bootstrap/debug/rustdoc");
+        let rustdoc = builder.bootstrap_out.join("rustdoc");
         let mut cmd = builder.tool_cmd(Tool::RustdocTheme);
         cmd.arg(rustdoc.to_str().unwrap())
             .arg(builder.src.join("src/librustdoc/html/static/css/themes").to_str().unwrap())
diff --git a/src/bootstrap/util.rs b/src/bootstrap/util.rs
index 8e770d4d57f..30d9665dd0f 100644
--- a/src/bootstrap/util.rs
+++ b/src/bootstrap/util.rs
@@ -440,3 +440,112 @@ fn fail(s: &str) -> ! {
     println!("\n\n{}\n\n", s);
     std::process::exit(1);
 }
+
+/// Copied from `std::path::absolute` until it stabilizes.
+///
+/// FIXME: this shouldn't exist.
+pub(crate) fn absolute(path: &Path) -> PathBuf {
+    if path.as_os_str().is_empty() {
+        panic!("can't make empty path absolute");
+    }
+    #[cfg(unix)]
+    {
+        t!(absolute_unix(path), format!("could not make path absolute: {}", path.display()))
+    }
+    #[cfg(windows)]
+    {
+        t!(absolute_windows(path), format!("could not make path absolute: {}", path.display()))
+    }
+    #[cfg(not(any(unix, windows)))]
+    {
+        println!("warning: bootstrap is not supported on non-unix platforms");
+        t!(std::fs::canonicalize(t!(std::env::current_dir()))).join(path)
+    }
+}
+
+#[cfg(unix)]
+/// Make a POSIX path absolute without changing its semantics.
+fn absolute_unix(path: &Path) -> io::Result<PathBuf> {
+    // This is mostly a wrapper around collecting `Path::components`, with
+    // exceptions made where this conflicts with the POSIX specification.
+    // See 4.13 Pathname Resolution, IEEE Std 1003.1-2017
+    // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13
+
+    use std::os::unix::prelude::OsStrExt;
+    let mut components = path.components();
+    let path_os = path.as_os_str().as_bytes();
+
+    let mut normalized = if path.is_absolute() {
+        // "If a pathname begins with two successive <slash> characters, the
+        // first component following the leading <slash> characters may be
+        // interpreted in an implementation-defined manner, although more than
+        // two leading <slash> characters shall be treated as a single <slash>
+        // character."
+        if path_os.starts_with(b"//") && !path_os.starts_with(b"///") {
+            components.next();
+            PathBuf::from("//")
+        } else {
+            PathBuf::new()
+        }
+    } else {
+        env::current_dir()?
+    };
+    normalized.extend(components);
+
+    // "Interfaces using pathname resolution may specify additional constraints
+    // when a pathname that does not name an existing directory contains at
+    // least one non- <slash> character and contains one or more trailing
+    // <slash> characters".
+    // A trailing <slash> is also meaningful if "a symbolic link is
+    // encountered during pathname resolution".
+
+    if path_os.ends_with(b"/") {
+        normalized.push("");
+    }
+
+    Ok(normalized)
+}
+
+#[cfg(windows)]
+fn absolute_windows(path: &std::path::Path) -> std::io::Result<std::path::PathBuf> {
+    use std::ffi::OsString;
+    use std::io::Error;
+    use std::os::windows::ffi::{OsStrExt, OsStringExt};
+    use std::ptr::null_mut;
+    #[link(name = "kernel32")]
+    extern "system" {
+        fn GetFullPathNameW(
+            lpFileName: *const u16,
+            nBufferLength: u32,
+            lpBuffer: *mut u16,
+            lpFilePart: *mut *const u16,
+        ) -> u32;
+    }
+
+    unsafe {
+        // encode the path as UTF-16
+        let path: Vec<u16> = path.as_os_str().encode_wide().chain([0]).collect();
+        let mut buffer = Vec::new();
+        // Loop until either success or failure.
+        loop {
+            // Try to get the absolute path
+            let len = GetFullPathNameW(
+                path.as_ptr(),
+                buffer.len().try_into().unwrap(),
+                buffer.as_mut_ptr(),
+                null_mut(),
+            );
+            match len as usize {
+                // Failure
+                0 => return Err(Error::last_os_error()),
+                // Buffer is too small, resize.
+                len if len > buffer.len() => buffer.resize(len, 0),
+                // Success!
+                len => {
+                    buffer.truncate(len);
+                    return Ok(OsString::from_wide(&buffer).into());
+                }
+            }
+        }
+    }
+}