summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PREUPLOAD.cfg5
-rw-r--r--build/Android.bp3
-rw-r--r--build/Android.mk8
-rwxr-xr-xscripts/symbol.py23
-rw-r--r--sdk/OWNERS2
-rw-r--r--tools/cargo_embargo/Android.bp2
-rw-r--r--tools/cargo_embargo/src/bp.rs1
-rw-r--r--tools/cargo_embargo/src/cargo/cargo_out.rs4
-rw-r--r--tools/cargo_embargo/src/cargo/metadata.rs253
-rw-r--r--tools/cargo_embargo/src/main.rs31
-rw-r--r--tools/cargo_embargo/testdata/aho-corasick/expected_Android.bp4
-rw-r--r--tools/cargo_embargo/testdata/async-trait/expected_Android.bp2
-rw-r--r--tools/cargo_embargo/testdata/either/expected_Android.bp4
-rw-r--r--tools/cargo_embargo/testdata/plotters/expected_Android.bp2
-rw-r--r--tools/cargo_embargo/testdata/rustc-demangle-capi/expected_Android.bp4
-rw-r--r--tools/external_crates/.gitignore1
-rw-r--r--tools/external_crates/Cargo.lock3045
-rw-r--r--tools/external_crates/Cargo.toml6
-rw-r--r--tools/external_crates/OWNERS2
-rw-r--r--tools/external_crates/crate_health/Cargo.toml27
-rw-r--r--tools/external_crates/crate_health/src/android_bp.rs139
-rw-r--r--tools/external_crates/crate_health/src/bin/health_report.rs52
-rw-r--r--tools/external_crates/crate_health/src/bin/migration_report.rs47
-rw-r--r--tools/external_crates/crate_health/src/crate_collection.rs93
-rw-r--r--tools/external_crates/crate_health/src/crate_type.rs392
-rw-r--r--tools/external_crates/crate_health/src/lib.rs92
-rw-r--r--tools/external_crates/crate_health/src/migration.rs105
-rw-r--r--tools/external_crates/crate_health/src/name_and_version.rs189
-rw-r--r--tools/external_crates/crate_health/src/name_and_version_map.rs289
-rw-r--r--tools/external_crates/crate_health/src/pseudo_crate.rs91
-rw-r--r--tools/external_crates/crate_health/src/reports.rs326
-rw-r--r--tools/external_crates/crate_health/src/templates/Cargo.toml.template10
-rw-r--r--tools/external_crates/crate_health/src/templates/crate_health.html.template33
-rw-r--r--tools/external_crates/crate_health/src/templates/migration_report.html.template53
-rw-r--r--tools/external_crates/crate_health/src/templates/size_report.html.template6
-rw-r--r--tools/external_crates/crate_health/src/templates/table.html.template10
-rw-r--r--tools/external_crates/crate_health/src/version_match.rs417
-rw-r--r--tools/external_crates/crate_health_proc_macros/Cargo.toml12
-rw-r--r--tools/external_crates/crate_health_proc_macros/src/lib.rs124
-rw-r--r--tools/ndk/OWNERS1
-rwxr-xr-xtools/repo_pull/gerrit.py26
-rw-r--r--tools/winscope/OWNERS1
-rwxr-xr-xtreble/compare_bp_system_image.sh8
-rw-r--r--vndk/tools/header-checker/README.md4
-rwxr-xr-xvndk/tools/header-checker/utils/create_reference_dumps.py30
45 files changed, 5896 insertions, 83 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index d38a96172..e032c4df7 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,6 +1,9 @@
# Per-project `repo upload` hook settings.
# https://android.googlesource.com/platform/tools/repohooks
+[Builtin Hooks]
+rustfmt = true
+
[Options]
ignore_merged_commits = true
@@ -11,4 +14,4 @@ checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPL
ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES}
-ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES} \ No newline at end of file
+ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES}
diff --git a/build/Android.bp b/build/Android.bp
index 9c3d0e899..f54346ff1 100644
--- a/build/Android.bp
+++ b/build/Android.bp
@@ -214,6 +214,9 @@ android_sdk_repo_host {
linux: {
strip_files: ["lib64/*.so"],
},
+ linux_bionic: {
+ enabled: false,
+ },
darwin: {
strip_files: ["lib64/*.dylib"],
},
diff --git a/build/Android.mk b/build/Android.mk
index be7aa4e63..b05bcd5ad 100644
--- a/build/Android.mk
+++ b/build/Android.mk
@@ -123,6 +123,10 @@ $(call dist-for-goals,sdk,$(full_target):system-data/annotations.zip)
full_target := $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/api_versions_module_lib_generated-api-versions.xml
$(call dist-for-goals,sdk,$(full_target):module-lib-data/api-versions.xml)
+# ======= Lint module-lib API XML (complete API) ===========
+full_target := $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/api_versions_module_lib_complete_generated-api-versions.xml
+$(call dist-for-goals,sdk,$(full_target):module-lib-data/api-versions-complete.xml)
+
# ======= Lint module-lib Annotations zip (complete API) ===========
full_target := $(call intermediates-dir-for,ETC,sdk-annotations-module-lib.zip)/sdk-annotations-module-lib.zip
$(call dist-for-goals,sdk,$(full_target):module-lib-data/annotations.zip)
@@ -131,6 +135,10 @@ $(call dist-for-goals,sdk,$(full_target):module-lib-data/annotations.zip)
full_target := $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/api_versions_system_server_generated-api-versions.xml
$(call dist-for-goals,sdk,$(full_target):system-server-data/api-versions.xml)
+# ======= Lint system-server API XML (complete API) ===========
+full_target := $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/api_versions_system_server_complete_generated-api-versions.xml
+$(call dist-for-goals,sdk,$(full_target):system-server-data/api-versions-complete.xml)
+
# ======= Lint system-server Annotations zip (complete API) ===========
full_target := $(call intermediates-dir-for,ETC,sdk-annotations-system-server.zip)/sdk-annotations-system-server.zip
$(call dist-for-goals,sdk,$(full_target):system-server-data/annotations.zip)
diff --git a/scripts/symbol.py b/scripts/symbol.py
index f4c239535..64242eab8 100755
--- a/scripts/symbol.py
+++ b/scripts/symbol.py
@@ -20,6 +20,7 @@ The information can include symbol names, offsets, and source locations.
"""
import atexit
+import json
import glob
import os
import platform
@@ -292,7 +293,7 @@ def CallLlvmSymbolizerForSet(lib, unique_addrs):
return None
cmd = [ToolPath("llvm-symbolizer"), "--functions", "--inlines",
- "--demangle", "--obj=" + symbols, "--output-style=GNU"]
+ "--demangle", "--obj=" + symbols, "--output-style=JSON"]
child = _PIPE_ADDR2LINE_CACHE.GetProcess(cmd)
for addr in addrs:
@@ -300,20 +301,12 @@ def CallLlvmSymbolizerForSet(lib, unique_addrs):
child.stdin.write("0x%s\n" % addr)
child.stdin.flush()
records = []
- first = True
- while True:
- symbol = child.stdout.readline().strip()
- if not symbol:
- break
- location = child.stdout.readline().strip()
- records.append((symbol, location))
- if first:
- # Write a blank line as a sentinel so we know when to stop
- # reading inlines from the output.
- # The blank line will cause llvm-symbolizer to emit a blank line.
- child.stdin.write("\n")
- child.stdin.flush()
- first = False
+ json_result = json.loads(child.stdout.readline().strip())
+ for symbol in json_result["Symbol"]:
+ function_name = symbol["FunctionName"]
+ # GNU style location: file_name:line_num
+ location = ("%s:%s" % (symbol["FileName"], symbol["Line"]))
+ records.append((function_name, location))
except IOError as e:
# Remove the / in front of the library name to match other output.
records = [(None, lib[1:] + " ***Error: " + str(e))]
diff --git a/sdk/OWNERS b/sdk/OWNERS
index e0c1e7d22..a05cf75ac 100644
--- a/sdk/OWNERS
+++ b/sdk/OWNERS
@@ -1,2 +1,2 @@
per-file plat_tools_source.prop_template=set noparent
-per-file plat_tools_source.prop_template=enh@google.com,shaju@google.com
+per-file plat_tools_source.prop_template=enh@google.com,sanglardf@google.com
diff --git a/tools/cargo_embargo/Android.bp b/tools/cargo_embargo/Android.bp
index 2e6eab4c4..3ef899193 100644
--- a/tools/cargo_embargo/Android.bp
+++ b/tools/cargo_embargo/Android.bp
@@ -19,7 +19,7 @@ package {
rust_defaults {
name: "cargo_embargo.defaults",
- srcs: ["src/main.rs"],
+ crate_root: "src/main.rs",
// Disable LTO for faster builds. Don't need the performance here.
flags: ["-C lto=off"],
rustlibs: [
diff --git a/tools/cargo_embargo/src/bp.rs b/tools/cargo_embargo/src/bp.rs
index cc186c46b..7744ae74f 100644
--- a/tools/cargo_embargo/src/bp.rs
+++ b/tools/cargo_embargo/src/bp.rs
@@ -111,6 +111,7 @@ impl BpProperties {
"crate_name",
"cargo_env_compat",
"cargo_pkg_version",
+ "crate_root",
"srcs",
"test_suites",
"auto_gen_config",
diff --git a/tools/cargo_embargo/src/cargo/cargo_out.rs b/tools/cargo_embargo/src/cargo/cargo_out.rs
index 01ca55c95..fb2f00f64 100644
--- a/tools/cargo_embargo/src/cargo/cargo_out.rs
+++ b/tools/cargo_embargo/src/cargo/cargo_out.rs
@@ -410,9 +410,9 @@ impl Crate {
manifest_path,
)
})?;
- out.package_name = package_metadata.name.clone();
+ out.package_name.clone_from(&package_metadata.name);
out.version = Some(package_metadata.version.clone());
- out.edition = package_metadata.edition.clone();
+ out.edition.clone_from(&package_metadata.edition);
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 2958a0117..ea4c95983 100644
--- a/tools/cargo_embargo/src/cargo/metadata.rs
+++ b/tools/cargo_embargo/src/cargo/metadata.rs
@@ -24,12 +24,12 @@ use std::path::{Path, PathBuf};
/// `cfg` strings for dependencies which should be considered enabled. It would be better to parse
/// them properly, but this is good enough in practice so far.
const ENABLED_CFGS: [&str; 6] = [
- r#"cfg(unix)"#,
- r#"cfg(not(windows))"#,
- r#"cfg(any(unix, target_os = "wasi"))"#,
- r#"cfg(not(all(target_family = "wasm", target_os = "unknown")))"#,
- r#"cfg(not(target_family = "wasm"))"#,
- r#"cfg(any(target_os = "linux", target_os = "android"))"#,
+ r#"unix"#,
+ r#"not(windows)"#,
+ r#"any(unix, target_os = "wasi")"#,
+ r#"not(all(target_family = "wasm", target_os = "unknown"))"#,
+ r#"not(target_family = "wasm")"#,
+ r#"any(target_os = "linux", target_os = "android")"#,
];
/// `cargo metadata` output.
@@ -39,7 +39,7 @@ pub struct WorkspaceMetadata {
pub workspace_members: Vec<String>,
}
-#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
+#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
pub struct PackageMetadata {
pub name: String,
pub version: String,
@@ -62,17 +62,21 @@ pub struct DependencyMetadata {
impl DependencyMetadata {
/// Returns whether the dependency should be included when the given features are enabled.
- fn enabled(&self, features: &[String]) -> bool {
+ fn enabled(&self, features: &[String], cfgs: &[String]) -> bool {
if let Some(target) = &self.target {
- if !ENABLED_CFGS.contains(&target.as_str()) {
- return false;
+ if target.starts_with("cfg(") && target.ends_with(')') {
+ let target_cfg = &target[4..target.len() - 1];
+ if !ENABLED_CFGS.contains(&target_cfg) && !cfgs.contains(&target_cfg.to_string()) {
+ return false;
+ }
}
}
- !self.optional || features.contains(&format!("dep:{}", self.name))
+ let name = self.rename.as_ref().unwrap_or(&self.name);
+ !self.optional || features.contains(&format!("dep:{}", name))
}
}
-#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
+#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
#[allow(dead_code)]
pub struct TargetMetadata {
pub crate_types: Vec<CrateType>,
@@ -103,12 +107,13 @@ pub enum TargetKind {
pub fn parse_cargo_metadata_str(cargo_metadata: &str, cfg: &VariantConfig) -> Result<Vec<Crate>> {
let metadata =
serde_json::from_str(cargo_metadata).context("failed to parse cargo metadata")?;
- parse_cargo_metadata(&metadata, &cfg.features, cfg.tests)
+ parse_cargo_metadata(&metadata, &cfg.features, &cfg.extra_cfg, cfg.tests)
}
fn parse_cargo_metadata(
metadata: &WorkspaceMetadata,
features: &Option<Vec<String>>,
+ cfgs: &[String],
include_tests: bool,
) -> Result<Vec<Crate>> {
let mut crates = Vec::new();
@@ -171,9 +176,11 @@ fn parse_cargo_metadata(
package,
&metadata.packages,
&features,
+ cfgs,
&target_kinds,
false,
)?,
+ cfgs: cfgs.to_owned(),
..Default::default()
});
}
@@ -193,9 +200,11 @@ fn parse_cargo_metadata(
package,
&metadata.packages,
&features,
+ cfgs,
&target_kinds,
true,
)?,
+ cfgs: cfgs.to_owned(),
..Default::default()
});
}
@@ -208,6 +217,7 @@ fn get_externs(
package: &PackageMetadata,
packages: &[PackageMetadata],
features: &[String],
+ cfgs: &[String],
target_kinds: &[TargetKind],
test: bool,
) -> Result<Vec<Extern>> {
@@ -216,7 +226,7 @@ fn get_externs(
.iter()
.filter_map(|dependency| {
// Kind is None for normal dependencies, as opposed to dev dependencies.
- if dependency.enabled(features)
+ if dependency.enabled(features, cfgs)
&& dependency.kind.as_deref() != Some("build")
&& (dependency.kind.is_none() || test)
{
@@ -440,6 +450,221 @@ mod tests {
}
#[test]
+ fn get_externs_cfg() {
+ let package = PackageMetadata {
+ name: "test_package".to_string(),
+ dependencies: vec![
+ DependencyMetadata {
+ name: "alwayslib".to_string(),
+ kind: None,
+ optional: false,
+ target: None,
+ rename: None,
+ },
+ DependencyMetadata {
+ name: "unixlib".to_string(),
+ kind: None,
+ optional: false,
+ target: Some("cfg(unix)".to_string()),
+ rename: None,
+ },
+ DependencyMetadata {
+ name: "windowslib".to_string(),
+ kind: None,
+ optional: false,
+ target: Some("cfg(windows)".to_string()),
+ rename: None,
+ },
+ ],
+ features: [].into_iter().collect(),
+ targets: vec![],
+ ..Default::default()
+ };
+ let packages = vec![
+ package.clone(),
+ PackageMetadata {
+ name: "alwayslib".to_string(),
+ targets: vec![TargetMetadata {
+ name: "alwayslib".to_string(),
+ kind: vec![TargetKind::Lib],
+ ..Default::default()
+ }],
+ ..Default::default()
+ },
+ PackageMetadata {
+ name: "unixlib".to_string(),
+ targets: vec![TargetMetadata {
+ name: "unixlib".to_string(),
+ kind: vec![TargetKind::Lib],
+ ..Default::default()
+ }],
+ ..Default::default()
+ },
+ PackageMetadata {
+ name: "windowslib".to_string(),
+ targets: vec![TargetMetadata {
+ name: "windowslib".to_string(),
+ kind: vec![TargetKind::Lib],
+ ..Default::default()
+ }],
+ ..Default::default()
+ },
+ ];
+ assert_eq!(
+ get_externs(&package, &packages, &[], &[], &[], false).unwrap(),
+ vec![
+ Extern {
+ name: "alwayslib".to_string(),
+ lib_name: "alwayslib".to_string(),
+ extern_type: ExternType::Rust
+ },
+ Extern {
+ name: "unixlib".to_string(),
+ lib_name: "unixlib".to_string(),
+ extern_type: ExternType::Rust
+ },
+ ]
+ );
+ }
+
+ #[test]
+ fn get_externs_extra_cfg() {
+ let package = PackageMetadata {
+ name: "test_package".to_string(),
+ dependencies: vec![
+ DependencyMetadata {
+ name: "foolib".to_string(),
+ kind: None,
+ optional: false,
+ target: Some("cfg(foo)".to_string()),
+ rename: None,
+ },
+ DependencyMetadata {
+ name: "barlib".to_string(),
+ kind: None,
+ optional: false,
+ target: Some("cfg(bar)".to_string()),
+ rename: None,
+ },
+ ],
+ features: [].into_iter().collect(),
+ targets: vec![],
+ ..Default::default()
+ };
+ let packages = vec![
+ package.clone(),
+ PackageMetadata {
+ name: "foolib".to_string(),
+ targets: vec![TargetMetadata {
+ name: "foolib".to_string(),
+ kind: vec![TargetKind::Lib],
+ ..Default::default()
+ }],
+ ..Default::default()
+ },
+ PackageMetadata {
+ name: "barlib".to_string(),
+ targets: vec![TargetMetadata {
+ name: "barlib".to_string(),
+ kind: vec![TargetKind::Lib],
+ ..Default::default()
+ }],
+ ..Default::default()
+ },
+ ];
+ assert_eq!(
+ get_externs(&package, &packages, &[], &["foo".to_string()], &[], false).unwrap(),
+ vec![Extern {
+ name: "foolib".to_string(),
+ lib_name: "foolib".to_string(),
+ extern_type: ExternType::Rust
+ },]
+ );
+ }
+
+ #[test]
+ fn get_externs_rename() {
+ let package = PackageMetadata {
+ name: "test_package".to_string(),
+ dependencies: vec![
+ DependencyMetadata {
+ name: "foo".to_string(),
+ kind: None,
+ optional: false,
+ target: None,
+ rename: Some("foo2".to_string()),
+ },
+ DependencyMetadata {
+ name: "bar".to_string(),
+ kind: None,
+ optional: true,
+ target: None,
+ rename: None,
+ },
+ DependencyMetadata {
+ name: "bar".to_string(),
+ kind: None,
+ optional: true,
+ target: None,
+ rename: Some("baz".to_string()),
+ },
+ ],
+ ..Default::default()
+ };
+ let packages = vec![
+ package.clone(),
+ PackageMetadata {
+ name: "foo".to_string(),
+ targets: vec![TargetMetadata {
+ name: "foo".to_string(),
+ kind: vec![TargetKind::Lib],
+ ..Default::default()
+ }],
+ ..Default::default()
+ },
+ PackageMetadata {
+ name: "bar".to_string(),
+ targets: vec![TargetMetadata {
+ name: "bar".to_string(),
+ kind: vec![TargetKind::Lib],
+ ..Default::default()
+ }],
+ ..Default::default()
+ },
+ ];
+ assert_eq!(
+ get_externs(&package, &packages, &["dep:bar".to_string()], &[], &[], false).unwrap(),
+ vec![
+ Extern {
+ name: "bar".to_string(),
+ lib_name: "bar".to_string(),
+ extern_type: ExternType::Rust
+ },
+ Extern {
+ name: "foo2".to_string(),
+ lib_name: "foo".to_string(),
+ extern_type: ExternType::Rust
+ },
+ ]
+ );
+ assert_eq!(
+ get_externs(&package, &packages, &["dep:baz".to_string()], &[], &[], false).unwrap(),
+ vec![
+ Extern {
+ name: "baz".to_string(),
+ lib_name: "bar".to_string(),
+ extern_type: ExternType::Rust
+ },
+ Extern {
+ name: "foo2".to_string(),
+ lib_name: "foo".to_string(),
+ extern_type: ExternType::Rust
+ },
+ ]
+ );
+ }
+
+ #[test]
fn parse_metadata() {
/// Remove anything before "external/rust/crates/" from the
/// `package_dir` field. This makes the test robust since you
diff --git a/tools/cargo_embargo/src/main.rs b/tools/cargo_embargo/src/main.rs
index 9c67eba0e..753f059e1 100644
--- a/tools/cargo_embargo/src/main.rs
+++ b/tools/cargo_embargo/src/main.rs
@@ -296,7 +296,9 @@ fn make_crates(args: &Args, cfg: &VariantConfig) -> Result<Vec<Crate>> {
}
} else {
let cargo_output = generate_cargo_out(cfg).context("generate_cargo_out failed")?;
- write(cargo_out_path, &cargo_output.cargo_out)?;
+ if cfg.run_cargo {
+ write(cargo_out_path, &cargo_output.cargo_out)?;
+ }
write(cargo_metadata_path, &cargo_output.cargo_metadata)?;
cargo_output
};
@@ -477,9 +479,23 @@ fn generate_cargo_out(cfg: &VariantConfig) -> Result<CargoOutput> {
let mut cargo_out = String::new();
if cfg.run_cargo {
+ let envs = if cfg.extra_cfg.is_empty() {
+ vec![]
+ } else {
+ vec![(
+ "RUSTFLAGS",
+ cfg.extra_cfg
+ .iter()
+ .map(|cfg_flag| format!("--cfg {}", cfg_flag))
+ .collect::<Vec<_>>()
+ .join(" "),
+ )]
+ };
+
// cargo build
cargo_out += &run_cargo(
Command::new("cargo")
+ .envs(envs.clone())
.args(["build", "--target", default_target])
.args(verbose_args)
.args(target_dir_args)
@@ -491,6 +507,7 @@ fn generate_cargo_out(cfg: &VariantConfig) -> Result<CargoOutput> {
// cargo build --tests
cargo_out += &run_cargo(
Command::new("cargo")
+ .envs(envs.clone())
.args(["build", "--target", default_target, "--tests"])
.args(verbose_args)
.args(target_dir_args)
@@ -500,6 +517,7 @@ fn generate_cargo_out(cfg: &VariantConfig) -> Result<CargoOutput> {
// cargo test -- --list
cargo_out += &run_cargo(
Command::new("cargo")
+ .envs(envs)
.args(["test", "--target", default_target])
.args(target_dir_args)
.args(&workspace_args)
@@ -898,9 +916,8 @@ fn crate_to_bp_modules(
}
}
- 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("crate_root", crate_.main_src.clone());
+ m.props.set_if_nonempty("srcs", extra_srcs.to_owned());
m.props.set("edition", crate_.edition.clone());
m.props.set_if_nonempty("features", crate_.features.clone());
@@ -911,7 +928,6 @@ fn crate_to_bp_modules(
.clone()
.into_iter()
.filter(|crate_cfg| !cfg.cfg_blocklist.contains(crate_cfg))
- .chain(cfg.extra_cfg.clone().into_iter())
.collect(),
);
@@ -1091,7 +1107,6 @@ fn crate_to_rulesmk(
.cfgs
.iter()
.filter(|crate_cfg| !cfg.cfg_blocklist.contains(crate_cfg))
- .chain(cfg.extra_cfg.iter())
.map(|cfg| format!("--cfg '{cfg}'")),
);
if !flags.is_empty() {
@@ -1269,7 +1284,7 @@ mod tests {
("host_supported".to_string(), BpValue::Bool(true)),
("name".to_string(), BpValue::String("libname".to_string())),
("product_available".to_string(), BpValue::Bool(true)),
- ("srcs".to_string(), BpValue::List(vec![BpValue::String("".to_string())])),
+ ("crate_root".to_string(), BpValue::String("".to_string())),
("vendor_available".to_string(), BpValue::Bool(true)),
]
.into_iter()
@@ -1312,7 +1327,7 @@ mod tests {
("host_supported".to_string(), BpValue::Bool(true)),
("name".to_string(), BpValue::String("libash_rust".to_string())),
("product_available".to_string(), BpValue::Bool(true)),
- ("srcs".to_string(), BpValue::List(vec![BpValue::String("".to_string())])),
+ ("crate_root".to_string(), BpValue::String("".to_string())),
("vendor_available".to_string(), BpValue::Bool(true)),
]
.into_iter()
diff --git a/tools/cargo_embargo/testdata/aho-corasick/expected_Android.bp b/tools/cargo_embargo/testdata/aho-corasick/expected_Android.bp
index 7eeebf833..f16d7a414 100644
--- a/tools/cargo_embargo/testdata/aho-corasick/expected_Android.bp
+++ b/tools/cargo_embargo/testdata/aho-corasick/expected_Android.bp
@@ -4,7 +4,7 @@ host_supported: true,
crate_name: "aho_corasick",
cargo_env_compat: true,
cargo_pkg_version: "0.7.20",
-srcs: ["src/lib.rs"],
+crate_root: "src/lib.rs",
test_suites: ["general-tests"],
auto_gen_config: true,
test_options: {
@@ -21,7 +21,7 @@ host_supported: true,
crate_name: "aho_corasick",
cargo_env_compat: true,
cargo_pkg_version: "0.7.20",
-srcs: ["src/lib.rs"],
+crate_root: "src/lib.rs",
edition: "2018",
features: ["default", "std"],
rustlibs: ["libmemchr"],
diff --git a/tools/cargo_embargo/testdata/async-trait/expected_Android.bp b/tools/cargo_embargo/testdata/async-trait/expected_Android.bp
index 60db1f4e6..88433ac32 100644
--- a/tools/cargo_embargo/testdata/async-trait/expected_Android.bp
+++ b/tools/cargo_embargo/testdata/async-trait/expected_Android.bp
@@ -3,7 +3,7 @@ name: "libasync_trait",
crate_name: "async_trait",
cargo_env_compat: true,
cargo_pkg_version: "0.1.74",
-srcs: ["src/lib.rs"],
+crate_root: "src/lib.rs",
edition: "2021",
rustlibs: ["libproc_macro2", "libquote", "libsyn"],
product_available: true,
diff --git a/tools/cargo_embargo/testdata/either/expected_Android.bp b/tools/cargo_embargo/testdata/either/expected_Android.bp
index c74ec8f3b..cf58a2c00 100644
--- a/tools/cargo_embargo/testdata/either/expected_Android.bp
+++ b/tools/cargo_embargo/testdata/either/expected_Android.bp
@@ -4,7 +4,7 @@ host_supported: true,
crate_name: "either",
cargo_env_compat: true,
cargo_pkg_version: "1.9.0",
-srcs: ["src/lib.rs"],
+crate_root: "src/lib.rs",
test_suites: ["general-tests"],
auto_gen_config: true,
test_options: {
@@ -21,7 +21,7 @@ host_supported: true,
crate_name: "either",
cargo_env_compat: true,
cargo_pkg_version: "1.9.0",
-srcs: ["src/lib.rs"],
+crate_root: "src/lib.rs",
edition: "2018",
features: ["default", "use_std"],
apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
diff --git a/tools/cargo_embargo/testdata/plotters/expected_Android.bp b/tools/cargo_embargo/testdata/plotters/expected_Android.bp
index 5cc185899..4a2c394f7 100644
--- a/tools/cargo_embargo/testdata/plotters/expected_Android.bp
+++ b/tools/cargo_embargo/testdata/plotters/expected_Android.bp
@@ -4,7 +4,7 @@ host_supported: true,
crate_name: "plotters",
cargo_env_compat: true,
cargo_pkg_version: "0.3.5",
-srcs: ["src/lib.rs"],
+crate_root: "src/lib.rs",
edition: "2018",
features: ["area_series", "line_series", "plotters-svg", "svg_backend"],
rustlibs: ["libnum_traits", "libplotters_backend", "libplotters_svg"],
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 b2f3169cc..9c633e4a3 100644
--- a/tools/cargo_embargo/testdata/rustc-demangle-capi/expected_Android.bp
+++ b/tools/cargo_embargo/testdata/rustc-demangle-capi/expected_Android.bp
@@ -4,7 +4,7 @@ host_supported: true,
crate_name: "rustc_demangle",
cargo_env_compat: true,
cargo_pkg_version: "0.1.0",
-srcs: ["src/lib.rs"],
+crate_root: "src/lib.rs",
edition: "2015",
rustlibs: ["librustc_demangle"],
include_dirs: ["include"],
@@ -24,7 +24,7 @@ host_supported: true,
crate_name: "rustc_demangle",
cargo_env_compat: true,
cargo_pkg_version: "0.1.0",
-srcs: ["src/lib.rs"],
+crate_root: "src/lib.rs",
test_suites: ["general-tests"],
auto_gen_config: true,
test_options: {
diff --git a/tools/external_crates/.gitignore b/tools/external_crates/.gitignore
new file mode 100644
index 000000000..2f7896d1d
--- /dev/null
+++ b/tools/external_crates/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/tools/external_crates/Cargo.lock b/tools/external_crates/Cargo.lock
new file mode 100644
index 000000000..62fafcc9b
--- /dev/null
+++ b/tools/external_crates/Cargo.lock
@@ -0,0 +1,3045 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "ahash"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
+dependencies = [
+ "cfg-if",
+ "getrandom",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
+
+[[package]]
+name = "arc-swap"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
+
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
+[[package]]
+name = "autocfg"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
+
+[[package]]
+name = "base16ct"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "base64ct"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
+
+[[package]]
+name = "bitmaps"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bstr"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
+dependencies = [
+ "memchr",
+ "regex-automata",
+ "serde",
+]
+
+[[package]]
+name = "btoi"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "bytes"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
+
+[[package]]
+name = "bytesize"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc"
+
+[[package]]
+name = "cargo"
+version = "0.73.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a6fe1f5394d14b81d2f3f605832a3ce35ed0bf120bc7ef437ce27fd4929c6a"
+dependencies = [
+ "anyhow",
+ "base64",
+ "bytesize",
+ "cargo-platform",
+ "cargo-util",
+ "clap",
+ "crates-io",
+ "curl",
+ "curl-sys",
+ "env_logger",
+ "filetime",
+ "flate2",
+ "fwdansi",
+ "git2",
+ "git2-curl",
+ "gix",
+ "gix-features",
+ "glob",
+ "hex",
+ "hmac",
+ "home",
+ "http-auth",
+ "humantime",
+ "ignore",
+ "im-rc",
+ "indexmap 1.9.3",
+ "itertools 0.10.5",
+ "jobserver",
+ "lazycell",
+ "libc",
+ "libgit2-sys",
+ "log",
+ "memchr",
+ "opener",
+ "os_info",
+ "pasetors",
+ "pathdiff",
+ "pulldown-cmark",
+ "rand",
+ "rustfix",
+ "semver",
+ "serde",
+ "serde-value",
+ "serde_ignored",
+ "serde_json",
+ "sha1",
+ "shell-escape",
+ "strip-ansi-escapes",
+ "syn",
+ "tar",
+ "tempfile",
+ "termcolor",
+ "time",
+ "toml",
+ "toml_edit",
+ "unicode-width",
+ "unicode-xid",
+ "url",
+ "walkdir",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo-util"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f2d9a9a8d3e0b61b1110c49ab8f6ed7a76ce4f2b1d53ae48a83152d3d5e8f5b"
+dependencies = [
+ "anyhow",
+ "core-foundation",
+ "filetime",
+ "hex",
+ "ignore",
+ "jobserver",
+ "libc",
+ "miow",
+ "same-file",
+ "sha2",
+ "shell-escape",
+ "tempfile",
+ "tracing",
+ "walkdir",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41"
+dependencies = [
+ "jobserver",
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "4.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+ "terminal_size",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
+
+[[package]]
+name = "clru"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8191fa7302e03607ff0e237d4246cc043ff5b3cb9409d995172ba3bea16b807"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
+name = "copy_dir"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "543d1dd138ef086e2ff05e3a48cf9da045da2033d16f8538fd76b86cd49b2ca3"
+dependencies = [
+ "walkdir",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crate_health"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "cargo",
+ "clap",
+ "copy_dir",
+ "crate_health_proc_macros",
+ "glob",
+ "itertools 0.11.0",
+ "num_cpus",
+ "semver",
+ "serde",
+ "serde_json",
+ "tempfile",
+ "thiserror",
+ "threadpool",
+ "tinytemplate",
+ "walkdir",
+ "whoami",
+]
+
+[[package]]
+name = "crate_health_proc_macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "crates-io"
+version = "0.37.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "876aa69b4afca5f2eb5e23daa3445930faf829bcb67075a20ffa884f11f8c57c"
+dependencies = [
+ "anyhow",
+ "curl",
+ "percent-encoding",
+ "serde",
+ "serde_json",
+ "url",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
+
+[[package]]
+name = "crypto-bigint"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
+dependencies = [
+ "generic-array",
+ "rand_core",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "ct-codecs"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df"
+
+[[package]]
+name = "curl"
+version = "0.4.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e2161dd6eba090ff1594084e95fd67aeccf04382ffea77999ea94ed42ec67b6"
+dependencies = [
+ "curl-sys",
+ "libc",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "socket2",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "curl-sys"
+version = "0.4.72+curl-8.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29cbdc8314c447d11e8fd156dcdd031d9e02a7a976163e396b548c03153bc9ea"
+dependencies = [
+ "cc",
+ "libc",
+ "libnghttp2-sys",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+ "vcpkg",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "der"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
+dependencies = [
+ "const-oid",
+ "pem-rfc7468",
+ "zeroize",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "const-oid",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "dunce"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
+
+[[package]]
+name = "ecdsa"
+version = "0.16.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
+dependencies = [
+ "der",
+ "digest",
+ "elliptic-curve",
+ "rfc6979",
+ "signature",
+ "spki",
+]
+
+[[package]]
+name = "ed25519-compact"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "either"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
+
+[[package]]
+name = "elliptic-curve"
+version = "0.13.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
+dependencies = [
+ "base16ct",
+ "crypto-bigint",
+ "digest",
+ "ff",
+ "generic-array",
+ "group",
+ "hkdf",
+ "pem-rfc7468",
+ "pkcs8",
+ "rand_core",
+ "sec1",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
+dependencies = [
+ "humantime",
+ "is-terminal",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "faster-hex"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "239f7bfb930f820ab16a9cd95afc26f88264cf6905c960b340a615384aa3338a"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"
+
+[[package]]
+name = "ff"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
+dependencies = [
+ "rand_core",
+ "subtle",
+]
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f"
+
+[[package]]
+name = "filetime"
+version = "0.2.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+dependencies = [
+ "crc32fast",
+ "libz-sys",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "fwdansi"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08c1f5787fe85505d1f7777268db5103d80a7a374d2316a7ce262e57baf8f208"
+dependencies = [
+ "memchr",
+ "termcolor",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+ "zeroize",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "git2"
+version = "0.17.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b989d6a7ca95a362cf2cfc5ad688b3a467be1f87e480b8dad07fee8c79b0044"
+dependencies = [
+ "bitflags 1.3.2",
+ "libc",
+ "libgit2-sys",
+ "log",
+ "openssl-probe",
+ "openssl-sys",
+ "url",
+]
+
+[[package]]
+name = "git2-curl"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8f8b7432b72928cff76f69e59ed5327f94a52763731e71274960dee72fe5f8c"
+dependencies = [
+ "curl",
+ "git2",
+ "log",
+ "url",
+]
+
+[[package]]
+name = "gix"
+version = "0.45.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf2a03ec66ee24d1b2bae3ab718f8d14f141613810cb7ff6756f7db667f1cd82"
+dependencies = [
+ "gix-actor",
+ "gix-attributes",
+ "gix-commitgraph",
+ "gix-config",
+ "gix-credentials",
+ "gix-date",
+ "gix-diff",
+ "gix-discover",
+ "gix-features",
+ "gix-fs",
+ "gix-glob",
+ "gix-hash",
+ "gix-hashtable",
+ "gix-ignore",
+ "gix-index",
+ "gix-lock",
+ "gix-mailmap",
+ "gix-negotiate",
+ "gix-object",
+ "gix-odb",
+ "gix-pack",
+ "gix-path",
+ "gix-prompt",
+ "gix-protocol",
+ "gix-ref",
+ "gix-refspec",
+ "gix-revision",
+ "gix-sec",
+ "gix-tempfile",
+ "gix-transport",
+ "gix-traverse",
+ "gix-url",
+ "gix-utils",
+ "gix-validate",
+ "gix-worktree",
+ "log",
+ "once_cell",
+ "prodash",
+ "signal-hook",
+ "smallvec",
+ "thiserror",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "gix-actor"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fe73f9f6be1afbf1bd5be919a9636fa560e2f14d42262a934423ed6760cd838"
+dependencies = [
+ "bstr",
+ "btoi",
+ "gix-date",
+ "itoa",
+ "nom",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-attributes"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78b79590ac382f80d87e06416f5fcac6fee5d83dcb152a00ed0bdbaa988acc31"
+dependencies = [
+ "bstr",
+ "gix-glob",
+ "gix-path",
+ "gix-quote",
+ "kstring",
+ "log",
+ "smallvec",
+ "thiserror",
+ "unicode-bom",
+]
+
+[[package]]
+name = "gix-bitmap"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a371db66cbd4e13f0ed9dc4c0fea712d7276805fccc877f77e96374d317e87ae"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
+name = "gix-chunk"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45c8751169961ba7640b513c3b24af61aa962c967aaf04116734975cd5af0c52"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
+name = "gix-command"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c576cfbf577f72c097b5f88aedea502cd62952bdc1fb3adcab4531d5525a4c7"
+dependencies = [
+ "bstr",
+]
+
+[[package]]
+name = "gix-commitgraph"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8490ae1b3d55c47e6a71d247c082304a2f79f8d0332c1a2f5693d42a2021a09"
+dependencies = [
+ "bstr",
+ "gix-chunk",
+ "gix-features",
+ "gix-hash",
+ "memmap2",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-config"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51f310120ae1ba8f0ca52fb22876ce9bad5b15c8ffb3eb7302e4b64a3b9f681c"
+dependencies = [
+ "bstr",
+ "gix-config-value",
+ "gix-features",
+ "gix-glob",
+ "gix-path",
+ "gix-ref",
+ "gix-sec",
+ "log",
+ "memchr",
+ "nom",
+ "once_cell",
+ "smallvec",
+ "thiserror",
+ "unicode-bom",
+]
+
+[[package]]
+name = "gix-config-value"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e874f41437441c02991dcea76990b9058fadfc54b02ab4dd06ab2218af43897"
+dependencies = [
+ "bitflags 2.5.0",
+ "bstr",
+ "gix-path",
+ "libc",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-credentials"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6f89fea8acd28f5ef8fa5042146f1637afd4d834bc8f13439d8fd1e5aca0d65"
+dependencies = [
+ "bstr",
+ "gix-command",
+ "gix-config-value",
+ "gix-path",
+ "gix-prompt",
+ "gix-sec",
+ "gix-url",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-date"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc164145670e9130a60a21670d9b6f0f4f8de04e5dd256c51fa5a0340c625902"
+dependencies = [
+ "bstr",
+ "itoa",
+ "thiserror",
+ "time",
+]
+
+[[package]]
+name = "gix-diff"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9029ad0083cc286a4bd2f5b3bf66bb66398abc26f2731a2824cd5edfc41a0e33"
+dependencies = [
+ "gix-hash",
+ "gix-object",
+ "imara-diff",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-discover"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aba9c6c0d1f2b2efe65581de73de4305004612d49c83773e783202a7ef204f46"
+dependencies = [
+ "bstr",
+ "dunce",
+ "gix-hash",
+ "gix-path",
+ "gix-ref",
+ "gix-sec",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-features"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a8c493409bf6060d408eec9bbdd1b12ea351266b50012e2a522f75dfc7b8314"
+dependencies = [
+ "bytes",
+ "crc32fast",
+ "crossbeam-channel",
+ "flate2",
+ "gix-hash",
+ "libc",
+ "once_cell",
+ "parking_lot",
+ "prodash",
+ "sha1_smol",
+ "thiserror",
+ "walkdir",
+]
+
+[[package]]
+name = "gix-fs"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30da8997008adb87f94e15beb7ee229f8a48e97af585a584bfee4a5a1880aab5"
+dependencies = [
+ "gix-features",
+]
+
+[[package]]
+name = "gix-glob"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd0ade1e80ab1f079703d1824e1daf73009096386aa7fd2f0477f6e4ac0a558e"
+dependencies = [
+ "bitflags 2.5.0",
+ "bstr",
+ "gix-features",
+ "gix-path",
+]
+
+[[package]]
+name = "gix-hash"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b422ff2ad9a0628baaad6da468cf05385bf3f5ab495ad5a33cce99b9f41092f"
+dependencies = [
+ "hex",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-hashtable"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "385f4ce6ecf3692d313ca3aa9bd3b3d8490de53368d6d94bedff3af8b6d9c58d"
+dependencies = [
+ "gix-hash",
+ "hashbrown 0.14.3",
+ "parking_lot",
+]
+
+[[package]]
+name = "gix-ignore"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc6f7f101a0ccce808dbf7008ba131dede94e20257e7bde7a44cbb2f8c775625"
+dependencies = [
+ "bstr",
+ "gix-glob",
+ "gix-path",
+ "unicode-bom",
+]
+
+[[package]]
+name = "gix-index"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "616ba958fabfb11263fa042c35690d48a6c7be4e9277e2c7e24ff263b3fe7b82"
+dependencies = [
+ "bitflags 2.5.0",
+ "bstr",
+ "btoi",
+ "filetime",
+ "gix-bitmap",
+ "gix-features",
+ "gix-hash",
+ "gix-lock",
+ "gix-object",
+ "gix-traverse",
+ "itoa",
+ "memmap2",
+ "smallvec",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-lock"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ec5d5e6f07316d3553aa7425e3ecd935ec29882556021fe1696297a448af8d2"
+dependencies = [
+ "gix-tempfile",
+ "gix-utils",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-mailmap"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4653701922c920e009f1bc4309feaff14882ade017770788f9a150928da3fa6a"
+dependencies = [
+ "bstr",
+ "gix-actor",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-negotiate"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "945c3ef1e912e44a5f405fc9e924edf42000566a1b257ed52cb1293300f6f08c"
+dependencies = [
+ "bitflags 2.5.0",
+ "gix-commitgraph",
+ "gix-hash",
+ "gix-object",
+ "gix-revision",
+ "smallvec",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-object"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8926c8f51c44dec3e709cb5dbc93deb9e8d4064c43c9efc54c158dcdfe8446c7"
+dependencies = [
+ "bstr",
+ "btoi",
+ "gix-actor",
+ "gix-features",
+ "gix-hash",
+ "gix-validate",
+ "hex",
+ "itoa",
+ "nom",
+ "smallvec",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-odb"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b234d806278eeac2f907c8b5a105c4ba537230c1a9d9236d822bf0db291f8f3"
+dependencies = [
+ "arc-swap",
+ "gix-features",
+ "gix-hash",
+ "gix-object",
+ "gix-pack",
+ "gix-path",
+ "gix-quote",
+ "parking_lot",
+ "tempfile",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-pack"
+version = "0.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d2a14cb3156037eedb17d6cb7209b7180522b8949b21fd0fe3184c0a1d0af88"
+dependencies = [
+ "clru",
+ "gix-chunk",
+ "gix-diff",
+ "gix-features",
+ "gix-hash",
+ "gix-hashtable",
+ "gix-object",
+ "gix-path",
+ "gix-tempfile",
+ "gix-traverse",
+ "memmap2",
+ "parking_lot",
+ "smallvec",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-packetline"
+version = "0.16.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a8384b1e964151aff0d5632dd9b191059d07dff358b96bd940f1b452600d7ab"
+dependencies = [
+ "bstr",
+ "faster-hex",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-path"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18609c8cbec8508ea97c64938c33cd305b75dfc04a78d0c3b78b8b3fd618a77c"
+dependencies = [
+ "bstr",
+ "gix-trace",
+ "home",
+ "once_cell",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-prompt"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c22decaf4a063ccae2b2108820c8630c01bd6756656df3fe464b32b8958a5ea"
+dependencies = [
+ "gix-command",
+ "gix-config-value",
+ "parking_lot",
+ "rustix",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-protocol"
+version = "0.33.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92a17058b45c461f0847528c5fb6ee6e76115e026979eb2d2202f98ee94f6c24"
+dependencies = [
+ "bstr",
+ "btoi",
+ "gix-credentials",
+ "gix-features",
+ "gix-hash",
+ "gix-transport",
+ "maybe-async",
+ "nom",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-quote"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbff4f9b9ea3fa7a25a70ee62f545143abef624ac6aa5884344e70c8b0a1d9ff"
+dependencies = [
+ "bstr",
+ "gix-utils",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-ref"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebdd999256f4ce8a5eefa89999879c159c263f3493a951d62aa5ce42c0397e1c"
+dependencies = [
+ "gix-actor",
+ "gix-features",
+ "gix-fs",
+ "gix-hash",
+ "gix-lock",
+ "gix-object",
+ "gix-path",
+ "gix-tempfile",
+ "gix-validate",
+ "memmap2",
+ "nom",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-refspec"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72bfd622abc86dd8ad1ec51b9eb77b4f1a766b94e3a1b87cf4a022c5b5570cf4"
+dependencies = [
+ "bstr",
+ "gix-hash",
+ "gix-revision",
+ "gix-validate",
+ "smallvec",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-revision"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5044f56cd7a487ce9b034cbe0252ae0b6b47ff56ca3dabd79bc30214d0932cd7"
+dependencies = [
+ "bstr",
+ "gix-date",
+ "gix-hash",
+ "gix-hashtable",
+ "gix-object",
+ "gix-revwalk",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-revwalk"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc2623ba8747914f151f5e12b65adac576ab459dbed5f50a36c7a3e9cbf2d3ca"
+dependencies = [
+ "gix-commitgraph",
+ "gix-hash",
+ "gix-hashtable",
+ "gix-object",
+ "smallvec",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-sec"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9615cbd6b456898aeb942cd75e5810c382fbfc48dbbff2fa23ebd2d33dcbe9c7"
+dependencies = [
+ "bitflags 2.5.0",
+ "gix-path",
+ "libc",
+ "windows",
+]
+
+[[package]]
+name = "gix-tempfile"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3785cb010e9dc5c446dfbf02bc1119fc17d3a48a27c029efcb3a3c32953eb10"
+dependencies = [
+ "gix-fs",
+ "libc",
+ "once_cell",
+ "parking_lot",
+ "signal-hook",
+ "signal-hook-registry",
+ "tempfile",
+]
+
+[[package]]
+name = "gix-trace"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b838b2db8f62c9447d483a4c28d251b67fee32741a82cb4d35e9eb4e9fdc5ab"
+
+[[package]]
+name = "gix-transport"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64a39ffed9a9078ed700605e064b15d7c6ae50aa65e7faa36ca6919e8081df15"
+dependencies = [
+ "base64",
+ "bstr",
+ "curl",
+ "gix-command",
+ "gix-credentials",
+ "gix-features",
+ "gix-packetline",
+ "gix-quote",
+ "gix-sec",
+ "gix-url",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-traverse"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0842e984cb4bf26339dc559f3a1b8bf8cdb83547799b2b096822a59f87f33d9"
+dependencies = [
+ "gix-hash",
+ "gix-hashtable",
+ "gix-object",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-url"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1663df25ac42047a2547618d2a6979a26f478073f6306997429235d2cd4c863"
+dependencies = [
+ "bstr",
+ "gix-features",
+ "gix-path",
+ "home",
+ "thiserror",
+ "url",
+]
+
+[[package]]
+name = "gix-utils"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0066432d4c277f9877f091279a597ea5331f68ca410efc874f0bdfb1cd348f92"
+dependencies = [
+ "fastrand",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "gix-validate"
+version = "0.7.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba9b3737b2cef3dcd014633485f0034b0f1a931ee54aeb7d8f87f177f3c89040"
+dependencies = [
+ "bstr",
+ "thiserror",
+]
+
+[[package]]
+name = "gix-worktree"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d388ad962e8854402734a7387af8790f6bdbc8d05349052dab16ca4a0def50f6"
+dependencies = [
+ "bstr",
+ "filetime",
+ "gix-attributes",
+ "gix-features",
+ "gix-fs",
+ "gix-glob",
+ "gix-hash",
+ "gix-ignore",
+ "gix-index",
+ "gix-object",
+ "gix-path",
+ "io-close",
+ "thiserror",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "globset"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "log",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "group"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
+dependencies = [
+ "ff",
+ "rand_core",
+ "subtle",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hkdf"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
+dependencies = [
+ "hmac",
+]
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "http-auth"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "643c9bbf6a4ea8a656d6b4cd53d34f79e3f841ad5203c1a55fb7d761923bc255"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "ignore"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1"
+dependencies = [
+ "crossbeam-deque",
+ "globset",
+ "log",
+ "memchr",
+ "regex-automata",
+ "same-file",
+ "walkdir",
+ "winapi-util",
+]
+
+[[package]]
+name = "im-rc"
+version = "15.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe"
+dependencies = [
+ "bitmaps",
+ "rand_core",
+ "rand_xoshiro",
+ "sized-chunks",
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "imara-diff"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
+dependencies = [
+ "ahash",
+ "hashbrown 0.12.3",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.14.3",
+]
+
+[[package]]
+name = "io-close"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "jobserver"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "kstring"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747"
+dependencies = [
+ "static_assertions",
+]
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.153"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+
+[[package]]
+name = "libgit2-sys"
+version = "0.15.2+1.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a80df2e11fb4a61f4ba2ab42dbe7f74468da143f1a75c74e11dee7c813f694fa"
+dependencies = [
+ "cc",
+ "libc",
+ "libssh2-sys",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+]
+
+[[package]]
+name = "libnghttp2-sys"
+version = "0.1.9+1.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b57e858af2798e167e709b9d969325b6d8e9d50232fcbc494d7d54f976854a64"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "libssh2-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee"
+dependencies = [
+ "cc",
+ "libc",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "libz-sys"
+version = "1.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
+
+[[package]]
+name = "lock_api"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+
+[[package]]
+name = "maybe-async"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
+
+[[package]]
+name = "memmap2"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "miow"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "359f76430b20a79f9e20e115b3428614e654f04fab314482fc0fda0ebd3c6044"
+dependencies = [
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "num_threads"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "opener"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "293c15678e37254c15bd2f092314abb4e51d7fdde05c2021279c12631b54f005"
+dependencies = [
+ "bstr",
+ "winapi",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "ordered-float"
+version = "2.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "orion"
+version = "0.17.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abdb10181903c8c4b016ba45d6d6d5af1a1e2a461aa4763a83b87f5df4695e5"
+dependencies = [
+ "fiat-crypto",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "os_info"
+version = "3.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092"
+dependencies = [
+ "log",
+ "serde",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "p384"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209"
+dependencies = [
+ "ecdsa",
+ "elliptic-curve",
+ "primeorder",
+ "sha2",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "pasetors"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b36d47c66f2230dd1b7143d9afb2b4891879020210eddf2ccb624e529b96dba"
+dependencies = [
+ "ct-codecs",
+ "ed25519-compact",
+ "getrandom",
+ "orion",
+ "p384",
+ "rand_core",
+ "regex",
+ "serde",
+ "serde_json",
+ "sha2",
+ "subtle",
+ "time",
+ "zeroize",
+]
+
+[[package]]
+name = "pathdiff"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
+
+[[package]]
+name = "pem-rfc7468"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
+dependencies = [
+ "base64ct",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "primeorder"
+version = "0.13.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
+dependencies = [
+ "elliptic-curve",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "prodash"
+version = "25.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d67eb4220992a4a052a4bb03cf776e493ecb1a3a36bab551804153d63486af7"
+dependencies = [
+ "parking_lot",
+]
+
+[[package]]
+name = "pulldown-cmark"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
+dependencies = [
+ "bitflags 2.5.0",
+ "memchr",
+ "unicase",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_xoshiro"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
+
+[[package]]
+name = "rfc6979"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
+dependencies = [
+ "hmac",
+ "subtle",
+]
+
+[[package]]
+name = "rustfix"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecd2853d9e26988467753bd9912c3a126f642d05d229a4b53f5752ee36c56481"
+dependencies = [
+ "anyhow",
+ "log",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
+dependencies = [
+ "bitflags 2.5.0",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "sec1"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
+dependencies = [
+ "base16ct",
+ "der",
+ "generic-array",
+ "pkcs8",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.197"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde-value"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
+dependencies = [
+ "ordered-float",
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.197"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_ignored"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8e319a36d1b52126a0d608f24e93b2d81297091818cd70625fcf50a15d84ddf"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.115"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha1_smol"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
+
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shell-escape"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
+
+[[package]]
+name = "signal-hook"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "digest",
+ "rand_core",
+]
+
+[[package]]
+name = "sized-chunks"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
+dependencies = [
+ "bitmaps",
+ "typenum",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
+name = "socket2"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strip-ansi-escapes"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8"
+dependencies = [
+ "vte",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "subtle"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
+
+[[package]]
+name = "syn"
+version = "2.0.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tar"
+version = "0.4.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb"
+dependencies = [
+ "filetime",
+ "libc",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "rustix",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "terminal_size"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
+dependencies = [
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "threadpool"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
+dependencies = [
+ "num_cpus",
+]
+
+[[package]]
+name = "time"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
+dependencies = [
+ "deranged",
+ "itoa",
+ "libc",
+ "num-conv",
+ "num_threads",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "toml"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap 2.2.6",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "unicase"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
+
+[[package]]
+name = "unicode-bom"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
+
+[[package]]
+name = "url"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "vte"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983"
+dependencies = [
+ "arrayvec",
+ "utf8parse",
+ "vte_generate_state_changes",
+]
+
+[[package]]
+name = "vte_generate_state_changes"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+
+[[package]]
+name = "web-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "whoami"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9"
+dependencies = [
+ "redox_syscall",
+ "wasite",
+ "web-sys",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.4",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.4",
+ "windows_aarch64_msvc 0.52.4",
+ "windows_i686_gnu 0.52.4",
+ "windows_i686_msvc 0.52.4",
+ "windows_x86_64_gnu 0.52.4",
+ "windows_x86_64_gnullvm 0.52.4",
+ "windows_x86_64_msvc 0.52.4",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
+
+[[package]]
+name = "winnow"
+version = "0.5.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
diff --git a/tools/external_crates/Cargo.toml b/tools/external_crates/Cargo.toml
new file mode 100644
index 000000000..f0b017094
--- /dev/null
+++ b/tools/external_crates/Cargo.toml
@@ -0,0 +1,6 @@
+[workspace]
+members = [
+ "crate_health",
+ "crate_health_proc_macros",
+]
+resolver = "2"
diff --git a/tools/external_crates/OWNERS b/tools/external_crates/OWNERS
new file mode 100644
index 000000000..2d111b4f5
--- /dev/null
+++ b/tools/external_crates/OWNERS
@@ -0,0 +1,2 @@
+jamesfarrell@google.com
+srhines@google.com
diff --git a/tools/external_crates/crate_health/Cargo.toml b/tools/external_crates/crate_health/Cargo.toml
new file mode 100644
index 000000000..3e02f3648
--- /dev/null
+++ b/tools/external_crates/crate_health/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "crate_health"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1"
+cargo = "0.73"
+clap = { version = "4.4.6", features = ["derive"] }
+copy_dir = "0.1"
+glob = "0.3"
+itertools = "0.11"
+num_cpus = "1"
+semver = "1"
+serde = { version = "1", features = ["derive"] }
+serde_json = "1"
+thiserror = "1"
+threadpool = "1"
+tinytemplate = "1.2"
+walkdir = "2"
+whoami = "1"
+crate_health_proc_macros = { path = "../crate_health_proc_macros" }
+
+[dev-dependencies]
+tempfile = "3"
diff --git a/tools/external_crates/crate_health/src/android_bp.rs b/tools/external_crates/crate_health/src/android_bp.rs
new file mode 100644
index 000000000..a6a0c3aa1
--- /dev/null
+++ b/tools/external_crates/crate_health/src/android_bp.rs
@@ -0,0 +1,139 @@
+// 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.
+
+use std::{
+ collections::BTreeMap,
+ env,
+ path::Path,
+ process::{Command, Output},
+ str::from_utf8,
+ sync::mpsc::channel,
+};
+
+use anyhow::{anyhow, Context, Result};
+use threadpool::ThreadPool;
+
+use crate::{Crate, NameAndVersion, NameAndVersionMap, NamedAndVersioned};
+
+pub fn generate_android_bps<'a, T: Iterator<Item = &'a Crate>>(
+ crates: T,
+) -> Result<BTreeMap<NameAndVersion, (Output, Output)>> {
+ let pool = ThreadPool::new(std::cmp::max(num_cpus::get(), 32));
+ let (tx, rx) = channel();
+
+ let mut num_crates = 0;
+ for krate in crates {
+ num_crates += 1;
+ let tx = tx.clone();
+ let crate_name = krate.name().to_string();
+ let crate_version = krate.version().clone();
+ let repo_root = krate.root().to_path_buf();
+ let android_bp_path = krate.android_bp();
+ let test_path = krate.staging_path();
+ pool.execute(move || {
+ println!("Generating Android.bp for {} {}", crate_name, crate_version);
+ tx.send((
+ crate_name,
+ crate_version,
+ generate_android_bp(&repo_root, &android_bp_path, &test_path),
+ ))
+ .expect("Failed to send");
+ });
+ }
+ let mut results = BTreeMap::new();
+ for (crate_name, crate_version, result) in rx.iter().take(num_crates) {
+ results.insert_or_error(NameAndVersion::new(crate_name, crate_version), result?)?;
+ }
+ Ok(results)
+}
+
+pub(crate) fn generate_android_bp(
+ repo_root: &impl AsRef<Path>,
+ android_bp_path: &impl AsRef<Path>,
+ staging_path: &impl AsRef<Path>,
+) -> Result<(Output, Output)> {
+ let generate_android_bp_output = run_cargo_embargo(repo_root, staging_path)?;
+ if !generate_android_bp_output.status.success() {
+ println!(
+ "cargo_embargo failed for {}\nstdout:\n{}\nstderr:\n{}",
+ android_bp_path.as_ref().display(),
+ from_utf8(&generate_android_bp_output.stdout)?,
+ from_utf8(&generate_android_bp_output.stderr)?
+ );
+ }
+ let diff_output =
+ diff(&android_bp_path.as_ref(), &staging_path.as_ref().join("Android.bp"), repo_root)
+ .context("Failed to diff Android.bp".to_string())?;
+ Ok((generate_android_bp_output, diff_output))
+}
+
+fn run_cargo_embargo(
+ repo_root: &impl AsRef<Path>,
+ staging_path: &impl AsRef<Path>,
+) -> Result<Output> {
+ // Make sure we can find bpfmt.
+ let host_bin = repo_root.as_ref().join("out/host/linux-x86/bin");
+ let new_path = match env::var_os("PATH") {
+ Some(p) => {
+ let mut paths = vec![host_bin];
+ paths.extend(env::split_paths(&p));
+ env::join_paths(paths)?
+ }
+ None => host_bin.as_os_str().into(),
+ };
+
+ let staging_path_absolute = repo_root.as_ref().join(staging_path);
+ let mut cmd = Command::new(repo_root.as_ref().join("out/host/linux-x86/bin/cargo_embargo"));
+ cmd.args(["generate", "cargo_embargo.json"])
+ .env("PATH", new_path)
+ .env("ANDROID_BUILD_TOP", repo_root.as_ref())
+ .current_dir(&staging_path_absolute)
+ .output()
+ .context(format!("Failed to execute {:?}", cmd.get_program()))
+}
+
+fn diff(a: &impl AsRef<Path>, b: &impl AsRef<Path>, root: &impl AsRef<Path>) -> Result<Output> {
+ Ok(Command::new("diff")
+ .args([
+ "-u",
+ "-w",
+ "-B",
+ "-I",
+ "// has rustc warnings",
+ "-I",
+ "This file is generated by",
+ "-I",
+ "cargo_pkg_version:",
+ ])
+ .arg(a.as_ref())
+ .arg(b.as_ref())
+ .current_dir(root)
+ .output()?)
+}
+
+pub fn build_cargo_embargo(repo_root: &impl AsRef<Path>) -> Result<()> {
+ let output = Command::new("/usr/bin/bash")
+ .args(["-c", "source build/envsetup.sh && lunch aosp_cf_x86_64_phone-trunk_staging-userdebug && m cargo_embargo bpfmt"])
+ .current_dir(repo_root)
+ .output()
+ .context("Failed to build cargo embargo and bpfmt")?;
+ if !output.status.success() {
+ return Err(anyhow!(
+ "Failed to build cargo embargo and bpfmt.\nstdout:\n{}\nstderr:\n{}",
+ from_utf8(&output.stdout)?,
+ from_utf8(&output.stderr)?
+ ));
+ }
+ Ok(())
+}
diff --git a/tools/external_crates/crate_health/src/bin/health_report.rs b/tools/external_crates/crate_health/src/bin/health_report.rs
new file mode 100644
index 000000000..6b083e686
--- /dev/null
+++ b/tools/external_crates/crate_health/src/bin/health_report.rs
@@ -0,0 +1,52 @@
+// 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.
+
+use std::path::PathBuf;
+
+use anyhow::Result;
+use clap::Parser;
+use crate_health::{
+ build_cargo_embargo, default_output_dir, default_repo_root, CrateCollection, NameAndVersionMap,
+ ReportEngine,
+};
+
+/// Generate a health report for crates in external/rust/crates
+#[derive(Parser, Debug)]
+#[command(about, long_about = None)]
+struct Args {
+ /// Path to the AOSP repo. Defaults to current working directory.
+ #[arg(long, default_value_os_t=default_repo_root().unwrap_or(PathBuf::from(".")))]
+ repo_root: PathBuf,
+
+ /// Path the health report will be written to.
+ #[arg(long, default_value_os_t=default_output_dir("crate-health-report.html"))]
+ output_path: PathBuf,
+}
+
+fn main() -> Result<()> {
+ let args = Args::parse();
+
+ build_cargo_embargo(&args.repo_root)?;
+
+ let mut cc = CrateCollection::new(args.repo_root);
+ cc.add_from(&"external/rust/crates", None::<&&str>)?;
+ cc.map_field_mut().retain(|_nv, krate| krate.is_crates_io());
+
+ cc.stage_crates()?;
+ cc.generate_android_bps()?;
+
+ let re = ReportEngine::new()?;
+
+ Ok(re.health_report(&cc, &args.output_path)?)
+}
diff --git a/tools/external_crates/crate_health/src/bin/migration_report.rs b/tools/external_crates/crate_health/src/bin/migration_report.rs
new file mode 100644
index 000000000..ca3edaa43
--- /dev/null
+++ b/tools/external_crates/crate_health/src/bin/migration_report.rs
@@ -0,0 +1,47 @@
+// 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.
+
+use std::path::PathBuf;
+
+use anyhow::Result;
+use clap::Parser;
+use crate_health::{
+ build_cargo_embargo, default_output_dir, default_repo_root, migrate, ReportEngine,
+};
+
+/// Generate a health report for crates in external/rust/crates
+#[derive(Parser, Debug)]
+#[command(about, long_about = None)]
+struct Args {
+ /// Path to the AOSP repo. Defaults to current working directory.
+ #[arg(long, default_value_os_t=default_repo_root().unwrap_or(PathBuf::from(".")))]
+ repo_root: PathBuf,
+
+ /// Path the health report will be written to.
+ #[arg(long, default_value_os_t=default_output_dir("crate-migration-report.html"))]
+ output_path: PathBuf,
+}
+
+fn main() -> Result<()> {
+ let args = Args::parse();
+
+ build_cargo_embargo(&args.repo_root)?;
+
+ let migration =
+ migrate(args.repo_root, &"external/rust/crates", &"out/rust-crate-migration-report")?;
+
+ let re = ReportEngine::new()?;
+
+ Ok(re.migration_report(&migration, &args.output_path)?)
+}
diff --git a/tools/external_crates/crate_health/src/crate_collection.rs b/tools/external_crates/crate_health/src/crate_collection.rs
new file mode 100644
index 000000000..03dc4fe22
--- /dev/null
+++ b/tools/external_crates/crate_health/src/crate_collection.rs
@@ -0,0 +1,93 @@
+// 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.
+
+use crate_health_proc_macros::NameAndVersionMap;
+
+use std::{
+ collections::HashSet,
+ path::{Path, PathBuf},
+};
+
+use anyhow::{anyhow, Result};
+use semver::Version;
+use walkdir::WalkDir;
+
+use crate::{
+ android_bp::generate_android_bps, Crate, CrateError, NameAndVersion, NameAndVersionMap,
+ NamedAndVersioned,
+};
+
+use std::collections::BTreeMap;
+
+#[derive(NameAndVersionMap)]
+pub struct CrateCollection {
+ crates: BTreeMap<NameAndVersion, Crate>,
+ repo_root: PathBuf,
+}
+
+impl CrateCollection {
+ pub fn new<P: Into<PathBuf>>(repo_root: P) -> CrateCollection {
+ CrateCollection { crates: BTreeMap::new(), repo_root: repo_root.into() }
+ }
+ pub fn add_from(
+ &mut self,
+ path: &impl AsRef<Path>,
+ pseudo_crate: Option<&impl AsRef<Path>>,
+ ) -> Result<()> {
+ for entry_or_err in WalkDir::new(self.repo_root.join(path)) {
+ let entry = entry_or_err?;
+ if entry.file_name() == "Cargo.toml" {
+ match Crate::from(
+ &entry.path(),
+ &self.repo_root.as_path(),
+ pseudo_crate.map(|p| p.as_ref()),
+ ) {
+ Ok(krate) => self.crates.insert_or_error(
+ NameAndVersion::new(krate.name().to_string(), krate.version().clone()),
+ krate,
+ )?,
+ Err(e) => match e.downcast_ref() {
+ Some(CrateError::VirtualCrate(_)) => (),
+ _ => return Err(e),
+ },
+ };
+ }
+ }
+ Ok(())
+ }
+ pub fn repo_root(&self) -> &Path {
+ self.repo_root.as_path()
+ }
+ pub fn print(&self) -> Result<()> {
+ for krate in self.crates.values() {
+ krate.print()?
+ }
+ Ok(())
+ }
+ pub fn stage_crates(&self) -> Result<()> {
+ for krate in self.crates.values() {
+ krate.stage_crate()?
+ }
+ Ok(())
+ }
+ pub fn generate_android_bps(&mut self) -> Result<()> {
+ for (nv, output) in generate_android_bps(self.crates.values())?.into_iter() {
+ self.crates
+ .get_mut(&nv)
+ .ok_or(anyhow!("Failed to get crate {} {}", nv.name(), nv.version()))?
+ .set_generate_android_bp_output(output.0, output.1);
+ }
+ Ok(())
+ }
+}
diff --git a/tools/external_crates/crate_health/src/crate_type.rs b/tools/external_crates/crate_health/src/crate_type.rs
new file mode 100644
index 000000000..7f02edd19
--- /dev/null
+++ b/tools/external_crates/crate_health/src/crate_type.rs
@@ -0,0 +1,392 @@
+// 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.
+
+use std::{
+ fs::{copy, read_dir, remove_dir_all},
+ path::{Path, PathBuf},
+ process::{Command, Output},
+ str::from_utf8,
+};
+
+use anyhow::{anyhow, Context, Result};
+use cargo::{
+ core::{Manifest, SourceId},
+ util::toml::read_manifest,
+ Config,
+};
+use copy_dir::copy_dir;
+use semver::Version;
+
+use crate::{
+ ensure_exists_and_empty, name_and_version::IsUpgradableTo, CrateError, NameAndVersionRef,
+ NamedAndVersioned,
+};
+
+#[derive(Debug)]
+pub struct Crate {
+ manifest: Manifest,
+
+ // root is absolute. All other paths are relative to it.
+ root: PathBuf,
+ relpath: PathBuf,
+ pseudo_crate: Option<PathBuf>,
+
+ // compatible_dest_version: Option<Version>,
+ patch_output: Vec<Output>,
+ generate_android_bp_output: Option<Output>,
+ android_bp_diff: Option<Output>,
+}
+
+impl NamedAndVersioned for Crate {
+ fn name(&self) -> &str {
+ self.manifest.name().as_str()
+ }
+ fn version(&self) -> &Version {
+ self.manifest.version()
+ }
+ fn key<'k>(&'k self) -> NameAndVersionRef<'k> {
+ NameAndVersionRef::new(self.name(), self.version())
+ }
+}
+
+impl IsUpgradableTo for Crate {}
+
+impl Crate {
+ pub fn new<P: Into<PathBuf>, Q: Into<PathBuf>, R: Into<PathBuf>>(
+ manifest: Manifest,
+ root: P,
+ relpath: Q,
+ pseudo_crate: Option<R>,
+ ) -> Crate {
+ Crate {
+ manifest,
+ root: root.into(),
+ relpath: relpath.into(),
+ pseudo_crate: pseudo_crate.map(|p| p.into()),
+ // compatible_dest_version: None,
+ patch_output: Vec::new(),
+ generate_android_bp_output: None,
+ android_bp_diff: None,
+ }
+ }
+ pub fn from<P: Into<PathBuf>, Q: Into<PathBuf>>(
+ cargo_toml: &impl AsRef<Path>,
+ root: P,
+ pseudo_crate: Option<Q>,
+ ) -> Result<Crate> {
+ let root: PathBuf = root.into();
+ let manifest_dir = cargo_toml.as_ref().parent().ok_or(anyhow!(
+ "Failed to get parent directory of manifest at {}",
+ cargo_toml.as_ref().display()
+ ))?;
+ let relpath = manifest_dir.strip_prefix(&root)?.to_path_buf();
+ let source_id = SourceId::for_path(manifest_dir)?;
+ let (manifest, _nested) =
+ read_manifest(cargo_toml.as_ref(), source_id, &Config::default()?)?;
+ match manifest {
+ cargo::core::EitherManifest::Real(r) => Ok(Crate::new(r, root, relpath, pseudo_crate)),
+ cargo::core::EitherManifest::Virtual(_) => {
+ Err(anyhow!(CrateError::VirtualCrate(cargo_toml.as_ref().to_path_buf())))
+ }
+ }
+ }
+
+ pub fn root(&self) -> &Path {
+ self.root.as_path()
+ }
+ pub fn relpath(&self) -> &Path {
+ &self.relpath.as_path()
+ }
+ pub fn path(&self) -> PathBuf {
+ self.root.join(&self.relpath)
+ }
+ pub fn android_bp(&self) -> PathBuf {
+ if let Some(d) = self.customization_dir() {
+ d.join("Android.bp.disabled")
+ } else {
+ self.path().join("Android.bp")
+ }
+ }
+ pub fn cargo_embargo_json(&self) -> PathBuf {
+ if let Some(d) = self.customization_dir() {
+ d.join("cargo_embargo.json")
+ } else {
+ self.path().join("cargo_embargo.json")
+ }
+ }
+ pub fn staging_path(&self) -> PathBuf {
+ Path::new("out/rust-crate-temporary-build").join(self.staging_dir_name())
+ }
+ pub fn customization_dir(&self) -> Option<PathBuf> {
+ self.pseudo_crate.as_ref().map(|pseudo_crate| {
+ pseudo_crate.join("android/customizations").join(self.staging_dir_name())
+ })
+ }
+ pub fn patch_dir(&self) -> Option<PathBuf> {
+ self.customization_dir().map(|p| p.join("patches"))
+ }
+ pub fn staging_dir_name(&self) -> String {
+ if let Some(dirname) = self.relpath.file_name().and_then(|x| x.to_str()) {
+ if dirname == self.name() {
+ return dirname.to_string();
+ }
+ }
+ format!("{}-{}", self.name(), self.version().to_string())
+ }
+
+ pub fn aosp_url(&self) -> Option<String> {
+ if self.relpath.starts_with("external/rust/crates") {
+ if self.relpath.ends_with(self.name()) {
+ Some(format!(
+ "https://android.googlesource.com/platform/{}/+/refs/heads/main",
+ self.relpath().display()
+ ))
+ } else if self.relpath.parent()?.ends_with(self.name()) {
+ Some(format!(
+ "https://android.googlesource.com/platform/{}/+/refs/heads/main/{}",
+ self.relpath().parent()?.display(),
+ self.relpath().file_name()?.to_str()?
+ ))
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+ pub fn crates_io_url(&self) -> String {
+ format!("https://crates.io/crates/{}", self.name())
+ }
+
+ pub fn is_vendored(&self) -> bool {
+ self.pseudo_crate.is_some()
+ }
+ pub fn is_crates_io(&self) -> bool {
+ const NOT_CRATES_IO: &'static [&'static str] = &[
+ "external/rust/beto-rust/", // Google crates
+ "external/rust/pica/", // Google crate
+ "external/rust/crates/webpki/third-party/", // Internal/example code
+ "external/rust/cxx/third-party/", // Internal/example code
+ "external/rust/cxx/demo/", // Internal/example code
+ ];
+ !NOT_CRATES_IO.iter().any(|prefix| self.relpath.starts_with(prefix))
+ }
+ pub fn is_migration_denied(&self) -> bool {
+ const MIGRATION_DENYLIST: &'static [&'static str] = &[
+ "external/rust/crates/openssl/", // It's complicated.
+ "external/rust/cxx/", // It's REALLY complicated.
+ ];
+ MIGRATION_DENYLIST.iter().any(|prefix| self.relpath.starts_with(prefix))
+ }
+ pub fn is_android_bp_healthy(&self) -> bool {
+ !self.is_migration_denied()
+ && self.android_bp().exists()
+ && self.cargo_embargo_json().exists()
+ && self.generate_android_bp_success()
+ && self.android_bp_unchanged()
+ }
+ pub fn patch_success(&self) -> bool {
+ self.patch_output.iter().all(|output| output.status.success())
+ }
+ pub fn generate_android_bp_success(&self) -> bool {
+ self.generate_android_bp_output.as_ref().is_some_and(|output| output.status.success())
+ }
+ pub fn android_bp_unchanged(&self) -> bool {
+ self.android_bp_diff.as_ref().is_some_and(|output| output.status.success())
+ }
+
+ pub fn print(&self) -> Result<()> {
+ println!("{} {} {}", self.name(), self.version(), self.relpath.display());
+ if let Some(output) = &self.generate_android_bp_output {
+ println!("generate Android.bp exit status: {}", output.status);
+ println!("{}", from_utf8(&output.stdout)?);
+ println!("{}", from_utf8(&output.stderr)?);
+ }
+ if let Some(output) = &self.android_bp_diff {
+ println!("diff exit status: {}", output.status);
+ println!("{}", from_utf8(&output.stdout)?);
+ println!("{}", from_utf8(&output.stderr)?);
+ }
+ Ok(())
+ }
+
+ // Make a clean copy of the crate in out/
+ pub fn stage_crate(&self) -> Result<()> {
+ let staging_path_absolute = self.root().join(self.staging_path());
+ ensure_exists_and_empty(&staging_path_absolute)?;
+ remove_dir_all(&staging_path_absolute)
+ .context(format!("Failed to remove {}", staging_path_absolute.display()))?;
+ copy_dir(self.path(), &staging_path_absolute).context(format!(
+ "Failed to copy {} to {}",
+ self.path().display(),
+ staging_path_absolute.display()
+ ))?;
+ if staging_path_absolute.join(".git").is_dir() {
+ remove_dir_all(staging_path_absolute.join(".git"))
+ .with_context(|| "Failed to remove .git".to_string())?;
+ }
+ self.copy_customizations()
+ }
+ pub fn copy_customizations(&self) -> Result<()> {
+ if let Some(customization_dir) = self.customization_dir() {
+ let customization_dir_absolute = self.root().join(customization_dir);
+ let staging_path_absolute = self.root().join(self.staging_path());
+ for entry in read_dir(&customization_dir_absolute)
+ .context(format!("Failed to read_dir {}", customization_dir_absolute.display()))?
+ {
+ let entry = entry?;
+ let entry_path = entry.path();
+ let mut filename = entry.file_name().to_os_string();
+ if entry_path.is_dir() {
+ copy_dir(&entry_path, staging_path_absolute.join(&filename)).context(
+ format!(
+ "Failed to copy {} to {}",
+ entry_path.display(),
+ staging_path_absolute.display()
+ ),
+ )?;
+ } else {
+ if let Some(extension) = entry_path.extension() {
+ if extension == "disabled" {
+ let mut new_filename = entry_path.clone();
+ new_filename.set_extension("");
+ filename = new_filename
+ .file_name()
+ .context(format!(
+ "Failed to get file name for {}",
+ new_filename.display()
+ ))?
+ .to_os_string();
+ }
+ }
+ copy(&entry_path, staging_path_absolute.join(filename)).context(format!(
+ "Failed to copy {} to {}",
+ entry_path.display(),
+ staging_path_absolute.display()
+ ))?;
+ }
+ }
+ }
+ Ok(())
+ }
+
+ pub fn apply_patches(&mut self) -> Result<()> {
+ if let Some(patch_dir) = self.patch_dir() {
+ let patch_dir_absolute = self.root().join(patch_dir);
+ if patch_dir_absolute.exists() {
+ println!("Patching {}", self.path().display());
+ for entry in read_dir(&patch_dir_absolute)
+ .context(format!("Failed to read_dir {}", patch_dir_absolute.display()))?
+ {
+ let entry = entry?;
+ if entry.file_name() == "Android.bp.patch"
+ || entry.file_name() == "Android.bp.diff"
+ {
+ continue;
+ }
+ let entry_path = entry.path();
+ println!(" applying {}", entry_path.display());
+ let output = Command::new("patch")
+ .args(["-p1", "-l"])
+ .arg(&entry_path)
+ .current_dir(self.root().join(self.staging_path()))
+ .output()?;
+ if !output.status.success() {
+ println!(
+ "Failed to apply {}\nstdout:\n{}\nstderr:\n:{}",
+ entry_path.display(),
+ from_utf8(&output.stdout)?,
+ from_utf8(&output.stderr)?
+ );
+ }
+ self.patch_output.push(output);
+ }
+ }
+ }
+ Ok(())
+ }
+
+ pub fn android_bp_diff(&self) -> Option<&Output> {
+ self.android_bp_diff.as_ref()
+ }
+ pub fn generate_android_bp_output(&self) -> Option<&Output> {
+ self.generate_android_bp_output.as_ref()
+ }
+ pub fn set_generate_android_bp_output(&mut self, c2a_output: Output, diff_output: Output) {
+ self.generate_android_bp_output.replace(c2a_output);
+ self.android_bp_diff.replace(diff_output);
+ }
+ pub fn set_patch_output(&mut self, patch_output: Vec<Output>) {
+ self.patch_output = patch_output;
+ }
+}
+
+pub trait Migratable {
+ fn is_migration_eligible(&self) -> bool;
+ fn is_migratable(&self) -> bool;
+}
+
+impl Migratable for Crate {
+ fn is_migration_eligible(&self) -> bool {
+ self.is_crates_io()
+ && !self.is_migration_denied()
+ && self.android_bp().exists()
+ && self.cargo_embargo_json().exists()
+ }
+ fn is_migratable(&self) -> bool {
+ self.patch_success() && self.generate_android_bp_success() && self.android_bp_unchanged()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::fs::{create_dir, write};
+
+ use super::*;
+ use anyhow::anyhow;
+ use tempfile::tempdir;
+
+ fn write_test_manifest(temp_crate_dir: &Path, name: &str, version: &str) -> Result<PathBuf> {
+ let cargo_toml: PathBuf = [temp_crate_dir, &Path::new("Cargo.toml")].iter().collect();
+ write(
+ cargo_toml.as_path(),
+ format!("[package]\nname = \"{}\"\nversion = \"{}\"\n", name, version),
+ )?;
+ let lib_rs: PathBuf = [temp_crate_dir, &Path::new("src/lib.rs")].iter().collect();
+ create_dir(lib_rs.parent().ok_or(anyhow!("Failed to get parent"))?)?;
+ write(lib_rs.as_path(), "// foo")?;
+ Ok(cargo_toml)
+ }
+
+ #[test]
+ fn test_from_and_properties() -> Result<()> {
+ let temp_crate_dir = tempdir()?;
+ let cargo_toml = write_test_manifest(temp_crate_dir.path(), "foo", "1.2.0")?;
+ let krate = Crate::from(&cargo_toml, &"/", None::<&&str>)?;
+ assert_eq!(krate.name(), "foo");
+ assert_eq!(krate.version().to_string(), "1.2.0");
+ assert!(krate.is_crates_io());
+ assert_eq!(krate.android_bp(), temp_crate_dir.path().join("Android.bp"));
+ assert_eq!(krate.cargo_embargo_json(), temp_crate_dir.path().join("cargo_embargo.json"));
+ Ok(())
+ }
+
+ #[test]
+ fn test_from_error() -> Result<()> {
+ let temp_crate_dir = tempdir()?;
+ let cargo_toml = write_test_manifest(temp_crate_dir.path(), "foo", "1.2.0")?;
+ assert!(Crate::from(&cargo_toml, &"/blah", None::<&&str>).is_err());
+ Ok(())
+ }
+}
diff --git a/tools/external_crates/crate_health/src/lib.rs b/tools/external_crates/crate_health/src/lib.rs
new file mode 100644
index 000000000..b2b8f57a4
--- /dev/null
+++ b/tools/external_crates/crate_health/src/lib.rs
@@ -0,0 +1,92 @@
+// 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.
+
+use std::env::current_dir;
+use std::fs::{create_dir_all, remove_dir_all};
+use std::path::{Path, PathBuf};
+
+use anyhow::{anyhow, Context, Result};
+use semver::Version;
+use thiserror::Error;
+
+pub use self::crate_type::{Crate, Migratable};
+mod crate_type;
+
+pub use self::crate_collection::CrateCollection;
+mod crate_collection;
+
+pub use self::reports::{ReportEngine, SizeReport, Table};
+mod reports;
+
+pub use self::migration::migrate;
+mod migration;
+
+pub use self::pseudo_crate::write_pseudo_crate;
+mod pseudo_crate;
+
+pub use self::version_match::{CompatibleVersionPair, VersionMatch, VersionPair};
+mod version_match;
+
+pub use self::android_bp::{build_cargo_embargo, generate_android_bps};
+mod android_bp;
+
+pub use self::name_and_version::{
+ IsUpgradableTo, NameAndVersion, NameAndVersionRef, NamedAndVersioned,
+};
+mod name_and_version;
+
+#[cfg(test)]
+pub use self::name_and_version_map::try_name_version_map_from_iter;
+pub use self::name_and_version_map::{
+ crates_with_multiple_versions, crates_with_single_version, most_recent_version,
+ NameAndVersionMap,
+};
+mod name_and_version_map;
+
+#[derive(Error, Debug)]
+pub enum CrateError {
+ #[error("Virtual crate: {0}")]
+ VirtualCrate(PathBuf),
+
+ #[error("Duplicate crate version: {0} {1}")]
+ DuplicateCrateVersion(String, Version),
+}
+
+pub fn default_repo_root() -> Result<PathBuf> {
+ let cwd = current_dir().context("Could not get current working directory")?;
+ for cur in cwd.ancestors() {
+ for e in cur.read_dir()? {
+ if e?.file_name() == ".repo" {
+ return Ok(cur.to_path_buf());
+ }
+ }
+ }
+ Err(anyhow!(".repo directory not found in any ancestor of {}", cwd.display()))
+}
+
+pub fn default_output_dir(filename: &str) -> PathBuf {
+ PathBuf::from("/google/data/rw/users")
+ .join(&whoami::username()[..2])
+ .join(whoami::username())
+ .join("www")
+ .join(filename)
+}
+
+pub fn ensure_exists_and_empty(dir: &impl AsRef<Path>) -> Result<()> {
+ let dir = dir.as_ref();
+ if dir.exists() {
+ remove_dir_all(&dir).context(format!("Failed to remove {}", dir.display()))?;
+ }
+ create_dir_all(&dir).context(format!("Failed to create {}", dir.display()))
+}
diff --git a/tools/external_crates/crate_health/src/migration.rs b/tools/external_crates/crate_health/src/migration.rs
new file mode 100644
index 000000000..0555cd463
--- /dev/null
+++ b/tools/external_crates/crate_health/src/migration.rs
@@ -0,0 +1,105 @@
+// 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.
+
+use std::{
+ fs::copy,
+ path::{Path, PathBuf},
+};
+
+use anyhow::{anyhow, Context, Result};
+use copy_dir::copy_dir;
+use glob::glob;
+
+use crate::{
+ ensure_exists_and_empty, most_recent_version, write_pseudo_crate, CompatibleVersionPair, Crate,
+ CrateCollection, Migratable, NameAndVersionMap, VersionMatch,
+};
+
+static CUSTOMIZATIONS: &'static [&'static str] =
+ &["*.bp", "cargo_embargo.json", "patches", "METADATA"];
+
+impl<'a> CompatibleVersionPair<'a, Crate> {
+ pub fn copy_customizations(&self) -> Result<()> {
+ let dest_dir_absolute = self.dest.root().join(
+ self.dest
+ .customization_dir()
+ .ok_or(anyhow!("No customization dir for {}", self.dest.path().display()))?,
+ );
+ ensure_exists_and_empty(&dest_dir_absolute)?;
+ for pattern in CUSTOMIZATIONS {
+ let full_pattern = self.source.path().join(pattern);
+ for entry in glob(
+ full_pattern
+ .to_str()
+ .ok_or(anyhow!("Failed to convert path {} to str", full_pattern.display()))?,
+ )? {
+ let entry = entry?;
+ let mut filename = entry
+ .file_name()
+ .context(format!("Failed to get file name for {}", entry.display()))?
+ .to_os_string();
+ if entry.is_dir() {
+ copy_dir(&entry, dest_dir_absolute.join(filename)).context(format!(
+ "Failed to copy {} to {}",
+ entry.display(),
+ dest_dir_absolute.display()
+ ))?;
+ } else {
+ if let Some(extension) = entry.extension() {
+ if extension == "bp" {
+ filename.push(".disabled");
+ }
+ }
+ copy(&entry, dest_dir_absolute.join(filename)).context(format!(
+ "Failed to copy {} to {}",
+ entry.display(),
+ dest_dir_absolute.display()
+ ))?;
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+pub fn migrate<P: Into<PathBuf>>(
+ repo_root: P,
+ source_dir: &impl AsRef<Path>,
+ pseudo_crate_dir: &impl AsRef<Path>,
+) -> Result<VersionMatch<CrateCollection>> {
+ let mut source = CrateCollection::new(repo_root);
+ source.add_from(source_dir, None::<&&str>)?;
+ source.map_field_mut().retain(|_nv, krate| krate.is_crates_io());
+
+ let pseudo_crate_dir_absolute = source.repo_root().join(pseudo_crate_dir);
+ write_pseudo_crate(
+ &pseudo_crate_dir_absolute,
+ source
+ .filter_versions(&most_recent_version)
+ .filter(|(_nv, krate)| krate.is_migration_eligible())
+ .map(|(_nv, krate)| krate),
+ )?;
+
+ let mut dest = CrateCollection::new(source.repo_root());
+ dest.add_from(&pseudo_crate_dir.as_ref().join("android/vendor"), Some(pseudo_crate_dir))?;
+
+ let mut version_match = VersionMatch::new(source, dest)?;
+
+ version_match.copy_customizations()?;
+ version_match.stage_crates()?;
+ version_match.apply_patches()?;
+ version_match.generate_android_bps()?;
+
+ Ok(version_match)
+}
diff --git a/tools/external_crates/crate_health/src/name_and_version.rs b/tools/external_crates/crate_health/src/name_and_version.rs
new file mode 100644
index 000000000..90dc24319
--- /dev/null
+++ b/tools/external_crates/crate_health/src/name_and_version.rs
@@ -0,0 +1,189 @@
+// 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.
+
+use std::{
+ borrow::Borrow,
+ cmp::Ordering,
+ hash::{Hash, Hasher},
+};
+
+#[cfg(test)]
+use anyhow::Result;
+
+use semver::{BuildMetadata, Prerelease, Version, VersionReq};
+
+static MIN_VERSION: Version =
+ Version { major: 0, minor: 0, patch: 0, pre: Prerelease::EMPTY, build: BuildMetadata::EMPTY };
+
+pub trait NamedAndVersioned {
+ fn name(&self) -> &str;
+ fn version(&self) -> &Version;
+ fn key<'a>(&'a self) -> NameAndVersionRef<'a>;
+}
+
+#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone)]
+pub struct NameAndVersion {
+ name: String,
+ version: Version,
+}
+
+#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
+pub struct NameAndVersionRef<'a> {
+ name: &'a str,
+ version: &'a Version,
+}
+
+impl NameAndVersion {
+ pub fn new(name: String, version: Version) -> Self {
+ NameAndVersion { name, version }
+ }
+ pub fn from(nv: &impl NamedAndVersioned) -> Self {
+ NameAndVersion { name: nv.name().to_string(), version: nv.version().clone() }
+ }
+ pub fn min_version(name: String) -> Self {
+ NameAndVersion { name, version: MIN_VERSION.clone() }
+ }
+ #[cfg(test)]
+ pub fn try_from_str(name: &str, version: &str) -> Result<Self> {
+ Ok(NameAndVersion::new(name.to_string(), Version::parse(version)?))
+ }
+}
+
+impl NamedAndVersioned for NameAndVersion {
+ fn name(&self) -> &str {
+ self.name.as_str()
+ }
+
+ fn version(&self) -> &Version {
+ &self.version
+ }
+ fn key<'k>(&'k self) -> NameAndVersionRef<'k> {
+ NameAndVersionRef::new(self.name(), self.version())
+ }
+}
+
+impl<'a> NameAndVersionRef<'a> {
+ pub fn new(name: &'a str, version: &'a Version) -> Self {
+ NameAndVersionRef { name, version }
+ }
+}
+
+impl<'a> NamedAndVersioned for NameAndVersionRef<'a> {
+ fn name(&self) -> &str {
+ self.name
+ }
+ fn version(&self) -> &Version {
+ self.version
+ }
+ fn key<'k>(&'k self) -> NameAndVersionRef<'k> {
+ *self
+ }
+}
+
+impl<'a> Borrow<dyn NamedAndVersioned + 'a> for NameAndVersion {
+ fn borrow(&self) -> &(dyn NamedAndVersioned + 'a) {
+ self
+ }
+}
+
+impl<'a> PartialEq for (dyn NamedAndVersioned + 'a) {
+ fn eq(&self, other: &Self) -> bool {
+ self.key().eq(&other.key())
+ }
+}
+
+impl<'a> Eq for (dyn NamedAndVersioned + 'a) {}
+
+impl<'a> PartialOrd for (dyn NamedAndVersioned + 'a) {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ self.key().partial_cmp(&other.key())
+ }
+}
+
+impl<'a> Ord for (dyn NamedAndVersioned + 'a) {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.key().cmp(&other.key())
+ }
+}
+
+impl<'a> Hash for (dyn NamedAndVersioned + 'a) {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.key().hash(state)
+ }
+}
+
+pub trait IsUpgradableTo: NamedAndVersioned {
+ fn is_upgradable_to(&self, other: &impl NamedAndVersioned) -> bool {
+ self.name() == other.name()
+ && VersionReq::parse(&self.version().to_string())
+ .is_ok_and(|req| req.matches(other.version()))
+ }
+}
+
+impl<'a> IsUpgradableTo for NameAndVersion {}
+impl<'a> IsUpgradableTo for NameAndVersionRef<'a> {}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use anyhow::Result;
+
+ #[test]
+ fn test_name_version_ref() -> Result<()> {
+ let version = Version::parse("2.3.4")?;
+ let compat1 = Version::parse("2.3.5")?;
+ let compat2 = Version::parse("2.4.0")?;
+ let incompat = Version::parse("3.0.0")?;
+ let older = Version::parse("2.3.3")?;
+ let nvp = NameAndVersionRef::new("foo", &version);
+ assert_eq!(nvp.name(), "foo");
+ assert_eq!(nvp.version().to_string(), "2.3.4");
+ assert!(nvp.is_upgradable_to(&NameAndVersionRef::new("foo", &compat1)), "Patch update");
+ assert!(
+ nvp.is_upgradable_to(&NameAndVersionRef::new("foo", &compat2)),
+ "Minor version update"
+ );
+ assert!(
+ !nvp.is_upgradable_to(&NameAndVersionRef::new("foo", &incompat)),
+ "Incompatible (major version) update"
+ );
+ assert!(!nvp.is_upgradable_to(&NameAndVersionRef::new("foo", &older)), "Downgrade");
+ assert!(!nvp.is_upgradable_to(&NameAndVersionRef::new("bar", &compat1)), "Different name");
+ Ok(())
+ }
+
+ #[test]
+ fn test_name_and_version() -> Result<()> {
+ let version = Version::parse("2.3.4")?;
+ let compat1 = Version::parse("2.3.5")?;
+ let compat2 = Version::parse("2.4.0")?;
+ let incompat = Version::parse("3.0.0")?;
+ let older = Version::parse("2.3.3")?;
+ let nvp = NameAndVersion::new("foo".to_string(), version);
+ assert_eq!(nvp.name(), "foo");
+ assert_eq!(nvp.version().to_string(), "2.3.4");
+ assert!(nvp.is_upgradable_to(&NameAndVersionRef::new("foo", &compat1)), "Patch update");
+ assert!(
+ nvp.is_upgradable_to(&NameAndVersionRef::new("foo", &compat2)),
+ "Minor version update"
+ );
+ assert!(
+ !nvp.is_upgradable_to(&NameAndVersionRef::new("foo", &incompat)),
+ "Incompatible (major version) update"
+ );
+ assert!(!nvp.is_upgradable_to(&NameAndVersionRef::new("foo", &older)), "Downgrade");
+ assert!(!nvp.is_upgradable_to(&NameAndVersionRef::new("bar", &compat1)), "Different name");
+ Ok(())
+ }
+}
diff --git a/tools/external_crates/crate_health/src/name_and_version_map.rs b/tools/external_crates/crate_health/src/name_and_version_map.rs
new file mode 100644
index 000000000..f35d40415
--- /dev/null
+++ b/tools/external_crates/crate_health/src/name_and_version_map.rs
@@ -0,0 +1,289 @@
+// 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.
+
+use std::collections::{BTreeMap, HashSet};
+
+use anyhow::Result;
+use itertools::Itertools;
+use semver::Version;
+
+use crate::{CrateError, IsUpgradableTo, NameAndVersion, NamedAndVersioned};
+
+pub trait NameAndVersionMap {
+ type Value;
+
+ fn map_field(&self) -> &BTreeMap<NameAndVersion, Self::Value>;
+ fn map_field_mut(&mut self) -> &mut BTreeMap<NameAndVersion, Self::Value>;
+
+ fn insert_or_error(&mut self, key: NameAndVersion, val: Self::Value) -> Result<(), CrateError>;
+ fn num_crates(&self) -> usize;
+ fn contains_name(&self, name: &str) -> bool {
+ self.get_versions(name).next().is_some()
+ }
+ fn get_versions<'a, 'b>(
+ &'a self,
+ name: &'b str,
+ ) -> Box<dyn Iterator<Item = (&'a NameAndVersion, &'a Self::Value)> + 'a>;
+ fn get_versions_mut<'a, 'b>(
+ &'a mut self,
+ name: &'b str,
+ ) -> Box<dyn Iterator<Item = (&'a NameAndVersion, &'a mut Self::Value)> + 'a>;
+ fn get_version_upgradable_from<T: NamedAndVersioned + IsUpgradableTo>(
+ &self,
+ other: &T,
+ ) -> Option<&NameAndVersion> {
+ let mut best_version = None;
+ for (nv, _val) in self.get_versions(other.name()) {
+ if other.is_upgradable_to(nv) {
+ best_version.replace(nv);
+ }
+ }
+ best_version
+ }
+ 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>;
+}
+
+impl<ValueType> NameAndVersionMap for BTreeMap<NameAndVersion, ValueType> {
+ type Value = ValueType;
+
+ fn map_field(&self) -> &BTreeMap<NameAndVersion, Self::Value> {
+ self
+ }
+
+ fn map_field_mut(&mut self) -> &mut BTreeMap<NameAndVersion, Self::Value> {
+ self
+ }
+
+ fn insert_or_error(&mut self, key: NameAndVersion, val: Self::Value) -> Result<(), CrateError> {
+ if self.contains_key(&key) {
+ Err(CrateError::DuplicateCrateVersion(key.name().to_string(), key.version().clone()))
+ } else {
+ self.insert(key, val);
+ Ok(())
+ }
+ }
+
+ fn num_crates(&self) -> usize {
+ let mut seen = ::std::collections::HashSet::new();
+ for nv in self.keys() {
+ seen.insert(nv.name().to_string());
+ }
+ seen.len()
+ }
+
+ fn get_versions<'a, 'b>(
+ &'a self,
+ name: &'b str,
+ ) -> Box<dyn Iterator<Item = (&'a NameAndVersion, &'a Self::Value)> + 'a> {
+ let owned_name = name.to_string();
+ Box::new(
+ self.range(std::ops::RangeFrom {
+ start: NameAndVersion::min_version(name.to_string()),
+ })
+ .map_while(move |x| if x.0.name() == owned_name { Some(x) } else { None }),
+ )
+ }
+
+ fn get_versions_mut<'a, 'b>(
+ &'a mut self,
+ name: &'b str,
+ ) -> Box<dyn Iterator<Item = (&'a NameAndVersion, &'a mut Self::Value)> + 'a> {
+ let owned_name = name.to_string();
+ Box::new(
+ self.range_mut(std::ops::RangeFrom {
+ start: NameAndVersion::min_version(name.to_string()),
+ })
+ .map_while(move |x| if x.0.name() == owned_name { Some(x) } else { None }),
+ )
+ }
+
+ 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> {
+ let mut kept_keys: HashSet<NameAndVersion> = HashSet::new();
+ for (key, mut group) in self.iter().group_by(|item| item.0.name()).into_iter() {
+ kept_keys.extend(
+ f(&mut group).into_iter().map(move |v| NameAndVersion::new(key.to_string(), v)),
+ );
+ }
+ Box::new(self.iter().filter(move |(nv, _krate)| kept_keys.contains(*nv)))
+ }
+}
+
+pub fn crates_with_single_version<'a, ValueType>(
+ versions: &mut dyn Iterator<Item = (&'a NameAndVersion, &'a ValueType)>,
+) -> HashSet<Version> {
+ let mut vset = HashSet::new();
+ versions.into_iter().map(|(nv, _crate)| vset.insert(nv.version().clone())).count();
+ if vset.len() != 1 {
+ vset.clear()
+ }
+ vset
+}
+
+pub fn crates_with_multiple_versions<'a, ValueType>(
+ versions: &mut dyn Iterator<Item = (&'a NameAndVersion, &'a ValueType)>,
+) -> HashSet<Version> {
+ let mut vset = HashSet::new();
+ versions.into_iter().map(|(nv, _crate)| vset.insert(nv.version().clone())).count();
+ if vset.len() == 1 {
+ vset.clear()
+ }
+ vset
+}
+
+pub fn most_recent_version<'a, ValueType>(
+ versions: &mut dyn Iterator<Item = (&'a NameAndVersion, &'a ValueType)>,
+) -> HashSet<Version> {
+ let mut vset = HashSet::new();
+ if let Some((nv, _crate)) = versions.into_iter().last() {
+ vset.insert(nv.version().clone());
+ }
+ vset
+}
+
+#[cfg(test)]
+pub fn try_name_version_map_from_iter<'a, ValueType>(
+ nvs: impl IntoIterator<Item = (&'a str, &'a str, ValueType)>,
+) -> Result<BTreeMap<NameAndVersion, ValueType>> {
+ let mut test_map = BTreeMap::new();
+ for (name, version, val) in nvs {
+ test_map.insert_or_error(NameAndVersion::try_from_str(name, version)?, val)?;
+ }
+ Ok(test_map)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{NameAndVersion, NameAndVersionRef};
+
+ use super::*;
+ use anyhow::Result;
+ use itertools::assert_equal;
+
+ #[test]
+ fn test_name_and_version_map_empty() -> Result<()> {
+ let mut test_map: BTreeMap<NameAndVersion, String> = BTreeMap::new();
+ let v = Version::parse("1.2.3")?;
+ let nvp = NameAndVersionRef::new("foo", &v);
+ // let nvp = NameAndVersion::try_from_str("foo", "1.2.3")?;
+ assert_eq!(test_map.num_crates(), 0);
+ assert!(!test_map.contains_key(&nvp as &dyn NamedAndVersioned));
+ assert!(!test_map.contains_name("foo"));
+ assert!(test_map.get(&nvp as &dyn NamedAndVersioned).is_none());
+ assert!(test_map.get_mut(&nvp as &dyn NamedAndVersioned).is_none());
+ Ok(())
+ }
+
+ #[test]
+ fn test_name_and_version_map_nonempty() -> Result<()> {
+ let mut test_map = try_name_version_map_from_iter([
+ ("foo", "1.2.3", "foo v1".to_string()),
+ ("foo", "2.3.4", "foo v2".to_string()),
+ ("bar", "1.0.0", "bar".to_string()),
+ ])?;
+
+ let foo1 = NameAndVersion::try_from_str("foo", "1.2.3")?;
+ let foo2 = NameAndVersion::try_from_str("foo", "2.3.4")?;
+ let bar = NameAndVersion::try_from_str("bar", "1.0.0")?;
+ let wrong_name = NameAndVersion::try_from_str("baz", "1.2.3")?;
+ let wrong_version = NameAndVersion::try_from_str("foo", "1.0.0")?;
+
+ assert_eq!(test_map.num_crates(), 2);
+
+ assert!(test_map.contains_key(&foo1));
+ assert!(test_map.contains_key(&foo2));
+ assert!(test_map.contains_key(&bar));
+ assert!(!test_map.contains_key(&wrong_name));
+ assert!(!test_map.contains_key(&wrong_version));
+
+ assert!(test_map.contains_name("foo"));
+ assert!(test_map.contains_name("bar"));
+ assert!(!test_map.contains_name("baz"));
+
+ assert_eq!(test_map.get(&foo1), Some(&"foo v1".to_string()));
+ assert_eq!(test_map.get(&foo2), Some(&"foo v2".to_string()));
+ assert_eq!(test_map.get(&bar), Some(&"bar".to_string()));
+ assert!(test_map.get(&wrong_name).is_none());
+ assert!(test_map.get(&wrong_version).is_none());
+
+ assert_eq!(test_map.get_mut(&foo1), Some(&mut "foo v1".to_string()));
+ assert_eq!(test_map.get_mut(&foo2), Some(&mut "foo v2".to_string()));
+ assert_eq!(test_map.get_mut(&bar), Some(&mut "bar".to_string()));
+ assert!(test_map.get_mut(&wrong_name).is_none());
+ assert!(test_map.get_mut(&wrong_version).is_none());
+
+ assert_eq!(
+ test_map.get_version_upgradable_from(&NameAndVersion::try_from_str("foo", "1.2.2")?),
+ Some(&foo1)
+ );
+
+ // TOOD: Iter
+ assert_equal(test_map.keys(), [&bar, &foo1, &foo2]);
+
+ assert_equal(test_map.values(), ["bar", "foo v1", "foo v2"]);
+ assert_equal(test_map.values_mut(), ["bar", "foo v1", "foo v2"]);
+
+ assert_equal(
+ test_map.iter().filter(|(_nv, x)| x.starts_with("foo")).map(|(_nv, val)| val),
+ ["foo v1", "foo v2"],
+ );
+
+ test_map.retain(|_nv, x| x.starts_with("foo"));
+ assert_equal(test_map.values(), ["foo v1", "foo v2"]);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_filter_versions() -> Result<()> {
+ let test_map = try_name_version_map_from_iter([
+ ("foo", "1.2.3", ()),
+ ("foo", "2.3.4", ()),
+ ("bar", "1.0.0", ()),
+ ])?;
+ let foo1 = NameAndVersion::try_from_str("foo", "1.2.3")?;
+ let foo2 = NameAndVersion::try_from_str("foo", "2.3.4")?;
+ let bar = NameAndVersion::try_from_str("bar", "1.0.0")?;
+
+ assert_equal(
+ test_map.filter_versions(crates_with_single_version).map(|(nv, _)| nv),
+ [&bar],
+ );
+ assert_equal(
+ test_map.filter_versions(crates_with_multiple_versions).map(|(nv, _)| nv),
+ [&foo1, &foo2],
+ );
+ assert_equal(
+ test_map.filter_versions(most_recent_version).map(|(nv, _)| nv),
+ [&bar, &foo2],
+ );
+
+ Ok(())
+ }
+}
diff --git a/tools/external_crates/crate_health/src/pseudo_crate.rs b/tools/external_crates/crate_health/src/pseudo_crate.rs
new file mode 100644
index 000000000..25b45bf0f
--- /dev/null
+++ b/tools/external_crates/crate_health/src/pseudo_crate.rs
@@ -0,0 +1,91 @@
+// 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.
+
+use std::{
+ fs::{create_dir, write},
+ path::Path,
+ process::Command,
+ str::from_utf8,
+};
+
+use anyhow::{anyhow, Context, Result};
+use serde::Serialize;
+use tinytemplate::TinyTemplate;
+
+use crate::{ensure_exists_and_empty, NamedAndVersioned};
+
+static CARGO_TOML_TEMPLATE: &'static str = include_str!("templates/Cargo.toml.template");
+
+#[derive(Serialize)]
+struct Dep {
+ name: String,
+ version: String,
+}
+
+#[derive(Serialize)]
+struct CargoToml {
+ deps: Vec<Dep>,
+}
+
+pub fn write_pseudo_crate<'a>(
+ dest_absolute: &impl AsRef<Path>,
+ crates: impl Iterator<Item = &'a (impl NamedAndVersioned + 'a)>,
+) -> Result<()> {
+ let dest_absolute = dest_absolute.as_ref();
+ ensure_exists_and_empty(&dest_absolute)?;
+
+ let mut deps = Vec::new();
+ for krate in crates {
+ // Special cases:
+ // * libsqlite3-sys is a sub-crate of rusqlite
+ // * remove_dir_all has a version not known by crates.io (b/313489216)
+ if krate.name() != "libsqlite3-sys" {
+ deps.push(Dep {
+ name: krate.name().to_string(),
+ version: if krate.name() == "remove_dir_all"
+ && krate.version().to_string() == "0.7.1"
+ {
+ "0.7.0".to_string()
+ } else {
+ krate.version().to_string()
+ },
+ });
+ }
+ }
+
+ let mut tt = TinyTemplate::new();
+ tt.add_template("cargo_toml", CARGO_TOML_TEMPLATE)?;
+ let cargo_toml = dest_absolute.join("Cargo.toml");
+ write(&cargo_toml, tt.render("cargo_toml", &CargoToml { deps })?)?;
+
+ create_dir(dest_absolute.join("src")).context("Failed to create src dir")?;
+ write(dest_absolute.join("src/lib.rs"), "// Nothing").context("Failed to create src/lib.rs")?;
+
+ let vendor_output = Command::new("cargo")
+ .args(["vendor", "android/vendor"])
+ .current_dir(dest_absolute)
+ .output()?;
+ if !vendor_output.status.success() {
+ return Err(anyhow!(
+ "cargo vendor failed with exit code {}\nstdout:\n{}\nstderr:\n{}",
+ vendor_output.status,
+ from_utf8(&vendor_output.stdout)?,
+ from_utf8(&vendor_output.stderr)?
+ ));
+ }
+
+ // TODO: Run "cargo deny"
+
+ Ok(())
+}
diff --git a/tools/external_crates/crate_health/src/reports.rs b/tools/external_crates/crate_health/src/reports.rs
new file mode 100644
index 000000000..c70b5676b
--- /dev/null
+++ b/tools/external_crates/crate_health/src/reports.rs
@@ -0,0 +1,326 @@
+// 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.
+
+use std::{fmt::Display, fs::write, path::Path, str::from_utf8};
+
+use crate::{
+ crates_with_multiple_versions, crates_with_single_version, Crate, CrateCollection,
+ NameAndVersionMap, NamedAndVersioned, VersionMatch, VersionPair,
+};
+
+use anyhow::Result;
+use serde::Serialize;
+use tinytemplate::TinyTemplate;
+
+static SIZE_REPORT_TEMPLATE: &'static str = include_str!("templates/size_report.html.template");
+static TABLE_TEMPLATE: &'static str = include_str!("templates/table.html.template");
+
+static CRATE_HEALTH_REPORT_TEMPLATE: &'static str =
+ include_str!("templates/crate_health.html.template");
+static MIGRATION_REPORT_TEMPLATE: &'static str =
+ include_str!("templates/migration_report.html.template");
+
+pub struct ReportEngine<'template> {
+ tt: TinyTemplate<'template>,
+}
+
+fn len_formatter(value: &serde_json::Value, out: &mut String) -> tinytemplate::error::Result<()> {
+ match value {
+ serde_json::Value::Array(a) => {
+ out.push_str(&format!("{}", a.len()));
+ Ok(())
+ }
+ _ => Err(tinytemplate::error::Error::GenericError {
+ msg: "Can only use length formatter on an array".to_string(),
+ }),
+ }
+}
+
+fn linkify(text: &dyn Display, url: &dyn Display) -> String {
+ format!("<a href=\"{}\">{}</a>", url, text)
+}
+
+impl<'template> ReportEngine<'template> {
+ pub fn new() -> Result<ReportEngine<'template>> {
+ let mut tt = TinyTemplate::new();
+ tt.add_template("size_report", SIZE_REPORT_TEMPLATE)?;
+ tt.add_template("table", TABLE_TEMPLATE)?;
+ tt.add_template("crate_health", CRATE_HEALTH_REPORT_TEMPLATE)?;
+ tt.add_template("migration", MIGRATION_REPORT_TEMPLATE)?;
+ tt.add_formatter("len", len_formatter);
+ Ok(ReportEngine { tt })
+ }
+ pub fn size_report(&self, cc: &CrateCollection) -> Result<String> {
+ let num_crates = cc.num_crates();
+ let crates_with_single_version = cc.filter_versions(&crates_with_single_version).count();
+ Ok(self.tt.render(
+ "size_report",
+ &SizeReport {
+ num_crates,
+ crates_with_single_version,
+ crates_with_multiple_versions: num_crates - crates_with_single_version,
+ num_dirs: cc.map_field().len(),
+ },
+ )?)
+ }
+ pub fn table<'a>(&self, crates: impl Iterator<Item = &'a Crate>) -> Result<String> {
+ let mut table = Table::new(&[&"Crate", &"Version", &"Path"]);
+ for krate in crates {
+ table.add_row(&[
+ &linkify(&krate.name(), &krate.crates_io_url()),
+ &krate.version().to_string(),
+ &krate.aosp_url().map_or(format!("{}", krate.relpath().display()), |url| {
+ linkify(&krate.relpath().display(), &url)
+ }),
+ ]);
+ }
+ Ok(self.tt.render("table", &table)?)
+ }
+ pub fn health_table<'a>(&self, crates: impl Iterator<Item = &'a Crate>) -> Result<String> {
+ let mut table = Table::new(&[
+ &"Crate",
+ &"Version",
+ &"Path",
+ &"Has Android.bp",
+ &"Generate Android.bp succeeds",
+ &"Android.bp unchanged",
+ &"Has cargo_embargo.json",
+ &"On migration denylist",
+ ]);
+ table.set_vertical_headers();
+ for krate in crates {
+ table.add_row(&[
+ &linkify(&krate.name(), &krate.crates_io_url()),
+ &krate.version().to_string(),
+ &krate.aosp_url().map_or(format!("{}", krate.relpath().display()), |url| {
+ linkify(&krate.relpath().display(), &url)
+ }),
+ &prefer_yes(krate.android_bp().exists()),
+ &prefer_yes_or_summarize(
+ krate.generate_android_bp_success(),
+ krate
+ .generate_android_bp_output()
+ .map_or("Error".to_string(), |o| {
+ format!(
+ "STDOUT:\n{}\n\nSTDERR:\n{}",
+ from_utf8(&o.stdout).unwrap_or("Error"),
+ from_utf8(&o.stderr).unwrap_or("Error")
+ )
+ })
+ .as_str(),
+ ),
+ &prefer_yes_or_summarize(
+ krate.android_bp_unchanged(),
+ krate
+ .android_bp_diff()
+ .map_or("Error", |o| from_utf8(&o.stdout).unwrap_or("Error")),
+ ),
+ &prefer_yes(krate.cargo_embargo_json().exists()),
+ &prefer_no(krate.is_migration_denied()),
+ ]);
+ }
+ Ok(self.tt.render("table", &table)?)
+ }
+ pub fn migration_ineligible_table<'a>(
+ &self,
+ crates: impl Iterator<Item = &'a Crate>,
+ ) -> Result<String> {
+ let mut table = Table::new(&[
+ &"Crate",
+ &"Version",
+ &"Path",
+ &"In crates.io",
+ &"Denylisted",
+ &"Has Android.bp",
+ &"Has cargo_embargo.json",
+ ]);
+ table.set_vertical_headers();
+ for krate in crates {
+ table.add_row(&[
+ &linkify(&krate.name(), &krate.crates_io_url()),
+ &krate.version().to_string(),
+ &krate.aosp_url().map_or(format!("{}", krate.relpath().display()), |url| {
+ linkify(&krate.relpath().display(), &url)
+ }),
+ &prefer_yes(krate.is_crates_io()),
+ &prefer_no(krate.is_migration_denied()),
+ &prefer_yes(krate.android_bp().exists()),
+ &prefer_yes(krate.cargo_embargo_json().exists()),
+ ]);
+ }
+ Ok(self.tt.render("table", &table)?)
+ }
+ pub fn migration_eligible_table<'a>(
+ &self,
+ crate_pairs: impl Iterator<Item = VersionPair<'a, Crate>>,
+ ) -> Result<String> {
+ let mut table = Table::new(&[
+ &"Crate",
+ &"Version",
+ &"Path",
+ &"Compatible version",
+ &"Patch succeeds",
+ &"Generate Android.bp succeeds",
+ &"Android.bp unchanged",
+ ]);
+ table.set_vertical_headers();
+ for crate_pair in crate_pairs {
+ let source = crate_pair.source;
+ let maybe_dest = crate_pair.dest;
+ table.add_row(&[
+ &linkify(&source.name(), &source.crates_io_url()),
+ &source.version().to_string(),
+ &source.aosp_url().map_or(format!("{}", source.relpath().display()), |url| {
+ linkify(&source.relpath().display(), &url)
+ }),
+ maybe_dest.map_or(&"None", |dest| {
+ if dest.version() != source.version() {
+ dest.version()
+ } else {
+ &""
+ }
+ }),
+ &prefer_yes(!maybe_dest.is_some_and(|dest| !dest.patch_success())),
+ &prefer_yes_or_summarize(
+ !maybe_dest.is_some_and(|dest| !dest.generate_android_bp_success()),
+ maybe_dest
+ .map_or("Error".to_string(), |dest| {
+ dest.generate_android_bp_output().map_or("Error".to_string(), |o| {
+ format!(
+ "STDOUT:\n{}\n\nSTDERR:\n{}",
+ from_utf8(&o.stdout).unwrap_or("Error"),
+ from_utf8(&o.stderr).unwrap_or("Error")
+ )
+ })
+ })
+ .as_str(),
+ ),
+ &prefer_yes_or_summarize(
+ !maybe_dest.is_some_and(|dest| !dest.android_bp_unchanged()),
+ maybe_dest.map_or("Error", |dest| {
+ dest.android_bp_diff()
+ .map_or("Error", |o| from_utf8(&o.stdout).unwrap_or("Error"))
+ }),
+ ),
+ ]);
+ }
+ Ok(self.tt.render("table", &table)?)
+ }
+ pub fn health_report(
+ &self,
+ cc: &CrateCollection,
+ output_path: &impl AsRef<Path>,
+ ) -> Result<()> {
+ let chr = CrateHealthReport {
+ crate_count: self.size_report(cc)?,
+ crate_multiversion: self.table(
+ cc.filter_versions(&crates_with_multiple_versions).map(|(_nv, krate)| krate),
+ )?,
+ healthy: self.table(
+ cc.map_field()
+ .iter()
+ .filter(|(_nv, krate)| krate.is_android_bp_healthy())
+ .map(|(_nv, krate)| krate),
+ )?,
+ unhealthy: self.health_table(
+ cc.map_field()
+ .iter()
+ .filter(|(_nv, krate)| !krate.is_android_bp_healthy())
+ .map(|(_nv, krate)| krate),
+ )?,
+ };
+ Ok(write(output_path, self.tt.render("crate_health", &chr)?)?)
+ }
+ pub fn migration_report(
+ &self,
+ m: &VersionMatch<CrateCollection>,
+ output_path: &impl AsRef<Path>,
+ ) -> Result<()> {
+ let mr = MigrationReport {
+ migratable: self.table(m.migratable().map(|pair| pair.source))?,
+ eligible: self.migration_eligible_table(m.eligible_but_not_migratable())?,
+ ineligible: self.migration_ineligible_table(m.ineligible())?,
+ superfluous: self.table(m.superfluous().map(|(_nv, krate)| krate))?,
+ };
+ Ok(write(output_path, self.tt.render("migration", &mr)?)?)
+ }
+}
+
+pub fn prefer_yes(p: bool) -> &'static str {
+ if p {
+ ""
+ } else {
+ "No"
+ }
+}
+pub fn prefer_yes_or_summarize(p: bool, details: &str) -> String {
+ if p {
+ "".to_string()
+ } else {
+ format!("<details><summary>No</summary><pre>{}</pre></details>", details)
+ }
+}
+pub fn prefer_no(p: bool) -> &'static str {
+ if p {
+ "Yes"
+ } else {
+ ""
+ }
+}
+
+#[derive(Serialize)]
+pub struct SizeReport {
+ num_crates: usize,
+ crates_with_single_version: usize,
+ crates_with_multiple_versions: usize,
+ num_dirs: usize,
+}
+
+#[derive(Serialize)]
+pub struct Table {
+ header: Vec<String>,
+ rows: Vec<Vec<String>>,
+ vertical: bool,
+}
+
+impl Table {
+ pub fn new(header: &[&dyn Display]) -> Table {
+ Table {
+ header: header.iter().map(|cell| format!("{}", cell)).collect::<Vec<_>>(),
+ rows: Vec::new(),
+ vertical: false,
+ }
+ }
+ pub fn add_row(&mut self, row: &[&dyn Display]) {
+ self.rows.push(row.iter().map(|cell| format!("{}", cell)).collect::<Vec<_>>());
+ }
+ pub fn set_vertical_headers(&mut self) {
+ self.vertical = true;
+ }
+}
+
+#[derive(Serialize)]
+pub struct CrateHealthReport {
+ crate_count: String,
+ crate_multiversion: String,
+ healthy: String,
+ unhealthy: String,
+}
+#[derive(Serialize)]
+pub struct MigrationReport {
+ migratable: String,
+ eligible: String,
+ ineligible: String,
+ superfluous: String,
+}
diff --git a/tools/external_crates/crate_health/src/templates/Cargo.toml.template b/tools/external_crates/crate_health/src/templates/Cargo.toml.template
new file mode 100644
index 000000000..52a6c5d78
--- /dev/null
+++ b/tools/external_crates/crate_health/src/templates/Cargo.toml.template
@@ -0,0 +1,10 @@
+[package]
+name = "new-rust-crates"
+version = "0.1.0"
+edition = "2021"
+publish = false
+license = "Apache-2.0"
+
+[dependencies]
+{{ for crate in deps }}{crate.name} = "{crate.version}"
+{{ endfor }}
diff --git a/tools/external_crates/crate_health/src/templates/crate_health.html.template b/tools/external_crates/crate_health/src/templates/crate_health.html.template
new file mode 100644
index 000000000..759fa4c1e
--- /dev/null
+++ b/tools/external_crates/crate_health/src/templates/crate_health.html.template
@@ -0,0 +1,33 @@
+<html>
+<head>
+<title>Crate health</title>
+<style>
+table, th, td \{
+ border: 1px solid black;
+ border-collapse: collapse;
+ padding: 3px;
+}
+td \{
+ vertical-align: top;
+}
+.vertical \{
+ writing-mode: vertical-lr;
+}
+</style>
+</head>
+<body>
+<h1>Crates in external/rust</h1>
+{crate_count | unescaped}
+<h1>Crates with multiple versions</h1>
+{crate_multiversion | unescaped}
+<h1>Healthy crates in external/rust</h1>
+<ul>
+<li>Has cargo_embargo.json</li>
+<li>cargo_embargo runs successfully</li>
+<li>The resulting Android.bp is unchanged</li>
+</ul>
+{healthy | unescaped }
+<h1>Unhealthy crates in external/rust</h1>
+{unhealthy | unescaped}
+</body>
+</html> \ No newline at end of file
diff --git a/tools/external_crates/crate_health/src/templates/migration_report.html.template b/tools/external_crates/crate_health/src/templates/migration_report.html.template
new file mode 100644
index 000000000..02bae1489
--- /dev/null
+++ b/tools/external_crates/crate_health/src/templates/migration_report.html.template
@@ -0,0 +1,53 @@
+<html>
+<head>
+<title>Crate health</title>
+<style>
+table, th, td \{
+ border: 1px solid black;
+ border-collapse: collapse;
+ padding: 3px;
+}
+td \{
+ vertical-align: top;
+}
+.vertical \{
+ writing-mode: vertical-lr;
+}
+</style>
+</head>
+<body>
+
+<h1>Migratable crates</h1>
+<p>Crates that can be safely migrated:
+<ul>
+<li>migration-eligible as defined below</li>
+<li>Has a vendored crate with a compatible version</li>
+<li>Patches apply successfully</li>
+<li>cargo_embargo succeeds on the vendored version.</li>
+<li>No significant diffs in the resulting Android.bp</li>
+</ul>
+</p>
+{migratable | unescaped}
+
+<h1>Migration-eligible crates</h1>
+<p>Crates that are eligible for migration, but can't yet be migrated:
+<ul>
+<li>It is in crates.io</li>
+<li>It is not denylisted</li>
+<li>It has an Android.bp</li>
+<li>It has a cargo_embargo.json</li>
+</ul>
+</p>
+{eligible | unescaped}
+
+<h1>Ineligible crates</h1>
+<p>Crates that are not eligible for migration</p>
+</p>
+{ineligible | unescaped}
+
+<h1>Superfluous vendored crates</h1>
+<p>Vendored crates that we don't know anything about</p>
+{superfluous | unescaped}
+
+</body>
+</html> \ No newline at end of file
diff --git a/tools/external_crates/crate_health/src/templates/size_report.html.template b/tools/external_crates/crate_health/src/templates/size_report.html.template
new file mode 100644
index 000000000..23f3aec8e
--- /dev/null
+++ b/tools/external_crates/crate_health/src/templates/size_report.html.template
@@ -0,0 +1,6 @@
+<ul>
+ <li>{num_crates} crates</li>
+ <li>{crates_with_single_version} have a single version</li>
+ <li>{crates_with_multiple_versions} have multiple versions</li>
+ <li>{num_dirs} total crate directories</li>
+</ul>
diff --git a/tools/external_crates/crate_health/src/templates/table.html.template b/tools/external_crates/crate_health/src/templates/table.html.template
new file mode 100644
index 000000000..b36da4761
--- /dev/null
+++ b/tools/external_crates/crate_health/src/templates/table.html.template
@@ -0,0 +1,10 @@
+<p>{rows | len} crate directories</p>
+<table>
+<tr>{{ for cell in header}}
+ <th{{ if vertical }} class="vertical"{{ endif }}>{cell}</th>{{ endfor }}
+</tr>
+{{ for row in rows }}
+<tr>{{ for cell in row }}
+ <td>{cell | unescaped}</td>{{ endfor }}
+</tr>{{ endfor }}
+</table> \ No newline at end of file
diff --git a/tools/external_crates/crate_health/src/version_match.rs b/tools/external_crates/crate_health/src/version_match.rs
new file mode 100644
index 000000000..6670302d0
--- /dev/null
+++ b/tools/external_crates/crate_health/src/version_match.rs
@@ -0,0 +1,417 @@
+// 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.
+
+use std::collections::BTreeMap;
+
+use anyhow::{anyhow, Result};
+
+use crate::{
+ generate_android_bps, CrateCollection, Migratable, NameAndVersion, NameAndVersionMap,
+ NamedAndVersioned,
+};
+
+#[derive(Debug)]
+pub struct VersionPair<'a, T> {
+ pub source: &'a T,
+ pub dest: Option<&'a T>,
+}
+
+#[derive(Debug)]
+pub struct CompatibleVersionPair<'a, T> {
+ pub source: &'a T,
+ pub dest: &'a T,
+}
+
+impl<'a, T> VersionPair<'a, T> {
+ pub fn to_compatible(self) -> Option<CompatibleVersionPair<'a, T>> {
+ self.dest.map(|dest| CompatibleVersionPair { source: self.source, dest })
+ }
+}
+
+pub struct VersionMatch<CollectionType: NameAndVersionMap> {
+ source: CollectionType,
+ dest: CollectionType,
+ compatibility: BTreeMap<NameAndVersion, Option<NameAndVersion>>,
+}
+
+impl<CollectionType: NameAndVersionMap> VersionMatch<CollectionType> {
+ pub fn new(source: CollectionType, dest: CollectionType) -> Result<Self> {
+ let mut vm = VersionMatch { source, dest, compatibility: BTreeMap::new() };
+
+ for nv in vm.dest.map_field().keys() {
+ vm.compatibility.insert_or_error(nv.to_owned(), None)?;
+ }
+
+ for nv in vm.source.map_field().keys() {
+ let compatibility = if let Some(dest_nv) = vm.dest.get_version_upgradable_from(nv) {
+ vm.compatibility.map_field_mut().remove(dest_nv).ok_or(anyhow!(
+ "Destination crate version {} {} expected but not found",
+ dest_nv.name(),
+ dest_nv.version()
+ ))?;
+ Some(dest_nv.clone())
+ } else {
+ None
+ };
+ vm.compatibility.insert_or_error(nv.to_owned(), compatibility)?;
+ }
+
+ Ok(vm)
+ }
+ pub fn is_superfluous(&self, dest: &dyn NamedAndVersioned) -> bool {
+ self.dest.map_field().contains_key(dest)
+ && self.compatibility.get(dest).is_some_and(|compatibility| compatibility.is_none())
+ }
+ pub fn get_compatible_version(
+ &self,
+ source: &dyn NamedAndVersioned,
+ ) -> Option<&NameAndVersion> {
+ self.compatibility.get(source).and_then(|compatibility| compatibility.as_ref())
+ }
+ pub fn get_compatible_item(
+ &self,
+ source: &dyn NamedAndVersioned,
+ ) -> Option<&CollectionType::Value> {
+ self.get_compatible_version(source).map(|nv| self.dest.map_field().get(nv).unwrap())
+ }
+ pub fn get_compatible_item_mut(
+ &mut self,
+ source: &dyn NamedAndVersioned,
+ ) -> Option<&mut CollectionType::Value> {
+ let nv = self.get_compatible_version(source)?.clone();
+ self.dest.map_field_mut().get_mut(&nv)
+ }
+
+ pub fn superfluous(&self) -> impl Iterator<Item = (&NameAndVersion, &CollectionType::Value)> {
+ self.dest.map_field().iter().filter(|(nv, _val)| {
+ self.compatibility.get(*nv).is_some_and(|compatibility| compatibility.is_none())
+ })
+ }
+ pub fn pairs<'a>(&'a self) -> impl Iterator<Item = VersionPair<'a, CollectionType::Value>> {
+ self.source
+ .map_field()
+ .iter()
+ .map(|(nv, source)| VersionPair { source, dest: self.get_compatible_item(nv) })
+ }
+ pub fn compatible_pairs<'a>(
+ &'a self,
+ ) -> impl Iterator<Item = CompatibleVersionPair<'a, CollectionType::Value>> {
+ self.pairs().into_iter().filter_map(
+ |pair: VersionPair<'_, <CollectionType as NameAndVersionMap>::Value>| {
+ pair.to_compatible()
+ },
+ )
+ }
+ pub fn print(&self) {
+ for (nv, compatibility) in self.compatibility.iter() {
+ match compatibility {
+ Some(dest) => {
+ println!("{} old {} -> new {}", nv.name(), nv.version(), dest.version())
+ }
+ None => {
+ if self.dest.contains_name(nv.name()) {
+ println!("{} {} -> NO MATCHING VERSION", nv.name(), nv.version())
+ } else {
+ println!("{} {} -> NOT FOUND IN NEW", nv.name(), nv.version())
+ }
+ }
+ }
+ }
+ for (nv, _) in self.superfluous() {
+ println!("{} {} -> NOT FOUND IN OLD", nv.name(), nv.version());
+ }
+ }
+}
+
+impl<CollectionType: NameAndVersionMap> VersionMatch<CollectionType>
+where
+ CollectionType::Value: Migratable,
+{
+ pub fn ineligible(&self) -> impl Iterator<Item = &CollectionType::Value> {
+ self.source.map_field().values().filter(|val| !val.is_migration_eligible())
+ }
+ pub fn eligible_but_not_migratable<'a>(
+ &'a self,
+ ) -> impl Iterator<Item = VersionPair<'a, CollectionType::Value>> {
+ self.pairs().filter(|pair| {
+ pair.source.is_migration_eligible()
+ && !pair.dest.is_some_and(|dest| dest.is_migratable())
+ })
+ }
+ pub fn compatible_and_eligible<'a>(
+ &'a self,
+ ) -> impl Iterator<Item = CompatibleVersionPair<'a, CollectionType::Value>> {
+ self.compatible_pairs().filter(|crate_pair| crate_pair.source.is_migration_eligible())
+ }
+ pub fn migratable<'a>(
+ &'a self,
+ ) -> impl Iterator<Item = CompatibleVersionPair<'a, CollectionType::Value>> {
+ self.compatible_pairs()
+ .filter(|pair| pair.source.is_migration_eligible() && pair.dest.is_migratable())
+ }
+}
+
+impl VersionMatch<CrateCollection> {
+ pub fn copy_customizations(&self) -> Result<()> {
+ println!("Copy customizations");
+ for pair in self.compatible_and_eligible() {
+ pair.copy_customizations()?;
+ }
+ Ok(())
+ }
+ pub fn stage_crates(&mut self) -> Result<()> {
+ println!("Stage crates");
+ for pair in self.compatible_and_eligible() {
+ pair.dest.stage_crate()?;
+ }
+ Ok(())
+ }
+ pub fn apply_patches(&mut self) -> Result<()> {
+ println!("Apply patches");
+
+ let (s, d, c) = (&self.source, &mut self.dest, &self.compatibility);
+ for (source_key, source_crate) in s.map_field() {
+ if source_crate.is_migration_eligible() {
+ if let Some(dest_crate) = c.get(source_key).and_then(|compatibility| {
+ compatibility.as_ref().and_then(|dest_key| d.map_field_mut().get_mut(dest_key))
+ }) {
+ dest_crate.apply_patches()?
+ }
+ }
+ }
+ Ok(())
+ }
+ pub fn generate_android_bps(&mut self) -> Result<()> {
+ println!("Generate Android.bp");
+ let results = generate_android_bps(self.compatible_and_eligible().map(|pair| pair.dest))?;
+ for (nv, output) in results.into_iter() {
+ self.dest
+ .map_field_mut()
+ .get_mut(&nv)
+ .ok_or(anyhow!("Failed to get crate {} {}", nv.name(), nv.version()))?
+ .set_generate_android_bp_output(output.0, output.1);
+ }
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::try_name_version_map_from_iter;
+
+ use super::*;
+ use anyhow::Result;
+ use itertools::assert_equal;
+ use std::collections::BTreeMap;
+
+ #[test]
+ fn test_version_map() -> Result<()> {
+ let source = try_name_version_map_from_iter([
+ ("equal", "2.3.4", "equal src".to_string()),
+ ("compatible", "1.2.3", "compatible src".to_string()),
+ ("incompatible", "1.1.1", "incompatible src".to_string()),
+ ("downgrade", "2.2.2", "downgrade src".to_string()),
+ ("missing", "1.0.0", "missing src".to_string()),
+ ])?;
+ let dest = try_name_version_map_from_iter([
+ ("equal", "2.3.4", "equal dest".to_string()),
+ ("compatible", "1.2.4", "compatible dest".to_string()),
+ ("incompatible", "2.0.0", "incompatible dest".to_string()),
+ ("downgrade", "2.2.1", "downgrade dest".to_string()),
+ ("superfluous", "1.0.0", "superfluous dest".to_string()),
+ ])?;
+
+ let equal = NameAndVersion::try_from_str("equal", "2.3.4")?;
+ let compatible_old = NameAndVersion::try_from_str("compatible", "1.2.3")?;
+ let incompatible_old = NameAndVersion::try_from_str("incompatible", "1.1.1")?;
+ let downgrade_old = NameAndVersion::try_from_str("downgrade", "2.2.2")?;
+ let missing = NameAndVersion::try_from_str("missing", "1.0.0")?;
+
+ let compatible_new = NameAndVersion::try_from_str("compatible", "1.2.4")?;
+ let incompatible_new = NameAndVersion::try_from_str("incompatible", "2.0.0")?;
+ let downgrade_new = NameAndVersion::try_from_str("downgrade", "2.2.1")?;
+ let superfluous = NameAndVersion::try_from_str("superfluous", "1.0.0")?;
+
+ let mut version_match = VersionMatch::new(source, dest)?;
+ assert_eq!(
+ version_match.compatibility,
+ BTreeMap::from([
+ (downgrade_new.clone(), None),
+ (downgrade_old.clone(), None),
+ (equal.clone(), Some(equal.clone())),
+ (compatible_old.clone(), Some(compatible_new.clone())),
+ (incompatible_old.clone(), None),
+ (incompatible_new.clone(), None),
+ (missing.clone(), None),
+ (superfluous.clone(), None),
+ ])
+ );
+
+ // assert!(version_match.has_compatible(&equal));
+ assert_eq!(version_match.get_compatible_version(&equal), Some(&equal));
+ assert_eq!(version_match.get_compatible_item(&equal), Some(&"equal dest".to_string()));
+ assert_eq!(
+ version_match.get_compatible_item_mut(&equal),
+ Some(&mut "equal dest".to_string())
+ );
+ assert!(!version_match.is_superfluous(&equal));
+
+ // assert!(version_match.has_compatible(&compatible_old));
+ assert_eq!(version_match.get_compatible_version(&compatible_old), Some(&compatible_new));
+ assert_eq!(
+ version_match.get_compatible_item(&compatible_old),
+ Some(&"compatible dest".to_string())
+ );
+ assert_eq!(
+ version_match.get_compatible_item_mut(&compatible_old),
+ Some(&mut "compatible dest".to_string())
+ );
+ assert!(!version_match.is_superfluous(&compatible_old));
+ assert!(!version_match.is_superfluous(&compatible_new));
+
+ // assert!(!version_match.has_compatible(&incompatible_old));
+ assert!(version_match.get_compatible_version(&incompatible_old).is_none());
+ assert!(version_match.get_compatible_item(&incompatible_old).is_none());
+ assert!(version_match.get_compatible_item_mut(&incompatible_old).is_none());
+ assert!(!version_match.is_superfluous(&incompatible_old));
+ assert!(version_match.is_superfluous(&incompatible_new));
+
+ // assert!(!version_match.has_compatible(&downgrade_old));
+ assert!(version_match.get_compatible_version(&downgrade_old).is_none());
+ assert!(version_match.get_compatible_item(&downgrade_old).is_none());
+ assert!(version_match.get_compatible_item_mut(&downgrade_old).is_none());
+ assert!(!version_match.is_superfluous(&downgrade_old));
+ assert!(version_match.is_superfluous(&downgrade_new));
+
+ // assert!(!version_match.has_compatible(&missing));
+ assert!(version_match.get_compatible_version(&missing).is_none());
+ assert!(version_match.get_compatible_item(&missing).is_none());
+ assert!(version_match.get_compatible_item_mut(&missing).is_none());
+ assert!(!version_match.is_superfluous(&missing));
+
+ // assert!(!version_match.has_compatible(&superfluous));
+ assert!(version_match.get_compatible_version(&superfluous).is_none());
+ assert!(version_match.get_compatible_item(&superfluous).is_none());
+ assert!(version_match.get_compatible_item_mut(&superfluous).is_none());
+ assert!(version_match.is_superfluous(&superfluous));
+
+ assert_equal(
+ version_match.superfluous().map(|(nv, _dest)| nv),
+ [&downgrade_new, &incompatible_new, &superfluous],
+ );
+
+ assert_equal(
+ version_match.pairs().map(|x| x.source),
+ ["compatible src", "downgrade src", "equal src", "incompatible src", "missing src"],
+ );
+ assert_equal(
+ version_match.pairs().map(|x| x.dest),
+ [
+ Some(&"compatible dest".to_string()),
+ None,
+ Some(&"equal dest".to_string()),
+ None,
+ None,
+ ],
+ );
+
+ assert_equal(
+ version_match.compatible_pairs().map(|x| x.source),
+ ["compatible src", "equal src"],
+ );
+ assert_equal(
+ version_match.compatible_pairs().map(|x| x.dest),
+ ["compatible dest", "equal dest"],
+ );
+
+ Ok(())
+ }
+
+ #[derive(Debug, PartialEq, Eq)]
+ struct FakeMigratable {
+ name: String,
+ source: bool,
+ eligible: bool,
+ migratable: bool,
+ }
+
+ impl FakeMigratable {
+ pub fn source(name: &str, eligible: bool) -> FakeMigratable {
+ FakeMigratable { name: name.to_string(), source: true, eligible, migratable: false }
+ }
+ pub fn dest(migratable: bool) -> FakeMigratable {
+ FakeMigratable { name: "".to_string(), source: false, eligible: false, migratable }
+ }
+ }
+
+ impl Migratable for FakeMigratable {
+ fn is_migration_eligible(&self) -> bool {
+ if !self.source {
+ unreachable!("Checking if dest is migration-eligible");
+ }
+ self.eligible
+ }
+
+ fn is_migratable(&self) -> bool {
+ if self.source {
+ unreachable!("Checking if source is migratable");
+ }
+ self.migratable
+ }
+ }
+
+ #[test]
+ fn test_migratability() -> Result<()> {
+ let source = try_name_version_map_from_iter([
+ ("ineligible", "1.2.3", FakeMigratable::source("ineligible", false)),
+ (
+ "eligible incompatible",
+ "1.2.3",
+ FakeMigratable::source("eligible incompatible", true),
+ ),
+ ("eligible compatible", "1.2.3", FakeMigratable::source("eligible compatible", true)),
+ ("migratable", "1.2.3", FakeMigratable::source("migratable", true)),
+ (
+ "migratable incompatible",
+ "1.2.3",
+ FakeMigratable::source("migratable incompatible", true),
+ ),
+ ])?;
+ let dest = try_name_version_map_from_iter([
+ ("ineligible", "1.2.3", FakeMigratable::dest(true)),
+ ("eligible incompatible", "2.0.0", FakeMigratable::dest(true)),
+ ("eligible compatible", "1.2.3", FakeMigratable::dest(false)),
+ ("migratable", "1.2.3", FakeMigratable::dest(true)),
+ ("migratable incompatible", "2.0.0", FakeMigratable::dest(true)),
+ ])?;
+
+ let version_match = VersionMatch::new(source, dest)?;
+
+ assert_equal(version_match.ineligible().map(|m| m.name.as_str()), ["ineligible"]);
+ assert_equal(
+ version_match.eligible_but_not_migratable().map(|pair| pair.source.name.as_str()),
+ ["eligible compatible", "eligible incompatible", "migratable incompatible"],
+ );
+ assert_equal(
+ version_match.compatible_and_eligible().map(|pair| pair.source.name.as_str()),
+ ["eligible compatible", "migratable"],
+ );
+ assert_equal(
+ version_match.migratable().map(|pair| pair.source.name.as_str()),
+ ["migratable"],
+ );
+
+ Ok(())
+ }
+}
diff --git a/tools/external_crates/crate_health_proc_macros/Cargo.toml b/tools/external_crates/crate_health_proc_macros/Cargo.toml
new file mode 100644
index 000000000..a62e929d6
--- /dev/null
+++ b/tools/external_crates/crate_health_proc_macros/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "crate_health_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/crate_health_proc_macros/src/lib.rs b/tools/external_crates/crate_health_proc_macros/src/lib.rs
new file mode 100644
index 000000000..a53dd5d65
--- /dev/null
+++ b/tools/external_crates/crate_health_proc_macros/src/lib.rs
@@ -0,0 +1,124 @@
+// 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.
+
+use syn::{parse_macro_input, DeriveInput, Error};
+
+#[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<(), CrateError> {
+ 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(TokenStream::from(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((key_type, _value_type)) = get_map_type(&field.ty) {
+ if let syn::Type::Path(path) = &key_type {
+ if path.path.segments.len() == 1
+ && path.path.segments[0].ident == "NameAndVersion"
+ {
+ return Ok(field);
+ }
+ }
+ }
+ }
+ return 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"))
+ }
+}
diff --git a/tools/ndk/OWNERS b/tools/ndk/OWNERS
index 7310b199a..14a30711c 100644
--- a/tools/ndk/OWNERS
+++ b/tools/ndk/OWNERS
@@ -1,4 +1,3 @@
danalbert@google.com
enh@google.com
-hhb@google.com
rprichard@google.com
diff --git a/tools/repo_pull/gerrit.py b/tools/repo_pull/gerrit.py
index c93461d25..601c3ff5c 100755
--- a/tools/repo_pull/gerrit.py
+++ b/tools/repo_pull/gerrit.py
@@ -608,7 +608,7 @@ def _parse_args():
parser = argparse.ArgumentParser()
add_common_parse_args(parser)
parser.add_argument('--format', default='json',
- choices=['json', 'oneline'],
+ choices=['json', 'oneline', 'porcelain'],
help='Print format')
return parser.parse_args()
@@ -635,16 +635,22 @@ def main():
if args.format == 'json':
json.dump(change_lists, sys.stdout, indent=4, separators=(', ', ': '))
print() # Print the end-of-line
- elif args.format == 'oneline':
+ else:
+ if args.format == 'oneline':
+ format_str = ('{i:<8} {number:<16} {status:<20} '
+ '{change_id:<60} {project:<120} '
+ '{subject}')
+ else:
+ format_str = ('{i}\t{number}\t{status}\t'
+ '{change_id}\t{project}\t{subject}')
+
for i, change in enumerate(change_lists):
- print('{i:<8} {number:<16} {status:<20} ' \
- '{change_id:<60} {project:<120} ' \
- '{subject}'.format(i=i,
- project=change['project'],
- change_id=change['change_id'],
- status=change['status'],
- number=change['_number'],
- subject=change['subject']))
+ print(format_str.format(i=i,
+ project=change['project'],
+ change_id=change['change_id'],
+ status=change['status'],
+ number=change['_number'],
+ subject=change['subject']))
if __name__ == '__main__':
diff --git a/tools/winscope/OWNERS b/tools/winscope/OWNERS
index 9cb826f69..820c48a4c 100644
--- a/tools/winscope/OWNERS
+++ b/tools/winscope/OWNERS
@@ -3,3 +3,4 @@ pablogamito@google.com
keanmariotti@google.com
jjaggi@google.com
roosa@google.com
+priyankaspatel@google.com
diff --git a/treble/compare_bp_system_image.sh b/treble/compare_bp_system_image.sh
new file mode 100755
index 000000000..7e8a736bd
--- /dev/null
+++ b/treble/compare_bp_system_image.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+echo "Note: Should run 'lunch aosp_cf_x86_64_only_phone-trunk_staging-userdebug && m aosp_cf_system_x86_64 && m' before running this script"
+bp_base_path=$ANDROID_BUILD_TOP/out/soong/.intermediates/device/google/cuttlefish/system_image/aosp_cf_system_x86_64/android_common
+latest_hash=$(ls $bp_base_path -t | head -1)
+bp_path=$bp_base_path/$latest_hash/root
+echo $OUT
+echo $bp_path
+compare_images -t $OUT $bp_path -s system -i
diff --git a/vndk/tools/header-checker/README.md b/vndk/tools/header-checker/README.md
index 846614411..9ac45950b 100644
--- a/vndk/tools/header-checker/README.md
+++ b/vndk/tools/header-checker/README.md
@@ -229,8 +229,8 @@ $ python3 utils/create_reference_dumps.py -l libfoo \
--ref-dump-dir /path/to/abi-dumps
```
-You may specify `-products` if you don't want to create the ABI dumps for
-all architectures. For example, with `-products aosp_arm`, the command creates
+You may specify `--product` if you don't want to create the ABI dumps for
+all architectures. For example, with `--product aosp_arm`, the command creates
dumps for 32-bit arm only.
#### Configure Cross-Version ABI Check
diff --git a/vndk/tools/header-checker/utils/create_reference_dumps.py b/vndk/tools/header-checker/utils/create_reference_dumps.py
index 9efb7cee0..98110ddcb 100755
--- a/vndk/tools/header-checker/utils/create_reference_dumps.py
+++ b/vndk/tools/header-checker/utils/create_reference_dumps.py
@@ -177,14 +177,15 @@ def _parse_args():
"""Parse the command line arguments."""
parser = argparse.ArgumentParser()
- parser.add_argument('--version', help=argparse.SUPPRESS)
parser.add_argument('--no-make-lib', action='store_true',
help='skip building dumps while creating references')
- parser.add_argument('-libs', action='append',
+ parser.add_argument('--lib', '-libs', action='append',
+ dest='libs', metavar='LIB',
help='libs to create references for')
- parser.add_argument('-products', action='append',
+ parser.add_argument('--product', '-products', action='append',
+ dest='products', metavar='PRODUCT',
help='products to create references for')
- parser.add_argument('-release',
+ parser.add_argument('--release', '-release',
help='release configuration to create references for. '
'e.g., trunk_staging, next.')
parser.add_argument('--build-variant', default='userdebug',
@@ -192,37 +193,26 @@ def _parse_args():
parser.add_argument('--lib-variant', action='append', dest='include_tags',
default=[], choices=KNOWN_TAGS,
help='library variant to create references for.')
- parser.add_argument('--compress', action='store_true',
- help=argparse.SUPPRESS)
- parser.add_argument('-ref-dump-dir',
+ parser.add_argument('--ref-dump-dir', '-ref-dump-dir',
help='directory to copy reference abi dumps into')
-
args = parser.parse_args()
- if args.version is not None:
- parser.error('--version is deprecated. Please specify the version in '
- 'the reference dump directory path. e.g., '
- '-ref-dump-dir prebuilts/abi-dumps/platform/current/64')
-
- if args.compress:
- parser.error("Compressed reference dumps are deprecated.")
-
if args.libs:
if any(lib_name.endswith(SOURCE_ABI_DUMP_EXT_END) or
lib_name.endswith(SO_EXT) for lib_name in args.libs):
- parser.error('-libs should be followed by a base name without '
+ parser.error('--lib should be followed by a base name without '
'file extension.')
if NON_AOSP_TAGS.intersection(args.include_tags) and not args.libs:
- parser.error('-libs must be given if --lib-variant is any of ' +
+ parser.error('--lib must be given if --lib-variant is any of ' +
str(NON_AOSP_TAGS))
if args.ref_dump_dir and not args.libs:
- parser.error('-libs must be given if -ref-dump-dir is given.')
+ parser.error('--lib must be given if --ref-dump-dir is given.')
if args.ref_dump_dir and len(args.include_tags) != 1:
print('WARNING: Exactly one --lib-variant should be specified if '
- '-ref-dump-dir is given.')
+ '--ref-dump-dir is given.')
if args.products is None:
# If `args.products` is unspecified, generate reference ABI dumps for