xref: /aosp_15_r20/external/bazelbuild-rules_rust/crate_universe/src/cli/generate.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1 //! The cli entrypoint for the `generate` subcommand
2 
3 use std::fs;
4 use std::path::{Path, PathBuf};
5 
6 use anyhow::{bail, Context as AnyhowContext, Result};
7 use cargo_lock::Lockfile;
8 use clap::Parser;
9 
10 use crate::config::Config;
11 use crate::context::Context;
12 use crate::lockfile::{lock_context, write_lockfile};
13 use crate::metadata::{load_metadata, Annotations, Cargo};
14 use crate::rendering::{write_outputs, Renderer};
15 use crate::splicing::SplicingManifest;
16 use crate::utils::normalize_cargo_file_paths;
17 
18 /// Command line options for the `generate` subcommand
19 #[derive(Parser, Debug)]
20 #[clap(about = "Command line options for the `generate` subcommand", version)]
21 pub struct GenerateOptions {
22     /// The path to a Cargo binary to use for gathering metadata
23     #[clap(long, env = "CARGO")]
24     pub cargo: Option<PathBuf>,
25 
26     /// The path to a rustc binary for use with Cargo
27     #[clap(long, env = "RUSTC")]
28     pub rustc: Option<PathBuf>,
29 
30     /// The config file with information about the Bazel and Cargo workspace
31     #[clap(long)]
32     pub config: PathBuf,
33 
34     /// A generated manifest of splicing inputs
35     #[clap(long)]
36     pub splicing_manifest: PathBuf,
37 
38     /// The path to either a Cargo or Bazel lockfile
39     #[clap(long)]
40     pub lockfile: Option<PathBuf>,
41 
42     /// The path to a [Cargo.lock](https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html) file.
43     #[clap(long)]
44     pub cargo_lockfile: PathBuf,
45 
46     /// The directory of the current repository rule
47     #[clap(long)]
48     pub repository_dir: PathBuf,
49 
50     /// A [Cargo config](https://doc.rust-lang.org/cargo/reference/config.html#configuration)
51     /// file to use when gathering metadata
52     #[clap(long)]
53     pub cargo_config: Option<PathBuf>,
54 
55     /// Whether or not to ignore the provided lockfile and re-generate one
56     #[clap(long)]
57     pub repin: bool,
58 
59     /// The path to a Cargo metadata `json` file. This file must be next to a `Cargo.toml` and `Cargo.lock` file.
60     #[clap(long)]
61     pub metadata: Option<PathBuf>,
62 
63     /// If true, outputs will be printed instead of written to disk.
64     #[clap(long)]
65     pub dry_run: bool,
66 }
67 
generate(opt: GenerateOptions) -> Result<()>68 pub fn generate(opt: GenerateOptions) -> Result<()> {
69     // Load the config
70     let config = Config::try_from_path(&opt.config)?;
71 
72     // Go straight to rendering if there is no need to repin
73     if !opt.repin {
74         if let Some(lockfile) = &opt.lockfile {
75             let context = Context::try_from_path(lockfile)?;
76 
77             // Render build files
78             let outputs = Renderer::new(config.rendering, config.supported_platform_triples)
79                 .render(&context)?;
80 
81             // make file paths compatible with bazel labels
82             let normalized_outputs = normalize_cargo_file_paths(outputs, &opt.repository_dir);
83 
84             // Write the outputs to disk
85             write_outputs(normalized_outputs, opt.dry_run)?;
86 
87             return Ok(());
88         }
89     }
90 
91     // Ensure Cargo and Rustc are available for use during generation.
92     let rustc_bin = match &opt.rustc {
93         Some(bin) => bin,
94         None => bail!("The `--rustc` argument is required when generating unpinned content"),
95     };
96 
97     let cargo_bin = Cargo::new(
98         match opt.cargo {
99             Some(bin) => bin,
100             None => bail!("The `--cargo` argument is required when generating unpinned content"),
101         },
102         rustc_bin.clone(),
103     );
104 
105     // Ensure a path to a metadata file was provided
106     let metadata_path = match &opt.metadata {
107         Some(path) => path,
108         None => bail!("The `--metadata` argument is required when generating unpinned content"),
109     };
110 
111     // Load Metadata and Lockfile
112     let (cargo_metadata, cargo_lockfile) = load_metadata(metadata_path)?;
113 
114     // Annotate metadata
115     let annotations = Annotations::new(cargo_metadata, cargo_lockfile.clone(), config.clone())?;
116 
117     // Generate renderable contexts for each package
118     let context = Context::new(annotations, config.rendering.are_sources_present())?;
119 
120     // Render build files
121     let outputs = Renderer::new(
122         config.rendering.clone(),
123         config.supported_platform_triples.clone(),
124     )
125     .render(&context)?;
126 
127     // make file paths compatible with bazel labels
128     let normalized_outputs = normalize_cargo_file_paths(outputs, &opt.repository_dir);
129 
130     // Write the outputs to disk
131     write_outputs(normalized_outputs, opt.dry_run)?;
132 
133     // Ensure Bazel lockfiles are written to disk so future generations can be short-circuited.
134     if let Some(lockfile) = opt.lockfile {
135         let splicing_manifest = SplicingManifest::try_from_path(&opt.splicing_manifest)?;
136 
137         let lock_content =
138             lock_context(context, &config, &splicing_manifest, &cargo_bin, rustc_bin)?;
139 
140         write_lockfile(lock_content, &lockfile, opt.dry_run)?;
141     }
142 
143     update_cargo_lockfile(&opt.cargo_lockfile, cargo_lockfile)?;
144 
145     Ok(())
146 }
147 
update_cargo_lockfile(path: &Path, cargo_lockfile: Lockfile) -> Result<()>148 fn update_cargo_lockfile(path: &Path, cargo_lockfile: Lockfile) -> Result<()> {
149     let old_contents = fs::read_to_string(path).ok();
150     let new_contents = cargo_lockfile.to_string();
151 
152     // Don't overwrite identical contents because timestamp changes may invalidate repo rules.
153     if old_contents.as_ref() == Some(&new_contents) {
154         return Ok(());
155     }
156 
157     fs::write(path, new_contents)
158         .context("Failed to write Cargo.lock file back to the workspace.")?;
159 
160     Ok(())
161 }
162