extern crate anyhow;
extern crate cargo;
extern crate git2;
extern crate itertools;
extern crate lazy_static;
extern crate md5;
extern crate regex;
extern crate structopt;
use anyhow::{anyhow, Context as _};
use cargo::core::registry::PackageRegistry;
use cargo::core::resolver::features::HasDevUnits;
use cargo::core::resolver::CliFeatures;
use cargo::core::source::GitReference;
use cargo::core::{Package, PackageSet, Resolve, Workspace};
use cargo::ops;
use cargo::util::{important_paths, CargoResult};
use cargo::{CliResult, Config};
use itertools::Itertools;
use std::default::Default;
use std::env;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::{Path, PathBuf};
use structopt::clap::AppSettings;
use structopt::StructOpt;
mod git;
mod license;
const CRATES_IO_URL: &str = "crates.io";
struct PackageInfo<'cfg> {
cfg: &'cfg Config,
current_manifest: PathBuf,
ws: Workspace<'cfg>,
}
impl<'cfg> PackageInfo<'cfg> {
fn new(config: &Config, manifest_path: Option<String>) -> CargoResult<PackageInfo> {
let manifest_path = manifest_path.map_or_else(|| config.cwd().to_path_buf(), PathBuf::from);
let root = important_paths::find_root_manifest_for_wd(&manifest_path)?;
let ws = Workspace::new(&root, config)?;
Ok(PackageInfo {
cfg: config,
current_manifest: root,
ws,
})
}
fn package(&self) -> CargoResult<&Package> {
self.ws.current()
}
fn registry(&self) -> CargoResult<PackageRegistry<'cfg>> {
let mut registry = PackageRegistry::new(self.cfg)?;
let package = self.package()?;
registry.add_sources(vec![package.package_id().source_id()])?;
Ok(registry)
}
fn resolve(&self) -> CargoResult<(PackageSet<'cfg>, Resolve)> {
let mut registry = self.registry()?;
let (packages, resolve) = ops::resolve_ws(&self.ws)?;
let resolve = ops::resolve_with_previous(
&mut registry,
&self.ws,
&CliFeatures::new_all(true),
HasDevUnits::No,
Some(&resolve),
None,
&[],
true,
)?;
Ok((packages, resolve))
}
fn rel_dir(&self) -> CargoResult<PathBuf> {
let root = self.ws.root().to_path_buf();
let cwd = self.current_manifest.parent().ok_or_else(|| {
anyhow!(
"Could not get parent of directory '{}'",
self.current_manifest.display()
)
})?;
cwd.strip_prefix(&root)
.map(Path::to_path_buf)
.context("Unable to if Cargo.toml is in a sub directory")
}
}
#[derive(StructOpt, Debug)]
struct Args {
#[structopt(short = "q")]
quiet: bool,
#[structopt(short = "v", parse(from_occurrences))]
verbose: usize,
#[structopt(short = "R")]
reproducible: bool,
#[structopt(short = "l", long = "--legacy-overrides")]
legacy_overrides: bool,
}
#[derive(StructOpt, Debug)]
#[structopt(
name = "cargo-bitbake",
bin_name = "cargo",
author,
about = "Generates a BitBake recipe for a given Cargo project",
global_settings(&[AppSettings::ColoredHelp])
)]
enum Opt {
#[structopt(name = "bitbake")]
Bitbake(Args),
}
fn main() {
let mut config = Config::default().unwrap();
let Opt::Bitbake(opt) = Opt::from_args();
let result = real_main(opt, &mut config);
if let Err(e) = result {
cargo::exit_with_error(e, &mut *config.shell());
}
}
fn real_main(options: Args, config: &mut Config) -> CliResult {
config.configure(
options.verbose as u32,
options.quiet,
None,
false,
false,
false,
&None,
&[],
&[],
)?;
let md = PackageInfo::new(config, None)?;
let package = md.package()?;
let crate_root = package
.manifest_path()
.parent()
.expect("Cargo.toml must have a parent");
if package.name().contains('_') {
println!("Package name contains an underscore");
}
let resolve = md.resolve()?;
let mut src_uri_extras = vec![];
let mut src_uris = resolve
.1
.iter()
.filter_map(|pkg| {
let src_id = pkg.source_id();
if pkg.name() == package.name() {
None
} else if src_id.is_registry() {
Some(format!(
" crate://{}/{}/{} \\\n",
CRATES_IO_URL,
pkg.name(),
pkg.version()
))
} else if src_id.is_path() {
None
} else if src_id.is_git() {
let url = git::git_to_yocto_git_url(
src_id.url().as_str(),
Some(pkg.name().as_str()),
git::GitPrefix::default(),
);
src_uri_extras.push(format!("SRCREV_FORMAT .= \"_{}\"", pkg.name()));
let precise = if options.reproducible {
src_id.precise()
} else {
None
};
let rev = if let Some(precise) = precise {
precise
} else {
match *src_id.git_reference()? {
GitReference::Tag(ref s) => s,
GitReference::Rev(ref s) => {
if s.len() == 40 {
s
} else {
let precise = src_id.precise();
if let Some(p) = precise {
p
} else {
panic!("cannot find rev in correct format!");
}
}
}
GitReference::Branch(ref s) => {
if s == "master" {
"${AUTOREV}"
} else {
s
}
}
GitReference::DefaultBranch => "${AUTOREV}",
}
};
src_uri_extras.push(format!("SRCREV_{} = \"{}\"", pkg.name(), rev));
src_uri_extras.push(format!(
"EXTRA_OECARGO_PATHS += \"${{WORKDIR}}/{}\"",
pkg.name()
));
Some(format!(" {} \\\n", url))
} else {
Some(format!(" {} \\\n", src_id.url()))
}
})
.collect::<Vec<String>>();
src_uris.sort();
let metadata = package.manifest().metadata();
let summary = metadata.description.as_ref().map_or_else(
|| {
println!("No package.description set in your Cargo.toml, using package.name");
package.name()
},
|s| cargo::util::interning::InternedString::new(&s.trim().replace("\n", " \\\n")),
);
let homepage = metadata
.homepage
.as_ref()
.map_or_else(
|| {
println!("No package.homepage set in your Cargo.toml, trying package.repository");
metadata
.repository
.as_ref()
.ok_or_else(|| anyhow!("No package.repository set in your Cargo.toml"))
},
Ok,
)?
.trim();
let license = metadata.license.as_ref().map_or_else(
|| {
println!("No package.license set in your Cargo.toml, trying package.license_file");
metadata.license_file.as_ref().map_or_else(
|| {
println!("No package.license_file set in your Cargo.toml");
println!("Assuming {} license", license::CLOSED_LICENSE);
license::CLOSED_LICENSE
},
String::as_str,
)
},
String::as_str,
);
let rel_dir = md.rel_dir()?;
let mut lic_files = vec![];
let licenses: Vec<&str> = license.split('/').collect();
let single_license = licenses.len() == 1;
for lic in licenses {
lic_files.push(format!(
" {}",
license::file(crate_root, &rel_dir, lic, single_license)
));
}
let license = license.split('/').map(str::trim).join(" | ");
let project_repo = git::ProjectRepo::new(config).unwrap_or_else(|e| {
println!("{}", e);
Default::default()
});
let git_srcpv = if !project_repo.tag && project_repo.rev.len() > 10 {
let mut pv_append_key = "PV:append";
if options.legacy_overrides {
pv_append_key = "PV_append";
}
format!("{} = \".AUTOINC+{}\"", pv_append_key, &project_repo.rev[..10])
} else {
"".into()
};
let recipe_path = PathBuf::from(format!("{}_{}.bb", package.name(), package.version()));
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&recipe_path)
.map_err(|e| anyhow!("Unable to open bitbake recipe file with: {}", e))?;
write!(
file,
include_str!("bitbake.template"),
name = package.name(),
version = package.version(),
summary = summary,
homepage = homepage,
license = license,
lic_files = lic_files.join(""),
src_uri = src_uris.join(""),
src_uri_extras = src_uri_extras.join("\n"),
project_rel_dir = rel_dir.display(),
project_src_uri = project_repo.uri,
project_src_rev = project_repo.rev,
git_srcpv = git_srcpv,
cargo_bitbake_ver = env!("CARGO_PKG_VERSION"),
)
.map_err(|e| anyhow!("Unable to write to bitbake recipe file with: {}", e))?;
println!("Wrote: {}", recipe_path.display());
Ok(())
}