]> git.lizzy.rs Git - rust.git/blob - xtask/src/main.rs
Make working with codegen less annoying
[rust.git] / xtask / src / main.rs
1 //! See https://github.com/matklad/cargo-xtask/.
2 //!
3 //! This binary defines various auxiliary build commands, which are not
4 //! expressible with just `cargo`. Notably, it provides `cargo xtask codegen`
5 //! for code generation and `cargo xtask install` for installation of
6 //! rust-analyzer server and client.
7 //!
8 //! This binary is integrated into the `cargo` command line by using an alias in
9 //! `.cargo/config`.
10 mod flags;
11
12 mod codegen;
13 mod ast_src;
14 #[cfg(test)]
15 mod tidy;
16
17 mod install;
18 mod release;
19 mod dist;
20 mod metrics;
21 mod pre_cache;
22
23 use anyhow::{bail, Result};
24 use std::{
25     env,
26     path::{Path, PathBuf},
27 };
28 use walkdir::{DirEntry, WalkDir};
29 use xshell::{cmd, cp, pushd, pushenv};
30
31 use crate::{codegen::Mode, dist::DistCmd};
32
33 fn main() -> Result<()> {
34     let _d = pushd(project_root())?;
35
36     let flags = flags::Xtask::from_env()?;
37     match flags.subcommand {
38         flags::XtaskCmd::Help(_) => {
39             println!("{}", flags::Xtask::HELP);
40             return Ok(());
41         }
42         flags::XtaskCmd::Install(cmd) => cmd.run(),
43         flags::XtaskCmd::Codegen(cmd) => cmd.run(),
44         flags::XtaskCmd::Lint(_) => run_clippy(),
45         flags::XtaskCmd::FuzzTests(_) => run_fuzzer(),
46         flags::XtaskCmd::PreCache(cmd) => cmd.run(),
47         flags::XtaskCmd::Release(cmd) => cmd.run(),
48         flags::XtaskCmd::Promote(cmd) => cmd.run(),
49         flags::XtaskCmd::Dist(flags) => {
50             DistCmd { nightly: flags.nightly, client_version: flags.client }.run()
51         }
52         flags::XtaskCmd::Metrics(cmd) => cmd.run(),
53         flags::XtaskCmd::Bb(cmd) => {
54             {
55                 let _d = pushd("./crates/rust-analyzer")?;
56                 cmd!("cargo build --release --features jemalloc").run()?;
57             }
58             cp("./target/release/rust-analyzer", format!("./target/rust-analyzer-{}", cmd.suffix))?;
59             Ok(())
60         }
61     }
62 }
63
64 fn project_root() -> PathBuf {
65     Path::new(
66         &env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()),
67     )
68     .ancestors()
69     .nth(1)
70     .unwrap()
71     .to_path_buf()
72 }
73
74 fn rust_files() -> impl Iterator<Item = PathBuf> {
75     rust_files_in(&project_root().join("crates"))
76 }
77
78 #[cfg(test)]
79 fn cargo_files() -> impl Iterator<Item = PathBuf> {
80     files_in(&project_root(), "toml")
81         .filter(|path| path.file_name().map(|it| it == "Cargo.toml").unwrap_or(false))
82 }
83
84 fn rust_files_in(path: &Path) -> impl Iterator<Item = PathBuf> {
85     files_in(path, "rs")
86 }
87
88 fn run_rustfmt(mode: Mode) -> Result<()> {
89     let _dir = pushd(project_root())?;
90     let _e = pushenv("RUSTUP_TOOLCHAIN", "stable");
91     ensure_rustfmt()?;
92     match mode {
93         Mode::Overwrite => cmd!("cargo fmt").run()?,
94         Mode::Ensure => {
95             let res = cmd!("cargo fmt -- --check").run();
96             if !res.is_ok() {
97                 let _ = cmd!("cargo fmt").run();
98             }
99             res?;
100         }
101     };
102     Ok(())
103 }
104
105 fn ensure_rustfmt() -> Result<()> {
106     let out = cmd!("rustfmt --version").read()?;
107     if !out.contains("stable") {
108         bail!(
109             "Failed to run rustfmt from toolchain 'stable'. \
110              Please run `rustup component add rustfmt --toolchain stable` to install it.",
111         )
112     }
113     Ok(())
114 }
115
116 fn run_clippy() -> Result<()> {
117     if cmd!("cargo clippy --version").read().is_err() {
118         bail!(
119             "Failed run cargo clippy. \
120             Please run `rustup component add clippy` to install it.",
121         )
122     }
123
124     let allowed_lints = "
125         -A clippy::collapsible_if
126         -A clippy::needless_pass_by_value
127         -A clippy::nonminimal_bool
128         -A clippy::redundant_pattern_matching
129     "
130     .split_ascii_whitespace();
131     cmd!("cargo clippy --all-features --all-targets -- {allowed_lints...}").run()?;
132     Ok(())
133 }
134
135 fn run_fuzzer() -> Result<()> {
136     let _d = pushd("./crates/syntax")?;
137     let _e = pushenv("RUSTUP_TOOLCHAIN", "nightly");
138     if cmd!("cargo fuzz --help").read().is_err() {
139         cmd!("cargo install cargo-fuzz").run()?;
140     };
141
142     // Expecting nightly rustc
143     let out = cmd!("rustc --version").read()?;
144     if !out.contains("nightly") {
145         bail!("fuzz tests require nightly rustc")
146     }
147
148     cmd!("cargo fuzz run parser").run()?;
149     Ok(())
150 }
151
152 fn date_iso() -> Result<String> {
153     let res = cmd!("date --iso --utc").read()?;
154     Ok(res)
155 }
156
157 fn is_release_tag(tag: &str) -> bool {
158     tag.len() == "2020-02-24".len() && tag.starts_with(|c: char| c.is_ascii_digit())
159 }
160
161 fn files_in(path: &Path, ext: &'static str) -> impl Iterator<Item = PathBuf> {
162     let iter = WalkDir::new(path);
163     return iter
164         .into_iter()
165         .filter_entry(|e| !is_hidden(e))
166         .map(|e| e.unwrap())
167         .filter(|e| !e.file_type().is_dir())
168         .map(|e| e.into_path())
169         .filter(move |path| path.extension().map(|it| it == ext).unwrap_or(false));
170
171     fn is_hidden(entry: &DirEntry) -> bool {
172         entry.file_name().to_str().map(|s| s.starts_with('.')).unwrap_or(false)
173     }
174 }