Skip to content

Commit

Permalink
[spr] initial version
Browse files Browse the repository at this point in the history
Created using spr 1.3.6-beta.1
  • Loading branch information
sunshowers committed Dec 18, 2024
1 parent 4a4d6ca commit 76319be
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 113 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ reqwest = { workspace = true, features = [ "rustls-tls" ] }
ring.workspace = true
semver.workspace = true
serde.workspace = true
shell-words.workspace = true
sled-hardware.workspace = true
slog.workspace = true
slog-async.workspace = true
Expand Down
174 changes: 61 additions & 113 deletions package/src/bin/omicron-package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

//! Utility for bundling target binaries as tarfiles.
use anyhow::{anyhow, bail, ensure, Context, Result};
use anyhow::{anyhow, bail, Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
use clap::{Parser, Subcommand};
use futures::stream::{self, StreamExt, TryStreamExt};
use illumos_utils::{zfs, zone};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use omicron_package::cargo_plan::build_cargo_plan;
use omicron_package::target::KnownTarget;
use omicron_package::{parse, BuildCommand, DeployCommand, TargetCommand};
use omicron_zone_package::config::{Config as PackageConfig, PackageMap};
Expand All @@ -24,7 +25,7 @@ use slog::o;
use slog::Drain;
use slog::Logger;
use slog::{info, warn};
use std::collections::{BTreeMap, BTreeSet};
use std::collections::BTreeMap;
use std::env;
use std::fs::create_dir_all;
use std::io::Write;
Expand Down Expand Up @@ -105,128 +106,59 @@ struct Args {
subcommand: SubCommand,
}

#[derive(Debug, Default)]
struct CargoPlan<'a> {
command: &'a str,
packages: BTreeSet<&'a String>,
bins: BTreeSet<&'a String>,
features: BTreeSet<&'a String>,
release: bool,
}
async fn do_show_cargo_commands(config: &Config) -> Result<()> {
let metadata = cargo_metadata::MetadataCommand::new().no_deps().exec()?;
let features = config.cargo_features();
let cargo_plan =
build_cargo_plan(&metadata, config.packages_to_build(), &features)?;

impl<'a> CargoPlan<'a> {
async fn run(&self, log: &Logger) -> Result<()> {
if self.bins.is_empty() {
return Ok(());
}
let release_command = cargo_plan.release.build_command("<command>", true);
let debug_command = cargo_plan.debug.build_command("<command>", true);

let mut cmd = Command::new("cargo");
// We rely on the rust-toolchain.toml file for toolchain information,
// rather than specifying one within the packaging tool.
cmd.arg(self.command);
// We specify _both_ --package and --bin; --bin does not imply
// --package, and without any --package options Cargo unifies features
// across all workspace default members. See rust-lang/cargo#8157.
for package in &self.packages {
cmd.arg("--package").arg(package);
}
for bin in &self.bins {
cmd.arg("--bin").arg(bin);
}
if !self.features.is_empty() {
cmd.arg("--features").arg(self.features.iter().fold(
String::new(),
|mut acc, s| {
if !acc.is_empty() {
acc.push(' ');
}
acc.push_str(s);
acc
},
));
}
if self.release {
cmd.arg("--release");
}
info!(log, "running: {:?}", cmd.as_std());
let status = cmd
.status()
.await
.context(format!("Failed to run command: ({:?})", cmd))?;
if !status.success() {
bail!("Failed to build packages");
}
print!("release command: ");
if let Some(command) = release_command {
println!("{}", command_to_string(&command));
} else {
println!("(none)");
}

Ok(())
print!("debug command: ");
if let Some(command) = debug_command {
println!("{}", command_to_string(&command));
} else {
println!("(none)");
}

Ok(())
}

fn command_to_string(command: &Command) -> String {
// Use shell-words to join the command and arguments into a single string.
let mut v = vec![command
.as_std()
.get_program()
.to_str()
.expect("program is valid UTF-8")];
v.extend(
command
.as_std()
.get_args()
.map(|arg| arg.to_str().expect("argument is valid UTF-8")),
);

shell_words::join(&v)
}

async fn do_for_all_rust_packages(
config: &Config,
command: &str,
) -> Result<()> {
// Collect a map of all of the workspace packages
let workspace = cargo_metadata::MetadataCommand::new().no_deps().exec()?;
let workspace_pkgs = workspace
.packages
.into_iter()
.filter_map(|package| {
workspace
.workspace_members
.contains(&package.id)
.then_some((package.name.clone(), package))
})
.collect::<BTreeMap<_, _>>();
let metadata = cargo_metadata::MetadataCommand::new().no_deps().exec()?;
let features = config.cargo_features();
let cargo_plan =
build_cargo_plan(&metadata, config.packages_to_build(), &features)?;

// Generate a list of all features we might want to request
let features = config
.target
.0
.iter()
.map(|(name, value)| format!("{name}-{value}"))
.collect::<Vec<_>>();

// We split the packages to be built into "release" and "debug" lists
let mut release =
CargoPlan { command, release: true, ..Default::default() };
let mut debug = CargoPlan { command, release: false, ..Default::default() };

for (name, pkg) in config.packages_to_build().0 {
// If this is a Rust package, `name` (the map key) is the name of the
// corresponding Rust crate.
if let PackageSource::Local { rust: Some(rust_pkg), .. } = &pkg.source {
let plan = if rust_pkg.release { &mut release } else { &mut debug };
// Add the package name to the plan
plan.packages.insert(name);
// Get the package metadata
let metadata = workspace_pkgs.get(name).with_context(|| {
format!("package '{name}' is not a workspace package")
})?;
// Add the binaries we want to build to the plan
let bins = metadata
.targets
.iter()
.filter_map(|target| target.is_bin().then_some(&target.name))
.collect::<BTreeSet<_>>();
for bin in &rust_pkg.binary_names {
ensure!(
bins.contains(bin),
"bin target '{bin}' does not belong to package '{name}'"
);
plan.bins.insert(bin);
}
// Add all features we want to request to the plan
plan.features.extend(
features
.iter()
.filter(|feature| metadata.features.contains_key(*feature)),
);
}
}

release.run(&config.log).await?;
debug.run(&config.log).await?;
Ok(())
cargo_plan.run(command, &config.log).await
}

async fn do_check(config: &Config) -> Result<()> {
Expand Down Expand Up @@ -1051,6 +983,19 @@ impl Config {
}
filtered_packages
}

/// Return a list of all possible Cargo features that could be requested for
/// the packages being built.
///
/// Out of these, the features that actually get requested are determined by
/// which features are available for the list of packages being built.
fn cargo_features(&self) -> Vec<String> {
self.target
.0
.iter()
.map(|(name, value)| format!("{name}-{value}"))
.collect::<Vec<_>>()
}
}

#[tokio::main]
Expand Down Expand Up @@ -1142,6 +1087,9 @@ async fn main() -> Result<()> {
)
.await?;
}
SubCommand::Build(BuildCommand::ShowCargoCommands) => {
do_show_cargo_commands(&get_config()?).await?;
}
SubCommand::Build(BuildCommand::Check) => {
do_check(&get_config()?).await?
}
Expand Down
Loading

0 comments on commit 76319be

Please sign in to comment.