aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock31
-rw-r--r--crates/ide/src/completion.rs49
-rw-r--r--crates/ide/src/completion/completion_context.rs27
-rw-r--r--crates/ide/src/completion/patterns.rs48
-rw-r--r--crates/ide/src/completion/test_utils.rs12
-rw-r--r--crates/vfs-notify/src/lib.rs17
-rw-r--r--docs/dev/lsp-extensions.md3
-rw-r--r--docs/dev/style.md61
-rw-r--r--docs/user/manual.adoc11
-rw-r--r--xtask/Cargo.toml2
-rw-r--r--xtask/src/codegen.rs21
-rw-r--r--xtask/src/codegen/gen_assists_docs.rs2
-rw-r--r--xtask/src/codegen/gen_features.rs12
-rw-r--r--xtask/src/codegen/gen_syntax.rs15
-rw-r--r--xtask/src/dist.rs33
-rw-r--r--xtask/src/install.rs46
-rw-r--r--xtask/src/lib.rs52
-rw-r--r--xtask/src/main.rs4
-rw-r--r--xtask/src/metrics.rs39
-rw-r--r--xtask/src/not_bash.rs169
-rw-r--r--xtask/src/pre_cache.rs5
-rw-r--r--xtask/src/pre_commit.rs8
-rw-r--r--xtask/src/release.rs60
-rw-r--r--xtask/tests/tidy.rs36
24 files changed, 391 insertions, 372 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d470d84f2..fa56ea3be 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -422,12 +422,6 @@ dependencies = [
422] 422]
423 423
424[[package]] 424[[package]]
425name = "fs-err"
426version = "2.5.0"
427source = "registry+https://github.com/rust-lang/crates.io-index"
428checksum = "bcd1163ae48bda72a20ae26d66a04d3094135cadab911cff418ae5e33f253431"
429
430[[package]]
431name = "fsevent" 425name = "fsevent"
432version = "2.0.2" 426version = "2.0.2"
433source = "registry+https://github.com/rust-lang/crates.io-index" 427source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1447,18 +1441,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
1447 1441
1448[[package]] 1442[[package]]
1449name = "serde" 1443name = "serde"
1450version = "1.0.116" 1444version = "1.0.117"
1451source = "registry+https://github.com/rust-lang/crates.io-index" 1445source = "registry+https://github.com/rust-lang/crates.io-index"
1452checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5" 1446checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
1453dependencies = [ 1447dependencies = [
1454 "serde_derive", 1448 "serde_derive",
1455] 1449]
1456 1450
1457[[package]] 1451[[package]]
1458name = "serde_derive" 1452name = "serde_derive"
1459version = "1.0.116" 1453version = "1.0.117"
1460source = "registry+https://github.com/rust-lang/crates.io-index" 1454source = "registry+https://github.com/rust-lang/crates.io-index"
1461checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8" 1455checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
1462dependencies = [ 1456dependencies = [
1463 "proc-macro2", 1457 "proc-macro2",
1464 "quote", 1458 "quote",
@@ -1921,16 +1915,31 @@ dependencies = [
1921] 1915]
1922 1916
1923[[package]] 1917[[package]]
1918name = "xshell"
1919version = "0.1.2"
1920source = "registry+https://github.com/rust-lang/crates.io-index"
1921checksum = "3e52c34730b631e3aea95f5f650e7457a9219f4c62df9ed2d1106ddacb0d54e2"
1922dependencies = [
1923 "xshell-macros",
1924]
1925
1926[[package]]
1927name = "xshell-macros"
1928version = "0.1.2"
1929source = "registry+https://github.com/rust-lang/crates.io-index"
1930checksum = "495f6e94cc76a22553d2a49188d7d49155c87d4b82414b749b121d69aa9c0240"
1931
1932[[package]]
1924name = "xtask" 1933name = "xtask"
1925version = "0.1.0" 1934version = "0.1.0"
1926dependencies = [ 1935dependencies = [
1927 "anyhow", 1936 "anyhow",
1928 "flate2", 1937 "flate2",
1929 "fs-err",
1930 "pico-args", 1938 "pico-args",
1931 "proc-macro2", 1939 "proc-macro2",
1932 "quote", 1940 "quote",
1933 "ungrammar", 1941 "ungrammar",
1934 "walkdir", 1942 "walkdir",
1935 "write-json", 1943 "write-json",
1944 "xshell",
1936] 1945]
diff --git a/crates/ide/src/completion.rs b/crates/ide/src/completion.rs
index b0e35b2bd..69e875014 100644
--- a/crates/ide/src/completion.rs
+++ b/crates/ide/src/completion.rs
@@ -112,6 +112,11 @@ pub(crate) fn completions(
112) -> Option<Completions> { 112) -> Option<Completions> {
113 let ctx = CompletionContext::new(db, position, config)?; 113 let ctx = CompletionContext::new(db, position, config)?;
114 114
115 if ctx.no_completion_required() {
116 // No work required here.
117 return None;
118 }
119
115 let mut acc = Completions::default(); 120 let mut acc = Completions::default();
116 complete_attribute::complete_attribute(&mut acc, &ctx); 121 complete_attribute::complete_attribute(&mut acc, &ctx);
117 complete_fn_param::complete_fn_param(&mut acc, &ctx); 122 complete_fn_param::complete_fn_param(&mut acc, &ctx);
@@ -157,6 +162,23 @@ mod tests {
157 panic!("completion detail not found: {}", expected.detail) 162 panic!("completion detail not found: {}", expected.detail)
158 } 163 }
159 164
165 fn check_no_completion(ra_fixture: &str) {
166 let (analysis, position) = fixture::position(ra_fixture);
167 let config = CompletionConfig::default();
168 analysis.completions(&config, position).unwrap();
169
170 let completions: Option<Vec<String>> = analysis
171 .completions(&config, position)
172 .unwrap()
173 .and_then(|completions| if completions.is_empty() { None } else { Some(completions) })
174 .map(|completions| {
175 completions.into_iter().map(|completion| format!("{:?}", completion)).collect()
176 });
177
178 // `assert_eq` instead of `assert!(completions.is_none())` to get the list of completions if test will panic.
179 assert_eq!(completions, None, "Completions were generated, but weren't expected");
180 }
181
160 #[test] 182 #[test]
161 fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() { 183 fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() {
162 check_detail_and_documentation( 184 check_detail_and_documentation(
@@ -208,4 +230,31 @@ mod tests {
208 DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" }, 230 DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" },
209 ); 231 );
210 } 232 }
233
234 #[test]
235 fn test_no_completions_required() {
236 // There must be no hint for 'in' keyword.
237 check_no_completion(
238 r#"
239 fn foo() {
240 for i i<|>
241 }
242 "#,
243 );
244 // After 'in' keyword hints may be spawned.
245 check_detail_and_documentation(
246 r#"
247 /// Do the foo
248 fn foo() -> &'static str { "foo" }
249
250 fn bar() {
251 for c in fo<|>
252 }
253 "#,
254 DetailAndDocumentation {
255 detail: "fn foo() -> &'static str",
256 documentation: "Do the foo",
257 },
258 );
259 }
211} 260}
diff --git a/crates/ide/src/completion/completion_context.rs b/crates/ide/src/completion/completion_context.rs
index 8dea8a4bf..d9f90477c 100644
--- a/crates/ide/src/completion/completion_context.rs
+++ b/crates/ide/src/completion/completion_context.rs
@@ -16,10 +16,11 @@ use crate::{
16 call_info::ActiveParameter, 16 call_info::ActiveParameter,
17 completion::{ 17 completion::{
18 patterns::{ 18 patterns::{
19 has_bind_pat_parent, has_block_expr_parent, has_field_list_parent, 19 fn_is_prev, for_is_prev2, has_bind_pat_parent, has_block_expr_parent,
20 has_impl_as_prev_sibling, has_impl_parent, has_item_list_or_source_file_parent, 20 has_field_list_parent, has_impl_as_prev_sibling, has_impl_parent,
21 has_ref_parent, has_trait_as_prev_sibling, has_trait_parent, if_is_prev, 21 has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling,
22 is_in_loop_body, is_match_arm, unsafe_is_prev, 22 has_trait_parent, if_is_prev, inside_impl_trait_block, is_in_loop_body, is_match_arm,
23 unsafe_is_prev,
23 }, 24 },
24 CompletionConfig, 25 CompletionConfig,
25 }, 26 },
@@ -86,11 +87,14 @@ pub(crate) struct CompletionContext<'a> {
86 pub(super) in_loop_body: bool, 87 pub(super) in_loop_body: bool,
87 pub(super) has_trait_parent: bool, 88 pub(super) has_trait_parent: bool,
88 pub(super) has_impl_parent: bool, 89 pub(super) has_impl_parent: bool,
90 pub(super) inside_impl_trait_block: bool,
89 pub(super) has_field_list_parent: bool, 91 pub(super) has_field_list_parent: bool,
90 pub(super) trait_as_prev_sibling: bool, 92 pub(super) trait_as_prev_sibling: bool,
91 pub(super) impl_as_prev_sibling: bool, 93 pub(super) impl_as_prev_sibling: bool,
92 pub(super) is_match_arm: bool, 94 pub(super) is_match_arm: bool,
93 pub(super) has_item_list_or_source_file_parent: bool, 95 pub(super) has_item_list_or_source_file_parent: bool,
96 pub(super) for_is_prev2: bool,
97 pub(super) fn_is_prev: bool,
94 pub(super) locals: Vec<(String, Local)>, 98 pub(super) locals: Vec<(String, Local)>,
95} 99}
96 100
@@ -168,12 +172,15 @@ impl<'a> CompletionContext<'a> {
168 block_expr_parent: false, 172 block_expr_parent: false,
169 has_trait_parent: false, 173 has_trait_parent: false,
170 has_impl_parent: false, 174 has_impl_parent: false,
175 inside_impl_trait_block: false,
171 has_field_list_parent: false, 176 has_field_list_parent: false,
172 trait_as_prev_sibling: false, 177 trait_as_prev_sibling: false,
173 impl_as_prev_sibling: false, 178 impl_as_prev_sibling: false,
174 if_is_prev: false, 179 if_is_prev: false,
175 is_match_arm: false, 180 is_match_arm: false,
176 has_item_list_or_source_file_parent: false, 181 has_item_list_or_source_file_parent: false,
182 for_is_prev2: false,
183 fn_is_prev: false,
177 locals, 184 locals,
178 }; 185 };
179 186
@@ -221,6 +228,15 @@ impl<'a> CompletionContext<'a> {
221 Some(ctx) 228 Some(ctx)
222 } 229 }
223 230
231 /// Checks whether completions in that particular case don't make much sense.
232 /// Examples:
233 /// - `fn <|>` -- we expect function name, it's unlikely that "hint" will be helpful.
234 /// Exception for this case is `impl Trait for Foo`, where we would like to hint trait method names.
235 /// - `for _ i<|>` -- obviously, it'll be "in" keyword.
236 pub(crate) fn no_completion_required(&self) -> bool {
237 (self.fn_is_prev && !self.inside_impl_trait_block) || self.for_is_prev2
238 }
239
224 /// The range of the identifier that is being completed. 240 /// The range of the identifier that is being completed.
225 pub(crate) fn source_range(&self) -> TextRange { 241 pub(crate) fn source_range(&self) -> TextRange {
226 // check kind of macro-expanded token, but use range of original token 242 // check kind of macro-expanded token, but use range of original token
@@ -244,6 +260,7 @@ impl<'a> CompletionContext<'a> {
244 self.in_loop_body = is_in_loop_body(syntax_element.clone()); 260 self.in_loop_body = is_in_loop_body(syntax_element.clone());
245 self.has_trait_parent = has_trait_parent(syntax_element.clone()); 261 self.has_trait_parent = has_trait_parent(syntax_element.clone());
246 self.has_impl_parent = has_impl_parent(syntax_element.clone()); 262 self.has_impl_parent = has_impl_parent(syntax_element.clone());
263 self.inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone());
247 self.has_field_list_parent = has_field_list_parent(syntax_element.clone()); 264 self.has_field_list_parent = has_field_list_parent(syntax_element.clone());
248 self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone()); 265 self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone());
249 self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); 266 self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone());
@@ -253,6 +270,8 @@ impl<'a> CompletionContext<'a> {
253 self.mod_declaration_under_caret = 270 self.mod_declaration_under_caret =
254 find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset) 271 find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset)
255 .filter(|module| module.item_list().is_none()); 272 .filter(|module| module.item_list().is_none());
273 self.for_is_prev2 = for_is_prev2(syntax_element.clone());
274 self.fn_is_prev = fn_is_prev(syntax_element.clone());
256 } 275 }
257 276
258 fn fill( 277 fn fill(
diff --git a/crates/ide/src/completion/patterns.rs b/crates/ide/src/completion/patterns.rs
index b17ddf133..cf6d5947d 100644
--- a/crates/ide/src/completion/patterns.rs
+++ b/crates/ide/src/completion/patterns.rs
@@ -9,7 +9,7 @@ use syntax::{
9}; 9};
10 10
11#[cfg(test)] 11#[cfg(test)]
12use crate::completion::test_utils::check_pattern_is_applicable; 12use crate::completion::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable};
13 13
14pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool { 14pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool {
15 not_same_range_ancestor(element) 15 not_same_range_ancestor(element)
@@ -34,6 +34,25 @@ pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool {
34fn test_has_impl_parent() { 34fn test_has_impl_parent() {
35 check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent); 35 check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent);
36} 36}
37
38pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool {
39 // Here we search `impl` keyword up through the all ancestors, unlike in `has_impl_parent`,
40 // where we only check the first parent with different text range.
41 element
42 .ancestors()
43 .find(|it| it.kind() == IMPL)
44 .map(|it| ast::Impl::cast(it).unwrap())
45 .map(|it| it.trait_().is_some())
46 .unwrap_or(false)
47}
48#[test]
49fn test_inside_impl_trait_block() {
50 check_pattern_is_applicable(r"impl Foo for Bar { f<|> }", inside_impl_trait_block);
51 check_pattern_is_applicable(r"impl Foo for Bar { fn f<|> }", inside_impl_trait_block);
52 check_pattern_is_not_applicable(r"impl A { f<|> }", inside_impl_trait_block);
53 check_pattern_is_not_applicable(r"impl A { fn f<|> }", inside_impl_trait_block);
54}
55
37pub(crate) fn has_field_list_parent(element: SyntaxElement) -> bool { 56pub(crate) fn has_field_list_parent(element: SyntaxElement) -> bool {
38 not_same_range_ancestor(element).filter(|it| it.kind() == RECORD_FIELD_LIST).is_some() 57 not_same_range_ancestor(element).filter(|it| it.kind() == RECORD_FIELD_LIST).is_some()
39} 58}
@@ -116,6 +135,33 @@ pub(crate) fn if_is_prev(element: SyntaxElement) -> bool {
116 .is_some() 135 .is_some()
117} 136}
118 137
138pub(crate) fn fn_is_prev(element: SyntaxElement) -> bool {
139 element
140 .into_token()
141 .and_then(|it| previous_non_trivia_token(it))
142 .filter(|it| it.kind() == FN_KW)
143 .is_some()
144}
145#[test]
146fn test_fn_is_prev() {
147 check_pattern_is_applicable(r"fn l<|>", fn_is_prev);
148}
149
150/// Check if the token previous to the previous one is `for`.
151/// For example, `for _ i<|>` => true.
152pub(crate) fn for_is_prev2(element: SyntaxElement) -> bool {
153 element
154 .into_token()
155 .and_then(|it| previous_non_trivia_token(it))
156 .and_then(|it| previous_non_trivia_token(it))
157 .filter(|it| it.kind() == FOR_KW)
158 .is_some()
159}
160#[test]
161fn test_for_is_prev2() {
162 check_pattern_is_applicable(r"for i i<|>", for_is_prev2);
163}
164
119#[test] 165#[test]
120fn test_if_is_prev() { 166fn test_if_is_prev() {
121 check_pattern_is_applicable(r"if l<|>", if_is_prev); 167 check_pattern_is_applicable(r"if l<|>", if_is_prev);
diff --git a/crates/ide/src/completion/test_utils.rs b/crates/ide/src/completion/test_utils.rs
index feb8cd2a6..dabbef888 100644
--- a/crates/ide/src/completion/test_utils.rs
+++ b/crates/ide/src/completion/test_utils.rs
@@ -104,6 +104,18 @@ pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -
104 .unwrap(); 104 .unwrap();
105} 105}
106 106
107pub(crate) fn check_pattern_is_not_applicable(code: &str, check: fn(SyntaxElement) -> bool) {
108 let (analysis, pos) = fixture::position(code);
109 analysis
110 .with_db(|db| {
111 let sema = Semantics::new(db);
112 let original_file = sema.parse(pos.file_id);
113 let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
114 assert!(!check(NodeOrToken::Token(token)));
115 })
116 .unwrap();
117}
118
107pub(crate) fn get_all_completion_items( 119pub(crate) fn get_all_completion_items(
108 config: CompletionConfig, 120 config: CompletionConfig,
109 code: &str, 121 code: &str,
diff --git a/crates/vfs-notify/src/lib.rs b/crates/vfs-notify/src/lib.rs
index e1e36612a..c605bcf3c 100644
--- a/crates/vfs-notify/src/lib.rs
+++ b/crates/vfs-notify/src/lib.rs
@@ -165,14 +165,15 @@ impl NotifyActor {
165 let mut res = Vec::new(); 165 let mut res = Vec::new();
166 166
167 for root in dirs.include.iter() { 167 for root in dirs.include.iter() {
168 let walkdir = WalkDir::new(root).into_iter().filter_entry(|entry| { 168 let walkdir =
169 if !entry.file_type().is_dir() { 169 WalkDir::new(root).follow_links(true).into_iter().filter_entry(|entry| {
170 return true; 170 if !entry.file_type().is_dir() {
171 } 171 return true;
172 let path = AbsPath::assert(entry.path()); 172 }
173 root == path 173 let path = AbsPath::assert(entry.path());
174 || dirs.exclude.iter().chain(&dirs.include).all(|it| it != path) 174 root == path
175 }); 175 || dirs.exclude.iter().chain(&dirs.include).all(|it| it != path)
176 });
176 177
177 let files = walkdir.filter_map(|it| it.ok()).filter_map(|entry| { 178 let files = walkdir.filter_map(|it| it.ok()).filter_map(|entry| {
178 let is_dir = entry.file_type().is_dir(); 179 let is_dir = entry.file_type().is_dir();
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 43a69d6ce..780f5cb91 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -392,7 +392,10 @@ rust-analyzer supports only one `kind`, `"cargo"`. The `args` for `"cargo"` look
392{ 392{
393 workspaceRoot?: string; 393 workspaceRoot?: string;
394 cargoArgs: string[]; 394 cargoArgs: string[];
395 cargoExtraArgs: string[];
395 executableArgs: string[]; 396 executableArgs: string[];
397 expectTest?: boolean;
398 overrideCargo?: string;
396} 399}
397``` 400```
398 401
diff --git a/docs/dev/style.md b/docs/dev/style.md
index 883a6845d..7a64a0d22 100644
--- a/docs/dev/style.md
+++ b/docs/dev/style.md
@@ -366,27 +366,66 @@ People read things from top to bottom, so place most important things first.
366 366
367Specifically, if all items except one are private, always put the non-private item on top. 367Specifically, if all items except one are private, always put the non-private item on top.
368 368
369Put `struct`s and `enum`s first, functions and impls last.
370
371Do
372
373```rust 369```rust
374// Good 370// Good
375struct Foo { 371pub(crate) fn frobnicate() {
376 bars: Vec<Bar> 372 Helper::act()
373}
374
375#[derive(Default)]
376struct Helper { stuff: i32 }
377
378impl Helper {
379 fn act(&self) {
380
381 }
382}
383
384// Not as good
385#[derive(Default)]
386struct Helper { stuff: i32 }
387
388pub(crate) fn frobnicate() {
389 Helper::act()
377} 390}
378 391
379struct Bar; 392impl Helper {
393 fn act(&self) {
394
395 }
396}
380``` 397```
381 398
382rather than 399If there's a mixture of private and public items, put public items first.
400If function bodies are folded in the editor, the source code should read as documentation for the public API.
401
402Put `struct`s and `enum`s first, functions and impls last. Order types declarations in top-down manner.
383 403
384```rust 404```rust
405// Good
406struct Parent {
407 children: Vec<Child>
408}
409
410struct Child;
411
412impl Parent {
413}
414
415impl Child {
416}
417
385// Not as good 418// Not as good
386struct Bar; 419struct Child;
387 420
388struct Foo { 421impl Child {
389 bars: Vec<Bar> 422}
423
424struct Parent {
425 children: Vec<Child>
426}
427
428impl Parent {
390} 429}
391``` 430```
392 431
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index 46e7bd091..8a3cc00df 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -260,16 +260,7 @@ If you get an error saying `No such file or directory: 'rust-analyzer'`, see the
260 260
261=== GNOME Builder 261=== GNOME Builder
262 262
263Prerequisites: You have installed the <<rust-analyzer-language-server-binary,`rust-analyzer` binary>>. 263GNOME Builder 3.37.1 and newer has native `rust-analyzer` support. If the LSP binary is not available, GNOME Builder can install it when opening a Rust file.
264
265Gnome Builder currently has support for RLS, and there's no way to configure the language server executable. A future version might support `rust-analyzer` out of the box.
266
2671. Rename, symlink or copy the `rust-analyzer` binary to `rls` and place it somewhere Builder can find (in `PATH`, or under `~/.cargo/bin`).
2682. Enable the Rust Builder plugin.
269
270==== GNOME Builder (Nightly)
271
272https://nightly.gnome.org/repo/appstream/org.gnome.Builder.flatpakref[GNOME Builder (Nightly)] has now native support for `rust-analyzer` out of the box. If the `rust-analyzer` binary is not available, GNOME Builder can install it when opening a Rust source file.
273 264
274== Non-Cargo Based Projects 265== Non-Cargo Based Projects
275 266
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
index 01a838825..2ef956485 100644
--- a/xtask/Cargo.toml
+++ b/xtask/Cargo.toml
@@ -18,5 +18,5 @@ quote = "1.0.2"
18ungrammar = "1.1.3" 18ungrammar = "1.1.3"
19walkdir = "2.3.1" 19walkdir = "2.3.1"
20write-json = "0.1.0" 20write-json = "0.1.0"
21fs-err = "2.3" 21xshell = "0.1"
22# Avoid adding more dependencies to this crate 22# Avoid adding more dependencies to this crate
diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs
index 45b17bb48..3ee4c1adf 100644
--- a/xtask/src/codegen.rs
+++ b/xtask/src/codegen.rs
@@ -15,12 +15,9 @@ use std::{
15 fmt, mem, 15 fmt, mem,
16 path::{Path, PathBuf}, 16 path::{Path, PathBuf},
17}; 17};
18use xshell::{cmd, pushenv, read_file, write_file};
18 19
19use crate::{ 20use crate::{ensure_rustfmt, project_root, Result};
20 ensure_rustfmt,
21 not_bash::{fs2, pushenv, run},
22 project_root, Result,
23};
24 21
25pub use self::{ 22pub use self::{
26 gen_assists_docs::{generate_assists_docs, generate_assists_tests}, 23 gen_assists_docs::{generate_assists_docs, generate_assists_tests},
@@ -57,7 +54,7 @@ impl CodegenCmd {
57/// A helper to update file on disk if it has changed. 54/// A helper to update file on disk if it has changed.
58/// With verify = false, 55/// With verify = false,
59fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> { 56fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> {
60 match fs2::read_to_string(path) { 57 match read_file(path) {
61 Ok(old_contents) if normalize(&old_contents) == normalize(contents) => { 58 Ok(old_contents) if normalize(&old_contents) == normalize(contents) => {
62 return Ok(()); 59 return Ok(());
63 } 60 }
@@ -67,7 +64,7 @@ fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> {
67 anyhow::bail!("`{}` is not up-to-date", path.display()); 64 anyhow::bail!("`{}` is not up-to-date", path.display());
68 } 65 }
69 eprintln!("updating {}", path.display()); 66 eprintln!("updating {}", path.display());
70 fs2::write(path, contents)?; 67 write_file(path, contents)?;
71 return Ok(()); 68 return Ok(());
72 69
73 fn normalize(s: &str) -> String { 70 fn normalize(s: &str) -> String {
@@ -77,13 +74,13 @@ fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> {
77 74
78const PREAMBLE: &str = "Generated file, do not edit by hand, see `xtask/src/codegen`"; 75const PREAMBLE: &str = "Generated file, do not edit by hand, see `xtask/src/codegen`";
79 76
80fn reformat(text: impl std::fmt::Display) -> Result<String> { 77fn reformat(text: &str) -> Result<String> {
81 let _e = pushenv("RUSTUP_TOOLCHAIN", "stable"); 78 let _e = pushenv("RUSTUP_TOOLCHAIN", "stable");
82 ensure_rustfmt()?; 79 ensure_rustfmt()?;
83 let stdout = run!( 80 let rustfmt_toml = project_root().join("rustfmt.toml");
84 "rustfmt --config-path {} --config fn_single_line=true", project_root().join("rustfmt.toml").display(); 81 let stdout = cmd!("rustfmt --config-path {rustfmt_toml} --config fn_single_line=true")
85 <text.to_string().as_bytes() 82 .stdin(text)
86 )?; 83 .read()?;
87 Ok(format!("//! {}\n\n{}\n", PREAMBLE, stdout)) 84 Ok(format!("//! {}\n\n{}\n", PREAMBLE, stdout))
88} 85}
89 86
diff --git a/xtask/src/codegen/gen_assists_docs.rs b/xtask/src/codegen/gen_assists_docs.rs
index f0ded8b87..ff307e2aa 100644
--- a/xtask/src/codegen/gen_assists_docs.rs
+++ b/xtask/src/codegen/gen_assists_docs.rs
@@ -134,7 +134,7 @@ r#####"
134 134
135 buf.push_str(&test) 135 buf.push_str(&test)
136 } 136 }
137 let buf = reformat(buf)?; 137 let buf = reformat(&buf.to_string())?;
138 codegen::update(&project_root().join("crates/assists/src/tests/generated.rs"), &buf, mode) 138 codegen::update(&project_root().join("crates/assists/src/tests/generated.rs"), &buf, mode)
139} 139}
140 140
diff --git a/xtask/src/codegen/gen_features.rs b/xtask/src/codegen/gen_features.rs
index 78268308b..3cf15ce02 100644
--- a/xtask/src/codegen/gen_features.rs
+++ b/xtask/src/codegen/gen_features.rs
@@ -3,15 +3,13 @@ use std::path::{Path, PathBuf};
3 3
4use quote::quote; 4use quote::quote;
5use walkdir::WalkDir; 5use walkdir::WalkDir;
6use xshell::{cmd, read_file};
6 7
7use crate::{ 8use crate::codegen::{project_root, reformat, update, Mode, Result};
8 codegen::{project_root, reformat, update, Mode, Result},
9 not_bash::{fs2, run},
10};
11 9
12pub fn generate_features(mode: Mode) -> Result<()> { 10pub fn generate_features(mode: Mode) -> Result<()> {
13 if !Path::new("./target/rust").exists() { 11 if !Path::new("./target/rust").exists() {
14 run!("git clone https://github.com/rust-lang/rust ./target/rust")?; 12 cmd!("git clone https://github.com/rust-lang/rust ./target/rust").run()?;
15 } 13 }
16 14
17 let contents = generate_descriptor("./target/rust/src/doc/unstable-book/src".into())?; 15 let contents = generate_descriptor("./target/rust/src/doc/unstable-book/src".into())?;
@@ -34,7 +32,7 @@ fn generate_descriptor(src_dir: PathBuf) -> Result<String> {
34 .map(|entry| { 32 .map(|entry| {
35 let path = entry.path(); 33 let path = entry.path();
36 let feature_ident = path.file_stem().unwrap().to_str().unwrap().replace("-", "_"); 34 let feature_ident = path.file_stem().unwrap().to_str().unwrap().replace("-", "_");
37 let doc = fs2::read_to_string(path).unwrap(); 35 let doc = read_file(path).unwrap();
38 36
39 quote! { LintCompletion { label: #feature_ident, description: #doc } } 37 quote! { LintCompletion { label: #feature_ident, description: #doc } }
40 }); 38 });
@@ -46,5 +44,5 @@ fn generate_descriptor(src_dir: PathBuf) -> Result<String> {
46 #(#definitions),* 44 #(#definitions),*
47 ]; 45 ];
48 }; 46 };
49 reformat(ts) 47 reformat(&ts.to_string())
50} 48}
diff --git a/xtask/src/codegen/gen_syntax.rs b/xtask/src/codegen/gen_syntax.rs
index 733493fef..02f4095ce 100644
--- a/xtask/src/codegen/gen_syntax.rs
+++ b/xtask/src/codegen/gen_syntax.rs
@@ -61,10 +61,13 @@ fn generate_tokens(grammar: &AstSrc) -> Result<String> {
61 } 61 }
62 }); 62 });
63 63
64 let pretty = reformat(quote! { 64 let pretty = reformat(
65 use crate::{SyntaxKind::{self, *}, SyntaxToken, ast::AstToken}; 65 &quote! {
66 #(#tokens)* 66 use crate::{SyntaxKind::{self, *}, SyntaxToken, ast::AstToken};
67 })? 67 #(#tokens)*
68 }
69 .to_string(),
70 )?
68 .replace("#[derive", "\n#[derive"); 71 .replace("#[derive", "\n#[derive");
69 Ok(pretty) 72 Ok(pretty)
70} 73}
@@ -261,7 +264,7 @@ fn generate_nodes(kinds: KindsSrc<'_>, grammar: &AstSrc) -> Result<String> {
261 } 264 }
262 } 265 }
263 266
264 let pretty = reformat(res)?; 267 let pretty = reformat(&res)?;
265 Ok(pretty) 268 Ok(pretty)
266} 269}
267 270
@@ -383,7 +386,7 @@ fn generate_syntax_kinds(grammar: KindsSrc<'_>) -> Result<String> {
383 } 386 }
384 }; 387 };
385 388
386 reformat(ast) 389 reformat(&ast.to_string())
387} 390}
388 391
389fn to_upper_snake_case(s: &str) -> String { 392fn to_upper_snake_case(s: &str) -> String {
diff --git a/xtask/src/dist.rs b/xtask/src/dist.rs
index aa7d94967..9e15a5a4c 100644
--- a/xtask/src/dist.rs
+++ b/xtask/src/dist.rs
@@ -1,4 +1,3 @@
1use flate2::{write::GzEncoder, Compression};
2use std::{ 1use std::{
3 env, 2 env,
4 fs::File, 3 fs::File,
@@ -7,11 +6,10 @@ use std::{
7}; 6};
8 7
9use anyhow::Result; 8use anyhow::Result;
9use flate2::{write::GzEncoder, Compression};
10use xshell::{cmd, cp, mkdir_p, pushd, read_file, rm_rf, write_file};
10 11
11use crate::{ 12use crate::{date_iso, project_root};
12 not_bash::{date_iso, fs2, pushd, rm_rf, run},
13 project_root,
14};
15 13
16pub struct DistCmd { 14pub struct DistCmd {
17 pub nightly: bool, 15 pub nightly: bool,
@@ -22,7 +20,7 @@ impl DistCmd {
22 pub fn run(self) -> Result<()> { 20 pub fn run(self) -> Result<()> {
23 let dist = project_root().join("dist"); 21 let dist = project_root().join("dist");
24 rm_rf(&dist)?; 22 rm_rf(&dist)?;
25 fs2::create_dir_all(&dist)?; 23 mkdir_p(&dist)?;
26 24
27 if let Some(version) = self.client_version { 25 if let Some(version) = self.client_version {
28 let release_tag = if self.nightly { "nightly".to_string() } else { date_iso()? }; 26 let release_tag = if self.nightly { "nightly".to_string() } else { date_iso()? };
@@ -34,7 +32,7 @@ impl DistCmd {
34} 32}
35 33
36fn dist_client(version: &str, release_tag: &str) -> Result<()> { 34fn dist_client(version: &str, release_tag: &str) -> Result<()> {
37 let _d = pushd("./editors/code"); 35 let _d = pushd("./editors/code")?;
38 let nightly = release_tag == "nightly"; 36 let nightly = release_tag == "nightly";
39 37
40 let mut patch = Patch::new("./package.json")?; 38 let mut patch = Patch::new("./package.json")?;
@@ -54,20 +52,16 @@ fn dist_client(version: &str, release_tag: &str) -> Result<()> {
54 } 52 }
55 patch.commit()?; 53 patch.commit()?;
56 54
57 run!("npm ci")?; 55 cmd!("npm ci").run()?;
58 run!("npx vsce package -o ../../dist/rust-analyzer.vsix")?; 56 cmd!("npx vsce package -o ../../dist/rust-analyzer.vsix").run()?;
59 Ok(()) 57 Ok(())
60} 58}
61 59
62fn dist_server() -> Result<()> { 60fn dist_server() -> Result<()> {
63 if cfg!(target_os = "linux") { 61 if cfg!(target_os = "linux") {
64 env::set_var("CC", "clang"); 62 env::set_var("CC", "clang");
65 run!(
66 "cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --release"
67 )?;
68 } else {
69 run!("cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --release")?;
70 } 63 }
64 cmd!("cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --release").run()?;
71 65
72 let (src, dst) = if cfg!(target_os = "linux") { 66 let (src, dst) = if cfg!(target_os = "linux") {
73 ("./target/release/rust-analyzer", "./dist/rust-analyzer-linux") 67 ("./target/release/rust-analyzer", "./dist/rust-analyzer-linux")
@@ -82,7 +76,7 @@ fn dist_server() -> Result<()> {
82 let src = Path::new(src); 76 let src = Path::new(src);
83 let dst = Path::new(dst); 77 let dst = Path::new(dst);
84 78
85 fs2::copy(&src, &dst)?; 79 cp(&src, &dst)?;
86 gzip(&src, &dst.with_extension("gz"))?; 80 gzip(&src, &dst.with_extension("gz"))?;
87 81
88 Ok(()) 82 Ok(())
@@ -105,7 +99,7 @@ struct Patch {
105impl Patch { 99impl Patch {
106 fn new(path: impl Into<PathBuf>) -> Result<Patch> { 100 fn new(path: impl Into<PathBuf>) -> Result<Patch> {
107 let path = path.into(); 101 let path = path.into();
108 let contents = fs2::read_to_string(&path)?; 102 let contents = read_file(&path)?;
109 Ok(Patch { path, original_contents: contents.clone(), contents }) 103 Ok(Patch { path, original_contents: contents.clone(), contents })
110 } 104 }
111 105
@@ -115,13 +109,14 @@ impl Patch {
115 self 109 self
116 } 110 }
117 111
118 fn commit(&self) -> io::Result<()> { 112 fn commit(&self) -> Result<()> {
119 fs2::write(&self.path, &self.contents) 113 write_file(&self.path, &self.contents)?;
114 Ok(())
120 } 115 }
121} 116}
122 117
123impl Drop for Patch { 118impl Drop for Patch {
124 fn drop(&mut self) { 119 fn drop(&mut self) {
125 fs2::write(&self.path, &self.original_contents).unwrap(); 120 write_file(&self.path, &self.original_contents).unwrap();
126 } 121 }
127} 122}
diff --git a/xtask/src/install.rs b/xtask/src/install.rs
index fcc4f05e4..789e9f27b 100644
--- a/xtask/src/install.rs
+++ b/xtask/src/install.rs
@@ -3,8 +3,7 @@
3use std::{env, path::PathBuf, str}; 3use std::{env, path::PathBuf, str};
4 4
5use anyhow::{bail, format_err, Context, Result}; 5use anyhow::{bail, format_err, Context, Result};
6 6use xshell::{cmd, pushd};
7use crate::not_bash::{pushd, run};
8 7
9// Latest stable, feel free to send a PR if this lags behind. 8// Latest stable, feel free to send a PR if this lags behind.
10const REQUIRED_RUST_VERSION: u32 = 47; 9const REQUIRED_RUST_VERSION: u32 = 47;
@@ -76,7 +75,7 @@ fn fix_path_for_mac() -> Result<()> {
76} 75}
77 76
78fn install_client(ClientOpt::VsCode: ClientOpt) -> Result<()> { 77fn install_client(ClientOpt::VsCode: ClientOpt) -> Result<()> {
79 let _dir = pushd("./editors/code"); 78 let _dir = pushd("./editors/code")?;
80 79
81 let find_code = |f: fn(&str) -> bool| -> Result<&'static str> { 80 let find_code = |f: fn(&str) -> bool| -> Result<&'static str> {
82 ["code", "code-insiders", "codium", "code-oss"] 81 ["code", "code-insiders", "codium", "code-oss"]
@@ -89,24 +88,25 @@ fn install_client(ClientOpt::VsCode: ClientOpt) -> Result<()> {
89 }; 88 };
90 89
91 let installed_extensions = if cfg!(unix) { 90 let installed_extensions = if cfg!(unix) {
92 run!("npm --version").context("`npm` is required to build the VS Code plugin")?; 91 cmd!("npm --version").run().context("`npm` is required to build the VS Code plugin")?;
93 run!("npm install")?; 92 cmd!("npm install").run()?;
94 93
95 run!("npm run package --scripts-prepend-node-path")?; 94 cmd!("npm run package --scripts-prepend-node-path").run()?;
96 95
97 let code = find_code(|bin| run!("{} --version", bin).is_ok())?; 96 let code = find_code(|bin| cmd!("{bin} --version").read().is_ok())?;
98 run!("{} --install-extension rust-analyzer.vsix --force", code)?; 97 cmd!("{code} --install-extension rust-analyzer.vsix --force").run()?;
99 run!("{} --list-extensions", code; echo = false)? 98 cmd!("{code} --list-extensions").read()?
100 } else { 99 } else {
101 run!("cmd.exe /c npm --version") 100 cmd!("cmd.exe /c npm --version")
101 .run()
102 .context("`npm` is required to build the VS Code plugin")?; 102 .context("`npm` is required to build the VS Code plugin")?;
103 run!("cmd.exe /c npm install")?; 103 cmd!("cmd.exe /c npm install").run()?;
104 104
105 run!("cmd.exe /c npm run package")?; 105 cmd!("cmd.exe /c npm run package").run()?;
106 106
107 let code = find_code(|bin| run!("cmd.exe /c {}.cmd --version", bin).is_ok())?; 107 let code = find_code(|bin| cmd!("cmd.exe /c {bin}.cmd --version").read().is_ok())?;
108 run!(r"cmd.exe /c {}.cmd --install-extension rust-analyzer.vsix --force", code)?; 108 cmd!("cmd.exe /c {code}.cmd --install-extension rust-analyzer.vsix --force").run()?;
109 run!("cmd.exe /c {}.cmd --list-extensions", code; echo = false)? 109 cmd!("cmd.exe /c {code}.cmd --list-extensions").read()?
110 }; 110 };
111 111
112 if !installed_extensions.contains("rust-analyzer") { 112 if !installed_extensions.contains("rust-analyzer") {
@@ -122,7 +122,7 @@ fn install_client(ClientOpt::VsCode: ClientOpt) -> Result<()> {
122 122
123fn install_server(opts: ServerOpt) -> Result<()> { 123fn install_server(opts: ServerOpt) -> Result<()> {
124 let mut old_rust = false; 124 let mut old_rust = false;
125 if let Ok(stdout) = run!("cargo --version") { 125 if let Ok(stdout) = cmd!("cargo --version").read() {
126 if !check_version(&stdout, REQUIRED_RUST_VERSION) { 126 if !check_version(&stdout, REQUIRED_RUST_VERSION) {
127 old_rust = true; 127 old_rust = true;
128 } 128 }
@@ -134,12 +134,13 @@ fn install_server(opts: ServerOpt) -> Result<()> {
134 REQUIRED_RUST_VERSION, 134 REQUIRED_RUST_VERSION,
135 ) 135 )
136 } 136 }
137 137 let features = match opts.malloc {
138 let malloc_feature = match opts.malloc { 138 Malloc::System => &[][..],
139 Malloc::System => "", 139 Malloc::Mimalloc => &["--features", "mimalloc"],
140 Malloc::Mimalloc => "--features mimalloc",
141 }; 140 };
142 let res = run!("cargo install --path crates/rust-analyzer --locked --force {}", malloc_feature); 141
142 let cmd = cmd!("cargo install --path crates/rust-analyzer --locked --force {features...}");
143 let res = cmd.run();
143 144
144 if res.is_err() && old_rust { 145 if res.is_err() && old_rust {
145 eprintln!( 146 eprintln!(
@@ -148,7 +149,8 @@ fn install_server(opts: ServerOpt) -> Result<()> {
148 ); 149 );
149 } 150 }
150 151
151 res.map(drop) 152 res?;
153 Ok(())
152} 154}
153 155
154fn check_version(version_output: &str, min_minor_version: u32) -> bool { 156fn check_version(version_output: &str, min_minor_version: u32) -> bool {
diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs
index e790d995f..babec2dbd 100644
--- a/xtask/src/lib.rs
+++ b/xtask/src/lib.rs
@@ -2,7 +2,6 @@
2//! 2//!
3//! See https://github.com/matklad/cargo-xtask/ 3//! See https://github.com/matklad/cargo-xtask/
4 4
5pub mod not_bash;
6pub mod codegen; 5pub mod codegen;
7mod ast_src; 6mod ast_src;
8 7
@@ -19,11 +18,9 @@ use std::{
19}; 18};
20 19
21use walkdir::{DirEntry, WalkDir}; 20use walkdir::{DirEntry, WalkDir};
21use xshell::{cmd, pushd, pushenv};
22 22
23use crate::{ 23use crate::codegen::Mode;
24 codegen::Mode,
25 not_bash::{pushd, pushenv},
26};
27 24
28pub use anyhow::{bail, Context as _, Result}; 25pub use anyhow::{bail, Context as _, Result};
29 26
@@ -53,18 +50,19 @@ pub fn rust_files(path: &Path) -> impl Iterator<Item = PathBuf> {
53} 50}
54 51
55pub fn run_rustfmt(mode: Mode) -> Result<()> { 52pub fn run_rustfmt(mode: Mode) -> Result<()> {
56 let _dir = pushd(project_root()); 53 let _dir = pushd(project_root())?;
57 let _e = pushenv("RUSTUP_TOOLCHAIN", "stable"); 54 let _e = pushenv("RUSTUP_TOOLCHAIN", "stable");
58 ensure_rustfmt()?; 55 ensure_rustfmt()?;
59 match mode { 56 let check = match mode {
60 Mode::Overwrite => run!("cargo fmt"), 57 Mode::Overwrite => &[][..],
61 Mode::Verify => run!("cargo fmt -- --check"), 58 Mode::Verify => &["--", "--check"],
62 }?; 59 };
60 cmd!("cargo fmt {check...}").run()?;
63 Ok(()) 61 Ok(())
64} 62}
65 63
66fn ensure_rustfmt() -> Result<()> { 64fn ensure_rustfmt() -> Result<()> {
67 let out = run!("rustfmt --version")?; 65 let out = cmd!("rustfmt --version").read()?;
68 if !out.contains("stable") { 66 if !out.contains("stable") {
69 bail!( 67 bail!(
70 "Failed to run rustfmt from toolchain 'stable'. \ 68 "Failed to run rustfmt from toolchain 'stable'. \
@@ -75,40 +73,46 @@ fn ensure_rustfmt() -> Result<()> {
75} 73}
76 74
77pub fn run_clippy() -> Result<()> { 75pub fn run_clippy() -> Result<()> {
78 if run!("cargo clippy --version").is_err() { 76 if cmd!("cargo clippy --version").read().is_err() {
79 bail!( 77 bail!(
80 "Failed run cargo clippy. \ 78 "Failed run cargo clippy. \
81 Please run `rustup component add clippy` to install it.", 79 Please run `rustup component add clippy` to install it.",
82 ) 80 )
83 } 81 }
84 82
85 let allowed_lints = [ 83 let allowed_lints = "
86 "clippy::collapsible_if", 84 -A clippy::collapsible_if
87 "clippy::needless_pass_by_value", 85 -A clippy::needless_pass_by_value
88 "clippy::nonminimal_bool", 86 -A clippy::nonminimal_bool
89 "clippy::redundant_pattern_matching", 87 -A clippy::redundant_pattern_matching
90 ]; 88 "
91 run!("cargo clippy --all-features --all-targets -- -A {}", allowed_lints.join(" -A "))?; 89 .split_ascii_whitespace();
90 cmd!("cargo clippy --all-features --all-targets -- {allowed_lints...}").run()?;
92 Ok(()) 91 Ok(())
93} 92}
94 93
95pub fn run_fuzzer() -> Result<()> { 94pub fn run_fuzzer() -> Result<()> {
96 let _d = pushd("./crates/syntax"); 95 let _d = pushd("./crates/syntax")?;
97 let _e = pushenv("RUSTUP_TOOLCHAIN", "nightly"); 96 let _e = pushenv("RUSTUP_TOOLCHAIN", "nightly");
98 if run!("cargo fuzz --help").is_err() { 97 if cmd!("cargo fuzz --help").read().is_err() {
99 run!("cargo install cargo-fuzz")?; 98 cmd!("cargo install cargo-fuzz").run()?;
100 }; 99 };
101 100
102 // Expecting nightly rustc 101 // Expecting nightly rustc
103 let out = run!("rustc --version")?; 102 let out = cmd!("rustc --version").read()?;
104 if !out.contains("nightly") { 103 if !out.contains("nightly") {
105 bail!("fuzz tests require nightly rustc") 104 bail!("fuzz tests require nightly rustc")
106 } 105 }
107 106
108 run!("cargo fuzz run parser")?; 107 cmd!("cargo fuzz run parser").run()?;
109 Ok(()) 108 Ok(())
110} 109}
111 110
111fn date_iso() -> Result<String> {
112 let res = cmd!("date --iso --utc").read()?;
113 Ok(res)
114}
115
112fn is_release_tag(tag: &str) -> bool { 116fn is_release_tag(tag: &str) -> bool {
113 tag.len() == "2020-02-24".len() && tag.starts_with(|c: char| c.is_ascii_digit()) 117 tag.len() == "2020-02-24".len() && tag.starts_with(|c: char| c.is_ascii_digit())
114} 118}
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 3f4aa5497..97e5dcd4e 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -12,12 +12,12 @@ use std::env;
12 12
13use codegen::CodegenCmd; 13use codegen::CodegenCmd;
14use pico_args::Arguments; 14use pico_args::Arguments;
15use xshell::pushd;
15use xtask::{ 16use xtask::{
16 codegen::{self, Mode}, 17 codegen::{self, Mode},
17 dist::DistCmd, 18 dist::DistCmd,
18 install::{ClientOpt, InstallCmd, Malloc, ServerOpt}, 19 install::{ClientOpt, InstallCmd, Malloc, ServerOpt},
19 metrics::MetricsCmd, 20 metrics::MetricsCmd,
20 not_bash::pushd,
21 pre_cache::PreCacheCmd, 21 pre_cache::PreCacheCmd,
22 pre_commit, project_root, 22 pre_commit, project_root,
23 release::{PromoteCmd, ReleaseCmd}, 23 release::{PromoteCmd, ReleaseCmd},
@@ -29,7 +29,7 @@ fn main() -> Result<()> {
29 return pre_commit::run_hook(); 29 return pre_commit::run_hook();
30 } 30 }
31 31
32 let _d = pushd(project_root()); 32 let _d = pushd(project_root())?;
33 33
34 let mut args = Arguments::from_env(); 34 let mut args = Arguments::from_env();
35 let subcommand = args.subcommand()?.unwrap_or_default(); 35 let subcommand = args.subcommand()?.unwrap_or_default();
diff --git a/xtask/src/metrics.rs b/xtask/src/metrics.rs
index 4bade2c7e..e0d1aaf97 100644
--- a/xtask/src/metrics.rs
+++ b/xtask/src/metrics.rs
@@ -7,8 +7,7 @@ use std::{
7}; 7};
8 8
9use anyhow::{bail, format_err, Result}; 9use anyhow::{bail, format_err, Result};
10 10use xshell::{cmd, mkdir_p, pushd, pushenv, read_file, rm_rf};
11use crate::not_bash::{fs2, pushd, pushenv, rm_rf, run};
12 11
13type Unit = String; 12type Unit = String;
14 13
@@ -23,12 +22,13 @@ impl MetricsCmd {
23 rm_rf("./target/release")?; 22 rm_rf("./target/release")?;
24 } 23 }
25 if !Path::new("./target/rustc-perf").exists() { 24 if !Path::new("./target/rustc-perf").exists() {
26 fs2::create_dir_all("./target/rustc-perf")?; 25 mkdir_p("./target/rustc-perf")?;
27 run!("git clone https://github.com/rust-lang/rustc-perf.git ./target/rustc-perf")?; 26 cmd!("git clone https://github.com/rust-lang/rustc-perf.git ./target/rustc-perf")
27 .run()?;
28 } 28 }
29 { 29 {
30 let _d = pushd("./target/rustc-perf"); 30 let _d = pushd("./target/rustc-perf")?;
31 run!("git reset --hard 1d9288b0da7febf2599917da1b57dc241a1af033")?; 31 cmd!("git reset --hard 1d9288b0da7febf2599917da1b57dc241a1af033").run()?;
32 } 32 }
33 33
34 let _env = pushenv("RA_METRICS", "1"); 34 let _env = pushenv("RA_METRICS", "1");
@@ -39,17 +39,20 @@ impl MetricsCmd {
39 metrics.measure_analysis_stats("webrender")?; 39 metrics.measure_analysis_stats("webrender")?;
40 40
41 if !self.dry_run { 41 if !self.dry_run {
42 let _d = pushd("target"); 42 let _d = pushd("target")?;
43 let metrics_token = env::var("METRICS_TOKEN").unwrap(); 43 let metrics_token = env::var("METRICS_TOKEN").unwrap();
44 let repo = format!("https://{}@github.com/rust-analyzer/metrics.git", metrics_token); 44 cmd!(
45 run!("git clone --depth 1 {}", repo)?; 45 "git clone --depth 1 https://{metrics_token}@github.com/rust-analyzer/metrics.git"
46 let _d = pushd("metrics"); 46 )
47 .run()?;
48 let _d = pushd("metrics")?;
47 49
48 let mut file = std::fs::OpenOptions::new().append(true).open("metrics.json")?; 50 let mut file = std::fs::OpenOptions::new().append(true).open("metrics.json")?;
49 writeln!(file, "{}", metrics.json())?; 51 writeln!(file, "{}", metrics.json())?;
50 run!("git add .")?; 52 cmd!("git add .").run()?;
51 run!("git -c user.name=Bot -c [email protected] commit --message 📈")?; 53 cmd!("git -c user.name=Bot -c [email protected] commit --message 📈")
52 run!("git push origin master")?; 54 .run()?;
55 cmd!("git push origin master").run()?;
53 } 56 }
54 eprintln!("{:#?}", metrics); 57 eprintln!("{:#?}", metrics);
55 Ok(()) 58 Ok(())
@@ -59,10 +62,10 @@ impl MetricsCmd {
59impl Metrics { 62impl Metrics {
60 fn measure_build(&mut self) -> Result<()> { 63 fn measure_build(&mut self) -> Result<()> {
61 eprintln!("\nMeasuring build"); 64 eprintln!("\nMeasuring build");
62 run!("cargo fetch")?; 65 cmd!("cargo fetch").run()?;
63 66
64 let time = Instant::now(); 67 let time = Instant::now();
65 run!("cargo build --release --package rust-analyzer --bin rust-analyzer")?; 68 cmd!("cargo build --release --package rust-analyzer --bin rust-analyzer").run()?;
66 let time = time.elapsed(); 69 let time = time.elapsed();
67 self.report("build", time.as_millis() as u64, "ms".into()); 70 self.report("build", time.as_millis() as u64, "ms".into());
68 Ok(()) 71 Ok(())
@@ -78,7 +81,7 @@ impl Metrics {
78 } 81 }
79 fn measure_analysis_stats_path(&mut self, name: &str, path: &str) -> Result<()> { 82 fn measure_analysis_stats_path(&mut self, name: &str, path: &str) -> Result<()> {
80 eprintln!("\nMeasuring analysis-stats/{}", name); 83 eprintln!("\nMeasuring analysis-stats/{}", name);
81 let output = run!("./target/release/rust-analyzer analysis-stats --quiet {}", path)?; 84 let output = cmd!("./target/release/rust-analyzer analysis-stats --quiet {path}").read()?;
82 for (metric, value, unit) in parse_metrics(&output) { 85 for (metric, value, unit) in parse_metrics(&output) {
83 self.report(&format!("analysis-stats/{}/{}", name, metric), value, unit.into()); 86 self.report(&format!("analysis-stats/{}/{}", name, metric), value, unit.into());
84 } 87 }
@@ -118,7 +121,7 @@ impl Metrics {
118 fn new() -> Result<Metrics> { 121 fn new() -> Result<Metrics> {
119 let host = Host::new()?; 122 let host = Host::new()?;
120 let timestamp = SystemTime::now(); 123 let timestamp = SystemTime::now();
121 let revision = run!("git rev-parse HEAD")?; 124 let revision = cmd!("git rev-parse HEAD").read()?;
122 Ok(Metrics { host, timestamp, revision, metrics: BTreeMap::new() }) 125 Ok(Metrics { host, timestamp, revision, metrics: BTreeMap::new() })
123 } 126 }
124 127
@@ -160,7 +163,7 @@ impl Host {
160 return Ok(Host { os, cpu, mem }); 163 return Ok(Host { os, cpu, mem });
161 164
162 fn read_field<'a>(path: &str, field: &str) -> Result<String> { 165 fn read_field<'a>(path: &str, field: &str) -> Result<String> {
163 let text = fs2::read_to_string(path)?; 166 let text = read_file(path)?;
164 167
165 let line = text 168 let line = text
166 .lines() 169 .lines()
diff --git a/xtask/src/not_bash.rs b/xtask/src/not_bash.rs
deleted file mode 100644
index 038898993..000000000
--- a/xtask/src/not_bash.rs
+++ /dev/null
@@ -1,169 +0,0 @@
1//! A bad shell -- small cross platform module for writing glue code
2
3use std::{
4 cell::RefCell,
5 env,
6 ffi::OsString,
7 io::{self, Write},
8 path::{Path, PathBuf},
9 process::{Command, Stdio},
10};
11
12use anyhow::{bail, Context, Result};
13
14pub use fs_err as fs2;
15
16#[macro_export]
17macro_rules! run {
18 ($($expr:expr),*) => {
19 run!($($expr),*; echo = true)
20 };
21 ($($expr:expr),* ; echo = $echo:expr) => {
22 $crate::not_bash::run_process(format!($($expr),*), $echo, None)
23 };
24 ($($expr:expr),* ; <$stdin:expr) => {
25 $crate::not_bash::run_process(format!($($expr),*), false, Some($stdin))
26 };
27}
28pub use crate::run;
29
30pub struct Pushd {
31 _p: (),
32}
33
34pub fn pushd(path: impl Into<PathBuf>) -> Pushd {
35 Env::with(|env| env.pushd(path.into()));
36 Pushd { _p: () }
37}
38
39impl Drop for Pushd {
40 fn drop(&mut self) {
41 Env::with(|env| env.popd())
42 }
43}
44
45pub struct Pushenv {
46 _p: (),
47}
48
49pub fn pushenv(var: &str, value: &str) -> Pushenv {
50 Env::with(|env| env.pushenv(var.into(), value.into()));
51 Pushenv { _p: () }
52}
53
54impl Drop for Pushenv {
55 fn drop(&mut self) {
56 Env::with(|env| env.popenv())
57 }
58}
59
60pub fn rm_rf(path: impl AsRef<Path>) -> io::Result<()> {
61 let path = path.as_ref();
62 if !path.exists() {
63 return Ok(());
64 }
65 if path.is_file() {
66 fs2::remove_file(path)
67 } else {
68 fs2::remove_dir_all(path)
69 }
70}
71
72#[doc(hidden)]
73pub fn run_process(cmd: String, echo: bool, stdin: Option<&[u8]>) -> Result<String> {
74 run_process_inner(&cmd, echo, stdin).with_context(|| format!("process `{}` failed", cmd))
75}
76
77pub fn date_iso() -> Result<String> {
78 run!("date --iso --utc")
79}
80
81fn run_process_inner(cmd: &str, echo: bool, stdin: Option<&[u8]>) -> Result<String> {
82 let mut args = shelx(cmd);
83 let binary = args.remove(0);
84 let current_dir = Env::with(|it| it.cwd().to_path_buf());
85
86 if echo {
87 println!("> {}", cmd)
88 }
89
90 let mut command = Command::new(binary);
91 command.args(args).current_dir(current_dir).stderr(Stdio::inherit());
92 let output = match stdin {
93 None => command.stdin(Stdio::null()).output(),
94 Some(stdin) => {
95 command.stdin(Stdio::piped()).stdout(Stdio::piped());
96 let mut process = command.spawn()?;
97 process.stdin.take().unwrap().write_all(stdin)?;
98 process.wait_with_output()
99 }
100 }?;
101 let stdout = String::from_utf8(output.stdout)?;
102
103 if echo {
104 print!("{}", stdout)
105 }
106
107 if !output.status.success() {
108 bail!("{}", output.status)
109 }
110
111 Ok(stdout.trim().to_string())
112}
113
114// FIXME: some real shell lexing here
115fn shelx(cmd: &str) -> Vec<String> {
116 let mut res = Vec::new();
117 for (string_piece, in_quotes) in cmd.split('\'').zip([false, true].iter().copied().cycle()) {
118 if in_quotes {
119 res.push(string_piece.to_string())
120 } else {
121 if !string_piece.is_empty() {
122 res.extend(string_piece.split_ascii_whitespace().map(|it| it.to_string()))
123 }
124 }
125 }
126 res
127}
128
129struct Env {
130 pushd_stack: Vec<PathBuf>,
131 pushenv_stack: Vec<(OsString, Option<OsString>)>,
132}
133
134impl Env {
135 fn with<F: FnOnce(&mut Env) -> T, T>(f: F) -> T {
136 thread_local! {
137 static ENV: RefCell<Env> = RefCell::new(Env {
138 pushd_stack: vec![env::current_dir().unwrap()],
139 pushenv_stack: vec![],
140 });
141 }
142 ENV.with(|it| f(&mut *it.borrow_mut()))
143 }
144
145 fn pushd(&mut self, dir: PathBuf) {
146 let dir = self.cwd().join(dir);
147 self.pushd_stack.push(dir);
148 env::set_current_dir(self.cwd())
149 .unwrap_or_else(|err| panic!("Failed to set cwd to {}: {}", self.cwd().display(), err));
150 }
151 fn popd(&mut self) {
152 self.pushd_stack.pop().unwrap();
153 env::set_current_dir(self.cwd()).unwrap();
154 }
155 fn pushenv(&mut self, var: OsString, value: OsString) {
156 self.pushenv_stack.push((var.clone(), env::var_os(&var)));
157 env::set_var(var, value)
158 }
159 fn popenv(&mut self) {
160 let (var, value) = self.pushenv_stack.pop().unwrap();
161 match value {
162 None => env::remove_var(var),
163 Some(value) => env::set_var(var, value),
164 }
165 }
166 fn cwd(&self) -> &Path {
167 self.pushd_stack.last().unwrap()
168 }
169}
diff --git a/xtask/src/pre_cache.rs b/xtask/src/pre_cache.rs
index 47ba6ba24..569f88f68 100644
--- a/xtask/src/pre_cache.rs
+++ b/xtask/src/pre_cache.rs
@@ -4,8 +4,7 @@ use std::{
4}; 4};
5 5
6use anyhow::Result; 6use anyhow::Result;
7 7use xshell::rm_rf;
8use crate::not_bash::{fs2, rm_rf};
9 8
10pub struct PreCacheCmd; 9pub struct PreCacheCmd;
11 10
@@ -26,7 +25,7 @@ impl PreCacheCmd {
26 } 25 }
27 } 26 }
28 27
29 fs2::remove_file("./target/.rustc_info.json")?; 28 rm_rf("./target/.rustc_info.json")?;
30 29
31 let to_delete = read_dir("./crates", FileType::is_dir)? 30 let to_delete = read_dir("./crates", FileType::is_dir)?
32 .into_iter() 31 .into_iter()
diff --git a/xtask/src/pre_commit.rs b/xtask/src/pre_commit.rs
index 056f34acf..8f2dbea19 100644
--- a/xtask/src/pre_commit.rs
+++ b/xtask/src/pre_commit.rs
@@ -3,19 +3,21 @@
3use std::{fs, path::PathBuf}; 3use std::{fs, path::PathBuf};
4 4
5use anyhow::{bail, Result}; 5use anyhow::{bail, Result};
6use xshell::cmd;
6 7
7use crate::{not_bash::run, project_root, run_rustfmt, Mode}; 8use crate::{project_root, run_rustfmt, Mode};
8 9
9// FIXME: if there are changed `.ts` files, also reformat TypeScript (by 10// FIXME: if there are changed `.ts` files, also reformat TypeScript (by
10// shelling out to `npm fmt`). 11// shelling out to `npm fmt`).
11pub fn run_hook() -> Result<()> { 12pub fn run_hook() -> Result<()> {
12 run_rustfmt(Mode::Overwrite)?; 13 run_rustfmt(Mode::Overwrite)?;
13 14
14 let diff = run!("git diff --diff-filter=MAR --name-only --cached")?; 15 let diff = cmd!("git diff --diff-filter=MAR --name-only --cached").read()?;
15 16
16 let root = project_root(); 17 let root = project_root();
17 for line in diff.lines() { 18 for line in diff.lines() {
18 run!("git update-index --add {}", root.join(line).display())?; 19 let file = root.join(line);
20 cmd!("git update-index --add {file}").run()?;
19 } 21 }
20 22
21 Ok(()) 23 Ok(())
diff --git a/xtask/src/release.rs b/xtask/src/release.rs
index 3aab29801..14fc1f0dd 100644
--- a/xtask/src/release.rs
+++ b/xtask/src/release.rs
@@ -1,8 +1,6 @@
1use crate::{ 1use xshell::{cmd, cp, pushd, read_dir, write_file};
2 codegen, is_release_tag, 2
3 not_bash::{date_iso, fs2, pushd, run}, 3use crate::{codegen, date_iso, is_release_tag, project_root, Mode, Result};
4 project_root, Mode, Result,
5};
6 4
7pub struct ReleaseCmd { 5pub struct ReleaseCmd {
8 pub dry_run: bool, 6 pub dry_run: bool,
@@ -11,10 +9,10 @@ pub struct ReleaseCmd {
11impl ReleaseCmd { 9impl ReleaseCmd {
12 pub fn run(self) -> Result<()> { 10 pub fn run(self) -> Result<()> {
13 if !self.dry_run { 11 if !self.dry_run {
14 run!("git switch release")?; 12 cmd!("git switch release").run()?;
15 run!("git fetch upstream --tags --force")?; 13 cmd!("git fetch upstream --tags --force").run()?;
16 run!("git reset --hard tags/nightly")?; 14 cmd!("git reset --hard tags/nightly").run()?;
17 run!("git push")?; 15 cmd!("git push").run()?;
18 } 16 }
19 codegen::generate_assists_docs(Mode::Overwrite)?; 17 codegen::generate_assists_docs(Mode::Overwrite)?;
20 codegen::generate_feature_docs(Mode::Overwrite)?; 18 codegen::generate_feature_docs(Mode::Overwrite)?;
@@ -23,8 +21,8 @@ impl ReleaseCmd {
23 let changelog_dir = website_root.join("./thisweek/_posts"); 21 let changelog_dir = website_root.join("./thisweek/_posts");
24 22
25 let today = date_iso()?; 23 let today = date_iso()?;
26 let commit = run!("git rev-parse HEAD")?; 24 let commit = cmd!("git rev-parse HEAD").read()?;
27 let changelog_n = fs2::read_dir(changelog_dir.as_path())?.count(); 25 let changelog_n = read_dir(changelog_dir.as_path())?.len();
28 26
29 let contents = format!( 27 let contents = format!(
30 "\ 28 "\
@@ -52,20 +50,20 @@ https://github.com/sponsors/rust-analyzer[GitHub Sponsors].
52 ); 50 );
53 51
54 let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n)); 52 let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n));
55 fs2::write(&path, &contents)?; 53 write_file(&path, &contents)?;
56 54
57 for &adoc in ["manual.adoc", "generated_features.adoc", "generated_assists.adoc"].iter() { 55 for &adoc in ["manual.adoc", "generated_features.adoc", "generated_assists.adoc"].iter() {
58 let src = project_root().join("./docs/user/").join(adoc); 56 let src = project_root().join("./docs/user/").join(adoc);
59 let dst = website_root.join(adoc); 57 let dst = website_root.join(adoc);
60 fs2::copy(src, dst)?; 58 cp(src, dst)?;
61 } 59 }
62 60
63 let tags = run!("git tag --list"; echo = false)?; 61 let tags = cmd!("git tag --list").read()?;
64 let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap(); 62 let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap();
65 63
66 let git_log = run!("git log {}..HEAD --merges --reverse", prev_tag; echo = false)?; 64 let git_log = cmd!("git log {prev_tag}..HEAD --merges --reverse").read()?;
67 let git_log_dst = website_root.join("git.log"); 65 let git_log_dst = website_root.join("git.log");
68 fs2::write(git_log_dst, &git_log)?; 66 write_file(git_log_dst, &git_log)?;
69 67
70 Ok(()) 68 Ok(())
71 } 69 }
@@ -77,27 +75,25 @@ pub struct PromoteCmd {
77 75
78impl PromoteCmd { 76impl PromoteCmd {
79 pub fn run(self) -> Result<()> { 77 pub fn run(self) -> Result<()> {
80 let _dir = pushd("../rust-rust-analyzer"); 78 let _dir = pushd("../rust-rust-analyzer")?;
81 run!("git switch master")?; 79 cmd!("git switch master").run()?;
82 run!("git fetch upstream")?; 80 cmd!("git fetch upstream").run()?;
83 run!("git reset --hard upstream/master")?; 81 cmd!("git reset --hard upstream/master").run()?;
84 run!("git submodule update --recursive")?; 82 cmd!("git submodule update --recursive").run()?;
85 83
86 let branch = format!("rust-analyzer-{}", date_iso()?); 84 let branch = format!("rust-analyzer-{}", date_iso()?);
87 run!("git switch -c {}", branch)?; 85 cmd!("git switch -c {branch}").run()?;
88 { 86 {
89 let _dir = pushd("src/tools/rust-analyzer"); 87 let _dir = pushd("src/tools/rust-analyzer")?;
90 run!("git fetch origin")?; 88 cmd!("git fetch origin").run()?;
91 run!("git reset --hard origin/release")?; 89 cmd!("git reset --hard origin/release").run()?;
92 } 90 }
93 run!("git add src/tools/rust-analyzer")?; 91 cmd!("git add src/tools/rust-analyzer").run()?;
94 run!("git commit -m':arrow_up: rust-analyzer'")?; 92 cmd!("git commit -m':arrow_up: rust-analyzer'").run()?;
95 if !self.dry_run { 93 if !self.dry_run {
96 run!("git push")?; 94 cmd!("git push").run()?;
97 run!( 95 cmd!("xdg-open https://github.com/matklad/rust/pull/new/{branch}?body=r%3F%20%40ghost")
98 "xdg-open https://github.com/matklad/rust/pull/new/{}?body=r%3F%20%40ghost", 96 .run()?;
99 branch
100 )?;
101 } 97 }
102 Ok(()) 98 Ok(())
103 } 99 }
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs
index b3bb9d543..d335adb72 100644
--- a/xtask/tests/tidy.rs
+++ b/xtask/tests/tidy.rs
@@ -3,9 +3,9 @@ use std::{
3 path::{Path, PathBuf}, 3 path::{Path, PathBuf},
4}; 4};
5 5
6use xshell::{cmd, read_file};
6use xtask::{ 7use xtask::{
7 codegen::{self, Mode}, 8 codegen::{self, Mode},
8 not_bash::{fs2, run},
9 project_root, run_rustfmt, rust_files, 9 project_root, run_rustfmt, rust_files,
10}; 10};
11 11
@@ -48,14 +48,13 @@ fn smoke_test_docs_generation() {
48fn check_lsp_extensions_docs() { 48fn check_lsp_extensions_docs() {
49 let expected_hash = { 49 let expected_hash = {
50 let lsp_ext_rs = 50 let lsp_ext_rs =
51 fs2::read_to_string(project_root().join("crates/rust-analyzer/src/lsp_ext.rs")) 51 read_file(project_root().join("crates/rust-analyzer/src/lsp_ext.rs")).unwrap();
52 .unwrap();
53 stable_hash(lsp_ext_rs.as_str()) 52 stable_hash(lsp_ext_rs.as_str())
54 }; 53 };
55 54
56 let actual_hash = { 55 let actual_hash = {
57 let lsp_extensions_md = 56 let lsp_extensions_md =
58 fs2::read_to_string(project_root().join("docs/dev/lsp-extensions.md")).unwrap(); 57 read_file(project_root().join("docs/dev/lsp-extensions.md")).unwrap();
59 let text = lsp_extensions_md 58 let text = lsp_extensions_md
60 .lines() 59 .lines()
61 .find_map(|line| line.strip_prefix("lsp_ext.rs hash:")) 60 .find_map(|line| line.strip_prefix("lsp_ext.rs hash:"))
@@ -83,7 +82,7 @@ Please adjust docs/dev/lsp-extensions.md.
83fn rust_files_are_tidy() { 82fn rust_files_are_tidy() {
84 let mut tidy_docs = TidyDocs::default(); 83 let mut tidy_docs = TidyDocs::default();
85 for path in rust_files(&project_root().join("crates")) { 84 for path in rust_files(&project_root().join("crates")) {
86 let text = fs2::read_to_string(&path).unwrap(); 85 let text = read_file(&path).unwrap();
87 check_todo(&path, &text); 86 check_todo(&path, &text);
88 check_trailing_ws(&path, &text); 87 check_trailing_ws(&path, &text);
89 deny_clippy(&path, &text); 88 deny_clippy(&path, &text);
@@ -94,8 +93,10 @@ fn rust_files_are_tidy() {
94 93
95#[test] 94#[test]
96fn check_merge_commits() { 95fn check_merge_commits() {
97 let stdout = run!("git rev-list --merges --invert-grep --author 'bors\\[bot\\]' HEAD~19.."; echo = false) 96 let stdout =
98 .unwrap(); 97 dbg!(cmd!("git rev-list --merges --invert-grep --author 'bors\\[bot\\]' HEAD~19.."))
98 .read()
99 .unwrap();
99 if !stdout.is_empty() { 100 if !stdout.is_empty() {
100 panic!( 101 panic!(
101 " 102 "
@@ -168,7 +169,7 @@ Zlib OR Apache-2.0 OR MIT
168 .filter(|it| !it.is_empty()) 169 .filter(|it| !it.is_empty())
169 .collect::<Vec<_>>(); 170 .collect::<Vec<_>>();
170 171
171 let meta = run!("cargo metadata --format-version 1"; echo = false).unwrap(); 172 let meta = cmd!("cargo metadata --format-version 1").read().unwrap();
172 let mut licenses = meta 173 let mut licenses = meta
173 .split(|c| c == ',' || c == '{' || c == '}') 174 .split(|c| c == ',' || c == '{' || c == '}')
174 .filter(|it| it.contains(r#""license""#)) 175 .filter(|it| it.contains(r#""license""#))
@@ -177,6 +178,25 @@ Zlib OR Apache-2.0 OR MIT
177 .collect::<Vec<_>>(); 178 .collect::<Vec<_>>();
178 licenses.sort(); 179 licenses.sort();
179 licenses.dedup(); 180 licenses.dedup();
181 if licenses != expected {
182 let mut diff = String::new();
183
184 diff += &format!("New Licenses:\n");
185 for &l in licenses.iter() {
186 if !expected.contains(&l) {
187 diff += &format!(" {}\n", l)
188 }
189 }
190
191 diff += &format!("\nMissing Licenses:\n");
192 for &l in expected.iter() {
193 if !licenses.contains(&l) {
194 diff += &format!(" {}\n", l)
195 }
196 }
197
198 panic!("different set of licenses!\n{}", diff);
199 }
180 assert_eq!(licenses, expected); 200 assert_eq!(licenses, expected);
181} 201}
182 202