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