use std::{collections::BTreeSet as Set, vec};
use cargo_metadata::{Node, Package, camino::Utf8PathBuf};
use cargo_util_schemas::core::{PackageIdSpec, SourceKind};
use itertools::Itertools;
use crate::{
buck::{Load, Rule, RustRule},
buckal_error, buckal_note,
context::BuckalContext,
utils::{UnwrapOrExit, get_vendor_dir},
};
use super::emit::{
emit_buildscript_build, emit_buildscript_run, emit_cargo_manifest, emit_filegroup,
emit_git_fetch, emit_http_archive, emit_rust_binary, emit_rust_library, emit_rust_test,
patch_with_buildscript,
};
pub fn buckify_dep_node(node: &Node, ctx: &BuckalContext) -> Vec<Rule> {
let package = ctx.packages_map.get(&node.id).unwrap().to_owned();
let mut buck_rules: Vec<Rule> = Vec::new();
let manifest_dir = package.manifest_path.parent().unwrap().to_owned();
let lib_target = package
.targets
.iter()
.find(|t| {
t.kind.contains(&cargo_metadata::TargetKind::Lib)
|| t.kind.contains(&cargo_metadata::TargetKind::CDyLib)
|| t.kind.contains(&cargo_metadata::TargetKind::DyLib)
|| t.kind.contains(&cargo_metadata::TargetKind::RLib)
|| t.kind.contains(&cargo_metadata::TargetKind::StaticLib)
|| t.kind.contains(&cargo_metadata::TargetKind::ProcMacro)
})
.expect("No library target found");
let package_id_spec =
PackageIdSpec::parse(&package.id.repr).unwrap_or_exit_ctx("failed to parse package ID");
match package_id_spec.kind().unwrap() {
SourceKind::Registry => {
let http_archive = emit_http_archive(&package, ctx);
buck_rules.push(Rule::HttpArchive(http_archive));
}
SourceKind::Path => {
buckal_error!(
"Local path ({}) is not supported for third-party packages.",
package_id_spec.url().unwrap().path()
);
buckal_note!(
"Please consider importing `{}` with registry or git source instead, or if it's a local package, move it to the workspace and it will be treated as a root package.",
package.name
);
std::process::exit(1);
}
SourceKind::Git(_) => {
let git_fetch = emit_git_fetch(&package);
buck_rules.push(Rule::GitFetch(git_fetch));
}
_ => {
buckal_error!("Unsupported source type for package `{}`.", package.name);
buckal_note!("Only registry and git sources are supported for third-party packages.");
std::process::exit(1);
}
}
let cargo_manifest = emit_cargo_manifest(&package, ctx);
buck_rules.push(Rule::CargoManifest(cargo_manifest));
let rust_library = emit_rust_library(
&package,
node,
lib_target,
&manifest_dir,
&package.name,
ctx,
);
buck_rules.push(Rule::RustLibrary(rust_library));
let custom_build_target = package
.targets
.iter()
.find(|t| t.kind.contains(&cargo_metadata::TargetKind::CustomBuild));
if let Some(build_target) = custom_build_target {
for rule in &mut buck_rules {
if let Some(rust_rule) = rule.as_rust_rule_mut() {
patch_with_buildscript(rust_rule, build_target);
}
}
let buildscript_build =
emit_buildscript_build(build_target, &package, node, &manifest_dir, ctx);
buck_rules.push(Rule::RustBinary(buildscript_build));
let buildscript_run = emit_buildscript_run(&package, node, build_target, ctx);
buck_rules.push(Rule::BuildscriptRun(buildscript_run));
}
buck_rules
}
pub fn buckify_root_node(node: &Node, ctx: &BuckalContext) -> Vec<Rule> {
let package = ctx.packages_map.get(&node.id).unwrap().to_owned();
let bin_targets = package
.targets
.iter()
.filter(|t| t.kind.contains(&cargo_metadata::TargetKind::Bin))
.collect::<Vec<_>>();
let lib_targets = package
.targets
.iter()
.filter(|t| {
t.kind.contains(&cargo_metadata::TargetKind::Lib)
|| t.kind.contains(&cargo_metadata::TargetKind::CDyLib)
|| t.kind.contains(&cargo_metadata::TargetKind::DyLib)
|| t.kind.contains(&cargo_metadata::TargetKind::RLib)
|| t.kind.contains(&cargo_metadata::TargetKind::StaticLib)
|| t.kind.contains(&cargo_metadata::TargetKind::ProcMacro)
})
.collect::<Vec<_>>();
let test_targets = package
.targets
.iter()
.filter(|t| t.kind.contains(&cargo_metadata::TargetKind::Test))
.collect::<Vec<_>>();
let mut buck_rules: Vec<Rule> = Vec::new();
let manifest_dir = package.manifest_path.parent().unwrap().to_owned();
let lib_buck_name: Option<String> = lib_targets.first().map(|lib_target| {
if bin_targets.iter().any(|b| b.name == lib_target.name) {
format!("{}-lib", lib_target.name)
} else {
lib_target.name.to_owned()
}
});
let filegroup = emit_filegroup();
buck_rules.push(Rule::FileGroup(filegroup));
let cargo_manifest = emit_cargo_manifest(&package, ctx);
buck_rules.push(Rule::CargoManifest(cargo_manifest));
for bin_target in &bin_targets {
let buckal_name = bin_target.name.to_owned();
let mut rust_binary =
emit_rust_binary(&package, node, bin_target, &manifest_dir, &buckal_name, ctx);
if let Some(lib_name) = &lib_buck_name {
rust_binary.deps_mut().insert(format!(":{lib_name}"));
}
buck_rules.push(Rule::RustBinary(rust_binary));
}
for lib_target in &lib_targets {
let buckal_name = if bin_targets.iter().any(|b| b.name == lib_target.name) {
format!("{}-lib", lib_target.name)
} else {
lib_target.name.to_owned()
};
let rust_library =
emit_rust_library(&package, node, lib_target, &manifest_dir, &buckal_name, ctx);
buck_rules.push(Rule::RustLibrary(rust_library));
if !ctx.repo_config.ignore_tests && lib_target.test {
let rust_test =
emit_rust_test(&package, node, lib_target, &manifest_dir, "unittest", ctx);
buck_rules.push(Rule::RustTest(rust_test));
}
}
if !ctx.repo_config.ignore_tests {
for test_target in &test_targets {
let buckal_name = test_target.name.to_owned();
let mut rust_test = emit_rust_test(
&package,
node,
test_target,
&manifest_dir,
&buckal_name,
ctx,
);
let package_name = package.name.replace("-", "_");
let mut lib_alias = false;
if bin_targets.iter().any(|b| b.name == package_name) {
lib_alias = true;
rust_test.env_mut().insert(
format!("CARGO_BIN_EXE_{}", package_name),
format!("$(location :{})", package_name),
);
}
if lib_targets.iter().any(|l| l.name == package_name) {
if lib_alias {
rust_test
.deps_mut()
.insert(format!(":{}-lib", package_name));
} else {
rust_test.deps_mut().insert(format!(":{}", package_name));
}
}
buck_rules.push(Rule::RustTest(rust_test));
}
}
let custom_build_target = package
.targets
.iter()
.find(|t| t.kind.contains(&cargo_metadata::TargetKind::CustomBuild));
if let Some(build_target) = custom_build_target {
for rule in &mut buck_rules {
if let Some(rust_rule) = rule.as_rust_rule_mut() {
patch_with_buildscript(rust_rule, build_target);
}
}
let buildscript_build =
emit_buildscript_build(build_target, &package, node, &manifest_dir, ctx);
buck_rules.push(Rule::RustBinary(buildscript_build));
let buildscript_run = emit_buildscript_run(&package, node, build_target, ctx);
buck_rules.push(Rule::BuildscriptRun(buildscript_run));
}
buck_rules
}
pub fn vendor_package(package: &Package) -> Utf8PathBuf {
let vendor_dir =
get_vendor_dir(&package.id).unwrap_or_exit_ctx("failed to get vendor directory");
if !vendor_dir.exists() {
std::fs::create_dir_all(&vendor_dir).expect("Failed to create target directory");
}
vendor_dir
}
pub fn gen_buck_content(rules: &[Rule]) -> String {
let mut has_cargo_manifest = false;
let mut has_rust_library = false;
let mut has_rust_binary = false;
let mut has_rust_test = false;
let mut has_buildscript_run = false;
for rule in rules {
match rule {
Rule::CargoManifest(_) => has_cargo_manifest = true,
Rule::RustLibrary(_) => has_rust_library = true,
Rule::RustBinary(_) => has_rust_binary = true,
Rule::RustTest(_) => has_rust_test = true,
Rule::BuildscriptRun(_) => has_buildscript_run = true,
_ => {}
}
}
let mut loads: Vec<Rule> = vec![];
if has_cargo_manifest {
loads.push(Rule::Load(Load {
bzl: "@buckal//:cargo_manifest.bzl".to_owned(),
items: Set::from(["cargo_manifest".to_owned()]),
}));
}
let mut wrapper_items: Set<String> = Set::new();
if has_rust_library {
wrapper_items.insert("rust_library".to_owned());
}
if has_rust_binary {
wrapper_items.insert("rust_binary".to_owned());
}
if has_rust_test {
wrapper_items.insert("rust_test".to_owned());
}
if has_buildscript_run {
wrapper_items.insert("buildscript_run".to_owned());
}
if !wrapper_items.is_empty() {
loads.push(Rule::Load(Load {
bzl: "@buckal//:wrapper.bzl".to_owned(),
items: wrapper_items,
}));
}
let mut content = rules
.iter()
.map(serde_starlark::to_string)
.map(|r| r.unwrap())
.join("\n");
if !loads.is_empty() {
let loads_string = loads
.iter()
.map(serde_starlark::to_string)
.map(|r| r.unwrap())
.join("");
content.insert(0, '\n');
content.insert_str(0, &loads_string);
}
content.insert_str(0, "# @generated by `cargo buckal`\n\n");
content
}