xref: /aosp_15_r20/development/tools/external_crates/crate_tool/src/main.rs (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
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