Merge "Rename GenericFieldDiffInfo to RecordFieldDiffResult"
diff --git a/apps/Development/src/com/android/development/Connectivity.java b/apps/Development/src/com/android/development/Connectivity.java
index 1d8a98b..e548fbf 100644
--- a/apps/Development/src/com/android/development/Connectivity.java
+++ b/apps/Development/src/com/android/development/Connectivity.java
@@ -631,7 +631,8 @@
         i.putExtra(TEST_ALARM_OFF_EXTRA, Long.toString(mSCOffDuration));
         i.putExtra(TEST_ALARM_CYCLE_EXTRA, Integer.toString(mSCCycleCount));
 
-        PendingIntent p = PendingIntent.getBroadcast(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
+        PendingIntent p = PendingIntent.getBroadcast(this, 0, i,
+                PendingIntent.FLAG_UPDATE_CURRENT|PendingIntent.FLAG_IMMUTABLE);
 
         am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delayMs, p);
     }
diff --git a/python-packages/Android.bp b/python-packages/Android.bp
index 17310f1..ae23602 100644
--- a/python-packages/Android.bp
+++ b/python-packages/Android.bp
@@ -15,10 +15,3 @@
 package {
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
-
-python_library_host {
-  name: "adb_py",
-  srcs: [
-    "adb/*.py",
-  ],
-}
diff --git a/python-packages/adb/Android.bp b/python-packages/adb/Android.bp
new file mode 100644
index 0000000..17310f1
--- /dev/null
+++ b/python-packages/adb/Android.bp
@@ -0,0 +1,24 @@
+// Copyright (C) 2017 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.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_library_host {
+  name: "adb_py",
+  srcs: [
+    "adb/*.py",
+  ],
+}
diff --git a/scripts/cargo2android.py b/scripts/cargo2android.py
index 47196b2..750932a 100755
--- a/scripts/cargo2android.py
+++ b/scripts/cargo2android.py
@@ -71,6 +71,7 @@
     # This map includes all changes to the default rust module names
     # to resolve name conflicts, avoid confusion, or work as plugin.
     'libash': 'libash_rust',
+    'libatomic': 'libatomic_rust',
     'libbacktrace': 'libbacktrace_rust',
     'libbase': 'libbase_rust',
     'libbase64': 'libbase64_rust',
@@ -1200,7 +1201,7 @@
     if os.path.isfile(path2global):
       # try to find: RustDefaultVersion = "1.44.0"
       version_pat = re.compile(
-          r'\s*RustDefaultVersion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+)".*$')
+          r'\s*RustDefaultVersion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+).*"')
       with open(path2global, 'r') as inf:
         for line in inf:
           result = version_pat.match(line)
diff --git a/scripts/update_crate_tests.py b/scripts/update_crate_tests.py
index ec639b1..3a4ea38 100755
--- a/scripts/update_crate_tests.py
+++ b/scripts/update_crate_tests.py
@@ -191,7 +191,41 @@
                 return True
         return False
 
-    def query_rdep_tests_dirs(self, modules, path):
+    # Return all the TEST_MAPPING files within a given path.
+    def find_all_test_mapping_files(self, path):
+        result = []
+        for root, dirs, files in os.walk(path):
+            if "TEST_MAPPING" in files:
+                result.append(os.path.join(root, "TEST_MAPPING"))
+        return result
+
+    # For a given test, return the TEST_MAPPING file where the test is mapped.
+    # This limits the search to the directory specified in "path" along with its subdirs.
+    def test_to_test_mapping(self, env, path, test):
+        test_mapping_files = self.find_all_test_mapping_files(env.ANDROID_BUILD_TOP + path)
+        for file in test_mapping_files:
+            with open(file) as fd:
+                if "\""+ test + "\"" in fd.read():
+                    mapping_path = file.split("/TEST_MAPPING")[0].split("//")[1]
+                    return mapping_path
+
+        return None
+
+    # Returns:
+    # rdep_test: for tests specified locally.
+    # rdep_dirs: for paths to TEST_MAPPING files for reverse dependencies.
+    #
+    # We import directories for non-local tests because including tests directly has proven to be
+    # fragile and burdensome. For example, whenever a project removes or renames a test, all the
+    # TEST_MAPPING files for its reverse dependencies must be updated or we get test breakages.
+    # That can be many tens of projects that must updated to prevent the reported breakage of tests
+    # that no longer exist. Similarly when a test is added, it won't be run when the reverse
+    # dependencies change unless/until update_crate_tests.py is run for its depenencies.
+    # Importing TEST_MAPPING files instead of tests solves both of these problems. When tests are
+    # removed, renamed, or added, only files local to the project need to be modified.
+    # The downside is that we potentially miss some tests. But this seems like a reasonable
+    # tradeoff.
+    def query_rdep_tests_dirs(self, env, modules, path, exclude_dir):
         """Returns all reverse dependency tests for modules in this package."""
         rdep_tests = set()
         rdep_dirs = set()
@@ -204,7 +238,15 @@
                         continue
                     path_match = path_pat.match(mod)
                     if path_match or not EXTERNAL_PAT.match(mod):
-                        rdep_tests.add(mod.split(":")[1].split("--")[0])
+                        rdep_path = mod.split(":")[0]
+                        rdep_test = mod.split(":")[1].split("--")[0]
+                        mapping_path = self.test_to_test_mapping(env, rdep_path, rdep_test)
+                        # Only include tests directly if they're local to the project.
+                        if (mapping_path is not None) and exclude_dir.endswith(mapping_path):
+                            rdep_tests.add(rdep_test)
+                        # All other tests are included by path.
+                        elif mapping_path is not None:
+                            rdep_dirs.add(mapping_path)
                     else:
                         label_match = LABEL_PAT.match(mod)
                         if label_match:
@@ -248,7 +290,8 @@
         # Move to the package_directory.
         os.chdir(self.dir)
         modules = bazel.query_modules(self.dir_rel)
-        (self.rdep_tests, self.rdep_dirs) = bazel.query_rdep_tests_dirs(modules, self.dir_rel)
+        (self.rdep_tests, self.rdep_dirs) = bazel.query_rdep_tests_dirs(env, modules,
+                                                                        self.dir_rel, self.dir)
 
     def get_rdep_tests_dirs(self):
         return (self.rdep_tests, self.rdep_dirs)
@@ -338,7 +381,6 @@
                         help='Pushes change to Gerrit.')
     return parser.parse_args()
 
-
 def main():
     args = parse_args()
     paths = args.paths if len(args.paths) > 0 else [os.getcwd()]
diff --git a/sdk/plat_tools_source.prop_template b/sdk/plat_tools_source.prop_template
index 9eb2a8b..5581bbc 100644
--- a/sdk/plat_tools_source.prop_template
+++ b/sdk/plat_tools_source.prop_template
@@ -1,2 +1,2 @@
 Pkg.UserSrc=false
