mod compat_pkg;
use compat_pkg::ManualPackage;
mod conditional;
mod rustlib;
use std::collections::HashSet;
use std::path::{Path as StdPath, PathBuf};
use std::string::FromUtf8Error;
use std::{fs, io};
use cargo_metadata::{CargoOpt, MetadataCommand, Package};
use ego_tree::{NodeId, Tree};
use proc_macro2::Span;
use syn::punctuated::Punctuated;
use syn::{Attribute, Ident, Meta, PathSegment, Token};
use crate::platforms::SystemInfo;
use crate::{Features, Items, Module, ModuleInformation};
use rustlib::{add_sysroot_crate, SYSROOT_CRATES};
use self::conditional::{CfgAttrGroup, CfgEval, CfgPredicate};
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("error fetching cargo metadata: {0}")]
CargoMetadata(#[from] cargo_metadata::Error),
#[error("error parsing source code: {0}")]
Syn(#[from] syn::Error),
#[error("io error: {0}")]
Io(#[from] io::Error),
#[error("unknown package: {0}")]
UnknownPackage(String),
#[error("failed to convert to utf8: {0}")]
Utf8(#[from] FromUtf8Error),
#[error(
"can't load the standard library from sysroot. \n\n\
try installing the rust-src the same way you installed rustc"
)]
Sysroot,
#[error(
"invalid sysroot crate: {0}.\n\n\
sysroot crates should be one of: \n\
`alloc`, `backtrace`, `core`, `panic_abort`, \
`panic_unwind`, `proc_macro`, `profiler_builtins`, \
`std`, `std_detect`, `test`, `unwind`"
)]
InvalidSysrootCrate(String),
}
pub type Result<T> = ::std::result::Result<T, Error>;
pub fn parse<P: AsRef<StdPath> + Into<PathBuf>>(
path: P,
cargo_opt: CargoOpt,
platform: &SystemInfo,
) -> Result<ModuleInformation> {
let metadata = MetadataCommand::new()
.manifest_path(path)
.features(cargo_opt.clone())
.exec()?;
let mut tree = Tree::new(Module::new_s("_", None));
let mut features = Features::new();
if let Some(root) = metadata.root_package() {
let entry = features.entry(root.name.clone()).or_default();
match cargo_opt {
CargoOpt::AllFeatures => {
for feature in root.features.keys() {
entry.insert(feature.clone());
}
}
CargoOpt::NoDefaultFeatures => (),
CargoOpt::SomeFeatures(ref features) => {
for feature in features {
entry.insert(feature.clone());
}
}
}
resolve_dependency_features(&mut Vec::new(), &mut features, root, &metadata.packages)?;
}
for pkg in SYSROOT_CRATES.keys() {
features.insert((*pkg).to_owned(), Default::default());
}
let mut std = false;
for package in metadata.packages {
resolve_package(&features, platform, &mut tree, package.into(), &mut std)?;
}
if !std {
add_sysroot_crate(&mut tree, &features, platform, "core")?;
}
Ok(tree.into())
}
pub(crate) fn resolve_package<'a, 'b: 'a>(
features: &Features,
platform: &SystemInfo,
tree: &mut Tree<Module>,
package: ManualPackage,
std: &mut bool,
) -> Result<()> {
let mut root = tree.root_mut();
let ident = package.name.replace('-', "_");
let pkg_root = root.append(Module::new_s(&ident, package.edition.clone()));
let path = get_lib_path(&package);
let path = match path {
Some(path) => path,
None => return Ok(()),
};
let file = fs::read_to_string(path)?;
let file = syn::parse_file(&file)?;
let pkg_root_id = pkg_root.id();
resolve_module(
features,
platform,
tree,
&package,
pkg_root_id,
file.items,
&new_path(&ident),
path.parent().unwrap(),
path,
)?;
if !*std {
let no_std_path = new_path("no_std");
let mut no_std = file.attrs.iter().any(|x| x.path() == &no_std_path);
if !no_std {
if let Some(features) = features.get(&*package.name) {
let attrs = get_attrs_cfg_attr(&file.attrs, features, platform)?;
no_std = attrs.iter().any(|x| x.path() == &no_std_path);
}
}
if !no_std {
add_sysroot_crate(tree, features, platform, "std")?;
*std = true;
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn resolve_module<'a, 'b: 'a>(
features: &Features,
platform: &SystemInfo,
tree: &mut Tree<Module>,
package: &ManualPackage,
position: NodeId,
items: Items,
syn_path: &syn::Path,
mod_path: &StdPath,
file_path: &StdPath,
) -> Result<()> {
for item in items {
match item {
syn::Item::Mod(item) => {
let our_features = &features[&*package.name];
let cfg = get_attr_cfg(&item.attrs, our_features, platform)?;
if !cfg {
continue;
}
let mut syn_path = syn_path.clone();
syn_path.segments.push(PathSegment {
ident: item.ident.clone(),
arguments: Default::default(),
});
let attrs = eval_cfg_attrs(item.attrs, our_features, platform)?;
let path_attr = get_path_attr(&attrs);
let mut parent_rel: PathBuf = syn_path
.segments
.iter()
.skip(1)
.map(|x| x.ident.to_string())
.collect();
parent_rel.pop();
let same_path = mod_path.join(item.ident.to_string() + ".rs");
let new_fs_path = mod_path.join(item.ident.to_string());
let (fs_path, file_path) = match path_attr {
Some(attr) => {
let parent_path = file_path.parent().unwrap();
let new_fs_path = parent_path.join(&attr).parent().unwrap().to_owned();
let file_path = parent_path.join(&attr);
(new_fs_path, file_path)
}
None => {
if same_path.exists() {
(new_fs_path, same_path)
} else {
(
new_fs_path,
mod_path.join(item.ident.to_string()).join("mod.rs"),
)
}
}
};
let items = match item.content {
None => {
let file = fs::read_to_string(&file_path)?;
syn::parse_file(&file)?.items
}
Some((_, items)) => items,
};
let mut position = tree.get_mut(position).unwrap();
let position = position.append(Module {
ident: item.ident,
vis: item.vis,
attrs,
items: Items::new(),
edition: None,
});
let position = position.id();
resolve_module(
features, platform, tree, package, position, items, &syn_path, &fs_path,
&file_path,
)?;
}
syn::Item::ExternCrate(expr) => {
if let Some(crat) = SYSROOT_CRATES.keys().find(|x| expr.ident == *x) {
add_sysroot_crate(tree, features, platform, crat)?;
}
}
item => {
let mut position = tree.get_mut(position).unwrap();
position.value().items.push(item.clone())
}
}
}
Ok(())
}
fn get_path_attr(attrs: &[Attribute]) -> Option<PathBuf> {
for attr in attrs {
if attr.path() == &new_path("path") {
match &attr.meta {
syn::Meta::Path(_) => (),
syn::Meta::List(_) => (),
syn::Meta::NameValue(x) => {
if let syn::Expr::Lit(x) = &x.value {
if let syn::Lit::Str(x) = &x.lit {
return Some(PathBuf::from(x.value()));
}
}
}
}
}
}
None
}
fn get_attr_cfg(
attrs: &[Attribute],
features: &HashSet<String>,
platform: &SystemInfo,
) -> Result<bool> {
for attr in attrs {
if attr.path() == &new_path("cfg") {
match &attr.meta {
syn::Meta::Path(_) => (),
syn::Meta::List(x) => {
let predicate: CfgPredicate = syn::parse2(x.tokens.clone())?;
return Ok(predicate.eval(features, platform));
}
syn::Meta::NameValue(_) => (),
}
}
}
Ok(true)
}
fn eval_cfg_attr(
attr: &Attribute,
features: &HashSet<String>,
platform: &SystemInfo,
) -> Result<Option<Punctuated<Meta, Token![,]>>> {
match &attr.meta {
syn::Meta::List(x) => {
let inner = syn::parse2(x.tokens.clone())?;
let inner: CfgAttrGroup = inner;
let eval = inner.cfg.eval(features, platform);
if eval {
Ok(Some(inner.attrs))
} else {
Ok(None)
}
}
_ => Err(syn::Error::new(attr.pound_token.span, "cfg_attr has invalid type!").into()),
}
}
fn get_attrs_cfg_attr(
attrs: &[Attribute],
features: &HashSet<String>,
platform: &SystemInfo,
) -> Result<Vec<Meta>> {
let mut vec = Vec::new();
for attr in attrs {
if attr.path() == &new_path("cfg_attr") {
let eval = eval_cfg_attr(attr, features, platform)?;
if let Some(eval) = eval {
vec.extend(eval.into_iter())
}
}
}
Ok(vec)
}
fn eval_cfg_attrs(
attrs: Vec<Attribute>,
features: &HashSet<String>,
platform: &SystemInfo,
) -> Result<Vec<Attribute>> {
let mut vec = Vec::new();
for attr in attrs {
if attr.path() == &new_path("cfg_attr") {
let eval = eval_cfg_attr(&attr, features, platform)?;
if let Some(eval) = eval {
for inner_attr in eval {
vec.push(syn::Attribute {
pound_token: attr.pound_token,
style: attr.style,
bracket_token: attr.bracket_token,
meta: inner_attr,
});
}
}
} else {
vec.push(attr)
}
}
Ok(vec)
}
fn get_lib_path<'a, 'b: 'a>(package: &'a ManualPackage) -> Option<&'a StdPath> {
for target in package.targets.iter() {
let mut kinds = target.kind.iter();
if kinds.any(|x| x == "lib") {
return Some(&target.src_path);
}
}
None
}
fn new_path(s: &str) -> syn::Path {
syn::Path {
leading_colon: None,
segments: {
let mut p = Punctuated::new();
p.push(syn::PathSegment {
ident: Ident::new(s, Span::call_site()),
arguments: Default::default(),
});
p
},
}
}
fn resolve_dependency_features<'a>(
stack: &mut Vec<&'a str>,
features: &mut Features,
package: &'a Package,
packages: &'a [Package],
) -> Result<()> {
for dependency in &package.dependencies {
let dep_package = match get_pkg(packages, &dependency.name) {
Ok(o) => o,
Err(_) => continue,
};
let mut enabled = !dependency.optional;
if let Some(features) = features.get(&package.name) {
for enabled_feature in features {
enabled |= package.features[enabled_feature].contains(&dependency.name);
}
}
if !enabled {
continue;
}
let entry = features.entry(dependency.name.clone()).or_default();
if dependency.uses_default_features {
if let Some(def) = dep_package.features.get("default") {
for def in def {
entry.insert(def.clone());
}
}
}
for feature in &dependency.features {
entry.insert(feature.clone());
}
if !stack.contains(&package.name.as_str()) {
stack.push(&package.name);
resolve_dependency_features(stack, features, dep_package, packages)?;
stack.pop();
}
}
Ok(())
}
fn get_pkg<'a>(packages: &'a [Package], this: &str) -> Result<&'a Package> {
packages
.iter()
.find(|x| x.name == this)
.ok_or_else(|| Error::UnknownPackage(this.to_owned()))
}