aboutsummaryrefslogtreecommitdiff
path: root/xtask/src
diff options
context:
space:
mode:
Diffstat (limited to 'xtask/src')
-rw-r--r--xtask/src/ast_src.rs621
-rw-r--r--xtask/src/boilerplate_gen.rs0
-rw-r--r--xtask/src/cmd.rs53
-rw-r--r--xtask/src/codegen.rs1
-rw-r--r--xtask/src/codegen/gen_syntax.rs234
-rw-r--r--xtask/src/help.rs46
-rw-r--r--xtask/src/install.rs178
-rw-r--r--xtask/src/lib.rs126
-rw-r--r--xtask/src/main.rs301
-rw-r--r--xtask/src/pre_commit.rs36
10 files changed, 1097 insertions, 499 deletions
diff --git a/xtask/src/ast_src.rs b/xtask/src/ast_src.rs
new file mode 100644
index 000000000..67d1f41bc
--- /dev/null
+++ b/xtask/src/ast_src.rs
@@ -0,0 +1,621 @@
1pub(crate) struct KindsSrc<'a> {
2 pub(crate) punct: &'a [(&'a str, &'a str)],
3 pub(crate) keywords: &'a [&'a str],
4 pub(crate) contextual_keywords: &'a [&'a str],
5 pub(crate) literals: &'a [&'a str],
6 pub(crate) tokens: &'a [&'a str],
7 pub(crate) nodes: &'a [&'a str],
8}
9
10pub(crate) const KINDS_SRC: KindsSrc = KindsSrc {
11 punct: &[
12 (";", "SEMI"),
13 (",", "COMMA"),
14 ("(", "L_PAREN"),
15 (")", "R_PAREN"),
16 ("{", "L_CURLY"),
17 ("}", "R_CURLY"),
18 ("[", "L_BRACK"),
19 ("]", "R_BRACK"),
20 ("<", "L_ANGLE"),
21 (">", "R_ANGLE"),
22 ("@", "AT"),
23 ("#", "POUND"),
24 ("~", "TILDE"),
25 ("?", "QUESTION"),
26 ("$", "DOLLAR"),
27 ("&", "AMP"),
28 ("|", "PIPE"),
29 ("+", "PLUS"),
30 ("*", "STAR"),
31 ("/", "SLASH"),
32 ("^", "CARET"),
33 ("%", "PERCENT"),
34 ("_", "UNDERSCORE"),
35 (".", "DOT"),
36 ("..", "DOTDOT"),
37 ("...", "DOTDOTDOT"),
38 ("..=", "DOTDOTEQ"),
39 (":", "COLON"),
40 ("::", "COLONCOLON"),
41 ("=", "EQ"),
42 ("==", "EQEQ"),
43 ("=>", "FAT_ARROW"),
44 ("!", "EXCL"),
45 ("!=", "NEQ"),
46 ("-", "MINUS"),
47 ("->", "THIN_ARROW"),
48 ("<=", "LTEQ"),
49 (">=", "GTEQ"),
50 ("+=", "PLUSEQ"),
51 ("-=", "MINUSEQ"),
52 ("|=", "PIPEEQ"),
53 ("&=", "AMPEQ"),
54 ("^=", "CARETEQ"),
55 ("/=", "SLASHEQ"),
56 ("*=", "STAREQ"),
57 ("%=", "PERCENTEQ"),
58 ("&&", "AMPAMP"),
59 ("||", "PIPEPIPE"),
60 ("<<", "SHL"),
61 (">>", "SHR"),
62 ("<<=", "SHLEQ"),
63 (">>=", "SHREQ"),
64 ],
65 keywords: &[
66 "as", "async", "await", "box", "break", "const", "continue", "crate", "dyn", "else",
67 "enum", "extern", "false", "fn", "for", "if", "impl", "in", "let", "loop", "macro",
68 "match", "mod", "move", "mut", "pub", "ref", "return", "self", "static", "struct", "super",
69 "trait", "true", "try", "type", "unsafe", "use", "where", "while",
70 ],
71 contextual_keywords: &["auto", "default", "existential", "union"],
72 literals: &[
73 "INT_NUMBER",
74 "FLOAT_NUMBER",
75 "CHAR",
76 "BYTE",
77 "STRING",
78 "RAW_STRING",
79 "BYTE_STRING",
80 "RAW_BYTE_STRING",
81 ],
82 tokens: &[
83 "ERROR",
84 "IDENT",
85 "WHITESPACE",
86 "LIFETIME",
87 "COMMENT",
88 "SHEBANG",
89 "L_DOLLAR",
90 "R_DOLLAR",
91 ],
92 nodes: &[
93 "SOURCE_FILE",
94 "STRUCT_DEF",
95 "UNION_DEF",
96 "ENUM_DEF",
97 "FN_DEF",
98 "RET_TYPE",
99 "EXTERN_CRATE_ITEM",
100 "MODULE",
101 "USE_ITEM",
102 "STATIC_DEF",
103 "CONST_DEF",
104 "TRAIT_DEF",
105 "IMPL_BLOCK",
106 "TYPE_ALIAS_DEF",
107 "MACRO_CALL",
108 "TOKEN_TREE",
109 "MACRO_DEF",
110 "PAREN_TYPE",
111 "TUPLE_TYPE",
112 "NEVER_TYPE",
113 "PATH_TYPE",
114 "POINTER_TYPE",
115 "ARRAY_TYPE",
116 "SLICE_TYPE",
117 "REFERENCE_TYPE",
118 "PLACEHOLDER_TYPE",
119 "FN_POINTER_TYPE",
120 "FOR_TYPE",
121 "IMPL_TRAIT_TYPE",
122 "DYN_TRAIT_TYPE",
123 "REF_PAT",
124 "BOX_PAT",
125 "BIND_PAT",
126 "PLACEHOLDER_PAT",
127 "DOT_DOT_PAT",
128 "PATH_PAT",
129 "RECORD_PAT",
130 "RECORD_FIELD_PAT_LIST",
131 "RECORD_FIELD_PAT",
132 "TUPLE_STRUCT_PAT",
133 "TUPLE_PAT",
134 "SLICE_PAT",
135 "RANGE_PAT",
136 "LITERAL_PAT",
137 // atoms
138 "TUPLE_EXPR",
139 "ARRAY_EXPR",
140 "PAREN_EXPR",
141 "PATH_EXPR",
142 "LAMBDA_EXPR",
143 "IF_EXPR",
144 "WHILE_EXPR",
145 "CONDITION",
146 "LOOP_EXPR",
147 "FOR_EXPR",
148 "CONTINUE_EXPR",
149 "BREAK_EXPR",
150 "LABEL",
151 "BLOCK_EXPR",
152 "RETURN_EXPR",
153 "MATCH_EXPR",
154 "MATCH_ARM_LIST",
155 "MATCH_ARM",
156 "MATCH_GUARD",
157 "RECORD_LIT",
158 "RECORD_FIELD_LIST",
159 "RECORD_FIELD",
160 "TRY_BLOCK_EXPR",
161 "BOX_EXPR",
162 // postfix
163 "CALL_EXPR",
164 "INDEX_EXPR",
165 "METHOD_CALL_EXPR",
166 "FIELD_EXPR",
167 "AWAIT_EXPR",
168 "TRY_EXPR",
169 "CAST_EXPR",
170 // unary
171 "REF_EXPR",
172 "PREFIX_EXPR",
173 "RANGE_EXPR", // just weird
174 "BIN_EXPR",
175 "BLOCK",
176 "EXTERN_BLOCK",
177 "EXTERN_ITEM_LIST",
178 "ENUM_VARIANT",
179 "RECORD_FIELD_DEF_LIST",
180 "RECORD_FIELD_DEF",
181 "TUPLE_FIELD_DEF_LIST",
182 "TUPLE_FIELD_DEF",
183 "ENUM_VARIANT_LIST",
184 "ITEM_LIST",
185 "ATTR",
186 "META_ITEM", // not an item actually
187 "USE_TREE",
188 "USE_TREE_LIST",
189 "PATH",
190 "PATH_SEGMENT",
191 "LITERAL",
192 "ALIAS",
193 "VISIBILITY",
194 "WHERE_CLAUSE",
195 "WHERE_PRED",
196 "ABI",
197 "NAME",
198 "NAME_REF",
199 "LET_STMT",
200 "EXPR_STMT",
201 "TYPE_PARAM_LIST",
202 "LIFETIME_PARAM",
203 "TYPE_PARAM",
204 "CONST_PARAM",
205 "TYPE_ARG_LIST",
206 "LIFETIME_ARG",
207 "TYPE_ARG",
208 "ASSOC_TYPE_ARG",
209 "CONST_ARG",
210 "PARAM_LIST",
211 "PARAM",
212 "SELF_PARAM",
213 "ARG_LIST",
214 "TYPE_BOUND",
215 "TYPE_BOUND_LIST",
216 // macro related
217 "MACRO_ITEMS",
218 "MACRO_STMTS",
219 ],
220};
221
222pub(crate) struct AstSrc<'a> {
223 pub(crate) nodes: &'a [AstNodeSrc<'a>],
224 pub(crate) enums: &'a [AstEnumSrc<'a>],
225}
226
227pub(crate) struct AstNodeSrc<'a> {
228 pub(crate) name: &'a str,
229 pub(crate) traits: &'a [&'a str],
230 pub(crate) fields: &'a [(&'a str, FieldSrc<&'a str>)],
231}
232
233pub(crate) enum FieldSrc<T> {
234 Shorthand,
235 Optional(T),
236 Many(T),
237}
238
239pub(crate) struct AstEnumSrc<'a> {
240 pub(crate) name: &'a str,
241 pub(crate) traits: &'a [&'a str],
242 pub(crate) variants: &'a [&'a str],
243}
244
245macro_rules! ast_nodes {
246 ($(
247 struct $name:ident$(: $($trait:ident),*)? {
248 $($field_name:ident $(: $ty:tt)?),*$(,)?
249 }
250 )*) => {
251 [$(
252 AstNodeSrc {
253 name: stringify!($name),
254 traits: &[$($(stringify!($trait)),*)?],
255 fields: &[$(
256 (stringify!($field_name), field_ty!($field_name $($ty)?))
257 ),*],
258
259 }
260 ),*]
261 };
262}
263
264macro_rules! field_ty {
265 ($field_name:ident) => {
266 FieldSrc::Shorthand
267 };
268 ($field_name:ident [$ty:ident]) => {
269 FieldSrc::Many(stringify!($ty))
270 };
271 ($field_name:ident $ty:ident) => {
272 FieldSrc::Optional(stringify!($ty))
273 };
274}
275
276macro_rules! ast_enums {
277 ($(
278 enum $name:ident $(: $($trait:ident),*)? {
279 $($variant:ident),*$(,)?
280 }
281 )*) => {
282 [$(
283 AstEnumSrc {
284 name: stringify!($name),
285 traits: &[$($(stringify!($trait)),*)?],
286 variants: &[$(stringify!($variant)),*],
287
288 }
289 ),*]
290 };
291}
292
293pub(crate) const AST_SRC: AstSrc = AstSrc {
294 nodes: &ast_nodes! {
295 struct SourceFile: ModuleItemOwner, FnDefOwner {
296 modules: [Module],
297 }
298
299 struct FnDef: VisibilityOwner, NameOwner, TypeParamsOwner, DocCommentsOwner, AttrsOwner {
300 ParamList,
301 RetType,
302 body: BlockExpr,
303 }
304
305 struct RetType { TypeRef }
306
307 struct StructDef: VisibilityOwner, NameOwner, TypeParamsOwner, AttrsOwner, DocCommentsOwner {
308 }
309
310 struct UnionDef: VisibilityOwner, NameOwner, TypeParamsOwner, AttrsOwner, DocCommentsOwner {
311 RecordFieldDefList,
312 }
313
314 struct RecordFieldDefList { fields: [RecordFieldDef] }
315 struct RecordFieldDef: VisibilityOwner, NameOwner, AttrsOwner, DocCommentsOwner, TypeAscriptionOwner { }
316
317 struct TupleFieldDefList { fields: [TupleFieldDef] }
318 struct TupleFieldDef: VisibilityOwner, AttrsOwner {
319 TypeRef,
320 }
321
322 struct EnumDef: VisibilityOwner, NameOwner, TypeParamsOwner, AttrsOwner, DocCommentsOwner {
323 variant_list: EnumVariantList,
324 }
325 struct EnumVariantList {
326 variants: [EnumVariant],
327 }
328 struct EnumVariant: NameOwner, DocCommentsOwner, AttrsOwner {
329 Expr
330 }
331
332 struct TraitDef: VisibilityOwner, NameOwner, AttrsOwner, DocCommentsOwner, TypeParamsOwner, TypeBoundsOwner {
333 ItemList,
334 }
335
336 struct Module: VisibilityOwner, NameOwner, AttrsOwner, DocCommentsOwner {
337 ItemList,
338 }
339
340 struct ItemList: FnDefOwner, ModuleItemOwner {
341 impl_items: [ImplItem],
342 }
343
344 struct ConstDef: VisibilityOwner, NameOwner, TypeParamsOwner, AttrsOwner, DocCommentsOwner, TypeAscriptionOwner {
345 body: Expr,
346 }
347
348 struct StaticDef: VisibilityOwner, NameOwner, TypeParamsOwner, AttrsOwner, DocCommentsOwner, TypeAscriptionOwner {
349 body: Expr,
350 }
351
352 struct TypeAliasDef: VisibilityOwner, NameOwner, TypeParamsOwner, AttrsOwner, DocCommentsOwner, TypeBoundsOwner {
353 TypeRef,
354 }
355
356 struct ImplBlock: TypeParamsOwner, AttrsOwner {
357 ItemList,
358 }
359
360 struct ParenType { TypeRef }
361 struct TupleType { fields: [TypeRef] }
362 struct NeverType { }
363 struct PathType { Path }
364 struct PointerType { TypeRef }
365 struct ArrayType { TypeRef, Expr }
366 struct SliceType { TypeRef }
367 struct ReferenceType { TypeRef }
368 struct PlaceholderType { }
369 struct FnPointerType { ParamList, RetType }
370 struct ForType { TypeRef }
371 struct ImplTraitType: TypeBoundsOwner {}
372 struct DynTraitType: TypeBoundsOwner {}
373
374 struct TupleExpr { exprs: [Expr] }
375 struct ArrayExpr { exprs: [Expr] }
376 struct ParenExpr { Expr }
377 struct PathExpr { Path }
378 struct LambdaExpr {
379 ParamList,
380 RetType,
381 body: Expr,
382 }
383 struct IfExpr { Condition }
384 struct LoopExpr: LoopBodyOwner { }
385 struct TryBlockExpr { body: BlockExpr }
386 struct ForExpr: LoopBodyOwner {
387 Pat,
388 iterable: Expr,
389 }
390 struct WhileExpr: LoopBodyOwner { Condition }
391 struct ContinueExpr {}
392 struct BreakExpr { Expr }
393 struct Label {}
394 struct BlockExpr { Block }
395 struct ReturnExpr { Expr }
396 struct CallExpr: ArgListOwner { Expr }
397 struct MethodCallExpr: ArgListOwner {
398 Expr, NameRef, TypeArgList,
399 }
400 struct IndexExpr {}
401 struct FieldExpr { Expr, NameRef }
402 struct AwaitExpr { Expr }
403 struct TryExpr { Expr }
404 struct CastExpr { Expr, TypeRef }
405 struct RefExpr { Expr }
406 struct PrefixExpr { Expr }
407 struct BoxExpr { Expr }
408 struct RangeExpr {}
409 struct BinExpr {}
410 struct Literal {}
411
412 struct MatchExpr { Expr, MatchArmList }
413 struct MatchArmList: AttrsOwner { arms: [MatchArm] }
414 struct MatchArm: AttrsOwner {
415 pats: [Pat],
416 guard: MatchGuard,
417 Expr,
418 }
419 struct MatchGuard { Expr }
420
421 struct RecordLit { Path, RecordFieldList }
422 struct RecordFieldList {
423 fields: [RecordField],
424 spread: Expr,
425 }
426 struct RecordField { NameRef, Expr }
427
428 struct RefPat { Pat }
429 struct BoxPat { Pat }
430 struct BindPat: NameOwner { Pat }
431 struct PlaceholderPat { }
432 struct DotDotPat { }
433 struct PathPat { Path }
434 struct SlicePat {}
435 struct RangePat {}
436 struct LiteralPat { Literal }
437
438 struct RecordPat { RecordFieldPatList, Path }
439 struct RecordFieldPatList {
440 record_field_pats: [RecordFieldPat],
441 bind_pats: [BindPat],
442 }
443 struct RecordFieldPat: NameOwner { Pat }
444
445 struct TupleStructPat { Path, args: [Pat] }
446 struct TuplePat { args: [Pat] }
447
448 struct Visibility {}
449 struct Name {}
450 struct NameRef {}
451
452 struct MacroCall: NameOwner, AttrsOwner,DocCommentsOwner {
453 TokenTree, Path
454 }
455 struct Attr { Path, input: AttrInput }
456 struct TokenTree {}
457 struct TypeParamList {
458 type_params: [TypeParam],
459 lifetime_params: [LifetimeParam],
460 }
461 struct TypeParam: NameOwner, AttrsOwner, TypeBoundsOwner {
462 default_type: TypeRef,
463 }
464 struct ConstParam: NameOwner, AttrsOwner, TypeAscriptionOwner {
465 default_val: Expr,
466 }
467 struct LifetimeParam: AttrsOwner { }
468 struct TypeBound { TypeRef}
469 struct TypeBoundList { bounds: [TypeBound] }
470 struct WherePred: TypeBoundsOwner { TypeRef }
471 struct WhereClause { predicates: [WherePred] }
472 struct ExprStmt { Expr }
473 struct LetStmt: TypeAscriptionOwner {
474 Pat,
475 initializer: Expr,
476 }
477 struct Condition { Pat, Expr }
478 struct Block: AttrsOwner, ModuleItemOwner {
479 statements: [Stmt],
480 Expr,
481 }
482 struct ParamList {
483 SelfParam,
484 params: [Param],
485 }
486 struct SelfParam: TypeAscriptionOwner, AttrsOwner { }
487 struct Param: TypeAscriptionOwner, AttrsOwner {
488 Pat,
489 }
490 struct UseItem: AttrsOwner, VisibilityOwner {
491 UseTree,
492 }
493 struct UseTree {
494 Path, UseTreeList, Alias
495 }
496 struct Alias: NameOwner { }
497 struct UseTreeList { use_trees: [UseTree] }
498 struct ExternCrateItem: AttrsOwner, VisibilityOwner {
499 NameRef, Alias,
500 }
501 struct ArgList {
502 args: [Expr],
503 }
504 struct Path {
505 segment: PathSegment,
506 qualifier: Path,
507 }
508 struct PathSegment {
509 NameRef, TypeArgList, ParamList, RetType, PathType,
510 }
511 struct TypeArgList {
512 type_args: [TypeArg],
513 lifetime_args: [LifetimeArg],
514 assoc_type_args: [AssocTypeArg],
515 const_arg: [ConstArg],
516 }
517 struct TypeArg { TypeRef }
518 struct AssocTypeArg { NameRef, TypeRef }
519 struct LifetimeArg {}
520 struct ConstArg { Literal, BlockExpr }
521
522 struct MacroItems: ModuleItemOwner, FnDefOwner { }
523
524 struct MacroStmts {
525 statements: [Stmt],
526 Expr,
527 }
528 },
529 enums: &ast_enums! {
530 enum NominalDef: NameOwner, TypeParamsOwner, AttrsOwner {
531 StructDef, EnumDef, UnionDef,
532 }
533
534 enum TypeRef {
535 ParenType,
536 TupleType,
537 NeverType,
538 PathType,
539 PointerType,
540 ArrayType,
541 SliceType,
542 ReferenceType,
543 PlaceholderType,
544 FnPointerType,
545 ForType,
546 ImplTraitType,
547 DynTraitType,
548 }
549
550 enum ModuleItem: AttrsOwner, VisibilityOwner {
551 StructDef,
552 UnionDef,
553 EnumDef,
554 FnDef,
555 TraitDef,
556 TypeAliasDef,
557 ImplBlock,
558 UseItem,
559 ExternCrateItem,
560 ConstDef,
561 StaticDef,
562 Module,
563 }
564
565 enum ImplItem: AttrsOwner {
566 FnDef, TypeAliasDef, ConstDef,
567 }
568
569 enum Expr {
570 TupleExpr,
571 ArrayExpr,
572 ParenExpr,
573 PathExpr,
574 LambdaExpr,
575 IfExpr,
576 LoopExpr,
577 ForExpr,
578 WhileExpr,
579 ContinueExpr,
580 BreakExpr,
581 Label,
582 BlockExpr,
583 ReturnExpr,
584 MatchExpr,
585 RecordLit,
586 CallExpr,
587 IndexExpr,
588 MethodCallExpr,
589 FieldExpr,
590 AwaitExpr,
591 TryExpr,
592 TryBlockExpr,
593 CastExpr,
594 RefExpr,
595 PrefixExpr,
596 RangeExpr,
597 BinExpr,
598 Literal,
599 MacroCall,
600 BoxExpr,
601 }
602
603 enum Pat {
604 RefPat,
605 BoxPat,
606 BindPat,
607 PlaceholderPat,
608 DotDotPat,
609 PathPat,
610 RecordPat,
611 TupleStructPat,
612 TuplePat,
613 SlicePat,
614 RangePat,
615 LiteralPat,
616 }
617
618 enum AttrInput { Literal, TokenTree }
619 enum Stmt { ExprStmt, LetStmt }
620 },
621};
diff --git a/xtask/src/boilerplate_gen.rs b/xtask/src/boilerplate_gen.rs
deleted file mode 100644
index e69de29bb..000000000
--- a/xtask/src/boilerplate_gen.rs
+++ /dev/null
diff --git a/xtask/src/cmd.rs b/xtask/src/cmd.rs
new file mode 100644
index 000000000..2027f4893
--- /dev/null
+++ b/xtask/src/cmd.rs
@@ -0,0 +1,53 @@
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/codegen.rs b/xtask/src/codegen.rs
index 53f524f42..158cfc2d6 100644
--- a/xtask/src/codegen.rs
+++ b/xtask/src/codegen.rs
@@ -24,7 +24,6 @@ pub use self::{
24 gen_syntax::generate_syntax, 24 gen_syntax::generate_syntax,
25}; 25};
26 26
27pub const GRAMMAR: &str = "crates/ra_syntax/src/grammar.ron";
28const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar"; 27const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar";
29const OK_INLINE_TESTS_DIR: &str = "crates/ra_syntax/test_data/parser/inline/ok"; 28const OK_INLINE_TESTS_DIR: &str = "crates/ra_syntax/test_data/parser/inline/ok";
30const ERR_INLINE_TESTS_DIR: &str = "crates/ra_syntax/test_data/parser/inline/err"; 29const ERR_INLINE_TESTS_DIR: &str = "crates/ra_syntax/test_data/parser/inline/err";
diff --git a/xtask/src/codegen/gen_syntax.rs b/xtask/src/codegen/gen_syntax.rs
index 88f2ac0e3..0f50ca569 100644
--- a/xtask/src/codegen/gen_syntax.rs
+++ b/xtask/src/codegen/gen_syntax.rs
@@ -3,149 +3,142 @@
3//! Specifically, it generates the `SyntaxKind` enum and a number of newtype 3//! Specifically, it generates the `SyntaxKind` enum and a number of newtype
4//! wrappers around `SyntaxNode` which implement `ra_syntax::AstNode`. 4//! wrappers around `SyntaxNode` which implement `ra_syntax::AstNode`.
5 5
6use std::{collections::BTreeMap, fs};
7
8use proc_macro2::{Punct, Spacing}; 6use proc_macro2::{Punct, Spacing};
9use quote::{format_ident, quote}; 7use quote::{format_ident, quote};
10use ron;
11use serde::Deserialize;
12 8
13use crate::{ 9use crate::{
10 ast_src::{AstSrc, FieldSrc, KindsSrc, AST_SRC, KINDS_SRC},
14 codegen::{self, update, Mode}, 11 codegen::{self, update, Mode},
15 project_root, Result, 12 project_root, Result,
16}; 13};
17 14
18pub fn generate_syntax(mode: Mode) -> Result<()> { 15pub fn generate_syntax(mode: Mode) -> Result<()> {
19 let grammar = project_root().join(codegen::GRAMMAR);
20 let grammar: Grammar = {
21 let text = fs::read_to_string(grammar)?;
22 ron::de::from_str(&text)?
23 };
24
25 let syntax_kinds_file = project_root().join(codegen::SYNTAX_KINDS); 16 let syntax_kinds_file = project_root().join(codegen::SYNTAX_KINDS);
26 let syntax_kinds = generate_syntax_kinds(&grammar)?; 17 let syntax_kinds = generate_syntax_kinds(KINDS_SRC)?;
27 update(syntax_kinds_file.as_path(), &syntax_kinds, mode)?; 18 update(syntax_kinds_file.as_path(), &syntax_kinds, mode)?;
28 19
29 let ast_file = project_root().join(codegen::AST); 20 let ast_file = project_root().join(codegen::AST);
30 let ast = generate_ast(&grammar)?; 21 let ast = generate_ast(AST_SRC)?;
31 update(ast_file.as_path(), &ast, mode)?; 22 update(ast_file.as_path(), &ast, mode)?;
32 23
33 Ok(()) 24 Ok(())
34} 25}
35 26
36fn generate_ast(grammar: &Grammar) -> Result<String> { 27fn generate_ast(grammar: AstSrc<'_>) -> Result<String> {
37 let nodes = grammar.ast.iter().map(|(name, ast_node)| { 28 let nodes = grammar.nodes.iter().map(|node| {
38 let variants = 29 let name = format_ident!("{}", node.name);
39 ast_node.variants.iter().map(|var| format_ident!("{}", var)).collect::<Vec<_>>(); 30 let kind = format_ident!("{}", to_upper_snake_case(&name.to_string()));
40 let name = format_ident!("{}", name); 31 let traits = node.traits.iter().map(|trait_name| {
41 32 let trait_name = format_ident!("{}", trait_name);
42 let adt = if variants.is_empty() { 33 quote!(impl ast::#trait_name for #name {})
43 let kind = format_ident!("{}", to_upper_snake_case(&name.to_string())); 34 });
44 quote! {
45 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
46 pub struct #name {
47 pub(crate) syntax: SyntaxNode,
48 }
49 35
50 impl AstNode for #name { 36 let methods = node.fields.iter().map(|(name, field)| {
51 fn can_cast(kind: SyntaxKind) -> bool { 37 let method_name = match field {
52 match kind { 38 FieldSrc::Shorthand => format_ident!("{}", to_lower_snake_case(&name)),
53 #kind => true, 39 _ => format_ident!("{}", name),
54 _ => false, 40 };
41 let ty = match field {
42 FieldSrc::Optional(ty) | FieldSrc::Many(ty) => ty,
43 FieldSrc::Shorthand => name,
44 };
45 let ty = format_ident!("{}", ty);
46
47 match field {
48 FieldSrc::Many(_) => {
49 quote! {
50 pub fn #method_name(&self) -> AstChildren<#ty> {
51 AstChildren::new(&self.syntax)
55 } 52 }
56 } 53 }
57 fn cast(syntax: SyntaxNode) -> Option<Self> { 54 }
58 if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } 55 FieldSrc::Optional(_) | FieldSrc::Shorthand => {
56 quote! {
57 pub fn #method_name(&self) -> Option<#ty> {
58 AstChildren::new(&self.syntax).next()
59 }
59 } 60 }
60 fn syntax(&self) -> &SyntaxNode { &self.syntax }
61 } 61 }
62 } 62 }
63 } else { 63 });
64 let kinds = variants
65 .iter()
66 .map(|name| format_ident!("{}", to_upper_snake_case(&name.to_string())))
67 .collect::<Vec<_>>();
68
69 quote! {
70 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
71 pub enum #name {
72 #(#variants(#variants),)*
73 }
74 64
75 #( 65 quote! {
76 impl From<#variants> for #name { 66 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
77 fn from(node: #variants) -> #name { 67 pub struct #name {
78 #name::#variants(node) 68 pub(crate) syntax: SyntaxNode,
79 } 69 }
80 }
81 )*
82 70
83 impl AstNode for #name { 71 impl AstNode for #name {
84 fn can_cast(kind: SyntaxKind) -> bool { 72 fn can_cast(kind: SyntaxKind) -> bool {
85 match kind { 73 match kind {
86 #(#kinds)|* => true, 74 #kind => true,
87 _ => false, 75 _ => false,
88 }
89 }
90 fn cast(syntax: SyntaxNode) -> Option<Self> {
91 let res = match syntax.kind() {
92 #(
93 #kinds => #name::#variants(#variants { syntax }),
94 )*
95 _ => return None,
96 };
97 Some(res)
98 }
99 fn syntax(&self) -> &SyntaxNode {
100 match self {
101 #(
102 #name::#variants(it) => &it.syntax,
103 )*
104 }
105 } 76 }
106 } 77 }
78 fn cast(syntax: SyntaxNode) -> Option<Self> {
79 if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None }
80 }
81 fn syntax(&self) -> &SyntaxNode { &self.syntax }
107 } 82 }
108 }; 83 #(#traits)*
84
85 impl #name {
86 #(#methods)*
87 }
88 }
89 });
109 90
110 let traits = ast_node.traits.iter().map(|trait_name| { 91 let enums = grammar.enums.iter().map(|en| {
92 let variants = en.variants.iter().map(|var| format_ident!("{}", var)).collect::<Vec<_>>();
93 let name = format_ident!("{}", en.name);
94 let kinds = variants
95 .iter()
96 .map(|name| format_ident!("{}", to_upper_snake_case(&name.to_string())))
97 .collect::<Vec<_>>();
98 let traits = en.traits.iter().map(|trait_name| {
111 let trait_name = format_ident!("{}", trait_name); 99 let trait_name = format_ident!("{}", trait_name);
112 quote!(impl ast::#trait_name for #name {}) 100 quote!(impl ast::#trait_name for #name {})
113 }); 101 });
114 102
115 let collections = ast_node.collections.iter().map(|(name, kind)| { 103 quote! {
116 let method_name = format_ident!("{}", name); 104 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
117 let kind = format_ident!("{}", kind); 105 pub enum #name {
118 quote! { 106 #(#variants(#variants),)*
119 pub fn #method_name(&self) -> AstChildren<#kind> {
120 AstChildren::new(&self.syntax)
121 }
122 } 107 }
123 });
124 108
125 let options = ast_node.options.iter().map(|attr| { 109 #(
126 let method_name = match attr { 110 impl From<#variants> for #name {
127 Attr::Type(t) => format_ident!("{}", to_lower_snake_case(&t)), 111 fn from(node: #variants) -> #name {
128 Attr::NameType(n, _) => format_ident!("{}", n), 112 #name::#variants(node)
129 };
130 let ty = match attr {
131 Attr::Type(t) | Attr::NameType(_, t) => format_ident!("{}", t),
132 };
133 quote! {
134 pub fn #method_name(&self) -> Option<#ty> {
135 AstChildren::new(&self.syntax).next()
136 } 113 }
137 } 114 }
138 }); 115 )*
139
140 quote! {
141 #adt
142
143 #(#traits)*
144 116
145 impl #name { 117 impl AstNode for #name {
146 #(#collections)* 118 fn can_cast(kind: SyntaxKind) -> bool {
147 #(#options)* 119 match kind {
120 #(#kinds)|* => true,
121 _ => false,
122 }
123 }
124 fn cast(syntax: SyntaxNode) -> Option<Self> {
125 let res = match syntax.kind() {
126 #(
127 #kinds => #name::#variants(#variants { syntax }),
128 )*
129 _ => return None,
130 };
131 Some(res)
132 }
133 fn syntax(&self) -> &SyntaxNode {
134 match self {
135 #(
136 #name::#variants(it) => &it.syntax,
137 )*
138 }
139 }
148 } 140 }
141 #(#traits)*
149 } 142 }
150 }); 143 });
151 144
@@ -156,13 +149,14 @@ fn generate_ast(grammar: &Grammar) -> Result<String> {
156 }; 149 };
157 150
158 #(#nodes)* 151 #(#nodes)*
152 #(#enums)*
159 }; 153 };
160 154
161 let pretty = codegen::reformat(ast)?; 155 let pretty = codegen::reformat(ast)?;
162 Ok(pretty) 156 Ok(pretty)
163} 157}
164 158
165fn generate_syntax_kinds(grammar: &Grammar) -> Result<String> { 159fn generate_syntax_kinds(grammar: KindsSrc<'_>) -> Result<String> {
166 let (single_byte_tokens_values, single_byte_tokens): (Vec<_>, Vec<_>) = grammar 160 let (single_byte_tokens_values, single_byte_tokens): (Vec<_>, Vec<_>) = grammar
167 .punct 161 .punct
168 .iter() 162 .iter()
@@ -274,38 +268,6 @@ fn generate_syntax_kinds(grammar: &Grammar) -> Result<String> {
274 codegen::reformat(ast) 268 codegen::reformat(ast)
275} 269}
276 270
277#[derive(Deserialize, Debug)]
278struct Grammar {
279 punct: Vec<(String, String)>,
280 keywords: Vec<String>,
281 contextual_keywords: Vec<String>,
282 literals: Vec<String>,
283 tokens: Vec<String>,
284 nodes: Vec<String>,
285 ast: BTreeMap<String, AstNode>,
286}
287
288#[derive(Deserialize, Debug)]
289struct AstNode {
290 #[serde(default)]
291 #[serde(rename = "enum")]
292 variants: Vec<String>,
293
294 #[serde(default)]
295 traits: Vec<String>,
296 #[serde(default)]
297 collections: Vec<(String, String)>,
298 #[serde(default)]
299 options: Vec<Attr>,
300}
301
302#[derive(Deserialize, Debug)]
303#[serde(untagged)]
304enum Attr {
305 Type(String),
306 NameType(String, String),
307}
308
309fn to_upper_snake_case(s: &str) -> String { 271fn to_upper_snake_case(s: &str) -> String {
310 let mut buf = String::with_capacity(s.len()); 272 let mut buf = String::with_capacity(s.len());
311 let mut prev_is_upper = None; 273 let mut prev_is_upper = None;
diff --git a/xtask/src/help.rs b/xtask/src/help.rs
deleted file mode 100644
index f4e25dcde..000000000
--- a/xtask/src/help.rs
+++ /dev/null
@@ -1,46 +0,0 @@
1//! FIXME: write short doc here
2
3pub const GLOBAL_HELP: &str = "tasks
4
5USAGE:
6 ra_tools <SUBCOMMAND>
7
8FLAGS:
9 -h, --help Prints help information
10
11SUBCOMMANDS:
12 format
13 install-pre-commit-hook
14 fuzz-tests
15 codegen
16 install
17 lint";
18
19pub const INSTALL_HELP: &str = "ra_tools-install
20
21USAGE:
22 ra_tools.exe install [FLAGS]
23
24FLAGS:
25 --client-code
26 -h, --help Prints help information
27 --jemalloc
28 --server";
29
30pub fn print_no_param_subcommand_help(subcommand: &str) {
31 eprintln!(
32 "ra_tools-{}
33
34USAGE:
35 ra_tools {}
36
37FLAGS:
38 -h, --help Prints help information",
39 subcommand, subcommand
40 );
41}
42
43pub const INSTALL_RA_CONFLICT: &str =
44 "error: The argument `--server` cannot be used with `--client-code`
45
46For more information try --help";
diff --git a/xtask/src/install.rs b/xtask/src/install.rs
new file mode 100644
index 000000000..fa82633de
--- /dev/null
+++ b/xtask/src/install.rs
@@ -0,0 +1,178 @@
1//! Installs rust-analyzer language server and/or editor plugin.
2
3use std::{env, path::PathBuf, str};
4
5use anyhow::{Context, Result};
6
7use crate::cmd::{run, run_with_output, Cmd};
8
9// Latest stable, feel free to send a PR if this lags behind.
10const REQUIRED_RUST_VERSION: u32 = 40;
11
12pub struct InstallCmd {
13 pub client: Option<ClientOpt>,
14 pub server: Option<ServerOpt>,
15}
16
17pub enum ClientOpt {
18 VsCode,
19}
20
21pub struct ServerOpt {
22 pub jemalloc: bool,
23}
24
25impl InstallCmd {
26 pub fn run(self) -> Result<()> {
27 if cfg!(target_os = "macos") {
28 fix_path_for_mac().context("Fix path for mac")?
29 }
30 if let Some(server) = self.server {
31 install_server(server).context("install server")?;
32 }
33 if let Some(client) = self.client {
34 install_client(client).context("install client")?;
35 }
36 Ok(())
37 }
38}
39
40fn fix_path_for_mac() -> Result<()> {
41 let mut vscode_path: Vec<PathBuf> = {
42 const COMMON_APP_PATH: &str =
43 r"/Applications/Visual Studio Code.app/Contents/Resources/app/bin";
44 const ROOT_DIR: &str = "";
45 let home_dir = match env::var("HOME") {
46 Ok(home) => home,
47 Err(e) => anyhow::bail!("Failed getting HOME from environment with error: {}.", e),
48 };
49
50 [ROOT_DIR, &home_dir]
51 .iter()
52 .map(|dir| String::from(*dir) + COMMON_APP_PATH)
53 .map(PathBuf::from)
54 .filter(|path| path.exists())
55 .collect()
56 };
57
58 if !vscode_path.is_empty() {
59 let vars = match env::var_os("PATH") {
60 Some(path) => path,
61 None => anyhow::bail!("Could not get PATH variable from env."),
62 };
63
64 let mut paths = env::split_paths(&vars).collect::<Vec<_>>();
65 paths.append(&mut vscode_path);
66 let new_paths = env::join_paths(paths).context("build env PATH")?;
67 env::set_var("PATH", &new_paths);
68 }
69
70 Ok(())
71}
72
73fn install_client(ClientOpt::VsCode: ClientOpt) -> Result<()> {
74 let npm_version = Cmd {
75 unix: r"npm --version",
76 windows: r"cmd.exe /c npm --version",
77 work_dir: "./editors/code",
78 }
79 .run();
80
81 if npm_version.is_err() {
82 eprintln!("\nERROR: `npm --version` failed, `npm` is required to build the VS Code plugin")
83 }
84
85 Cmd { unix: r"npm install", windows: r"cmd.exe /c npm install", work_dir: "./editors/code" }
86 .run()?;
87 Cmd {
88 unix: r"npm run package --scripts-prepend-node-path",
89 windows: r"cmd.exe /c npm run package",
90 work_dir: "./editors/code",
91 }
92 .run()?;
93
94 let code_binary = ["code", "code-insiders", "codium", "code-oss"].iter().find(|bin| {
95 Cmd {
96 unix: &format!("{} --version", bin),
97 windows: &format!("cmd.exe /c {}.cmd --version", bin),
98 work_dir: "./editors/code",
99 }
100 .run()
101 .is_ok()
102 });
103
104 let code_binary = match code_binary {
105 Some(it) => it,
106 None => anyhow::bail!("Can't execute `code --version`. Perhaps it is not in $PATH?"),
107 };
108
109 Cmd {
110 unix: &format!(r"{} --install-extension ./ra-lsp-0.0.1.vsix --force", code_binary),
111 windows: &format!(
112 r"cmd.exe /c {}.cmd --install-extension ./ra-lsp-0.0.1.vsix --force",
113 code_binary
114 ),
115 work_dir: "./editors/code",
116 }
117 .run()?;
118
119 let output = Cmd {
120 unix: &format!(r"{} --list-extensions", code_binary),
121 windows: &format!(r"cmd.exe /c {}.cmd --list-extensions", code_binary),
122 work_dir: ".",
123 }
124 .run_with_output()?;
125
126 if !str::from_utf8(&output.stdout)?.contains("ra-lsp") {
127 anyhow::bail!(
128 "Could not install the Visual Studio Code extension. \
129 Please make sure you have at least NodeJS 10.x together with the latest version of VS Code installed and try again."
130 );
131 }
132
133 Ok(())
134}
135
136fn install_server(opts: ServerOpt) -> Result<()> {
137 let mut old_rust = false;
138 if let Ok(output) = run_with_output("cargo --version", ".") {
139 if let Ok(stdout) = String::from_utf8(output.stdout) {
140 println!("{}", stdout);
141 if !check_version(&stdout, REQUIRED_RUST_VERSION) {
142 old_rust = true;
143 }
144 }
145 }
146
147 if old_rust {
148 eprintln!(
149 "\nWARNING: at least rust 1.{}.0 is required to compile rust-analyzer\n",
150 REQUIRED_RUST_VERSION,
151 )
152 }
153
154 let res = if opts.jemalloc {
155 run("cargo install --path crates/ra_lsp_server --locked --force --features jemalloc", ".")
156 } else {
157 run("cargo install --path crates/ra_lsp_server --locked --force", ".")
158 };
159
160 if res.is_err() && old_rust {
161 eprintln!(
162 "\nWARNING: at least rust 1.{}.0 is required to compile rust-analyzer\n",
163 REQUIRED_RUST_VERSION,
164 )
165 }
166
167 res
168}
169
170fn check_version(version_output: &str, min_minor_version: u32) -> bool {
171 // Parse second the number out of
172 // cargo 1.39.0-beta (1c6ec66d5 2019-09-30)
173 let minor: Option<u32> = version_output.split('.').nth(1).and_then(|it| it.parse().ok());
174 match minor {
175 None => true,
176 Some(minor) => minor >= min_minor_version,
177 }
178}
diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs
index 40a6682be..e46c21db7 100644
--- a/xtask/src/lib.rs
+++ b/xtask/src/lib.rs
@@ -1,17 +1,22 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3mod cmd;
4pub mod install;
5pub mod pre_commit;
6
3pub mod codegen; 7pub mod codegen;
8mod ast_src;
4 9
5use anyhow::Context; 10use anyhow::Context;
6pub use anyhow::Result;
7use std::{ 11use std::{
8 env, fs, 12 env, fs,
9 io::{Error as IoError, ErrorKind},
10 path::{Path, PathBuf}, 13 path::{Path, PathBuf},
11 process::{Command, Output, Stdio}, 14 process::{Command, Stdio},
12}; 15};
13 16
14use crate::codegen::Mode; 17use crate::{cmd::run, codegen::Mode};
18
19pub use anyhow::Result;
15 20
16const TOOLCHAIN: &str = "stable"; 21const TOOLCHAIN: &str = "stable";
17 22
@@ -25,40 +30,6 @@ pub fn project_root() -> PathBuf {
25 .to_path_buf() 30 .to_path_buf()
26} 31}
27 32
28pub struct Cmd<'a> {
29 pub unix: &'a str,
30 pub windows: &'a str,
31 pub work_dir: &'a str,
32}
33
34impl Cmd<'_> {
35 pub fn run(self) -> Result<()> {
36 if cfg!(windows) {
37 run(self.windows, self.work_dir)
38 } else {
39 run(self.unix, self.work_dir)
40 }
41 }
42 pub fn run_with_output(self) -> Result<Output> {
43 if cfg!(windows) {
44 run_with_output(self.windows, self.work_dir)
45 } else {
46 run_with_output(self.unix, self.work_dir)
47 }
48 }
49}
50
51pub fn run(cmdline: &str, dir: &str) -> Result<()> {
52 do_run(cmdline, dir, |c| {
53 c.stdout(Stdio::inherit());
54 })
55 .map(|_| ())
56}
57
58pub fn run_with_output(cmdline: &str, dir: &str) -> Result<Output> {
59 do_run(cmdline, dir, |_| {})
60}
61
62pub fn run_rustfmt(mode: Mode) -> Result<()> { 33pub fn run_rustfmt(mode: Mode) -> Result<()> {
63 match Command::new("rustup") 34 match Command::new("rustup")
64 .args(&["run", TOOLCHAIN, "--", "cargo", "fmt", "--version"]) 35 .args(&["run", TOOLCHAIN, "--", "cargo", "fmt", "--version"])
@@ -78,23 +49,11 @@ pub fn run_rustfmt(mode: Mode) -> Result<()> {
78 Ok(()) 49 Ok(())
79} 50}
80 51
81pub fn install_rustfmt() -> Result<()> { 52fn install_rustfmt() -> Result<()> {
82 run(&format!("rustup toolchain install {}", TOOLCHAIN), ".")?; 53 run(&format!("rustup toolchain install {}", TOOLCHAIN), ".")?;
83 run(&format!("rustup component add rustfmt --toolchain {}", TOOLCHAIN), ".") 54 run(&format!("rustup component add rustfmt --toolchain {}", TOOLCHAIN), ".")
84} 55}
85 56
86pub fn install_pre_commit_hook() -> Result<()> {
87 let result_path =
88 PathBuf::from(format!("./.git/hooks/pre-commit{}", std::env::consts::EXE_SUFFIX));
89 if !result_path.exists() {
90 let me = std::env::current_exe()?;
91 fs::copy(me, result_path)?;
92 } else {
93 Err(IoError::new(ErrorKind::AlreadyExists, "Git hook already created"))?;
94 }
95 Ok(())
96}
97
98pub fn run_clippy() -> Result<()> { 57pub fn run_clippy() -> Result<()> {
99 match Command::new("rustup") 58 match Command::new("rustup")
100 .args(&["run", TOOLCHAIN, "--", "cargo", "clippy", "--version"]) 59 .args(&["run", TOOLCHAIN, "--", "cargo", "clippy", "--version"])
@@ -124,7 +83,7 @@ pub fn run_clippy() -> Result<()> {
124 Ok(()) 83 Ok(())
125} 84}
126 85
127pub fn install_clippy() -> Result<()> { 86fn install_clippy() -> Result<()> {
128 run(&format!("rustup toolchain install {}", TOOLCHAIN), ".")?; 87 run(&format!("rustup toolchain install {}", TOOLCHAIN), ".")?;
129 run(&format!("rustup component add clippy --toolchain {}", TOOLCHAIN), ".") 88 run(&format!("rustup component add clippy --toolchain {}", TOOLCHAIN), ".")
130} 89}
@@ -143,41 +102,40 @@ pub fn run_fuzzer() -> Result<()> {
143 run("rustup run nightly -- cargo fuzz run parser", "./crates/ra_syntax") 102 run("rustup run nightly -- cargo fuzz run parser", "./crates/ra_syntax")
144} 103}
145 104
146pub fn reformat_staged_files() -> Result<()> { 105/// Cleans the `./target` dir after the build such that only
147 run_rustfmt(Mode::Overwrite)?; 106/// dependencies are cached on CI.
148 let root = project_root(); 107pub fn run_pre_cache() -> Result<()> {
149 let output = Command::new("git") 108 let slow_tests_cookie = Path::new("./target/.slow_tests_cookie");
150 .arg("diff") 109 if !slow_tests_cookie.exists() {
151 .arg("--diff-filter=MAR") 110 panic!("slow tests were skipped on CI!")
152 .arg("--name-only")
153 .arg("--cached")
154 .current_dir(&root)
155 .output()?;
156 if !output.status.success() {
157 anyhow::bail!(
158 "`git diff --diff-filter=MAR --name-only --cached` exited with {}",
159 output.status
160 );
161 } 111 }
162 for line in String::from_utf8(output.stdout)?.lines() { 112 rm_rf(slow_tests_cookie)?;
163 run(&format!("git update-index --add {}", root.join(line).to_string_lossy()), ".")?; 113
114 for entry in Path::new("./target/debug").read_dir()? {
115 let entry = entry?;
116 if entry.file_type().map(|it| it.is_file()).ok() == Some(true) {
117 // Can't delete yourself on windows :-(
118 if !entry.path().ends_with("xtask.exe") {
119 rm_rf(&entry.path())?
120 }
121 }
122 }
123
124 fs::remove_file("./target/.rustc_info.json")?;
125 let to_delete = ["ra_", "heavy_test"];
126 for &dir in ["./target/debug/deps", "target/debug/.fingerprint"].iter() {
127 for entry in Path::new(dir).read_dir()? {
128 let entry = entry?;
129 if to_delete.iter().any(|&it| entry.path().display().to_string().contains(it)) {
130 rm_rf(&entry.path())?
131 }
132 }
164 } 133 }
134
165 Ok(()) 135 Ok(())
166} 136}
167 137
168fn do_run<F>(cmdline: &str, dir: &str, mut f: F) -> Result<Output> 138fn rm_rf(path: &Path) -> Result<()> {
169where 139 if path.is_file() { fs::remove_file(path) } else { fs::remove_dir_all(path) }
170 F: FnMut(&mut Command), 140 .with_context(|| format!("failed to remove {:?}", path))
171{
172 eprintln!("\nwill run: {}", cmdline);
173 let proj_dir = project_root().join(dir);
174 let mut args = cmdline.split_whitespace();
175 let exec = args.next().unwrap();
176 let mut cmd = Command::new(exec);
177 f(cmd.args(args).current_dir(proj_dir).stderr(Stdio::inherit()));
178 let output = cmd.output().with_context(|| format!("running `{}`", cmdline))?;
179 if !output.status.success() {
180 anyhow::bail!("`{}` exited with {}", cmdline, output.status);
181 }
182 Ok(output)
183} 141}
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 9cefad925..c347de9ab 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -7,272 +7,109 @@
7//! 7//!
8//! This binary is integrated into the `cargo` command line by using an alias in 8//! This binary is integrated into the `cargo` command line by using an alias in
9//! `.cargo/config`. 9//! `.cargo/config`.
10mod help;
11 10
12use std::{env, fmt::Write, path::PathBuf, str}; 11use std::env;
13 12
14use anyhow::Context;
15use pico_args::Arguments; 13use pico_args::Arguments;
16use xtask::{ 14use xtask::{
17 codegen::{self, Mode}, 15 codegen::{self, Mode},
18 install_pre_commit_hook, reformat_staged_files, run, run_clippy, run_fuzzer, run_rustfmt, 16 install::{ClientOpt, InstallCmd, ServerOpt},
19 run_with_output, Cmd, Result, 17 pre_commit, run_clippy, run_fuzzer, run_pre_cache, run_rustfmt, Result,
20}; 18};
21 19
22// Latest stable, feel free to send a PR if this lags behind.
23const REQUIRED_RUST_VERSION: u32 = 40;
24
25struct InstallOpt {
26 client: Option<ClientOpt>,
27 server: Option<ServerOpt>,
28}
29
30enum ClientOpt {
31 VsCode,
32}
33
34struct ServerOpt {
35 jemalloc: bool,
36}
37
38fn main() -> Result<()> { 20fn main() -> Result<()> {
39 if env::args().next().map(|it| it.contains("pre-commit")) == Some(true) { 21 if env::args().next().map(|it| it.contains("pre-commit")) == Some(true) {
40 return reformat_staged_files(); 22 return pre_commit::run_hook();
41 } 23 }
42 24
43 let subcommand = match std::env::args_os().nth(1) { 25 let mut args = Arguments::from_env();
44 None => { 26 let subcommand = args.subcommand()?.unwrap_or_default();
45 eprintln!("{}", help::GLOBAL_HELP); 27
46 return Ok(()); 28 match subcommand.as_str() {
47 }
48 Some(s) => s,
49 };
50 let mut matches = Arguments::from_vec(std::env::args_os().skip(2).collect());
51 let subcommand = &*subcommand.to_string_lossy();
52 match subcommand {
53 "install" => { 29 "install" => {
54 if matches.contains(["-h", "--help"]) { 30 if args.contains(["-h", "--help"]) {
55 eprintln!("{}", help::INSTALL_HELP); 31 eprintln!(
32 "\
33cargo xtask install
34Install rust-analyzer server or editor plugin.
35
36USAGE:
37 cargo xtask install [FLAGS]
38
39FLAGS:
40 --client-code Install only VS Code plugin
41 --server Install only the language server
42 --jemalloc Use jemalloc for server
43 -h, --help Prints help information
44 "
45 );
56 return Ok(()); 46 return Ok(());
57 } 47 }
58 let server = matches.contains("--server"); 48 let server = args.contains("--server");
59 let client_code = matches.contains("--client-code"); 49 let client_code = args.contains("--client-code");
60 if server && client_code { 50 if server && client_code {
61 eprintln!("{}", help::INSTALL_RA_CONFLICT); 51 eprintln!(
52 "error: The argument `--server` cannot be used with `--client-code`\n\n\
53 For more information try --help"
54 );
62 return Ok(()); 55 return Ok(());
63 } 56 }
64 let jemalloc = matches.contains("--jemalloc"); 57
65 matches.finish().or_else(handle_extra_flags)?; 58 let jemalloc = args.contains("--jemalloc");
66 let opts = InstallOpt { 59
60 args.finish()?;
61
62 InstallCmd {
67 client: if server { None } else { Some(ClientOpt::VsCode) }, 63 client: if server { None } else { Some(ClientOpt::VsCode) },
68 server: if client_code { None } else { Some(ServerOpt { jemalloc }) }, 64 server: if client_code { None } else { Some(ServerOpt { jemalloc }) },
69 }; 65 }
70 install(opts)? 66 .run()
71 } 67 }
72 "codegen" => { 68 "codegen" => {
73 if matches.contains(["-h", "--help"]) { 69 args.finish()?;
74 help::print_no_param_subcommand_help(&subcommand);
75 return Ok(());
76 }
77 codegen::generate_syntax(Mode::Overwrite)?; 70 codegen::generate_syntax(Mode::Overwrite)?;
78 codegen::generate_parser_tests(Mode::Overwrite)?; 71 codegen::generate_parser_tests(Mode::Overwrite)?;
79 codegen::generate_assists_docs(Mode::Overwrite)?; 72 codegen::generate_assists_docs(Mode::Overwrite)?;
73 Ok(())
80 } 74 }
81 "format" => { 75 "format" => {
82 if matches.contains(["-h", "--help"]) { 76 args.finish()?;
83 help::print_no_param_subcommand_help(&subcommand); 77 run_rustfmt(Mode::Overwrite)
84 return Ok(());
85 }
86 run_rustfmt(Mode::Overwrite)?
87 } 78 }
88 "install-pre-commit-hook" => { 79 "install-pre-commit-hook" => {
89 if matches.contains(["-h", "--help"]) { 80 args.finish()?;
90 help::print_no_param_subcommand_help(&subcommand); 81 pre_commit::install_hook()
91 return Ok(());
92 }
93 install_pre_commit_hook()?
94 } 82 }
95 "lint" => { 83 "lint" => {
96 if matches.contains(["-h", "--help"]) { 84 args.finish()?;
97 help::print_no_param_subcommand_help(&subcommand); 85 run_clippy()
98 return Ok(());
99 }
100 run_clippy()?
101 } 86 }
102 "fuzz-tests" => { 87 "fuzz-tests" => {
103 if matches.contains(["-h", "--help"]) { 88 args.finish()?;
104 help::print_no_param_subcommand_help(&subcommand); 89 run_fuzzer()
105 return Ok(());
106 }
107 run_fuzzer()?
108 } 90 }
109 _ => eprintln!("{}", help::GLOBAL_HELP), 91 "pre-cache" => {
110 } 92 args.finish()?;
111 Ok(()) 93 run_pre_cache()
112}
113
114fn handle_extra_flags(e: pico_args::Error) -> Result<()> {
115 if let pico_args::Error::UnusedArgsLeft(flags) = e {
116 let mut invalid_flags = String::new();
117 for flag in flags {
118 write!(&mut invalid_flags, "{}, ", flag)?;
119 }
120 let (invalid_flags, _) = invalid_flags.split_at(invalid_flags.len() - 2);
121 anyhow::bail!("Invalid flags: {}", invalid_flags)
122 } else {
123 anyhow::bail!(e.to_string())
124 }
125}
126
127fn install(opts: InstallOpt) -> Result<()> {
128 if cfg!(target_os = "macos") {
129 fix_path_for_mac().context("Fix path for mac")?
130 }
131 if let Some(server) = opts.server {
132 install_server(server).context("install server")?;
133 }
134 if let Some(client) = opts.client {
135 install_client(client).context("install client")?;
136 }
137 Ok(())
138}
139
140fn fix_path_for_mac() -> Result<()> {
141 let mut vscode_path: Vec<PathBuf> = {
142 const COMMON_APP_PATH: &str =
143 r"/Applications/Visual Studio Code.app/Contents/Resources/app/bin";
144 const ROOT_DIR: &str = "";
145 let home_dir = match env::var("HOME") {
146 Ok(home) => home,
147 Err(e) => anyhow::bail!("Failed getting HOME from environment with error: {}.", e),
148 };
149
150 [ROOT_DIR, &home_dir]
151 .iter()
152 .map(|dir| String::from(*dir) + COMMON_APP_PATH)
153 .map(PathBuf::from)
154 .filter(|path| path.exists())
155 .collect()
156 };
157
158 if !vscode_path.is_empty() {
159 let vars = match env::var_os("PATH") {
160 Some(path) => path,
161 None => anyhow::bail!("Could not get PATH variable from env."),
162 };
163
164 let mut paths = env::split_paths(&vars).collect::<Vec<_>>();
165 paths.append(&mut vscode_path);
166 let new_paths = env::join_paths(paths).context("build env PATH")?;
167 env::set_var("PATH", &new_paths);
168 }
169
170 Ok(())
171}
172
173fn install_client(ClientOpt::VsCode: ClientOpt) -> Result<()> {
174 let npm_version = Cmd {
175 unix: r"npm --version",
176 windows: r"cmd.exe /c npm --version",
177 work_dir: "./editors/code",
178 }
179 .run();
180
181 if npm_version.is_err() {
182 eprintln!("\nERROR: `npm --version` failed, `npm` is required to build the VS Code plugin")
183 }
184
185 Cmd { unix: r"npm install", windows: r"cmd.exe /c npm install", work_dir: "./editors/code" }
186 .run()?;
187 Cmd {
188 unix: r"npm run package --scripts-prepend-node-path",
189 windows: r"cmd.exe /c npm run package",
190 work_dir: "./editors/code",
191 }
192 .run()?;
193
194 let code_binary = ["code", "code-insiders", "codium"].iter().find(|bin| {
195 Cmd {
196 unix: &format!("{} --version", bin),
197 windows: &format!("cmd.exe /c {}.cmd --version", bin),
198 work_dir: "./editors/code",
199 } 94 }
200 .run() 95 _ => {
201 .is_ok() 96 eprintln!(
202 }); 97 "\
203 98cargo xtask
204 let code_binary = match code_binary { 99Run custom build command.
205 Some(it) => it, 100
206 None => anyhow::bail!("Can't execute `code --version`. Perhaps it is not in $PATH?"), 101USAGE:
207 }; 102 cargo xtask <SUBCOMMAND>
208 103
209 Cmd { 104SUBCOMMANDS:
210 unix: &format!(r"{} --install-extension ./ra-lsp-0.0.1.vsix --force", code_binary), 105 format
211 windows: &format!( 106 install-pre-commit-hook
212 r"cmd.exe /c {}.cmd --install-extension ./ra-lsp-0.0.1.vsix --force", 107 fuzz-tests
213 code_binary 108 codegen
214 ), 109 install
215 work_dir: "./editors/code", 110 lint"
216 } 111 );
217 .run()?; 112 Ok(())
218
219 let output = Cmd {
220 unix: &format!(r"{} --list-extensions", code_binary),
221 windows: &format!(r"cmd.exe /c {}.cmd --list-extensions", code_binary),
222 work_dir: ".",
223 }
224 .run_with_output()?;
225
226 if !str::from_utf8(&output.stdout)?.contains("ra-lsp") {
227 anyhow::bail!(
228 "Could not install the Visual Studio Code extension. \
229 Please make sure you have at least NodeJS 10.x together with the latest version of VS Code installed and try again."
230 );
231 }
232
233 Ok(())
234}
235
236fn install_server(opts: ServerOpt) -> Result<()> {
237 let mut old_rust = false;
238 if let Ok(output) = run_with_output("cargo --version", ".") {
239 if let Ok(stdout) = String::from_utf8(output.stdout) {
240 println!("{}", stdout);
241 if !check_version(&stdout, REQUIRED_RUST_VERSION) {
242 old_rust = true;
243 }
244 } 113 }
245 } 114 }
246
247 if old_rust {
248 eprintln!(
249 "\nWARNING: at least rust 1.{}.0 is required to compile rust-analyzer\n",
250 REQUIRED_RUST_VERSION,
251 )
252 }
253
254 let res = if opts.jemalloc {
255 run("cargo install --path crates/ra_lsp_server --locked --force --features jemalloc", ".")
256 } else {
257 run("cargo install --path crates/ra_lsp_server --locked --force", ".")
258 };
259
260 if res.is_err() && old_rust {
261 eprintln!(
262 "\nWARNING: at least rust 1.{}.0 is required to compile rust-analyzer\n",
263 REQUIRED_RUST_VERSION,
264 )
265 }
266
267 res
268}
269
270fn check_version(version_output: &str, min_minor_version: u32) -> bool {
271 // Parse second the number out of
272 // cargo 1.39.0-beta (1c6ec66d5 2019-09-30)
273 let minor: Option<u32> = version_output.split('.').nth(1).and_then(|it| it.parse().ok());
274 match minor {
275 None => true,
276 Some(minor) => minor >= min_minor_version,
277 }
278} 115}
diff --git a/xtask/src/pre_commit.rs b/xtask/src/pre_commit.rs
new file mode 100644
index 000000000..88e868ca6
--- /dev/null
+++ b/xtask/src/pre_commit.rs
@@ -0,0 +1,36 @@
1//! pre-commit hook for code formatting.
2
3use std::{fs, path::PathBuf};
4
5use anyhow::{bail, Result};
6
7use crate::{cmd::run_with_output, project_root, run, run_rustfmt, Mode};
8
9// FIXME: if there are changed `.ts` files, also reformat TypeScript (by
10// shelling out to `npm fmt`).
11pub fn run_hook() -> Result<()> {
12 run_rustfmt(Mode::Overwrite)?;
13
14 let diff = run_with_output("git diff --diff-filter=MAR --name-only --cached", ".")?;
15
16 let root = project_root();
17 for line in String::from_utf8(diff.stdout)?.lines() {
18 run(&format!("git update-index --add {}", root.join(line).to_string_lossy()), ".")?;
19 }
20
21 Ok(())
22}
23
24pub fn install_hook() -> Result<()> {
25 let hook_path: PathBuf =
26 format!("./.git/hooks/pre-commit{}", std::env::consts::EXE_SUFFIX).into();
27
28 if hook_path.exists() {
29 bail!("Git hook already created");
30 }
31
32 let me = std::env::current_exe()?;
33 fs::copy(me, hook_path)?;
34
35 Ok(())
36}