Merge "Revert "Create config.json in each llndk abi-dump."" into main
diff --git a/tools/cargo_embargo/src/bp.rs b/tools/cargo_embargo/src/bp.rs
index 118cadf..ef58ade 100644
--- a/tools/cargo_embargo/src/bp.rs
+++ b/tools/cargo_embargo/src/bp.rs
@@ -73,10 +73,10 @@
         BpProperties { map: BTreeMap::new(), raw_block: None }
     }
 
-    pub fn get_string(&self, k: &str) -> &str {
-        match self.map.get(k).unwrap() {
-            BpValue::String(s) => s,
-            _ => unreachable!(),
+    pub fn get_string(&self, k: &str) -> Option<&str> {
+        match self.map.get(k)? {
+            BpValue::String(s) => Some(s),
+            _ => None,
         }
     }
 
diff --git a/tools/cargo_embargo/src/cargo.rs b/tools/cargo_embargo/src/cargo.rs
index a19d860..723459a 100644
--- a/tools/cargo_embargo/src/cargo.rs
+++ b/tools/cargo_embargo/src/cargo.rs
@@ -93,6 +93,8 @@
     pub edition: String,
     pub package_dir: PathBuf, // canonicalized
     pub main_src: PathBuf,    // relative to package_dir
+    pub license: Option<String>,
+    pub license_file: Option<String>,
     /// Whether it is a test crate which doesn't actually contain any tests or benchmarks.
     pub empty_test: bool,
 }
diff --git a/tools/cargo_embargo/src/cargo/cargo_out.rs b/tools/cargo_embargo/src/cargo/cargo_out.rs
index 76959b7..92c894b 100644
--- a/tools/cargo_embargo/src/cargo/cargo_out.rs
+++ b/tools/cargo_embargo/src/cargo/cargo_out.rs
@@ -457,6 +457,8 @@
         out.package_name.clone_from(&package_metadata.name);
         out.version = Some(package_metadata.version.clone());
         out.edition.clone_from(&package_metadata.edition);
+        out.license.clone_from(&package_metadata.license);
+        out.license_file.clone_from(&package_metadata.license_file);
 
         let output_filename = out.name.clone() + &extra_filename;
         if let Some(test_contents) = tests.get(&output_filename).and_then(|m| m.get(&out.main_src))
diff --git a/tools/cargo_embargo/src/cargo/metadata.rs b/tools/cargo_embargo/src/cargo/metadata.rs
index bff6cb7..a233101 100644
--- a/tools/cargo_embargo/src/cargo/metadata.rs
+++ b/tools/cargo_embargo/src/cargo/metadata.rs
@@ -49,6 +49,8 @@
     pub features: BTreeMap<String, Vec<String>>,
     pub id: String,
     pub targets: Vec<TargetMetadata>,
+    pub license: Option<String>,
+    pub license_file: Option<String>,
 }
 
 #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
@@ -169,6 +171,8 @@
                     types: target.crate_types.clone(),
                     features: features_without_deps.clone(),
                     edition: package.edition.to_owned(),
+                    license: package.license.clone(),
+                    license_file: package.license_file.clone(),
                     package_dir: package_dir.clone(),
                     main_src: main_src.to_owned(),
                     target: target_triple.clone(),
@@ -193,6 +197,8 @@
                     types: vec![CrateType::Test],
                     features: features_without_deps.clone(),
                     edition: package.edition.to_owned(),
+                    license: package.license.clone(),
+                    license_file: package.license_file.clone(),
                     package_dir: package_dir.clone(),
                     main_src: main_src.to_owned(),
                     target: target_triple.clone(),
diff --git a/tools/cargo_embargo/src/config.rs b/tools/cargo_embargo/src/config.rs
index 7ddc5a2..e64eb6d 100644
--- a/tools/cargo_embargo/src/config.rs
+++ b/tools/cargo_embargo/src/config.rs
@@ -376,11 +376,16 @@
     /// Patch file to apply after rules.mk is generated.
     #[serde(skip_serializing_if = "Option::is_none")]
     pub rulesmk_patch: Option<PathBuf>,
+    /// `license_text` to use for `license` module, overriding the `license_file` given by the
+    /// package or the default "LICENSE".
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub license_text: Option<String>,
 }
 
 impl PackageConfig {
     /// Names of all the fields on `PackageConfig`.
-    const FIELD_NAMES: [&'static str; 3] = ["add_toplevel_block", "patch", "rulesmk_patch"];
+    const FIELD_NAMES: [&'static str; 4] =
+        ["add_toplevel_block", "license_text", "patch", "rulesmk_patch"];
 }
 
 /// Options that apply to everything in a package (i.e. everything associated with a particular
diff --git a/tools/cargo_embargo/src/main.rs b/tools/cargo_embargo/src/main.rs
index b684169..af21791 100644
--- a/tools/cargo_embargo/src/main.rs
+++ b/tools/cargo_embargo/src/main.rs
@@ -429,10 +429,13 @@
     Ok(())
 }
 
-/// Runs the given command, and returns its standard output and standard error as a string.
-fn run_cargo(cmd: &mut Command) -> Result<String> {
+/// Runs the given command, and returns its standard output and (optionally) standard error as a string.
+fn run_cargo(cmd: &mut Command, include_stderr: bool) -> Result<String> {
     let (pipe_read, pipe_write) = pipe2(OFlag::O_CLOEXEC)?;
-    cmd.stdout(pipe_write.try_clone()?).stderr(pipe_write).stdin(Stdio::null());
+    if include_stderr {
+        cmd.stderr(pipe_write.try_clone()?);
+    }
+    cmd.stdout(pipe_write).stdin(Stdio::null());
     debug!("Running: {:?}\n", cmd);
     let mut child = cmd.spawn()?;
 
@@ -469,7 +472,7 @@
     let target_dir = intermediates_dir.join("target.tmp");
 
     // cargo clean
-    run_cargo(Command::new("cargo").arg("clean").arg("--target-dir").arg(&target_dir))
+    run_cargo(Command::new("cargo").arg("clean").arg("--target-dir").arg(&target_dir), true)
         .context("Running cargo clean")?;
 
     let default_target = "x86_64-unknown-linux-gnu";
@@ -504,6 +507,7 @@
             .arg("--format-version")
             .arg("1")
             .args(&feature_args),
+        false,
     )
     .context("Running cargo metadata")?;
 
@@ -532,6 +536,7 @@
                 .arg(&target_dir)
                 .args(&workspace_args)
                 .args(&feature_args),
+            true,
         )?;
 
         if cfg.tests {
@@ -545,6 +550,7 @@
                     .arg(&target_dir)
                     .args(&workspace_args)
                     .args(&feature_args),
+                true,
             )?;
             // cargo test -- --list
             cargo_out += &run_cargo(
@@ -556,6 +562,7 @@
                     .args(&workspace_args)
                     .args(&feature_args)
                     .args(["--", "--list"]),
+                true,
             )?;
         }
     }
