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