1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! The cli entrypoint for the `splice` subcommand

use std::path::PathBuf;

use anyhow::Context;
use clap::Parser;

use crate::cli::Result;
use crate::config::Config;
use crate::metadata::{
    write_metadata, Cargo, CargoUpdateRequest, Generator, MetadataGenerator, TreeResolver,
};
use crate::splicing::{generate_lockfile, Splicer, SplicingManifest, WorkspaceMetadata};

/// Command line options for the `splice` subcommand
#[derive(Parser, Debug)]
#[clap(about = "Command line options for the `splice` subcommand", version)]
pub struct SpliceOptions {
    /// A generated manifest of splicing inputs
    #[clap(long)]
    pub splicing_manifest: PathBuf,

    /// The path to a [Cargo.lock](https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html) file.
    #[clap(long)]
    pub cargo_lockfile: Option<PathBuf>,

    /// The desired update/repin behavior
    #[clap(long, env = "CARGO_BAZEL_REPIN", num_args=0..=1, default_missing_value = "true")]
    pub repin: Option<CargoUpdateRequest>,

    /// The directory in which to build the workspace. If this argument is not
    /// passed, a temporary directory will be generated.
    #[clap(long)]
    pub workspace_dir: Option<PathBuf>,

    /// The location where the results of splicing are written.
    #[clap(long)]
    pub output_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 the config file (containing [crate::config::Config].)
    #[clap(long)]
    pub config: 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)
        .context("Failed to parse splicing manifest")?;

    // Determine the splicing workspace
    let temp_dir;
    let splicing_dir = match &opt.workspace_dir {
        Some(dir) => dir.clone(),
        None => {
            temp_dir = tempfile::tempdir().context("Failed to generate temporary directory")?;
            temp_dir.as_ref().to_path_buf()
        }
    };

    // Generate a splicer for creating a Cargo workspace manifest
    let splicer = Splicer::new(splicing_dir, splicing_manifest)?;

    // Splice together the manifest
    let manifest_path = splicer
        .splice_workspace(&opt.cargo)
        .context("Failed to splice workspace")?;

    let cargo = Cargo::new(opt.cargo);

    // Generate a lockfile
    let cargo_lockfile = generate_lockfile(
        &manifest_path,
        &opt.cargo_lockfile,
        cargo.clone(),
        &opt.rustc,
        &opt.repin,
    )
    .context("Failed to generate lockfile")?;

    let config = Config::try_from_path(&opt.config).context("Failed to parse config")?;

    let resolver_data = TreeResolver::new(cargo.clone(), opt.rustc.clone())
        .generate(
            manifest_path.as_path_buf(),
            &config.supported_platform_triples,
        )
        .context("Failed to generate features")?;
    // Write the registry url info to the manifest now that a lockfile has been generated
    WorkspaceMetadata::write_registry_urls_and_feature_map(
        &cargo,
        &cargo_lockfile,
        resolver_data,
        manifest_path.as_path_buf(),
        manifest_path.as_path_buf(),
    )
    .context("Failed to write registry URLs and feature map")?;

    let output_dir = opt.output_dir.clone();

    // Write metadata to the workspace for future reuse
    let (cargo_metadata, _) = Generator::new()
        .with_cargo(cargo)
        .with_rustc(opt.rustc)
        .generate(manifest_path.as_path_buf())
        .context("Failed to generate cargo metadata")?;

    let cargo_lockfile_path = manifest_path
        .as_path_buf()
        .parent()
        .with_context(|| {
            format!(
                "The path {} is expected to have a parent directory",
                manifest_path.as_path_buf().display()
            )
        })?
        .join("Cargo.lock");

    // Generate the consumable outputs of the splicing process
    std::fs::create_dir_all(&output_dir)
        .with_context(|| format!("Failed to create directories for {}", &output_dir.display()))?;

    write_metadata(&opt.output_dir.join("metadata.json"), &cargo_metadata)
        .context("Failed to write metadata")?;

    std::fs::copy(cargo_lockfile_path, output_dir.join("Cargo.lock"))
        .context("Failed to copy lockfile")?;

    Ok(())
}