1mod audit;
12mod license;
13mod metadata;
14
15use anyhow::{format_err, Context, Result};
16use cargo_metadata::CargoOpt;
17use cargo_metadata::MetadataCommand;
18use std::collections::BTreeSet;
19use std::fs::OpenOptions;
20use std::path::Path;
21
22use audit::audit_package;
23use license::{normalize_license, split_spdx_license};
24use metadata::EbuildConfig;
25
26pub fn gen_ebuild_data( manifest_path: Option<&Path>
27 , package_name: Option<&str>
28 , audit: bool ) -> Result<EbuildConfig> {
29 let mut cmd = MetadataCommand::new();
30
31 cmd.features(CargoOpt::AllFeatures);
32
33 if let Some(path) = manifest_path {
34 cmd.manifest_path(path);
35 }
36
37 let metadata = cmd
38 .exec()
39 .map_err(|e| format_err!("cargo metadata failed: {}", e))?;
40
41 let resolve = metadata
42 .resolve
43 .as_ref()
44 .ok_or_else(|| format_err!("cargo metadata did not resolve the depend graph"))?;
45
46 let root =
47 if let Some(pkg_name) = package_name {
48 let found_package =
49 metadata.packages.iter().find(|&p| {
50 p.name == pkg_name
51 }).ok_or_else(|| format_err!("cargo metadata contains no specified package"))?;
52 &found_package.id
53 } else {
54 resolve
55 .root
56 .as_ref()
57 .ok_or_else(|| format_err!("cargo metadata failed to resolve the root package"))?
58 };
59
60 if audit {
61 audit_package(metadata.workspace_root.as_ref(), manifest_path)?;
62 }
63
64 let mut licenses = BTreeSet::new();
65 let mut crates = Vec::new();
66 let mut root_pkg = None;
67
68 for pkg in &metadata.packages {
69 if &pkg.id == root {
70 root_pkg = Some(pkg.clone());
71 }
72
73 if let Some(lic_list) = pkg.license.as_ref().map(|l| split_spdx_license(&l)) {
74 for lic in lic_list.iter() {
75 if let Some(norm) = normalize_license(&lic) {
76 licenses.insert(norm.to_string());
78 } else {
79 println!(
81 "WARNING: unknown license \"{}\" at package \"{}\", please correct manually",
82 &lic,
83 &pkg.name,
84 );
85 licenses.insert(lic.to_string());
86 }
87 }
88 }
89
90 if pkg.license_file.is_some() {
91 println!("WARNING: {} uses a license-file, not handled", pkg.name);
92 }
93
94 if let Some(src) = &pkg.source {
95 if src.is_crates_io() {
97 crates.push(format!("\t{}-{}\n", pkg.name, pkg.version));
98 }
99 }
100 }
101
102 let root_pkg = root_pkg
103 .ok_or_else(|| format_err!("unable to determine package to generate ebuild for"))?;
104
105 Ok(EbuildConfig::from_package(root_pkg, crates, licenses))
106}
107
108pub fn write_ebuild(
109 ebuild_data: EbuildConfig,
110 ebuild_path: &Path,
111 template_path: Option<&Path>,
112) -> Result<()> {
113 let mut file = OpenOptions::new()
115 .write(true)
116 .create(true)
117 .truncate(true)
118 .open(ebuild_path)
119 .context(format!(
120 "Unable to create {}",
121 ebuild_path.display()
122 ))?;
123
124 let mut tera = tera::Tera::default();
125 let mut context = tera::Context::from_serialize(ebuild_data)?;
126
127 tera.add_raw_template("base.tera", include_str!("base.tera"))?;
128 if let Some(template) = template_path {
129 tera.add_template_file(template, Some("ebuild.tera"))?;
130 } else {
131 tera.add_raw_template("ebuild.tera", include_str!("ebuild.tera"))?;
132 }
133
134 context.insert("cargo_ebuild_ver", env!("CARGO_PKG_VERSION"));
135 context.insert("this_year", &time::OffsetDateTime::now_utc().year());
136
137 tera.render_to("ebuild.tera", &context, &mut file)
138 .context(format!(
139 "Failed to write to {}",
140 ebuild_path.display()
141 ))
142}