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::{path::PathBuf, process::Command, str::from_utf8};
16
17 use anyhow::{anyhow, Result};
18 use clap::{Parser, Subcommand};
19 use crate_health::{
20 default_repo_root, maybe_build_cargo_embargo, migrate, CrateCollection, Migratable,
21 NameAndVersionMap, NamedAndVersioned, RepoPath,
22 };
23
24 #[derive(Parser)]
25 struct Cli {
26 #[command(subcommand)]
27 command: Cmd,
28
29 #[arg(long, default_value_os_t=default_repo_root().unwrap_or(PathBuf::from(".")))]
30 repo_root: PathBuf,
31
32 /// Rebuild cargo_embargo and bpfmt, even if they are already present in the out directory.
33 #[arg(long, default_value_t = false)]
34 rebuild_cargo_embargo: bool,
35 }
36
37 #[derive(Subcommand)]
38 enum Cmd {
39 /// Check the health of a crate, and whether it is safe to migrate.
40 MigrationHealth {
41 /// The crate name. Also the directory name in external/rust/crates
42 crate_name: String,
43 },
44 /// Migrate a crate from external/rust/crates to the monorepo.
45 Migrate {
46 /// The crate name. Also the directory name in external/rust/crates
47 crate_name: String,
48 },
49 Regenerate {
50 /// The crate name.
51 crate_name: String,
52 },
53 /// Run pre-upload checks.
54 PreuploadCheck {
55 /// List of changed files
56 files: Vec<String>,
57 },
58 }
59
60 static IGNORED_FILES: &'static [&'static str] = &[
61 ".appveyor.yml",
62 ".bazelci",
63 ".bazelignore",
64 ".bazelrc",
65 ".bazelversion",
66 ".buildkite",
67 ".cargo",
68 ".cargo-checksum.json",
69 ".cargo_vcs_info.json",
70 ".circleci",
71 ".cirrus.yml",
72 ".clang-format",
73 ".clang-tidy",
74 ".clippy.toml",
75 ".clog.toml",
76 ".clog.toml",
77 ".codecov.yaml",
78 ".codecov.yml",
79 ".editorconfig",
80 ".gcloudignore",
81 ".gdbinit",
82 ".git",
83 ".git-blame-ignore-revs",
84 ".git-ignore-revs",
85 ".gitallowed",
86 ".gitattributes",
87 ".github",
88 ".gitignore",
89 ".idea",
90 ".ignore",
91 ".istanbul.yml",
92 ".mailmap",
93 ".md-inc.toml",
94 ".mdl-style.rb",
95 ".mdlrc",
96 ".pylintrc",
97 ".pylintrc-examples",
98 ".pylintrc-tests",
99 ".reuse",
100 ".rspec",
101 ".rustfmt.toml",
102 ".shellcheckrc",
103 ".standard-version",
104 ".tarpaulin.toml",
105 ".tokeignore",
106 ".travis.yml",
107 ".versionrc",
108 ".vim",
109 ".vscode",
110 ".yapfignore",
111 ".yardopts",
112 "BUILD",
113 "Cargo.lock",
114 "Cargo.lock.saved",
115 "Cargo.toml.orig",
116 "OWNERS",
117 // rules.mk related files that we won't migrate.
118 "cargo2rulesmk.json",
119 "CleanSpec.mk",
120 "rules.mk",
121 // cargo_embargo intermediates.
122 "Android.bp.orig",
123 "cargo.metadata",
124 "cargo.out",
125 "target.tmp",
126 ];
127
main() -> Result<()>128 fn main() -> Result<()> {
129 let args = Cli::parse();
130
131 maybe_build_cargo_embargo(&args.repo_root, args.rebuild_cargo_embargo)?;
132
133 match args.command {
134 Cmd::MigrationHealth { crate_name } => {
135 if args
136 .repo_root
137 .join("external/rust/android-crates-io/crates")
138 .join(&crate_name)
139 .exists()
140 {
141 return Err(anyhow!(
142 "Crate {} already exists in external/rust/android-crates-io/crates",
143 crate_name
144 ));
145 }
146
147 let mut cc = CrateCollection::new(&args.repo_root);
148 cc.add_from(&PathBuf::from("external/rust/crates").join(&crate_name))?;
149 cc.map_field_mut().retain(|_nv, krate| krate.is_crates_io());
150 if cc.map_field().len() != 1 {
151 return Err(anyhow!(
152 "Expected a single crate version for {}, but found {}. Crates with multiple versions are not supported yet.",
153 crate_name,
154 cc.map_field().len()
155 ));
156 }
157
158 cc.stage_crates()?;
159 cc.generate_android_bps()?;
160 cc.diff_android_bps()?;
161
162 let krate = cc.map_field().values().next().unwrap();
163 println!("Found {} v{} in {}", krate.name(), krate.version(), krate.path());
164 let migratable;
165 if !krate.is_android_bp_healthy() {
166 if krate.is_migration_denied() {
167 println!("This crate is on the migration denylist");
168 }
169 if !krate.android_bp().abs().exists() {
170 println!("There is no Android.bp file in {}", krate.path());
171 }
172 if !krate.cargo_embargo_json().abs().exists() {
173 println!("There is no cargo_embargo.json file in {}", krate.path());
174 } else if !krate.generate_android_bp_success() {
175 println!("cargo_embargo execution did not succeed for {}", krate.path());
176 } else if !krate.android_bp_unchanged() {
177 println!(
178 "Running cargo_embargo on {} produced changes to the Android.bp file:",
179 krate.path()
180 );
181 println!(
182 "{}",
183 from_utf8(
184 &krate
185 .android_bp_diff()
186 .ok_or(anyhow!("No Android.bp diff found"))?
187 .stdout
188 )?
189 );
190 }
191 migratable = false;
192 } else {
193 let migration = migrate(
194 RepoPath::new(
195 args.repo_root.clone(),
196 PathBuf::from("external/rust/crates").join(&crate_name),
197 ),
198 RepoPath::new(args.repo_root.clone(), &"out/rust-crate-migration-report"),
199 )?;
200 let compatible_pairs = migration.compatible_pairs().collect::<Vec<_>>();
201 if compatible_pairs.len() != 1 {
202 return Err(anyhow!("Couldn't find a compatible version to migrate to",));
203 }
204 let pair = compatible_pairs.first().unwrap();
205 if pair.source.version() != pair.dest.version() {
206 println!(
207 "Source and destination versions are different: {} -> {}",
208 pair.source.version(),
209 pair.dest.version()
210 );
211 }
212 if !pair.dest.is_migratable() {
213 if !pair.dest.patch_success() {
214 println!("Patches did not apply successfully to the migrated crate");
215 // TODO: Show errors.
216 }
217 if !pair.dest.generate_android_bp_success() {
218 println!("cargo_embargo execution did not succeed for the migrated crate");
219 } else if pair.dest.android_bp_unchanged() {
220 println!("Running cargo_embargo for the migrated crate produced changes to the Android.bp file:");
221 println!(
222 "{}",
223 from_utf8(
224 &pair
225 .dest
226 .android_bp_diff()
227 .ok_or(anyhow!("No Android.bp diff found"))?
228 .stdout
229 )?
230 );
231 }
232 }
233
234 let diff_status = Command::new("diff")
235 .args(["-u", "-r", "-w", "--no-dereference"])
236 .args(IGNORED_FILES.iter().map(|ignored| format!("--exclude={}", ignored)))
237 .arg(pair.source.path().rel())
238 .arg(pair.dest.staging_path().rel())
239 .current_dir(&args.repo_root)
240 .spawn()?
241 .wait()?;
242 if !diff_status.success() {
243 println!(
244 "Found differences between {} and {}",
245 pair.source.path(),
246 pair.dest.staging_path()
247 );
248 }
249 println!("All diffs:");
250 Command::new("diff")
251 .args(["-u", "-r", "-w", "-q", "--no-dereference"])
252 .arg(pair.source.path().rel())
253 .arg(pair.dest.staging_path().rel())
254 .current_dir(&args.repo_root)
255 .spawn()?
256 .wait()?;
257
258 migratable = pair.dest.is_migratable() && diff_status.success()
259 }
260
261 println!(
262 "The crate is {}",
263 if krate.is_android_bp_healthy() && migratable { "healthy" } else { "UNHEALTHY" }
264 );
265 }
266 Cmd::Migrate { crate_name: _ } => todo!(),
267 Cmd::Regenerate { crate_name: _ } => todo!(),
268 Cmd::PreuploadCheck { files: _ } => todo!(),
269 }
270
271 Ok(())
272 }
273