1 //! A helper tool for generating urls and sha256 checksums of cargo-bazel binaries and writing them to a module.
2
3 use std::collections::BTreeMap;
4 use std::io::{BufRead, BufReader};
5 use std::path::{Path, PathBuf};
6 use std::process::Command;
7 use std::{env, fs};
8
9 use clap::Parser;
10 use hex::ToHex;
11 use sha2::{Digest, Sha256};
12
13 #[derive(Parser, Debug)]
14 struct Options {
15 /// The path to an artifacts directory expecting to contain directories
16 /// named after platform tripes with binaries inside.
17 #[clap(long)]
18 pub(crate) artifacts_dir: PathBuf,
19
20 /// A url prefix where the artifacts can be found
21 #[clap(long)]
22 pub(crate) url_prefix: String,
23
24 /// The path to a buildifier binary. If set, it will be ran on the module
25 #[clap(long)]
26 pub(crate) buildifier: Option<PathBuf>,
27 }
28
29 struct Artifact {
30 pub(crate) url: String,
31 pub(crate) triple: String,
32 pub(crate) sha256: String,
33 }
34
calculate_sha256(file_path: &Path) -> String35 fn calculate_sha256(file_path: &Path) -> String {
36 let file = fs::File::open(file_path).unwrap();
37 let mut reader = BufReader::new(file);
38 let mut hasher = Sha256::new();
39
40 loop {
41 let consummed = {
42 let buffer = reader.fill_buf().unwrap();
43 if buffer.is_empty() {
44 break;
45 }
46 hasher.update(buffer);
47 buffer.len()
48 };
49 reader.consume(consummed);
50 }
51
52 let digest = hasher.finalize();
53 digest.encode_hex::<String>()
54 }
55
locate_artifacts(artifacts_dir: &Path, url_prefix: &str) -> Vec<Artifact>56 fn locate_artifacts(artifacts_dir: &Path, url_prefix: &str) -> Vec<Artifact> {
57 let artifact_dirs: Vec<PathBuf> = artifacts_dir
58 .read_dir()
59 .unwrap()
60 .flatten()
61 .filter(|entry| entry.path().is_dir())
62 .map(|entry| entry.path())
63 .collect();
64
65 artifact_dirs
66 .iter()
67 .map(|path| {
68 let triple = path.file_name().unwrap().to_string_lossy();
69 let mut artifacts: Vec<Artifact> = path
70 .read_dir()
71 .unwrap()
72 .flatten()
73 .map(|f_entry| {
74 let f_path = f_entry.path();
75 let stem = f_path.file_stem().unwrap().to_string_lossy();
76 let extension = f_path
77 .extension()
78 .map(|ext| format!(".{}", ext.to_string_lossy()))
79 .unwrap_or_default();
80 Artifact {
81 url: format!("{url_prefix}/{stem}-{triple}{extension}"),
82 triple: triple.to_string(),
83 sha256: calculate_sha256(&f_entry.path()),
84 }
85 })
86 .collect();
87 if artifacts.len() > 1 {
88 panic!("Too many artifacts given for {}", triple)
89 }
90 artifacts.pop().unwrap()
91 })
92 .collect()
93 }
94
95 const TEMPLATE: &str = r#""""A file containing urls and associated sha256 values for cargo-bazel binaries
96
97 This file is auto-generated for each release to match the urls and sha256s of
98 the binaries produced for it.
99 """
100
101 # Example:
102 # {
103 # "x86_64-unknown-linux-gnu": "https://domain.com/downloads/cargo-bazel-x86_64-unknown-linux-gnu",
104 # "x86_64-apple-darwin": "https://domain.com/downloads/cargo-bazel-x86_64-apple-darwin",
105 # "x86_64-pc-windows-msvc": "https://domain.com/downloads/cargo-bazel-x86_64-pc-windows-msvc",
106 # }
107 CARGO_BAZEL_URLS = {}
108
109 # Example:
110 # {
111 # "x86_64-unknown-linux-gnu": "1d687fcc860dc8a1aa6198e531f0aee0637ed506d6a412fe2b9884ff5b2b17c0",
112 # "x86_64-apple-darwin": "0363e450125002f581d29cf632cc876225d738cfa433afa85ca557afb671eafa",
113 # "x86_64-pc-windows-msvc": "f5647261d989f63dafb2c3cb8e131b225338a790386c06cf7112e43dd9805882",
114 # }
115 CARGO_BAZEL_SHA256S = {}
116
117 # Example:
118 # Label("//crate_universe:cargo_bazel_bin")
119 CARGO_BAZEL_LABEL = Label("@cargo_bazel_bootstrap//:binary")
120 "#;
121
render_module(artifacts: &[Artifact]) -> String122 fn render_module(artifacts: &[Artifact]) -> String {
123 let urls: BTreeMap<&String, &String> = artifacts
124 .iter()
125 .map(|artifact| (&artifact.triple, &artifact.url))
126 .collect();
127
128 let sha256s: BTreeMap<&String, &String> = artifacts
129 .iter()
130 .map(|artifact| (&artifact.triple, &artifact.sha256))
131 .collect();
132
133 TEMPLATE
134 .replace(
135 "CARGO_BAZEL_URLS = {}",
136 &format!(
137 "CARGO_BAZEL_URLS = {}",
138 serde_json::to_string_pretty(&urls).unwrap()
139 ),
140 )
141 .replace(
142 "CARGO_BAZEL_SHA256S = {}",
143 &format!(
144 "CARGO_BAZEL_SHA256S = {}",
145 serde_json::to_string_pretty(&sha256s).unwrap()
146 ),
147 )
148 .replace(
149 "CARGO_BAZEL_LABEL = Label(\"@cargo_bazel_bootstrap//:binary\")",
150 "CARGO_BAZEL_LABEL = Label(\"//crate_universe:cargo_bazel_bin\")",
151 )
152 }
153
write_module(content: &str) -> PathBuf154 fn write_module(content: &str) -> PathBuf {
155 let dest = PathBuf::from(
156 env::var("BUILD_WORKSPACE_DIRECTORY").expect("This binary is required to run under Bazel"),
157 )
158 .join(env!("MODULE_ROOT_PATH"));
159
160 fs::write(&dest, content).unwrap();
161
162 dest
163 }
164
run_buildifier(buildifier_path: &Path, module: &Path)165 fn run_buildifier(buildifier_path: &Path, module: &Path) {
166 Command::new(buildifier_path)
167 .arg("-lint=fix")
168 .arg("-mode=fix")
169 .arg("-warnings=all")
170 .arg(module)
171 .output()
172 .unwrap();
173 }
174
main()175 fn main() {
176 let opt = Options::parse();
177
178 let artifacts = locate_artifacts(&opt.artifacts_dir, &opt.url_prefix);
179
180 let content = render_module(&artifacts);
181
182 let path = write_module(&content);
183
184 if let Some(buildifier_path) = opt.buildifier {
185 run_buildifier(&buildifier_path, &path);
186 }
187 }
188