1 // Copyright (C) 2024 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 use std::{
16 collections::{BTreeMap, BTreeSet},
17 path::PathBuf,
18 };
19
20 use anyhow::Result;
21 use clap::{Args, Parser, Subcommand};
22 use crate_tool::{default_repo_root, maybe_build_cargo_embargo, ManagedRepo};
23 use rooted_path::RootedPath;
24 use semver::Version;
25
26 #[derive(Parser)]
27 struct Cli {
28 #[command(subcommand)]
29 command: Cmd,
30
31 // The path to the Android source repo.
32 #[arg(long, default_value_os_t=default_repo_root().unwrap_or(PathBuf::from(".")))]
33 android_root: PathBuf,
34
35 // The path to the crate monorepo, relative to the Android source repo.
36 #[arg(long, default_value_os_t=PathBuf::from("external/rust/android-crates-io"))]
37 managed_repo_path: PathBuf,
38
39 /// Rebuild cargo_embargo and bpfmt, even if they are already present in the out directory.
40 #[arg(long, default_value_t = false)]
41 rebuild_cargo_embargo: bool,
42
43 /// Print command output in case of error, and full diffs.
44 #[arg(long, default_value_t = false)]
45 verbose: bool,
46
47 /// Don't make network requests to crates.io. Only check the local cache.
48 #[arg(long, default_value_t = false)]
49 offline: bool,
50 }
51
52 #[derive(Subcommand)]
53 enum Cmd {
54 /// Check the health of a crate, and whether it is safe to migrate.
55 MigrationHealth {
56 #[command(flatten)]
57 crates: MigrationCrateList,
58 },
59 /// Migrate crates from external/rust/crates to the monorepo.
60 Migrate {
61 #[command(flatten)]
62 crates: MigrationCrateList,
63 },
64 /// Analyze a crate to see if it can be imported.
65 #[command(hide = true)]
66 AnalyzeImport {
67 /// The crate name.
68 crate_name: String,
69 },
70 /// Import a crate and its dependencies into the monorepo.
71 #[command(hide = true)]
72 Import {
73 /// The crate name.
74 crate_name: String,
75
76 /// The crate version.
77 version: String,
78
79 /// Run "cargo_embargo autoconfig"
80 #[arg(long, default_value_t = false)]
81 autoconfig: bool,
82 },
83 /// Regenerate crates from vendored code by applying patches, running cargo_embargo, etc.
84 Regenerate {
85 #[command(flatten)]
86 crates: CrateList,
87 },
88 /// Run pre-upload checks.
89 PreuploadCheck {
90 /// List of changed files
91 files: Vec<String>,
92 },
93 /// Fix problems with license files.
94 FixLicenses {
95 #[command(flatten)]
96 crates: CrateList,
97 },
98 /// Fix up METADATA files.
99 FixMetadata {
100 #[command(flatten)]
101 crates: CrateList,
102 },
103 /// Recontextualize patch files.
104 RecontextualizePatches {
105 #[command(flatten)]
106 crates: CrateList,
107 },
108 /// Find crates with a newer version on crates.io
109 UpdatableCrates {},
110 /// Analyze possible updates for a crate and try to identify potential problems.
111 AnalyzeUpdates { crate_name: String },
112 /// Suggest crate updates.
113 SuggestUpdates {
114 /// Don't exclude crates that have patches.
115 #[arg(long, default_value_t = false)]
116 patches: bool,
117 },
118 /// Update a crate to the specified version.
119 Update {
120 /// The crate name.
121 crate_name: String,
122
123 /// The crate version.
124 version: String,
125 },
126 /// Try suggested crate updates and see which ones succeed.
127 ///
128 /// Take about 15 minutes per crate, so suggested use is to tee to a file and let it run overnight:
129 ///
130 /// ./android_cargo.py run --bin crate_tool -- try-updates | tee crate-updates
131 TryUpdates {},
132 /// Initialize a new managed repo.
133 Init {},
134 /// Update TEST_MAPPING files.
135 #[command(hide = true)]
136 TestMapping {
137 #[command(flatten)]
138 crates: CrateList,
139 },
140 }
141
142 #[derive(Args)]
143 struct CrateList {
144 /// The crate names.
145 crates: Vec<String>,
146
147 /// All crates.
148 #[arg(long, default_value_t = false)]
149 all: bool,
150
151 /// Comma-separated list of crates to exclude from --all.
152 #[arg(long, value_parser = CrateList::parse_crate_list, required=false, default_value="")]
153 exclude: BTreeSet<String>,
154 }
155
156 impl CrateList {
to_list(&self, managed_repo: &ManagedRepo) -> Result<Vec<String>>157 fn to_list(&self, managed_repo: &ManagedRepo) -> Result<Vec<String>> {
158 Ok(if self.all {
159 managed_repo.all_crate_names()?.difference(&self.exclude).cloned().collect::<Vec<_>>()
160 } else {
161 self.crates.clone()
162 })
163 }
parse_crate_list(arg: &str) -> Result<BTreeSet<String>>164 pub fn parse_crate_list(arg: &str) -> Result<BTreeSet<String>> {
165 Ok(arg.split(',').map(|k| k.to_string()).collect())
166 }
167 }
168
169 #[derive(Args)]
170 struct MigrationCrateList {
171 /// The crate names. Also the directory names in external/rust/crates.
172 crates: Vec<String>,
173
174 /// Don't pin the crate version for the specified crates when checking health or migrating.
175 /// Specified as a comma-separated list of crate names.
176 #[arg(long, value_parser = CrateList::parse_crate_list, required=false, default_value="")]
177 unpinned: BTreeSet<String>,
178
179 /// For crates with multiple versions, use the specified version.
180 /// Specified as a comma-separated list of <crate_name>=<version>.
181 #[arg(long, value_parser = MigrationCrateList::parse_crate_versions, required=false, default_value="")]
182 versions: BTreeMap<String, Version>,
183 }
184
185 impl MigrationCrateList {
parse_crate_versions(arg: &str) -> Result<BTreeMap<String, Version>>186 pub fn parse_crate_versions(arg: &str) -> Result<BTreeMap<String, Version>> {
187 Ok(arg
188 .split(',')
189 .filter(|k| !k.is_empty())
190 .map(|nv| nv.split_once("=").unwrap())
191 .map(|(crate_name, version)| (crate_name.to_string(), Version::parse(version).unwrap()))
192 .collect())
193 }
194 }
195
main() -> Result<()>196 fn main() -> Result<()> {
197 let args = Cli::parse();
198
199 maybe_build_cargo_embargo(&args.android_root, args.rebuild_cargo_embargo)?;
200
201 let managed_repo = ManagedRepo::new(
202 RootedPath::new(args.android_root, args.managed_repo_path)?,
203 args.offline,
204 )?;
205
206 match args.command {
207 Cmd::MigrationHealth { crates } => {
208 for crate_name in &crates.crates {
209 if let Err(e) = managed_repo.migration_health(
210 crate_name,
211 args.verbose,
212 crates.unpinned.contains(crate_name),
213 crates.versions.get(crate_name),
214 ) {
215 println!("Crate {} error: {}", crate_name, e);
216 }
217 }
218 Ok(())
219 }
220 Cmd::Migrate { crates } => {
221 managed_repo.migrate(crates.crates, args.verbose, &crates.unpinned, &crates.versions)
222 }
223 Cmd::Regenerate { crates } => {
224 managed_repo.regenerate(crates.to_list(&managed_repo)?.into_iter(), true)
225 }
226 Cmd::PreuploadCheck { files } => managed_repo.preupload_check(&files),
227 Cmd::AnalyzeImport { crate_name } => managed_repo.analyze_import(&crate_name),
228 Cmd::Import { crate_name, version, autoconfig } => {
229 managed_repo.import(&crate_name, &version, autoconfig)
230 }
231 Cmd::FixLicenses { crates } => {
232 managed_repo.fix_licenses(crates.to_list(&managed_repo)?.into_iter())
233 }
234 Cmd::FixMetadata { crates } => {
235 managed_repo.fix_metadata(crates.to_list(&managed_repo)?.into_iter())
236 }
237 Cmd::RecontextualizePatches { crates } => {
238 managed_repo.recontextualize_patches(crates.to_list(&managed_repo)?.into_iter())
239 }
240 Cmd::UpdatableCrates {} => managed_repo.updatable_crates(),
241 Cmd::AnalyzeUpdates { crate_name } => managed_repo.analyze_updates(crate_name),
242 Cmd::SuggestUpdates { patches } => managed_repo.suggest_updates(patches).map(|_x| ()),
243 Cmd::Update { crate_name, version } => managed_repo.update(crate_name, version),
244 Cmd::TryUpdates {} => managed_repo.try_updates(),
245 Cmd::Init {} => managed_repo.init(),
246 Cmd::TestMapping { crates } => {
247 managed_repo.fix_test_mapping(crates.to_list(&managed_repo)?.into_iter())
248 }
249 }
250 }
251