Squashed 'third_party/rules_rust/' content from commit bf59038ca
git-subtree-dir: third_party/rules_rust
git-subtree-split: bf59038cac11798cbaef9f3bf965bad8182b97fa
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I5a20e403203d670df467ea97dde9a4ac40339a8d
diff --git a/crate_universe/src/cli/generate.rs b/crate_universe/src/cli/generate.rs
new file mode 100644
index 0000000..67ae868
--- /dev/null
+++ b/crate_universe/src/cli/generate.rs
@@ -0,0 +1,142 @@
+//! The cli entrypoint for the `generate` subcommand
+
+use std::path::PathBuf;
+
+use anyhow::{bail, Result};
+use clap::Parser;
+
+use crate::config::Config;
+use crate::context::Context;
+use crate::lockfile::{is_cargo_lockfile, lock_context, write_lockfile, LockfileKind};
+use crate::metadata::load_metadata;
+use crate::metadata::Annotations;
+use crate::rendering::{write_outputs, Renderer};
+use crate::splicing::SplicingManifest;
+
+/// Command line options for the `generate` subcommand
+#[derive(Parser, Debug)]
+#[clap(about, version)]
+pub struct GenerateOptions {
+ /// The path to a Cargo binary to use for gathering metadata
+ #[clap(long, env = "CARGO")]
+ pub cargo: Option<PathBuf>,
+
+ /// The path to a rustc binary for use with Cargo
+ #[clap(long, env = "RUSTC")]
+ pub rustc: Option<PathBuf>,
+
+ /// The config file with information about the Bazel and Cargo workspace
+ #[clap(long)]
+ pub config: PathBuf,
+
+ /// A generated manifest of splicing inputs
+ #[clap(long)]
+ pub splicing_manifest: PathBuf,
+
+ /// The path to either a Cargo or Bazel lockfile
+ #[clap(long)]
+ pub lockfile: PathBuf,
+
+ /// The type of lockfile
+ #[clap(long)]
+ pub lockfile_kind: LockfileKind,
+
+ /// The directory of the current repository rule
+ #[clap(long)]
+ pub repository_dir: PathBuf,
+
+ /// A [Cargo config](https://doc.rust-lang.org/cargo/reference/config.html#configuration)
+ /// file to use when gathering metadata
+ #[clap(long)]
+ pub cargo_config: Option<PathBuf>,
+
+ /// Whether or not to ignore the provided lockfile and re-generate one
+ #[clap(long)]
+ pub repin: bool,
+
+ /// The path to a Cargo metadata `json` file.
+ #[clap(long)]
+ pub metadata: Option<PathBuf>,
+
+ /// If true, outputs will be printed instead of written to disk.
+ #[clap(long)]
+ pub dry_run: bool,
+}
+
+pub fn generate(opt: GenerateOptions) -> Result<()> {
+ // Load the config
+ let config = Config::try_from_path(&opt.config)?;
+
+ // Determine if the dependencies need to be repinned.
+ let mut should_repin = opt.repin;
+
+ // Cargo lockfiles must always be repinned.
+ if is_cargo_lockfile(&opt.lockfile, &opt.lockfile_kind) {
+ should_repin = true;
+ }
+
+ // Go straight to rendering if there is no need to repin
+ if !should_repin {
+ let context = Context::try_from_path(opt.lockfile)?;
+
+ // Render build files
+ let outputs = Renderer::new(config.rendering).render(&context)?;
+
+ // Write the outputs to disk
+ write_outputs(outputs, &opt.repository_dir, opt.dry_run)?;
+
+ return Ok(());
+ }
+
+ // Ensure Cargo and Rustc are available for use during generation.
+ let cargo_bin = match &opt.cargo {
+ Some(bin) => bin,
+ None => bail!("The `--cargo` argument is required when generating unpinned content"),
+ };
+ let rustc_bin = match &opt.rustc {
+ Some(bin) => bin,
+ None => bail!("The `--rustc` argument is required when generating unpinned content"),
+ };
+
+ // Ensure a path to a metadata file was provided
+ let metadata_path = match &opt.metadata {
+ Some(path) => path,
+ None => bail!("The `--metadata` argument is required when generating unpinned content"),
+ };
+
+ // Load Metadata and Lockfile
+ let (cargo_metadata, cargo_lockfile) = load_metadata(
+ metadata_path,
+ if is_cargo_lockfile(&opt.lockfile, &opt.lockfile_kind) {
+ Some(&opt.lockfile)
+ } else {
+ None
+ },
+ )?;
+
+ // Copy the rendering config for later use
+ let render_config = config.rendering.clone();
+
+ // Annotate metadata
+ let annotations = Annotations::new(cargo_metadata, cargo_lockfile, config.clone())?;
+
+ // Generate renderable contexts for earch package
+ let context = Context::new(annotations)?;
+
+ // Render build files
+ let outputs = Renderer::new(render_config).render(&context)?;
+
+ // Write outputs
+ write_outputs(outputs, &opt.repository_dir, opt.dry_run)?;
+
+ // Ensure Bazel lockfiles are written to disk so future generations can be short-circuted.
+ if matches!(opt.lockfile_kind, LockfileKind::Bazel) {
+ let splicing_manifest = SplicingManifest::try_from_path(&opt.splicing_manifest)?;
+
+ let lockfile = lock_context(context, &config, &splicing_manifest, cargo_bin, rustc_bin)?;
+
+ write_lockfile(lockfile, &opt.lockfile, opt.dry_run)?;
+ }
+
+ Ok(())
+}
diff --git a/crate_universe/src/cli/query.rs b/crate_universe/src/cli/query.rs
new file mode 100644
index 0000000..668f64f
--- /dev/null
+++ b/crate_universe/src/cli/query.rs
@@ -0,0 +1,87 @@
+//! The cli entrypoint for the `query` subcommand
+
+use std::fs;
+use std::path::PathBuf;
+
+use anyhow::Result;
+use clap::Parser;
+
+use crate::config::Config;
+use crate::context::Context;
+use crate::lockfile::Digest;
+use crate::splicing::SplicingManifest;
+
+/// Command line options for the `query` subcommand
+#[derive(Parser, Debug)]
+#[clap(about, version)]
+pub struct QueryOptions {
+ /// The lockfile path for reproducible Cargo->Bazel renderings
+ #[clap(long)]
+ pub lockfile: PathBuf,
+
+ /// The config file with information about the Bazel and Cargo workspace
+ #[clap(long)]
+ pub config: PathBuf,
+
+ /// A generated manifest of splicing inputs
+ #[clap(long)]
+ pub splicing_manifest: PathBuf,
+
+ /// The path to a Cargo binary to use for gathering metadata
+ #[clap(long, env = "CARGO")]
+ pub cargo: PathBuf,
+
+ /// The path to a rustc binary for use with Cargo
+ #[clap(long, env = "RUSTC")]
+ pub rustc: PathBuf,
+}
+
+/// Determine if the current lockfile needs to be re-pinned
+pub fn query(opt: QueryOptions) -> Result<()> {
+ // Read the lockfile
+ let content = match fs::read_to_string(&opt.lockfile) {
+ Ok(c) => c,
+ Err(_) => return announce_repin("Unable to read lockfile"),
+ };
+
+ // Deserialize it so we can easily compare it with
+ let lockfile: Context = match serde_json::from_str(&content) {
+ Ok(ctx) => ctx,
+ Err(_) => return announce_repin("Could not load lockfile"),
+ };
+
+ // Check to see if a digest has been set
+ let digest = match &lockfile.checksum {
+ Some(d) => d.clone(),
+ None => return announce_repin("No digest provided in lockfile"),
+ };
+
+ // Load the config file
+ let config = Config::try_from_path(&opt.config)?;
+
+ let splicing_manifest = SplicingManifest::try_from_path(&opt.splicing_manifest)?;
+
+ // Generate a new digest so we can compare it with the one in the lockfile
+ let expected = Digest::new(
+ &lockfile,
+ &config,
+ &splicing_manifest,
+ &opt.cargo,
+ &opt.rustc,
+ )?;
+ if digest != expected {
+ return announce_repin(&format!(
+ "Digests do not match: {:?} != {:?}",
+ digest, expected
+ ));
+ }
+
+ // There is no need to repin
+ Ok(())
+}
+
+fn announce_repin(reason: &str) -> Result<()> {
+ eprintln!("{}", reason);
+ println!("repin");
+ Ok(())
+}
diff --git a/crate_universe/src/cli/splice.rs b/crate_universe/src/cli/splice.rs
new file mode 100644
index 0000000..cb8ba20
--- /dev/null
+++ b/crate_universe/src/cli/splice.rs
@@ -0,0 +1,90 @@
+//! The cli entrypoint for the `splice` subcommand
+
+use std::path::PathBuf;
+
+use clap::Parser;
+
+use crate::cli::Result;
+use crate::metadata::{write_metadata, Generator, MetadataGenerator};
+use crate::splicing::{
+ generate_lockfile, ExtraManifestsManifest, Splicer, SplicingManifest, WorkspaceMetadata,
+};
+
+/// Command line options for the `splice` subcommand
+#[derive(Parser, Debug)]
+#[clap(about, version)]
+pub struct SpliceOptions {
+ /// A generated manifest of splicing inputs
+ #[clap(long)]
+ pub splicing_manifest: PathBuf,
+
+ /// A generated manifest of "extra workspace members"
+ #[clap(long)]
+ pub extra_manifests_manifest: PathBuf,
+
+ /// A Cargo lockfile (Cargo.lock).
+ #[clap(long)]
+ pub cargo_lockfile: Option<PathBuf>,
+
+ /// The directory in which to build the workspace. A `Cargo.toml` file
+ /// should always be produced within this directory.
+ #[clap(long)]
+ pub workspace_dir: PathBuf,
+
+ /// If true, outputs will be printed instead of written to disk.
+ #[clap(long)]
+ pub dry_run: bool,
+
+ /// The path to a Cargo configuration file.
+ #[clap(long)]
+ pub cargo_config: Option<PathBuf>,
+
+ /// The path to a Cargo binary to use for gathering metadata
+ #[clap(long, env = "CARGO")]
+ pub cargo: PathBuf,
+
+ /// The path to a rustc binary for use with Cargo
+ #[clap(long, env = "RUSTC")]
+ pub rustc: PathBuf,
+}
+
+/// Combine a set of disjoint manifests into a single workspace.
+pub fn splice(opt: SpliceOptions) -> Result<()> {
+ // Load the all config files required for splicing a workspace
+ let splicing_manifest = SplicingManifest::try_from_path(&opt.splicing_manifest)?;
+ let extra_manifests_manifest =
+ ExtraManifestsManifest::try_from_path(opt.extra_manifests_manifest)?;
+
+ // Generate a splicer for creating a Cargo workspace manifest
+ let splicer = Splicer::new(
+ opt.workspace_dir,
+ splicing_manifest,
+ extra_manifests_manifest,
+ )?;
+
+ // Splice together the manifest
+ let manifest_path = splicer.splice_workspace()?;
+
+ // Generate a lockfile
+ let cargo_lockfile =
+ generate_lockfile(&manifest_path, &opt.cargo_lockfile, &opt.cargo, &opt.rustc)?;
+
+ // Write the registry url info to the manifest now that a lockfile has been generated
+ WorkspaceMetadata::write_registry_urls(&cargo_lockfile, &manifest_path)?;
+
+ // Write metadata to the workspace for future reuse
+ let (cargo_metadata, _) = Generator::new()
+ .with_cargo(opt.cargo)
+ .with_rustc(opt.rustc)
+ .generate(&manifest_path.as_path_buf())?;
+
+ // Write metadata next to the manifest
+ let metadata_path = manifest_path
+ .as_path_buf()
+ .parent()
+ .expect("Newly spliced cargo manifest has no parent directory")
+ .join("cargo-bazel-spliced-metadata.json");
+ write_metadata(&metadata_path, &cargo_metadata)?;
+
+ Ok(())
+}
diff --git a/crate_universe/src/cli/vendor.rs b/crate_universe/src/cli/vendor.rs
new file mode 100644
index 0000000..68e107f
--- /dev/null
+++ b/crate_universe/src/cli/vendor.rs
@@ -0,0 +1,167 @@
+//! The cli entrypoint for the `vendor` subcommand
+
+use std::collections::BTreeSet;
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::process::{self, ExitStatus};
+
+use anyhow::{bail, Context as AnyhowContext, Result};
+use clap::Parser;
+
+use crate::config::{Config, VendorMode};
+use crate::context::Context;
+use crate::metadata::{Annotations, VendorGenerator};
+use crate::metadata::{Generator, MetadataGenerator};
+use crate::rendering::{render_module_label, write_outputs, Renderer};
+use crate::splicing::{
+ generate_lockfile, ExtraManifestsManifest, Splicer, SplicingManifest, WorkspaceMetadata,
+};
+
+/// Command line options for the `vendor` subcommand
+#[derive(Parser, Debug)]
+#[clap(about, version)]
+pub struct VendorOptions {
+ /// The path to a Cargo binary to use for gathering metadata
+ #[clap(long, env = "CARGO")]
+ pub cargo: PathBuf,
+
+ /// The path to a rustc binary for use with Cargo
+ #[clap(long, env = "RUSTC")]
+ pub rustc: PathBuf,
+
+ /// The path to a buildifier binary for formatting generated BUILD files
+ #[clap(long)]
+ pub buildifier: Option<PathBuf>,
+
+ /// The config file with information about the Bazel and Cargo workspace
+ #[clap(long)]
+ pub config: PathBuf,
+
+ /// A generated manifest of splicing inputs
+ #[clap(long)]
+ pub splicing_manifest: PathBuf,
+
+ /// The path to a Cargo lockfile
+ #[clap(long)]
+ pub cargo_lockfile: Option<PathBuf>,
+
+ /// A [Cargo config](https://doc.rust-lang.org/cargo/reference/config.html#configuration)
+ /// file to use when gathering metadata
+ #[clap(long)]
+ pub cargo_config: Option<PathBuf>,
+
+ /// The path to a Cargo metadata `json` file.
+ #[clap(long)]
+ pub metadata: Option<PathBuf>,
+
+ /// A generated manifest of "extra workspace members"
+ #[clap(long)]
+ pub extra_manifests_manifest: PathBuf,
+
+ /// The directory in which to build the workspace. A `Cargo.toml` file
+ /// should always be produced within this directory.
+ #[clap(long, env = "BUILD_WORKSPACE_DIRECTORY")]
+ pub workspace_dir: PathBuf,
+
+ /// If true, outputs will be printed instead of written to disk.
+ #[clap(long)]
+ pub dry_run: bool,
+}
+
+/// Run buildifier on a given file.
+fn buildifier_format(bin: &Path, file: &Path) -> Result<ExitStatus> {
+ let status = process::Command::new(bin)
+ .args(["-lint=fix", "-mode=fix", "-warnings=all"])
+ .arg(file)
+ .status()
+ .context("Failed to apply buildifier fixes")?;
+
+ if !status.success() {
+ bail!(status)
+ }
+
+ Ok(status)
+}
+
+pub fn vendor(opt: VendorOptions) -> Result<()> {
+ // Load the all config files required for splicing a workspace
+ let splicing_manifest =
+ SplicingManifest::try_from_path(&opt.splicing_manifest)?.absoulutize(&opt.workspace_dir);
+ let extra_manifests_manifest =
+ ExtraManifestsManifest::try_from_path(opt.extra_manifests_manifest)?.absoulutize();
+
+ let temp_dir = tempfile::tempdir().context("Failed to create temporary directory")?;
+
+ // Generate a splicer for creating a Cargo workspace manifest
+ let splicer = Splicer::new(
+ PathBuf::from(temp_dir.as_ref()),
+ splicing_manifest,
+ extra_manifests_manifest,
+ )
+ .context("Failed to crate splicer")?;
+
+ // Splice together the manifest
+ let manifest_path = splicer
+ .splice_workspace()
+ .context("Failed to splice workspace")?;
+
+ // Generate a lockfile
+ let cargo_lockfile =
+ generate_lockfile(&manifest_path, &opt.cargo_lockfile, &opt.cargo, &opt.rustc)?;
+
+ // Write the registry url info to the manifest now that a lockfile has been generated
+ WorkspaceMetadata::write_registry_urls(&cargo_lockfile, &manifest_path)?;
+
+ // Write metadata to the workspace for future reuse
+ let (cargo_metadata, cargo_lockfile) = Generator::new()
+ .with_cargo(opt.cargo.clone())
+ .with_rustc(opt.rustc.clone())
+ .generate(&manifest_path.as_path_buf())?;
+
+ // Load the config from disk
+ let config = Config::try_from_path(&opt.config)?;
+
+ // Annotate metadata
+ let annotations = Annotations::new(cargo_metadata, cargo_lockfile, config.clone())?;
+
+ // Generate renderable contexts for earch package
+ let context = Context::new(annotations)?;
+
+ // Render build files
+ let outputs = Renderer::new(config.rendering.clone()).render(&context)?;
+
+ // Cache the file names for potential use with buildifier
+ let file_names: BTreeSet<PathBuf> = outputs.keys().cloned().collect();
+
+ // First ensure vendoring and rendering happen in a clean directory
+ let vendor_dir_label = render_module_label(&config.rendering.crates_module_template, "BUILD")?;
+ let vendor_dir = opt
+ .workspace_dir
+ .join(vendor_dir_label.package.unwrap_or_default());
+ if vendor_dir.exists() {
+ fs::remove_dir_all(&vendor_dir)
+ .with_context(|| format!("Failed to delete {}", vendor_dir.display()))?;
+ }
+
+ // Vendor the crates from the spliced workspace
+ if matches!(config.rendering.vendor_mode, Some(VendorMode::Local)) {
+ VendorGenerator::new(opt.cargo.clone(), opt.rustc.clone())
+ .generate(manifest_path.as_path_buf(), &vendor_dir)
+ .context("Failed to vendor dependencies")?;
+ }
+
+ // Write outputs
+ write_outputs(outputs, &opt.workspace_dir, opt.dry_run)
+ .context("Failed writing output files")?;
+
+ // Optionally apply buildifier fixes
+ if let Some(buildifier_bin) = opt.buildifier {
+ for file in file_names {
+ let file_path = opt.workspace_dir.join(file);
+ buildifier_format(&buildifier_bin, &file_path)
+ .with_context(|| format!("Failed to run buildifier on {}", file_path.display()))?;
+ }
+ }
+
+ Ok(())
+}