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