aboutsummaryrefslogtreecommitdiff
path: root/xtask
diff options
context:
space:
mode:
Diffstat (limited to 'xtask')
-rw-r--r--xtask/src/ast_src.rs16
-rw-r--r--xtask/src/cmd.rs53
-rw-r--r--xtask/src/install.rs138
-rw-r--r--xtask/src/lib.rs94
-rw-r--r--xtask/src/main.rs7
-rw-r--r--xtask/src/not_bash.rs165
-rw-r--r--xtask/src/pre_commit.rs8
7 files changed, 310 insertions, 171 deletions
diff --git a/xtask/src/ast_src.rs b/xtask/src/ast_src.rs
index 67d1f41bc..2d9ae904b 100644
--- a/xtask/src/ast_src.rs
+++ b/xtask/src/ast_src.rs
@@ -120,6 +120,8 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc {
120 "FOR_TYPE", 120 "FOR_TYPE",
121 "IMPL_TRAIT_TYPE", 121 "IMPL_TRAIT_TYPE",
122 "DYN_TRAIT_TYPE", 122 "DYN_TRAIT_TYPE",
123 "OR_PAT",
124 "PAREN_PAT",
123 "REF_PAT", 125 "REF_PAT",
124 "BOX_PAT", 126 "BOX_PAT",
125 "BIND_PAT", 127 "BIND_PAT",
@@ -412,26 +414,28 @@ pub(crate) const AST_SRC: AstSrc = AstSrc {
412 struct MatchExpr { Expr, MatchArmList } 414 struct MatchExpr { Expr, MatchArmList }
413 struct MatchArmList: AttrsOwner { arms: [MatchArm] } 415 struct MatchArmList: AttrsOwner { arms: [MatchArm] }
414 struct MatchArm: AttrsOwner { 416 struct MatchArm: AttrsOwner {
415 pats: [Pat], 417 pat: Pat,
416 guard: MatchGuard, 418 guard: MatchGuard,
417 Expr, 419 Expr,
418 } 420 }
419 struct MatchGuard { Expr } 421 struct MatchGuard { Expr }
420 422
421 struct RecordLit { Path, RecordFieldList } 423 struct RecordLit { Path, RecordFieldList }
422 struct RecordFieldList { 424 struct RecordFieldList {
423 fields: [RecordField], 425 fields: [RecordField],
424 spread: Expr, 426 spread: Expr,
425 } 427 }
426 struct RecordField { NameRef, Expr } 428 struct RecordField { NameRef, Expr }
427 429
430 struct OrPat { pats: [Pat] }
431 struct ParenPat { Pat }
428 struct RefPat { Pat } 432 struct RefPat { Pat }
429 struct BoxPat { Pat } 433 struct BoxPat { Pat }
430 struct BindPat: NameOwner { Pat } 434 struct BindPat: NameOwner { Pat }
431 struct PlaceholderPat { } 435 struct PlaceholderPat { }
432 struct DotDotPat { } 436 struct DotDotPat { }
433 struct PathPat { Path } 437 struct PathPat { Path }
434 struct SlicePat {} 438 struct SlicePat { args: [Pat] }
435 struct RangePat {} 439 struct RangePat {}
436 struct LiteralPat { Literal } 440 struct LiteralPat { Literal }
437 441
@@ -601,6 +605,8 @@ pub(crate) const AST_SRC: AstSrc = AstSrc {
601 } 605 }
602 606
603 enum Pat { 607 enum Pat {
608 OrPat,
609 ParenPat,
604 RefPat, 610 RefPat,
605 BoxPat, 611 BoxPat,
606 BindPat, 612 BindPat,
diff --git a/xtask/src/cmd.rs b/xtask/src/cmd.rs
deleted file mode 100644
index 2027f4893..000000000
--- a/xtask/src/cmd.rs
+++ /dev/null
@@ -1,53 +0,0 @@
1use std::process::{Command, Output, Stdio};
2
3use anyhow::{Context, Result};
4
5use crate::project_root;
6
7pub struct Cmd<'a> {
8 pub unix: &'a str,
9 pub windows: &'a str,
10 pub work_dir: &'a str,
11}
12
13impl Cmd<'_> {
14 pub fn run(self) -> Result<()> {
15 if cfg!(windows) {
16 run(self.windows, self.work_dir)
17 } else {
18 run(self.unix, self.work_dir)
19 }
20 }
21 pub fn run_with_output(self) -> Result<Output> {
22 if cfg!(windows) {
23 run_with_output(self.windows, self.work_dir)
24 } else {
25 run_with_output(self.unix, self.work_dir)
26 }
27 }
28}
29
30pub fn run(cmdline: &str, dir: &str) -> Result<()> {
31 do_run(cmdline, dir, &mut |c| {
32 c.stdout(Stdio::inherit());
33 })
34 .map(|_| ())
35}
36
37pub fn run_with_output(cmdline: &str, dir: &str) -> Result<Output> {
38 do_run(cmdline, dir, &mut |_| {})
39}
40
41fn do_run(cmdline: &str, dir: &str, f: &mut dyn FnMut(&mut Command)) -> Result<Output> {
42 eprintln!("\nwill run: {}", cmdline);
43 let proj_dir = project_root().join(dir);
44 let mut args = cmdline.split_whitespace();
45 let exec = args.next().unwrap();
46 let mut cmd = Command::new(exec);
47 f(cmd.args(args).current_dir(proj_dir).stderr(Stdio::inherit()));
48 let output = cmd.output().with_context(|| format!("running `{}`", cmdline))?;
49 if !output.status.success() {
50 anyhow::bail!("`{}` exited with {}", cmdline, output.status);
51 }
52 Ok(output)
53}
diff --git a/xtask/src/install.rs b/xtask/src/install.rs
index 8c65b51e3..00bbabce4 100644
--- a/xtask/src/install.rs
+++ b/xtask/src/install.rs
@@ -2,9 +2,9 @@
2 2
3use std::{env, path::PathBuf, str}; 3use std::{env, path::PathBuf, str};
4 4
5use anyhow::{Context, Result}; 5use anyhow::{bail, format_err, Context, Result};
6 6
7use crate::cmd::{run, run_with_output, Cmd}; 7use crate::not_bash::{ls, pushd, rm, run};
8 8
9// Latest stable, feel free to send a PR if this lags behind. 9// Latest stable, feel free to send a PR if this lags behind.
10const REQUIRED_RUST_VERSION: u32 = 41; 10const REQUIRED_RUST_VERSION: u32 = 41;
@@ -24,6 +24,7 @@ pub struct ServerOpt {
24 24
25impl InstallCmd { 25impl InstallCmd {
26 pub fn run(self) -> Result<()> { 26 pub fn run(self) -> Result<()> {
27 let both = self.server.is_some() && self.client.is_some();
27 if cfg!(target_os = "macos") { 28 if cfg!(target_os = "macos") {
28 fix_path_for_mac().context("Fix path for mac")? 29 fix_path_for_mac().context("Fix path for mac")?
29 } 30 }
@@ -33,6 +34,16 @@ impl InstallCmd {
33 if let Some(client) = self.client { 34 if let Some(client) = self.client {
34 install_client(client).context("install client")?; 35 install_client(client).context("install client")?;
35 } 36 }
37 if both {
38 eprintln!(
39 "
40 Installation complete.
41
42 Add `\"rust-analyzer.raLspServerPath\": \"ra_lsp_server\",` to VS Code settings,
43 otherwise it will use the latest release from GitHub.
44"
45 )
46 }
36 Ok(()) 47 Ok(())
37 } 48 }
38} 49}
@@ -44,7 +55,7 @@ fn fix_path_for_mac() -> Result<()> {
44 const ROOT_DIR: &str = ""; 55 const ROOT_DIR: &str = "";
45 let home_dir = match env::var("HOME") { 56 let home_dir = match env::var("HOME") {
46 Ok(home) => home, 57 Ok(home) => home,
47 Err(e) => anyhow::bail!("Failed getting HOME from environment with error: {}.", e), 58 Err(e) => bail!("Failed getting HOME from environment with error: {}.", e),
48 }; 59 };
49 60
50 [ROOT_DIR, &home_dir] 61 [ROOT_DIR, &home_dir]
@@ -58,7 +69,7 @@ fn fix_path_for_mac() -> Result<()> {
58 if !vscode_path.is_empty() { 69 if !vscode_path.is_empty() {
59 let vars = match env::var_os("PATH") { 70 let vars = match env::var_os("PATH") {
60 Some(path) => path, 71 Some(path) => path,
61 None => anyhow::bail!("Could not get PATH variable from env."), 72 None => bail!("Could not get PATH variable from env."),
62 }; 73 };
63 74
64 let mut paths = env::split_paths(&vars).collect::<Vec<_>>(); 75 let mut paths = env::split_paths(&vars).collect::<Vec<_>>();
@@ -71,91 +82,63 @@ fn fix_path_for_mac() -> Result<()> {
71} 82}
72 83
73fn install_client(ClientOpt::VsCode: ClientOpt) -> Result<()> { 84fn install_client(ClientOpt::VsCode: ClientOpt) -> Result<()> {
74 let npm_version = Cmd { 85 let _dir = pushd("./editors/code");
75 unix: r"npm --version",
76 windows: r"cmd.exe /c npm --version",
77 work_dir: "./editors/code",
78 }
79 .run();
80 86
81 if npm_version.is_err() { 87 let find_code = |f: fn(&str) -> bool| -> Result<&'static str> {
82 eprintln!("\nERROR: `npm --version` failed, `npm` is required to build the VS Code plugin") 88 ["code", "code-insiders", "codium", "code-oss"]
83 } 89 .iter()
90 .copied()
91 .find(|bin| f(bin))
92 .ok_or_else(|| {
93 format_err!("Can't execute `code --version`. Perhaps it is not in $PATH?")
94 })
95 };
84 96
85 Cmd { unix: r"npm install", windows: r"cmd.exe /c npm install", work_dir: "./editors/code" } 97 let installed_extensions;
86 .run()?; 98 if cfg!(unix) {
87 Cmd { 99 run!("npm --version").context("`npm` is required to build the VS Code plugin")?;
88 unix: r"npm run package --scripts-prepend-node-path", 100 run!("npm install")?;
89 windows: r"cmd.exe /c npm run package",
90 work_dir: "./editors/code",
91 }
92 .run()?;
93 101
94 let code_binary = ["code", "code-insiders", "codium", "code-oss"].iter().find(|bin| { 102 let vsix_pkg = {
95 Cmd { 103 rm("*.vsix")?;
96 unix: &format!("{} --version", bin), 104 run!("npm run package --scripts-prepend-node-path")?;
97 windows: &format!("cmd.exe /c {}.cmd --version", bin), 105 ls("*.vsix")?.pop().unwrap()
98 work_dir: "./editors/code", 106 };
99 }
100 .run()
101 .is_ok()
102 });
103 107
104 let code_binary = match code_binary { 108 let code = find_code(|bin| run!("{} --version", bin).is_ok())?;
105 Some(it) => it, 109 run!("{} --install-extension {} --force", code, vsix_pkg.display())?;
106 None => anyhow::bail!("Can't execute `code --version`. Perhaps it is not in $PATH?"), 110 installed_extensions = run!("{} --list-extensions", code; echo = false)?;
107 }; 111 } else {
112 run!("cmd.exe /c npm --version")
113 .context("`npm` is required to build the VS Code plugin")?;
114 run!("cmd.exe /c npm install")?;
115
116 let vsix_pkg = {
117 rm("*.vsix")?;
118 run!("cmd.exe /c npm run package")?;
119 ls("*.vsix")?.pop().unwrap()
120 };
108 121
109 Cmd { 122 let code = find_code(|bin| run!("cmd.exe /c {}.cmd --version", bin).is_ok())?;
110 unix: &format!(r"{} --install-extension ./rust-analyzer-0.1.0.vsix --force", code_binary), 123 run!(r"cmd.exe /c {}.cmd --install-extension {} --force", code, vsix_pkg.display())?;
111 windows: &format!( 124 installed_extensions = run!("cmd.exe /c {}.cmd --list-extensions", code; echo = false)?;
112 r"cmd.exe /c {}.cmd --install-extension ./rust-analyzer-0.1.0.vsix --force",
113 code_binary
114 ),
115 work_dir: "./editors/code",
116 } 125 }
117 .run()?;
118
119 let installed_extensions = {
120 let output = Cmd {
121 unix: &format!(r"{} --list-extensions", code_binary),
122 windows: &format!(r"cmd.exe /c {}.cmd --list-extensions", code_binary),
123 work_dir: ".",
124 }
125 .run_with_output()?;
126 String::from_utf8(output.stdout)?
127 };
128 126
129 if !installed_extensions.contains("rust-analyzer") { 127 if !installed_extensions.contains("rust-analyzer") {
130 anyhow::bail!( 128 bail!(
131 "Could not install the Visual Studio Code extension. \ 129 "Could not install the Visual Studio Code extension. \
132 Please make sure you have at least NodeJS 10.x together with the latest version of VS Code installed and try again." 130 Please make sure you have at least NodeJS 12.x together with the latest version of VS Code installed and try again."
133 ); 131 );
134 } 132 }
135 133
136 if installed_extensions.contains("ra-lsp") {
137 Cmd {
138 unix: &format!(r"{} --uninstall-extension matklad.ra-lsp", code_binary),
139 windows: &format!(
140 r"cmd.exe /c {}.cmd --uninstall-extension matklad.ra-lsp",
141 code_binary
142 ),
143 work_dir: "./editors/code",
144 }
145 .run()?;
146 }
147
148 Ok(()) 134 Ok(())
149} 135}
150 136
151fn install_server(opts: ServerOpt) -> Result<()> { 137fn install_server(opts: ServerOpt) -> Result<()> {
152 let mut old_rust = false; 138 let mut old_rust = false;
153 if let Ok(output) = run_with_output("cargo --version", ".") { 139 if let Ok(stdout) = run!("cargo --version") {
154 if let Ok(stdout) = String::from_utf8(output.stdout) { 140 if !check_version(&stdout, REQUIRED_RUST_VERSION) {
155 println!("{}", stdout); 141 old_rust = true;
156 if !check_version(&stdout, REQUIRED_RUST_VERSION) {
157 old_rust = true;
158 }
159 } 142 }
160 } 143 }
161 144
@@ -166,20 +149,17 @@ fn install_server(opts: ServerOpt) -> Result<()> {
166 ) 149 )
167 } 150 }
168 151
169 let res = if opts.jemalloc { 152 let jemalloc = if opts.jemalloc { "--features jemalloc" } else { "" };
170 run("cargo install --path crates/ra_lsp_server --locked --force --features jemalloc", ".") 153 let res = run!("cargo install --path crates/ra_lsp_server --locked --force {}", jemalloc);
171 } else {
172 run("cargo install --path crates/ra_lsp_server --locked --force", ".")
173 };
174 154
175 if res.is_err() && old_rust { 155 if res.is_err() && old_rust {
176 eprintln!( 156 eprintln!(
177 "\nWARNING: at least rust 1.{}.0 is required to compile rust-analyzer\n", 157 "\nWARNING: at least rust 1.{}.0 is required to compile rust-analyzer\n",
178 REQUIRED_RUST_VERSION, 158 REQUIRED_RUST_VERSION,
179 ) 159 );
180 } 160 }
181 161
182 res 162 res.map(drop)
183} 163}
184 164
185fn check_version(version_output: &str, min_minor_version: u32) -> bool { 165fn check_version(version_output: &str, min_minor_version: u32) -> bool {
diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs
index 8fdf43e4a..2bcd76d60 100644
--- a/xtask/src/lib.rs
+++ b/xtask/src/lib.rs
@@ -1,6 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3mod cmd; 3pub mod not_bash;
4pub mod install; 4pub mod install;
5pub mod pre_commit; 5pub mod pre_commit;
6 6
@@ -9,13 +9,16 @@ mod ast_src;
9 9
10use anyhow::Context; 10use anyhow::Context;
11use std::{ 11use std::{
12 env, fs, 12 env,
13 io::Write, 13 io::Write,
14 path::{Path, PathBuf}, 14 path::{Path, PathBuf},
15 process::{Command, Stdio}, 15 process::{Command, Stdio},
16}; 16};
17 17
18use crate::{cmd::run, codegen::Mode}; 18use crate::{
19 codegen::Mode,
20 not_bash::{fs2, pushd, rm_rf, run},
21};
19 22
20pub use anyhow::Result; 23pub use anyhow::Result;
21 24
@@ -35,9 +38,9 @@ pub fn run_rustfmt(mode: Mode) -> Result<()> {
35 ensure_rustfmt()?; 38 ensure_rustfmt()?;
36 39
37 if mode == Mode::Verify { 40 if mode == Mode::Verify {
38 run(&format!("rustup run {} -- cargo fmt -- --check", TOOLCHAIN), ".")?; 41 run!("rustup run {} -- cargo fmt -- --check", TOOLCHAIN)?;
39 } else { 42 } else {
40 run(&format!("rustup run {} -- cargo fmt", TOOLCHAIN), ".")?; 43 run!("rustup run {} -- cargo fmt", TOOLCHAIN)?;
41 } 44 }
42 Ok(()) 45 Ok(())
43} 46}
@@ -67,8 +70,9 @@ fn ensure_rustfmt() -> Result<()> {
67 Ok(status) if status.success() => return Ok(()), 70 Ok(status) if status.success() => return Ok(()),
68 _ => (), 71 _ => (),
69 }; 72 };
70 run(&format!("rustup toolchain install {}", TOOLCHAIN), ".")?; 73 run!("rustup toolchain install {}", TOOLCHAIN)?;
71 run(&format!("rustup component add rustfmt --toolchain {}", TOOLCHAIN), ".") 74 run!("rustup component add rustfmt --toolchain {}", TOOLCHAIN)?;
75 Ok(())
72} 76}
73 77
74pub fn run_clippy() -> Result<()> { 78pub fn run_clippy() -> Result<()> {
@@ -89,34 +93,28 @@ pub fn run_clippy() -> Result<()> {
89 "clippy::nonminimal_bool", 93 "clippy::nonminimal_bool",
90 "clippy::redundant_pattern_matching", 94 "clippy::redundant_pattern_matching",
91 ]; 95 ];
92 run( 96 run!(
93 &format!( 97 "rustup run {} -- cargo clippy --all-features --all-targets -- -A {}",
94 "rustup run {} -- cargo clippy --all-features --all-targets -- -A {}", 98 TOOLCHAIN,
95 TOOLCHAIN, 99 allowed_lints.join(" -A ")
96 allowed_lints.join(" -A ")
97 ),
98 ".",
99 )?; 100 )?;
100 Ok(()) 101 Ok(())
101} 102}
102 103
103fn install_clippy() -> Result<()> { 104fn install_clippy() -> Result<()> {
104 run(&format!("rustup toolchain install {}", TOOLCHAIN), ".")?; 105 run!("rustup toolchain install {}", TOOLCHAIN)?;
105 run(&format!("rustup component add clippy --toolchain {}", TOOLCHAIN), ".") 106 run!("rustup component add clippy --toolchain {}", TOOLCHAIN)?;
107 Ok(())
106} 108}
107 109
108pub fn run_fuzzer() -> Result<()> { 110pub fn run_fuzzer() -> Result<()> {
109 match Command::new("cargo") 111 let _d = pushd("./crates/ra_syntax");
110 .args(&["fuzz", "--help"]) 112 if run!("cargo fuzz --help").is_err() {
111 .stderr(Stdio::null()) 113 run!("cargo install cargo-fuzz")?;
112 .stdout(Stdio::null())
113 .status()
114 {
115 Ok(status) if status.success() => (),
116 _ => run("cargo install cargo-fuzz", ".")?,
117 }; 114 };
118 115
119 run("rustup run nightly -- cargo fuzz run parser", "./crates/ra_syntax") 116 run!("rustup run nightly -- cargo fuzz run parser")?;
117 Ok(())
120} 118}
121 119
122/// Cleans the `./target` dir after the build such that only 120/// Cleans the `./target` dir after the build such that only
@@ -138,7 +136,7 @@ pub fn run_pre_cache() -> Result<()> {
138 } 136 }
139 } 137 }
140 138
141 fs::remove_file("./target/.rustc_info.json")?; 139 fs2::remove_file("./target/.rustc_info.json")?;
142 let to_delete = ["ra_", "heavy_test"]; 140 let to_delete = ["ra_", "heavy_test"];
143 for &dir in ["./target/debug/deps", "target/debug/.fingerprint"].iter() { 141 for &dir in ["./target/debug/deps", "target/debug/.fingerprint"].iter() {
144 for entry in Path::new(dir).read_dir()? { 142 for entry in Path::new(dir).read_dir()? {
@@ -152,7 +150,45 @@ pub fn run_pre_cache() -> Result<()> {
152 Ok(()) 150 Ok(())
153} 151}
154 152
155fn rm_rf(path: &Path) -> Result<()> { 153pub fn run_release(dry_run: bool) -> Result<()> {
156 if path.is_file() { fs::remove_file(path) } else { fs::remove_dir_all(path) } 154 if !dry_run {
157 .with_context(|| format!("failed to remove {:?}", path)) 155 run!("git switch release")?;
156 run!("git fetch upstream")?;
157 run!("git reset --hard upstream/master")?;
158 run!("git push")?;
159 }
160
161 let website_root = project_root().join("../rust-analyzer.github.io");
162 let changelog_dir = website_root.join("./thisweek/_posts");
163
164 let today = run!("date --iso")?;
165 let commit = run!("git rev-parse HEAD")?;
166 let changelog_n = fs2::read_dir(changelog_dir.as_path())?.count();
167
168 let contents = format!(
169 "\
170= Changelog #{}
171:sectanchors:
172:page-layout: post
173
174Commit: commit:{}[] +
175Release: release:{}[]
176
177== New Features
178
179* pr:[] .
180
181== Fixes
182
183== Internal Improvements
184",
185 changelog_n, commit, today
186 );
187
188 let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n));
189 fs2::write(&path, &contents)?;
190
191 fs2::copy(project_root().join("./docs/user/readme.adoc"), website_root.join("manual.adoc"))?;
192
193 Ok(())
158} 194}
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index c347de9ab..a7dffe2cc 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -14,7 +14,7 @@ use pico_args::Arguments;
14use xtask::{ 14use xtask::{
15 codegen::{self, Mode}, 15 codegen::{self, Mode},
16 install::{ClientOpt, InstallCmd, ServerOpt}, 16 install::{ClientOpt, InstallCmd, ServerOpt},
17 pre_commit, run_clippy, run_fuzzer, run_pre_cache, run_rustfmt, Result, 17 pre_commit, run_clippy, run_fuzzer, run_pre_cache, run_release, run_rustfmt, Result,
18}; 18};
19 19
20fn main() -> Result<()> { 20fn main() -> Result<()> {
@@ -92,6 +92,11 @@ FLAGS:
92 args.finish()?; 92 args.finish()?;
93 run_pre_cache() 93 run_pre_cache()
94 } 94 }
95 "release" => {
96 let dry_run = args.contains("--dry-run");
97 args.finish()?;
98 run_release(dry_run)
99 }
95 _ => { 100 _ => {
96 eprintln!( 101 eprintln!(
97 "\ 102 "\
diff --git a/xtask/src/not_bash.rs b/xtask/src/not_bash.rs
new file mode 100644
index 000000000..3e30e7279
--- /dev/null
+++ b/xtask/src/not_bash.rs
@@ -0,0 +1,165 @@
1//! A bad shell -- small cross platform module for writing glue code
2use std::{
3 cell::RefCell,
4 env,
5 ffi::OsStr,
6 fs,
7 path::{Path, PathBuf},
8 process::{Command, Stdio},
9};
10
11use anyhow::{bail, Context, Result};
12
13pub mod fs2 {
14 use std::{fs, path::Path};
15
16 use anyhow::{Context, Result};
17
18 pub fn read_dir<P: AsRef<Path>>(path: P) -> Result<fs::ReadDir> {
19 let path = path.as_ref();
20 fs::read_dir(path).with_context(|| format!("Failed to read {}", path.display()))
21 }
22
23 pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
24 let path = path.as_ref();
25 fs::write(path, contents).with_context(|| format!("Failed to write {}", path.display()))
26 }
27
28 pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<u64> {
29 let from = from.as_ref();
30 let to = to.as_ref();
31 fs::copy(from, to)
32 .with_context(|| format!("Failed to copy {} to {}", from.display(), to.display()))
33 }
34
35 pub fn remove_file<P: AsRef<Path>>(path: P) -> Result<()> {
36 let path = path.as_ref();
37 fs::remove_file(path).with_context(|| format!("Failed to remove file {}", path.display()))
38 }
39
40 pub fn remove_dir_all<P: AsRef<Path>>(path: P) -> Result<()> {
41 let path = path.as_ref();
42 fs::remove_dir_all(path).with_context(|| format!("Failed to remove dir {}", path.display()))
43 }
44}
45
46macro_rules! _run {
47 ($($expr:expr),*) => {
48 run!($($expr),*; echo = true)
49 };
50 ($($expr:expr),* ; echo = $echo:expr) => {
51 $crate::not_bash::run_process(format!($($expr),*), $echo)
52 };
53}
54pub(crate) use _run as run;
55
56pub struct Pushd {
57 _p: (),
58}
59
60pub fn pushd(path: impl Into<PathBuf>) -> Pushd {
61 Env::with(|env| env.pushd(path.into()));
62 Pushd { _p: () }
63}
64
65impl Drop for Pushd {
66 fn drop(&mut self) {
67 Env::with(|env| env.popd())
68 }
69}
70
71pub fn rm(glob: &str) -> Result<()> {
72 let cwd = Env::with(|env| env.cwd());
73 ls(glob)?.into_iter().try_for_each(|it| fs::remove_file(cwd.join(it)))?;
74 Ok(())
75}
76
77pub fn rm_rf(path: impl AsRef<Path>) -> Result<()> {
78 let path = path.as_ref();
79 if path.is_file() {
80 fs2::remove_file(path)
81 } else {
82 fs2::remove_dir_all(path)
83 }
84}
85
86pub fn ls(glob: &str) -> Result<Vec<PathBuf>> {
87 let cwd = Env::with(|env| env.cwd());
88 let mut res = Vec::new();
89 for entry in fs::read_dir(&cwd)? {
90 let entry = entry?;
91 if matches(&entry.file_name(), glob) {
92 let path = entry.path();
93 let path = path.strip_prefix(&cwd).unwrap();
94 res.push(path.to_path_buf())
95 }
96 }
97 return Ok(res);
98
99 fn matches(file_name: &OsStr, glob: &str) -> bool {
100 assert!(glob.starts_with('*'));
101 file_name.to_string_lossy().ends_with(&glob[1..])
102 }
103}
104
105#[doc(hidden)]
106pub fn run_process(cmd: String, echo: bool) -> Result<String> {
107 run_process_inner(&cmd, echo).with_context(|| format!("process `{}` failed", cmd))
108}
109
110fn run_process_inner(cmd: &str, echo: bool) -> Result<String> {
111 let cwd = Env::with(|env| env.cwd());
112 let mut args = shelx(cmd);
113 let binary = args.remove(0);
114
115 if echo {
116 println!("> {}", cmd)
117 }
118
119 let output = Command::new(binary)
120 .args(args)
121 .current_dir(cwd)
122 .stdin(Stdio::null())
123 .stderr(Stdio::inherit())
124 .output()?;
125 let stdout = String::from_utf8(output.stdout)?;
126
127 if echo {
128 print!("{}", stdout)
129 }
130
131 if !output.status.success() {
132 bail!("{}", output.status)
133 }
134
135 Ok(stdout.trim().to_string())
136}
137
138// FIXME: some real shell lexing here
139fn shelx(cmd: &str) -> Vec<String> {
140 cmd.split_whitespace().map(|it| it.to_string()).collect()
141}
142
143#[derive(Default)]
144struct Env {
145 pushd_stack: Vec<PathBuf>,
146}
147
148impl Env {
149 fn with<F: FnOnce(&mut Env) -> T, T>(f: F) -> T {
150 thread_local! {
151 static ENV: RefCell<Env> = Default::default();
152 }
153 ENV.with(|it| f(&mut *it.borrow_mut()))
154 }
155
156 fn pushd(&mut self, dir: PathBuf) {
157 self.pushd_stack.push(dir)
158 }
159 fn popd(&mut self) {
160 self.pushd_stack.pop().unwrap();
161 }
162 fn cwd(&self) -> PathBuf {
163 self.pushd_stack.last().cloned().unwrap_or_else(|| env::current_dir().unwrap())
164 }
165}
diff --git a/xtask/src/pre_commit.rs b/xtask/src/pre_commit.rs
index 88e868ca6..056f34acf 100644
--- a/xtask/src/pre_commit.rs
+++ b/xtask/src/pre_commit.rs
@@ -4,18 +4,18 @@ use std::{fs, path::PathBuf};
4 4
5use anyhow::{bail, Result}; 5use anyhow::{bail, Result};
6 6
7use crate::{cmd::run_with_output, project_root, run, run_rustfmt, Mode}; 7use crate::{not_bash::run, project_root, run_rustfmt, Mode};
8 8
9// FIXME: if there are changed `.ts` files, also reformat TypeScript (by 9// FIXME: if there are changed `.ts` files, also reformat TypeScript (by
10// shelling out to `npm fmt`). 10// shelling out to `npm fmt`).
11pub fn run_hook() -> Result<()> { 11pub fn run_hook() -> Result<()> {
12 run_rustfmt(Mode::Overwrite)?; 12 run_rustfmt(Mode::Overwrite)?;
13 13
14 let diff = run_with_output("git diff --diff-filter=MAR --name-only --cached", ".")?; 14 let diff = run!("git diff --diff-filter=MAR --name-only --cached")?;
15 15
16 let root = project_root(); 16 let root = project_root();
17 for line in String::from_utf8(diff.stdout)?.lines() { 17 for line in diff.lines() {
18 run(&format!("git update-index --add {}", root.join(line).to_string_lossy()), ".")?; 18 run!("git update-index --add {}", root.join(line).display())?;
19 } 19 }
20 20
21 Ok(()) 21 Ok(())