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