• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Loads a Cargo project into a static instance of analysis, without support
2 //! for incorporating changes.
3 use std::path::Path;
4 
5 use anyhow::{anyhow, Result};
6 use crossbeam_channel::{unbounded, Receiver};
7 use ide::{AnalysisHost, Change};
8 use ide_db::{
9     base_db::{CrateGraph, ProcMacros},
10     FxHashMap,
11 };
12 use proc_macro_api::ProcMacroServer;
13 use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
14 use triomphe::Arc;
15 use vfs::{loader::Handle, AbsPath, AbsPathBuf};
16 
17 use crate::reload::{load_proc_macro, ProjectFolders, SourceRootConfig};
18 
19 // Note: Since this type is used by external tools that use rust-analyzer as a library
20 // what otherwise would be `pub(crate)` has to be `pub` here instead.
21 pub struct LoadCargoConfig {
22     pub load_out_dirs_from_check: bool,
23     pub with_proc_macro_server: ProcMacroServerChoice,
24     pub prefill_caches: bool,
25 }
26 
27 #[derive(Debug, Clone, PartialEq, Eq)]
28 pub enum ProcMacroServerChoice {
29     Sysroot,
30     Explicit(AbsPathBuf),
31     None,
32 }
33 
34 // Note: Since this function is used by external tools that use rust-analyzer as a library
35 // what otherwise would be `pub(crate)` has to be `pub` here instead.
load_workspace_at( root: &Path, cargo_config: &CargoConfig, load_config: &LoadCargoConfig, progress: &dyn Fn(String), ) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)>36 pub fn load_workspace_at(
37     root: &Path,
38     cargo_config: &CargoConfig,
39     load_config: &LoadCargoConfig,
40     progress: &dyn Fn(String),
41 ) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
42     let root = AbsPathBuf::assert(std::env::current_dir()?.join(root));
43     let root = ProjectManifest::discover_single(&root)?;
44     let mut workspace = ProjectWorkspace::load(root, cargo_config, progress)?;
45 
46     if load_config.load_out_dirs_from_check {
47         let build_scripts = workspace.run_build_scripts(cargo_config, progress)?;
48         workspace.set_build_scripts(build_scripts)
49     }
50 
51     load_workspace(workspace, &cargo_config.extra_env, load_config)
52 }
53 
54 // Note: Since this function is used by external tools that use rust-analyzer as a library
55 // what otherwise would be `pub(crate)` has to be `pub` here instead.
56 //
57 // The reason both, `load_workspace_at` and `load_workspace` are `pub` is that some of
58 // these tools need access to `ProjectWorkspace`, too, which `load_workspace_at` hides.
load_workspace( ws: ProjectWorkspace, extra_env: &FxHashMap<String, String>, load_config: &LoadCargoConfig, ) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)>59 pub fn load_workspace(
60     ws: ProjectWorkspace,
61     extra_env: &FxHashMap<String, String>,
62     load_config: &LoadCargoConfig,
63 ) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
64     let (sender, receiver) = unbounded();
65     let mut vfs = vfs::Vfs::default();
66     let mut loader = {
67         let loader =
68             vfs_notify::NotifyHandle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
69         Box::new(loader)
70     };
71 
72     let proc_macro_server = match &load_config.with_proc_macro_server {
73         ProcMacroServerChoice::Sysroot => ws
74             .find_sysroot_proc_macro_srv()
75             .and_then(|it| ProcMacroServer::spawn(it).map_err(Into::into)),
76         ProcMacroServerChoice::Explicit(path) => {
77             ProcMacroServer::spawn(path.clone()).map_err(Into::into)
78         }
79         ProcMacroServerChoice::None => Err(anyhow!("proc macro server disabled")),
80     };
81 
82     let (crate_graph, proc_macros) = ws.to_crate_graph(
83         &mut |path: &AbsPath| {
84             let contents = loader.load_sync(path);
85             let path = vfs::VfsPath::from(path.to_path_buf());
86             vfs.set_file_contents(path.clone(), contents);
87             vfs.file_id(&path)
88         },
89         extra_env,
90     );
91     let proc_macros = {
92         let proc_macro_server = match &proc_macro_server {
93             Ok(it) => Ok(it),
94             Err(e) => Err(e.to_string()),
95         };
96         proc_macros
97             .into_iter()
98             .map(|(crate_id, path)| {
99                 (
100                     crate_id,
101                     path.map_or_else(
102                         |_| Err("proc macro crate is missing dylib".to_owned()),
103                         |(_, path)| {
104                             proc_macro_server.as_ref().map_err(Clone::clone).and_then(
105                                 |proc_macro_server| load_proc_macro(proc_macro_server, &path, &[]),
106                             )
107                         },
108                     ),
109                 )
110             })
111             .collect()
112     };
113 
114     let project_folders = ProjectFolders::new(&[ws], &[]);
115     loader.set_config(vfs::loader::Config {
116         load: project_folders.load,
117         watch: vec![],
118         version: 0,
119     });
120 
121     tracing::debug!("crate graph: {:?}", crate_graph);
122     let host = load_crate_graph(
123         crate_graph,
124         proc_macros,
125         project_folders.source_root_config,
126         &mut vfs,
127         &receiver,
128     );
129 
130     if load_config.prefill_caches {
131         host.analysis().parallel_prime_caches(1, |_| {})?;
132     }
133     Ok((host, vfs, proc_macro_server.ok()))
134 }
135 
load_crate_graph( crate_graph: CrateGraph, proc_macros: ProcMacros, source_root_config: SourceRootConfig, vfs: &mut vfs::Vfs, receiver: &Receiver<vfs::loader::Message>, ) -> AnalysisHost136 fn load_crate_graph(
137     crate_graph: CrateGraph,
138     proc_macros: ProcMacros,
139     source_root_config: SourceRootConfig,
140     vfs: &mut vfs::Vfs,
141     receiver: &Receiver<vfs::loader::Message>,
142 ) -> AnalysisHost {
143     let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
144     let mut host = AnalysisHost::new(lru_cap);
145     let mut analysis_change = Change::new();
146 
147     host.raw_database_mut().enable_proc_attr_macros();
148 
149     // wait until Vfs has loaded all roots
150     for task in receiver {
151         match task {
152             vfs::loader::Message::Progress { n_done, n_total, config_version: _ } => {
153                 if n_done == n_total {
154                     break;
155                 }
156             }
157             vfs::loader::Message::Loaded { files } => {
158                 for (path, contents) in files {
159                     vfs.set_file_contents(path.into(), contents);
160                 }
161             }
162         }
163     }
164     let changes = vfs.take_changes();
165     for file in changes {
166         if file.exists() {
167             let contents = vfs.file_contents(file.file_id);
168             if let Ok(text) = std::str::from_utf8(contents) {
169                 analysis_change.change_file(file.file_id, Some(Arc::from(text)))
170             }
171         }
172     }
173     let source_roots = source_root_config.partition(vfs);
174     analysis_change.set_roots(source_roots);
175 
176     analysis_change.set_crate_graph(crate_graph);
177     analysis_change.set_proc_macros(proc_macros);
178 
179     host.apply_change(analysis_change);
180     host
181 }
182 
183 #[cfg(test)]
184 mod tests {
185     use super::*;
186 
187     use hir::Crate;
188 
189     #[test]
test_loading_rust_analyzer()190     fn test_loading_rust_analyzer() {
191         let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
192         let cargo_config = CargoConfig::default();
193         let load_cargo_config = LoadCargoConfig {
194             load_out_dirs_from_check: false,
195             with_proc_macro_server: ProcMacroServerChoice::None,
196             prefill_caches: false,
197         };
198         let (host, _vfs, _proc_macro) =
199             load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap();
200 
201         let n_crates = Crate::all(host.raw_database()).len();
202         // RA has quite a few crates, but the exact count doesn't matter
203         assert!(n_crates > 20);
204     }
205 }
206