Skip to main content

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