diff --git a/Cargo.lock b/Cargo.lock index 5d084bcf1c..fab0edfe30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2072,18 +2072,18 @@ checksum = "da260301476ad19a4733a0e930db8227a48ea04561e235a5102978145ec69fcc" [[package]] name = "xshell" -version = "0.1.17" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaad2035244c56da05573d4d7fda5f903c60a5f35b9110e157a14a1df45a9f14" +checksum = "3332cab90be2998a2aacb6494db45344bd16dfcc43ff36c42255018c6bcc96be" dependencies = [ "xshell-macros", ] [[package]] name = "xshell-macros" -version = "0.1.17" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4916a4a3cad759e499a3620523bf9545cc162d7a06163727dde97ce9aaa4cf39" +checksum = "f47e54cffa76000b7641328ab3bb1e146f93a1690ab86c5909c656f49d91019c" [[package]] name = "xtask" diff --git a/crates/ide_db/Cargo.toml b/crates/ide_db/Cargo.toml index e00803708b..c71a213c40 100644 --- a/crates/ide_db/Cargo.toml +++ b/crates/ide_db/Cargo.toml @@ -35,5 +35,5 @@ limit = { path = "../limit", version = "0.0.0" } [dev-dependencies] test_utils = { path = "../test_utils" } sourcegen = { path = "../sourcegen" } -xshell = "0.1" +xshell = "0.2.0" expect-test = "1.2.0-pre.1" diff --git a/crates/ide_db/src/tests/sourcegen_lints.rs b/crates/ide_db/src/tests/sourcegen_lints.rs index 61c05c7ffd..b7db1dd8db 100644 --- a/crates/ide_db/src/tests/sourcegen_lints.rs +++ b/crates/ide_db/src/tests/sourcegen_lints.rs @@ -4,16 +4,20 @@ use std::{borrow::Cow, fs, path::Path}; use itertools::Itertools; use stdx::format_to; use test_utils::project_root; -use xshell::cmd; +use xshell::{cmd, Shell}; /// This clones rustc repo, and so is not worth to keep up-to-date. We update /// manually by un-ignoring the test from time to time. #[test] #[ignore] fn sourcegen_lint_completions() { + let sh = &Shell::new().unwrap(); + let rust_repo = project_root().join("./target/rust"); if !rust_repo.exists() { - cmd!("git clone --depth=1 https://github.com/rust-lang/rust {rust_repo}").run().unwrap(); + cmd!(sh, "git clone --depth=1 https://github.com/rust-lang/rust {rust_repo}") + .run() + .unwrap(); } let mut contents = String::from( @@ -30,16 +34,19 @@ pub struct LintGroup { ", ); - generate_lint_descriptor(&mut contents); + generate_lint_descriptor(sh, &mut contents); contents.push('\n'); generate_feature_descriptor(&mut contents, &rust_repo.join("src/doc/unstable-book/src")); contents.push('\n'); let lints_json = project_root().join("./target/clippy_lints.json"); - cmd!("curl https://rust-lang.github.io/rust-clippy/master/lints.json --output {lints_json}") - .run() - .unwrap(); + cmd!( + sh, + "curl https://rust-lang.github.io/rust-clippy/master/lints.json --output {lints_json}" + ) + .run() + .unwrap(); generate_descriptor_clippy(&mut contents, &lints_json); let contents = sourcegen::add_preamble("sourcegen_lints", sourcegen::reformat(contents)); @@ -48,10 +55,10 @@ pub struct LintGroup { sourcegen::ensure_file_contents(destination.as_path(), &contents); } -fn generate_lint_descriptor(buf: &mut String) { +fn generate_lint_descriptor(sh: &Shell, buf: &mut String) { // FIXME: rustdoc currently requires an input file for -Whelp cc https://github.com/rust-lang/rust/pull/88831 let file = project_root().join(file!()); - let stdout = cmd!("rustdoc -W help {file}").read().unwrap(); + let stdout = cmd!(sh, "rustdoc -W help {file}").read().unwrap(); let start_lints = stdout.find("---- ------- -------").unwrap(); let start_lint_groups = stdout.find("---- ---------").unwrap(); let start_lints_rustdoc = diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 87b6cd7186..68d728b41f 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -75,7 +75,7 @@ jemallocator = { version = "0.4.1", package = "tikv-jemallocator", optional = tr [dev-dependencies] expect-test = "1.2.0-pre.1" jod-thread = "0.1.0" -xshell = "0.1" +xshell = "0.2.0" test_utils = { path = "../test_utils" } sourcegen = { path = "../sourcegen" } diff --git a/crates/rust-analyzer/tests/slow-tests/tidy.rs b/crates/rust-analyzer/tests/slow-tests/tidy.rs index 2b51e7fecb..9e53aa847d 100644 --- a/crates/rust-analyzer/tests/slow-tests/tidy.rs +++ b/crates/rust-analyzer/tests/slow-tests/tidy.rs @@ -3,14 +3,15 @@ use std::{ path::{Path, PathBuf}, }; -use xshell::{cmd, pushd, pushenv, read_file}; +use xshell::{cmd, Shell}; #[test] fn check_code_formatting() { - let _dir = pushd(sourcegen::project_root()).unwrap(); - let _e = pushenv("RUSTUP_TOOLCHAIN", "stable"); + let sh = &Shell::new().unwrap(); + sh.change_dir(sourcegen::project_root()); + sh.set_var("RUSTUP_TOOLCHAIN", "stable"); - let out = cmd!("rustfmt --version").read().unwrap(); + let out = cmd!(sh, "rustfmt --version").read().unwrap(); if !out.contains("stable") { panic!( "Failed to run rustfmt from toolchain 'stable'. \ @@ -18,25 +19,27 @@ fn check_code_formatting() { ) } - let res = cmd!("cargo fmt -- --check").run(); + let res = cmd!(sh, "cargo fmt -- --check").run(); if res.is_err() { - let _ = cmd!("cargo fmt").run(); + let _ = cmd!(sh, "cargo fmt").run(); } res.unwrap() } #[test] fn check_lsp_extensions_docs() { + let sh = &Shell::new().unwrap(); + let expected_hash = { - let lsp_ext_rs = - read_file(sourcegen::project_root().join("crates/rust-analyzer/src/lsp_ext.rs")) - .unwrap(); + let lsp_ext_rs = sh + .read_file(sourcegen::project_root().join("crates/rust-analyzer/src/lsp_ext.rs")) + .unwrap(); stable_hash(lsp_ext_rs.as_str()) }; let actual_hash = { let lsp_extensions_md = - read_file(sourcegen::project_root().join("docs/dev/lsp-extensions.md")).unwrap(); + sh.read_file(sourcegen::project_root().join("docs/dev/lsp-extensions.md")).unwrap(); let text = lsp_extensions_md .lines() .find_map(|line| line.strip_prefix("lsp_ext.rs hash:")) @@ -62,6 +65,8 @@ Please adjust docs/dev/lsp-extensions.md. #[test] fn files_are_tidy() { + let sh = &Shell::new().unwrap(); + let files = sourcegen::list_files(&sourcegen::project_root().join("crates")); let mut tidy_docs = TidyDocs::default(); @@ -70,7 +75,7 @@ fn files_are_tidy() { let extension = path.extension().unwrap_or_default().to_str().unwrap_or_default(); match extension { "rs" => { - let text = read_file(&path).unwrap(); + let text = sh.read_file(&path).unwrap(); check_todo(&path, &text); check_dbg(&path, &text); check_test_attrs(&path, &text); @@ -80,7 +85,7 @@ fn files_are_tidy() { tidy_marks.visit(&path, &text); } "toml" => { - let text = read_file(&path).unwrap(); + let text = sh.read_file(&path).unwrap(); check_cargo_toml(&path, text); } _ => (), @@ -139,8 +144,10 @@ fn check_cargo_toml(path: &Path, text: String) { #[test] fn check_merge_commits() { - let bors = cmd!("git rev-list --merges --author 'bors\\[bot\\]' HEAD~19..").read().unwrap(); - let all = cmd!("git rev-list --merges HEAD~19..").read().unwrap(); + let sh = &Shell::new().unwrap(); + + let bors = cmd!(sh, "git rev-list --merges --author 'bors\\[bot\\]' HEAD~19..").read().unwrap(); + let all = cmd!(sh, "git rev-list --merges HEAD~19..").read().unwrap(); if bors != all { panic!( " @@ -213,6 +220,8 @@ See https://github.com/rust-lang/rust-clippy/issues/5537 for discussion. #[test] fn check_licenses() { + let sh = &Shell::new().unwrap(); + let expected = " 0BSD OR MIT OR Apache-2.0 Apache-2.0 @@ -235,7 +244,7 @@ Zlib OR Apache-2.0 OR MIT .filter(|it| !it.is_empty()) .collect::>(); - let meta = cmd!("cargo metadata --format-version 1").read().unwrap(); + let meta = cmd!(sh, "cargo metadata --format-version 1").read().unwrap(); let mut licenses = meta .split(|c| c == ',' || c == '{' || c == '}') .filter(|it| it.contains(r#""license""#)) diff --git a/crates/sourcegen/Cargo.toml b/crates/sourcegen/Cargo.toml index 9cfe181c2e..10587d913d 100644 --- a/crates/sourcegen/Cargo.toml +++ b/crates/sourcegen/Cargo.toml @@ -10,4 +10,4 @@ rust-version = "1.57" doctest = false [dependencies] -xshell = "0.1" +xshell = "0.2.0" diff --git a/crates/sourcegen/src/lib.rs b/crates/sourcegen/src/lib.rs index 398e846a14..719b35b630 100644 --- a/crates/sourcegen/src/lib.rs +++ b/crates/sourcegen/src/lib.rs @@ -11,7 +11,7 @@ use std::{ path::{Path, PathBuf}, }; -use xshell::{cmd, pushenv}; +use xshell::{cmd, Shell}; pub fn list_rust_files(dir: &Path) -> Vec { let mut res = list_files(dir); @@ -133,8 +133,8 @@ impl fmt::Display for Location { } } -fn ensure_rustfmt() { - let version = cmd!("rustfmt --version").read().unwrap_or_default(); +fn ensure_rustfmt(sh: &Shell) { + let version = cmd!(sh, "rustfmt --version").read().unwrap_or_default(); if !version.contains("stable") { panic!( "Failed to run rustfmt from toolchain 'stable'. \ @@ -144,10 +144,11 @@ fn ensure_rustfmt() { } pub fn reformat(text: String) -> String { - let _e = pushenv("RUSTUP_TOOLCHAIN", "stable"); - ensure_rustfmt(); + let sh = Shell::new().unwrap(); + sh.set_var("RUSTUP_TOOLCHAIN", "stable"); + ensure_rustfmt(&sh); let rustfmt_toml = project_root().join("rustfmt.toml"); - let mut stdout = cmd!("rustfmt --config-path {rustfmt_toml} --config fn_single_line=true") + let mut stdout = cmd!(sh, "rustfmt --config-path {rustfmt_toml} --config fn_single_line=true") .stdin(text) .read() .unwrap(); diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 0acbaf8176..d9adf88c02 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -10,6 +10,6 @@ rust-version = "1.57" anyhow = "1.0.26" flate2 = "1.0" write-json = "0.1.0" -xshell = "0.1" +xshell = "0.2.0" xflags = "0.2.1" # Avoid adding more dependencies to this crate diff --git a/xtask/src/dist.rs b/xtask/src/dist.rs index 985829880c..3c58fbb76d 100644 --- a/xtask/src/dist.rs +++ b/xtask/src/dist.rs @@ -5,25 +5,23 @@ use std::{ path::{Path, PathBuf}, }; -use anyhow::Result; use flate2::{write::GzEncoder, Compression}; -use xshell::{cmd, cp, mkdir_p, pushd, pushenv, read_file, rm_rf, write_file}; +use xshell::{cmd, Shell}; use crate::{date_iso, flags, project_root}; impl flags::Dist { - pub(crate) fn run(self) -> Result<()> { - let stable = - std::env::var("GITHUB_REF").unwrap_or_default().as_str() == "refs/heads/release"; + pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> { + let stable = sh.var("GITHUB_REF").unwrap_or_default().as_str() == "refs/heads/release"; let project_root = project_root(); let target = Target::get(&project_root); let dist = project_root.join("dist"); - rm_rf(&dist)?; - mkdir_p(&dist)?; + sh.remove_path(&dist)?; + sh.create_dir(&dist)?; let release_channel = if stable { "stable" } else { "nightly" }; - dist_server(release_channel, &target)?; + dist_server(sh, release_channel, &target)?; if let Some(patch_version) = self.client_patch_version { let version = if stable { @@ -32,50 +30,55 @@ impl flags::Dist { // A hack to make VS Code prefer nightly over stable. format!("0.3.{}", patch_version) }; - let release_tag = if stable { date_iso()? } else { "nightly".to_string() }; - dist_client(&version, &release_tag, &target)?; + let release_tag = if stable { date_iso(sh)? } else { "nightly".to_string() }; + dist_client(sh, &version, &release_tag, &target)?; } Ok(()) } } -fn dist_client(version: &str, release_tag: &str, target: &Target) -> Result<()> { +fn dist_client( + sh: &Shell, + version: &str, + release_tag: &str, + target: &Target, +) -> anyhow::Result<()> { let bundle_path = Path::new("editors").join("code").join("server"); - mkdir_p(&bundle_path)?; - cp(&target.server_path, &bundle_path)?; + sh.create_dir(&bundle_path)?; + sh.copy_file(&target.server_path, &bundle_path)?; if let Some(symbols_path) = &target.symbols_path { - cp(symbols_path, &bundle_path)?; + sh.copy_file(symbols_path, &bundle_path)?; } - let _d = pushd("./editors/code")?; + let _d = sh.push_dir("./editors/code"); - let mut patch = Patch::new("./package.json")?; + let mut patch = Patch::new(sh, "./package.json")?; patch .replace(r#""version": "0.4.0-dev""#, &format!(r#""version": "{}""#, version)) .replace(r#""releaseTag": null"#, &format!(r#""releaseTag": "{}""#, release_tag)) .replace(r#""$generated-start": {},"#, "") .replace(",\n \"$generated-end\": {}", "") .replace(r#""enabledApiProposals": [],"#, r#""#); - patch.commit()?; + patch.commit(sh)?; Ok(()) } -fn dist_server(release_channel: &str, target: &Target) -> Result<()> { - let _e = pushenv("RUST_ANALYZER_CHANNEL", release_channel); - let _e = pushenv("CARGO_PROFILE_RELEASE_LTO", "thin"); +fn dist_server(sh: &Shell, release_channel: &str, target: &Target) -> anyhow::Result<()> { + let _e = sh.push_env("RUST_ANALYZER_CHANNEL", release_channel); + let _e = sh.push_env("CARGO_PROFILE_RELEASE_LTO", "thin"); // Uncomment to enable debug info for releases. Note that: // * debug info is split on windows and macs, so it does nothing for those platforms, // * on Linux, this blows up the binary size from 8MB to 43MB, which is unreasonable. - // let _e = pushenv("CARGO_PROFILE_RELEASE_DEBUG", "1"); + // let _e = sh.push_env("CARGO_PROFILE_RELEASE_DEBUG", "1"); if target.name.contains("-linux-") { env::set_var("CC", "clang"); } let target_name = &target.name; - cmd!("cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} --release").run()?; + cmd!(sh, "cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} --release").run()?; let dst = Path::new("dist").join(&target.artifact_name); gzip(&target.server_path, &dst.with_extension("gz"))?; @@ -83,7 +86,7 @@ fn dist_server(release_channel: &str, target: &Target) -> Result<()> { Ok(()) } -fn gzip(src_path: &Path, dest_path: &Path) -> Result<()> { +fn gzip(src_path: &Path, dest_path: &Path) -> anyhow::Result<()> { let mut encoder = GzEncoder::new(File::create(dest_path)?, Compression::best()); let mut input = io::BufReader::new(File::open(src_path)?); io::copy(&mut input, &mut encoder)?; @@ -133,9 +136,9 @@ struct Patch { } impl Patch { - fn new(path: impl Into) -> Result { + fn new(sh: &Shell, path: impl Into) -> anyhow::Result { let path = path.into(); - let contents = read_file(&path)?; + let contents = sh.read_file(&path)?; Ok(Patch { path, original_contents: contents.clone(), contents }) } @@ -145,8 +148,8 @@ impl Patch { self } - fn commit(&self) -> Result<()> { - write_file(&self.path, &self.contents)?; + fn commit(&self, sh: &Shell) -> anyhow::Result<()> { + sh.write_file(&self.path, &self.contents)?; Ok(()) } } diff --git a/xtask/src/install.rs b/xtask/src/install.rs index 58f386be85..ae978d5512 100644 --- a/xtask/src/install.rs +++ b/xtask/src/install.rs @@ -3,20 +3,20 @@ use std::{env, path::PathBuf, str}; use anyhow::{bail, format_err, Context, Result}; -use xshell::{cmd, pushd}; +use xshell::{cmd, Shell}; use crate::flags; impl flags::Install { - pub(crate) fn run(self) -> Result<()> { + pub(crate) fn run(self, sh: &Shell) -> Result<()> { if cfg!(target_os = "macos") { - fix_path_for_mac().context("Fix path for mac")?; + fix_path_for_mac(sh).context("Fix path for mac")?; } if let Some(server) = self.server() { - install_server(server).context("install server")?; + install_server(sh, server).context("install server")?; } if let Some(client) = self.client() { - install_client(client).context("install client")?; + install_client(sh, client).context("install client")?; } Ok(()) } @@ -39,15 +39,14 @@ pub(crate) enum Malloc { Jemalloc, } -fn fix_path_for_mac() -> Result<()> { +fn fix_path_for_mac(sh: &Shell) -> Result<()> { let mut vscode_path: Vec = { const COMMON_APP_PATH: &str = r"/Applications/Visual Studio Code.app/Contents/Resources/app/bin"; const ROOT_DIR: &str = ""; - let home_dir = match env::var("HOME") { - Ok(home) => home, - Err(e) => bail!("Failed getting HOME from environment with error: {}.", e), - }; + let home_dir = sh.var("HOME").map_err(|err| { + format_err!("Failed getting HOME from environment with error: {}.", err) + })?; [ROOT_DIR, &home_dir] .into_iter() @@ -58,36 +57,33 @@ fn fix_path_for_mac() -> Result<()> { }; if !vscode_path.is_empty() { - let vars = match env::var_os("PATH") { - Some(path) => path, - None => bail!("Could not get PATH variable from env."), - }; + let vars = sh.var_os("PATH").context("Could not get PATH variable from env.")?; let mut paths = env::split_paths(&vars).collect::>(); paths.append(&mut vscode_path); let new_paths = env::join_paths(paths).context("build env PATH")?; - env::set_var("PATH", &new_paths); + sh.set_var("PATH", &new_paths); } Ok(()) } -fn install_client(client_opt: ClientOpt) -> Result<()> { - let _dir = pushd("./editors/code"); +fn install_client(sh: &Shell, client_opt: ClientOpt) -> Result<()> { + let _dir = sh.push_dir("./editors/code"); // Package extension. if cfg!(unix) { - cmd!("npm --version").run().context("`npm` is required to build the VS Code plugin")?; - cmd!("npm ci").run()?; + cmd!(sh, "npm --version").run().context("`npm` is required to build the VS Code plugin")?; + cmd!(sh, "npm ci").run()?; - cmd!("npm run package --scripts-prepend-node-path").run()?; + cmd!(sh, "npm run package --scripts-prepend-node-path").run()?; } else { - cmd!("cmd.exe /c npm --version") + cmd!(sh, "cmd.exe /c npm --version") .run() .context("`npm` is required to build the VS Code plugin")?; - cmd!("cmd.exe /c npm ci").run()?; + cmd!(sh, "cmd.exe /c npm ci").run()?; - cmd!("cmd.exe /c npm run package").run()?; + cmd!(sh, "cmd.exe /c npm run package").run()?; }; // Find the appropriate VS Code binary. @@ -104,9 +100,9 @@ fn install_client(client_opt: ClientOpt) -> Result<()> { .copied() .find(|&bin| { if cfg!(unix) { - cmd!("{bin} --version").read().is_ok() + cmd!(sh, "{bin} --version").read().is_ok() } else { - cmd!("cmd.exe /c {bin}.cmd --version").read().is_ok() + cmd!(sh, "cmd.exe /c {bin}.cmd --version").read().is_ok() } }) .ok_or_else(|| { @@ -115,11 +111,11 @@ fn install_client(client_opt: ClientOpt) -> Result<()> { // Install & verify. let installed_extensions = if cfg!(unix) { - cmd!("{code} --install-extension rust-analyzer.vsix --force").run()?; - cmd!("{code} --list-extensions").read()? + cmd!(sh, "{code} --install-extension rust-analyzer.vsix --force").run()?; + cmd!(sh, "{code} --list-extensions").read()? } else { - cmd!("cmd.exe /c {code}.cmd --install-extension rust-analyzer.vsix --force").run()?; - cmd!("cmd.exe /c {code}.cmd --list-extensions").read()? + cmd!(sh, "cmd.exe /c {code}.cmd --install-extension rust-analyzer.vsix --force").run()?; + cmd!(sh, "cmd.exe /c {code}.cmd --list-extensions").read()? }; if !installed_extensions.contains("rust-analyzer") { @@ -133,14 +129,14 @@ fn install_client(client_opt: ClientOpt) -> Result<()> { Ok(()) } -fn install_server(opts: ServerOpt) -> Result<()> { +fn install_server(sh: &Shell, opts: ServerOpt) -> Result<()> { let features = match opts.malloc { Malloc::System => &[][..], Malloc::Mimalloc => &["--features", "mimalloc"], Malloc::Jemalloc => &["--features", "jemalloc"], }; - let cmd = cmd!("cargo install --path crates/rust-analyzer --locked --force --features force-always-assert {features...}"); + let cmd = cmd!(sh, "cargo install --path crates/rust-analyzer --locked --force --features force-always-assert {features...}"); cmd.run()?; Ok(()) } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 5e5401ce28..df726dc23c 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -14,15 +14,16 @@ mod release; mod dist; mod metrics; -use anyhow::{bail, Result}; +use anyhow::bail; use std::{ env, path::{Path, PathBuf}, }; -use xshell::{cmd, cp, pushd, pushenv}; +use xshell::{cmd, Shell}; -fn main() -> Result<()> { - let _d = pushd(project_root())?; +fn main() -> anyhow::Result<()> { + let sh = &Shell::new()?; + sh.change_dir(project_root()); let flags = flags::Xtask::from_env()?; match flags.subcommand { @@ -30,18 +31,21 @@ fn main() -> Result<()> { println!("{}", flags::Xtask::HELP); Ok(()) } - flags::XtaskCmd::Install(cmd) => cmd.run(), - flags::XtaskCmd::FuzzTests(_) => run_fuzzer(), - flags::XtaskCmd::Release(cmd) => cmd.run(), - flags::XtaskCmd::Promote(cmd) => cmd.run(), - flags::XtaskCmd::Dist(cmd) => cmd.run(), - flags::XtaskCmd::Metrics(cmd) => cmd.run(), + flags::XtaskCmd::Install(cmd) => cmd.run(sh), + flags::XtaskCmd::FuzzTests(_) => run_fuzzer(sh), + flags::XtaskCmd::Release(cmd) => cmd.run(sh), + flags::XtaskCmd::Promote(cmd) => cmd.run(sh), + flags::XtaskCmd::Dist(cmd) => cmd.run(sh), + flags::XtaskCmd::Metrics(cmd) => cmd.run(sh), flags::XtaskCmd::Bb(cmd) => { { - let _d = pushd("./crates/rust-analyzer")?; - cmd!("cargo build --release --features jemalloc").run()?; + let _d = sh.push_dir("./crates/rust-analyzer"); + cmd!(sh, "cargo build --release --features jemalloc").run()?; } - cp("./target/release/rust-analyzer", format!("./target/rust-analyzer-{}", cmd.suffix))?; + sh.copy_file( + "./target/release/rust-analyzer", + format!("./target/rust-analyzer-{}", cmd.suffix), + )?; Ok(()) } } @@ -57,25 +61,25 @@ fn project_root() -> PathBuf { .to_path_buf() } -fn run_fuzzer() -> Result<()> { - let _d = pushd("./crates/syntax")?; - let _e = pushenv("RUSTUP_TOOLCHAIN", "nightly"); - if cmd!("cargo fuzz --help").read().is_err() { - cmd!("cargo install cargo-fuzz").run()?; +fn run_fuzzer(sh: &Shell) -> anyhow::Result<()> { + let _d = sh.push_dir("./crates/syntax"); + let _e = sh.push_env("RUSTUP_TOOLCHAIN", "nightly"); + if cmd!(sh, "cargo fuzz --help").read().is_err() { + cmd!(sh, "cargo install cargo-fuzz").run()?; }; // Expecting nightly rustc - let out = cmd!("rustc --version").read()?; + let out = cmd!(sh, "rustc --version").read()?; if !out.contains("nightly") { bail!("fuzz tests require nightly rustc") } - cmd!("cargo fuzz run parser").run()?; + cmd!(sh, "cargo fuzz run parser").run()?; Ok(()) } -fn date_iso() -> Result { - let res = cmd!("date -u +%Y-%m-%d").read()?; +fn date_iso(sh: &Shell) -> anyhow::Result { + let res = cmd!(sh, "date -u +%Y-%m-%d").read()?; Ok(res) } diff --git a/xtask/src/metrics.rs b/xtask/src/metrics.rs index e4d3e981af..b683b32e6d 100644 --- a/xtask/src/metrics.rs +++ b/xtask/src/metrics.rs @@ -1,95 +1,103 @@ use std::{ collections::BTreeMap, - env, + env, fs, io::Write as _, path::Path, time::{Instant, SystemTime, UNIX_EPOCH}, }; -use anyhow::{bail, format_err, Result}; -use xshell::{cmd, mkdir_p, pushd, pushenv, read_file, rm_rf}; +use anyhow::{bail, format_err}; +use xshell::{cmd, Shell}; use crate::flags; type Unit = String; impl flags::Metrics { - pub(crate) fn run(self) -> Result<()> { - let mut metrics = Metrics::new()?; + pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> { + let mut metrics = Metrics::new(sh)?; if !self.dry_run { - rm_rf("./target/release")?; + sh.remove_path("./target/release")?; } if !Path::new("./target/rustc-perf").exists() { - mkdir_p("./target/rustc-perf")?; - cmd!("git clone https://github.com/rust-lang/rustc-perf.git ./target/rustc-perf") + sh.create_dir("./target/rustc-perf")?; + cmd!(sh, "git clone https://github.com/rust-lang/rustc-perf.git ./target/rustc-perf") .run()?; } { - let _d = pushd("./target/rustc-perf")?; + let _d = sh.push_dir("./target/rustc-perf"); let revision = &metrics.perf_revision; - cmd!("git reset --hard {revision}").run()?; + cmd!(sh, "git reset --hard {revision}").run()?; } - let _env = pushenv("RA_METRICS", "1"); + let _env = sh.push_env("RA_METRICS", "1"); { // https://github.com/rust-analyzer/rust-analyzer/issues/9997 - let _d = pushd("target/rustc-perf/collector/benchmarks/webrender")?; - cmd!("cargo update -p url --precise 1.6.1").run()?; + let _d = sh.push_dir("target/rustc-perf/collector/benchmarks/webrender"); + cmd!(sh, "cargo update -p url --precise 1.6.1").run()?; } - metrics.measure_build()?; - metrics.measure_analysis_stats_self()?; - metrics.measure_analysis_stats("ripgrep")?; - metrics.measure_analysis_stats("webrender")?; - metrics.measure_analysis_stats("diesel/diesel")?; + metrics.measure_build(sh)?; + metrics.measure_analysis_stats_self(sh)?; + metrics.measure_analysis_stats(sh, "ripgrep")?; + metrics.measure_analysis_stats(sh, "webrender")?; + metrics.measure_analysis_stats(sh, "diesel/diesel")?; if !self.dry_run { - let _d = pushd("target")?; + let _d = sh.push_dir("target"); let metrics_token = env::var("METRICS_TOKEN").unwrap(); cmd!( + sh, "git clone --depth 1 https://{metrics_token}@github.com/rust-analyzer/metrics.git" ) .run()?; - let _d = pushd("metrics")?; + let _d = sh.push_dir("metrics"); - let mut file = std::fs::OpenOptions::new().append(true).open("metrics.json")?; + let mut file = fs::File::options().append(true).open("metrics.json")?; writeln!(file, "{}", metrics.json())?; - cmd!("git add .").run()?; - cmd!("git -c user.name=Bot -c user.email=dummy@example.com commit --message 📈") + cmd!(sh, "git add .").run()?; + cmd!(sh, "git -c user.name=Bot -c user.email=dummy@example.com commit --message 📈") .run()?; - cmd!("git push origin master").run()?; + cmd!(sh, "git push origin master").run()?; } - eprintln!("{:#?}", metrics); + eprintln!("{metrics:#?}"); Ok(()) } } impl Metrics { - fn measure_build(&mut self) -> Result<()> { + fn measure_build(&mut self, sh: &Shell) -> anyhow::Result<()> { eprintln!("\nMeasuring build"); - cmd!("cargo fetch").run()?; + cmd!(sh, "cargo fetch").run()?; let time = Instant::now(); - cmd!("cargo build --release --package rust-analyzer --bin rust-analyzer").run()?; + cmd!(sh, "cargo build --release --package rust-analyzer --bin rust-analyzer").run()?; let time = time.elapsed(); self.report("build", time.as_millis() as u64, "ms".into()); Ok(()) } - fn measure_analysis_stats_self(&mut self) -> Result<()> { - self.measure_analysis_stats_path("self", ".") + fn measure_analysis_stats_self(&mut self, sh: &Shell) -> anyhow::Result<()> { + self.measure_analysis_stats_path(sh, "self", ".") } - fn measure_analysis_stats(&mut self, bench: &str) -> Result<()> { + fn measure_analysis_stats(&mut self, sh: &Shell, bench: &str) -> anyhow::Result<()> { self.measure_analysis_stats_path( + sh, bench, &format!("./target/rustc-perf/collector/benchmarks/{}", bench), ) } - fn measure_analysis_stats_path(&mut self, name: &str, path: &str) -> Result<()> { - eprintln!("\nMeasuring analysis-stats/{}", name); - let output = cmd!("./target/release/rust-analyzer -q analysis-stats --memory-usage {path}") - .read()?; + fn measure_analysis_stats_path( + &mut self, + sh: &Shell, + name: &str, + path: &str, + ) -> anyhow::Result<()> { + eprintln!("\nMeasuring analysis-stats/{name}"); + let output = + cmd!(sh, "./target/release/rust-analyzer -q analysis-stats --memory-usage {path}") + .read()?; for (metric, value, unit) in parse_metrics(&output) { - self.report(&format!("analysis-stats/{}/{}", name, metric), value, unit.into()); + self.report(&format!("analysis-stats/{name}/{metric}"), value, unit.into()); } Ok(()) } @@ -125,10 +133,10 @@ struct Host { } impl Metrics { - fn new() -> Result { - let host = Host::new()?; + fn new(sh: &Shell) -> anyhow::Result { + let host = Host::new(sh)?; let timestamp = SystemTime::now(); - let revision = cmd!("git rev-parse HEAD").read()?; + let revision = cmd!(sh, "git rev-parse HEAD").read()?; let perf_revision = "c52ee623e231e7690a93be88d943016968c1036b".into(); Ok(Metrics { host, timestamp, revision, perf_revision, metrics: BTreeMap::new() }) } @@ -157,28 +165,29 @@ impl Metrics { } impl Host { - fn new() -> Result { + fn new(sh: &Shell) -> anyhow::Result { if cfg!(not(target_os = "linux")) { bail!("can only collect metrics on Linux "); } - let os = read_field("/etc/os-release", "PRETTY_NAME=")?.trim_matches('"').to_string(); + let os = read_field(sh, "/etc/os-release", "PRETTY_NAME=")?.trim_matches('"').to_string(); - let cpu = - read_field("/proc/cpuinfo", "model name")?.trim_start_matches(':').trim().to_string(); + let cpu = read_field(sh, "/proc/cpuinfo", "model name")? + .trim_start_matches(':') + .trim() + .to_string(); - let mem = read_field("/proc/meminfo", "MemTotal:")?; + let mem = read_field(sh, "/proc/meminfo", "MemTotal:")?; return Ok(Host { os, cpu, mem }); - fn read_field(path: &str, field: &str) -> Result { - let text = read_file(path)?; + fn read_field(sh: &Shell, path: &str, field: &str) -> anyhow::Result { + let text = sh.read_file(path)?; - let line = text - .lines() - .find(|it| it.starts_with(field)) - .ok_or_else(|| format_err!("can't parse {}", path))?; - Ok(line[field.len()..].trim().to_string()) + text.lines() + .find_map(|it| it.strip_prefix(field)) + .map(|it| it.trim().to_string()) + .ok_or_else(|| format_err!("can't parse {}", path)) } } fn to_json(&self, mut obj: write_json::Object<'_>) { diff --git a/xtask/src/release.rs b/xtask/src/release.rs index acb13282f6..9bd5d9d310 100644 --- a/xtask/src/release.rs +++ b/xtask/src/release.rs @@ -1,15 +1,15 @@ mod changelog; -use xshell::{cmd, pushd, read_dir, read_file, write_file}; +use xshell::{cmd, Shell}; -use crate::{date_iso, flags, is_release_tag, project_root, Result}; +use crate::{date_iso, flags, is_release_tag, project_root}; impl flags::Release { - pub(crate) fn run(self) -> Result<()> { + pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> { if !self.dry_run { - cmd!("git switch release").run()?; - cmd!("git fetch upstream --tags --force").run()?; - cmd!("git reset --hard tags/nightly").run()?; + cmd!(sh, "git switch release").run()?; + cmd!(sh, "git fetch upstream --tags --force").run()?; + cmd!(sh, "git reset --hard tags/nightly").run()?; // The `release` branch sometimes has a couple of cherry-picked // commits for patch releases. If that's the case, just overwrite // it. As we are setting `release` branch to an up-to-date `nightly` @@ -19,24 +19,24 @@ impl flags::Release { // commits -- they'll be kept alive by the tag. More generally, we // don't care about historic releases all that much, it's fine even // to delete old tags. - cmd!("git push --force").run()?; + cmd!(sh, "git push --force").run()?; } // Generates bits of manual.adoc. - cmd!("cargo test -p ide_assists -p ide_diagnostics -p rust-analyzer -- sourcegen_") + cmd!(sh, "cargo test -p ide_assists -p ide_diagnostics -p rust-analyzer -- sourcegen_") .run()?; let website_root = project_root().join("../rust-analyzer.github.io"); { - let _dir = pushd(&website_root)?; - cmd!("git switch src").run()?; - cmd!("git pull").run()?; + let _dir = sh.push_dir(&website_root); + cmd!(sh, "git switch src").run()?; + cmd!(sh, "git pull").run()?; } let changelog_dir = website_root.join("./thisweek/_posts"); - let today = date_iso()?; - let commit = cmd!("git rev-parse HEAD").read()?; - let changelog_n = read_dir(changelog_dir.as_path())?.len(); + let today = date_iso(sh)?; + let commit = cmd!(sh, "git rev-parse HEAD").read()?; + let changelog_n = sh.read_dir(changelog_dir.as_path())?.len(); for adoc in [ "manual.adoc", @@ -48,42 +48,46 @@ impl flags::Release { let src = project_root().join("./docs/user/").join(adoc); let dst = website_root.join(adoc); - let contents = read_file(src)?; - write_file(dst, contents)?; + let contents = sh.read_file(src)?; + sh.write_file(dst, contents)?; } - let tags = cmd!("git tag --list").read()?; + let tags = cmd!(sh, "git tag --list").read()?; let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap(); - let contents = changelog::get_changelog(changelog_n, &commit, prev_tag, &today)?; + let contents = changelog::get_changelog(sh, changelog_n, &commit, prev_tag, &today)?; let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n)); - write_file(&path, &contents)?; + sh.write_file(&path, &contents)?; Ok(()) } } impl flags::Promote { - pub(crate) fn run(self) -> Result<()> { - let _dir = pushd("../rust-rust-analyzer")?; - cmd!("git switch master").run()?; - cmd!("git fetch upstream").run()?; - cmd!("git reset --hard upstream/master").run()?; - cmd!("git submodule update --recursive").run()?; + pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> { + let _dir = sh.push_dir("../rust-rust-analyzer"); + cmd!(sh, "git switch master").run()?; + cmd!(sh, "git fetch upstream").run()?; + cmd!(sh, "git reset --hard upstream/master").run()?; + cmd!(sh, "git submodule update --recursive").run()?; - let branch = format!("rust-analyzer-{}", date_iso()?); - cmd!("git switch -c {branch}").run()?; + let date = date_iso(sh)?; + let branch = format!("rust-analyzer-{date}"); + cmd!(sh, "git switch -c {branch}").run()?; { - let _dir = pushd("src/tools/rust-analyzer")?; - cmd!("git fetch origin").run()?; - cmd!("git reset --hard origin/release").run()?; + let _dir = sh.push_dir("src/tools/rust-analyzer"); + cmd!(sh, "git fetch origin").run()?; + cmd!(sh, "git reset --hard origin/release").run()?; } - cmd!("git add src/tools/rust-analyzer").run()?; - cmd!("git commit -m':arrow_up: rust-analyzer'").run()?; + cmd!(sh, "git add src/tools/rust-analyzer").run()?; + cmd!(sh, "git commit -m':arrow_up: rust-analyzer'").run()?; if !self.dry_run { - cmd!("git push -u origin {branch}").run()?; - cmd!("xdg-open https://github.com/matklad/rust/pull/new/{branch}?body=r%3F%20%40ghost") - .run()?; + cmd!(sh, "git push -u origin {branch}").run()?; + cmd!( + sh, + "xdg-open https://github.com/matklad/rust/pull/new/{branch}?body=r%3F%20%40ghost" + ) + .run()?; } Ok(()) } diff --git a/xtask/src/release/changelog.rs b/xtask/src/release/changelog.rs index 2384a746f3..ceef9fac2d 100644 --- a/xtask/src/release/changelog.rs +++ b/xtask/src/release/changelog.rs @@ -1,16 +1,17 @@ use std::fmt::Write; use std::{env, iter}; -use anyhow::{bail, Result}; -use xshell::cmd; +use anyhow::bail; +use xshell::{cmd, Shell}; pub(crate) fn get_changelog( + sh: &Shell, changelog_n: usize, commit: &str, prev_tag: &str, today: &str, -) -> Result { - let git_log = cmd!("git log {prev_tag}..HEAD --merges --reverse").read()?; +) -> anyhow::Result { + let git_log = cmd!(sh, "git log {prev_tag}..HEAD --merges --reverse").read()?; let mut features = String::new(); let mut fixes = String::new(); let mut internal = String::new(); @@ -30,14 +31,15 @@ pub(crate) fn get_changelog( // we don't use an HTTPS client or JSON parser to keep the build times low let pr_json = - cmd!("curl -s -H {accept} -H {authorization} {pr_url}/{pr}").read()?; - let pr_title = cmd!("jq .title").stdin(&pr_json).read()?; + cmd!(sh, "curl -s -H {accept} -H {authorization} {pr_url}/{pr}").read()?; + let pr_title = cmd!(sh, "jq .title").stdin(&pr_json).read()?; let pr_title = unescape(&pr_title[1..pr_title.len() - 1]); - let pr_comment = cmd!("jq .body").stdin(pr_json).read()?; + let pr_comment = cmd!(sh, "jq .body").stdin(pr_json).read()?; let comments_json = - cmd!("curl -s -H {accept} -H {authorization} {pr_url}/{pr}/comments").read()?; - let pr_comments = cmd!("jq .[].body").stdin(comments_json).read()?; + cmd!(sh, "curl -s -H {accept} -H {authorization} {pr_url}/{pr}/comments") + .read()?; + let pr_comments = cmd!(sh, "jq .[].body").stdin(comments_json).read()?; let l = iter::once(pr_comment.as_str()) .chain(pr_comments.lines())