Skip to content

Commit

Permalink
Use cargo metadata to resolve targets (#214)
Browse files Browse the repository at this point in the history
* Use `cargo metadata` to resolve targets

* Address review comments

* Changes packages to BTreeMap
  • Loading branch information
Kobzol committed May 23, 2023
1 parent 2b3c0c3 commit c0af149
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 133 deletions.
188 changes: 68 additions & 120 deletions src/skeleton/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
mod read;
mod target;
mod version_masking;

use crate::skeleton::target::{Target, TargetKind};
use crate::OptimisationProfile;
use anyhow::Context;
use cargo_manifest::Product;
use fs_err as fs;
use globwalk::GlobWalkerBuilder;
use serde::{Deserialize, Serialize};
Expand All @@ -20,11 +23,13 @@ pub struct Manifest {
/// Relative path with respect to the project root.
pub relative_path: PathBuf,
pub contents: String,
pub targets: Vec<Target>,
}

pub(in crate::skeleton) struct ParsedManifest {
relative_path: PathBuf,
contents: toml::Value,
targets: Vec<Target>,
}

impl Skeleton {
Expand Down Expand Up @@ -85,14 +90,38 @@ impl Skeleton {
fs::write(config_file_path, config_file.as_str())?;
}

let no_std_entrypoint = "#![no_std]
const NO_STD_ENTRYPOINT: &str = "#![no_std]
#![no_main]
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
loop {}
}
";
const NO_STD_HARNESS_ENTRYPOINT: &str = r#"#![no_std]
#![no_main]
#![feature(custom_test_frameworks)]
#![test_runner(test_runner)]
#[no_mangle]
pub extern "C" fn _init() {}
fn test_runner(_: &[&dyn Fn()]) {}
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
loop {}
}
"#;

let get_test_like_entrypoint = |harness: bool| -> &str {
match (no_std, harness) {
(true, true) => NO_STD_HARNESS_ENTRYPOINT,
(true, false) => NO_STD_ENTRYPOINT,
(false, true) => "",
(false, false) => "fn main() {}",
}
};

