patch_sync: Sort android patches

Android requests that patches are sorted. They use their own
__lt__ implementation in cherrypick_cl.py, which we should
leverage to keep the sorting stable and robust to
implementation details.

BUG=b:217767120
TEST=patch_sync transpose <...>

Change-Id: I3013b66c4552fd47052e15009df252cdcdc245ad
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/3440375
Reviewed-by: George Burgess <gbiv@chromium.org>
Reviewed-by: Pirama Arumuga Nainar <pirama@google.com>
Commit-Queue: Jordan Abrahams-Whitehead <ajordanr@google.com>
Tested-by: Jordan Abrahams-Whitehead <ajordanr@google.com>
diff --git a/llvm_tools/patch_sync/src/android_utils.rs b/llvm_tools/patch_sync/src/android_utils.rs
index c6c1cd5..77cb4b8 100644
--- a/llvm_tools/patch_sync/src/android_utils.rs
+++ b/llvm_tools/patch_sync/src/android_utils.rs
@@ -3,20 +3,15 @@
 
 use anyhow::{bail, ensure, Result};
 
+const LLVM_ANDROID_REL_PATH: &str = "toolchain/llvm_android";
+
 /// Return the Android checkout's current llvm version.
 ///
 /// This uses android_version.get_svn_revision_number, a python function
 /// that can't be executed directly. We spawn a Python3 program
 /// to run it and get the result from that.
 pub fn get_android_llvm_version(android_checkout: &Path) -> Result<String> {
-    let mut command = Command::new("python3");
-    let llvm_android_dir = android_checkout.join("toolchain/llvm_android");
-    ensure!(
-        llvm_android_dir.is_dir(),
-        "can't get android llvm version; {} is not a directory",
-        llvm_android_dir.display()
-    );
-    command.current_dir(llvm_android_dir);
+    let mut command = new_android_cmd(android_checkout, "python3")?;
     command.args([
         "-c",
         "import android_version; print(android_version.get_svn_revision_number(), end='')",
@@ -31,3 +26,37 @@
     let out_string = String::from_utf8(output.stdout)?.trim().to_string();
     Ok(out_string)
 }
+
+/// Sort the Android patches using the cherrypick_cl.py Android utility.
+///
+/// This assumes that:
+///   1. There exists a python script called cherrypick_cl.py
+///   2. That calling it with the given arguments sorts the PATCHES.json file.
+///   3. Calling it does nothing besides sorting the PATCHES.json file.
+///
+/// We aren't doing our own sorting because we shouldn't have to update patch_sync along
+/// with cherrypick_cl.py any time they change the __lt__ implementation.
+pub fn sort_android_patches(android_checkout: &Path) -> Result<()> {
+    let mut command = new_android_cmd(android_checkout, "python3")?;
+    command.args(["cherrypick_cl.py", "--reason", "patch_sync sorting"]);
+    let output = command.output()?;
+    if !output.status.success() {
+        bail!(
+            "could not sort: {}",
+            String::from_utf8_lossy(&output.stderr)
+        );
+    }
+    Ok(())
+}
+
+fn new_android_cmd(android_checkout: &Path, cmd: &str) -> Result<Command> {
+    let mut command = Command::new(cmd);
+    let llvm_android_dir = android_checkout.join(LLVM_ANDROID_REL_PATH);
+    ensure!(
+        llvm_android_dir.is_dir(),
+        "can't make android command; {} is not a directory",
+        llvm_android_dir.display()
+    );
+    command.current_dir(llvm_android_dir);
+    Ok(command)
+}
diff --git a/llvm_tools/patch_sync/src/main.rs b/llvm_tools/patch_sync/src/main.rs
index a8a957f..5827615 100644
--- a/llvm_tools/patch_sync/src/main.rs
+++ b/llvm_tools/patch_sync/src/main.rs
@@ -228,6 +228,12 @@
             .context("uploading chromiumos changes")?;
     }
     if !opt.new_cros_patches.is_empty() {
+        if let Err(e) = android_utils::sort_android_patches(&ctx.android_checkout) {
+            eprintln!(
+                "Couldn't sort Android patches; continuing. Caused by: {}",
+                e
+            );
+        }
         ctx.android_repo_upload(&opt.android_reviewers)
             .context("uploading android changes")?;
     }