aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_project_model/src/lib.rs
blob: e5c93fd856f37891d7467e8d51899158fc03b8d0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
mod cargo_workspace;
mod sysroot;

use std::path::{Path, PathBuf};

use failure::bail;
use rustc_hash::FxHashMap;

use ra_db::{CrateGraph, FileId, Edition};

pub use crate::{
    cargo_workspace::{CargoWorkspace, Package, Target, TargetKind},
    sysroot::Sysroot,
};

// TODO use proper error enum
pub type Result<T> = ::std::result::Result<T, ::failure::Error>;

#[derive(Debug, Clone)]
pub struct ProjectWorkspace {
    pub cargo: CargoWorkspace,
    pub sysroot: Sysroot,
}

impl ProjectWorkspace {
    pub fn discover(path: &Path) -> Result<ProjectWorkspace> {
        let cargo_toml = find_cargo_toml(path)?;
        let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml)?;
        let sysroot = Sysroot::discover(&cargo_toml)?;
        let res = ProjectWorkspace { cargo, sysroot };
        Ok(res)
    }

    pub fn to_crate_graph(&self, load: &mut dyn FnMut(&Path) -> Option<FileId>) -> CrateGraph {
        let mut crate_graph = CrateGraph::default();
        let mut sysroot_crates = FxHashMap::default();
        for krate in self.sysroot.crates() {
            if let Some(file_id) = load(krate.root(&self.sysroot)) {
                sysroot_crates
                    .insert(krate, crate_graph.add_crate_root(file_id, Edition::Edition2015));
            }
        }
        for from in self.sysroot.crates() {
            for to in from.deps(&self.sysroot) {
                let name = to.name(&self.sysroot);
                if let (Some(&from), Some(&to)) =
                    (sysroot_crates.get(&from), sysroot_crates.get(&to))
                {
                    if let Err(_) = crate_graph.add_dep(from, name.into(), to) {
                        log::error!("cyclic dependency between sysroot crates")
                    }
                }
            }
        }

        let libstd = self.sysroot.std().and_then(|it| sysroot_crates.get(&it).map(|&it| it));

        let mut pkg_to_lib_crate = FxHashMap::default();
        let mut pkg_crates = FxHashMap::default();
        // Next, create crates for each package, target pair
        for pkg in self.cargo.packages() {
            let mut lib_tgt = None;
            for tgt in pkg.targets(&self.cargo) {
                let root = tgt.root(&self.cargo);
                if let Some(file_id) = load(root) {
                    let edition = if pkg.edition(&self.cargo) == "2015" {
                        Edition::Edition2015
                    } else {
                        Edition::Edition2018
                    };
                    let crate_id = crate_graph.add_crate_root(file_id, edition);
                    if tgt.kind(&self.cargo) == TargetKind::Lib {
                        lib_tgt = Some(crate_id);
                        pkg_to_lib_crate.insert(pkg, crate_id);
                    }
                    pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
                }
            }

            // Set deps to the std and to the lib target of the current package
            for &from in pkg_crates.get(&pkg).into_iter().flatten() {
                if let Some(to) = lib_tgt {
                    if to != from {
                        if let Err(_) = crate_graph.add_dep(from, pkg.name(&self.cargo).into(), to)
                        {
                            log::error!(
                                "cyclic dependency between targets of {}",
                                pkg.name(&self.cargo)
                            )
                        }
                    }
                }
                if let Some(std) = libstd {
                    if let Err(_) = crate_graph.add_dep(from, "std".into(), std) {
                        log::error!("cyclic dependency on std for {}", pkg.name(&self.cargo))
                    }
                }
            }
        }

        // Now add a dep ednge from all targets of upstream to the lib
        // target of downstream.
        for pkg in self.cargo.packages() {
            for dep in pkg.dependencies(&self.cargo) {
                if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
                    for &from in pkg_crates.get(&pkg).into_iter().flatten() {
                        if let Err(_) = crate_graph.add_dep(from, dep.name.clone().into(), to) {
                            log::error!(
                                "cyclic dependency {} -> {}",
                                pkg.name(&self.cargo),
                                dep.pkg.name(&self.cargo)
                            )
                        }
                    }
                }
            }
        }

        crate_graph
    }
}

fn find_cargo_toml(path: &Path) -> Result<PathBuf> {
    if path.ends_with("Cargo.toml") {
        return Ok(path.to_path_buf());
    }
    let mut curr = Some(path);
    while let Some(path) = curr {
        let candidate = path.join("Cargo.toml");
        if candidate.exists() {
            return Ok(candidate);
        }
        curr = path.parent();
    }
    bail!("can't find Cargo.toml at {}", path.display())
}