aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-01-07 13:42:56 +0000
committerAleksey Kladov <[email protected]>2020-01-07 13:42:56 +0000
commit91f9bc2b866fc87dbaadb29a8c72eba10ae57c5c (patch)
tree685e2dc2f1e02aadbcd94de5076692a56a09a475
parentb02576d5623478526328b4716b1fd9c5c2efd637 (diff)
Refactor xtasks
-rw-r--r--xtask/src/help.rs33
-rw-r--r--xtask/src/install.rs178
-rw-r--r--xtask/src/lib.rs41
-rw-r--r--xtask/src/main.rs283
-rw-r--r--xtask/src/pre_commit.rs36
5 files changed, 290 insertions, 281 deletions
diff --git a/xtask/src/help.rs b/xtask/src/help.rs
deleted file mode 100644
index 9c78ba37f..000000000
--- a/xtask/src/help.rs
+++ /dev/null
@@ -1,33 +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 const INSTALL_RA_CONFLICT: &str =
31 "error: The argument `--server` cannot be used with `--client-code`
32
33For more information try --help";
diff --git a/xtask/src/install.rs b/xtask/src/install.rs
new file mode 100644
index 000000000..ab6ed92f7
--- /dev/null
+++ b/xtask/src/install.rs
@@ -0,0 +1,178 @@
1//! Installs rust-analyzer langauge server and/or editor plugin.
2
3use std::{env, path::PathBuf, str};
4
5use anyhow::{Context, Result};
6
7use crate::{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"].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 51a868dee..fb853e71a 100644
--- a/xtask/src/lib.rs
+++ b/xtask/src/lib.rs
@@ -1,13 +1,14 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3pub mod codegen; 3pub mod codegen;
4pub mod install;
5pub mod pre_commit;
4mod ast_src; 6mod ast_src;
5 7
6use anyhow::Context; 8use anyhow::Context;
7pub use anyhow::Result; 9pub use anyhow::Result;
8use std::{ 10use std::{
9 env, fs, 11 env,
10 io::{Error as IoError, ErrorKind},
11 path::{Path, PathBuf}, 12 path::{Path, PathBuf},
12 process::{Command, Output, Stdio}, 13 process::{Command, Output, Stdio},
13}; 14};
@@ -79,23 +80,11 @@ pub fn run_rustfmt(mode: Mode) -> Result<()> {
79 Ok(()) 80 Ok(())
80} 81}
81 82
82pub fn install_rustfmt() -> Result<()> { 83fn install_rustfmt() -> Result<()> {
83 run(&format!("rustup toolchain install {}", TOOLCHAIN), ".")?; 84 run(&format!("rustup toolchain install {}", TOOLCHAIN), ".")?;
84 run(&format!("rustup component add rustfmt --toolchain {}", TOOLCHAIN), ".") 85 run(&format!("rustup component add rustfmt --toolchain {}", TOOLCHAIN), ".")
85} 86}
86 87
87pub fn install_pre_commit_hook() -> Result<()> {
88 let result_path =
89 PathBuf::from(format!("./.git/hooks/pre-commit{}", std::env::consts::EXE_SUFFIX));
90 if !result_path.exists() {
91 let me = std::env::current_exe()?;
92 fs::copy(me, result_path)?;
93 } else {
94 Err(IoError::new(ErrorKind::AlreadyExists, "Git hook already created"))?;
95 }
96 Ok(())
97}
98
99pub fn run_clippy() -> Result<()> { 88pub fn run_clippy() -> Result<()> {
100 match Command::new("rustup") 89 match Command::new("rustup")
101 .args(&["run", TOOLCHAIN, "--", "cargo", "clippy", "--version"]) 90 .args(&["run", TOOLCHAIN, "--", "cargo", "clippy", "--version"])
@@ -144,28 +133,6 @@ pub fn run_fuzzer() -> Result<()> {
144 run("rustup run nightly -- cargo fuzz run parser", "./crates/ra_syntax") 133 run("rustup run nightly -- cargo fuzz run parser", "./crates/ra_syntax")
145} 134}
146 135
147pub fn reformat_staged_files() -> Result<()> {
148 run_rustfmt(Mode::Overwrite)?;
149 let root = project_root();
150 let output = Command::new("git")
151 .arg("diff")
152 .arg("--diff-filter=MAR")
153 .arg("--name-only")
154 .arg("--cached")
155 .current_dir(&root)
156 .output()?;
157 if !output.status.success() {
158 anyhow::bail!(
159 "`git diff --diff-filter=MAR --name-only --cached` exited with {}",
160 output.status
161 );
162 }
163 for line in String::from_utf8(output.stdout)?.lines() {
164 run(&format!("git update-index --add {}", root.join(line).to_string_lossy()), ".")?;
165 }
166 Ok(())
167}
168
169fn do_run<F>(cmdline: &str, dir: &str, mut f: F) -> Result<Output> 136fn do_run<F>(cmdline: &str, dir: &str, mut f: F) -> Result<Output>
170where 137where
171 F: FnMut(&mut Command), 138 F: FnMut(&mut Command),
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index b42946a4c..9309b2fbd 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -7,244 +7,105 @@
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_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 subcommand = std::env::args().nth(1).unwrap_or_default();
44 None => { 26 let mut args = Arguments::from_vec(std::env::args_os().skip(2).collect());
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" => {
69 args.finish()?;
73 codegen::generate_syntax(Mode::Overwrite)?; 70 codegen::generate_syntax(Mode::Overwrite)?;
74 codegen::generate_parser_tests(Mode::Overwrite)?; 71 codegen::generate_parser_tests(Mode::Overwrite)?;
75 codegen::generate_assists_docs(Mode::Overwrite)?; 72 codegen::generate_assists_docs(Mode::Overwrite)?;
73 Ok(())
76 } 74 }
77 "format" => run_rustfmt(Mode::Overwrite)?, 75 "format" => {
78 "install-pre-commit-hook" => install_pre_commit_hook()?, 76 args.finish()?;
79 "lint" => run_clippy()?, 77 run_rustfmt(Mode::Overwrite)
80 "fuzz-tests" => run_fuzzer()?,
81 _ => eprintln!("{}", help::GLOBAL_HELP),
82 }
83 Ok(())
84}
85
86fn handle_extra_flags(e: pico_args::Error) -> Result<()> {
87 if let pico_args::Error::UnusedArgsLeft(flags) = e {
88 let mut invalid_flags = String::new();
89 for flag in flags {
90 write!(&mut invalid_flags, "{}, ", flag)?;
91 } 78 }
92 let (invalid_flags, _) = invalid_flags.split_at(invalid_flags.len() - 2); 79 "install-pre-commit-hook" => {
93 anyhow::bail!("Invalid flags: {}", invalid_flags) 80 args.finish()?;
94 } else { 81 pre_commit::install_hook()
95 anyhow::bail!(e.to_string())
96 }
97}
98
99fn install(opts: InstallOpt) -> Result<()> {
100 if cfg!(target_os = "macos") {
101 fix_path_for_mac().context("Fix path for mac")?
102 }
103 if let Some(server) = opts.server {
104 install_server(server).context("install server")?;
105 }
106 if let Some(client) = opts.client {
107 install_client(client).context("install client")?;
108 }
109 Ok(())
110}
111
112fn fix_path_for_mac() -> Result<()> {
113 let mut vscode_path: Vec<PathBuf> = {
114 const COMMON_APP_PATH: &str =
115 r"/Applications/Visual Studio Code.app/Contents/Resources/app/bin";
116 const ROOT_DIR: &str = "";
117 let home_dir = match env::var("HOME") {
118 Ok(home) => home,
119 Err(e) => anyhow::bail!("Failed getting HOME from environment with error: {}.", e),
120 };
121
122 [ROOT_DIR, &home_dir]
123 .iter()
124 .map(|dir| String::from(*dir) + COMMON_APP_PATH)
125 .map(PathBuf::from)
126 .filter(|path| path.exists())
127 .collect()
128 };
129
130 if !vscode_path.is_empty() {
131 let vars = match env::var_os("PATH") {
132 Some(path) => path,
133 None => anyhow::bail!("Could not get PATH variable from env."),
134 };
135
136 let mut paths = env::split_paths(&vars).collect::<Vec<_>>();
137 paths.append(&mut vscode_path);
138 let new_paths = env::join_paths(paths).context("build env PATH")?;
139 env::set_var("PATH", &new_paths);
140 }
141
142 Ok(())
143}
144
145fn install_client(ClientOpt::VsCode: ClientOpt) -> Result<()> {
146 let npm_version = Cmd {
147 unix: r"npm --version",
148 windows: r"cmd.exe /c npm --version",
149 work_dir: "./editors/code",
150 }
151 .run();
152
153 if npm_version.is_err() {
154 eprintln!("\nERROR: `npm --version` failed, `npm` is required to build the VS Code plugin")
155 }
156
157 Cmd { unix: r"npm install", windows: r"cmd.exe /c npm install", work_dir: "./editors/code" }
158 .run()?;
159 Cmd {
160 unix: r"npm run package --scripts-prepend-node-path",
161 windows: r"cmd.exe /c npm run package",
162 work_dir: "./editors/code",
163 }
164 .run()?;
165
166 let code_binary = ["code", "code-insiders", "codium"].iter().find(|bin| {
167 Cmd {
168 unix: &format!("{} --version", bin),
169 windows: &format!("cmd.exe /c {}.cmd --version", bin),
170 work_dir: "./editors/code",
171 } 82 }
172 .run() 83 "lint" => {
173 .is_ok() 84 args.finish()?;
174 }); 85 run_clippy()
175 86 }
176 let code_binary = match code_binary { 87 "fuzz-tests" => {
177 Some(it) => it, 88 args.finish()?;
178 None => anyhow::bail!("Can't execute `code --version`. Perhaps it is not in $PATH?"), 89 run_fuzzer()
179 }; 90 }
180 91 _ => {
181 Cmd { 92 eprintln!(
182 unix: &format!(r"{} --install-extension ./ra-lsp-0.0.1.vsix --force", code_binary), 93 "\
183 windows: &format!( 94cargo xtask
184 r"cmd.exe /c {}.cmd --install-extension ./ra-lsp-0.0.1.vsix --force", 95Run custom build command.
185 code_binary 96
186 ), 97USAGE:
187 work_dir: "./editors/code", 98 cargo xtask <SUBCOMMAND>
188 } 99
189 .run()?; 100SUBCOMMANDS:
190 101 format
191 let output = Cmd { 102 install-pre-commit-hook
192 unix: &format!(r"{} --list-extensions", code_binary), 103 fuzz-tests
193 windows: &format!(r"cmd.exe /c {}.cmd --list-extensions", code_binary), 104 codegen
194 work_dir: ".", 105 install
195 } 106 lint"
196 .run_with_output()?; 107 );
197 108 Ok(())
198 if !str::from_utf8(&output.stdout)?.contains("ra-lsp") {
199 anyhow::bail!(
200 "Could not install the Visual Studio Code extension. \
201 Please make sure you have at least NodeJS 10.x together with the latest version of VS Code installed and try again."
202 );
203 }
204
205 Ok(())
206}
207
208fn install_server(opts: ServerOpt) -> Result<()> {
209 let mut old_rust = false;
210 if let Ok(output) = run_with_output("cargo --version", ".") {
211 if let Ok(stdout) = String::from_utf8(output.stdout) {
212 println!("{}", stdout);
213 if !check_version(&stdout, REQUIRED_RUST_VERSION) {
214 old_rust = true;
215 }
216 } 109 }
217 }
218
219 if old_rust {
220 eprintln!(
221 "\nWARNING: at least rust 1.{}.0 is required to compile rust-analyzer\n",
222 REQUIRED_RUST_VERSION,
223 )
224 }
225
226 let res = if opts.jemalloc {
227 run("cargo install --path crates/ra_lsp_server --locked --force --features jemalloc", ".")
228 } else {
229 run("cargo install --path crates/ra_lsp_server --locked --force", ".")
230 };
231
232 if res.is_err() && old_rust {
233 eprintln!(
234 "\nWARNING: at least rust 1.{}.0 is required to compile rust-analyzer\n",
235 REQUIRED_RUST_VERSION,
236 )
237 }
238
239 res
240}
241
242fn check_version(version_output: &str, min_minor_version: u32) -> bool {
243 // Parse second the number out of
244 // cargo 1.39.0-beta (1c6ec66d5 2019-09-30)
245 let minor: Option<u32> = version_output.split('.').nth(1).and_then(|it| it.parse().ok());
246 match minor {
247 None => true,
248 Some(minor) => minor >= min_minor_version,
249 } 110 }
250} 111}
diff --git a/xtask/src/pre_commit.rs b/xtask/src/pre_commit.rs
new file mode 100644
index 000000000..7984ba963
--- /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::{project_root, run, run_rustfmt, run_with_output, 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}