aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_project_model
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-02-09 10:21:13 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-02-09 10:21:13 +0000
commitd0a32627a741826502692f2c3de71512b7ec23cf (patch)
treeee3cef56bd9738f5c3b98caae9b8ce1b8dbf401f /crates/ra_project_model
parent34398a8756b56c323d3b4b2ef32fbca32d88a105 (diff)
parente91a46eb0c4a355af25656d77dead55c2e29258e (diff)
Merge #767
767: Extract project model to separate crate r=matklad a=flodiebold I'm looking into creating a separate crate that would allow getting a HIR db for a project for 'batch' analyses, and this seems to be an obvious first step. We'd probably want to change the error handling to not rely on failure, though, right? Co-authored-by: Florian Diebold <[email protected]>
Diffstat (limited to 'crates/ra_project_model')
-rw-r--r--crates/ra_project_model/Cargo.toml21
-rw-r--r--crates/ra_project_model/src/cargo_workspace.rs172
-rw-r--r--crates/ra_project_model/src/lib.rs130
-rw-r--r--crates/ra_project_model/src/sysroot.rs136
4 files changed, 459 insertions, 0 deletions
diff --git a/crates/ra_project_model/Cargo.toml b/crates/ra_project_model/Cargo.toml
new file mode 100644
index 000000000..90f8b8398
--- /dev/null
+++ b/crates/ra_project_model/Cargo.toml
@@ -0,0 +1,21 @@
1[package]
2edition = "2018"
3name = "ra_project_model"
4version = "0.1.0"
5authors = ["Aleksey Kladov <[email protected]>"]
6
7[dependencies]
8log = "0.4.5"
9rustc-hash = "1.0"
10
11failure = "0.1.4"
12
13walkdir = "2.2.7"
14
15cargo_metadata = "0.7.0"
16
17ra_arena = { path = "../ra_arena" }
18ra_db = { path = "../ra_db" }
19
20[dev-dependencies]
21test_utils = { path = "../test_utils" }
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
new file mode 100644
index 000000000..8adf463a6
--- /dev/null
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -0,0 +1,172 @@
1use std::path::{Path, PathBuf};
2
3use cargo_metadata::{MetadataCommand, CargoOpt};
4use ra_arena::{Arena, RawId, impl_arena_id};
5use rustc_hash::FxHashMap;
6use failure::format_err;
7
8use crate::Result;
9
10/// `CargoWorkspace` represents the logical structure of, well, a Cargo
11/// workspace. It pretty closely mirrors `cargo metadata` output.
12///
13/// Note that internally, rust analyzer uses a different structure:
14/// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates,
15/// while this knows about `Pacakges` & `Targets`: purely cargo-related
16/// concepts.
17#[derive(Debug, Clone)]
18pub struct CargoWorkspace {
19 packages: Arena<Package, PackageData>,
20 targets: Arena<Target, TargetData>,
21}
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
24pub struct Package(RawId);
25impl_arena_id!(Package);
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28pub struct Target(RawId);
29impl_arena_id!(Target);
30
31#[derive(Debug, Clone)]
32struct PackageData {
33 name: String,
34 manifest: PathBuf,
35 targets: Vec<Target>,
36 is_member: bool,
37 dependencies: Vec<PackageDependency>,
38}
39
40#[derive(Debug, Clone)]
41pub struct PackageDependency {
42 pub pkg: Package,
43 pub name: String,
44}
45
46#[derive(Debug, Clone)]
47struct TargetData {
48 pkg: Package,
49 name: String,
50 root: PathBuf,
51 kind: TargetKind,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum TargetKind {
56 Bin,
57 Lib,
58 Example,
59 Test,
60 Bench,
61 Other,
62}
63
64impl TargetKind {
65 fn new(kinds: &[String]) -> TargetKind {
66 for kind in kinds {
67 return match kind.as_str() {
68 "bin" => TargetKind::Bin,
69 "test" => TargetKind::Test,
70 "bench" => TargetKind::Bench,
71 "example" => TargetKind::Example,
72 _ if kind.contains("lib") => TargetKind::Lib,
73 _ => continue,
74 };
75 }
76 TargetKind::Other
77 }
78}
79
80impl Package {
81 pub fn name(self, ws: &CargoWorkspace) -> &str {
82 ws.packages[self].name.as_str()
83 }
84 pub fn root(self, ws: &CargoWorkspace) -> &Path {
85 ws.packages[self].manifest.parent().unwrap()
86 }
87 pub fn targets<'a>(self, ws: &'a CargoWorkspace) -> impl Iterator<Item = Target> + 'a {
88 ws.packages[self].targets.iter().cloned()
89 }
90 #[allow(unused)]
91 pub fn is_member(self, ws: &CargoWorkspace) -> bool {
92 ws.packages[self].is_member
93 }
94 pub fn dependencies<'a>(
95 self,
96 ws: &'a CargoWorkspace,
97 ) -> impl Iterator<Item = &'a PackageDependency> + 'a {
98 ws.packages[self].dependencies.iter()
99 }
100}
101
102impl Target {
103 pub fn package(self, ws: &CargoWorkspace) -> Package {
104 ws.targets[self].pkg
105 }
106 pub fn name(self, ws: &CargoWorkspace) -> &str {
107 ws.targets[self].name.as_str()
108 }
109 pub fn root(self, ws: &CargoWorkspace) -> &Path {
110 ws.targets[self].root.as_path()
111 }
112 pub fn kind(self, ws: &CargoWorkspace) -> TargetKind {
113 ws.targets[self].kind
114 }
115}
116
117impl CargoWorkspace {
118 pub fn from_cargo_metadata(cargo_toml: &Path) -> Result<CargoWorkspace> {
119 let mut meta = MetadataCommand::new();
120 meta.manifest_path(cargo_toml).features(CargoOpt::AllFeatures);
121 if let Some(parent) = cargo_toml.parent() {
122 meta.current_dir(parent);
123 }
124 let meta = meta.exec().map_err(|e| format_err!("cargo metadata failed: {}", e))?;
125 let mut pkg_by_id = FxHashMap::default();
126 let mut packages = Arena::default();
127 let mut targets = Arena::default();
128
129 let ws_members = &meta.workspace_members;
130
131 for meta_pkg in meta.packages {
132 let is_member = ws_members.contains(&meta_pkg.id);
133 let pkg = packages.alloc(PackageData {
134 name: meta_pkg.name.into(),
135 manifest: meta_pkg.manifest_path.clone(),
136 targets: Vec::new(),
137 is_member,
138 dependencies: Vec::new(),
139 });
140 let pkg_data = &mut packages[pkg];
141 pkg_by_id.insert(meta_pkg.id.clone(), pkg);
142 for meta_tgt in meta_pkg.targets {
143 let tgt = targets.alloc(TargetData {
144 pkg,
145 name: meta_tgt.name.into(),
146 root: meta_tgt.src_path.clone(),
147 kind: TargetKind::new(meta_tgt.kind.as_slice()),
148 });
149 pkg_data.targets.push(tgt);
150 }
151 }
152 let resolve = meta.resolve.expect("metadata executed with deps");
153 for node in resolve.nodes {
154 let source = pkg_by_id[&node.id];
155 for dep_node in node.deps {
156 let dep =
157 PackageDependency { name: dep_node.name.into(), pkg: pkg_by_id[&dep_node.pkg] };
158 packages[source].dependencies.push(dep);
159 }
160 }
161
162 Ok(CargoWorkspace { packages, targets })
163 }
164
165 pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + 'a {
166 self.packages.iter().map(|(id, _pkg)| id)
167 }
168
169 pub fn target_by_root(&self, root: &Path) -> Option<Target> {
170 self.packages().filter_map(|pkg| pkg.targets(self).find(|it| it.root(self) == root)).next()
171 }
172}
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
new file mode 100644
index 000000000..3b1e07149
--- /dev/null
+++ b/crates/ra_project_model/src/lib.rs
@@ -0,0 +1,130 @@
1mod cargo_workspace;
2mod sysroot;
3
4use std::path::{Path, PathBuf};
5
6use failure::bail;
7use rustc_hash::FxHashMap;
8
9use ra_db::{CrateGraph, FileId};
10
11pub use crate::{
12 cargo_workspace::{CargoWorkspace, Package, Target, TargetKind},
13 sysroot::Sysroot,
14};
15
16// TODO use proper error enum
17pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
18
19#[derive(Debug, Clone)]
20pub struct ProjectWorkspace {
21 pub cargo: CargoWorkspace,
22 pub sysroot: Sysroot,
23}
24
25impl ProjectWorkspace {
26 pub fn discover(path: &Path) -> Result<ProjectWorkspace> {
27 let cargo_toml = find_cargo_toml(path)?;
28 let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml)?;
29 let sysroot = Sysroot::discover(&cargo_toml)?;
30 let res = ProjectWorkspace { cargo, sysroot };
31 Ok(res)
32 }
33
34 pub fn to_crate_graph(&self, load: &mut dyn FnMut(&Path) -> Option<FileId>) -> CrateGraph {
35 let mut crate_graph = CrateGraph::default();
36 let mut sysroot_crates = FxHashMap::default();
37 for krate in self.sysroot.crates() {
38 if let Some(file_id) = load(krate.root(&self.sysroot)) {
39 sysroot_crates.insert(krate, crate_graph.add_crate_root(file_id));
40 }
41 }
42 for from in self.sysroot.crates() {
43 for to in from.deps(&self.sysroot) {
44 let name = to.name(&self.sysroot);
45 if let (Some(&from), Some(&to)) =
46 (sysroot_crates.get(&from), sysroot_crates.get(&to))
47 {
48 if let Err(_) = crate_graph.add_dep(from, name.into(), to) {
49 log::error!("cyclic dependency between sysroot crates")
50 }
51 }
52 }
53 }
54
55 let libstd = self.sysroot.std().and_then(|it| sysroot_crates.get(&it).map(|&it| it));
56
57 let mut pkg_to_lib_crate = FxHashMap::default();
58 let mut pkg_crates = FxHashMap::default();
59 // Next, create crates for each package, target pair
60 for pkg in self.cargo.packages() {
61 let mut lib_tgt = None;
62 for tgt in pkg.targets(&self.cargo) {
63 let root = tgt.root(&self.cargo);
64 if let Some(file_id) = load(root) {
65 let crate_id = crate_graph.add_crate_root(file_id);
66 if tgt.kind(&self.cargo) == TargetKind::Lib {
67 lib_tgt = Some(crate_id);
68 pkg_to_lib_crate.insert(pkg, crate_id);
69 }
70 pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
71 }
72 }
73
74 // Set deps to the std and to the lib target of the current package
75 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
76 if let Some(to) = lib_tgt {
77 if to != from {
78 if let Err(_) = crate_graph.add_dep(from, pkg.name(&self.cargo).into(), to)
79 {
80 log::error!(
81 "cyclic dependency between targets of {}",
82 pkg.name(&self.cargo)
83 )
84 }
85 }
86 }
87 if let Some(std) = libstd {
88 if let Err(_) = crate_graph.add_dep(from, "std".into(), std) {
89 log::error!("cyclic dependency on std for {}", pkg.name(&self.cargo))
90 }
91 }
92 }
93 }
94
95 // Now add a dep ednge from all targets of upstream to the lib
96 // target of downstream.
97 for pkg in self.cargo.packages() {
98 for dep in pkg.dependencies(&self.cargo) {
99 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
100 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
101 if let Err(_) = crate_graph.add_dep(from, dep.name.clone().into(), to) {
102 log::error!(
103 "cyclic dependency {} -> {}",
104 pkg.name(&self.cargo),
105 dep.pkg.name(&self.cargo)
106 )
107 }
108 }
109 }
110 }
111 }
112
113 crate_graph
114 }
115}
116
117fn find_cargo_toml(path: &Path) -> Result<PathBuf> {
118 if path.ends_with("Cargo.toml") {
119 return Ok(path.to_path_buf());
120 }
121 let mut curr = Some(path);
122 while let Some(path) = curr {
123 let candidate = path.join("Cargo.toml");
124 if candidate.exists() {
125 return Ok(candidate);
126 }
127 curr = path.parent();
128 }
129 bail!("can't find Cargo.toml at {}", path.display())
130}
diff --git a/crates/ra_project_model/src/sysroot.rs b/crates/ra_project_model/src/sysroot.rs
new file mode 100644
index 000000000..8b87aa7bd
--- /dev/null
+++ b/crates/ra_project_model/src/sysroot.rs
@@ -0,0 +1,136 @@
1use std::{
2 path::{Path, PathBuf},
3 process::Command,
4};
5
6use ra_arena::{Arena, RawId, impl_arena_id};
7
8use crate::Result;
9
10#[derive(Debug, Clone)]
11pub struct Sysroot {
12 crates: Arena<SysrootCrate, SysrootCrateData>,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct SysrootCrate(RawId);
17impl_arena_id!(SysrootCrate);
18
19#[derive(Debug, Clone)]
20struct SysrootCrateData {
21 name: String,
22 root: PathBuf,
23 deps: Vec<SysrootCrate>,
24}
25
26impl Sysroot {
27 pub fn std(&self) -> Option<SysrootCrate> {
28 self.by_name("std")
29 }
30
31 pub fn crates<'a>(&'a self) -> impl Iterator<Item = SysrootCrate> + 'a {
32 self.crates.iter().map(|(id, _data)| id)
33 }
34
35 pub fn discover(cargo_toml: &Path) -> Result<Sysroot> {
36 let rustc_output = Command::new("rustc")
37 .current_dir(cargo_toml.parent().unwrap())
38 .args(&["--print", "sysroot"])
39 .output()?;
40 if !rustc_output.status.success() {
41 failure::bail!("failed to locate sysroot")
42 }
43 let stdout = String::from_utf8(rustc_output.stdout)?;
44 let sysroot_path = Path::new(stdout.trim());
45 let src = sysroot_path.join("lib/rustlib/src/rust/src");
46 if !src.exists() {
47 failure::bail!(
48 "can't load standard library from sysroot\n\
49 {:?}\n\
50 try running `rustup component add rust-src`",
51 src,
52 );
53 }
54
55 let mut sysroot = Sysroot { crates: Arena::default() };
56 for name in SYSROOT_CRATES.trim().lines() {
57 let root = src.join(format!("lib{}", name)).join("lib.rs");
58 if root.exists() {
59 sysroot.crates.alloc(SysrootCrateData {
60 name: name.into(),
61 root,
62 deps: Vec::new(),
63 });
64 }
65 }
66 if let Some(std) = sysroot.std() {
67 for dep in STD_DEPS.trim().lines() {
68 if let Some(dep) = sysroot.by_name(dep) {
69 sysroot.crates[std].deps.push(dep)
70 }
71 }
72 }
73 Ok(sysroot)
74 }
75
76 fn by_name(&self, name: &str) -> Option<SysrootCrate> {
77 self.crates.iter().find(|(_id, data)| data.name == name).map(|(id, _data)| id)
78 }
79}
80
81impl SysrootCrate {
82 pub fn name(self, sysroot: &Sysroot) -> &str {
83 &sysroot.crates[self].name
84 }
85 pub fn root(self, sysroot: &Sysroot) -> &Path {
86 sysroot.crates[self].root.as_path()
87 }
88 pub fn root_dir(self, sysroot: &Sysroot) -> &Path {
89 self.root(sysroot).parent().unwrap()
90 }
91 pub fn deps<'a>(self, sysroot: &'a Sysroot) -> impl Iterator<Item = SysrootCrate> + 'a {
92 sysroot.crates[self].deps.iter().map(|&it| it)
93 }
94}
95
96const SYSROOT_CRATES: &str = "
97std
98core
99alloc
100collections
101libc
102panic_unwind
103proc_macro
104rustc_unicode
105std_unicode
106test
107alloc_jemalloc
108alloc_system
109compiler_builtins
110getopts
111panic_unwind
112panic_abort
113rand
114term
115unwind
116build_helper
117rustc_asan
118rustc_lsan
119rustc_msan
120rustc_tsan
121syntax";
122
123const STD_DEPS: &str = "
124alloc
125alloc_jemalloc
126alloc_system
127core
128panic_abort
129rand
130compiler_builtins
131unwind
132rustc_asan
133rustc_lsan
134rustc_msan
135rustc_tsan
136build_helper";