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