]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/cli/load_cargo.rs
Merge #9218
[rust.git] / crates / rust-analyzer / src / cli / load_cargo.rs
1 //! Loads a Cargo project into a static instance of analysis, without support
2 //! for incorporating changes.
3 use std::{path::Path, sync::Arc};
4
5 use anyhow::Result;
6 use crossbeam_channel::{unbounded, Receiver};
7 use hir::db::DefDatabase;
8 use ide::{AnalysisHost, Change};
9 use ide_db::base_db::CrateGraph;
10 use project_model::{
11     BuildDataCollector, CargoConfig, ProcMacroClient, ProjectManifest, ProjectWorkspace,
12 };
13 use vfs::{loader::Handle, AbsPath, AbsPathBuf};
14
15 use crate::reload::{ProjectFolders, SourceRootConfig};
16
17 pub(crate) struct LoadCargoConfig {
18     pub(crate) load_out_dirs_from_check: bool,
19     pub(crate) wrap_rustc: bool,
20     pub(crate) with_proc_macro: bool,
21     pub(crate) prefill_caches: bool,
22 }
23
24 pub(crate) fn load_workspace_at(
25     root: &Path,
26     cargo_config: &CargoConfig,
27     load_config: &LoadCargoConfig,
28     progress: &dyn Fn(String),
29 ) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroClient>)> {
30     let root = AbsPathBuf::assert(std::env::current_dir()?.join(root));
31     let root = ProjectManifest::discover_single(&root)?;
32     let workspace = ProjectWorkspace::load(root, cargo_config, progress)?;
33
34     load_workspace(workspace, load_config, progress)
35 }
36
37 fn load_workspace(
38     ws: ProjectWorkspace,
39     config: &LoadCargoConfig,
40     progress: &dyn Fn(String),
41 ) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroClient>)> {
42     let (sender, receiver) = unbounded();
43     let mut vfs = vfs::Vfs::default();
44     let mut loader = {
45         let loader =
46             vfs_notify::NotifyHandle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
47         Box::new(loader)
48     };
49
50     let proc_macro_client = if config.with_proc_macro {
51         let path = std::env::current_exe()?;
52         Some(ProcMacroClient::extern_process(path, &["proc-macro"]).unwrap())
53     } else {
54         None
55     };
56
57     let build_data = if config.load_out_dirs_from_check {
58         let mut collector = BuildDataCollector::new(config.wrap_rustc);
59         ws.collect_build_data_configs(&mut collector);
60         Some(collector.collect(progress)?)
61     } else {
62         None
63     };
64
65     let crate_graph = ws.to_crate_graph(
66         build_data.as_ref(),
67         proc_macro_client.as_ref(),
68         &mut |path: &AbsPath| {
69             let contents = loader.load_sync(path);
70             let path = vfs::VfsPath::from(path.to_path_buf());
71             vfs.set_file_contents(path.clone(), contents);
72             vfs.file_id(&path)
73         },
74     );
75
76     let project_folders = ProjectFolders::new(&[ws], &[], build_data.as_ref());
77     loader.set_config(vfs::loader::Config {
78         load: project_folders.load,
79         watch: vec![],
80         version: 0,
81     });
82
83     log::debug!("crate graph: {:?}", crate_graph);
84     let host =
85         load_crate_graph(crate_graph, project_folders.source_root_config, &mut vfs, &receiver);
86
87     if config.prefill_caches {
88         host.analysis().prime_caches(|_| {})?;
89     }
90     Ok((host, vfs, proc_macro_client))
91 }
92
93 fn load_crate_graph(
94     crate_graph: CrateGraph,
95     source_root_config: SourceRootConfig,
96     vfs: &mut vfs::Vfs,
97     receiver: &Receiver<vfs::loader::Message>,
98 ) -> AnalysisHost {
99     let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
100     let mut host = AnalysisHost::new(lru_cap);
101     let mut analysis_change = Change::new();
102
103     host.raw_database_mut().set_enable_proc_attr_macros(true);
104
105     // wait until Vfs has loaded all roots
106     for task in receiver {
107         match task {
108             vfs::loader::Message::Progress { n_done, n_total, config_version: _ } => {
109                 if n_done == n_total {
110                     break;
111                 }
112             }
113             vfs::loader::Message::Loaded { files } => {
114                 for (path, contents) in files {
115                     vfs.set_file_contents(path.into(), contents);
116                 }
117             }
118         }
119     }
120     let changes = vfs.take_changes();
121     for file in changes {
122         if file.exists() {
123             let contents = vfs.file_contents(file.file_id).to_vec();
124             if let Ok(text) = String::from_utf8(contents) {
125                 analysis_change.change_file(file.file_id, Some(Arc::new(text)))
126             }
127         }
128     }
129     let source_roots = source_root_config.partition(&vfs);
130     analysis_change.set_roots(source_roots);
131
132     analysis_change.set_crate_graph(crate_graph);
133
134     host.apply_change(analysis_change);
135     host
136 }
137
138 #[cfg(test)]
139 mod tests {
140     use super::*;
141
142     use hir::Crate;
143
144     #[test]
145     fn test_loading_rust_analyzer() -> Result<()> {
146         let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
147         let cargo_config = Default::default();
148         let load_cargo_config = LoadCargoConfig {
149             load_out_dirs_from_check: false,
150             wrap_rustc: false,
151             with_proc_macro: false,
152             prefill_caches: false,
153         };
154         let (host, _vfs, _proc_macro) =
155             load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {})?;
156
157         let n_crates = Crate::all(host.raw_database()).len();
158         // RA has quite a few crates, but the exact count doesn't matter
159         assert!(n_crates > 20);
160
161         Ok(())
162     }
163 }