bootstrap/core/build_steps/
install.rs

1//! Implementation of the install aspects of the compiler.
2//!
3//! This module is responsible for installing the standard library,
4//! compiler, and documentation.
5
6use std::path::{Component, Path, PathBuf};
7use std::{env, fs};
8
9use crate::core::build_steps::dist;
10use crate::core::builder::{Builder, RunConfig, ShouldRun, Step};
11use crate::core::config::{Config, TargetSelection};
12use crate::utils::exec::command;
13use crate::utils::helpers::t;
14use crate::utils::tarball::GeneratedTarball;
15use crate::{Compiler, Kind};
16
17#[cfg(target_os = "illumos")]
18const SHELL: &str = "bash";
19#[cfg(not(target_os = "illumos"))]
20const SHELL: &str = "sh";
21
22/// We have to run a few shell scripts, which choke quite a bit on both `\`
23/// characters and on `C:\` paths, so normalize both of them away.
24fn sanitize_sh(path: &Path, is_cygwin: bool) -> String {
25    let path = path.to_str().unwrap().replace('\\', "/");
26    return if is_cygwin { path } else { change_drive(unc_to_lfs(&path)).unwrap_or(path) };
27
28    fn unc_to_lfs(s: &str) -> &str {
29        s.strip_prefix("//?/").unwrap_or(s)
30    }
31
32    fn change_drive(s: &str) -> Option<String> {
33        let mut ch = s.chars();
34        let drive = ch.next().unwrap_or('C');
35        if ch.next() != Some(':') {
36            return None;
37        }
38        if ch.next() != Some('/') {
39            return None;
40        }
41        // The prefix for Windows drives in Cygwin/MSYS2 is configurable, but
42        // /proc/cygdrive is available regardless of configuration since 1.7.33
43        Some(format!("/proc/cygdrive/{}/{}", drive, &s[drive.len_utf8() + 2..]))
44    }
45}
46
47fn is_dir_writable_for_user(dir: &Path) -> bool {
48    let tmp = dir.join(".tmp");
49    match fs::create_dir_all(&tmp) {
50        Ok(_) => {
51            fs::remove_dir_all(tmp).unwrap();
52            true
53        }
54        Err(e) => {
55            if e.kind() == std::io::ErrorKind::PermissionDenied {
56                false
57            } else {
58                panic!("Failed the write access check for the current user. {e}");
59            }
60        }
61    }
62}
63
64fn install_sh(
65    builder: &Builder<'_>,
66    package: &str,
67    stage: u32,
68    host: Option<TargetSelection>,
69    tarball: &GeneratedTarball,
70) {
71    let _guard = builder.msg(
72        Kind::Install,
73        package,
74        None,
75        (host.unwrap_or(builder.host_target), stage),
76        host,
77    );
78
79    let prefix = default_path(&builder.config.prefix, "/usr/local");
80    let sysconfdir = prefix.join(default_path(&builder.config.sysconfdir, "/etc"));
81    let destdir_env = env::var_os("DESTDIR").map(PathBuf::from);
82    let is_cygwin = builder.config.host_target.is_cygwin();
83
84    // Sanity checks on the write access of user.
85    //
86    // When the `DESTDIR` environment variable is present, there is no point to
87    // check write access for `prefix` and `sysconfdir` individually, as they
88    // are combined with the path from the `DESTDIR` environment variable. In
89    // this case, we only need to check the `DESTDIR` path, disregarding the
90    // `prefix` and `sysconfdir` paths.
91    if let Some(destdir) = &destdir_env {
92        assert!(is_dir_writable_for_user(destdir), "User doesn't have write access on DESTDIR.");
93    } else {
94        assert!(
95            is_dir_writable_for_user(&prefix),
96            "User doesn't have write access on `install.prefix` path in the `bootstrap.toml`.",
97        );
98        assert!(
99            is_dir_writable_for_user(&sysconfdir),
100            "User doesn't have write access on `install.sysconfdir` path in `bootstrap.toml`."
101        );
102    }
103
104    let datadir = prefix.join(default_path(&builder.config.datadir, "share"));
105    let docdir = prefix.join(default_path(&builder.config.docdir, &format!("share/doc/{package}")));
106    let mandir = prefix.join(default_path(&builder.config.mandir, "share/man"));
107    let libdir = prefix.join(default_path(&builder.config.libdir, "lib"));
108    let bindir = prefix.join(&builder.config.bindir); // Default in config.rs
109
110    let empty_dir = builder.out.join("tmp/empty_dir");
111    t!(fs::create_dir_all(&empty_dir));
112
113    let mut cmd = command(SHELL);
114    cmd.current_dir(&empty_dir)
115        .arg(sanitize_sh(&tarball.decompressed_output().join("install.sh"), is_cygwin))
116        .arg(format!("--prefix={}", prepare_dir(&destdir_env, prefix, is_cygwin)))
117        .arg(format!("--sysconfdir={}", prepare_dir(&destdir_env, sysconfdir, is_cygwin)))
118        .arg(format!("--datadir={}", prepare_dir(&destdir_env, datadir, is_cygwin)))
119        .arg(format!("--docdir={}", prepare_dir(&destdir_env, docdir, is_cygwin)))
120        .arg(format!("--bindir={}", prepare_dir(&destdir_env, bindir, is_cygwin)))
121        .arg(format!("--libdir={}", prepare_dir(&destdir_env, libdir, is_cygwin)))
122        .arg(format!("--mandir={}", prepare_dir(&destdir_env, mandir, is_cygwin)))
123        .arg("--disable-ldconfig");
124    cmd.run(builder);
125    t!(fs::remove_dir_all(&empty_dir));
126}
127
128fn default_path(config: &Option<PathBuf>, default: &str) -> PathBuf {
129    config.as_ref().cloned().unwrap_or_else(|| PathBuf::from(default))
130}
131
132fn prepare_dir(destdir_env: &Option<PathBuf>, mut path: PathBuf, is_cygwin: bool) -> String {
133    // The DESTDIR environment variable is a standard way to install software in a subdirectory
134    // while keeping the original directory structure, even if the prefix or other directories
135    // contain absolute paths.
136    //
137    // More information on the environment variable is available here:
138    // https://www.gnu.org/prep/standards/html_node/DESTDIR.html
139    if let Some(destdir) = destdir_env {
140        let without_destdir = path.clone();
141        path.clone_from(destdir);
142        // Custom .join() which ignores disk roots.
143        for part in without_destdir.components() {
144            if let Component::Normal(s) = part {
145                path.push(s)
146            }
147        }
148    }
149
150    // The installation command is not executed from the current directory, but from a temporary
151    // directory. To prevent relative paths from breaking this converts relative paths to absolute
152    // paths. std::fs::canonicalize is not used as that requires the path to actually be present.
153    if path.is_relative() {
154        path = std::env::current_dir().expect("failed to get the current directory").join(path);
155        assert!(path.is_absolute(), "could not make the path relative");
156    }
157
158    sanitize_sh(&path, is_cygwin)
159}
160
161macro_rules! install {
162    (($sel:ident, $builder:ident, $_config:ident),
163       $($name:ident,
164       $condition_name: ident = $path_or_alias: literal,
165       $default_cond:expr,
166       only_hosts: $only_hosts:expr,
167       $run_item:block $(, $c:ident)*;)+) => {
168        $(
169            #[derive(Debug, Clone, Hash, PartialEq, Eq)]
170        pub struct $name {
171            pub compiler: Compiler,
172            pub target: TargetSelection,
173        }
174
175        impl $name {
176            #[allow(dead_code)]
177            fn should_build(config: &Config) -> bool {
178                config.extended && config.tools.as_ref()
179                    .map_or(true, |t| t.contains($path_or_alias))
180            }
181        }
182
183        impl Step for $name {
184            type Output = ();
185            const DEFAULT: bool = true;
186            const ONLY_HOSTS: bool = $only_hosts;
187            $(const $c: bool = true;)*
188
189            fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
190                let $_config = &run.builder.config;
191                run.$condition_name($path_or_alias).default_condition($default_cond)
192            }
193
194            fn make_run(run: RunConfig<'_>) {
195                run.builder.ensure($name {
196                    compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.host_target),
197                    target: run.target,
198                });
199            }
200
201            fn run($sel, $builder: &Builder<'_>) {
202                $run_item
203            }
204        })+
205    }
206}
207
208install!((self, builder, _config),
209    Docs, path = "src/doc", _config.docs, only_hosts: false, {
210        let tarball = builder.ensure(dist::Docs { host: self.target }).expect("missing docs");
211        install_sh(builder, "docs", self.compiler.stage, Some(self.target), &tarball);
212    };
213    Std, path = "library/std", true, only_hosts: false, {
214        // `expect` should be safe, only None when host != build, but this
215        // only runs when host == build
216        let tarball = builder.ensure(dist::Std {
217            compiler: self.compiler,
218            target: self.target
219        }).expect("missing std");
220        install_sh(builder, "std", self.compiler.stage, Some(self.target), &tarball);
221    };
222    Cargo, alias = "cargo", Self::should_build(_config), only_hosts: true, {
223        let tarball = builder
224            .ensure(dist::Cargo { build_compiler: self.compiler, target: self.target })
225            .expect("missing cargo");
226        install_sh(builder, "cargo", self.compiler.stage, Some(self.target), &tarball);
227    };
228    RustAnalyzer, alias = "rust-analyzer", Self::should_build(_config), only_hosts: true, {
229        if let Some(tarball) =
230            builder.ensure(dist::RustAnalyzer { build_compiler: self.compiler, target: self.target })
231        {
232            install_sh(builder, "rust-analyzer", self.compiler.stage, Some(self.target), &tarball);
233        } else {
234            builder.info(
235                &format!("skipping Install rust-analyzer stage{} ({})", self.compiler.stage, self.target),
236            );
237        }
238    };
239    Clippy, alias = "clippy", Self::should_build(_config), only_hosts: true, {
240        let tarball = builder
241            .ensure(dist::Clippy { build_compiler: self.compiler, target: self.target })
242            .expect("missing clippy");
243        install_sh(builder, "clippy", self.compiler.stage, Some(self.target), &tarball);
244    };
245    Miri, alias = "miri", Self::should_build(_config), only_hosts: true, {
246        if let Some(tarball) = builder.ensure(dist::Miri { build_compiler: self.compiler, target: self.target }) {
247            install_sh(builder, "miri", self.compiler.stage, Some(self.target), &tarball);
248        } else {
249            // Miri is only available on nightly
250            builder.info(
251                &format!("skipping Install miri stage{} ({})", self.compiler.stage, self.target),
252            );
253        }
254    };
255    LlvmTools, alias = "llvm-tools", _config.llvm_tools_enabled && _config.llvm_enabled(_config.host_target), only_hosts: true, {
256        if let Some(tarball) = builder.ensure(dist::LlvmTools { target: self.target }) {
257            install_sh(builder, "llvm-tools", self.compiler.stage, Some(self.target), &tarball);
258        } else {
259            builder.info(
260                &format!("skipping llvm-tools stage{} ({}): external LLVM", self.compiler.stage, self.target),
261            );
262        }
263    };
264    Rustfmt, alias = "rustfmt", Self::should_build(_config), only_hosts: true, {
265        if let Some(tarball) = builder.ensure(dist::Rustfmt {
266            build_compiler: self.compiler,
267            target: self.target
268        }) {
269            install_sh(builder, "rustfmt", self.compiler.stage, Some(self.target), &tarball);
270        } else {
271            builder.info(
272                &format!("skipping Install Rustfmt stage{} ({})", self.compiler.stage, self.target),
273            );
274        }
275    };
276    Rustc, path = "compiler/rustc", true, only_hosts: true, {
277        let tarball = builder.ensure(dist::Rustc {
278            compiler: builder.compiler(builder.top_stage, self.target),
279        });
280        install_sh(builder, "rustc", self.compiler.stage, Some(self.target), &tarball);
281    };
282    RustcCodegenCranelift, alias = "rustc-codegen-cranelift", Self::should_build(_config), only_hosts: true, {
283        if let Some(tarball) = builder.ensure(dist::CraneliftCodegenBackend {
284            build_compiler: self.compiler,
285            target: self.target
286        }) {
287            install_sh(builder, "rustc-codegen-cranelift", self.compiler.stage, Some(self.target), &tarball);
288        } else {
289            builder.info(
290                &format!("skipping Install CodegenBackend(\"cranelift\") stage{} ({})",
291                         self.compiler.stage, self.target),
292            );
293        }
294    };
295    LlvmBitcodeLinker, alias = "llvm-bitcode-linker", Self::should_build(_config), only_hosts: true, {
296        if let Some(tarball) = builder.ensure(dist::LlvmBitcodeLinker { build_compiler: self.compiler, target: self.target }) {
297            install_sh(builder, "llvm-bitcode-linker", self.compiler.stage, Some(self.target), &tarball);
298        } else {
299            builder.info(
300                &format!("skipping llvm-bitcode-linker stage{} ({})", self.compiler.stage, self.target),
301            );
302        }
303    };
304);
305
306#[derive(Debug, Clone, Hash, PartialEq, Eq)]
307pub struct Src {
308    pub stage: u32,
309}
310
311impl Step for Src {
312    type Output = ();
313    const DEFAULT: bool = true;
314    const ONLY_HOSTS: bool = true;
315
316    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
317        let config = &run.builder.config;
318        let cond = config.extended && config.tools.as_ref().is_none_or(|t| t.contains("src"));
319        run.path("src").default_condition(cond)
320    }
321
322    fn make_run(run: RunConfig<'_>) {
323        run.builder.ensure(Src { stage: run.builder.top_stage });
324    }
325
326    fn run(self, builder: &Builder<'_>) {
327        let tarball = builder.ensure(dist::Src);
328        install_sh(builder, "src", self.stage, None, &tarball);
329    }
330}