// Save all manifests to disks
for manifest in &self.manifests {
Expand All @@ -108,129 +137,47 @@ fn panic(_: &core::panic::PanicInfo) -> ! {
let parsed_manifest =
cargo_manifest::Manifest::from_slice(manifest.contents.as_bytes())?;

let package_name = parsed_manifest.package.as_ref().map(|v| &v.name);
// Create dummy entrypoint files for all binaries
for bin in &parsed_manifest.bin.unwrap_or_default() {
// Relative to the manifest path
let binary_relative_path = bin.path.to_owned().unwrap_or_else(|| match &bin.name {
Some(name) if Some(name) != package_name => {
format!("src/bin/{}.rs", name)
}
_ => "src/main.rs".to_owned(),
});
let binary_path = parent_directory.join(binary_relative_path);
if let Some(parent_directory) = binary_path.parent() {
fs::create_dir_all(parent_directory)?;
}
if no_std {
fs::write(binary_path, no_std_entrypoint)?;
} else {
fs::write(binary_path, "fn main() {}")?;
}
}

// Create dummy entrypoint files for for all libraries
for lib in &parsed_manifest.lib {
// Relative to the manifest path
let lib_relative_path = lib.path.as_deref().unwrap_or("src/lib.rs");
let lib_path = parent_directory.join(lib_relative_path);
if let Some(parent_directory) = lib_path.parent() {
fs::create_dir_all(parent_directory)?;
}
if no_std && !lib.proc_macro {
fs::write(lib_path, "#![no_std]")?;
} else {
fs::write(lib_path, "")?;
}
}

// Create dummy entrypoint files for for all benchmarks
for bench in &parsed_manifest.bench.unwrap_or_default() {
// Relative to the manifest path
let bench_name = bench.name.as_ref().context("Missing benchmark name.")?;
let bench_relative_path = bench
.path
.clone()
.unwrap_or_else(|| format!("benches/{}.rs", bench_name));
let bench_path = parent_directory.join(bench_relative_path);
if let Some(parent_directory) = bench_path.parent() {
fs::create_dir_all(parent_directory)?;
}
fs::write(bench_path, "fn main() {}")?;
}

// Create dummy entrypoint files for for all tests
for test in &parsed_manifest.test.unwrap_or_default() {
// Relative to the manifest path
let test_name = test.name.as_ref().context("Missing test name.")?;
let test_relative_path = test
.path
.clone()
.unwrap_or_else(|| format!("tests/{}.rs", test_name));
let test_path = parent_directory.join(test_relative_path);
if let Some(parent_directory) = test_path.parent() {
fs::create_dir_all(parent_directory)?;
}
if no_std {
if test.harness {
fs::write(
test_path,
r#"#![no_std]
#![no_main]
#![feature(custom_test_frameworks)]
#![test_runner(test_runner)]
#[no_mangle]
pub extern "C" fn _init() {}
fn test_runner(_: &[&dyn Fn()]) {}
let is_harness = |products: &Option<Vec<Product>>, name: &str| -> bool {
products
.as_ref()
.and_then(|v| {
v.iter()
.find(|product| product.name.as_deref() == Some(name))
.map(|p| p.harness)
})
.unwrap_or(true)
};

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
loop {}
}
"#,
)?;
} else {
fs::write(test_path, no_std_entrypoint)?;
// Create dummy entrypoints for all targets
for target in &manifest.targets {
let content = match target.kind {
TargetKind::BuildScript => "fn main() {}",
TargetKind::Bin | TargetKind::Example => {
if no_std {
NO_STD_ENTRYPOINT
} else {
"fn main() {}"
}
}
} else if test.harness {
fs::write(test_path, "")?;
} else {
fs::write(test_path, "fn main() {}")?;
}
}

// Create dummy entrypoint files for for all examples
for example in &parsed_manifest.example.unwrap_or_default() {
// Relative to the manifest path
let example_name = example.name.as_ref().context("Missing example name.")?;
let example_relative_path = example
.path
.clone()
.unwrap_or_else(|| format!("examples/{}.rs", example_name));
let example_path = parent_directory.join(example_relative_path);
if let Some(parent_directory) = example_path.parent() {
fs::create_dir_all(parent_directory)?;
}
if no_std {
fs::write(example_path, no_std_entrypoint)?;
} else {
fs::write(example_path, "fn main() {}")?;
}
}

// Create dummy build script file if specified
if let Some(package) = parsed_manifest.package {
if let Some(cargo_manifest::Value::String(build_raw_path)) = package.build {
// Relative to the manifest path
let build_relative_path = PathBuf::from(build_raw_path);
let build_path = parent_directory.join(build_relative_path);
if let Some(parent_directory) = build_path.parent() {
fs::create_dir_all(parent_directory)?;
TargetKind::Lib { is_proc_macro } => {
if no_std && !is_proc_macro {
"#![no_std]"
} else {
""
}
}
TargetKind::Bench => {
get_test_like_entrypoint(is_harness(&parsed_manifest.bench, &target.name))
}
TargetKind::Test => {
get_test_like_entrypoint(is_harness(&parsed_manifest.test, &target.name))
}
fs::write(build_path, "fn main() {}")?;
};
let path = parent_directory.join(&target.path);
if let Some(dir) = path.parent() {
fs::create_dir_all(dir)?;
}
fs::write(&path, content)?;
}
}
Ok(())
Expand Down Expand Up @@ -332,6 +279,7 @@ fn serialize_manifests(manifests: Vec<ParsedManifest>) -> Result<Vec<Manifest>,
serialised_manifests.push(Manifest {
relative_path: manifest.relative_path,
contents,
targets: manifest.targets,
});
}
Ok(serialised_manifests)
Expand Down
76 changes: 63 additions & 13 deletions src/skeleton/read.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
//! Logic to read all the files required to build a caching layer for a project.
use super::ParsedManifest;
use cargo_metadata::Metadata;
use std::collections::BTreeSet;
use crate::skeleton::target::{Target, TargetKind};
use cargo_metadata::{Metadata, Package};
use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::str::FromStr;

pub(super) fn config<P: AsRef<Path>>(base_path: &P) -> Result<Option<String>, anyhow::Error> {
Expand Down Expand Up @@ -39,20 +40,29 @@ pub(super) fn manifests<P: AsRef<Path>>(
base_path: &P,
metadata: Metadata,
) -> Result<Vec<ParsedManifest>, anyhow::Error> {
let manifest_paths = metadata
let mut packages: BTreeMap<PathBuf, BTreeSet<Target>> = metadata
.workspace_packages()
.iter()
.map(|package| package.manifest_path.clone().into_std_path_buf())
.chain(
metadata
.root_package()
.map(|p| p.clone().manifest_path.into_std_path_buf())
.or_else(|| Some(base_path.as_ref().join("Cargo.toml"))),
)
.collect::<BTreeSet<_>>();
.copied()
.chain(metadata.root_package())
.map(|p| {
(
p.manifest_path.clone().into_std_path_buf(),
gather_targets(p),
)
})
.collect();

if metadata.root_package().is_none() {
// At the root, there might be a Cargo.toml manifest with a [workspace] section.
// However, if this root manifest doesn't contain [package], it is not considered a package
// by cargo metadata. Therefore, we have to add it manually.
// Workspaces currently cannot be nested, so this should only happen at the root.
packages.insert(base_path.as_ref().join("Cargo.toml"), Default::default());
}

let mut manifests = vec![];
for absolute_path in manifest_paths {
for (absolute_path, targets) in packages {
let contents = fs::read_to_string(&absolute_path)?;

let mut parsed = cargo_manifest::Manifest::from_str(&contents)?;
Expand Down Expand Up @@ -89,15 +99,55 @@ pub(super) fn manifests<P: AsRef<Path>>(
&absolute_path
)
})?;

manifests.push(ParsedManifest {
relative_path,
contents: intermediate,
targets: targets.into_iter().collect(),
});
}

Ok(manifests)
}

fn gather_targets(package: &Package) -> BTreeSet<Target> {
let manifest_path = package.manifest_path.clone().into_std_path_buf();
let root_dir = manifest_path.parent().unwrap();
package
.targets
.iter()
.map(|target| {
let relative_path = pathdiff::diff_paths(&target.src_path, root_dir).unwrap();
let kind = if target.is_bench() {
TargetKind::Bench
} else if target.is_example() {
TargetKind::Example
} else if target.is_test() {
TargetKind::Test
} else if target.is_bin() {
TargetKind::Bin
} else if target.is_custom_build() {
TargetKind::BuildScript
} else {
// If a library has custom crate type (e.g. "cdylib"), it's kind will be "cdylib"
// instead of just "lib". Therefore, we assume that this target is a library.
TargetKind::Lib {
is_proc_macro: target
.crate_types
.iter()
.any(|t| t.as_str() == "proc-macro"),
}
};

Target {
path: relative_path,
kind,
name: target.name.clone(),
}
})
.collect()
}

pub(super) fn lockfile<P: AsRef<Path>>(
base_path: &P,
) -> Result<Option<toml::Value>, anyhow::Error> {
Expand Down
18 changes: 18 additions & 0 deletions src/skeleton/target.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use std::path::PathBuf;

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub enum TargetKind {
Lib { is_proc_macro: bool },
Bin,
Test,
Bench,
Example,
BuildScript,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub struct Target {
pub(crate) path: PathBuf,
pub(crate) kind: TargetKind,
pub(crate) name: String,
}
Loading

0 comments on commit c0af149

Please sign in to comment.