1use 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
22fn 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!("/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 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); 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 if let Some(destdir) = destdir_env {
134 let without_destdir = path.clone();
135 path.clone_from(destdir);
136 for part in without_destdir.components() {
138 if let Component::Normal(s) = part {
139 path.push(s)
140 }
141 }
142 }
143
144 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 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 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}