diff options
Diffstat (limited to 'xtask')
-rw-r--r-- | xtask/Cargo.toml | 2 | ||||
-rw-r--r-- | xtask/src/ast_src.rs | 621 | ||||
-rw-r--r-- | xtask/src/boilerplate_gen.rs | 0 | ||||
-rw-r--r-- | xtask/src/cmd.rs | 53 | ||||
-rw-r--r-- | xtask/src/codegen.rs | 1 | ||||
-rw-r--r-- | xtask/src/codegen/gen_syntax.rs | 234 | ||||
-rw-r--r-- | xtask/src/help.rs | 46 | ||||
-rw-r--r-- | xtask/src/install.rs | 178 | ||||
-rw-r--r-- | xtask/src/lib.rs | 126 | ||||
-rw-r--r-- | xtask/src/main.rs | 301 | ||||
-rw-r--r-- | xtask/src/pre_commit.rs | 36 |
11 files changed, 1097 insertions, 501 deletions
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 65cabf005..72dd5e581 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml | |||
@@ -13,6 +13,4 @@ walkdir = "2.1.3" | |||
13 | pico-args = "0.3.0" | 13 | pico-args = "0.3.0" |
14 | quote = "1.0.2" | 14 | quote = "1.0.2" |
15 | proc-macro2 = "1.0.1" | 15 | proc-macro2 = "1.0.1" |
16 | ron = "0.5.1" | ||
17 | serde = { version = "1.0.0", features = ["derive"] } | ||
18 | anyhow = "1.0.19" | 16 | anyhow = "1.0.19" |
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 @@ | |||
1 | pub(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 | |||
10 | pub(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 | |||
222 | pub(crate) struct AstSrc<'a> { | ||
223 | pub(crate) nodes: &'a [AstNodeSrc<'a>], | ||
224 | pub(crate) enums: &'a [AstEnumSrc<'a>], | ||
225 | } | ||
226 | |||
227 | pub(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 | |||
233 | pub(crate) enum FieldSrc<T> { | ||
234 | Shorthand, | ||
235 | Optional(T), | ||
236 | Many(T), | ||
237 | } | ||
238 | |||
239 | pub(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 | |||
245 | macro_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 | |||
264 | macro_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 | |||
276 | macro_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 | |||
293 | pub(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 @@ | |||
1 | use std::process::{Command, Output, Stdio}; | ||
2 | |||
3 | use anyhow::{Context, Result}; | ||
4 | |||
5 | use crate::project_root; | ||
6 | |||
7 | pub struct Cmd<'a> { | ||
8 | pub unix: &'a str, | ||
9 | pub windows: &'a str, | ||
10 | pub work_dir: &'a str, | ||
11 | } | ||
12 | |||
13 | impl 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 | |||
30 | pub fn run(cmdline: &str, dir: &str) -> Result<()> { | ||
31 | do_run(cmdline, dir, &mut |c| { | ||
32 | c.stdout(Stdio::inherit()); | ||
33 | }) | ||
34 | .map(|_| ()) | ||
35 | } | ||
36 | |||
37 | pub fn run_with_output(cmdline: &str, dir: &str) -> Result<Output> { | ||
38 | do_run(cmdline, dir, &mut |_| {}) | ||
39 | } | ||
40 | |||
41 | fn 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 | ||
27 | pub const GRAMMAR: &str = "crates/ra_syntax/src/grammar.ron"; | ||
28 | const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar"; | 27 | const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar"; |
29 | const OK_INLINE_TESTS_DIR: &str = "crates/ra_syntax/test_data/parser/inline/ok"; | 28 | const OK_INLINE_TESTS_DIR: &str = "crates/ra_syntax/test_data/parser/inline/ok"; |
30 | const ERR_INLINE_TESTS_DIR: &str = "crates/ra_syntax/test_data/parser/inline/err"; | 29 | const 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 | ||
6 | use std::{collections::BTreeMap, fs}; | ||
7 | |||
8 | use proc_macro2::{Punct, Spacing}; | 6 | use proc_macro2::{Punct, Spacing}; |
9 | use quote::{format_ident, quote}; | 7 | use quote::{format_ident, quote}; |
10 | use ron; | ||
11 | use serde::Deserialize; | ||
12 | 8 | ||
13 | use crate::{ | 9 | use 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 | ||
18 | pub fn generate_syntax(mode: Mode) -> Result<()> { | 15 | pub 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 | ||
36 | fn generate_ast(grammar: &Grammar) -> Result<String> { | 27 | fn 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 | ||
165 | fn generate_syntax_kinds(grammar: &Grammar) -> Result<String> { | 159 | fn 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)] | ||
278 | struct 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)] | ||
289 | struct 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)] | ||
304 | enum Attr { | ||
305 | Type(String), | ||
306 | NameType(String, String), | ||
307 | } | ||
308 | |||
309 | fn to_upper_snake_case(s: &str) -> String { | 271 | fn 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 | |||
3 | pub const GLOBAL_HELP: &str = "tasks | ||
4 | |||
5 | USAGE: | ||
6 | ra_tools <SUBCOMMAND> | ||
7 | |||
8 | FLAGS: | ||
9 | -h, --help Prints help information | ||
10 | |||
11 | SUBCOMMANDS: | ||
12 | format | ||
13 | install-pre-commit-hook | ||
14 | fuzz-tests | ||
15 | codegen | ||
16 | install | ||
17 | lint"; | ||
18 | |||
19 | pub const INSTALL_HELP: &str = "ra_tools-install | ||
20 | |||
21 | USAGE: | ||
22 | ra_tools.exe install [FLAGS] | ||
23 | |||
24 | FLAGS: | ||
25 | --client-code | ||
26 | -h, --help Prints help information | ||
27 | --jemalloc | ||
28 | --server"; | ||
29 | |||
30 | pub fn print_no_param_subcommand_help(subcommand: &str) { | ||
31 | eprintln!( | ||
32 | "ra_tools-{} | ||
33 | |||
34 | USAGE: | ||
35 | ra_tools {} | ||
36 | |||
37 | FLAGS: | ||
38 | -h, --help Prints help information", | ||
39 | subcommand, subcommand | ||
40 | ); | ||
41 | } | ||
42 | |||
43 | pub const INSTALL_RA_CONFLICT: &str = | ||
44 | "error: The argument `--server` cannot be used with `--client-code` | ||
45 | |||
46 | For 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 | |||
3 | use std::{env, path::PathBuf, str}; | ||
4 | |||
5 | use anyhow::{Context, Result}; | ||
6 | |||
7 | use crate::cmd::{run, run_with_output, Cmd}; | ||
8 | |||
9 | // Latest stable, feel free to send a PR if this lags behind. | ||
10 | const REQUIRED_RUST_VERSION: u32 = 40; | ||
11 | |||
12 | pub struct InstallCmd { | ||
13 | pub client: Option<ClientOpt>, | ||
14 | pub server: Option<ServerOpt>, | ||
15 | } | ||
16 | |||
17 | pub enum ClientOpt { | ||
18 | VsCode, | ||
19 | } | ||
20 | |||
21 | pub struct ServerOpt { | ||
22 | pub jemalloc: bool, | ||
23 | } | ||
24 | |||
25 | impl 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 | |||
40 | fn 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 | |||
73 | fn 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 | |||
136 | fn 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 | |||
170 | fn 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 | ||
3 | mod cmd; | ||
4 | pub mod install; | ||
5 | pub mod pre_commit; | ||
6 | |||
3 | pub mod codegen; | 7 | pub mod codegen; |
8 | mod ast_src; | ||
4 | 9 | ||
5 | use anyhow::Context; | 10 | use anyhow::Context; |
6 | pub use anyhow::Result; | ||
7 | use std::{ | 11 | use 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 | ||
14 | use crate::codegen::Mode; | 17 | use crate::{cmd::run, codegen::Mode}; |
18 | |||
19 | pub use anyhow::Result; | ||
15 | 20 | ||
16 | const TOOLCHAIN: &str = "stable"; | 21 | const 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 | ||
28 | pub struct Cmd<'a> { | ||
29 | pub unix: &'a str, | ||
30 | pub windows: &'a str, | ||
31 | pub work_dir: &'a str, | ||
32 | } | ||
33 | |||
34 | impl 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 | |||
51 | pub fn run(cmdline: &str, dir: &str) -> Result<()> { | ||
52 | do_run(cmdline, dir, |c| { | ||
53 | c.stdout(Stdio::inherit()); | ||
54 | }) | ||
55 | .map(|_| ()) | ||
56 | } | ||
57 | |||
58 | pub fn run_with_output(cmdline: &str, dir: &str) -> Result<Output> { | ||
59 | do_run(cmdline, dir, |_| {}) | ||
60 | } | ||
61 | |||
62 | pub fn run_rustfmt(mode: Mode) -> Result<()> { | 33 | pub 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 | ||
81 | pub fn install_rustfmt() -> Result<()> { | 52 | fn 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 | ||
86 | pub 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 | |||
98 | pub fn run_clippy() -> Result<()> { | 57 | pub 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 | ||
127 | pub fn install_clippy() -> Result<()> { | 86 | fn 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 | ||
146 | pub 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(); | 107 | pub 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 | ||
168 | fn do_run<F>(cmdline: &str, dir: &str, mut f: F) -> Result<Output> | 138 | fn rm_rf(path: &Path) -> Result<()> { |
169 | where | 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`. |
10 | mod help; | ||
11 | 10 | ||
12 | use std::{env, fmt::Write, path::PathBuf, str}; | 11 | use std::env; |
13 | 12 | ||
14 | use anyhow::Context; | ||
15 | use pico_args::Arguments; | 13 | use pico_args::Arguments; |
16 | use xtask::{ | 14 | use 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. | ||
23 | const REQUIRED_RUST_VERSION: u32 = 40; | ||
24 | |||
25 | struct InstallOpt { | ||
26 | client: Option<ClientOpt>, | ||
27 | server: Option<ServerOpt>, | ||
28 | } | ||
29 | |||
30 | enum ClientOpt { | ||
31 | VsCode, | ||
32 | } | ||
33 | |||
34 | struct ServerOpt { | ||
35 | jemalloc: bool, | ||
36 | } | ||
37 | |||
38 | fn main() -> Result<()> { | 20 | fn 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 | "\ | ||
33 | cargo xtask install | ||
34 | Install rust-analyzer server or editor plugin. | ||
35 | |||
36 | USAGE: | ||
37 | cargo xtask install [FLAGS] | ||
38 | |||
39 | FLAGS: | ||
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 | |||
114 | fn 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 | |||
127 | fn 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 | |||
140 | fn 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 | |||
173 | fn 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 | 98 | cargo xtask | |
204 | let code_binary = match code_binary { | 99 | Run custom build command. |
205 | Some(it) => it, | 100 | |
206 | None => anyhow::bail!("Can't execute `code --version`. Perhaps it is not in $PATH?"), | 101 | USAGE: |
207 | }; | 102 | cargo xtask <SUBCOMMAND> |
208 | 103 | ||
209 | Cmd { | 104 | SUBCOMMANDS: |
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 | |||
236 | fn 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 | |||
270 | fn 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 | |||
3 | use std::{fs, path::PathBuf}; | ||
4 | |||
5 | use anyhow::{bail, Result}; | ||
6 | |||
7 | use 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`). | ||
11 | pub 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 | |||
24 | pub 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 | } | ||