mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-28 04:44:57 +00:00
Extract project model to separate crate
This commit is contained in:
parent
34398a8756
commit
fcd615e4b7
8 changed files with 109 additions and 53 deletions
173
crates/ra_project_model/src/cargo_workspace.rs
Normal file
173
crates/ra_project_model/src/cargo_workspace.rs
Normal file
|
@ -0,0 +1,173 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use cargo_metadata::{MetadataCommand, CargoOpt};
|
||||
use smol_str::SmolStr;
|
||||
use ra_arena::{Arena, RawId, impl_arena_id};
|
||||
use rustc_hash::FxHashMap;
|
||||
use failure::format_err;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
/// `CargoWorkspace` represents the logical structure of, well, a Cargo
|
||||
/// workspace. It pretty closely mirrors `cargo metadata` output.
|
||||
///
|
||||
/// Note that internally, rust analyzer uses a different structure:
|
||||
/// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates,
|
||||
/// while this knows about `Pacakges` & `Targets`: purely cargo-related
|
||||
/// concepts.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CargoWorkspace {
|
||||
packages: Arena<Package, PackageData>,
|
||||
targets: Arena<Target, TargetData>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Package(RawId);
|
||||
impl_arena_id!(Package);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Target(RawId);
|
||||
impl_arena_id!(Target);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PackageData {
|
||||
name: SmolStr,
|
||||
manifest: PathBuf,
|
||||
targets: Vec<Target>,
|
||||
is_member: bool,
|
||||
dependencies: Vec<PackageDependency>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PackageDependency {
|
||||
pub pkg: Package,
|
||||
pub name: SmolStr,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TargetData {
|
||||
pkg: Package,
|
||||
name: SmolStr,
|
||||
root: PathBuf,
|
||||
kind: TargetKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TargetKind {
|
||||
Bin,
|
||||
Lib,
|
||||
Example,
|
||||
Test,
|
||||
Bench,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl TargetKind {
|
||||
fn new(kinds: &[String]) -> TargetKind {
|
||||
for kind in kinds {
|
||||
return match kind.as_str() {
|
||||
"bin" => TargetKind::Bin,
|
||||
"test" => TargetKind::Test,
|
||||
"bench" => TargetKind::Bench,
|
||||
"example" => TargetKind::Example,
|
||||
_ if kind.contains("lib") => TargetKind::Lib,
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
TargetKind::Other
|
||||
}
|
||||
}
|
||||
|
||||
impl Package {
|
||||
pub fn name(self, ws: &CargoWorkspace) -> &str {
|
||||
ws.packages[self].name.as_str()
|
||||
}
|
||||
pub fn root(self, ws: &CargoWorkspace) -> &Path {
|
||||
ws.packages[self].manifest.parent().unwrap()
|
||||
}
|
||||
pub fn targets<'a>(self, ws: &'a CargoWorkspace) -> impl Iterator<Item = Target> + 'a {
|
||||
ws.packages[self].targets.iter().cloned()
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn is_member(self, ws: &CargoWorkspace) -> bool {
|
||||
ws.packages[self].is_member
|
||||
}
|
||||
pub fn dependencies<'a>(
|
||||
self,
|
||||
ws: &'a CargoWorkspace,
|
||||
) -> impl Iterator<Item = &'a PackageDependency> + 'a {
|
||||
ws.packages[self].dependencies.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Target {
|
||||
pub fn package(self, ws: &CargoWorkspace) -> Package {
|
||||
ws.targets[self].pkg
|
||||
}
|
||||
pub fn name(self, ws: &CargoWorkspace) -> &str {
|
||||
ws.targets[self].name.as_str()
|
||||
}
|
||||
pub fn root(self, ws: &CargoWorkspace) -> &Path {
|
||||
ws.targets[self].root.as_path()
|
||||
}
|
||||
pub fn kind(self, ws: &CargoWorkspace) -> TargetKind {
|
||||
ws.targets[self].kind
|
||||
}
|
||||
}
|
||||
|
||||
impl CargoWorkspace {
|
||||
pub fn from_cargo_metadata(cargo_toml: &Path) -> Result<CargoWorkspace> {
|
||||
let mut meta = MetadataCommand::new();
|
||||
meta.manifest_path(cargo_toml).features(CargoOpt::AllFeatures);
|
||||
if let Some(parent) = cargo_toml.parent() {
|
||||
meta.current_dir(parent);
|
||||
}
|
||||
let meta = meta.exec().map_err(|e| format_err!("cargo metadata failed: {}", e))?;
|
||||
let mut pkg_by_id = FxHashMap::default();
|
||||
let mut packages = Arena::default();
|
||||
let mut targets = Arena::default();
|
||||
|
||||
let ws_members = &meta.workspace_members;
|
||||
|
||||
for meta_pkg in meta.packages {
|
||||
let is_member = ws_members.contains(&meta_pkg.id);
|
||||
let pkg = packages.alloc(PackageData {
|
||||
name: meta_pkg.name.into(),
|
||||
manifest: meta_pkg.manifest_path.clone(),
|
||||
targets: Vec::new(),
|
||||
is_member,
|
||||
dependencies: Vec::new(),
|
||||
});
|
||||
let pkg_data = &mut packages[pkg];
|
||||
pkg_by_id.insert(meta_pkg.id.clone(), pkg);
|
||||
for meta_tgt in meta_pkg.targets {
|
||||
let tgt = targets.alloc(TargetData {
|
||||
pkg,
|
||||
name: meta_tgt.name.into(),
|
||||
root: meta_tgt.src_path.clone(),
|
||||
kind: TargetKind::new(meta_tgt.kind.as_slice()),
|
||||
});
|
||||
pkg_data.targets.push(tgt);
|
||||
}
|
||||
}
|
||||
let resolve = meta.resolve.expect("metadata executed with deps");
|
||||
for node in resolve.nodes {
|
||||
let source = pkg_by_id[&node.id];
|
||||
for dep_node in node.deps {
|
||||
let dep =
|
||||
PackageDependency { name: dep_node.name.into(), pkg: pkg_by_id[&dep_node.pkg] };
|
||||
packages[source].dependencies.push(dep);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CargoWorkspace { packages, targets })
|
||||
}
|
||||
|
||||
pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + 'a {
|
||||
self.packages.iter().map(|(id, _pkg)| id)
|
||||
}
|
||||
|
||||
pub fn target_by_root(&self, root: &Path) -> Option<Target> {
|
||||
self.packages().filter_map(|pkg| pkg.targets(self).find(|it| it.root(self) == root)).next()
|
||||
}
|
||||
}
|
45
crates/ra_project_model/src/lib.rs
Normal file
45
crates/ra_project_model/src/lib.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
mod cargo_workspace;
|
||||
mod sysroot;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use failure::bail;
|
||||
|
||||
pub use crate::{
|
||||
cargo_workspace::{CargoWorkspace, Package, Target, TargetKind},
|
||||
sysroot::Sysroot,
|
||||
};
|
||||
|
||||
// TODO use own 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)
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
138
crates/ra_project_model/src/sysroot.rs
Normal file
138
crates/ra_project_model/src/sysroot.rs
Normal file
|
@ -0,0 +1,138 @@
|
|||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use ra_arena::{Arena, RawId, impl_arena_id};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Sysroot {
|
||||
crates: Arena<SysrootCrate, SysrootCrateData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct SysrootCrate(RawId);
|
||||
impl_arena_id!(SysrootCrate);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct SysrootCrateData {
|
||||
name: SmolStr,
|
||||
root: PathBuf,
|
||||
deps: Vec<SysrootCrate>,
|
||||
}
|
||||
|
||||
impl Sysroot {
|
||||
pub fn std(&self) -> Option<SysrootCrate> {
|
||||
self.by_name("std")
|
||||
}
|
||||
|
||||
pub fn crates<'a>(&'a self) -> impl Iterator<Item = SysrootCrate> + 'a {
|
||||
self.crates.iter().map(|(id, _data)| id)
|
||||
}
|
||||
|
||||
pub fn discover(cargo_toml: &Path) -> Result<Sysroot> {
|
||||
let rustc_output = Command::new("rustc")
|
||||
.current_dir(cargo_toml.parent().unwrap())
|
||||
.args(&["--print", "sysroot"])
|
||||
.output()?;
|
||||
if !rustc_output.status.success() {
|
||||
failure::bail!("failed to locate sysroot")
|
||||
}
|
||||
let stdout = String::from_utf8(rustc_output.stdout)?;
|
||||
let sysroot_path = Path::new(stdout.trim());
|
||||
let src = sysroot_path.join("lib/rustlib/src/rust/src");
|
||||
if !src.exists() {
|
||||
failure::bail!(
|
||||
"can't load standard library from sysroot\n\
|
||||
{:?}\n\
|
||||
try running `rustup component add rust-src`",
|
||||
src,
|
||||
);
|
||||
}
|
||||
|
||||
let mut sysroot = Sysroot { crates: Arena::default() };
|
||||
for name in SYSROOT_CRATES.trim().lines() {
|
||||
let root = src.join(format!("lib{}", name)).join("lib.rs");
|
||||
if root.exists() {
|
||||
sysroot.crates.alloc(SysrootCrateData {
|
||||
name: name.into(),
|
||||
root,
|
||||
deps: Vec::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
if let Some(std) = sysroot.std() {
|
||||
for dep in STD_DEPS.trim().lines() {
|
||||
if let Some(dep) = sysroot.by_name(dep) {
|
||||
sysroot.crates[std].deps.push(dep)
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(sysroot)
|
||||
}
|
||||
|
||||
fn by_name(&self, name: &str) -> Option<SysrootCrate> {
|
||||
self.crates.iter().find(|(_id, data)| data.name == name).map(|(id, _data)| id)
|
||||
}
|
||||
}
|
||||
|
||||
impl SysrootCrate {
|
||||
pub fn name(self, sysroot: &Sysroot) -> &SmolStr {
|
||||
&sysroot.crates[self].name
|
||||
}
|
||||
pub fn root(self, sysroot: &Sysroot) -> &Path {
|
||||
sysroot.crates[self].root.as_path()
|
||||
}
|
||||
pub fn root_dir(self, sysroot: &Sysroot) -> &Path {
|
||||
self.root(sysroot).parent().unwrap()
|
||||
}
|
||||
pub fn deps<'a>(self, sysroot: &'a Sysroot) -> impl Iterator<Item = SysrootCrate> + 'a {
|
||||
sysroot.crates[self].deps.iter().map(|&it| it)
|
||||
}
|
||||
}
|
||||
|
||||
const SYSROOT_CRATES: &str = "
|
||||
std
|
||||
core
|
||||
alloc
|
||||
collections
|
||||
libc
|
||||
panic_unwind
|
||||
proc_macro
|
||||
rustc_unicode
|
||||
std_unicode
|
||||
test
|
||||
alloc_jemalloc
|
||||
alloc_system
|
||||
compiler_builtins
|
||||
getopts
|
||||
panic_unwind
|
||||
panic_abort
|
||||
rand
|
||||
term
|
||||
unwind
|
||||
build_helper
|
||||
rustc_asan
|
||||
rustc_lsan
|
||||
rustc_msan
|
||||
rustc_tsan
|
||||
syntax";
|
||||
|
||||
const STD_DEPS: &str = "
|
||||
alloc
|
||||
alloc_jemalloc
|
||||
alloc_system
|
||||
core
|
||||
panic_abort
|
||||
rand
|
||||
compiler_builtins
|
||||
unwind
|
||||
rustc_asan
|
||||
rustc_lsan
|
||||
rustc_msan
|
||||
rustc_tsan
|
||||
build_helper";
|
Loading…
Add table
Add a link
Reference in a new issue