Merge "Build license_checker and test_mapping with Soong." into main
diff --git a/tools/crate-updater/src/main.rs b/tools/crate-updater/src/main.rs
index 107788a..0b2afc1 100644
--- a/tools/crate-updater/src/main.rs
+++ b/tools/crate-updater/src/main.rs
@@ -13,11 +13,9 @@
 // limitations under the License.
 
 use std::{
-    collections::BTreeSet,
     path::{Path, PathBuf},
     process::{Command, ExitStatus, Output},
     str::from_utf8,
-    sync::LazyLock,
 };
 
 use anyhow::{bail, Result};
@@ -268,36 +266,6 @@
     Ok(())
 }
 
-#[rustfmt::skip]
-static DENYLIST: LazyLock<BTreeSet<&'static str>> = LazyLock::new(|| {
-    BTreeSet::from([
-        // Paired crates that need to be updated together
-        "async-stream",
-        "async-stream-impl",
-        "clap",
-        "clap_builder",
-        "linkme",
-        "linkme-impl",
-        "pin-project",
-        "pin-project-internal",
-        "protobuf-support",
-        "protobuf",
-        "ptr_meta",
-        "ptr_meta_derive",
-        "regex",
-        "regex-automata",
-        "regex-syntax",
-        "serde_derive",
-        "serde",
-        "thiserror-impl",
-        "thiserror",
-        "tikv-jemalloc-sys",
-        "tikv-jemallocator",
-        "zerocopy-derive",
-        "zerocopy",
-    ])
-});
-
 fn main() -> Result<()> {
     let args = Cli::parse();
     if !args.android_root.is_absolute() {
@@ -325,10 +293,6 @@
     for suggestion in get_suggestions(&monorepo_path)? {
         let crate_name = suggestion.name.as_str();
         let version = suggestion.version.as_str();
-        if DENYLIST.contains(crate_name) {
-            println!("Skipping {crate_name} (on deny list)");
-            continue;
-        }
         if updates_tried.contains(crate_name, version) {
             println!("Skipping {crate_name} (already attempted recently)");
             continue;
diff --git a/tools/external_crates/Cargo.toml b/tools/external_crates/Cargo.toml
index 4ee0bfd..4e4c644 100644
--- a/tools/external_crates/Cargo.toml
+++ b/tools/external_crates/Cargo.toml
@@ -6,7 +6,6 @@
     "google_metadata",
     "license_checker",
     "name_and_version",
-    "name_and_version_proc_macros",
     "repo_config",
     "rooted_path",
     "test_mapping",
diff --git a/tools/external_crates/cargo_embargo.json b/tools/external_crates/cargo_embargo.json
index 32e2350..6000c7d 100644
--- a/tools/external_crates/cargo_embargo.json
+++ b/tools/external_crates/cargo_embargo.json
@@ -20,9 +20,6 @@
     "name_and_version": {
       "device_supported": false
     },
-    "name_and_version_proc_macros": {
-      "device_supported": false
-    },
     "repo_config": {
       "device_supported": false
     },
diff --git a/tools/external_crates/crate_config/src/lib.rs b/tools/external_crates/crate_config/src/lib.rs
index b6c978b..4e8e0b7 100644
--- a/tools/external_crates/crate_config/src/lib.rs
+++ b/tools/external_crates/crate_config/src/lib.rs
@@ -23,6 +23,9 @@
 pub struct CrateConfig {
     #[serde(default, skip_serializing_if = "Vec::is_empty")]
     deletions: Vec<String>,
+
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    update_with: Vec<String>,
 }
 
 #[allow(missing_docs)]
@@ -52,6 +55,10 @@
     pub fn deletions(&self) -> impl Iterator<Item = &str> {
         self.deletions.iter().map(|d| d.as_str())
     }
+    /// Get an iterator of crates that also need to be updated at the same time as this crate.
+    pub fn update_with(&self) -> impl Iterator<Item = &str> {
+        self.update_with.iter().map(|d| d.as_str())
+    }
 }
 
 #[cfg(test)]
diff --git a/tools/external_crates/crate_tool/Cargo.toml b/tools/external_crates/crate_tool/Cargo.toml
index d49dfd0..5f27d90 100644
--- a/tools/external_crates/crate_tool/Cargo.toml
+++ b/tools/external_crates/crate_tool/Cargo.toml
@@ -29,7 +29,6 @@
 google_metadata = { path = "../google_metadata"}
 license_checker = { path = "../license_checker", features = ["fuzzy_content_match"] }
 name_and_version = { path = "../name_and_version" }
-name_and_version_proc_macros = { path = "../name_and_version_proc_macros" }
 repo_config = { path = "../repo_config" }
 rooted_path = { path = "../rooted_path" }
 test_mapping = { path = "../test_mapping" }
diff --git a/tools/external_crates/crate_tool/src/crate_collection.rs b/tools/external_crates/crate_tool/src/crate_collection.rs
index f1255f3..50fb29a 100644
--- a/tools/external_crates/crate_tool/src/crate_collection.rs
+++ b/tools/external_crates/crate_tool/src/crate_collection.rs
@@ -13,23 +13,18 @@
 // limitations under the License.
 
 use name_and_version::{NameAndVersion, NameAndVersionMap, NamedAndVersioned};
-use name_and_version_proc_macros::NameAndVersionMap;
 use rooted_path::RootedPath;
 
-use std::{
-    collections::HashSet,
-    path::{Path, PathBuf},
-};
+use std::path::{Path, PathBuf};
 
 use anyhow::{anyhow, Result};
-use semver::Version;
 use walkdir::WalkDir;
 
 use crate::{crate_type::Crate, CrateError};
 
 use std::collections::BTreeMap;
 
-#[derive(NameAndVersionMap, Debug)]
+#[derive(Debug)]
 pub struct CrateCollection {
     crates: BTreeMap<NameAndVersion, Crate>,
     repo_root: PathBuf,
@@ -64,4 +59,22 @@
         }
         Ok(())
     }
+    pub fn len(&self) -> usize {
+        self.crates.len()
+    }
+    pub fn get(&self, nv: &dyn NamedAndVersioned) -> Option<&Crate> {
+        self.crates.get(nv)
+    }
+    pub fn values(&self) -> impl Iterator<Item = &Crate> {
+        self.crates.values()
+    }
+    pub fn get_versions(
+        &self,
+        crate_name: impl AsRef<str>,
+    ) -> Box<dyn Iterator<Item = (&NameAndVersion, &Crate)> + '_> {
+        self.crates.get_versions(crate_name.as_ref())
+    }
+    pub fn contains_crate(&self, crate_name: impl AsRef<str>) -> bool {
+        self.crates.contains_name(crate_name.as_ref())
+    }
 }
