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