• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 mod android_utils;
6 mod patch_parsing;
7 mod version_control;
8 
9 use std::borrow::ToOwned;
10 use std::collections::BTreeSet;
11 use std::path::{Path, PathBuf};
12 
13 use anyhow::{Context, Result};
14 use structopt::StructOpt;
15 
16 use patch_parsing::{filter_patches_by_platform, PatchCollection, PatchDictSchema, VersionRange};
17 use version_control::RepoSetupContext;
18 
main() -> Result<()>19 fn main() -> Result<()> {
20     match Opt::from_args() {
21         Opt::Show {
22             cros_checkout_path,
23             android_checkout_path,
24             sync,
25             keep_unmerged,
26         } => show_subcmd(ShowOpt {
27             cros_checkout_path,
28             android_checkout_path,
29             sync,
30             keep_unmerged,
31         }),
32         Opt::Transpose {
33             cros_checkout_path,
34             cros_reviewers,
35             old_cros_ref,
36             android_checkout_path,
37             android_reviewers,
38             old_android_ref,
39             sync,
40             verbose,
41             dry_run,
42             no_commit,
43             wip,
44             disable_cq,
45         } => transpose_subcmd(TransposeOpt {
46             cros_checkout_path,
47             cros_reviewers: cros_reviewers
48                 .map(|r| r.split(',').map(ToOwned::to_owned).collect())
49                 .unwrap_or_default(),
50             old_cros_ref,
51             android_checkout_path,
52             android_reviewers: android_reviewers
53                 .map(|r| r.split(',').map(ToOwned::to_owned).collect())
54                 .unwrap_or_default(),
55             old_android_ref,
56             sync,
57             verbose,
58             dry_run,
59             no_commit,
60             wip,
61             disable_cq,
62         }),
63     }
64 }
65 
66 struct ShowOpt {
67     cros_checkout_path: PathBuf,
68     android_checkout_path: PathBuf,
69     keep_unmerged: bool,
70     sync: bool,
71 }
72 
show_subcmd(args: ShowOpt) -> Result<()>73 fn show_subcmd(args: ShowOpt) -> Result<()> {
74     let ShowOpt {
75         cros_checkout_path,
76         android_checkout_path,
77         keep_unmerged,
78         sync,
79     } = args;
80     let ctx = RepoSetupContext {
81         cros_checkout: cros_checkout_path,
82         android_checkout: android_checkout_path,
83         sync_before: sync,
84         wip_mode: true,   // Has no effect, as we're not making changes
85         enable_cq: false, // Has no effect, as we're not uploading anything
86     };
87     ctx.setup()?;
88     let make_collection = |platform: &str, patches_fp: &Path| -> Result<PatchCollection> {
89         let parsed_collection = PatchCollection::parse_from_file(patches_fp)
90             .with_context(|| format!("could not parse {} PATCHES.json", platform))?;
91         Ok(if keep_unmerged {
92             parsed_collection
93         } else {
94             filter_patches_by_platform(&parsed_collection, platform).map_patches(|p| {
95                 // Need to do this platforms creation as Rust 1.55 cannot use "from".
96                 let mut platforms = BTreeSet::new();
97                 platforms.insert(platform.to_string());
98                 PatchDictSchema {
99                     platforms,
100                     ..p.clone()
101                 }
102             })
103         })
104     };
105     let cur_cros_collection = make_collection("chromiumos", &ctx.cros_patches_path())?;
106     let cur_android_collection = make_collection("android", &ctx.android_patches_path())?;
107     let merged = cur_cros_collection.union(&cur_android_collection)?;
108     println!("{}", merged.serialize_patches()?);
109     Ok(())
110 }
111 
112 struct TransposeOpt {
113     cros_checkout_path: PathBuf,
114     old_cros_ref: String,
115     android_checkout_path: PathBuf,
116     old_android_ref: String,
117     sync: bool,
118     verbose: bool,
119     dry_run: bool,
120     no_commit: bool,
121     cros_reviewers: Vec<String>,
122     android_reviewers: Vec<String>,
123     wip: bool,
124     disable_cq: bool,
125 }
126 
transpose_subcmd(args: TransposeOpt) -> Result<()>127 fn transpose_subcmd(args: TransposeOpt) -> Result<()> {
128     let ctx = RepoSetupContext {
129         cros_checkout: args.cros_checkout_path,
130         android_checkout: args.android_checkout_path,
131         sync_before: args.sync,
132         wip_mode: args.wip,
133         enable_cq: !args.disable_cq,
134     };
135     ctx.setup()?;
136     let cros_patches_path = ctx.cros_patches_path();
137     let android_patches_path = ctx.android_patches_path();
138 
139     // Get new Patches -------------------------------------------------------
140     let patch_parsing::PatchTemporalDiff {
141         cur_collection: cur_cros_collection,
142         new_patches: new_cros_patches,
143         version_updates: cros_version_updates,
144     } = patch_parsing::new_patches(
145         &cros_patches_path,
146         &ctx.old_cros_patch_contents(&args.old_cros_ref)?,
147         "chromiumos",
148     )
149     .context("finding new patches for chromiumos")?;
150     let patch_parsing::PatchTemporalDiff {
151         cur_collection: cur_android_collection,
152         new_patches: new_android_patches,
153         version_updates: android_version_updates,
154     } = patch_parsing::new_patches(
155         &android_patches_path,
156         &ctx.old_android_patch_contents(&args.old_android_ref)?,
157         "android",
158     )
159     .context("finding new patches for android")?;
160 
161     // Have to ignore patches that are already at the destination, even if
162     // the patches are new.
163     let new_cros_patches = new_cros_patches.subtract(&cur_android_collection)?;
164     let new_android_patches = new_android_patches.subtract(&cur_cros_collection)?;
165 
166     // Need to do an extra filtering step for Android, as AOSP doesn't
167     // want patches outside of the start/end bounds.
168     let android_llvm_version: u64 = {
169         let android_llvm_version_str =
170             android_utils::get_android_llvm_version(&ctx.android_checkout)?;
171         android_llvm_version_str.parse::<u64>().with_context(|| {
172             format!(
173                 "converting llvm version to u64: '{}'",
174                 android_llvm_version_str
175             )
176         })?
177     };
178     let new_android_patches = new_android_patches.filter_patches(|p| {
179         match (p.get_from_version(), p.get_until_version()) {
180             (Some(start), Some(end)) => start <= android_llvm_version && android_llvm_version < end,
181             (Some(start), None) => start <= android_llvm_version,
182             (None, Some(end)) => android_llvm_version < end,
183             (None, None) => true,
184         }
185     });
186 
187     // Need to filter version updates to only existing patches to the other platform.
188     let cros_version_updates =
189         filter_version_changes(cros_version_updates, &cur_android_collection);
190     let android_version_updates =
191         filter_version_changes(android_version_updates, &cur_cros_collection);
192 
193     if args.verbose {
194         display_patches("New patches from ChromiumOS", &new_cros_patches);
195         display_version_updates("Version updates from ChromiumOS", &cros_version_updates);
196         display_patches("New patches from Android", &new_android_patches);
197         display_version_updates("Version updates from Android", &android_version_updates);
198     }
199 
200     if args.dry_run {
201         println!("--dry-run specified; skipping modifications");
202         return Ok(());
203     }
204 
205     modify_repos(
206         &ctx,
207         args.no_commit,
208         ModifyOpt {
209             new_cros_patches,
210             cur_cros_collection,
211             cros_version_updates,
212             cros_reviewers: args.cros_reviewers,
213             new_android_patches,
214             cur_android_collection,
215             android_version_updates,
216             android_reviewers: args.android_reviewers,
217         },
218     )
219 }
220 
221 struct ModifyOpt {
222     new_cros_patches: PatchCollection,
223     cur_cros_collection: PatchCollection,
224     cros_version_updates: Vec<(String, Option<VersionRange>)>,
225     cros_reviewers: Vec<String>,
226     new_android_patches: PatchCollection,
227     cur_android_collection: PatchCollection,
228     android_version_updates: Vec<(String, Option<VersionRange>)>,
229     android_reviewers: Vec<String>,
230 }
231 
modify_repos(ctx: &RepoSetupContext, no_commit: bool, opt: ModifyOpt) -> Result<()>232 fn modify_repos(ctx: &RepoSetupContext, no_commit: bool, opt: ModifyOpt) -> Result<()> {
233     // Cleanup on scope exit.
234     scopeguard::defer! {
235         ctx.cleanup();
236     }
237     // Transpose Patches -----------------------------------------------------
238     let mut cur_android_collection = opt.cur_android_collection;
239     let mut cur_cros_collection = opt.cur_cros_collection;
240     // Apply any version ranges and new patches, then write out.
241     if !opt.new_cros_patches.is_empty() || !opt.cros_version_updates.is_empty() {
242         cur_android_collection =
243             cur_android_collection.update_version_ranges(&opt.cros_version_updates);
244         opt.new_cros_patches
245             .transpose_write(&mut cur_android_collection)?;
246     }
247     if !opt.new_android_patches.is_empty() || !opt.android_version_updates.is_empty() {
248         cur_cros_collection =
249             cur_cros_collection.update_version_ranges(&opt.android_version_updates);
250         opt.new_android_patches
251             .transpose_write(&mut cur_cros_collection)?;
252     }
253 
254     if no_commit {
255         println!("--no-commit specified; not committing or uploading");
256         return Ok(());
257     }
258     // Commit and upload for review ------------------------------------------
259     // Note we want to check if the android patches are empty for CrOS, and
260     // vice versa. This is a little counterintuitive.
261     if !opt.new_android_patches.is_empty() {
262         ctx.cros_repo_upload(&opt.cros_reviewers)
263             .context("uploading chromiumos changes")?;
264     }
265     if !opt.new_cros_patches.is_empty() {
266         if let Err(e) = android_utils::sort_android_patches(&ctx.android_checkout) {
267             eprintln!(
268                 "Couldn't sort Android patches; continuing. Caused by: {}",
269                 e
270             );
271         }
272         ctx.android_repo_upload(&opt.android_reviewers)
273             .context("uploading android changes")?;
274     }
275     Ok(())
276 }
277 
278 /// Filter version changes that can't apply to a given collection.
filter_version_changes<T>( version_updates: T, other_platform_collection: &PatchCollection, ) -> Vec<(String, Option<VersionRange>)> where T: IntoIterator<Item = (String, Option<VersionRange>)>,279 fn filter_version_changes<T>(
280     version_updates: T,
281     other_platform_collection: &PatchCollection,
282 ) -> Vec<(String, Option<VersionRange>)>
283 where
284     T: IntoIterator<Item = (String, Option<VersionRange>)>,
285 {
286     version_updates
287         .into_iter()
288         .filter(|(rel_patch_path, _)| {
289             other_platform_collection
290                 .patches
291                 .iter()
292                 .any(|p| &p.rel_patch_path == rel_patch_path)
293         })
294         .collect()
295 }
296 
display_patches(prelude: &str, collection: &PatchCollection)297 fn display_patches(prelude: &str, collection: &PatchCollection) {
298     println!("{}", prelude);
299     if collection.patches.is_empty() {
300         println!("  [No Patches]");
301         return;
302     }
303     println!("{}", collection);
304 }
305 
display_version_updates(prelude: &str, version_updates: &[(String, Option<VersionRange>)])306 fn display_version_updates(prelude: &str, version_updates: &[(String, Option<VersionRange>)]) {
307     println!("{}", prelude);
308     if version_updates.is_empty() {
309         println!("  [No Version Changes]");
310         return;
311     }
312     for (rel_patch_path, _) in version_updates {
313         println!("*  {}", rel_patch_path);
314     }
315 }
316 
317 #[derive(Debug, structopt::StructOpt)]
318 #[structopt(name = "patch_sync", about = "A pipeline for syncing the patch code")]
319 enum Opt {
320     /// Show a combined view of the PATCHES.json file, without making any changes.
321     #[allow(dead_code)]
322     Show {
323         #[structopt(parse(from_os_str))]
324         cros_checkout_path: PathBuf,
325         #[structopt(parse(from_os_str))]
326         android_checkout_path: PathBuf,
327 
328         /// Keep a patch's platform field even if it's not merged at that platform.
329         #[structopt(long)]
330         keep_unmerged: bool,
331 
332         /// Run repo sync before transposing.
333         #[structopt(short, long)]
334         sync: bool,
335     },
336     /// Transpose patches from two PATCHES.json files
337     /// to each other.
338     Transpose {
339         /// Path to the ChromiumOS source repo checkout.
340         #[structopt(long = "cros-checkout", parse(from_os_str))]
341         cros_checkout_path: PathBuf,
342 
343         /// Emails to send review requests to during ChromiumOS upload.
344         /// Comma separated.
345         #[structopt(long = "cros-rev")]
346         cros_reviewers: Option<String>,
347 
348         /// Git ref (e.g. hash) for the ChromiumOS overlay to use as the base.
349         #[structopt(long = "overlay-base-ref")]
350         old_cros_ref: String,
351 
352         /// Path to the Android Open Source Project source repo checkout.
353         #[structopt(long = "aosp-checkout", parse(from_os_str))]
354         android_checkout_path: PathBuf,
355 
356         /// Emails to send review requests to during Android upload.
357         /// Comma separated.
358         #[structopt(long = "aosp-rev")]
359         android_reviewers: Option<String>,
360 
361         /// Git ref (e.g. hash) for the llvm_android repo to use as the base.
362         #[structopt(long = "aosp-base-ref")]
363         old_android_ref: String,
364 
365         /// Run repo sync before transposing.
366         #[structopt(short, long)]
367         sync: bool,
368 
369         /// Print information to stdout
370         #[structopt(short, long)]
371         verbose: bool,
372 
373         /// Do not change any files. Useful in combination with `--verbose`
374         /// Implies `--no-commit`.
375         #[structopt(long)]
376         dry_run: bool,
377 
378         /// Do not commit or upload any changes made.
379         #[structopt(long)]
380         no_commit: bool,
381 
382         /// Upload and send things for review, but mark as WIP and send no
383         /// emails.
384         #[structopt(long)]
385         wip: bool,
386 
387         /// Don't run CQ if set. Only has an effect if uploading.
388         #[structopt(long)]
389         disable_cq: bool,
390     },
391 }
392