]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs
Rollup merge of #101171 - thomcc:fix-winfs-ub, r=ChrisDenton
[rust.git] / src / tools / rust-analyzer / crates / rust-analyzer / src / bin / main.rs
1 //! Driver for rust-analyzer.
2 //!
3 //! Based on cli flags, either spawns an LSP server, or runs a batch analysis
4
5 #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
6
7 mod logger;
8 mod rustc_wrapper;
9
10 use std::{env, fs, path::Path, process};
11
12 use lsp_server::Connection;
13 use project_model::ProjectManifest;
14 use rust_analyzer::{cli::flags, config::Config, from_json, lsp_ext::supports_utf8, Result};
15 use vfs::AbsPathBuf;
16
17 #[cfg(all(feature = "mimalloc"))]
18 #[global_allocator]
19 static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
20
21 #[cfg(all(feature = "jemalloc", not(target_env = "msvc")))]
22 #[global_allocator]
23 static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
24
25 fn main() {
26     if std::env::var("RA_RUSTC_WRAPPER").is_ok() {
27         let mut args = std::env::args_os();
28         let _me = args.next().unwrap();
29         let rustc = args.next().unwrap();
30         let code = match rustc_wrapper::run_rustc_skipping_cargo_checking(rustc, args.collect()) {
31             Ok(rustc_wrapper::ExitCode(code)) => code.unwrap_or(102),
32             Err(err) => {
33                 eprintln!("{}", err);
34                 101
35             }
36         };
37         process::exit(code);
38     }
39
40     if let Err(err) = try_main() {
41         tracing::error!("Unexpected error: {}", err);
42         eprintln!("{}", err);
43         process::exit(101);
44     }
45 }
46
47 fn try_main() -> Result<()> {
48     let flags = flags::RustAnalyzer::from_env()?;
49
50     #[cfg(debug_assertions)]
51     if flags.wait_dbg || env::var("RA_WAIT_DBG").is_ok() {
52         #[allow(unused_mut)]
53         let mut d = 4;
54         while d == 4 {
55             d = 4;
56         }
57     }
58
59     let mut log_file = flags.log_file.as_deref();
60
61     let env_log_file = env::var("RA_LOG_FILE").ok();
62     if let Some(env_log_file) = env_log_file.as_deref() {
63         log_file = Some(Path::new(env_log_file));
64     }
65
66     setup_logging(log_file)?;
67     let verbosity = flags.verbosity();
68
69     match flags.subcommand {
70         flags::RustAnalyzerCmd::LspServer(cmd) => {
71             if cmd.print_config_schema {
72                 println!("{:#}", Config::json_schema());
73                 return Ok(());
74             }
75             if cmd.version {
76                 println!("rust-analyzer {}", rust_analyzer::version());
77                 return Ok(());
78             }
79             if cmd.help {
80                 println!("{}", flags::RustAnalyzer::HELP);
81                 return Ok(());
82             }
83             with_extra_thread("LspServer", run_server)?;
84         }
85         flags::RustAnalyzerCmd::ProcMacro(flags::ProcMacro) => {
86             with_extra_thread("MacroExpander", || proc_macro_srv::cli::run().map_err(Into::into))?;
87         }
88         flags::RustAnalyzerCmd::Parse(cmd) => cmd.run()?,
89         flags::RustAnalyzerCmd::Symbols(cmd) => cmd.run()?,
90         flags::RustAnalyzerCmd::Highlight(cmd) => cmd.run()?,
91         flags::RustAnalyzerCmd::AnalysisStats(cmd) => cmd.run(verbosity)?,
92         flags::RustAnalyzerCmd::Diagnostics(cmd) => cmd.run()?,
93         flags::RustAnalyzerCmd::Ssr(cmd) => cmd.run()?,
94         flags::RustAnalyzerCmd::Search(cmd) => cmd.run()?,
95         flags::RustAnalyzerCmd::Lsif(cmd) => cmd.run()?,
96         flags::RustAnalyzerCmd::Scip(cmd) => cmd.run()?,
97     }
98     Ok(())
99 }
100
101 fn setup_logging(log_file: Option<&Path>) -> Result<()> {
102     if cfg!(windows) {
103         // This is required so that windows finds our pdb that is placed right beside the exe.
104         // By default it doesn't look at the folder the exe resides in, only in the current working
105         // directory which we set to the project workspace.
106         // https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/general-environment-variables
107         // https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-syminitialize
108         if let Ok(path) = env::current_exe() {
109             if let Some(path) = path.parent() {
110                 env::set_var("_NT_SYMBOL_PATH", path);
111             }
112         }
113     }
114     if env::var("RUST_BACKTRACE").is_err() {
115         env::set_var("RUST_BACKTRACE", "short");
116     }
117
118     let log_file = match log_file {
119         Some(path) => {
120             if let Some(parent) = path.parent() {
121                 let _ = fs::create_dir_all(parent);
122             }
123             Some(fs::File::create(path)?)
124         }
125         None => None,
126     };
127     let filter = env::var("RA_LOG").ok();
128     // deliberately enable all `error` logs if the user has not set RA_LOG, as there is usually useful
129     // information in there for debugging
130     logger::Logger::new(log_file, filter.as_deref().or(Some("error"))).install()?;
131
132     profile::init();
133
134     Ok(())
135 }
136
137 const STACK_SIZE: usize = 1024 * 1024 * 8;
138
139 /// Parts of rust-analyzer can use a lot of stack space, and some operating systems only give us
140 /// 1 MB by default (eg. Windows), so this spawns a new thread with hopefully sufficient stack
141 /// space.
142 fn with_extra_thread(
143     thread_name: impl Into<String>,
144     f: impl FnOnce() -> Result<()> + Send + 'static,
145 ) -> Result<()> {
146     let handle =
147         std::thread::Builder::new().name(thread_name.into()).stack_size(STACK_SIZE).spawn(f)?;
148     match handle.join() {
149         Ok(res) => res,
150         Err(panic) => std::panic::resume_unwind(panic),
151     }
152 }
153
154 fn run_server() -> Result<()> {
155     tracing::info!("server version {} will start", rust_analyzer::version());
156
157     let (connection, io_threads) = Connection::stdio();
158
159     let (initialize_id, initialize_params) = connection.initialize_start()?;
160     tracing::info!("InitializeParams: {}", initialize_params);
161     let initialize_params =
162         from_json::<lsp_types::InitializeParams>("InitializeParams", &initialize_params)?;
163
164     let root_path = match initialize_params
165         .root_uri
166         .and_then(|it| it.to_file_path().ok())
167         .and_then(|it| AbsPathBuf::try_from(it).ok())
168     {
169         Some(it) => it,
170         None => {
171             let cwd = env::current_dir()?;
172             AbsPathBuf::assert(cwd)
173         }
174     };
175
176     let mut config = Config::new(root_path, initialize_params.capabilities);
177     if let Some(json) = initialize_params.initialization_options {
178         if let Err(e) = config.update(json) {
179             use lsp_types::{
180                 notification::{Notification, ShowMessage},
181                 MessageType, ShowMessageParams,
182             };
183             let not = lsp_server::Notification::new(
184                 ShowMessage::METHOD.to_string(),
185                 ShowMessageParams { typ: MessageType::WARNING, message: e.to_string() },
186             );
187             connection.sender.send(lsp_server::Message::Notification(not)).unwrap();
188         }
189     }
190
191     let server_capabilities = rust_analyzer::server_capabilities(&config);
192
193     let initialize_result = lsp_types::InitializeResult {
194         capabilities: server_capabilities,
195         server_info: Some(lsp_types::ServerInfo {
196             name: String::from("rust-analyzer"),
197             version: Some(rust_analyzer::version().to_string()),
198         }),
199         offset_encoding: if supports_utf8(config.caps()) {
200             Some("utf-8".to_string())
201         } else {
202             None
203         },
204     };
205
206     let initialize_result = serde_json::to_value(initialize_result).unwrap();
207
208     connection.initialize_finish(initialize_id, initialize_result)?;
209
210     if let Some(client_info) = initialize_params.client_info {
211         tracing::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default());
212     }
213
214     if config.linked_projects().is_empty() && config.detached_files().is_empty() {
215         let workspace_roots = initialize_params
216             .workspace_folders
217             .map(|workspaces| {
218                 workspaces
219                     .into_iter()
220                     .filter_map(|it| it.uri.to_file_path().ok())
221                     .filter_map(|it| AbsPathBuf::try_from(it).ok())
222                     .collect::<Vec<_>>()
223             })
224             .filter(|workspaces| !workspaces.is_empty())
225             .unwrap_or_else(|| vec![config.root_path().clone()]);
226
227         let discovered = ProjectManifest::discover_all(&workspace_roots);
228         tracing::info!("discovered projects: {:?}", discovered);
229         if discovered.is_empty() {
230             tracing::error!("failed to find any projects in {:?}", workspace_roots);
231         }
232         config.discovered_projects = Some(discovered);
233     }
234
235     rust_analyzer::main_loop(config, connection)?;
236
237     io_threads.join()?;
238     tracing::info!("server did shut down");
239     Ok(())
240 }