bootstrap/core/build_steps/
run.rs

1//! Build-and-run steps for in-repo tools
2//!
3//! A bit of a hodge-podge as e.g. if a tool's a test fixture it should be in `build_steps::test`.
4//! If it can be reached from `./x.py run` it can go here.
5
6use std::path::PathBuf;
7
8use build_helper::exit;
9use build_helper::git::get_git_untracked_files;
10use clap_complete::{Generator, shells};
11
12use crate::core::build_steps::dist::distdir;
13use crate::core::build_steps::test;
14use crate::core::build_steps::tool::{self, RustcPrivateCompilers, SourceType, Tool};
15use crate::core::build_steps::vendor::{Vendor, default_paths_to_vendor};
16use crate::core::builder::{Builder, Kind, RunConfig, ShouldRun, Step, StepMetadata};
17use crate::core::config::TargetSelection;
18use crate::core::config::flags::{get_completion, top_level_help};
19use crate::utils::exec::command;
20use crate::{Mode, t};
21
22#[derive(Debug, Clone, Hash, PartialEq, Eq)]
23pub struct BuildManifest;
24
25impl Step for BuildManifest {
26    type Output = ();
27    const IS_HOST: bool = true;
28
29    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
30        run.path("src/tools/build-manifest")
31    }
32
33    fn make_run(run: RunConfig<'_>) {
34        run.builder.ensure(BuildManifest);
35    }
36
37    fn run(self, builder: &Builder<'_>) {
38        // This gets called by `promote-release`
39        // (https://github.com/rust-lang/promote-release).
40        let mut cmd = builder.tool_cmd(Tool::BuildManifest);
41        let sign = builder.config.dist_sign_folder.as_ref().unwrap_or_else(|| {
42            panic!("\n\nfailed to specify `dist.sign-folder` in `bootstrap.toml`\n\n")
43        });
44        let addr = builder.config.dist_upload_addr.as_ref().unwrap_or_else(|| {
45            panic!("\n\nfailed to specify `dist.upload-addr` in `bootstrap.toml`\n\n")
46        });
47
48        let today = command("date").arg("+%Y-%m-%d").run_capture_stdout(builder).stdout();
49
50        cmd.arg(sign);
51        cmd.arg(distdir(builder));
52        cmd.arg(today.trim());
53        cmd.arg(addr);
54        cmd.arg(&builder.config.channel);
55
56        builder.create_dir(&distdir(builder));
57        cmd.run(builder);
58    }
59}
60
61#[derive(Debug, Clone, Hash, PartialEq, Eq)]
62pub struct BumpStage0;
63
64impl Step for BumpStage0 {
65    type Output = ();
66    const IS_HOST: bool = true;
67
68    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
69        run.path("src/tools/bump-stage0")
70    }
71
72    fn make_run(run: RunConfig<'_>) {
73        run.builder.ensure(BumpStage0);
74    }
75
76    fn run(self, builder: &Builder<'_>) -> Self::Output {
77        let mut cmd = builder.tool_cmd(Tool::BumpStage0);
78        cmd.args(builder.config.args());
79        cmd.run(builder);
80    }
81}
82
83#[derive(Debug, Clone, Hash, PartialEq, Eq)]
84pub struct ReplaceVersionPlaceholder;
85
86impl Step for ReplaceVersionPlaceholder {
87    type Output = ();
88    const IS_HOST: bool = true;
89
90    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
91        run.path("src/tools/replace-version-placeholder")
92    }
93
94    fn make_run(run: RunConfig<'_>) {
95        run.builder.ensure(ReplaceVersionPlaceholder);
96    }
97
98    fn run(self, builder: &Builder<'_>) -> Self::Output {
99        let mut cmd = builder.tool_cmd(Tool::ReplaceVersionPlaceholder);
100        cmd.arg(&builder.src);
101        cmd.run(builder);
102    }
103}
104
105/// Invoke the Miri tool on a specified file.
106///
107/// Note that Miri always executed on the host, as it is an interpreter.
108/// That means that `x run miri --target FOO` will build miri for the host,
109/// prepare a miri sysroot for the target `FOO` and then execute miri with
110/// the target `FOO`.
111#[derive(Debug, Clone, PartialEq, Eq, Hash)]
112pub struct Miri {
113    /// The build compiler that will build miri and the target compiler to which miri links.
114    compilers: RustcPrivateCompilers,
115    /// The target which will miri interpret.
116    target: TargetSelection,
117}
118
119impl Step for Miri {
120    type Output = ();
121
122    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
123        run.path("src/tools/miri")
124    }
125
126    fn make_run(run: RunConfig<'_>) {
127        let builder = run.builder;
128
129        // `x run` uses stage 0 by default, but miri does not work well with stage 0.
130        // Change the stage to 1 if it's not set explicitly.
131        let stage = if builder.config.is_explicit_stage() || builder.top_stage >= 1 {
132            builder.top_stage
133        } else {
134            1
135        };
136
137        if stage == 0 {
138            eprintln!("ERROR: miri cannot be run at stage 0");
139            exit!(1);
140        }
141
142        // Miri always runs on the host, because it can interpret code for any target
143        let compilers = RustcPrivateCompilers::new(builder, stage, builder.host_target);
144
145        run.builder.ensure(Miri { compilers, target: run.target });
146    }
147
148    fn run(self, builder: &Builder<'_>) {
149        let host = builder.build.host_target;
150        let compilers = self.compilers;
151        let target = self.target;
152
153        builder.ensure(tool::Miri::from_compilers(compilers));
154
155        // Get a target sysroot for Miri.
156        let miri_sysroot =
157            test::Miri::build_miri_sysroot(builder, compilers.target_compiler(), target);
158
159        // # Run miri.
160        // Running it via `cargo run` as that figures out the right dylib path.
161        // add_rustc_lib_path does not add the path that contains librustc_driver-<...>.so.
162        let mut miri = tool::prepare_tool_cargo(
163            builder,
164            compilers.build_compiler(),
165            Mode::ToolRustcPrivate,
166            host,
167            Kind::Run,
168            "src/tools/miri",
169            SourceType::InTree,
170            &[],
171        );
172        miri.add_rustc_lib_path(builder);
173        miri.arg("--").arg("--target").arg(target.rustc_target_arg());
174
175        // miri tests need to know about the stage sysroot
176        miri.arg("--sysroot").arg(miri_sysroot);
177
178        // Forward arguments. This may contain further arguments to the program
179        // after another --, so this must be at the end.
180        miri.args(builder.config.args());
181
182        miri.into_cmd().run(builder);
183    }
184
185    fn metadata(&self) -> Option<StepMetadata> {
186        Some(StepMetadata::run("miri", self.target).built_by(self.compilers.build_compiler()))
187    }
188}
189
190#[derive(Debug, Clone, Hash, PartialEq, Eq)]
191pub struct CollectLicenseMetadata;
192
193impl Step for CollectLicenseMetadata {
194    type Output = PathBuf;
195    const IS_HOST: bool = true;
196
197    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
198        run.path("src/tools/collect-license-metadata")
199    }
200
201    fn make_run(run: RunConfig<'_>) {
202        run.builder.ensure(CollectLicenseMetadata);
203    }
204
205    fn run(self, builder: &Builder<'_>) -> Self::Output {
206        let Some(reuse) = &builder.config.reuse else {
207            panic!("REUSE is required to collect the license metadata");
208        };
209
210        let dest = builder.src.join("license-metadata.json");
211
212        if !builder.config.dry_run() {
213            builder.require_and_update_all_submodules();
214            if let Ok(Some(untracked)) = get_git_untracked_files(None) {
215                eprintln!(
216                    "Warning: {} untracked files may cause the license report to be incorrect.",
217                    untracked.len()
218                );
219            }
220        }
221
222        let mut cmd = builder.tool_cmd(Tool::CollectLicenseMetadata);
223        cmd.env("REUSE_EXE", reuse);
224        cmd.env("DEST", &dest);
225        cmd.run(builder);
226
227        dest
228    }
229}
230
231#[derive(Debug, Clone, Hash, PartialEq, Eq)]
232pub struct GenerateCopyright;
233
234impl Step for GenerateCopyright {
235    type Output = Vec<PathBuf>;
236    const IS_HOST: bool = true;
237
238    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
239        run.path("src/tools/generate-copyright")
240    }
241
242    fn make_run(run: RunConfig<'_>) {
243        run.builder.ensure(GenerateCopyright);
244    }
245
246    fn run(self, builder: &Builder<'_>) -> Self::Output {
247        let license_metadata = builder.src.join("license-metadata.json");
248        let dest = builder.out.join("COPYRIGHT.html");
249        let dest_libstd = builder.out.join("COPYRIGHT-library.html");
250
251        let paths_to_vendor = default_paths_to_vendor(builder);
252        for (_, submodules) in &paths_to_vendor {
253            for submodule in submodules {
254                builder.build.require_submodule(submodule, None);
255            }
256        }
257        let cargo_manifests = paths_to_vendor
258            .into_iter()
259            .map(|(path, _submodules)| path.to_str().unwrap().to_string())
260            .inspect(|path| assert!(!path.contains(','), "{path} contains a comma in its name"))
261            .collect::<Vec<_>>()
262            .join(",");
263
264        let vendored_sources = if let Some(path) = builder.vendored_crates_path() {
265            path
266        } else {
267            let cache_dir = builder.out.join("tmp").join("generate-copyright-vendor");
268            builder.ensure(Vendor {
269                sync_args: Vec::new(),
270                versioned_dirs: true,
271                root_dir: builder.src.clone(),
272                output_dir: cache_dir.clone(),
273            });
274            cache_dir
275        };
276
277        let _guard = builder.group("generate-copyright");
278
279        let mut cmd = builder.tool_cmd(Tool::GenerateCopyright);
280        cmd.env("CARGO_MANIFESTS", &cargo_manifests);
281        cmd.env("LICENSE_METADATA", &license_metadata);
282        cmd.env("DEST", &dest);
283        cmd.env("DEST_LIBSTD", &dest_libstd);
284        cmd.env("SRC_DIR", &builder.src);
285        cmd.env("VENDOR_DIR", &vendored_sources);
286        cmd.env("CARGO", &builder.initial_cargo);
287        cmd.env("CARGO_HOME", t!(home::cargo_home()));
288        // it is important that generate-copyright runs from the root of the
289        // source tree, because it uses relative paths
290        cmd.current_dir(&builder.src);
291        cmd.run(builder);
292
293        vec![dest, dest_libstd]
294    }
295}
296
297#[derive(Debug, Clone, Hash, PartialEq, Eq)]
298pub struct GenerateWindowsSys;
299
300impl Step for GenerateWindowsSys {
301    type Output = ();
302    const IS_HOST: bool = true;
303
304    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
305        run.path("src/tools/generate-windows-sys")
306    }
307
308    fn make_run(run: RunConfig<'_>) {
309        run.builder.ensure(GenerateWindowsSys);
310    }
311
312    fn run(self, builder: &Builder<'_>) {
313        let mut cmd = builder.tool_cmd(Tool::GenerateWindowsSys);
314        cmd.arg(&builder.src);
315        cmd.run(builder);
316    }
317}
318
319/// Return tuples of (shell, file containing completions).
320pub fn get_completion_paths(builder: &Builder<'_>) -> Vec<(&'static dyn Generator, PathBuf)> {
321    vec![
322        (&shells::Bash as &'static dyn Generator, builder.src.join("src/etc/completions/x.py.sh")),
323        (&shells::Zsh, builder.src.join("src/etc/completions/x.py.zsh")),
324        (&shells::Fish, builder.src.join("src/etc/completions/x.py.fish")),
325        (&shells::PowerShell, builder.src.join("src/etc/completions/x.py.ps1")),
326        (&shells::Bash, builder.src.join("src/etc/completions/x.sh")),
327        (&shells::Zsh, builder.src.join("src/etc/completions/x.zsh")),
328        (&shells::Fish, builder.src.join("src/etc/completions/x.fish")),
329        (&shells::PowerShell, builder.src.join("src/etc/completions/x.ps1")),
330    ]
331}
332
333#[derive(Debug, Clone, PartialEq, Eq, Hash)]
334pub struct GenerateCompletions;
335
336impl Step for GenerateCompletions {
337    type Output = ();
338
339    /// Uses `clap_complete` to generate shell completions.
340    fn run(self, builder: &Builder<'_>) {
341        for (shell, path) in get_completion_paths(builder) {
342            if let Some(comp) = get_completion(shell, &path) {
343                std::fs::write(&path, comp).unwrap_or_else(|e| {
344                    panic!("writing completion into {} failed: {e:?}", path.display())
345                });
346            }
347        }
348    }
349
350    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
351        run.alias("generate-completions")
352    }
353
354    fn make_run(run: RunConfig<'_>) {
355        run.builder.ensure(GenerateCompletions);
356    }
357}
358
359#[derive(Debug, Clone, Hash, PartialEq, Eq)]
360pub struct UnicodeTableGenerator;
361
362impl Step for UnicodeTableGenerator {
363    type Output = ();
364    const IS_HOST: bool = true;
365
366    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
367        run.path("src/tools/unicode-table-generator")
368    }
369
370    fn make_run(run: RunConfig<'_>) {
371        run.builder.ensure(UnicodeTableGenerator);
372    }
373
374    fn run(self, builder: &Builder<'_>) {
375        let mut cmd = builder.tool_cmd(Tool::UnicodeTableGenerator);
376        cmd.arg(builder.src.join("library/core/src/unicode/unicode_data.rs"));
377        cmd.run(builder);
378    }
379}
380
381#[derive(Debug, Clone, Hash, PartialEq, Eq)]
382pub struct FeaturesStatusDump;
383
384impl Step for FeaturesStatusDump {
385    type Output = ();
386    const IS_HOST: bool = true;
387
388    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
389        run.path("src/tools/features-status-dump")
390    }
391
392    fn make_run(run: RunConfig<'_>) {
393        run.builder.ensure(FeaturesStatusDump);
394    }
395
396    fn run(self, builder: &Builder<'_>) {
397        let mut cmd = builder.tool_cmd(Tool::FeaturesStatusDump);
398
399        cmd.arg("--library-path");
400        cmd.arg(builder.src.join("library"));
401
402        cmd.arg("--compiler-path");
403        cmd.arg(builder.src.join("compiler"));
404
405        cmd.arg("--output-path");
406        cmd.arg(builder.out.join("features-status-dump.json"));
407
408        cmd.run(builder);
409    }
410}
411
412/// Dummy step that can be used to deliberately trigger bootstrap's step cycle
413/// detector, for automated and manual testing.
414#[derive(Clone, Debug, PartialEq, Eq, Hash)]
415pub struct CyclicStep {
416    n: u32,
417}
418
419impl Step for CyclicStep {
420    type Output = ();
421
422    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
423        run.alias("cyclic-step")
424    }
425
426    fn make_run(run: RunConfig<'_>) {
427        // Start with n=2, so that we build up a few stack entries before panicking.
428        run.builder.ensure(CyclicStep { n: 2 })
429    }
430
431    fn run(self, builder: &Builder<'_>) -> Self::Output {
432        // When n=0, the step will try to ensure itself, causing a step cycle.
433        builder.ensure(CyclicStep { n: self.n.saturating_sub(1) })
434    }
435}
436
437/// Step to manually run the coverage-dump tool (`./x run coverage-dump`).
438///
439/// The coverage-dump tool is an internal detail of coverage tests, so this run
440/// step is only needed when testing coverage-dump manually.
441#[derive(Debug, Clone, Hash, PartialEq, Eq)]
442pub struct CoverageDump;
443
444impl Step for CoverageDump {
445    type Output = ();
446
447    const DEFAULT: bool = false;
448    const IS_HOST: bool = true;
449
450    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
451        run.path("src/tools/coverage-dump")
452    }
453
454    fn make_run(run: RunConfig<'_>) {
455        run.builder.ensure(Self {});
456    }
457
458    fn run(self, builder: &Builder<'_>) {
459        let mut cmd = builder.tool_cmd(Tool::CoverageDump);
460        cmd.args(&builder.config.free_args);
461        cmd.run(builder);
462    }
463}
464
465#[derive(Debug, Clone, PartialEq, Eq, Hash)]
466pub struct Rustfmt;
467
468impl Step for Rustfmt {
469    type Output = ();
470    const IS_HOST: bool = true;
471
472    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
473        run.path("src/tools/rustfmt")
474    }
475
476    fn make_run(run: RunConfig<'_>) {
477        run.builder.ensure(Rustfmt);
478    }
479
480    fn run(self, builder: &Builder<'_>) {
481        let host = builder.build.host_target;
482
483        // `x run` uses stage 0 by default but rustfmt does not work well with stage 0.
484        // Change the stage to 1 if it's not set explicitly.
485        let stage = if builder.config.is_explicit_stage() || builder.top_stage >= 1 {
486            builder.top_stage
487        } else {
488            1
489        };
490
491        if stage == 0 {
492            eprintln!("rustfmt cannot be run at stage 0");
493            eprintln!("HELP: Use `x fmt` to use stage 0 rustfmt.");
494            std::process::exit(1);
495        }
496
497        let compilers = RustcPrivateCompilers::new(builder, stage, host);
498        let rustfmt_build = builder.ensure(tool::Rustfmt::from_compilers(compilers));
499
500        let mut rustfmt = tool::prepare_tool_cargo(
501            builder,
502            rustfmt_build.build_compiler,
503            Mode::ToolRustcPrivate,
504            host,
505            Kind::Run,
506            "src/tools/rustfmt",
507            SourceType::InTree,
508            &[],
509        );
510
511        rustfmt.args(["--bin", "rustfmt", "--"]);
512        rustfmt.args(builder.config.args());
513
514        rustfmt.into_cmd().run(builder);
515    }
516}
517
518/// Return the path of x.py's help.
519pub fn get_help_path(builder: &Builder<'_>) -> PathBuf {
520    builder.src.join("src/etc/xhelp")
521}
522
523#[derive(Debug, Clone, PartialEq, Eq, Hash)]
524pub struct GenerateHelp;
525
526impl Step for GenerateHelp {
527    type Output = ();
528
529    fn run(self, builder: &Builder<'_>) {
530        let help = top_level_help();
531        let path = get_help_path(builder);
532        std::fs::write(&path, help)
533            .unwrap_or_else(|e| panic!("writing help into {} failed: {e:?}", path.display()));
534    }
535
536    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
537        run.alias("generate-help")
538    }
539
540    fn make_run(run: RunConfig<'_>) {
541        run.builder.ensure(GenerateHelp)
542    }
543}