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