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