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