]> git.lizzy.rs Git - rust.git/blob - xtask/src/lib.rs
Clean xtask partial artifacts in `xtask pre-cache`
[rust.git] / xtask / src / lib.rs
1 //! Support library for `cargo xtask` command.
2 //!
3 //! See https://github.com/matklad/cargo-xtask/
4
5 pub mod not_bash;
6 pub mod install;
7 pub mod pre_commit;
8
9 pub mod codegen;
10 mod ast_src;
11
12 use anyhow::Context;
13 use std::{
14     env,
15     io::Write,
16     path::{Path, PathBuf},
17     process::{Command, Stdio},
18 };
19
20 use crate::{
21     codegen::Mode,
22     not_bash::{fs2, pushd, rm_rf, run},
23 };
24
25 pub use anyhow::Result;
26
27 const TOOLCHAIN: &str = "stable";
28
29 pub fn project_root() -> PathBuf {
30     Path::new(
31         &env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()),
32     )
33     .ancestors()
34     .nth(1)
35     .unwrap()
36     .to_path_buf()
37 }
38
39 pub fn run_rustfmt(mode: Mode) -> Result<()> {
40     ensure_rustfmt()?;
41
42     if mode == Mode::Verify {
43         run!("rustup run {} -- cargo fmt -- --check", TOOLCHAIN)?;
44     } else {
45         run!("rustup run {} -- cargo fmt", TOOLCHAIN)?;
46     }
47     Ok(())
48 }
49
50 fn reformat(text: impl std::fmt::Display) -> Result<String> {
51     ensure_rustfmt()?;
52     let mut rustfmt = Command::new("rustup")
53         .args(&["run", TOOLCHAIN, "--", "rustfmt", "--config-path"])
54         .arg(project_root().join("rustfmt.toml"))
55         .stdin(Stdio::piped())
56         .stdout(Stdio::piped())
57         .spawn()?;
58     write!(rustfmt.stdin.take().unwrap(), "{}", text)?;
59     let output = rustfmt.wait_with_output()?;
60     let stdout = String::from_utf8(output.stdout)?;
61     let preamble = "Generated file, do not edit by hand, see `xtask/src/codegen`";
62     Ok(format!("//! {}\n\n{}", preamble, stdout))
63 }
64
65 fn ensure_rustfmt() -> Result<()> {
66     match Command::new("rustup")
67         .args(&["run", TOOLCHAIN, "--", "cargo", "fmt", "--version"])
68         .stderr(Stdio::null())
69         .stdout(Stdio::null())
70         .status()
71     {
72         Ok(status) if status.success() => return Ok(()),
73         _ => (),
74     };
75     run!("rustup toolchain install {}", TOOLCHAIN)?;
76     run!("rustup component add rustfmt --toolchain {}", TOOLCHAIN)?;
77     Ok(())
78 }
79
80 pub fn run_clippy() -> Result<()> {
81     match Command::new("rustup")
82         .args(&["run", TOOLCHAIN, "--", "cargo", "clippy", "--version"])
83         .stderr(Stdio::null())
84         .stdout(Stdio::null())
85         .status()
86     {
87         Ok(status) if status.success() => (),
88         _ => install_clippy().context("install clippy")?,
89     };
90
91     let allowed_lints = [
92         "clippy::collapsible_if",
93         "clippy::map_clone", // FIXME: remove when Iterator::copied stabilizes (1.36.0)
94         "clippy::needless_pass_by_value",
95         "clippy::nonminimal_bool",
96         "clippy::redundant_pattern_matching",
97     ];
98     run!(
99         "rustup run {} -- cargo clippy --all-features --all-targets -- -A {}",
100         TOOLCHAIN,
101         allowed_lints.join(" -A ")
102     )?;
103     Ok(())
104 }
105
106 fn install_clippy() -> Result<()> {
107     run!("rustup toolchain install {}", TOOLCHAIN)?;
108     run!("rustup component add clippy --toolchain {}", TOOLCHAIN)?;
109     Ok(())
110 }
111
112 pub fn run_fuzzer() -> Result<()> {
113     let _d = pushd("./crates/ra_syntax");
114     if run!("cargo fuzz --help").is_err() {
115         run!("cargo install cargo-fuzz")?;
116     };
117
118     run!("rustup run nightly -- cargo fuzz run parser")?;
119     Ok(())
120 }
121
122 /// Cleans the `./target` dir after the build such that only
123 /// dependencies are cached on CI.
124 pub fn run_pre_cache() -> Result<()> {
125     let slow_tests_cookie = Path::new("./target/.slow_tests_cookie");
126     if !slow_tests_cookie.exists() {
127         panic!("slow tests were skipped on CI!")
128     }
129     rm_rf(slow_tests_cookie)?;
130
131     for entry in Path::new("./target/debug").read_dir()? {
132         let entry = entry?;
133         if entry.file_type().map(|it| it.is_file()).ok() == Some(true) {
134             // Can't delete yourself on windows :-(
135             if !entry.path().ends_with("xtask.exe") {
136                 rm_rf(&entry.path())?
137             }
138         }
139     }
140
141     fs2::remove_file("./target/.rustc_info.json")?;
142     let to_delete = ["ra_", "heavy_test", "xtask"];
143     for &dir in ["./target/debug/deps", "target/debug/.fingerprint"].iter() {
144         for entry in Path::new(dir).read_dir()? {
145             let entry = entry?;
146             if to_delete.iter().any(|&it| entry.path().display().to_string().contains(it)) {
147                 // Can't delete yourself on windows :-(
148                 if !entry.path().ends_with("xtask.exe") {
149                     rm_rf(&entry.path())?
150                 }
151             }
152         }
153     }
154
155     Ok(())
156 }
157
158 pub fn run_release(dry_run: bool) -> Result<()> {
159     if !dry_run {
160         run!("git switch release")?;
161         run!("git fetch upstream")?;
162         run!("git reset --hard upstream/master")?;
163         run!("git push")?;
164     }
165
166     let website_root = project_root().join("../rust-analyzer.github.io");
167     let changelog_dir = website_root.join("./thisweek/_posts");
168
169     let today = run!("date --iso")?;
170     let commit = run!("git rev-parse HEAD")?;
171     let changelog_n = fs2::read_dir(changelog_dir.as_path())?.count();
172
173     let contents = format!(
174         "\
175 = Changelog #{}
176 :sectanchors:
177 :page-layout: post
178
179 Commit: commit:{}[] +
180 Release: release:{}[]
181
182 == New Features
183
184 * pr:[] .
185
186 == Fixes
187
188 == Internal Improvements
189 ",
190         changelog_n, commit, today
191     );
192
193     let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n));
194     fs2::write(&path, &contents)?;
195
196     fs2::copy(project_root().join("./docs/user/readme.adoc"), website_root.join("manual.adoc"))?;
197
198     let tags = run!("git tag --list"; echo = false)?;
199     let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap();
200
201     println!("\n    git log {}..HEAD --merges --reverse", prev_tag);
202
203     Ok(())
204 }
205
206 fn is_release_tag(tag: &str) -> bool {
207     tag.len() == "2020-02-24".len() && tag.starts_with(|c: char| c.is_ascii_digit())
208 }