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(())
+}