]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs
Rollup merge of #100473 - compiler-errors:normalize-the-fn-def-sig-plz, r=lcnr
[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     }
97     Ok(())
98 }
99
100 fn setup_logging(log_file: Option<&Path>) -> Result<()> {
101     if cfg!(windows) {
102         // This is required so that windows finds our pdb that is placed right beside the exe.
103         // By default it doesn't look at the folder the exe resides in, only in the current working
104         // directory which we set to the project workspace.
105         // https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/general-environment-variables
106         // https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-syminitialize
107         if let Ok(path) = env::current_exe() {
108             if let Some(path) = path.parent() {
109                 env::set_var("_NT_SYMBOL_PATH", path);
110             }
111         }
112     }
113     if env::var("RUST_BACKTRACE").is_err() {
114         env::set_var("RUST_BACKTRACE", "short");
115     }
116
117     let log_file = match log_file {
118         Some(path) => {
119             if let Some(parent) = path.parent() {
120                 let _ = fs::create_dir_all(parent);
121             }
122             Some(fs::File::create(path)?)
123         }
124         None => None,
125     };
126     let filter = env::var("RA_LOG").ok();
127     // deliberately enable all `error` logs if the user has not set RA_LOG, as there is usually useful
128     // information in there for debugging
129     logger::Logger::new(log_file, filter.as_deref().or(Some("error"))).install()?;
130
131     profile::init();
132
133     Ok(())
134 }
135
136 const STACK_SIZE: usize = 1024 * 1024 * 8;
137
138 /// Parts of rust-analyzer can use a lot of stack space, and some operating systems only give us
139 /// 1 MB by default (eg. Windows), so this spawns a new thread with hopefully sufficient stack
140 /// space.
141 fn with_extra_thread(
142     thread_name: impl Into<String>,
143     f: impl FnOnce() -> Result<()> + Send + 'static,
144 ) -> Result<()> {
145     let handle =
146         std::thread::Builder::new().name(thread_name.into()).stack_size(STACK_SIZE).spawn(f)?;
147     match handle.join() {
148         Ok(res) => res,
149         Err(panic) => std::panic::resume_unwind(panic),
150     }
151 }
152
153 fn run_server() -> Result<()> {
154     tracing::info!("server version {} will start", rust_analyzer::version());
155
156     let (connection, io_threads) = Connection::stdio();
157
158     let (initialize_id, initialize_params) = connection.initialize_start()?;
159     tracing::info!("InitializeParams: {}", initialize_params);
160     let initialize_params =
161         from_json::<lsp_types::InitializeParams>("InitializeParams", &initialize_params)?;
162
163     let root_path = match initialize_params
164         .root_uri
165         .and_then(|it| it.to_file_path().ok())
166         .and_then(|it| AbsPathBuf::try_from(it).ok())
167     {
168         Some(it) => it,
169         None => {
170             let cwd = env::current_dir()?;
171             AbsPathBuf::assert(cwd)
172         }
173     };
174
175     let mut config = Config::new(root_path, initialize_params.capabilities);
176     if let Some(json) = initialize_params.initialization_options {
177         if let Err(e) = config.update(json) {
178             use lsp_types::{
179                 notification::{Notification, ShowMessage},
180                 MessageType, ShowMessageParams,
181             };
182             let not = lsp_server::Notification::new(
183                 ShowMessage::METHOD.to_string(),
184                 ShowMessageParams { typ: MessageType::WARNING, message: e.to_string() },
185             );
186             connection.sender.send(lsp_server::Message::Notification(not)).unwrap();
187         }
188     }
189
190     let server_capabilities = rust_analyzer::server_capabilities(&config);
191
192     let initialize_result = lsp_types::InitializeResult {
193         capabilities: server_capabilities,
194         server_info: Some(lsp_types::ServerInfo {
195             name: String::from("rust-analyzer"),
196             version: Some(rust_analyzer::version().to_string()),
197         }),
198         offset_encoding: if supports_utf8(config.caps()) {
199             Some("utf-8".to_string())
200         } else {
201             None
202         },
203     };
204
205     let initialize_result = serde_json::to_value(initialize_result).unwrap();
206
207     connection.initialize_finish(initialize_id, initialize_result)?;
208
209     if let Some(client_info) = initialize_params.client_info {
210         tracing::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default());
211     }
212
213     if config.linked_projects().is_empty() && config.detached_files().is_empty() {
214         let workspace_roots = initialize_params
215             .workspace_folders
216             .map(|workspaces| {
217                 workspaces
218                     .into_iter()
219                     .filter_map(|it| it.uri.to_file_path().ok())
220                     .filter_map(|it| AbsPathBuf::try_from(it).ok())
221                     .collect::<Vec<_>>()
222             })
223             .filter(|workspaces| !workspaces.is_empty())
224             .unwrap_or_else(|| vec![config.root_path().clone()]);
225
226         let discovered = ProjectManifest::discover_all(&workspace_roots);
227         tracing::info!("discovered projects: {:?}", discovered);
228         if discovered.is_empty() {
229             tracing::error!("failed to find any projects in {:?}", workspace_roots);
230         }
231         config.discovered_projects = Some(discovered);
232     }
233
234     rust_analyzer::main_loop(config, connection)?;
235
236     io_threads.join()?;
237     tracing::info!("server did shut down");
238     Ok(())
239 }