diff --git a/tools/external_crates/crate_tool/src/managed_crate.rs b/tools/external_crates/crate_tool/src/managed_crate.rs
index c63d089..68bb4a0 100644
--- a/tools/external_crates/crate_tool/src/managed_crate.rs
+++ b/tools/external_crates/crate_tool/src/managed_crate.rs
@@ -237,11 +237,11 @@
     }
     pub fn fix_test_mapping(&self) -> Result<()> {
         let mut tm = TestMapping::read(self.android_crate_path().clone())?;
-        println!("{}", self.name());
         let mut changed = tm.fix_import_paths();
         changed |= tm.add_new_tests_to_postsubmit()?;
         changed |= tm.remove_unknown_tests()?;
         if changed {
+            println!("Updating TEST_MAPPING for {}", self.name());
             tm.write()?;
         }
         Ok(())
@@ -443,6 +443,7 @@
                 writeback |= true;
             }
             if writeback {
+                println!("Updating METADATA for {}", staged.name());
                 metadata.write()?;
             }
         }
diff --git a/tools/external_crates/crate_tool/src/managed_repo.rs b/tools/external_crates/crate_tool/src/managed_repo.rs
index d0c9e0b..0057ae4 100644
--- a/tools/external_crates/crate_tool/src/managed_repo.rs
+++ b/tools/external_crates/crate_tool/src/managed_repo.rs
@@ -29,10 +29,10 @@
 use google_metadata::GoogleMetadata;
 use itertools::Itertools;
 use license_checker::find_licenses;
-use name_and_version::{NameAndVersionMap, NameAndVersionRef, NamedAndVersioned};
+use name_and_version::{NameAndVersion, NameAndVersionMap, NameAndVersionRef, NamedAndVersioned};
 use repo_config::RepoConfig;
 use rooted_path::RootedPath;
-use semver::Version;
+use semver::{Version, VersionReq};
 use serde::Serialize;
 use spdx::Licensee;
 