@@ -606,11 +613,11 @@
     for (variant_index, variant_config) in cfg.variants.iter().enumerate() {
         let variant_crates = &crates[variant_index];
         let def = PackageVariantConfig::default();
-        let package_cfg = variant_config.package.get(package_name).unwrap_or(&def);
+        let package_variant_cfg = variant_config.package.get(package_name).unwrap_or(&def);
 
         // If `copy_out` is enabled and there are any generated out files for the package, copy them to
         // the appropriate directory.
-        if package_cfg.copy_out && !out_files[variant_index].is_empty() {
+        if package_variant_cfg.copy_out && !out_files[variant_index].is_empty() {
             let out_dir = package_dir.join("out");
             if !out_dir.exists() {
                 std::fs::create_dir(&out_dir).expect("failed to create out dir");
@@ -625,7 +632,7 @@
         if variant_config.generate_androidbp {
             bp_contents += &generate_android_bp(
                 variant_config,
-                package_cfg,
+                package_variant_cfg,
                 package_name,
                 variant_crates,
                 &out_files[variant_index],
@@ -634,7 +641,7 @@
         if variant_config.generate_rulesmk {
             mk_contents += &generate_rules_mk(
                 variant_config,
-                package_cfg,
+                package_variant_cfg,
                 package_name,
                 variant_crates,
                 &out_files[variant_index],
@@ -651,13 +658,13 @@
     }
     if !bp_contents.is_empty() {
         let output_path = package_dir.join("Android.bp");
-        let bp_contents = "// This file is generated by cargo_embargo.\n".to_owned()
-            + "// Do not modify this file after the first \"rust_*\" or \"genrule\" module\n"
-            + "// because the changes will be overridden on upgrade.\n"
-            + "// Content before the first \"rust_*\" or \"genrule\" module is preserved.\n\n"
-            + read_license_header(&output_path)?.trim()
-            + "\n"
-            + &bp_contents;
+        let package_header = generate_android_bp_package_header(
+            package_name,
+            package_cfg,
+            read_license_header(&output_path)?.trim(),
+            crates,
+        )?;
+        let bp_contents = package_header + &bp_contents;
         write_format_android_bp(&output_path, &bp_contents, package_cfg.patch.as_deref())?;
     }
     if !mk_contents.is_empty() {
@@ -678,6 +685,72 @@
     Ok(())
 }
 
+fn generate_android_bp_package_header(
+    package_name: &str,
+    package_cfg: &PackageConfig,
+    license_header: &str,
+    crates: &[Vec<Crate>],
+) -> Result<String> {
+    let crates = crates.iter().flatten().collect::<Vec<_>>();
+    if let Some(first) = crates.first() {
+        if let Some(license) = first.license.as_ref() {
+            if crates.iter().all(|c| c.license.as_ref() == Some(license)) {
+                let mut modules = Vec::new();
+                let license = choose_license(license);
+                let license_name = format!("external_rust_crates_{}_license", package_name);
+
+                let mut package_module = BpModule::new("package".to_string());
+                package_module.props.set("default_applicable_licenses", vec![license_name.clone()]);
+                modules.push(package_module);
+
+                let mut license_module = BpModule::new("license".to_string());
+                license_module.props.set("name", license_name);
+                license_module.props.set("visibility", vec![":__subpackages__"]);
+                license_module
+                    .props
+                    .set("license_kinds", vec![format!("SPDX-license-identifier-{}", license)]);
+                let license_text = package_cfg
+                    .license_text
+                    .as_deref()
+                    .unwrap_or_else(|| first.license_file.as_deref().unwrap_or("LICENSE"))
+                    .to_string();
+                license_module.props.set("license_text", vec![license_text]);
+                modules.push(license_module);
+
+                let mut bp_contents = "// This file is generated by cargo_embargo.\n".to_owned()
+                + "// Do not modify this file because the changes will be overridden on upgrade.\n\n";
+                for m in modules {
+                    m.write(&mut bp_contents)?;
+                    bp_contents += "\n";
+                }
+                return Ok(bp_contents);
+            } else {
+                eprintln!("Crates have different licenses.");
+            }
+        }
+    }
+
+    Ok("// This file is generated by cargo_embargo.\n".to_owned()
+        + "// Do not modify this file after the first \"rust_*\" or \"genrule\" module\n"
+        + "// because the changes will be overridden on upgrade.\n"
+        + "// Content before the first \"rust_*\" or \"genrule\" module is preserved.\n\n"
+        + license_header
+        + "\n")
+}
+
+/// Given an SPDX license expression that may offer a choice between several licenses, choose one to
+/// use.
+fn choose_license(license: &str) -> &str {
+    match license {
+        "MIT OR Apache-2.0" => "Apache-2.0",
+        "Apache-2.0 OR MIT" => "Apache-2.0",
+        "MIT/Apache-2.0" => "Apache-2.0",
+        "Apache-2.0/MIT" => "Apache-2.0",
+        "Unlicense OR MIT" => "MIT",
+        _ => license,
+    }
+}
+
 /// Generates and returns a Soong Blueprint for the given set of crates, for a single variant of a
 /// package.
 fn generate_android_bp(
@@ -735,7 +808,7 @@
     modules.sort();
     modules.dedup();
 
-    modules.sort_by_key(|m| m.props.get_string("name").to_string());
+    modules.sort_by_key(|m| m.props.get_string("name").unwrap().to_string());
     for m in modules {
         m.write(&mut bp_contents)?;
         bp_contents += "\n";
@@ -1261,7 +1334,11 @@
             assert_eq!(module_by_package.len(), 1);
             let crates = module_by_package.into_values().next().unwrap();
 
-            let mut output = String::new();
+            let package_name = &crates[0][0].package_name;
+            let def = PackageConfig::default();
+            let package_cfg = cfg.package.get(package_name).unwrap_or(&def);
+            let mut output =
+                generate_android_bp_package_header(package_name, package_cfg, "", &crates).unwrap();
             for (variant_index, variant_cfg) in cfg.variants.iter().enumerate() {
                 let variant_crates = &crates[variant_index];
                 let package_name = &variant_crates[0].package_name;
diff --git a/tools/cargo_embargo/testdata/aho-corasick/crates.json b/tools/cargo_embargo/testdata/aho-corasick/crates.json
index 90a413b..092440b 100644
--- a/tools/cargo_embargo/testdata/aho-corasick/crates.json
+++ b/tools/cargo_embargo/testdata/aho-corasick/crates.json
@@ -9,7 +9,12 @@
       "features": ["default", "std"],
       "cfgs": [],
       "externs": [
-        { "name": "memchr", "lib_name": "memchr", "raw_name": "memchr", "extern_type": "Rust" }
+        {
+          "name": "memchr",
+          "lib_name": "memchr",
+          "raw_name": "memchr",
+          "extern_type": "Rust"
+        }
       ],
       "codegens": [],
       "cap_lints": "",
@@ -18,6 +23,8 @@
       "edition": "2018",
       "package_dir": "/usr/local/google/home/qwandor/aosp/external/rust/crates/aho-corasick",
       "main_src": "src/lib.rs",
+      "license": "Unlicense OR MIT",
+      "license_file": null,
       "empty_test": false
     },
     {
@@ -29,7 +36,12 @@
       "features": ["default", "std"],
       "cfgs": [],
       "externs": [
-        { "name": "memchr", "lib_name": "memchr", "raw_name": "memchr", "extern_type": "Rust" }
+        {
+          "name": "memchr",
+          "lib_name": "memchr",
+          "raw_name": "memchr",
+          "extern_type": "Rust"
+        }
       ],
       "codegens": [],
       "cap_lints": "",
@@ -38,6 +50,8 @@
       "edition": "2018",
       "package_dir": "/usr/local/google/home/qwandor/aosp/external/rust/crates/aho-corasick",
       "main_src": "src/lib.rs",
+      "license": "Unlicense OR MIT",
+      "license_file": null,
       "empty_test": false
     }
   ]
diff --git a/tools/cargo_embargo/testdata/aho-corasick/expected_Android.bp b/tools/cargo_embargo/testdata/aho-corasick/expected_Android.bp
index f16d7a4..beea086 100644
--- a/tools/cargo_embargo/testdata/aho-corasick/expected_Android.bp
+++ b/tools/cargo_embargo/testdata/aho-corasick/expected_Android.bp
@@ -1,3 +1,17 @@
+// This file is generated by cargo_embargo.
+// Do not modify this file because the changes will be overridden on upgrade.
+
+package {
+default_applicable_licenses: ["external_rust_crates_aho-corasick_license"],
+}
+
+license {
+name: "external_rust_crates_aho-corasick_license",
+visibility: [":__subpackages__"],
+license_kinds: ["SPDX-license-identifier-MIT"],
+license_text: ["LICENSE"],
+}
+
 rust_test {
 name: "aho-corasick_test_src_lib",
 host_supported: true,
diff --git a/tools/cargo_embargo/testdata/async-trait/crates.json b/tools/cargo_embargo/testdata/async-trait/crates.json
index 2f558a7..6434f82 100644
--- a/tools/cargo_embargo/testdata/async-trait/crates.json
+++ b/tools/cargo_embargo/testdata/async-trait/crates.json
@@ -15,8 +15,18 @@
           "raw_name": "proc-macro2",
           "extern_type": "Rust"
         },
-        { "name": "quote", "lib_name": "quote", "raw_name": "quote", "extern_type": "Rust" },
-        { "name": "syn", "lib_name": "syn", "raw_name": "syn", "extern_type": "Rust" }
+        {
+          "name": "quote",
+          "lib_name": "quote",
+          "raw_name": "quote",
+          "extern_type": "Rust"
+        },
+        {
+          "name": "syn",
+          "lib_name": "syn",
+          "raw_name": "syn",
+          "extern_type": "Rust"
+        }
       ],
       "codegens": [],
       "cap_lints": "",
@@ -25,6 +35,8 @@
       "edition": "2021",
       "package_dir": "/usr/local/google/home/qwandor/aosp/external/rust/crates/async-trait",
       "main_src": "src/lib.rs",
+      "license": "MIT OR Apache-2.0",
+      "license_file": null,
       "empty_test": false
     }
   ]
diff --git a/tools/cargo_embargo/testdata/async-trait/expected_Android.bp b/tools/cargo_embargo/testdata/async-trait/expected_Android.bp
index 88433ac..0b748cb 100644
--- a/tools/cargo_embargo/testdata/async-trait/expected_Android.bp
+++ b/tools/cargo_embargo/testdata/async-trait/expected_Android.bp
@@ -1,3 +1,17 @@
+// This file is generated by cargo_embargo.
+// Do not modify this file because the changes will be overridden on upgrade.
+
+package {
+default_applicable_licenses: ["external_rust_crates_async-trait_license"],
+}
+
+license {
+name: "external_rust_crates_async-trait_license",
+visibility: [":__subpackages__"],
+license_kinds: ["SPDX-license-identifier-Apache-2.0"],
+license_text: ["LICENSE"],
+}
+
 rust_proc_macro {
 name: "libasync_trait",
 crate_name: "async_trait",
diff --git a/tools/cargo_embargo/testdata/either/crates.json b/tools/cargo_embargo/testdata/either/crates.json
index 60fa7c4..546fe4e 100644
--- a/tools/cargo_embargo/testdata/either/crates.json
+++ b/tools/cargo_embargo/testdata/either/crates.json
@@ -16,6 +16,8 @@
       "edition": "2018",
       "package_dir": ".../external/rust/crates/either",
       "main_src": "src/lib.rs",
+      "license": "MIT OR Apache-2.0",
+      "license_file": null,
       "empty_test": false
     },
     {
@@ -41,6 +43,8 @@
       "edition": "2018",
       "package_dir": ".../external/rust/crates/either",
       "main_src": "src/lib.rs",
+      "license": "MIT OR Apache-2.0",
+      "license_file": null,
       "empty_test": false
     }
   ]
diff --git a/tools/cargo_embargo/testdata/either/expected_Android.bp b/tools/cargo_embargo/testdata/either/expected_Android.bp
index cf58a2c..71d6a74 100644
--- a/tools/cargo_embargo/testdata/either/expected_Android.bp
+++ b/tools/cargo_embargo/testdata/either/expected_Android.bp
@@ -1,3 +1,17 @@
+// This file is generated by cargo_embargo.
+// Do not modify this file because the changes will be overridden on upgrade.
+
+package {
+default_applicable_licenses: ["external_rust_crates_either_license"],
+}
+
+license {
+name: "external_rust_crates_either_license",
+visibility: [":__subpackages__"],
+license_kinds: ["SPDX-license-identifier-Apache-2.0"],
+license_text: ["LICENSE"],
+}
+
 rust_test {
 name: "either_test_src_lib",
 host_supported: true,
diff --git a/tools/cargo_embargo/testdata/plotters/crates.json b/tools/cargo_embargo/testdata/plotters/crates.json
index 768f93d..b0f486c 100644
--- a/tools/cargo_embargo/testdata/plotters/crates.json
+++ b/tools/cargo_embargo/testdata/plotters/crates.json
@@ -35,6 +35,8 @@
       "edition": "2018",
       "package_dir": ".../external/rust/crates/plotters",
       "main_src": "src/lib.rs",
+      "license": "MIT",
+      "license_file": null,
       "empty_test": false
     }
   ]
diff --git a/tools/cargo_embargo/testdata/plotters/expected_Android.bp b/tools/cargo_embargo/testdata/plotters/expected_Android.bp
index 4a2c394..f0ec617 100644
--- a/tools/cargo_embargo/testdata/plotters/expected_Android.bp
+++ b/tools/cargo_embargo/testdata/plotters/expected_Android.bp
@@ -1,3 +1,17 @@
+// This file is generated by cargo_embargo.
+// Do not modify this file because the changes will be overridden on upgrade.
+
+package {
+default_applicable_licenses: ["external_rust_crates_plotters_license"],
+}
+
+license {
+name: "external_rust_crates_plotters_license",
+visibility: [":__subpackages__"],
+license_kinds: ["SPDX-license-identifier-MIT"],
+license_text: ["LICENSE"],
+}
+
 rust_library {
 name: "libplotters",
 host_supported: true,
diff --git a/tools/cargo_embargo/testdata/rustc-demangle-capi/crates.json b/tools/cargo_embargo/testdata/rustc-demangle-capi/crates.json
index 7719408..2a34308 100644
--- a/tools/cargo_embargo/testdata/rustc-demangle-capi/crates.json
+++ b/tools/cargo_embargo/testdata/rustc-demangle-capi/crates.json
@@ -23,6 +23,8 @@
       "edition": "2015",
       "package_dir": "/usr/local/google/home/qwandor/aosp/external/rust/crates/rustc-demangle-capi",
       "main_src": "src/lib.rs",
+      "license": "MIT/Apache-2.0",
+      "license_file": null,
       "empty_test": false
     },
     {
@@ -48,6 +50,8 @@
       "edition": "2015",
       "package_dir": "/usr/local/google/home/qwandor/aosp/external/rust/crates/rustc-demangle-capi",
       "main_src": "src/lib.rs",
+      "license": "MIT/Apache-2.0",
+      "license_file": null,
       "empty_test": false
     }
   ]
diff --git a/tools/cargo_embargo/testdata/rustc-demangle-capi/expected_Android.bp b/tools/cargo_embargo/testdata/rustc-demangle-capi/expected_Android.bp
index 9c633e4..99ed268 100644
--- a/tools/cargo_embargo/testdata/rustc-demangle-capi/expected_Android.bp
+++ b/tools/cargo_embargo/testdata/rustc-demangle-capi/expected_Android.bp
@@ -1,3 +1,17 @@
+// This file is generated by cargo_embargo.
+// Do not modify this file because the changes will be overridden on upgrade.
+
+package {
+default_applicable_licenses: ["external_rust_crates_rustc-demangle-capi_license"],
+}
+
+license {
+name: "external_rust_crates_rustc-demangle-capi_license",
+visibility: [":__subpackages__"],
+license_kinds: ["SPDX-license-identifier-Apache-2.0"],
+license_text: ["LICENSE"],
+}
+
 rust_ffi_static {
 name: "librustc_demangle_static",
 host_supported: true,
diff --git a/tools/external_crates/crate_health/src/main.rs b/tools/external_crates/crate_health/src/main.rs
index 5363a3b..0d6e4ec 100644
--- a/tools/external_crates/crate_health/src/main.rs
+++ b/tools/external_crates/crate_health/src/main.rs
@@ -12,26 +12,39 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use std::{path::PathBuf, process::Command, str::from_utf8};
+use std::{
+    fs::{create_dir, remove_dir_all, remove_file, rename, write},
+    path::{Path, PathBuf},
+    process::Command,
+    str::from_utf8,
+};
 
 use anyhow::{anyhow, Result};
 use clap::{Parser, Subcommand};
 use crate_health::{
-    default_repo_root, maybe_build_cargo_embargo, migrate, CrateCollection, Migratable,
-    NameAndVersionMap, NamedAndVersioned, RepoPath,
+    copy_dir, default_repo_root, maybe_build_cargo_embargo, CrateCollection, Migratable,
+    NameAndVersion, NameAndVersionMap, NameAndVersionRef, NamedAndVersioned, PseudoCrate, RepoPath,
+    VersionMatch,
 };
+use glob::glob;
+use semver::Version;
 
 #[derive(Parser)]
 struct Cli {
     #[command(subcommand)]
     command: Cmd,
 
+    // The path to the Android source repo.
     #[arg(long, default_value_os_t=default_repo_root().unwrap_or(PathBuf::from(".")))]
     repo_root: PathBuf,
 
     /// Rebuild cargo_embargo and bpfmt, even if they are already present in the out directory.
     #[arg(long, default_value_t = false)]
     rebuild_cargo_embargo: bool,
+
+    /// Print command output in case of error, and full diffs.
+    #[arg(long, default_value_t = false)]
+    verbose: bool,
 }
 
 #[derive(Subcommand)]
@@ -40,20 +53,19 @@
     MigrationHealth {
         /// The crate name. Also the directory name in external/rust/crates
         crate_name: String,
-
-        /// Print command output in case of error, and full diffs.
-        #[arg(long, default_value_t = false)]
-        verbose: bool,
     },
     /// Migrate a crate from external/rust/crates to the monorepo.
     Migrate {
         /// The crate name. Also the directory name in external/rust/crates
         crate_name: String,
     },
+    /// Regenerate a crate directory.
     Regenerate {
         /// The crate name.
         crate_name: String,
     },
+    /// Regenerate all crates
+    RegenerateAll {},
     /// Run pre-upload checks.
     PreuploadCheck {
         /// List of changed files
@@ -135,191 +147,360 @@
     maybe_build_cargo_embargo(&args.repo_root, args.rebuild_cargo_embargo)?;
 
     match args.command {
-        Cmd::MigrationHealth { crate_name, verbose } => {
-            if args
-                .repo_root
-                .join("external/rust/android-crates-io/crates")
-                .join(&crate_name)
-                .exists()
-            {
-                return Err(anyhow!(
-                    "Crate {} already exists in external/rust/android-crates-io/crates",
-                    crate_name
-                ));
+        Cmd::MigrationHealth { crate_name } => {
+            migration_health(&args.repo_root, &crate_name, args.verbose)?;
+            Ok(())
+        }
+        Cmd::Migrate { crate_name } => {
+            let version = migration_health(&args.repo_root, &crate_name, args.verbose)?;
+            let src_dir = args.repo_root.join("external/rust/crates").join(&crate_name);
+
+            let monorepo_crate_dir = args.repo_root.join("external/rust/android-crates-io/crates");
+            if !monorepo_crate_dir.exists() {
+                create_dir(&monorepo_crate_dir)?;
             }
+            copy_dir(&src_dir, &monorepo_crate_dir.join(&crate_name))?;
+            let pseudo_crate = PseudoCrate::new(RepoPath::new(
+                &args.repo_root,
+                "external/rust/android-crates-io/pseudo_crate",
+            ));
+            pseudo_crate.add(&NameAndVersionRef::new(&crate_name, &version))?;
 
-            let mut cc = CrateCollection::new(&args.repo_root);
-            cc.add_from(&PathBuf::from("external/rust/crates").join(&crate_name))?;
-            cc.map_field_mut().retain(|_nv, krate| krate.is_crates_io());
-            if cc.map_field().len() != 1 {
-                return Err(anyhow!(
-                    "Expected a single crate version for {}, but found {}. Crates with multiple versions are not supported yet.",
-                    crate_name,
-                    cc.map_field().len()
-                ));
+            regenerate(&args.repo_root, [crate_name.as_str()].into_iter())?;
+
+            for entry in glob(
+                src_dir
+                    .join("*.bp")
+                    .to_str()
+                    .ok_or(anyhow!("Failed to convert path *.bp to str"))?,
+            )? {
+                remove_file(entry?)?
             }
+            remove_file(src_dir.join("cargo_embargo.json"))?;
+            write(
+                src_dir.join("Android.bp"),
+                "// This crate has been migrated to external/rust/android-crates-io.\n",
+            )?;
 
-            cc.stage_crates()?;
-            cc.generate_android_bps()?;
-            cc.diff_android_bps()?;
+            Ok(())
+        }
+        Cmd::Regenerate { crate_name } => {
+            regenerate(&args.repo_root, [crate_name.as_str()].into_iter())
+        }
+        Cmd::RegenerateAll {} => regenerate_all(&args.repo_root),
+        Cmd::PreuploadCheck { files: _ } => preupload_check(&args.repo_root),
+    }
+}
 
-            let krate = cc.map_field().values().next().unwrap();
-            println!("Found {} v{} in {}", krate.name(), krate.version(), krate.path());
-            let migratable;
-            if !krate.is_android_bp_healthy() {
-                let mut show_cargo_embargo_results = true;
-                if krate.is_migration_denied() {
-                    println!("This crate is on the migration denylist");
-                    show_cargo_embargo_results = false;
-                }
-                if !krate.android_bp().abs().exists() {
-                    println!("There is no Android.bp file in {}", krate.path());
-                    show_cargo_embargo_results = false;
-                }
-                if !krate.cargo_embargo_json().abs().exists() {
-                    show_cargo_embargo_results = false;
-                    println!("There is no cargo_embargo.json file in {}", krate.path());
-                }
-                if show_cargo_embargo_results {
-                    if !krate.generate_android_bp_success() {
-                        println!(
-                            "cargo_embargo execution did not succeed for {}",
-                            krate.staging_path(),
-                        );
-                        if verbose {
-                            println!(
-                                "stdout:\n{}\nstderr:\n{}",
-                                from_utf8(
-                                    &krate
-                                        .generate_android_bp_output()
-                                        .ok_or(anyhow!("cargo_embargo output not found"))?
-                                        .stdout
-                                )?,
-                                from_utf8(
-                                    &krate
-                                        .generate_android_bp_output()
-                                        .ok_or(anyhow!("cargo_embargo output not found"))?
-                                        .stderr
-                                )?,
-                            );
-                        }
-                    } else if !krate.android_bp_unchanged() {
-                        println!(
-                            "Running cargo_embargo on {} produced changes to the Android.bp file",
-                            krate.path()
-                        );
-                        if verbose {
-                            println!(
-                                "{}",
-                                from_utf8(
-                                    &krate
-                                        .android_bp_diff()
-                                        .ok_or(anyhow!("No Android.bp diff found"))?
-                                        .stdout
-                                )?
-                            );
-                        }
-                    }
-                }
-                migratable = false;
-            } else {
-                let migration = migrate(
-                    RepoPath::new(
-                        args.repo_root.clone(),
-                        PathBuf::from("external/rust/crates").join(&crate_name),
-                    ),
-                    RepoPath::new(args.repo_root.clone(), &"out/rust-crate-migration-report"),
-                    true,
-                )?;
-                let compatible_pairs = migration.compatible_pairs().collect::<Vec<_>>();
-                if compatible_pairs.len() != 1 {
-                    return Err(anyhow!("Couldn't find a compatible version to migrate to",));
-                }
-                let pair = compatible_pairs.first().unwrap();
-                if pair.source.version() != pair.dest.version() {
-                    println!(
-                        "Source and destination versions are different: {} -> {}",
-                        pair.source.version(),
-                        pair.dest.version()
-                    );
-                }
-                if !pair.dest.is_migratable() {
-                    if !pair.dest.patch_success() {
-                        println!("Patches did not apply successfully to the migrated crate");
-                        if verbose {
-                            for output in pair.dest.patch_output() {
-                                if !output.1.status.success() {
-                                    println!(
-                                        "Failed to apply {}\nstdout:\n{}\nstderr:\n:{}",
-                                        output.0,
-                                        from_utf8(&output.1.stdout)?,
-                                        from_utf8(&output.1.stderr)?
-                                    );
-                                }
-                            }
-                        }
-                    }
-                    if !pair.dest.generate_android_bp_success() {
-                        println!("cargo_embargo execution did not succeed for the migrated crate");
-                    } else if !pair.dest.android_bp_unchanged() {
-                        println!("Running cargo_embargo for the migrated crate produced changes to the Android.bp file");
-                        if verbose {
-                            println!(
-                                "{}",
-                                from_utf8(
-                                    &pair
-                                        .dest
-                                        .android_bp_diff()
-                                        .ok_or(anyhow!("No Android.bp diff found"))?
-                                        .stdout
-                                )?
-                            );
-                        }
-                    }
-                }
+pub fn migration_health(
+    repo_root: &impl AsRef<Path>,
+    crate_name: &str,
+    verbose: bool,
+) -> Result<Version> {
+    let repo_root = repo_root.as_ref();
+    if repo_root.join("external/rust/android-crates-io/crates").join(&crate_name).exists() {
+        return Err(anyhow!(
+            "Crate {} already exists in external/rust/android-crates-io/crates",
+            crate_name
+        ));
+    }
 
-                let mut diff_cmd = Command::new("diff");
-                diff_cmd.args(["-u", "-r", "-w", "--no-dereference"]);
-                if !verbose {
-                    diff_cmd.arg("-q");
-                }
-                let diff_status = diff_cmd
-                    .args(IGNORED_FILES.iter().map(|ignored| format!("--exclude={}", ignored)))
-                    .arg(pair.source.path().rel())
-                    .arg(pair.dest.staging_path().rel())
-                    .current_dir(&args.repo_root)
-                    .spawn()?
-                    .wait()?;
-                if !diff_status.success() {
-                    println!(
-                        "Found differences between {} and {}",
-                        pair.source.path(),
-                        pair.dest.staging_path()
-                    );
-                }
+    let mut cc = CrateCollection::new(repo_root);
+    cc.add_from(&PathBuf::from("external/rust/crates").join(&crate_name))?;
+    cc.map_field_mut().retain(|_nv, krate| krate.is_crates_io());
+    if cc.map_field().len() != 1 {
+        return Err(anyhow!(
+        "Expected a single crate version for {}, but found {}. Crates with multiple versions are not supported yet.",
+        crate_name,
+        cc.map_field().len()
+    ));
+    }
+
+    cc.stage_crates()?;
+    cc.generate_android_bps()?;
+    cc.diff_android_bps()?;
+
+    let krate = cc.map_field().values().next().unwrap();
+    let mut version = krate.version().clone();
+    println!("Found {} v{} in {}", krate.name(), krate.version(), krate.path());
+    let migratable;
+    if !krate.is_android_bp_healthy() {
+        let mut show_cargo_embargo_results = true;
+        if krate.is_migration_denied() {
+            println!("This crate is on the migration denylist");
+            show_cargo_embargo_results = false;
+        }
+        if !krate.android_bp().abs().exists() {
+            println!("There is no Android.bp file in {}", krate.path());
+            show_cargo_embargo_results = false;
+        }
+        if !krate.cargo_embargo_json().abs().exists() {
+            show_cargo_embargo_results = false;
+            println!("There is no cargo_embargo.json file in {}", krate.path());
+        }
+        if show_cargo_embargo_results {
+            if !krate.generate_android_bp_success() {
+                println!("cargo_embargo execution did not succeed for {}", krate.staging_path(),);
                 if verbose {
-                    println!("All diffs:");
-                    Command::new("diff")
-                        .args(["-u", "-r", "-w", "-q", "--no-dereference"])
-                        .arg(pair.source.path().rel())
-                        .arg(pair.dest.staging_path().rel())
-                        .current_dir(&args.repo_root)
-                        .spawn()?
-                        .wait()?;
+                    println!(
+                        "stdout:\n{}\nstderr:\n{}",
+                        from_utf8(
+                            &krate
+                                .generate_android_bp_output()
+                                .ok_or(anyhow!("cargo_embargo output not found"))?
+                                .stdout
+                        )?,
+                        from_utf8(
+                            &krate
+                                .generate_android_bp_output()
+                                .ok_or(anyhow!("cargo_embargo output not found"))?
+                                .stderr
+                        )?,
+                    );
                 }
-
-                migratable = pair.dest.is_migratable() && diff_status.success()
+            } else if !krate.android_bp_unchanged() {
+                println!(
+                    "Running cargo_embargo on {} produced changes to the Android.bp file",
+                    krate.path()
+                );
+                if verbose {
+                    println!(
+                        "{}",
+                        from_utf8(
+                            &krate
+                                .android_bp_diff()
+                                .ok_or(anyhow!("No Android.bp diff found"))?
+                                .stdout
+                        )?
+                    );
+                }
             }
+        }
+        migratable = false;
+    } else {
+        let pseudo_crate = PseudoCrate::new(RepoPath::new(
+            repo_root,
+            "external/rust/android-crates-io/pseudo_crate",
+        ));
+        pseudo_crate.add(krate)?;
+        pseudo_crate.vendor()?;
 
+        let mut source = CrateCollection::new(repo_root);
+        source.add_from(&PathBuf::from("external/rust/crates").join(&crate_name))?;
+
+        let mut dest = CrateCollection::new(cc.repo_root());
+        dest.add_from(&pseudo_crate.get_path().join(&"vendor").rel())?;
+
+        let mut version_match = VersionMatch::new(source, dest)?;
+
+        version_match.stage_crates()?;
+        version_match.copy_customizations()?;
+        version_match.apply_patches()?;
+        version_match.generate_android_bps()?;
+        version_match.diff_android_bps()?;
+
+        pseudo_crate.remove(krate)?;
+        pseudo_crate.vendor()?;
+
+        let compatible_pairs = version_match.compatible_pairs().collect::<Vec<_>>();
+        if compatible_pairs.len() != 1 {
+            return Err(anyhow!("Couldn't find a compatible version to migrate to",));
+        }
+        let pair = compatible_pairs.first().unwrap();
+        version = pair.dest.version().clone();
+        if pair.source.version() != pair.dest.version() {
             println!(
-                "Crate {} is {}",
-                krate.name(),
-                if krate.is_android_bp_healthy() && migratable { "healthy" } else { "UNHEALTHY" }
+                "Source and destination versions are different: {} -> {}",
+                pair.source.version(),
+                pair.dest.version()
             );
         }
-        Cmd::Migrate { crate_name: _ } => todo!(),
-        Cmd::Regenerate { crate_name: _ } => todo!(),
-        Cmd::PreuploadCheck { files: _ } => todo!(),
+        if !pair.dest.is_migratable() {
+            if !pair.dest.patch_success() {
+                println!("Patches did not apply successfully to the migrated crate");
+                if verbose {
+                    for output in pair.dest.patch_output() {
+                        if !output.1.status.success() {
+                            println!(
+                                "Failed to apply {}\nstdout:\n{}\nstderr:\n:{}",
+                                output.0,
+                                from_utf8(&output.1.stdout)?,
+                                from_utf8(&output.1.stderr)?
+                            );
+                        }
+                    }
+                }
+            }
+            if !pair.dest.generate_android_bp_success() {
+                println!("cargo_embargo execution did not succeed for the migrated crate");
+            } else if !pair.dest.android_bp_unchanged() {
+                println!("Running cargo_embargo for the migrated crate produced changes to the Android.bp file");
+                if verbose {
+                    println!(
+                        "{}",
+                        from_utf8(
+                            &pair
+                                .dest
+                                .android_bp_diff()
+                                .ok_or(anyhow!("No Android.bp diff found"))?
+                                .stdout
+                        )?
+                    );
+                }
+            }
+        }
+
+        let mut diff_cmd = Command::new("diff");
+        diff_cmd.args(["-u", "-r", "-w", "--no-dereference"]);
+        if !verbose {
+            diff_cmd.arg("-q");
+        }
+        let diff_status = diff_cmd
+            .args(IGNORED_FILES.iter().map(|ignored| format!("--exclude={}", ignored)))
+            .arg(pair.source.path().rel())
+            .arg(pair.dest.staging_path().rel())
+            .current_dir(repo_root)
+            .spawn()?
+            .wait()?;
+        if !diff_status.success() {
+            println!(
+                "Found differences between {} and {}",
+                pair.source.path(),
+                pair.dest.staging_path()
+            );
+        }
+        if verbose {
+            println!("All diffs:");
+            Command::new("diff")
+                .args(["-u", "-r", "-w", "-q", "--no-dereference"])
+                .arg(pair.source.path().rel())
+                .arg(pair.dest.staging_path().rel())
+                .current_dir(repo_root)
+                .spawn()?
+                .wait()?;
+        }
+
+        migratable = pair.dest.is_migratable() && diff_status.success()
+    }
+
+    let healthy = krate.is_android_bp_healthy() && migratable;
+    println!("The crate is {}", if healthy { "healthy" } else { "UNHEALTHY" });
+    if healthy {
+        return Ok(version);
+    } else {
+        return Err(anyhow!("Crate {} is unhealthy", crate_name));
+    }
+}
+
+pub fn regenerate<'a>(
+    repo_root: &impl AsRef<Path>,
+    crates: impl Iterator<Item = &'a str>,
+) -> Result<()> {
+    let repo_root = repo_root.as_ref();
+
+    let version_match = stage(&repo_root, crates)?;
+
+    for pair in version_match.pairs() {
+        let source_version = NameAndVersion::from(&pair.source.key());
+        let pair = pair.to_compatible().ok_or(anyhow!(
+            "No compatible vendored crate found for {} v{}",
+            source_version.name(),
+            source_version.version()
+        ))?;
+
+        let android_crate_dir =
+            repo_root.join("external/rust/android-crates-io/crates").join(pair.source.name());
+        remove_dir_all(&android_crate_dir)?;
+        rename(pair.dest.staging_path().abs(), &android_crate_dir)?;
+    }
+
+    Ok(())
+}
+
+pub fn stage<'a>(
+    repo_root: &impl AsRef<Path>,
+    crates: impl Iterator<Item = &'a str>,
+) -> Result<VersionMatch<CrateCollection>> {
+    let repo_root = repo_root.as_ref();
+
+    let mut cc = CrateCollection::new(repo_root);
+    for crate_name in crates {
+        let android_crate_dir =
+            repo_root.join("external/rust/android-crates-io/crates").join(crate_name);
+        if !android_crate_dir.exists() {
+            return Err(anyhow!(
+                "Crate {} not found in external/rust/android-crates-io/crates",
+                crate_name
+            ));
+        }
+
+        // Source
+        cc.add_from(&android_crate_dir)?;
+        cc.map_field_mut().retain(|_nv, krate| krate.is_crates_io());
+        let num_versions = cc.get_versions(crate_name).count();
+        if num_versions != 1 {
+            return Err(anyhow!(
+                "Expected a single crate version for {}, but found {}. Crates with multiple versions are not supported yet.",
+                crate_name,
+                num_versions
+            ));
+        }
+    }
+
+    let pseudo_crate =
+        PseudoCrate::new(RepoPath::new(repo_root, "external/rust/android-crates-io/pseudo_crate"));
+    pseudo_crate.vendor()?;
+
+    // Dest
+    let mut dest = CrateCollection::new(repo_root);
+    dest.add_from(&pseudo_crate.get_path().join(&"vendor").rel())?;
+
+    let mut version_match = VersionMatch::new(cc, dest)?;
+
+    version_match.stage_crates()?;
+    version_match.copy_customizations()?;
+    version_match.apply_patches()?;
+    version_match.generate_android_bps()?;
+    version_match.diff_android_bps()?;
+
+    Ok(version_match)
+}
+
+pub fn regenerate_all(repo_root: &impl AsRef<Path>) -> Result<()> {
+    let repo_root = repo_root.as_ref();
+    let pseudo_crate =
+        PseudoCrate::new(RepoPath::new(repo_root, "external/rust/android-crates-io/pseudo_crate"));
+    regenerate(&repo_root, pseudo_crate.deps()?.keys().map(|k| k.as_str()))
+}
+
+pub fn preupload_check(repo_root: &impl AsRef<Path>) -> Result<()> {
+    let repo_root = repo_root.as_ref();
+    let pseudo_crate =
+        PseudoCrate::new(RepoPath::new(repo_root, "external/rust/android-crates-io/pseudo_crate"));
+    let version_match = stage(&repo_root, pseudo_crate.deps()?.keys().map(|k| k.as_str()))?;
+
+    for pair in version_match.pairs() {
+        let source_version = NameAndVersion::from(&pair.source.key());
+        let pair = pair.to_compatible().ok_or(anyhow!(
+            "No compatible vendored crate found for {} v{}",
+            source_version.name(),
+            source_version.version()
+        ))?;
+
+        let diff_status = Command::new("diff")
+            .args(["-u", "-r", "-w", "--no-dereference"])
+            .arg(pair.dest.staging_path().rel())
+            .arg(Path::new("external/rust/android-crates-io/crates").join(pair.source.name()))
+            .current_dir(repo_root)
+            .spawn()?
+            .wait()?;
+        if !diff_status.success() {
+            return Err(anyhow!(
+                "Found differences between {} and {}",
+                pair.source.path(),
+                pair.dest.staging_path()
+            ));
+        }
     }
 
     Ok(())
diff --git a/tools/external_crates/crate_health/src/pseudo_crate.rs b/tools/external_crates/crate_health/src/pseudo_crate.rs
index b8c39ac..0757557 100644
--- a/tools/external_crates/crate_health/src/pseudo_crate.rs
+++ b/tools/external_crates/crate_health/src/pseudo_crate.rs
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 use std::{
+    collections::BTreeMap,
     fs::{create_dir, write},
     process::Command,
     str::from_utf8,
@@ -20,6 +21,7 @@
 
 use anyhow::{anyhow, Context, Result};
 use serde::Serialize;
+use serde_json::Value;
 use tinytemplate::TinyTemplate;
 
 use crate::{ensure_exists_and_empty, NamedAndVersioned, RepoPath};
@@ -107,6 +109,19 @@
         }
         Ok(())
     }
+    pub fn remove(&self, krate: &impl NamedAndVersioned) -> Result<()> {
+        let status = Command::new("cargo")
+            .args(["remove", krate.name()])
+            .current_dir(self.path.abs())
+            .spawn()
+            .context("Failed to spawn 'cargo remove'")?
+            .wait()
+            .context("Failed to wait on 'cargo remove'")?;
+        if !status.success() {
+            return Err(anyhow!("Failed to run 'cargo remove {}'", krate.name()));
+        }
+        Ok(())
+    }
     pub fn vendor(&self) -> Result<()> {
         let output =
             Command::new("cargo").args(["vendor"]).current_dir(self.path.abs()).output()?;
@@ -124,4 +139,32 @@
         }
         Ok(())
     }
+    pub fn deps(&self) -> Result<BTreeMap<String, String>> {
+        let output = Command::new("cargo")
+            .args(["metadata", "--offline", "--format-version=1"])
+            .current_dir(self.path.abs())
+            .output()?;
+        if !output.status.success() {
+            println!("{}", from_utf8(&output.stderr)?);
+            return Err(anyhow!("Failed to run 'cargo metadata'"));
+        }
+        let metadata: Value = serde_json::from_slice(&output.stdout)?;
+        let mut deps = BTreeMap::new();
+        for dep in metadata["packages"][0]["dependencies"]
+            .as_array()
+            .ok_or(anyhow!("Failed to deserialize cargo metadata"))?
+        {
+            deps.insert(
+                dep["name"]
+                    .as_str()
+                    .ok_or(anyhow!("Failed to deserialize cargo metadata"))?
+                    .to_string(),
+                dep["req"]
+                    .as_str()
+                    .ok_or(anyhow!("Failed to deserialize cargo metadata"))?
+                    .to_string(),
+            );
+        }
+        Ok(deps)
+    }
 }
diff --git a/vndk/tools/header-checker/src/repr/abi_diff_helpers.cpp b/vndk/tools/header-checker/src/repr/abi_diff_helpers.cpp
index 9c4f20c..c00d218 100644
--- a/vndk/tools/header-checker/src/repr/abi_diff_helpers.cpp
+++ b/vndk/tools/header-checker/src/repr/abi_diff_helpers.cpp
@@ -357,6 +357,23 @@
   return DiffStatus::kDirectDiff;
 }
 
+// This function returns a map from field names to RecordFieldIR.
+// It appends anonymous fields to anonymous_fields.
+static AbiElementMap<const RecordFieldIR *> BuildRecordFieldNameMap(
+    const std::vector<RecordFieldIR> &fields,
+    std::vector<const RecordFieldIR *> &anonymous_fields) {
+  AbiElementMap<const RecordFieldIR *> field_map;
+  for (const RecordFieldIR &field : fields) {
+    const std::string &name = field.GetName();
+    if (name.empty()) {
+      anonymous_fields.emplace_back(&field);
+    } else {
+      field_map.emplace(name, &field);
+    }
+  }
+  return field_map;
+}
+
 DiffStatus AbiDiffHelper::CompareCommonRecordFields(
     const RecordFieldIR *old_field, const RecordFieldIR *new_field,
     DiffMessageIR::DiffKind diff_kind) {
@@ -376,6 +393,27 @@
   return field_diff_status;
 }
 
+// FilterOutRenamedRecordFields calls this function to compare record fields in
+// two dumps.
+// If this function returns 0, the fields may be compatible.
+// If it returns -1 or 1, the fields must be incompatible.
+static int CompareRenamedRecordFields(const RecordFieldIR *old_field,
+                                      const RecordFieldIR *new_field) {
+  if (old_field->GetOffset() != new_field->GetOffset()) {
+    return old_field->GetOffset() < new_field->GetOffset() ? -1 : 1;
+  }
+  if (old_field->IsBitField() != new_field->IsBitField()) {
+    return old_field->IsBitField() < new_field->IsBitField() ? -1 : 1;
+  }
+  if (old_field->GetBitWidth() != new_field->GetBitWidth()) {
+    return old_field->GetBitWidth() < new_field->GetBitWidth() ? -1 : 1;
+  }
+  // Skip GetReferencedType because the same type in old and new dumps may have
+  // different IDs, especially in the cases of anonymous types and multiple
+  // definitions.
+  return 0;
+}
+
 // This function filters out the pairs of old and new fields that meet the
 // following conditions:
 //   The old field's (offset, bit width, type) is unique in old_fields.
@@ -391,17 +429,12 @@
   DiffStatus diff_status = DiffStatus::kNoDiff;
   const auto old_end = old_fields.end();
   const auto new_end = new_fields.end();
+  // Sort fields by (offset, bit width, type).
   auto is_less = [](const RecordFieldIR *first, const RecordFieldIR *second) {
-    if (first->GetOffset() != second->GetOffset()) {
-      return first->GetOffset() < second->GetOffset();
-    }
-    if (first->IsBitField() != second->IsBitField()) {
-      return first->IsBitField() < second->IsBitField();
-    }
-    if (first->GetBitWidth() != second->GetBitWidth()) {
-      return first->GetBitWidth() < second->GetBitWidth();
-    }
-    return first->GetReferencedType() < second->GetReferencedType();
+    int result = CompareRenamedRecordFields(first, second);
+    return result != 0
+               ? result < 0
+               : first->GetReferencedType() < second->GetReferencedType();
   };
   std::sort(old_fields.begin(), old_end, is_less);
   std::sort(new_fields.begin(), new_end, is_less);
@@ -411,11 +444,12 @@
   auto old_it = old_fields.begin();
   auto new_it = new_fields.begin();
   while (old_it != old_end && new_it != new_end) {
+    int old_new_cmp = CompareRenamedRecordFields(*old_it, *new_it);
     auto next_old_it = std::next(old_it);
     while (next_old_it != old_end && !is_less(*old_it, *next_old_it)) {
       next_old_it++;
     }
-    if (is_less(*old_it, *new_it) || next_old_it - old_it > 1) {
+    if (old_new_cmp < 0 || next_old_it - old_it > 1) {
       out_old_fields.insert(out_old_fields.end(), old_it, next_old_it);
       old_it = next_old_it;
       continue;
@@ -425,7 +459,7 @@
     while (next_new_it != new_end && !is_less(*new_it, *next_new_it)) {
       next_new_it++;
     }
-    if (is_less(*new_it, *old_it) || next_new_it - new_it > 1) {
+    if (old_new_cmp > 0 || next_new_it - new_it > 1) {
       out_new_fields.insert(out_new_fields.end(), new_it, next_new_it);
       new_it = next_new_it;
       continue;
@@ -457,25 +491,17 @@
   RecordFieldDiffResult result;
   DiffStatus &diff_status = result.status;
   diff_status = DiffStatus::kNoDiff;
-  // Map names to RecordFieldIR.
-  AbiElementMap<const RecordFieldIR *> old_fields_map;
-  AbiElementMap<const RecordFieldIR *> new_fields_map;
+  AbiElementMap<const RecordFieldIR *> old_fields_map =
+      BuildRecordFieldNameMap(old_fields, result.removed_fields);
+  AbiElementMap<const RecordFieldIR *> new_fields_map =
+      BuildRecordFieldNameMap(new_fields, result.added_fields);
 
-  auto get_field_name = [](const RecordFieldIR *f) -> std::string {
-    return !f->GetName().empty()
-               ? f->GetName()
-               : std::to_string(f->GetOffset()) + "#" + f->GetReferencedType();
-  };
-
-  utils::AddToMap(&old_fields_map, old_fields, get_field_name,
-                  [](const RecordFieldIR *f) { return f; });
-  utils::AddToMap(&new_fields_map, new_fields, get_field_name,
-                  [](const RecordFieldIR *f) { return f; });
-  // Compare the fields whose names are not present in both records.
-  result.removed_fields =
-      utils::FindRemovedElements(old_fields_map, new_fields_map);
-  result.added_fields =
-      utils::FindRemovedElements(new_fields_map, old_fields_map);
+  // Compare the anonymous fields and the fields whose names are not present in
+  // both records.
+  utils::InsertAll(result.removed_fields,
+                   utils::FindRemovedElements(old_fields_map, new_fields_map));
+  utils::InsertAll(result.added_fields,
+                   utils::FindRemovedElements(new_fields_map, old_fields_map));
   diff_status.CombineWith(FilterOutRenamedRecordFields(
       diff_kind, result.removed_fields, result.added_fields));
   if (result.removed_fields.size() != 0) {
diff --git a/vndk/tools/header-checker/src/utils/header_abi_util.h b/vndk/tools/header-checker/src/utils/header_abi_util.h
index 9915c52..9ca60f7 100644
--- a/vndk/tools/header-checker/src/utils/header_abi_util.h
+++ b/vndk/tools/header-checker/src/utils/header_abi_util.h
@@ -93,6 +93,11 @@
   return common_elements;
 }
 
+template <typename T>
+void InsertAll(std::vector<T> &first, const std::vector<T> &second) {
+  first.insert(first.end(), second.begin(), second.end());
+}
+
 
 }  // namespace utils
 }  // namespace header_checker
diff --git a/vndk/tools/header-checker/tests/integration/union/include/base.h b/vndk/tools/header-checker/tests/integration/union/include/base.h
index d68209e..03dd9e3 100644
--- a/vndk/tools/header-checker/tests/integration/union/include/base.h
+++ b/vndk/tools/header-checker/tests/integration/union/include/base.h
@@ -21,6 +21,16 @@
   int member_4[0];
 };
 
+union ReorderAnonymousType {
+  struct {
+    int member_1;
+  } member_1;
+  struct {
+    int member_2;
+  };
+};
+
 extern "C" {
-void function(ChangeType, Rename, Swap, ChangeTypeInStruct);
+void function(ChangeType, Rename, Swap, ChangeTypeInStruct,
+              ReorderAnonymousType);
 }
diff --git a/vndk/tools/header-checker/tests/integration/union/include/diff.h b/vndk/tools/header-checker/tests/integration/union/include/diff.h
index 2f5d62e..51595f5 100644
--- a/vndk/tools/header-checker/tests/integration/union/include/diff.h
+++ b/vndk/tools/header-checker/tests/integration/union/include/diff.h
@@ -21,6 +21,16 @@
   int member_4[0];
 };
 
+union ReorderAnonymousType {
+  struct {
+    int rename_2;
+  };
+  struct {
+    int rename_1;
+  } member_1;
+};
+
 extern "C" {
-void function(ChangeType, Rename, Swap, ChangeTypeInStruct);
+void function(ChangeType, Rename, Swap, ChangeTypeInStruct,
+              ReorderAnonymousType);
 }
diff --git a/vndk/tools/header-checker/tests/reference_dumps/arm64/libunion.so.lsdump b/vndk/tools/header-checker/tests/reference_dumps/arm64/libunion.so.lsdump
index 4f323c7..14dfa66 100644
--- a/vndk/tools/header-checker/tests/reference_dumps/arm64/libunion.so.lsdump
+++ b/vndk/tools/header-checker/tests/reference_dumps/arm64/libunion.so.lsdump
@@ -65,6 +65,9 @@
     },
     {
      "referenced_type" : "_ZTI18ChangeTypeInStruct"
+    },
+    {
+     "referenced_type" : "_ZTI20ReorderAnonymousType"
     }
    ],
    "return_type" : "_ZTIv",
@@ -135,6 +138,24 @@
    [
     {
      "field_name" : "member_1",
+     "referenced_type" : "_ZTIN20ReorderAnonymousTypeUt_E"
+    },
+    {
+     "referenced_type" : "_ZTIN20ReorderAnonymousTypeUt0_E"
+    }
+   ],
+   "linker_set_key" : "_ZTI20ReorderAnonymousType",
+   "name" : "ReorderAnonymousType",
+   "record_kind" : "union",
+   "size" : 4,
+   "source_file" : "development/vndk/tools/header-checker/tests/integration/union/include/base.h"
+  },
+  {
+   "alignment" : 4,
+   "fields" :
+   [
+    {
+     "field_name" : "member_1",
      "referenced_type" : "_ZTIi"
     },
     {
@@ -166,6 +187,36 @@
    "record_kind" : "union",
    "size" : 4,
    "source_file" : "development/vndk/tools/header-checker/tests/integration/union/include/base.h"
+  },
+  {
+   "alignment" : 4,
+   "fields" :
+   [
+    {
+     "field_name" : "member_2",
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "is_anonymous" : true,
+   "linker_set_key" : "_ZTIN20ReorderAnonymousTypeUt0_E",
+   "name" : "ReorderAnonymousType::(anonymous)",
+   "size" : 4,
+   "source_file" : "development/vndk/tools/header-checker/tests/integration/union/include/base.h"
+  },
+  {
+   "alignment" : 4,
+   "fields" :
+   [
+    {
+     "field_name" : "member_1",
+     "referenced_type" : "_ZTIi"
+    }
+   ],
+   "is_anonymous" : true,
+   "linker_set_key" : "_ZTIN20ReorderAnonymousTypeUt_E",
+   "name" : "ReorderAnonymousType::(unnamed)",
+   "size" : 4,
+   "source_file" : "development/vndk/tools/header-checker/tests/integration/union/include/base.h"
   }
  ],
  "rvalue_reference_types" : []