use std::collections::BTreeMap;
use crate::{
PackageInfo, Result, Version, WorkspaceInfo, WorkspaceKind, WorkspaceSearch, WorkspaceStructure,
};
use axoasset::SourceFile;
use camino::{Utf8Path, Utf8PathBuf};
use guppy::{
graph::{BuildTargetId, BuildTargetKind, DependencyDirection, PackageGraph, PackageMetadata},
MetadataCommand,
};
use itertools::{concat, Itertools};
pub use axoasset::toml_edit::DocumentMut;
pub type CargoProfiles = BTreeMap<String, CargoProfile>;
pub fn get_workspace(start_dir: &Utf8Path, clamp_to_dir: Option<&Utf8Path>) -> WorkspaceSearch {
let manifest_path = match workspace_manifest(start_dir, clamp_to_dir) {
Ok(path) => path,
Err(e) => {
return WorkspaceSearch::Missing(e);
}
};
let graph = match package_graph(start_dir) {
Ok(graph) => graph,
Err(e) => {
let error = match e {
crate::AxoprojectError::CargoMetadata(e) => {
if cargo_version_works() {
crate::AxoprojectError::CargoMetadata(e)
} else {
crate::AxoprojectError::CargoMissing {}
}
}
_ => e,
};
return WorkspaceSearch::Missing(error);
}
};
let workspace = workspace_info(&graph);
match workspace {
Ok(workspace) => WorkspaceSearch::Found(workspace),
Err(e) => WorkspaceSearch::Broken {
manifest_path,
cause: e,
},
}
}
fn cargo_version_works() -> bool {
std::process::Command::new("cargo")
.arg("--version")
.status()
.map(|s| s.success())
.unwrap_or(false)
}
fn package_graph(start_dir: &Utf8Path) -> Result<PackageGraph> {
let mut metadata_cmd = MetadataCommand::new();
metadata_cmd.current_dir(start_dir);
let pkg_graph = metadata_cmd.build_graph()?;
Ok(pkg_graph)
}
fn workspace_info(pkg_graph: &PackageGraph) -> Result<WorkspaceStructure> {
let workspace = pkg_graph.workspace();
let members = pkg_graph.resolve_workspace();
let manifest_path = workspace.root().join("Cargo.toml");
assert!(
manifest_path.exists(),
"cargo metadata returned a workspace without a Cargo.toml!?"
);
let cargo_profiles = get_profiles(&manifest_path)?;
let cargo_metadata_table = Some(workspace.metadata_table().clone());
let workspace_root = workspace.root();
let root_auto_includes = crate::find_auto_includes(workspace_root)?;
let mut all_package_info = vec![];
for package in members.packages(DependencyDirection::Forward) {
let mut info = package_info(workspace_root, &package, pkg_graph)?;
crate::merge_auto_includes(&mut info, &root_auto_includes);
all_package_info.push(info);
}
let target_dir = workspace.target_directory().to_owned();
let workspace_dir = workspace.root().to_owned();
Ok(WorkspaceStructure {
sub_workspaces: vec![],
packages: all_package_info,
workspace: WorkspaceInfo {
kind: WorkspaceKind::Rust,
target_dir,
workspace_dir,
manifest_path,
dist_manifest_path: None,
root_auto_includes,
cargo_metadata_table,
cargo_profiles,
},
})
}
fn package_info(
_workspace_root: &Utf8Path,
package: &PackageMetadata,
pkg_graph: &PackageGraph,
) -> Result<PackageInfo> {
let manifest_path = package.manifest_path().to_owned();
let package_root = manifest_path
.parent()
.expect("package manifest had no parent!?")
.to_owned();
let cargo_package_id = Some(package.id().clone());
let cargo_metadata_table = Some(package.metadata_table().clone());
let mut binaries = vec![];
let mut cdylibs = vec![];
let mut cstaticlibs = vec![];
for target in package.build_targets() {
let build_id = target.id();
match build_id {
BuildTargetId::Binary(name) => {
binaries.push(name.to_owned());
}
BuildTargetId::Library => {
if let BuildTargetKind::LibraryOrExample(crate_types) = target.kind() {
for crate_type in crate_types {
match &**crate_type {
"cdylib" => {
cdylibs.push(target.name().to_owned());
}
"staticlib" => {
cstaticlibs.push(target.name().to_owned());
}
_ => {
}
}
}
}
}
_ => {
}
}
}
let keywords_and_categories: Option<Vec<String>> =
if package.keywords().is_empty() && package.categories().is_empty() {
None
} else {
let categories = package.categories().to_vec();
let keywords = package.keywords().to_vec();
Some(
concat(vec![categories, keywords])
.into_iter()
.unique()
.collect::<Vec<String>>(),
)
};
let query = pkg_graph.query_forward(std::iter::once(package.id()))?;
let package_set = query.resolve();
let mut axoupdater_versions = vec![];
for p in package_set.packages(DependencyDirection::Reverse) {
for subpackage in p.direct_links() {
if subpackage.dep_name() == "axoupdater" {
axoupdater_versions.push((
p.name().to_owned(),
Version::Cargo(subpackage.to().version().to_owned()),
))
}
}
}
let version = Some(Version::Cargo(package.version().clone()));
let mut info = PackageInfo {
true_name: package.name().to_owned(),
true_version: version.clone(),
name: package.name().to_owned(),
version,
manifest_path,
dist_manifest_path: None,
package_root: package_root.clone(),
description: package.description().map(ToOwned::to_owned),
authors: package.authors().to_vec(),
keywords: keywords_and_categories,
license: package.license().map(ToOwned::to_owned),
publish: !package.publish().is_never(),
repository_url: package.repository().map(ToOwned::to_owned),
homepage_url: package.homepage().map(ToOwned::to_owned),
documentation_url: package.documentation().map(ToOwned::to_owned),
readme_file: package.readme().map(|readme| package_root.join(readme)),
license_files: package
.license_file()
.map(ToOwned::to_owned)
.into_iter()
.collect(),
changelog_file: None,
binaries,
out_dir: None,
cdylibs,
cstaticlibs,
cargo_metadata_table,
cargo_package_id,
npm_scope: None,
build_command: None,
axoupdater_versions,
dist: None,
};
let auto_includes = crate::find_auto_includes(&package_root)?;
crate::merge_auto_includes(&mut info, &auto_includes);
if info.documentation_url.is_none() {
info.documentation_url = Some(format!(
"https://docs.rs/{}/{}",
info.name,
info.version.as_ref().unwrap()
));
}
Ok(info)
}
fn workspace_manifest(
start_dir: &Utf8Path,
clamp_to_dir: Option<&Utf8Path>,
) -> Result<Utf8PathBuf> {
crate::find_file("Cargo.toml", start_dir, clamp_to_dir)
}
pub fn load_root_cargo_toml(manifest_path: &Utf8Path) -> Result<DocumentMut> {
let manifest_src = SourceFile::load_local(manifest_path)?;
let manifest = manifest_src.deserialize_toml_edit()?;
Ok(manifest)
}
fn get_profiles(manifest_path: &Utf8Path) -> Result<BTreeMap<String, CargoProfile>> {
let mut profiles = CargoProfiles::new();
let workspace_toml = load_root_cargo_toml(manifest_path)?;
let Some(profiles_table) = &workspace_toml.get("profile").and_then(|t| t.as_table()) else {
return Ok(profiles);
};
for (profile_name, profile) in profiles_table.iter() {
let debug = profile.get("debug");
let split_debuginfo = profile.get("split-debuginfo");
let inherits = profile.get("inherits");
let debug = debug.and_then(|debug| {
debug
.as_bool()
.map(|val| if val { 2 } else { 0 })
.or_else(|| debug.as_integer())
});
let split_debuginfo = split_debuginfo
.and_then(|v| v.as_str())
.map(ToOwned::to_owned);
let inherits = inherits.and_then(|v| v.as_str()).map(ToOwned::to_owned);
let entry = CargoProfile {
inherits,
debug,
split_debuginfo,
};
profiles.insert(profile_name.to_owned(), entry);
}
Ok(profiles)
}
#[derive(Debug, Clone)]
pub struct CargoProfile {
pub inherits: Option<String>,
pub debug: Option<i64>,
pub split_debuginfo: Option<String>,
}