@@ -106,7 +106,6 @@
                 let cc = self.legacy_crates_for(crate_name)?;
                 let nv = NameAndVersionRef::new(crate_name, v);
                 Ok(cc
-                    .map_field()
                     .get(&nv as &dyn NamedAndVersioned)
                     .ok_or(anyhow!("Failed to find crate {} v{}", crate_name, v))?
                     .path()
@@ -165,7 +164,7 @@
         let krate = match version {
             Some(v) => {
                 let nv = NameAndVersionRef::new(crate_name, v);
-                match cc.map_field().get(&nv as &dyn NamedAndVersioned) {
+                match cc.get(&nv as &dyn NamedAndVersioned) {
                     Some(k) => k,
                     None => {
                         return Err(anyhow!("Did not find crate {} v{}", crate_name, v));
@@ -173,7 +172,7 @@
                 }
             }
             None => {
-                if cc.map_field().len() != 1 {
+                if cc.len() != 1 {
                     return Err(anyhow!(
                         "Expected a single crate version for {}, but found {}. Specify a version with --versions={}=<version>",
                         crate_name,
@@ -181,7 +180,7 @@
                         crate_name
                     ));
                 }
-                cc.map_field().values().next().unwrap()
+                cc.values().next().unwrap()
             }
         };
         println!("Found {} v{} in {}", krate.name(), krate.version(), krate.path());
@@ -428,12 +427,12 @@
             let mut found_problems = false;
             for (dep, req) in version.android_deps_with_version_reqs() {
                 println!("Found dep {}", dep.crate_name());
-                let cc = if managed_crates.contains_name(dep.crate_name()) {
+                let cc = if managed_crates.contains_crate(dep.crate_name()) {
                     &managed_crates
                 } else {
                     &legacy_crates
                 };
-                if !cc.contains_name(dep.crate_name()) {
+                if !cc.contains_crate(dep.crate_name()) {
                     found_problems = true;
                     println!(
                         "  Dep {} {} has not been imported to Android",
@@ -701,7 +700,7 @@
         let mut cc = self.new_cc();
         cc.add_from(self.managed_dir().rel())?;
 
-        for krate in cc.map_field().values() {
+        for krate in cc.values() {
             let cio_crate = self.crates_io.get_crate(krate.name())?;
             let upgrades =
                 cio_crate.versions_gt(krate.version()).map(|v| v.version()).collect::<Vec<_>>();
@@ -776,12 +775,12 @@
             // * If a dep is missing, but the same dep exists for the current version of the crate, it's probably not actually necessary.
             // * Use relaxed version requirements, treating 0.x and 0.y as compatible, even though they aren't according to semver rules.
             for (dep, req) in version.android_deps_with_version_reqs() {
-                let cc = if managed_crates.contains_name(dep.crate_name()) {
+                let cc = if managed_crates.contains_crate(dep.crate_name()) {
                     &managed_crates
                 } else {
                     &legacy_crates
                 };
-                if !cc.contains_name(dep.crate_name()) {
+                if !cc.contains_crate(dep.crate_name()) {
                     found_problems = true;
                     println!(
                         "  Dep {} {} has not been imported to Android",
@@ -831,7 +830,7 @@
         managed_crates.add_from(self.managed_dir().rel())?;
         let legacy_crates = self.legacy_crates()?;
 
-        for krate in managed_crates.map_field().values() {
+        for krate in managed_crates.values() {
             let cio_crate = self.crates_io.get_crate(krate.name())?;
 
             let base_version = cio_crate.get_version(krate.version());
@@ -869,7 +868,7 @@
                     if !dep.is_changed_dep(&base_deps) {
                         return false;
                     }
-                    let cc = if managed_crates.contains_name(dep.crate_name()) {
+                    let cc = if managed_crates.contains_crate(dep.crate_name()) {
                         &managed_crates
                     } else {
                         &legacy_crates
@@ -908,12 +907,55 @@
         Ok(())
     }
     pub fn update(&self, crate_name: impl AsRef<str>, version: impl AsRef<str>) -> Result<()> {
-        let pseudo_crate = self.pseudo_crate();
+        let crate_name = crate_name.as_ref();
         let version = Version::parse(version.as_ref())?;
-        let nv = NameAndVersionRef::new(crate_name.as_ref(), &version);
-        pseudo_crate.remove(&crate_name)?;
-        pseudo_crate.cargo_add(&nv)?;
-        self.regenerate([&crate_name].iter(), true)?;
+
+        let pseudo_crate = self.pseudo_crate();
+        let managed_crate = self.managed_crate_for(crate_name)?;
+        let mut crate_updates = vec![NameAndVersion::new(crate_name.to_string(), version.clone())];
+
+        let cio_crate = self.crates_io.get_crate(crate_name)?;
+        let cio_crate_version = cio_crate
+            .get_version(&version)
+            .ok_or(anyhow!("Could not find {crate_name} {version} on crates.io"))?;
+
+        for dependent_crate_name in managed_crate.config().update_with() {
+            let dep = cio_crate_version
+                .dependencies()
+                .iter()
+                .find(|dep| dep.crate_name() == dependent_crate_name)
+                .ok_or(anyhow!(
+                    "Could not find crate {dependent_crate_name} as a dependency of {crate_name}"
+                ))?;
+            let req = VersionReq::parse(dep.requirement())?;
+            let dep_cio_crate = self.crates_io.get_crate(dependent_crate_name)?;
+            let version = dep_cio_crate
+                .safe_versions()
+                .find(|v| {
+                    if let Ok(parsed_version) = Version::parse(v.version()) {
+                        req.matches(&parsed_version)
+                    } else {
+                        false
+                    }
+                })
+                .ok_or(anyhow!(
+                    "Failed to find a version of {dependent_crate_name} that satisfies {}",
+                    dep.requirement()
+                ))?;
+            println!("Also updating {dependent_crate_name} to {}", version.version());
+            crate_updates.push(NameAndVersion::new(
+                dependent_crate_name.to_string(),
+                Version::parse(version.version())?,
+            ));
+        }
+
+        for nv in &crate_updates {
+            pseudo_crate.remove(nv.name())?;
+        }
+        for nv in &crate_updates {
+            pseudo_crate.cargo_add(nv)?;
+        }
+        self.regenerate(crate_updates.iter().map(|nv| nv.name()), true)?;
         Ok(())
     }
     pub fn init(&self) -> Result<()> {
diff --git a/tools/external_crates/license_checker/src/content_checker.rs b/tools/external_crates/license_checker/src/content_checker.rs
index 7e3a144..6f37105 100644
--- a/tools/external_crates/license_checker/src/content_checker.rs
+++ b/tools/external_crates/license_checker/src/content_checker.rs
@@ -82,6 +82,7 @@
         ("Unlicense", include_str!("licenses/Unlicense.txt")),
         ("Zlib", include_str!("licenses/Zlib.txt")),
         ("OpenSSL", include_str!("licenses/OpenSSL.txt")),
+        ("NCSA", include_str!("licenses/NCSA.txt")),
     ]
     .into_iter()
     .map(|(req, tokens)| {
diff --git a/tools/external_crates/license_checker/src/licenses/NCSA.txt b/tools/external_crates/license_checker/src/licenses/NCSA.txt
new file mode 100644
index 0000000..2df0b9b
--- /dev/null
+++ b/tools/external_crates/license_checker/src/licenses/NCSA.txt
@@ -0,0 +1,26 @@
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal with
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimers.
+
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimers in the
+      documentation and/or other materials provided with the distribution.
+
+    * Neither the names of the LLVM Team, University of Illinois at
+      Urbana-Champaign, nor the names of its contributors may be used to
+      endorse or promote products derived from this Software without specific
+      prior written permission.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
+SOFTWARE.
\ No newline at end of file
diff --git a/tools/external_crates/name_and_version_proc_macros/Android.bp b/tools/external_crates/name_and_version_proc_macros/Android.bp
deleted file mode 100644
index aadadae..0000000
--- a/tools/external_crates/name_and_version_proc_macros/Android.bp
+++ /dev/null
@@ -1,23 +0,0 @@
-// This file is generated by cargo_embargo.
-// Do not modify this file after the first "rust_*" or "genrule" module
-// because the changes will be overridden on upgrade.
-// Content before the first "rust_*" or "genrule" module is preserved.
-
-package {
-    default_team: "trendy_team_android_rust",
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-rust_proc_macro {
-    name: "libname_and_version_proc_macros",
-    crate_name: "name_and_version_proc_macros",
-    cargo_env_compat: true,
-    cargo_pkg_version: "0.1.0",
-    crate_root: "src/lib.rs",
-    edition: "2021",
-    rustlibs: [
-        "libproc_macro2",
-        "libquote",
-        "libsyn",
-    ],
-}
diff --git a/tools/external_crates/name_and_version_proc_macros/Cargo.toml b/tools/external_crates/name_and_version_proc_macros/Cargo.toml
deleted file mode 100644
index 1f72836..0000000
--- a/tools/external_crates/name_and_version_proc_macros/Cargo.toml
+++ /dev/null
@@ -1,12 +0,0 @@
-[package]
-name = "name_and_version_proc_macros"
-version = "0.1.0"
-edition = "2021"
-
-[lib]
-proc-macro = true
-
-[dependencies]
-proc-macro2 = "1"
-syn = "2.0"
-quote = "1.0"
diff --git a/tools/external_crates/name_and_version_proc_macros/src/lib.rs b/tools/external_crates/name_and_version_proc_macros/src/lib.rs
deleted file mode 100644
index 832b2c1..0000000
--- a/tools/external_crates/name_and_version_proc_macros/src/lib.rs
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! Derive the NameAndVersionMap trait for a struct with a suitable map field.
-
-use syn::{parse_macro_input, DeriveInput, Error};
-
-/// Derive the NameAndVersionMap trait for a struct with a suitable map field.
-#[proc_macro_derive(NameAndVersionMap)]
-pub fn derive_name_and_version_map(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
-    let input = parse_macro_input!(input as DeriveInput);
-    name_and_version_map::expand(input).unwrap_or_else(Error::into_compile_error).into()
-}
-
-mod name_and_version_map {
-    use proc_macro2::TokenStream;
-    use quote::quote;
-    use syn::{
-        Data, DataStruct, DeriveInput, Error, Field, GenericArgument, PathArguments, Result, Type,
-    };
-
-    pub(crate) fn expand(input: DeriveInput) -> Result<TokenStream> {
-        let name = &input.ident;
-        let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
-
-        let mapfield = get_map_field(get_struct(&input)?)?;
-        let mapfield_name = mapfield
-            .ident
-            .as_ref()
-            .ok_or(Error::new_spanned(mapfield, "mapfield ident is none"))?;
-        let (_, value_type) = get_map_type(&mapfield.ty)?;
-
-        let expanded = quote! {
-            #[automatically_derived]
-            impl #impl_generics NameAndVersionMap for #name #ty_generics #where_clause {
-                type Value = #value_type;
-
-                fn map_field(&self) -> &BTreeMap<NameAndVersion, Self::Value> {
-                    self.#mapfield_name.map_field()
-                }
-
-                fn map_field_mut(&mut self) -> &mut BTreeMap<NameAndVersion, Self::Value> {
-                    self.#mapfield_name.map_field_mut()
-                }
-
-                fn insert_or_error(&mut self, key: NameAndVersion, val: Self::Value) -> Result<(), name_and_version::Error> {
-                    self.#mapfield_name.insert_or_error(key, val)
-                }
-
-                fn num_crates(&self) -> usize {
-                    self.#mapfield_name.num_crates()
-                }
-
-                fn get_versions<'a, 'b>(&'a self, name: &'b str) -> Box<dyn Iterator<Item = (&'a NameAndVersion, &'a Self::Value)> + 'a> {
-                    self.#mapfield_name.get_versions(name)
-                }
-
-                fn get_versions_mut<'a, 'b>(&'a mut self, name: &'b str) -> Box<dyn Iterator<Item = (&'a NameAndVersion, &'a mut Self::Value)> + 'a> {
-                    self.#mapfield_name.get_versions_mut(name)
-                }
-
-                fn filter_versions<'a: 'b, 'b, F: Fn(&mut dyn Iterator<Item = (&'b NameAndVersion, &'b Self::Value)>,
-                ) -> HashSet<Version> + 'a>(
-                    &'a self,
-                    f: F,
-                ) -> Box<dyn Iterator<Item =(&'a NameAndVersion, &'a Self::Value)> + 'a> {
-                    self.#mapfield_name.filter_versions(f)
-                }
-            }
-        };
-
-        Ok(expanded)
-    }
-
-    fn get_struct(input: &DeriveInput) -> Result<&DataStruct> {
-        match &input.data {
-            Data::Struct(strukt) => Ok(strukt),
-            _ => Err(Error::new_spanned(input, "Not a struct")),
-        }
-    }
-
-    fn get_map_field(strukt: &DataStruct) -> Result<&Field> {
-        for field in &strukt.fields {
-            if let Ok((syn::Type::Path(path), _value_type)) = get_map_type(&field.ty) {
-                if path.path.segments.len() == 1 && path.path.segments[0].ident == "NameAndVersion"
-                {
-                    return Ok(field);
-                }
-            }
-        }
-        Err(Error::new_spanned(strukt.struct_token, "No field of type NameAndVersionMap"))
-    }
-
-    fn get_map_type(typ: &Type) -> Result<(&Type, &Type)> {
-        if let syn::Type::Path(path) = &typ {
-            if path.path.segments.len() == 1 && path.path.segments[0].ident == "BTreeMap" {
-                if let PathArguments::AngleBracketed(args) = &path.path.segments[0].arguments {
-                    if args.args.len() == 2 {
-                        return Ok((get_type(&args.args[0])?, get_type(&args.args[1])?));
-                    }
-                }
-            }
-        }
-        Err(Error::new_spanned(typ, "Must be BTreeMap"))
-    }
-
-    fn get_type(arg: &GenericArgument) -> Result<&Type> {
-        if let GenericArgument::Type(typ) = arg {
-            return Ok(typ);
-        }
-        Err(Error::new_spanned(arg, "Could not extract argument type"))
-    }
-}