-Pkg.Revision=33.0.3
+Pkg.Revision=33.0.4
diff --git a/tools/cargo_embargo/Android.bp b/tools/cargo_embargo/Android.bp
new file mode 100644
index 0000000..07abe45
--- /dev/null
+++ b/tools/cargo_embargo/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2022 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.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary_host {
+    name: "cargo_embargo",
+    srcs: ["src/main.rs"],
+    // Disable LTO for faster builds. Don't need the performance here.
+    flags: ["-C lto=off"],
+    rustlibs: [
+        "libanyhow",
+        "libclap",
+        "libglob",
+        "libonce_cell",
+        "libregex",
+        "libserde",
+        "libserde_json",
+    ],
+}
diff --git a/tools/cargo_embargo/OWNERS b/tools/cargo_embargo/OWNERS
new file mode 100644
index 0000000..204e026
--- /dev/null
+++ b/tools/cargo_embargo/OWNERS
@@ -0,0 +1,2 @@
+fmayle@google.com
+smoreland@google.com
diff --git a/tools/cargo_embargo/src/bp.rs b/tools/cargo_embargo/src/bp.rs
new file mode 100644
index 0000000..f660aed
--- /dev/null
+++ b/tools/cargo_embargo/src/bp.rs
@@ -0,0 +1,186 @@
+// Copyright (C) 2022 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.
+
+use anyhow::Result;
+use std::collections::BTreeMap;
+
+/// Build module.
+pub struct BpModule {
+    module_type: String,
+    pub props: BpProperties,
+}
+
+/// Properties of a build module, or of a nested object value.
+#[derive(Clone, PartialEq, Eq)]
+pub struct BpProperties {
+    map: BTreeMap<String, BpValue>,
+    /// A raw block of text to append after the last key-value pair, but before the closing brace.
+    /// For example, if you have the properties
+    ///
+    ///     {
+    ///         name: "foo",
+    ///         srcs: ["main.rs"],
+    ///     }
+    ///
+    /// and add `raw_block = "some random text"`, you'll get
+    ///
+    ///     {
+    ///         name: "foo",
+    ///         srcs: ["main.rs"],
+    ///         some random text
+    ///     }
+    pub raw_block: Option<String>,
+}
+
+#[derive(Clone, PartialEq, Eq)]
+pub enum BpValue {
+    Object(BpProperties),
+    Bool(bool),
+    String(String),
+    List(Vec<BpValue>),
+}
+
+impl BpModule {
+    pub fn new(module_type: String) -> BpModule {
+        BpModule { module_type, props: BpProperties::new() }
+    }
+
+    /// Serialize to Android.bp format.
+    pub fn write(&self, w: &mut impl std::fmt::Write) -> Result<()> {
+        w.write_str(&self.module_type)?;
+        w.write_str(" ")?;
+        self.props.write(w)?;
+        w.write_str("\n")?;
+        Ok(())
+    }
+}
+
+impl BpProperties {
+    pub fn new() -> Self {
+        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 set<T: Into<BpValue>>(&mut self, k: &str, v: T) {
+        self.map.insert(k.to_string(), v.into());
+    }
+
+    pub fn object(&mut self, k: &str) -> &mut BpProperties {
+        let v =
+            self.map.entry(k.to_string()).or_insert_with(|| BpValue::Object(BpProperties::new()));
+        match v {
+            BpValue::Object(v) => v,
+            _ => panic!("key {k:?} already has non-object value"),
+        }
+    }
+
+    /// Serialize to Android.bp format.
+    pub fn write(&self, w: &mut impl std::fmt::Write) -> Result<()> {
+        w.write_str("{\n")?;
+        // Sort stuff to match what cargo2android.py's output order.
+        let canonical_order = &[
+            "name",
+            "defaults",
+            "stem",
+            "host_supported",
+            "prefer_rlib",
+            "crate_name",
+            "cargo_env_compat",
+            "cargo_pkg_version",
+            "srcs",
+            "test_suites",
+            "auto_gen_config",
+            "test_options",
+            "edition",
+            "features",
+            "rustlibs",
+            "proc_macros",
+            "static_libs",
+            "shared_libs",
+            "arch",
+            "target",
+            "ld_flags",
+            "apex_available",
+        ];
+        let mut props: Vec<(&String, &BpValue)> = self.map.iter().collect();
+        props.sort_by_key(|(k, _)| {
+            let i = canonical_order.iter().position(|x| k == x).unwrap_or(canonical_order.len());
+            (i, (*k).clone())
+        });
+        for (k, v) in props {
+            w.write_str(k)?;
+            w.write_str(": ")?;
+            v.write(w)?;
+            w.write_str(",\n")?;
+        }
+        if let Some(raw_block) = &self.raw_block {
+            w.write_str(raw_block)?;
+            w.write_str(",\n")?;
+        }
+        w.write_str("}")?;
+        Ok(())
+    }
+}
+
+impl BpValue {
+    /// Serialize to Android.bp format.
+    pub fn write(&self, w: &mut impl std::fmt::Write) -> Result<()> {
+        match self {
+            BpValue::Object(p) => p.write(w)?,
+            BpValue::Bool(b) => write!(w, "{b}")?,
+            BpValue::String(s) => write!(w, "\"{s}\"")?,
+            BpValue::List(vs) => {
+                w.write_str("[")?;
+                for (i, v) in vs.iter().enumerate() {
+                    v.write(w)?;
+                    if i != vs.len() - 1 {
+                        w.write_str(", ")?;
+                    }
+                }
+                w.write_str("]")?;
+            }
+        }
+        Ok(())
+    }
+}
+
+impl From<bool> for BpValue {
+    fn from(x: bool) -> Self {
+        BpValue::Bool(x)
+    }
+}
+
+impl From<&str> for BpValue {
+    fn from(x: &str) -> Self {
+        BpValue::String(x.to_string())
+    }
+}
+
+impl From<String> for BpValue {
+    fn from(x: String) -> Self {
+        BpValue::String(x)
+    }
+}
+
+impl<T: Into<BpValue>> From<Vec<T>> for BpValue {
+    fn from(x: Vec<T>) -> Self {
+        BpValue::List(x.into_iter().map(|x| x.into()).collect())
+    }
+}
diff --git a/tools/cargo_embargo/src/cargo_out.rs b/tools/cargo_embargo/src/cargo_out.rs
new file mode 100644
index 0000000..2aa6ad4
--- /dev/null
+++ b/tools/cargo_embargo/src/cargo_out.rs
@@ -0,0 +1,384 @@
+// Copyright (C) 2022 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.
+
+use anyhow::anyhow;
+use anyhow::bail;
+use anyhow::Context;
+use anyhow::Result;
+use once_cell::sync::Lazy;
+use regex::Regex;
+use std::collections::BTreeMap;
+use std::path::Path;
+use std::path::PathBuf;
+
+/// Info extracted from `CargoOut` for a crate.
+///
+/// Note that there is a 1-to-many relationship between a Cargo.toml file and these `Crate`
+/// objects. For example, a Cargo.toml file might have a bin, a lib, and various tests. Each of
+/// those will be a separate `Crate`. All of them will have the same `package_name`.
+#[derive(Debug, Default)]
+pub struct Crate {
+    pub name: String,
+    pub package_name: String,
+    pub version: Option<String>,
+    // cargo calls rustc with multiple --crate-type flags.
+    // rustc can accept:
+    //   --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
+    pub types: Vec<String>,
+    pub test: bool,                             // --test
+    pub target: Option<String>,                 // --target
+    pub features: Vec<String>,                  // --cfg feature=
+    pub cfgs: Vec<String>,                      // non-feature --cfg
+    pub externs: Vec<(String, Option<String>)>, // name => rlib file
+    pub codegens: Vec<String>,                  // -C
+    pub cap_lints: String,
+    pub static_libs: Vec<String>,
+    pub shared_libs: Vec<String>,
+    pub emit_list: String,
+    pub edition: String,
+    pub package_dir: PathBuf, // canonicalized
+    pub main_src: PathBuf,    // relative to package_dir
+}
+
+pub fn parse_cargo_out(cargo_out_path: &str, cargo_metadata_path: &str) -> Result<Vec<Crate>> {
+    let metadata: WorkspaceMetadata = serde_json::from_str(
+        &std::fs::read_to_string(cargo_metadata_path).context("failed to read cargo.metadata")?,
+    )
+    .context("failed to parse cargo.metadata")?;
+
+    let cargo_out = CargoOut::parse(
+        &std::fs::read_to_string(cargo_out_path).context("failed to read cargo.out")?,
+    )
+    .context("failed to parse cargo.out")?;
+
+    assert!(cargo_out.cc_invocations.is_empty(), "cc not supported yet");
+    assert!(cargo_out.ar_invocations.is_empty(), "ar not supported yet");
+
+    let mut crates = Vec::new();
+    for rustc in cargo_out.rustc_invocations.iter() {
+        let c = Crate::from_rustc_invocation(rustc, &metadata)
+            .with_context(|| format!("failed to process rustc invocation: {rustc}"))?;
+        // Ignore build.rs crates.
+        if c.name.starts_with("build_script_") {
+            continue;
+        }
+        // Ignore crates outside the current directory.
+        let cwd = std::env::current_dir().unwrap().canonicalize().unwrap();
+        if !c.package_dir.starts_with(cwd) {
+            continue;
+        }
+        crates.push(c);
+    }
+    Ok(crates)
+}
+
+/// `cargo metadata` output.
+#[derive(serde::Deserialize)]
+struct WorkspaceMetadata {
+    packages: Vec<PackageMetadata>,
+}
+
+#[derive(serde::Deserialize)]
+struct PackageMetadata {
+    name: String,
+    version: String,
+    edition: String,
+    manifest_path: String,
+}
+
+/// Raw-ish data extracted from cargo.out file.
+#[derive(Debug, Default)]
+struct CargoOut {
+    rustc_invocations: Vec<String>,
+
+    // package name => cmd args
+    cc_invocations: BTreeMap<String, String>,
+    ar_invocations: BTreeMap<String, String>,
+
+    // lines starting with "warning: ".
+    // line number => line
+    warning_lines: BTreeMap<usize, String>,
+    warning_files: Vec<String>,
+
+    errors: Vec<String>,
+    test_errors: Vec<String>,
+}
+
+fn match1(regex: &Regex, s: &str) -> Option<String> {
+    regex.captures(s).and_then(|x| x.get(1)).map(|x| x.as_str().to_string())
+}
+
+fn match3(regex: &Regex, s: &str) -> Option<(String, String, String)> {
+    regex.captures(s).and_then(|x| match (x.get(1), x.get(2), x.get(3)) {
+        (Some(a), Some(b), Some(c)) => {
+            Some((a.as_str().to_string(), b.as_str().to_string(), c.as_str().to_string()))
+        }
+        _ => None,
+    })
+}
+
+impl CargoOut {
+    /// Parse the output of a `cargo build -v` run.
+    fn parse(contents: &str) -> Result<CargoOut> {
+        let mut result = CargoOut::default();
+        let mut in_tests = false;
+        let mut lines_iter = contents.lines().enumerate();
+        while let Some((n, line)) = lines_iter.next() {
+            if line.starts_with("warning: ") {
+                result.warning_lines.insert(n, line.to_string());
+                continue;
+            }
+
+            // Cargo -v output of a call to rustc.
+            static RUSTC_REGEX: Lazy<Regex> =
+                Lazy::new(|| Regex::new(r"^ +Running `rustc (.*)`$").unwrap());
+            if let Some(args) = match1(&RUSTC_REGEX, line) {
+                result.rustc_invocations.push(args);
+                continue;
+            }
+            // Cargo -vv output of a call to rustc could be split into multiple lines.
+            // Assume that the first line will contain some CARGO_* env definition.
+            static RUSTC_VV_REGEX: Lazy<Regex> =
+                Lazy::new(|| Regex::new(r"^ +Running `.*CARGO_.*=.*$").unwrap());
+            if RUSTC_VV_REGEX.is_match(line) {
+                // cargo build -vv output can have multiple lines for a rustc command due to
+                // '\n' in strings for environment variables.
+                let mut line = line.to_string();
+                loop {
+                    // Use an heuristic to detect the completions of a multi-line command.
+                    if line.ends_with('`') && line.chars().filter(|c| *c == '`').count() % 2 == 0 {
+                        break;
+                    }
+                    if let Some((_, next_line)) = lines_iter.next() {
+                        line += next_line;
+                        continue;
+                    }
+                    break;
+                }
+                // The combined -vv output rustc command line pattern.
+                static RUSTC_VV_CMD_ARGS: Lazy<Regex> =
+                    Lazy::new(|| Regex::new(r"^ *Running `.*CARGO_.*=.* rustc (.*)`$").unwrap());
+                if let Some(args) = match1(&RUSTC_VV_CMD_ARGS, &line) {
+                    result.rustc_invocations.push(args);
+                } else {
+                    bail!("failed to parse cargo.out line: {}", line);
+                }
+                continue;
+            }
+            // Cargo -vv output of a "cc" or "ar" command; all in one line.
+            static CC_AR_VV_REGEX: Lazy<Regex> = Lazy::new(|| {
+                Regex::new(r#"^\[([^ ]*)[^\]]*\] running:? "(cc|ar)" (.*)$"#).unwrap()
+            });
+            if let Some((pkg, cmd, args)) = match3(&CC_AR_VV_REGEX, line) {
+                match cmd.as_str() {
+                    "ar" => result.ar_invocations.insert(pkg, args),
+                    "cc" => result.cc_invocations.insert(pkg, args),
+                    _ => unreachable!(),
+                };
+                continue;
+            }
+            // Rustc output of file location path pattern for a warning message.
+            static WARNING_FILE_REGEX: Lazy<Regex> =
+                Lazy::new(|| Regex::new(r"^ *--> ([^:]*):[0-9]+").unwrap());
+            if result.warning_lines.contains_key(&n.saturating_sub(1)) {
+                if let Some(fpath) = match1(&WARNING_FILE_REGEX, line) {
+                    result.warning_files.push(fpath);
+                    continue;
+                }
+            }
+            if line.starts_with("error: ") || line.starts_with("error[E") {
+                if in_tests {
+                    result.test_errors.push(line.to_string());
+                } else {
+                    result.errors.push(line.to_string());
+                }
+                continue;
+            }
+            static CARGO2ANDROID_RUNNING_REGEX: Lazy<Regex> =
+                Lazy::new(|| Regex::new(r"^### Running: .*$").unwrap());
+            if CARGO2ANDROID_RUNNING_REGEX.is_match(line) {
+                in_tests = line.contains("cargo test") && line.contains("--list");
+                continue;
+            }
+        }
+
+        // self.find_warning_owners()
+
+        Ok(result)
+    }
+}
+
+impl Crate {
+    fn from_rustc_invocation(rustc: &str, metadata: &WorkspaceMetadata) -> Result<Crate> {
+        let mut out = Crate::default();
+
+        // split into args
+        let args: Vec<&str> = rustc.split_whitespace().collect();
+        let mut arg_iter = args
+            .iter()
+            // Remove quotes from simple strings, panic for others.
+            .map(|arg| match (arg.chars().next(), arg.chars().skip(1).last()) {
+                (Some('"'), Some('"')) => &arg[1..arg.len() - 1],
+                (Some('\''), Some('\'')) => &arg[1..arg.len() - 1],
+                (Some('"'), _) => panic!("can't handle strings with whitespace"),
+                (Some('\''), _) => panic!("can't handle strings with whitespace"),
+                _ => arg,
+            });
+        // process each arg
+        while let Some(arg) = arg_iter.next() {
+            match arg {
+                "--crate-name" => out.name = arg_iter.next().unwrap().to_string(),
+                "--crate-type" => out.types.push(arg_iter.next().unwrap().to_string()),
+                "--test" => out.test = true,
+                "--target" => out.target = Some(arg_iter.next().unwrap().to_string()),
+                "--cfg" => {
+                    // example: feature=\"sink\"
+                    let arg = arg_iter.next().unwrap();
+                    if let Some(feature) =
+                        arg.strip_prefix("feature=\"").and_then(|s| s.strip_suffix('\"'))
+                    {
+                        out.features.push(feature.to_string());
+                    } else {
+                        out.cfgs.push(arg.to_string());
+                    }
+                }
+                "--extern" => {
+                    // example: proc_macro
+                    // example: memoffset=/some/path/libmemoffset-2cfda327d156e680.rmeta
+                    let arg = arg_iter.next().unwrap();
+                    if let Some((name, path)) = arg.split_once('=') {
+                        out.externs.push((
+                            name.to_string(),
+                            Some(path.split('/').last().unwrap().to_string()),
+                        ));
+                    } else {
+                        out.externs.push((arg.to_string(), None));
+                    }
+                }
+                _ if arg.starts_with("-C") => {
+                    // handle both "-Cfoo" and "-C foo"
+                    let arg = if arg == "-C" {
+                        arg_iter.next().unwrap()
+                    } else {
+                        arg.strip_prefix("-C").unwrap()
+                    };
+                    // 'prefer-dynamic' does not work with common flag -C lto
+                    // 'embed-bitcode' is ignored; we might control LTO with other .bp flag
+                    // 'codegen-units' is set in Android global config or by default
+                    //
+                    // TODO: this is business logic. move it out of the parsing code
+                    if !arg.starts_with("codegen-units=")
+                        && !arg.starts_with("debuginfo=")
+                        && !arg.starts_with("embed-bitcode=")
+                        && !arg.starts_with("extra-filename=")
+                        && !arg.starts_with("incremental=")
+                        && !arg.starts_with("metadata=")
+                        && arg != "prefer-dynamic"
+                    {
+                        out.codegens.push(arg.to_string());
+                    }
+                }
+                "--cap-lints" => out.cap_lints = arg_iter.next().unwrap().to_string(),
+                "-l" => {
+                    let arg = arg_iter.next().unwrap();
+                    if let Some(lib) = arg.strip_prefix("static=") {
+                        out.static_libs.push(lib.to_string());
+                    } else if let Some(lib) = arg.strip_prefix("dylib=") {
+                        out.shared_libs.push(lib.to_string());
+                    } else {
+                        out.shared_libs.push(arg.to_string());
+                    }
+                }
+                _ if arg.starts_with("--emit=") => {
+                    out.emit_list = arg.strip_prefix("--emit=").unwrap().to_string();
+                }
+                _ if !arg.starts_with('-') => {
+                    let src_path = Path::new(arg);
+                    // Canonicalize the path because:
+                    //
+                    // 1. We don't consistently get relative or absolute paths elsewhere. If we
+                    //    canonicalize everything, it becomes easy to compare paths.
+                    //
+                    // 2. We don't want to consider symlinks to code outside the cwd as part of the
+                    //    project (e.g. AOSP's import of crosvm has symlinks from crosvm's own 3p
+                    //    directory to the android 3p directories).
+                    let src_path = src_path
+                        .canonicalize()
+                        .unwrap_or_else(|e| panic!("failed to canonicalize {src_path:?}: {}", e));
+                    out.package_dir = src_path.parent().unwrap().to_path_buf();
+                    while !out.package_dir.join("Cargo.toml").try_exists()? {
+                        if let Some(parent) = out.package_dir.parent() {
+                            out.package_dir = parent.to_path_buf();
+                        } else {
+                            bail!("No Cargo.toml found in parents of {:?}", src_path);
+                        }
+                    }
+                    out.main_src = src_path.strip_prefix(&out.package_dir).unwrap().to_path_buf();
+                }
+
+                // ignored flags
+                "-L" => {
+                    arg_iter.next().unwrap();
+                }
+                "--out-dir" => {
+                    arg_iter.next().unwrap();
+                }
+                "--color" => {
+                    arg_iter.next().unwrap();
+                }
+                _ if arg.starts_with("--error-format=") => {}
+                _ if arg.starts_with("--edition=") => {}
+                _ if arg.starts_with("--json=") => {}
+                _ if arg.starts_with("-Aclippy") => {}
+                _ if arg.starts_with("-Wclippy") => {}
+                "-W" => {}
+                "-D" => {}
+
+                arg => bail!("unsupported rustc argument: {arg:?}"),
+            }
+        }
+
+        if out.name.is_empty() {
+            bail!("missing --crate-name");
+        }
+        if out.main_src.as_os_str().is_empty() {
+            bail!("missing main source file");
+        }
+        if out.types.is_empty() != out.test {
+            bail!("expected exactly one of either --crate-type or --test");
+        }
+        if out.types.iter().any(|x| x == "lib") && out.types.iter().any(|x| x == "rlib") {
+            bail!("cannot both have lib and rlib crate types");
+        }
+
+        // Find the metadata for the crates containing package by matching the manifest's path.
+        let manifest_path = out.package_dir.join("Cargo.toml");
+        let package_metadata = metadata
+            .packages
+            .iter()
+            .find(|p| Path::new(&p.manifest_path).canonicalize().unwrap() == manifest_path)
+            .ok_or_else(|| {
+                anyhow!(
+                    "can't find metadata for crate {:?} with manifest path {:?}",
+                    out.name,
+                    manifest_path,
+                )
+            })?;
+        out.package_name = package_metadata.name.clone();
+        out.version = Some(package_metadata.version.clone());
+        out.edition = package_metadata.edition.clone();
+
+        Ok(out)
+    }
+}
diff --git a/tools/cargo_embargo/src/main.rs b/tools/cargo_embargo/src/main.rs
new file mode 100644
index 0000000..b39af22
--- /dev/null
+++ b/tools/cargo_embargo/src/main.rs
@@ -0,0 +1,581 @@
+// Copyright (C) 2022 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.
+
+//! Converts a cargo project to Soong.
+//!
+//! Forked from development/scripts/cargo2android.py. Missing many of its features. Adds various
+//! features to make it easier to work with projects containing many crates.
+//!
+//! At a high level, this is done by
+//!
+//!     1. Running `cargo build -v` and saving the output to a "cargo.out" file.
+//!     2. Parsing the "cargo.out" file to find invocations of compilers, e.g. `rustc` and `cc`.
+//!     3. For each compiler invocation, generating a equivalent Soong module, e.g. a "rust_library".
+//!
+//! The last step often involves messy, project specific business logic, so many options are
+//! available to tweak it via a config file.
+
+mod bp;
+mod cargo_out;
+
+use anyhow::bail;
+use anyhow::Context;
+use anyhow::Result;
+use bp::*;
+use cargo_out::*;
+use clap::Parser;
+use once_cell::sync::Lazy;
+use regex::Regex;
+use std::collections::BTreeMap;
+use std::collections::VecDeque;
+use std::fs::File;
+use std::io::Write;
+use std::path::Path;
+use std::path::PathBuf;
+use std::process::Command;
+
+// Major TODOs
+//  * handle errors, esp. in cargo.out parsing. they should fail the program with an error code
+//  * handle warnings. put them in comments in the android.bp, some kind of report section
+
+#[derive(Parser, Debug)]
+#[clap()]
+struct Args {
+    /// Use the cargo binary in the `cargo_bin` directory. Defaults to cargo in $PATH.
+    ///
+    /// TODO: Should default to android prebuilts.
+    #[clap(long)]
+    cargo_bin: Option<PathBuf>,
+    /// Config file.
+    #[clap(long)]
+    cfg: PathBuf,
+    /// Skip the `cargo build` commands and reuse the "cargo.out" file from a previous run if
+    /// available.
+    #[clap(long)]
+    reuse_cargo_out: bool,
+}
+
+fn default_apex_available() -> Vec<String> {
+    vec!["//apex_available:platform".to_string(), "//apex_available:anyapex".to_string()]
+}
+
+/// Options that apply to everything.
+#[derive(serde::Deserialize)]
+#[serde(deny_unknown_fields)]
+struct Config {
+    /// Whether to output "rust_test" modules.
+    tests: bool,
+    /// Set of features to enable. If non-empty, disables the default crate features.
+    #[serde(default)]
+    features: Vec<String>,
+    /// Whether to build with --workspace.
+    #[serde(default)]
+    workspace: bool,
+    /// When workspace is enabled, list of --exclude crates.
+    #[serde(default)]
+    workspace_excludes: Vec<String>,
+    /// Value to use for every generated module's "defaults" field.
+    global_defaults: Option<String>,
+    /// Value to use for every generated library module's "apex_available" field.
+    #[serde(default = "default_apex_available")]
+    apex_available: Vec<String>,
+    /// Map of renames for modules. For example, if a "libfoo" would be generated and there is an
+    /// entry ("libfoo", "libbar"), the generated module will be called "libbar" instead.
+    ///
+    /// Also, affects references to dependencies (e.g. in a "static_libs" list), even those outside
+    /// the project being processed.
+    #[serde(default)]
+    module_name_overrides: BTreeMap<String, String>,
+    /// Package specific config options.
+    #[serde(default)]
+    package: BTreeMap<String, PackageConfig>,
+    /// Modules in this list will not be generated.
+    #[serde(default)]
+    module_blocklist: Vec<String>,
+}
+
+/// Options that apply to everything in a package (i.e. everything associated with a particular
+/// Cargo.toml file).
+#[derive(serde::Deserialize, Default)]
+#[serde(deny_unknown_fields)]
+struct PackageConfig {
+    /// Whether to compile for device. Defaults to true.
+    #[serde(default)]
+    device_supported: Option<bool>,
+    /// Whether to compile for host. Defaults to true.
+    #[serde(default)]
+    host_supported: Option<bool>,
+    /// Generate "rust_library_rlib" instead of "rust_library".
+    #[serde(default)]
+    force_rlib: bool,
+    /// Whether to disable "unit_test" for "rust_test" modules.
+    // TODO: Should probably be a list of modules or crates. A package might have a mix of unit and
+    // integration tests.
+    #[serde(default)]
+    no_presubmit: bool,
+    /// File with content to append to the end of the generated Android.bp.
+    add_toplevel_block: Option<PathBuf>,
+    /// File with content to append to the end of each generated module.
+    add_module_block: Option<PathBuf>,
+    /// Modules in this list will not be added as dependencies of generated modules.
+    #[serde(default)]
+    dep_blocklist: Vec<String>,
+    /// Patch file to apply after Android.bp is generated.
+    patch: Option<PathBuf>,
+    /// Copy build.rs output to ./out/* and add a genrule to copy ./out/* to genrule output.
+    /// For crates with code pattern:
+    ///     include!(concat!(env!("OUT_DIR"), "/<some_file>.rs"))
+    #[serde(default)]
+    copy_out: bool,
+}
+
+fn main() -> Result<()> {
+    let args = Args::parse();
+
+    let json_str = std::fs::read_to_string(&args.cfg)
+        .with_context(|| format!("failed to read file: {:?}", args.cfg))?;
+    // Add some basic support for comments to JSON.
+    let json_str: String = json_str.lines().filter(|l| !l.trim_start().starts_with("//")).collect();
+    let cfg: Config = serde_json::from_str(&json_str).context("failed to parse config")?;
+
+    if !Path::new("Cargo.toml").try_exists().context("when checking Cargo.toml")? {
+        bail!("Cargo.toml missing. Run in a directory with a Cargo.toml file.");
+    }
+
+    // Add the custom cargo to PATH.
+    // NOTE: If the directory with cargo has more binaries, this could have some unpredictable side
+    // effects. That is partly intended though, because we want to use that cargo binary's
+    // associated rustc.
+    if let Some(cargo_bin) = args.cargo_bin {
+        let path = std::env::var_os("PATH").unwrap();
+        let mut paths = std::env::split_paths(&path).collect::<VecDeque<_>>();
+        paths.push_front(cargo_bin);
+        let new_path = std::env::join_paths(paths)?;
+        std::env::set_var("PATH", new_path);
+    }
+
+    let cargo_out_path = "cargo.out";
+    let cargo_metadata_path = "cargo.metadata";
+    if !args.reuse_cargo_out || !Path::new(cargo_out_path).exists() {
+        generate_cargo_out(&cfg, cargo_out_path, cargo_metadata_path)
+            .context("generate_cargo_out failed")?;
+    }
+
+    let crates =
+        parse_cargo_out(cargo_out_path, cargo_metadata_path).context("parse_cargo_out failed")?;
+
+    // Find out files.
+    // Example: target.tmp/x86_64-unknown-linux-gnu/debug/build/metrics-d2dd799cebf1888d/out/event_details.rs
+    let mut package_out_files: BTreeMap<String, Vec<PathBuf>> = BTreeMap::new();
+    if cfg.package.iter().any(|(_, v)| v.copy_out) {
+        for entry in glob::glob("target.tmp/**/build/*/out/*")? {
+            match entry {
+                Ok(path) => {
+                    let package_name = || -> Option<_> {
+                        let dir_name = path.parent()?.parent()?.file_name()?.to_str()?;
+                        Some(dir_name.rsplit_once('-')?.0)
+                    }()
+                    .unwrap_or_else(|| panic!("failed to parse out file path: {:?}", path));
+                    package_out_files
+                        .entry(package_name.to_string())
+                        .or_default()
+                        .push(path.clone());
+                }
+                Err(e) => eprintln!("failed to check for out files: {}", e),
+            }
+        }
+    }
+
+    // Group by package.
+    let mut module_by_package: BTreeMap<PathBuf, Vec<Crate>> = BTreeMap::new();
+    for c in crates {
+        module_by_package.entry(c.package_dir.clone()).or_default().push(c);
+    }
+    // Write an Android.bp file per package.
+    for (package_dir, crates) in module_by_package {
+        write_android_bp(
+            &cfg,
+            package_dir,
+            &crates,
+            package_out_files.get(&crates[0].package_name),
+        )?;
+    }
+
+    Ok(())
+}
+
+fn run_cargo(cargo_out: &mut File, cmd: &mut Command) -> Result<()> {
+    use std::os::unix::io::OwnedFd;
+    use std::process::Stdio;
+    let fd: OwnedFd = cargo_out.try_clone()?.into();
+    // eprintln!("Running: {:?}\n", cmd);
+    let output = cmd.stdout(Stdio::from(fd.try_clone()?)).stderr(Stdio::from(fd)).output()?;
+    if !output.status.success() {
+        bail!("cargo command failed with exit status: {:?}", output.status);
+    }
+    Ok(())
+}
+
+/// Run various cargo commands and save the output to `cargo_out_path`.
+fn generate_cargo_out(cfg: &Config, cargo_out_path: &str, cargo_metadata_path: &str) -> Result<()> {
+    let mut cargo_out_file = std::fs::File::create(cargo_out_path)?;
+    let mut cargo_metadata_file = std::fs::File::create(cargo_metadata_path)?;
+
+    let verbose_args = ["-v"];
+    let target_dir_args = ["--target-dir", "target.tmp"];
+
+    // cargo clean
+    run_cargo(&mut cargo_out_file, Command::new("cargo").arg("clean").args(target_dir_args))?;
+
+    let default_target = "x86_64-unknown-linux-gnu";
+    let feature_args = if cfg.features.is_empty() {
+        vec![]
+    } else {
+        vec!["--no-default-features".to_string(), "--features".to_string(), cfg.features.join(",")]
+    };
+
+    let workspace_args = if cfg.workspace {
+        let mut v = vec!["--workspace".to_string()];
+        if !cfg.workspace_excludes.is_empty() {
+            for x in cfg.workspace_excludes.iter() {
+                v.push("--exclude".to_string());
+                v.push(x.clone());
+            }
+        }
+        v
+    } else {
+        vec![]
+    };
+
+    // cargo metadata
+    run_cargo(
+        &mut cargo_metadata_file,
+        Command::new("cargo")
+            .arg("metadata")
+            .arg("-q") // don't output warnings to stderr
+            .arg("--format-version")
+            .arg("1")
+            .args(&feature_args),
+    )?;
+
+    // cargo build
+    run_cargo(
+        &mut cargo_out_file,
+        Command::new("cargo")
+            .args(["build", "--target", default_target])
+            .args(verbose_args)
+            .args(target_dir_args)
+            .args(&workspace_args)
+            .args(&feature_args),
+    )?;
+
+    if cfg.tests {
+        // cargo build --tests
+        run_cargo(
+            &mut cargo_out_file,
+            Command::new("cargo")
+                .args(["build", "--target", default_target, "--tests"])
+                .args(verbose_args)
+                .args(target_dir_args)
+                .args(&workspace_args)
+                .args(&feature_args),
+        )?;
+    }
+
+    Ok(())
+}
+
+/// Create the Android.bp file for `package_dir`.
+fn write_android_bp(
+    cfg: &Config,
+    package_dir: PathBuf,
+    crates: &[Crate],
+    out_files: Option<&Vec<PathBuf>>,
+) -> Result<()> {
+    let bp_path = package_dir.join("Android.bp");
+
+    let package_name = crates[0].package_name.clone();
+    let def = PackageConfig::default();
+    let package_cfg = cfg.package.get(&package_name).unwrap_or(&def);
+
+    // Keep the old license header.
+    let license_section = match std::fs::read_to_string(&bp_path) {
+        Ok(s) => s
+            .lines()
+            .skip_while(|l| l.starts_with("//"))
+            .take_while(|l| !l.starts_with("rust_") && !l.starts_with("genrule {"))
+            .collect::<Vec<&str>>()
+            .join("\n"),
+        Err(e) if e.kind() == std::io::ErrorKind::NotFound => "// TODO: Add license.\n".to_string(),
+        Err(e) => bail!("error when reading {bp_path:?}: {e}"),
+    };
+
+    let mut bp_contents = String::new();
+    bp_contents += "// This file is generated by cargo_embargo.\n";
+    bp_contents += "// Do not modify this file as changes will be overridden on upgrade.\n\n";
+    bp_contents += license_section.trim();
+    bp_contents += "\n";
+
+    let mut modules = Vec::new();
+
+    let extra_srcs = match (package_cfg.copy_out, out_files) {
+        (true, Some(out_files)) => {
+            let out_dir = package_dir.join("out");
+            if !out_dir.exists() {
+                std::fs::create_dir(&out_dir).expect("failed to create out dir");
+            }
+
+            let mut outs: Vec<String> = Vec::new();
+            for f in out_files.iter() {
+                let dest = out_dir.join(f.file_name().unwrap());
+                std::fs::copy(f, dest).expect("failed to copy out file");
+                outs.push(f.file_name().unwrap().to_str().unwrap().to_string());
+            }
+
+            let mut m = BpModule::new("genrule".to_string());
+            let module_name = format!("copy_{}_build_out", package_name);
+            m.props.set("name", module_name.clone());
+            m.props.set("srcs", vec!["out/*"]);
+            m.props.set("cmd", "cp $(in) $(genDir)");
+            m.props.set("out", outs);
+            modules.push(m);
+
+            vec![":".to_string() + &module_name]
+        }
+        _ => vec![],
+    };
+
+    for c in crates {
+        modules.extend(crate_to_bp_modules(c, cfg, package_cfg, &extra_srcs)?);
+    }
+    if modules.is_empty() {
+        return Ok(());
+    }
+
+    modules.sort_by_key(|m| m.props.get_string("name").to_string());
+    for m in modules {
+        m.write(&mut bp_contents)?;
+        bp_contents += "\n";
+    }
+    if let Some(path) = &package_cfg.add_toplevel_block {
+        bp_contents +=
+            &std::fs::read_to_string(path).with_context(|| format!("failed to read {path:?}"))?;
+        bp_contents += "\n";
+    }
+    File::create(&bp_path)?.write_all(bp_contents.as_bytes())?;
+
+    let bpfmt_output = Command::new("bpfmt").arg("-w").arg(&bp_path).output()?;
+    if !bpfmt_output.status.success() {
+        eprintln!(
+            "WARNING: bpfmt -w {:?} failed before patch: {}",
+            bp_path,
+            String::from_utf8_lossy(&bpfmt_output.stderr)
+        );
+    }
+
+    if let Some(patch_path) = &package_cfg.patch {
+        let patch_output =
+            Command::new("patch").arg("-s").arg(&bp_path).arg(patch_path).output()?;
+        if !patch_output.status.success() {
+            eprintln!("WARNING: failed to apply patch {:?}", patch_path);
+        }
+        // Re-run bpfmt after the patch so
+        let bpfmt_output = Command::new("bpfmt").arg("-w").arg(&bp_path).output()?;
+        if !bpfmt_output.status.success() {
+            eprintln!(
+                "WARNING: bpfmt -w {:?} failed after patch: {}",
+                bp_path,
+                String::from_utf8_lossy(&bpfmt_output.stderr)
+            );
+        }
+    }
+
+    Ok(())
+}
+
+/// Convert a `Crate` into `BpModule`s.
+///
+/// If messy business logic is necessary, prefer putting it here.
+fn crate_to_bp_modules(
+    crate_: &Crate,
+    cfg: &Config,
+    package_cfg: &PackageConfig,
+    extra_srcs: &[String],
+) -> Result<Vec<BpModule>> {
+    let mut modules = Vec::new();
+    let mut types = crate_.types.clone();
+    if crate_.test {
+        types.push("test".to_string());
+    }
+    for crate_type in types {
+        let host = if package_cfg.device_supported.unwrap_or(true) { "" } else { "_host" };
+        let rlib = if package_cfg.force_rlib { "_rlib" } else { "" };
+        let (module_type, module_name, stem) = match crate_type.as_str() {
+            "bin" => ("rust_binary".to_string() + host, crate_.name.clone(), crate_.name.clone()),
+            "lib" | "rlib" => {
+                let stem = "lib".to_string() + &crate_.name;
+                ("rust_library".to_string() + rlib + host, stem.clone(), stem)
+            }
+            "dylib" => {
+                let stem = "lib".to_string() + &crate_.name;
+                ("rust_library".to_string() + host + "_dylib", stem.clone() + "_dylib", stem)
+            }
+            "cdylib" => {
+                let stem = "lib".to_string() + &crate_.name;
+                ("rust_ffi".to_string() + host + "_shared", stem.clone() + "_shared", stem)
+            }
+            "staticlib" => {
+                let stem = "lib".to_string() + &crate_.name;
+                ("rust_ffi".to_string() + host + "_static", stem.clone() + "_static", stem)
+            }
+            "proc-macro" => {
+                let stem = "lib".to_string() + &crate_.name;
+                ("rust_proc_macro".to_string(), stem.clone(), stem)
+            }
+            "test" => {
+                let suffix = crate_.main_src.to_string_lossy().into_owned();
+                let suffix = suffix.replace('/', "_").replace(".rs", "");
+                let stem = crate_.package_name.clone() + "_test_" + &suffix;
+                ("rust_test".to_string() + host, stem.clone(), stem)
+            }
+            _ => panic!("unexpected crate type: {}", crate_type),
+        };
+
+        let mut m = BpModule::new(module_type.clone());
+        let module_name = cfg.module_name_overrides.get(&module_name).unwrap_or(&module_name);
+        if cfg.module_blocklist.contains(module_name) {
+            continue;
+        }
+        m.props.set("name", module_name.clone());
+        if &stem != module_name {
+            m.props.set("stem", stem);
+        }
+
+        if let Some(defaults) = &cfg.global_defaults {
+            m.props.set("defaults", vec![defaults.clone()]);
+        }
+
+        if package_cfg.host_supported.unwrap_or(true)
+            && package_cfg.device_supported.unwrap_or(true)
+            && module_type != "rust_proc_macro"
+        {
+            m.props.set("host_supported", true);
+        }
+
+        m.props.set("crate_name", crate_.name.clone());
+        m.props.set("cargo_env_compat", true);
+
+        if let Some(version) = &crate_.version {
+            m.props.set("cargo_pkg_version", version.clone());
+        }
+
+        if crate_.test {
+            m.props.set("test_suites", vec!["general-tests"]);
+            m.props.set("auto_gen_config", true);
+            if package_cfg.host_supported.unwrap_or(true) {
+                m.props.object("test_options").set("unit_test", !package_cfg.no_presubmit);
+            }
+        }
+
+        let mut srcs = vec![crate_.main_src.to_string_lossy().to_string()];
+        srcs.extend(extra_srcs.iter().cloned());
+        m.props.set("srcs", srcs);
+
+        m.props.set("edition", crate_.edition.clone());
+        if !crate_.features.is_empty() {
+            m.props.set("features", crate_.features.clone());
+        }
+        if !crate_.cfgs.is_empty() {
+            m.props.set("cfgs", crate_.cfgs.clone());
+        }
+
+        let mut flags = Vec::new();
+        if !crate_.cap_lints.is_empty() {
+            flags.push(crate_.cap_lints.clone());
+        }
+        flags.extend(crate_.codegens.clone());
+        if !flags.is_empty() {
+            m.props.set("flags", flags);
+        }
+
+        let mut rust_libs = Vec::new();
+        let mut proc_macro_libs = Vec::new();
+        for (extern_name, filename) in &crate_.externs {
+            if extern_name == "proc_macro" {
+                continue;
+            }
+            let filename =
+                filename.as_ref().unwrap_or_else(|| panic!("no filename for {}", extern_name));
+            // Example filename: "libgetrandom-fd8800939535fc59.rmeta"
+            static REGEX: Lazy<Regex> =
+                Lazy::new(|| Regex::new(r"^lib(.*)-[0-9a-f]*.(rlib|so|rmeta)$").unwrap());
+            let lib_name = if let Some(x) = REGEX.captures(filename).and_then(|x| x.get(1)) {
+                x
+            } else {
+                bail!("bad filename for extern {}: {}", extern_name, filename);
+            };
+            if filename.ends_with(".rlib") || filename.ends_with(".rmeta") {
+                rust_libs.push(lib_name.as_str().to_string());
+            } else if filename.ends_with(".so") {
+                // Assume .so files are always proc_macros. May not always be right.
+                proc_macro_libs.push(lib_name.as_str().to_string());
+            } else {
+                unreachable!();
+            }
+        }
+
+        // Add "lib" prefix and apply name overrides.
+        let process_lib_deps = |libs: Vec<String>| -> Vec<String> {
+            let mut result = Vec::new();
+            for x in libs {
+                let module_name = "lib".to_string() + x.as_str();
+                let module_name =
+                    cfg.module_name_overrides.get(&module_name).unwrap_or(&module_name);
+                if package_cfg.dep_blocklist.contains(module_name) {
+                    continue;
+                }
+                result.push(module_name.to_string());
+            }
+            result.sort();
+            result
+        };
+        if !rust_libs.is_empty() {
+            m.props.set("rustlibs", process_lib_deps(rust_libs));
+        }
+        if !proc_macro_libs.is_empty() {
+            m.props.set("proc_macros", process_lib_deps(proc_macro_libs));
+        }
+        if !crate_.static_libs.is_empty() {
+            m.props.set("static_libs", process_lib_deps(crate_.static_libs.clone()));
+        }
+        if !crate_.shared_libs.is_empty() {
+            m.props.set("shared_libs", process_lib_deps(crate_.shared_libs.clone()));
+        }
+
+        if !cfg.apex_available.is_empty()
+            && ["lib", "rlib", "dylib", "staticlib", "cdylib"].contains(&crate_type.as_str())
+        {
+            m.props.set("apex_available", cfg.apex_available.clone());
+        }
+
+        if let Some(path) = &package_cfg.add_module_block {
+            let content = std::fs::read_to_string(path)
+                .with_context(|| format!("failed to read {path:?}"))?;
+            m.props.raw_block = Some(content);
+        }
+
+        modules.push(m);
+    }
+    Ok(modules)
+}
diff --git a/vendor_snapshot/update.py b/vendor_snapshot/update.py
index 15cf36f..dab7842 100644
--- a/vendor_snapshot/update.py
+++ b/vendor_snapshot/update.py
@@ -523,23 +523,23 @@
     logging.debug('{} {} {} {} {}'.format(
         target_arch, arch_install_dir, variation, name, props))
 
-    def add_info(file, name, variation, arch, is_cfi, is_header):
-        info = (name, variation, arch, is_cfi, is_header)
+    def add_info(file, name, variation, arch, is_sanitized, is_header):
+        info = (name, variation, arch, is_sanitized, is_header)
         info_list = file_to_info.get(file)
         if not info_list:
             info_list = []
             file_to_info[file] = info_list
         info_list.append(info)
 
-    def find_file_in_list(dict, key, is_cfi):
+    def find_file_in_list(dict, key, is_sanitized):
         list = dict.get(key)
         logging.debug('    {} {}'.format(key, list))
         if list:
             for item in list:
                 item_path = os.path.join(arch_install_dir, item)
-                add_info(item_path, name, variation, arch, is_cfi, False)
+                add_info(item_path, name, variation, arch, is_sanitized, False)
 
-    def find_file_in_dirs(dict, key, is_cfi, is_header):
+    def find_file_in_dirs(dict, key, is_sanitized, is_header):
         dirs = dict.get(key)
         logging.debug('    {} {}'.format(key, dirs))
         if dirs:
@@ -549,30 +549,30 @@
                 for root, _, files in os.walk(dir_path, followlinks = True):
                     for file_name in sorted(files):
                         item_path = os.path.join(root, file_name)
-                        add_info(item_path, name, variation, arch, is_cfi, is_header)
+                        add_info(item_path, name, variation, arch, is_sanitized, is_header)
 
-    def find_file_in_dict(dict, is_cfi):
+    def find_file_in_dict(dict, is_sanitized):
         logging.debug('    arch {}'.format(arch))
         logging.debug('    name {}'.format( name))
-        logging.debug('    is_cfi {}'.format(is_cfi))
+        logging.debug('    is_sanitized {}'.format(is_sanitized))
 
         src = dict.get('src')
         logging.debug('    src {}'.format(src))
         if src:
             src_path = os.path.join(arch_install_dir, src)
-            add_info(src_path, name, variation, arch, is_cfi, False)
+            add_info(src_path, name, variation, arch, is_sanitized, False)
 
         notice = dict.get('notice')
         logging.debug('    notice {}'.format(notice))
         if notice:
             notice_path = os.path.join(arch_install_dir, notice)
-            add_info(notice_path, name, variation, arch, is_cfi, False)
+            add_info(notice_path, name, variation, arch, is_sanitized, False)
 
-        find_file_in_list(dict, 'init_rc', is_cfi)
-        find_file_in_list(dict, 'vintf_fragments', is_cfi)
+        find_file_in_list(dict, 'init_rc', is_sanitized)
+        find_file_in_list(dict, 'vintf_fragments', is_sanitized)
 
-        find_file_in_dirs(dict, 'export_include_dirs', is_cfi, True)
-        find_file_in_dirs(dict, 'export_system_include_dirs', is_cfi, True)
+        find_file_in_dirs(dict, 'export_include_dirs', is_sanitized, True)
+        find_file_in_dirs(dict, 'export_system_include_dirs', is_sanitized, True)
 
     for arch in sorted(props):
         name = props[arch]['name']
@@ -580,6 +580,9 @@
         cfi = props[arch].get('cfi')
         if cfi:
             find_file_in_dict(cfi, True)
+        hwasan = props[arch].get('hwasan')
+        if hwasan:
+            find_file_in_dict(hwasan, True)
 
 
 def find_all_props_files(install_dir):
@@ -713,7 +716,7 @@
     for f, i in sorted(used_file_to_info.items()):
         logging.debug('{} {}'.format(f, i))
         for m in i:
-            (name, variation, arch, is_cfi, is_header) = m
+            (name, variation, arch, is_sanitized, is_header) = m
             if not is_header:
                 used_modules.add(name)
 
diff --git a/vndk/tools/header-checker/Android.bp b/vndk/tools/header-checker/Android.bp
index e5b7e1d..d8d8633 100644
--- a/vndk/tools/header-checker/Android.bp
+++ b/vndk/tools/header-checker/Android.bp
@@ -36,7 +36,6 @@
     cppflags: [
         "-fno-exceptions",
         "-fno-rtti",
-        "-std=c++14",
     ],
 
     target: {
diff --git a/vndk/tools/header-checker/android/envsetup.sh b/vndk/tools/header-checker/android/envsetup.sh
index 4c80d72..49096b6 100644
--- a/vndk/tools/header-checker/android/envsetup.sh
+++ b/vndk/tools/header-checker/android/envsetup.sh
@@ -15,5 +15,5 @@
 # limitations under the License.
 
 export LLVM_BUILD_HOST_TOOLS=true
-export LLVM_PREBUILTS_VERSION=clang-r468909b
-export LLVM_RELEASE_VERSION=15.0.3
+export LLVM_PREBUILTS_VERSION=clang-r475365b
+export LLVM_RELEASE_VERSION=16.0.2