summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PREUPLOAD.cfg5
-rw-r--r--build/tools/sdk-preprocess-files.mk (renamed from build/Android.mk)0
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/Monkey.java14
-rw-r--r--gki/Android.mk21
-rw-r--r--gki/kmi_abi_chk/Android.bp54
-rw-r--r--gki/kmi_abi_chk/kmi_compatibility_test.mk68
l---------rustfmt.toml1
-rw-r--r--samples/training/NsdChat/Android.bp13
-rw-r--r--samples/training/NsdChat/AndroidManifest.xml40
-rw-r--r--samples/training/NsdChat/proguard.cfg26
-rw-r--r--samples/training/NsdChat/project.properties14
-rw-r--r--samples/training/NsdChat/res/drawable-hdpi/ic_launcher.pngbin9397 -> 0 bytes
-rw-r--r--samples/training/NsdChat/res/drawable-ldpi/ic_launcher.pngbin2729 -> 0 bytes
-rw-r--r--samples/training/NsdChat/res/drawable-mdpi/ic_launcher.pngbin5237 -> 0 bytes
-rw-r--r--samples/training/NsdChat/res/drawable-xhdpi/ic_launcher.pngbin14383 -> 0 bytes
-rw-r--r--samples/training/NsdChat/res/layout/main.xml70
-rw-r--r--samples/training/NsdChat/res/values/strings.xml26
-rw-r--r--samples/training/NsdChat/src/com/example/android/nsdchat/ChatConnection.java288
-rw-r--r--samples/training/NsdChat/src/com/example/android/nsdchat/NsdChatActivity.java154
-rw-r--r--samples/training/NsdChat/src/com/example/android/nsdchat/NsdHelper.java188
-rwxr-xr-xscripts/stack_core.py8
-rwxr-xr-xscripts/symbol.py23
-rw-r--r--tools/cargo_embargo/Android.bp1
-rw-r--r--tools/cargo_embargo/src/cargo/metadata.rs85
-rw-r--r--tools/cargo_embargo/src/main.rs94
-rw-r--r--tools/external_crates/.gitignore1
-rw-r--r--tools/external_crates/Cargo.lock3035
-rw-r--r--tools/external_crates/Cargo.toml6
-rw-r--r--tools/external_crates/crate_health/Cargo.toml26
-rw-r--r--tools/external_crates/crate_health/src/android_bp.rs119
-rw-r--r--tools/external_crates/crate_health/src/bin/health_report.rs53
-rw-r--r--tools/external_crates/crate_health/src/bin/migration_report.rs47
-rw-r--r--tools/external_crates/crate_health/src/crate_collection.rs99
-rw-r--r--tools/external_crates/crate_health/src/crate_type.rs370
-rw-r--r--tools/external_crates/crate_health/src/lib.rs110
-rw-r--r--tools/external_crates/crate_health/src/main.rs281
-rw-r--r--tools/external_crates/crate_health/src/migration.rs128
-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.rs125
-rw-r--r--tools/external_crates/crate_health/src/reports.rs350
-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.rs427
-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
-rwxr-xr-xtreble/compare_bp_system_image.sh8
-rw-r--r--vndk/tools/header-checker/src/repr/protobuf/converter.cpp40
-rw-r--r--vndk/tools/header-checker/src/repr/protobuf/converter.h89
-rw-r--r--vndk/tools/header-checker/src/repr/protobuf/ir_dumper.cpp104
-rw-r--r--vndk/tools/header-checker/src/repr/protobuf/ir_reader.cpp1
54 files changed, 6256 insertions, 1082 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.mk b/build/tools/sdk-preprocess-files.mk
index b05bcd5ad..b05bcd5ad 100644
--- a/build/Android.mk
+++ b/build/tools/sdk-preprocess-files.mk
diff --git a/cmds/monkey/src/com/android/commands/monkey/Monkey.java b/cmds/monkey/src/com/android/commands/monkey/Monkey.java
index 25048276f..f821a0e5a 100644
--- a/cmds/monkey/src/com/android/commands/monkey/Monkey.java
+++ b/cmds/monkey/src/com/android/commands/monkey/Monkey.java
@@ -481,18 +481,14 @@ public class Monkey {
private void commandLineReport(String reportName, String command) {
Logger.err.println(reportName + ":");
Runtime rt = Runtime.getRuntime();
- Writer logOutput = null;
- try {
+ try (Writer logOutput = mRequestBugreport ?
+ new BufferedWriter(new FileWriter(new File(Environment
+ .getLegacyExternalStorageDirectory(), reportName), true)) : null) {
// Process must be fully qualified here because android.os.Process
// is used elsewhere
java.lang.Process p = Runtime.getRuntime().exec(command);
- if (mRequestBugreport) {
- logOutput =
- new BufferedWriter(new FileWriter(new File(Environment
- .getLegacyExternalStorageDirectory(), reportName), true));
- }
// pipe everything from process stdout -> System.err
InputStream inStream = p.getInputStream();
InputStreamReader inReader = new InputStreamReader(inStream);
@@ -519,10 +515,6 @@ public class Monkey {
int status = p.waitFor();
Logger.err.println("// " + reportName + " status was " + status);
-
- if (logOutput != null) {
- logOutput.close();
- }
} catch (Exception e) {
Logger.err.println("// Exception from " + reportName + ":");
Logger.err.println(e.toString());
diff --git a/gki/Android.mk b/gki/Android.mk
deleted file mode 100644
index 98675ffde..000000000
--- a/gki/Android.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-#
-# Copyright 2021 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-gki_path := $(LOCAL_PATH)
-
-include $(gki_path)/kmi_abi_chk/kmi_compatibility_test.mk
diff --git a/gki/kmi_abi_chk/Android.bp b/gki/kmi_abi_chk/Android.bp
new file mode 100644
index 000000000..f1ff6e5bd
--- /dev/null
+++ b/gki/kmi_abi_chk/Android.bp
@@ -0,0 +1,54 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: [
+ "Android-Apache-2.0",
+ ],
+}
+
+genrule {
+ name: "a14_5_15_kmi_compatibility_test",
+ srcs: [
+ "sym-5.15/Module.symvers-10342779",
+ ],
+ tool_files: ["kmi_compatibility_test.sh"],
+ out: ["a14_5_15_kmi_compatibility_test"],
+ // kmi_compatibility_test.sh $(CURR_SYMVERS) $(PREV_SYMVERS)
+ cmd: "$(location kmi_compatibility_test.sh) $(location sym-5.15/Module.symvers-10342779) $(location sym-5.15/Module.symvers-10342779) && " +
+ "touch $(out)",
+}
+
+genrule {
+ name: "a14_6_1_kmi_compatibility_test",
+ srcs: [
+ "sym-6.1/Module.symvers-10816536",
+ ],
+ tool_files: ["kmi_compatibility_test.sh"],
+ out: ["a14_6_1_kmi_compatibility_test"],
+ // kmi_compatibility_test.sh $(CURR_SYMVERS) $(PREV_SYMVERS)
+ cmd: "$(location kmi_compatibility_test.sh) $(location sym-6.1/Module.symvers-10816536) $(location sym-6.1/Module.symvers-10816536) && " +
+ "touch $(out)",
+}
+
+phony_rule {
+ name: "a14_kmi_compatibility_test",
+ phony_deps: [
+ "a14_5_15_kmi_compatibility_test",
+ "a14_6_1_kmi_compatibility_test",
+ ],
+}
diff --git a/gki/kmi_abi_chk/kmi_compatibility_test.mk b/gki/kmi_abi_chk/kmi_compatibility_test.mk
deleted file mode 100644
index 3abc91017..000000000
--- a/gki/kmi_abi_chk/kmi_compatibility_test.mk
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright (C) 2021 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.
-LOCAL_PATH := $(call my-dir)
-
-KMI_CHK_SCRIPT := $(LOCAL_PATH)/kmi_compatibility_test.sh
-
-# Current kernel symbol files to be checked
-# Use the one under $(LOCAL_PATH)/sym-[56].* by default for self testing.
-# The reason not to use the one under kernel/prebuilts/5.* by default
-# is because the KMI ABI may not be stable during development.
-#
-# Set CURR_5_15_SYMVERS/CURR_6_1_SYMVERS explicitly for the actual
-# current kernel symbol file to be checked. E.g.,
-# $ m CURR_6_1_SYMVERS=kernel/prebuilts/6.1/arm64/Module.symvers \
-# gki_6_1_kmi_compatibility_test
-CURR_5_15_SYMVERS ?= development/gki/kmi_abi_chk/sym-5.15/Module.symvers
-CURR_6_1_SYMVERS ?= development/gki/kmi_abi_chk/sym-6.1/Module.symvers
-
-# Previous kernel symbol files, against which the latest one is checked
-# The file names of previous kernel symbol files are of this form:
-# *.symvers-$(BID)
-# Here *.symvers is a symbolic link to the latest build.
-PREV_5_15_SYMVERS := $(LOCAL_PATH)/sym-5.15/Module.symvers
-PREV_6_1_SYMVERS := $(LOCAL_PATH)/sym-6.1/Module.symvers
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := a14_5_15_kmi_compatibility_test
-LOCAL_MODULE_CLASS := FAKE
-LOCAL_MODULE_TAGS := optional
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-include $(BUILD_SYSTEM)/base_rules.mk
-
-$(LOCAL_BUILT_MODULE): $(KMI_CHK_SCRIPT) $(CURR_5_15_SYMVERS) $(PREV_5_15_SYMVERS)
- @mkdir -p $(dir $@)
- $(hide) $(KMI_CHK_SCRIPT) $(CURR_5_15_SYMVERS) $(PREV_5_15_SYMVERS)
- $(hide) touch $@
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := a14_6_1_kmi_compatibility_test
-LOCAL_MODULE_CLASS := FAKE
-LOCAL_MODULE_TAGS := optional
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-include $(BUILD_SYSTEM)/base_rules.mk
-
-$(LOCAL_BUILT_MODULE): $(KMI_CHK_SCRIPT) $(CURR_6_1_SYMVERS) $(PREV_6_1_SYMVERS)
- @mkdir -p $(dir $@)
- $(hide) $(KMI_CHK_SCRIPT) $(CURR_6_1_SYMVERS) $(PREV_6_1_SYMVERS)
- $(hide) touch $@
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := a14_kmi_compatibility_test
-LOCAL_REQUIRED_MODULES := a14_5_15_kmi_compatibility_test a14_6_1_kmi_compatibility_test
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-include $(BUILD_PHONY_PACKAGE)
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 120000
index 000000000..239055a55
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1 @@
+../build/soong/scripts/rustfmt.toml \ No newline at end of file
diff --git a/samples/training/NsdChat/Android.bp b/samples/training/NsdChat/Android.bp
deleted file mode 100644
index dc1f127af..000000000
--- a/samples/training/NsdChat/Android.bp
+++ /dev/null
@@ -1,13 +0,0 @@
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_app {
- name: "NsdChat",
- // Only compile source java files in this apk.
- srcs: ["src/**/*.java"],
- sdk_version: "current",
- optimize: {
- proguard_flags_files: ["proguard.cfg"],
- },
-}
diff --git a/samples/training/NsdChat/AndroidManifest.xml b/samples/training/NsdChat/AndroidManifest.xml
deleted file mode 100644
index bb5e73c7c..000000000
--- a/samples/training/NsdChat/AndroidManifest.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2012 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.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.android.nsdchat"
- android:versionCode="1"
- android:versionName="1.0" >
-
- <uses-sdk android:minSdkVersion="16"
- android:targetSdkVersion="16" />
- <uses-permission android:required="true" android:name="android.permission.INTERNET"/>
-
- <application
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name" >
- <activity
- android:name="com.example.android.nsdchat.NsdChatActivity"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
-</manifest>
diff --git a/samples/training/NsdChat/proguard.cfg b/samples/training/NsdChat/proguard.cfg
deleted file mode 100644
index 52f242d8a..000000000
--- a/samples/training/NsdChat/proguard.cfg
+++ /dev/null
@@ -1,26 +0,0 @@
-# To enable ProGuard in your project, edit project.properties
-# to define the proguard.config property as described in that file.
-#
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in ${sdk.dir}/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the ProGuard
-# include property in project.properties.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Keep onClickListeners.
--keepclassmembers class * extends android.app.Activity {
- public void *(android.view.View);
-}
-
diff --git a/samples/training/NsdChat/project.properties b/samples/training/NsdChat/project.properties
deleted file mode 100644
index 9b84a6b4b..000000000
--- a/samples/training/NsdChat/project.properties
+++ /dev/null
@@ -1,14 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system edit
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-#
-# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
-#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
-
-# Project target.
-target=android-16
diff --git a/samples/training/NsdChat/res/drawable-hdpi/ic_launcher.png b/samples/training/NsdChat/res/drawable-hdpi/ic_launcher.png
deleted file mode 100644
index 96a442e5b..000000000
--- a/samples/training/NsdChat/res/drawable-hdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/samples/training/NsdChat/res/drawable-ldpi/ic_launcher.png b/samples/training/NsdChat/res/drawable-ldpi/ic_launcher.png
deleted file mode 100644
index 99238729d..000000000
--- a/samples/training/NsdChat/res/drawable-ldpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/samples/training/NsdChat/res/drawable-mdpi/ic_launcher.png b/samples/training/NsdChat/res/drawable-mdpi/ic_launcher.png
deleted file mode 100644
index 359047dfa..000000000
--- a/samples/training/NsdChat/res/drawable-mdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/samples/training/NsdChat/res/drawable-xhdpi/ic_launcher.png b/samples/training/NsdChat/res/drawable-xhdpi/ic_launcher.png
deleted file mode 100644
index 71c6d760f..000000000
--- a/samples/training/NsdChat/res/drawable-xhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/samples/training/NsdChat/res/layout/main.xml b/samples/training/NsdChat/res/layout/main.xml
deleted file mode 100644
index e516458f1..000000000
--- a/samples/training/NsdChat/res/layout/main.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2012 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.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
-
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="96dp"
- android:orientation="horizontal" >
-
- <Button
- android:id="@+id/advertise_btn"
- android:layout_width="96dp"
- android:layout_height="64dp"
- android:onClick="clickAdvertise"
- android:text="@string/register" />
-
- <Button
- android:id="@+id/discover_btn"
- android:layout_width="96dp"
- android:layout_height="64dp"
- android:onClick="clickDiscover"
- android:text="@string/discover" />
-
- <Button
- android:id="@+id/connect_btn"
- android:layout_width="96dp"
- android:layout_height="64dp"
- android:onClick="clickConnect"
- android:text="@string/connect" />
- </LinearLayout>
-
- <TextView
- android:id="@+id/status"
- android:layout_width="fill_parent"
- android:layout_height="200dp"
- android:focusable="true" />
-
- <EditText
- android:id="@+id/chatInput"
- android:layout_width="fill_parent"
- android:layout_height="80dp"
- android:inputType="text"
- android:singleLine="true" />
-
- <Button
- android:id="@+id/send_btn"
- android:layout_width="96dp"
- android:layout_height="64dp"
- android:onClick="clickSend"
- android:text="@string/send" />
-
-</LinearLayout> \ No newline at end of file
diff --git a/samples/training/NsdChat/res/values/strings.xml b/samples/training/NsdChat/res/values/strings.xml
deleted file mode 100644
index 1e15e362d..000000000
--- a/samples/training/NsdChat/res/values/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2012 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.
--->
-<resources>
-
- <string name="app_name">NsdChat</string>
-
- <string name="connect">Connect</string>
- <string name="discover">Discover</string>
- <string name="register">Register</string>
- <string name="send">Send</string>
-
-</resources>
diff --git a/samples/training/NsdChat/src/com/example/android/nsdchat/ChatConnection.java b/samples/training/NsdChat/src/com/example/android/nsdchat/ChatConnection.java
deleted file mode 100644
index 534f218bd..000000000
--- a/samples/training/NsdChat/src/com/example/android/nsdchat/ChatConnection.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.example.android.nsdchat;
-
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.net.InetAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-
-public class ChatConnection {
-
- private Handler mUpdateHandler;
- private ChatServer mChatServer;
- private ChatClient mChatClient;
-
- private static final String TAG = "ChatConnection";
-
- private Socket mSocket;
- private int mPort = -1;
-
- public ChatConnection(Handler handler) {
- mUpdateHandler = handler;
- mChatServer = new ChatServer(handler);
- }
-
- public void tearDown() {
- mChatServer.tearDown();
- if (mChatClient != null) {
- mChatClient.tearDown();
- }
- }
-
- public void connectToServer(InetAddress address, int port) {
- mChatClient = new ChatClient(address, port);
- }
-
- public void sendMessage(String msg) {
- if (mChatClient != null) {
- mChatClient.sendMessage(msg);
- }
- }
-
- public int getLocalPort() {
- return mPort;
- }
-
- public void setLocalPort(int port) {
- mPort = port;
- }
-
-
- public synchronized void updateMessages(String msg, boolean local) {
- Log.e(TAG, "Updating message: " + msg);
-
- if (local) {
- msg = "me: " + msg;
- } else {
- msg = "them: " + msg;
- }
-
- Bundle messageBundle = new Bundle();
- messageBundle.putString("msg", msg);
-
- Message message = new Message();
- message.setData(messageBundle);
- mUpdateHandler.sendMessage(message);
-
- }
-
- private synchronized void setSocket(Socket socket) {
- Log.d(TAG, "setSocket being called.");
- if (socket == null) {
- Log.d(TAG, "Setting a null socket.");
- }
- if (mSocket != null) {
- if (mSocket.isConnected()) {
- try {
- mSocket.close();
- } catch (IOException e) {
- // TODO(alexlucas): Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- mSocket = socket;
- }
-
- private Socket getSocket() {
- return mSocket;
- }
-
- private class ChatServer {
- ServerSocket mServerSocket = null;
- Thread mThread = null;
-
- public ChatServer(Handler handler) {
- mThread = new Thread(new ServerThread());
- mThread.start();
- }
-
- public void tearDown() {
- mThread.interrupt();
- try {
- mServerSocket.close();
- } catch (IOException ioe) {
- Log.e(TAG, "Error when closing server socket.");
- }
- }
-
- class ServerThread implements Runnable {
-
- @Override
- public void run() {
-
- try {
- // Since discovery will happen via Nsd, we don't need to care which port is
- // used. Just grab an available one and advertise it via Nsd.
- mServerSocket = new ServerSocket(0);
- setLocalPort(mServerSocket.getLocalPort());
-
- while (!Thread.currentThread().isInterrupted()) {
- Log.d(TAG, "ServerSocket Created, awaiting connection");
- setSocket(mServerSocket.accept());
- Log.d(TAG, "Connected.");
- if (mChatClient == null) {
- int port = mSocket.getPort();
- InetAddress address = mSocket.getInetAddress();
- connectToServer(address, port);
- }
- }
- } catch (IOException e) {
- Log.e(TAG, "Error creating ServerSocket: ", e);
- e.printStackTrace();
- }
- }
- }
- }
-
- private class ChatClient {
-
- private InetAddress mAddress;
- private int PORT;
-
- private final String CLIENT_TAG = "ChatClient";
-
- private Thread mSendThread;
- private Thread mRecThread;
-
- public ChatClient(InetAddress address, int port) {
-
- Log.d(CLIENT_TAG, "Creating chatClient");
- this.mAddress = address;
- this.PORT = port;
-
- mSendThread = new Thread(new SendingThread());
- mSendThread.start();
- }
-
- class SendingThread implements Runnable {
-
- BlockingQueue<String> mMessageQueue;
- private int QUEUE_CAPACITY = 10;
-
- public SendingThread() {
- mMessageQueue = new ArrayBlockingQueue<String>(QUEUE_CAPACITY);
- }
-
- @Override
- public void run() {
- try {
- if (getSocket() == null) {
- setSocket(new Socket(mAddress, PORT));
- Log.d(CLIENT_TAG, "Client-side socket initialized.");
-
- } else {
- Log.d(CLIENT_TAG, "Socket already initialized. skipping!");
- }
-
- mRecThread = new Thread(new ReceivingThread());
- mRecThread.start();
-
- } catch (UnknownHostException e) {
- Log.d(CLIENT_TAG, "Initializing socket failed, UHE", e);
- } catch (IOException e) {
- Log.d(CLIENT_TAG, "Initializing socket failed, IOE.", e);
- }
-
- while (true) {
- try {
- String msg = mMessageQueue.take();
- sendMessage(msg);
- } catch (InterruptedException ie) {
- Log.d(CLIENT_TAG, "Message sending loop interrupted, exiting");
- }
- }
- }
- }
-
- class ReceivingThread implements Runnable {
-
- @Override
- public void run() {
-
- BufferedReader input;
- try {
- input = new BufferedReader(new InputStreamReader(
- mSocket.getInputStream()));
- while (!Thread.currentThread().isInterrupted()) {
-
- String messageStr = null;
- messageStr = input.readLine();
- if (messageStr != null) {
- Log.d(CLIENT_TAG, "Read from the stream: " + messageStr);
- updateMessages(messageStr, false);
- } else {
- Log.d(CLIENT_TAG, "The nulls! The nulls!");
- break;
- }
- }
- input.close();
-
- } catch (IOException e) {
- Log.e(CLIENT_TAG, "Server loop error: ", e);
- }
- }
- }
-
- public void tearDown() {
- try {
- getSocket().close();
- } catch (IOException ioe) {
- Log.e(CLIENT_TAG, "Error when closing server socket.");
- }
- }
-
- public void sendMessage(String msg) {
- try {
- Socket socket = getSocket();
- if (socket == null) {
- Log.d(CLIENT_TAG, "Socket is null, wtf?");
- } else if (socket.getOutputStream() == null) {
- Log.d(CLIENT_TAG, "Socket output stream is null, wtf?");
- }
-
- PrintWriter out = new PrintWriter(
- new BufferedWriter(
- new OutputStreamWriter(getSocket().getOutputStream())), true);
- out.println(msg);
- out.flush();
- updateMessages(msg, true);
- } catch (UnknownHostException e) {
- Log.d(CLIENT_TAG, "Unknown Host", e);
- } catch (IOException e) {
- Log.d(CLIENT_TAG, "I/O Exception", e);
- } catch (Exception e) {
- Log.d(CLIENT_TAG, "Error3", e);
- }
- Log.d(CLIENT_TAG, "Client sent message: " + msg);
- }
- }
-}
diff --git a/samples/training/NsdChat/src/com/example/android/nsdchat/NsdChatActivity.java b/samples/training/NsdChat/src/com/example/android/nsdchat/NsdChatActivity.java
deleted file mode 100644
index 5782634ff..000000000
--- a/samples/training/NsdChat/src/com/example/android/nsdchat/NsdChatActivity.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.example.android.nsdchat;
-
-import android.app.Activity;
-import android.net.nsd.NsdServiceInfo;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import android.view.View;
-import android.widget.EditText;
-import android.widget.TextView;
-
-import com.example.android.nsdchat.NsdHelper;
-
-public class NsdChatActivity extends Activity {
-
- NsdHelper mNsdHelper;
-
- private TextView mStatusView;
- private Handler mUpdateHandler;
-
- public static final String TAG = "NsdChat";
-
- ChatConnection mConnection;
-
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Log.d(TAG, "Creating chat activity");
- setContentView(R.layout.main);
- mStatusView = (TextView) findViewById(R.id.status);
-
- mUpdateHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- String chatLine = msg.getData().getString("msg");
- addChatLine(chatLine);
- }
- };
-
- }
-
- public void clickAdvertise(View v) {
- // Register service
- if(mConnection.getLocalPort() > -1) {
- mNsdHelper.registerService(mConnection.getLocalPort());
- } else {
- Log.d(TAG, "ServerSocket isn't bound.");
- }
- }
-
- public void clickDiscover(View v) {
- mNsdHelper.discoverServices();
- }
-
- public void clickConnect(View v) {
- NsdServiceInfo service = mNsdHelper.getChosenServiceInfo();
- if (service != null) {
- Log.d(TAG, "Connecting.");
- mConnection.connectToServer(service.getHost(),
- service.getPort());
- } else {
- Log.d(TAG, "No service to connect to!");
- }
- }
-
- public void clickSend(View v) {
- EditText messageView = (EditText) this.findViewById(R.id.chatInput);
- if (messageView != null) {
- String messageString = messageView.getText().toString();
- if (!messageString.isEmpty()) {
- mConnection.sendMessage(messageString);
- }
- messageView.setText("");
- }
- }
-
- public void addChatLine(String line) {
- mStatusView.append("\n" + line);
- }
-
- @Override
- protected void onStart() {
- Log.d(TAG, "Starting.");
- mConnection = new ChatConnection(mUpdateHandler);
-
- mNsdHelper = new NsdHelper(this);
- mNsdHelper.initializeNsd();
- super.onStart();
- }
-
-
- @Override
- protected void onPause() {
- Log.d(TAG, "Pausing.");
- if (mNsdHelper != null) {
- mNsdHelper.stopDiscovery();
- }
- super.onPause();
- }
-
- @Override
- protected void onResume() {
- Log.d(TAG, "Resuming.");
- super.onResume();
- if (mNsdHelper != null) {
- mNsdHelper.discoverServices();
- }
- }
-
-
- // For KitKat and earlier releases, it is necessary to remove the
- // service registration when the application is stopped. There's
- // no guarantee that the onDestroy() method will be called (we're
- // killable after onStop() returns) and the NSD service won't remove
- // the registration for us if we're killed.
-
- // In L and later, NsdService will automatically unregister us when
- // our connection goes away when we're killed, so this step is
- // optional (but recommended).
-
- @Override
- protected void onStop() {
- Log.d(TAG, "Being stopped.");
- mNsdHelper.tearDown();
- mConnection.tearDown();
- mNsdHelper = null;
- mConnection = null;
- super.onStop();
- }
-
- @Override
- protected void onDestroy() {
- Log.d(TAG, "Being destroyed.");
- super.onDestroy();
- }
-}
diff --git a/samples/training/NsdChat/src/com/example/android/nsdchat/NsdHelper.java b/samples/training/NsdChat/src/com/example/android/nsdchat/NsdHelper.java
deleted file mode 100644
index 5111318cf..000000000
--- a/samples/training/NsdChat/src/com/example/android/nsdchat/NsdHelper.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.example.android.nsdchat;
-
-import android.content.Context;
-import android.net.nsd.NsdServiceInfo;
-import android.net.nsd.NsdManager;
-import android.util.Log;
-
-public class NsdHelper {
-
- Context mContext;
-
- NsdManager mNsdManager;
- NsdManager.ResolveListener mResolveListener;
- NsdManager.DiscoveryListener mDiscoveryListener;
- NsdManager.RegistrationListener mRegistrationListener;
-
- public static final String SERVICE_TYPE = "_http._tcp.";
-
- public static final String TAG = "NsdHelper";
- public String mServiceName = "NsdChat";
-
- NsdServiceInfo mService;
-
- public NsdHelper(Context context) {
- mContext = context;
- mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
- }
-
- public void initializeNsd() {
- initializeResolveListener();
-
- //mNsdManager.init(mContext.getMainLooper(), this);
-
- }
-
- public void initializeDiscoveryListener() {
- mDiscoveryListener = new NsdManager.DiscoveryListener() {
-
- @Override
- public void onDiscoveryStarted(String regType) {
- Log.d(TAG, "Service discovery started");
- }
-
- @Override
- public void onServiceFound(NsdServiceInfo service) {
- Log.d(TAG, "Service discovery success" + service);
- if (!service.getServiceType().equals(SERVICE_TYPE)) {
- Log.d(TAG, "Unknown Service Type: " + service.getServiceType());
- } else if (service.getServiceName().equals(mServiceName)) {
- Log.d(TAG, "Same machine: " + mServiceName);
- } else if (service.getServiceName().contains(mServiceName)){
- mNsdManager.resolveService(service, mResolveListener);
- }
- }
-
- @Override
- public void onServiceLost(NsdServiceInfo service) {
- Log.e(TAG, "service lost" + service);
- if (mService == service) {
- mService = null;
- }
- }
-
- @Override
- public void onDiscoveryStopped(String serviceType) {
- Log.i(TAG, "Discovery stopped: " + serviceType);
- }
-
- @Override
- public void onStartDiscoveryFailed(String serviceType, int errorCode) {
- Log.e(TAG, "Discovery failed: Error code:" + errorCode);
- }
-
- @Override
- public void onStopDiscoveryFailed(String serviceType, int errorCode) {
- Log.e(TAG, "Discovery failed: Error code:" + errorCode);
- }
- };
- }
-
- public void initializeResolveListener() {
- mResolveListener = new NsdManager.ResolveListener() {
-
- @Override
- public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
- Log.e(TAG, "Resolve failed" + errorCode);
- }
-
- @Override
- public void onServiceResolved(NsdServiceInfo serviceInfo) {
- Log.e(TAG, "Resolve Succeeded. " + serviceInfo);
-
- if (serviceInfo.getServiceName().equals(mServiceName)) {
- Log.d(TAG, "Same IP.");
- return;
- }
- mService = serviceInfo;
- }
- };
- }
-
- public void initializeRegistrationListener() {
- mRegistrationListener = new NsdManager.RegistrationListener() {
-
- @Override
- public void onServiceRegistered(NsdServiceInfo NsdServiceInfo) {
- mServiceName = NsdServiceInfo.getServiceName();
- Log.d(TAG, "Service registered: " + mServiceName);
- }
-
- @Override
- public void onRegistrationFailed(NsdServiceInfo arg0, int arg1) {
- Log.d(TAG, "Service registration failed: " + arg1);
- }
-
- @Override
- public void onServiceUnregistered(NsdServiceInfo arg0) {
- Log.d(TAG, "Service unregistered: " + arg0.getServiceName());
- }
-
- @Override
- public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
- Log.d(TAG, "Service unregistration failed: " + errorCode);
- }
-
- };
- }
-
- public void registerService(int port) {
- tearDown(); // Cancel any previous registration request
- initializeRegistrationListener();
- NsdServiceInfo serviceInfo = new NsdServiceInfo();
- serviceInfo.setPort(port);
- serviceInfo.setServiceName(mServiceName);
- serviceInfo.setServiceType(SERVICE_TYPE);
-
- mNsdManager.registerService(
- serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
-
- }
-
- public void discoverServices() {
- stopDiscovery(); // Cancel any existing discovery request
- initializeDiscoveryListener();
- mNsdManager.discoverServices(
- SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
- }
-
- public void stopDiscovery() {
- if (mDiscoveryListener != null) {
- try {
- mNsdManager.stopServiceDiscovery(mDiscoveryListener);
- } finally {
- }
- mDiscoveryListener = null;
- }
- }
-
- public NsdServiceInfo getChosenServiceInfo() {
- return mService;
- }
-
- public void tearDown() {
- if (mRegistrationListener != null) {
- try {
- mNsdManager.unregisterService(mRegistrationListener);
- } finally {
- }
- mRegistrationListener = null;
- }
- }
-}
diff --git a/scripts/stack_core.py b/scripts/stack_core.py
index 67ae536bd..215a45d5a 100755
--- a/scripts/stack_core.py
+++ b/scripts/stack_core.py
@@ -62,14 +62,6 @@ class TraceConverter:
apk_info = dict()
lib_to_path = dict()
- register_names = {
- "arm": "r0|r1|r2|r3|r4|r5|r6|r7|r8|r9|sl|fp|ip|sp|lr|pc|cpsr",
- "arm64": "x0|x1|x2|x3|x4|x5|x6|x7|x8|x9|x10|x11|x12|x13|x14|x15|x16|x17|x18|x19|x20|x21|x22|x23|x24|x25|x26|x27|x28|x29|x30|sp|pc|pstate",
- "x86": "eax|ebx|ecx|edx|esi|edi|x?cs|x?ds|x?es|x?fs|x?ss|eip|ebp|esp|flags",
- "x86_64": "rax|rbx|rcx|rdx|rsi|rdi|r8|r9|r10|r11|r12|r13|r14|r15|cs|ss|rip|rbp|rsp|eflags",
- "riscv64": "ra|sp|gp|tp|t0|t1|t2|s0|s1|a0|a1|a2|a3|a4|a5|a6|a7|s2|s3|s4|s5|s6|s7|s8|s9|s10|s11|t3|t4|t5|t6|pc",
- }
-
# We use the "file" command line tool to extract BuildId from ELF files.
ElfInfo = collections.namedtuple("ElfInfo", ["bitness", "build_id"])
readelf_output = re.compile(r"Class:\s*ELF(?P<bitness>32|64).*"
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/tools/cargo_embargo/Android.bp b/tools/cargo_embargo/Android.bp
index 3ef899193..b1460e52c 100644
--- a/tools/cargo_embargo/Android.bp
+++ b/tools/cargo_embargo/Android.bp
@@ -33,6 +33,7 @@ rust_defaults {
"libregex",
"libserde",
"libserde_json",
+ "libtempfile",
],
}
diff --git a/tools/cargo_embargo/src/cargo/metadata.rs b/tools/cargo_embargo/src/cargo/metadata.rs
index 127c81630..ea4c95983 100644
--- a/tools/cargo_embargo/src/cargo/metadata.rs
+++ b/tools/cargo_embargo/src/cargo/metadata.rs
@@ -71,7 +71,8 @@ impl DependencyMetadata {
}
}
}
- !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))
}
}
@@ -582,6 +583,88 @@ mod tests {
}
#[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 753f059e1..bcb7a0810 100644
--- a/tools/cargo_embargo/src/main.rs
+++ b/tools/cargo_embargo/src/main.rs
@@ -56,6 +56,7 @@ use std::io::{Read, Write};
use std::path::Path;
use std::path::PathBuf;
use std::process::{Command, Stdio};
+use tempfile::tempdir;
// Major TODOs
// * handle errors, esp. in cargo.out parsing. they should fail the program with an error code
@@ -126,8 +127,11 @@ struct Args {
/// Use the cargo binary in the `cargo_bin` directory. Defaults to using the Android prebuilt.
#[clap(long)]
cargo_bin: Option<PathBuf>,
+ /// Store `cargo build` output in this directory. If not set, a temporary directory is created and used.
+ #[clap(long)]
+ cargo_out_dir: Option<PathBuf>,
/// Skip the `cargo build` commands and reuse the "cargo.out" file from a previous run if
- /// available.
+ /// available. Requires setting --cargo_out_dir.
#[clap(long)]
reuse_cargo_out: bool,
#[command(subcommand)]
@@ -161,15 +165,21 @@ fn main() -> Result<()> {
env_logger::init();
let args = Args::parse();
+ if args.reuse_cargo_out && args.cargo_out_dir.is_none() {
+ return Err(anyhow!("Must specify --cargo_out_dir with --reuse_cargo_out"));
+ }
+ let tempdir = tempdir()?;
+ let intermediates_dir = args.cargo_out_dir.as_deref().unwrap_or(tempdir.path());
+
match &args.mode {
Mode::DumpCrates { config, crates } => {
- dump_crates(&args, config, crates)?;
+ dump_crates(&args, config, crates, intermediates_dir)?;
}
Mode::Generate { config } => {
- run_embargo(&args, config)?;
+ run_embargo(&args, config, intermediates_dir)?;
}
Mode::Autoconfig { config } => {
- autoconfig(&args, config)?;
+ autoconfig(&args, config, intermediates_dir)?;
}
}
@@ -178,9 +188,14 @@ fn main() -> Result<()> {
/// Runs cargo_embargo with the given JSON configuration string, but dumps the crate data to the
/// given `crates.json` file rather than generating an `Android.bp`.
-fn dump_crates(args: &Args, config_filename: &Path, crates_filename: &Path) -> Result<()> {
+fn dump_crates(
+ args: &Args,
+ config_filename: &Path,
+ crates_filename: &Path,
+ intermediates_dir: &Path,
+) -> Result<()> {
let cfg = Config::from_file(config_filename)?;
- let crates = make_all_crates(args, &cfg)?;
+ let crates = make_all_crates(args, &cfg, intermediates_dir)?;
serde_json::to_writer(
File::create(crates_filename)
.with_context(|| format!("Failed to create {:?}", crates_filename))?,
@@ -191,13 +206,13 @@ fn dump_crates(args: &Args, config_filename: &Path, crates_filename: &Path) -> R
/// Tries to automatically generate a suitable `cargo_embargo.json` for the package in the current
/// directory.
-fn autoconfig(args: &Args, config_filename: &Path) -> Result<()> {
+fn autoconfig(args: &Args, config_filename: &Path, intermediates_dir: &Path) -> Result<()> {
println!("Trying default config with tests...");
let mut config_with_build = Config {
variants: vec![VariantConfig { tests: true, ..Default::default() }],
package: Default::default(),
};
- let mut crates_with_build = make_all_crates(args, &config_with_build)?;
+ let mut crates_with_build = make_all_crates(args, &config_with_build, intermediates_dir)?;
let has_tests =
crates_with_build[0].iter().any(|c| c.types.contains(&CrateType::Test) && !c.empty_test);
@@ -205,7 +220,7 @@ fn autoconfig(args: &Args, config_filename: &Path) -> Result<()> {
println!("No tests, removing from config.");
config_with_build =
Config { variants: vec![Default::default()], package: Default::default() };
- crates_with_build = make_all_crates(args, &config_with_build)?;
+ crates_with_build = make_all_crates(args, &config_with_build, intermediates_dir)?;
}
println!("Trying without cargo build...");
@@ -213,7 +228,7 @@ fn autoconfig(args: &Args, config_filename: &Path) -> Result<()> {
variants: vec![VariantConfig { run_cargo: false, tests: has_tests, ..Default::default() }],
package: Default::default(),
};
- let crates_without_build = make_all_crates(args, &config_no_build)?;
+ let crates_without_build = make_all_crates(args, &config_no_build, intermediates_dir)?;
let config = if crates_with_build == crates_without_build {
println!("Output without build was the same, using that.");
@@ -266,11 +281,11 @@ fn add_to_path(extra_path: PathBuf) -> Result<()> {
}
/// Calls make_crates for each variant in the given config.
-fn make_all_crates(args: &Args, cfg: &Config) -> Result<Vec<Vec<Crate>>> {
- cfg.variants.iter().map(|variant| make_crates(args, variant)).collect()
+fn make_all_crates(args: &Args, cfg: &Config, intermediates_dir: &Path) -> Result<Vec<Vec<Crate>>> {
+ cfg.variants.iter().map(|variant| make_crates(args, variant, intermediates_dir)).collect()
}
-fn make_crates(args: &Args, cfg: &VariantConfig) -> Result<Vec<Crate>> {
+fn make_crates(args: &Args, cfg: &VariantConfig, intermediates_dir: &Path) -> Result<Vec<Crate>> {
if !Path::new("Cargo.toml").try_exists().context("when checking Cargo.toml")? {
bail!("Cargo.toml missing. Run in a directory with a Cargo.toml file.");
}
@@ -287,15 +302,16 @@ fn make_crates(args: &Args, cfg: &VariantConfig) -> Result<Vec<Crate>> {
};
add_to_path(cargo_bin)?;
- let cargo_out_path = "cargo.out";
- let cargo_metadata_path = "cargo.metadata";
- let cargo_output = if args.reuse_cargo_out && Path::new(cargo_out_path).exists() {
+ let cargo_out_path = intermediates_dir.join("cargo.out");
+ let cargo_metadata_path = intermediates_dir.join("cargo.metadata");
+ let cargo_output = if args.reuse_cargo_out && cargo_out_path.exists() {
CargoOutput {
cargo_out: read_to_string(cargo_out_path)?,
cargo_metadata: read_to_string(cargo_metadata_path)?,
}
} else {
- let cargo_output = generate_cargo_out(cfg).context("generate_cargo_out failed")?;
+ let cargo_output =
+ generate_cargo_out(cfg, intermediates_dir).context("generate_cargo_out failed")?;
if cfg.run_cargo {
write(cargo_out_path, &cargo_output.cargo_out)?;
}
@@ -311,9 +327,15 @@ fn make_crates(args: &Args, cfg: &VariantConfig) -> Result<Vec<Crate>> {
}
/// Runs cargo_embargo with the given JSON configuration file.
-fn run_embargo(args: &Args, config_filename: &Path) -> Result<()> {
+fn run_embargo(args: &Args, config_filename: &Path, intermediates_dir: &Path) -> Result<()> {
+ let intermediates_glob = intermediates_dir
+ .to_str()
+ .ok_or(anyhow!("Failed to convert intermediate dir path to string"))?
+ .to_string()
+ + "target.tmp/**/build/*/out/*";
+
let cfg = Config::from_file(config_filename)?;
- let crates = make_all_crates(args, &cfg)?;
+ let crates = make_all_crates(args, &cfg, intermediates_dir)?;
// TODO: Use different directories for different variants.
// Find out files.
@@ -322,7 +344,7 @@ fn run_embargo(args: &Args, config_filename: &Path) -> Result<()> {
let mut package_out_files: BTreeMap<String, Vec<Vec<PathBuf>>> = BTreeMap::new();
for (variant_index, variant_cfg) in cfg.variants.iter().enumerate() {
if variant_cfg.package.iter().any(|(_, v)| v.copy_out) {
- for entry in glob::glob("target.tmp/**/build/*/out/*")? {
+ for entry in glob::glob(&intermediates_glob)? {
match entry {
Ok(path) => {
let package_name = || -> Option<_> {
@@ -347,7 +369,7 @@ fn run_embargo(args: &Args, config_filename: &Path) -> Result<()> {
for variant in &mut cfg_no_cargo.variants {
variant.run_cargo = false;
}
- let crates_no_cargo = make_all_crates(args, &cfg_no_cargo)?;
+ let crates_no_cargo = make_all_crates(args, &cfg_no_cargo, intermediates_dir)?;
if crates_no_cargo == crates {
eprintln!("Running cargo appears to be unnecessary for this crate, consider adding `\"run_cargo\": false` to your cargo_embargo.json.");
}
@@ -384,16 +406,24 @@ fn write_all_build_files(
let num_variants = cfg.variants.len();
let empty_package_out_files = vec![vec![]; num_variants];
+ let mut has_error = false;
// Write a build file per package.
for (package_dir, crates) in module_by_package {
let package_name = &crates.iter().flatten().next().unwrap().package_name;
- write_build_files(
+ if let Err(e) = write_build_files(
cfg,
package_name,
package_dir,
&crates,
package_out_files.get(package_name).unwrap_or(&empty_package_out_files),
- )?;
+ ) {
+ // print the error, but continue to accumulate all of the errors
+ eprintln!("ERROR: {:#}", e);
+ has_error = true;
+ }
+ }
+ if has_error {
+ panic!("Encountered fatal errors that must be fixed.");
}
Ok(())
@@ -434,12 +464,12 @@ pub struct CargoOutput {
}
/// Run various cargo commands and returns the output.
-fn generate_cargo_out(cfg: &VariantConfig) -> Result<CargoOutput> {
+fn generate_cargo_out(cfg: &VariantConfig, intermediates_dir: &Path) -> Result<CargoOutput> {
let verbose_args = ["-v"];
- let target_dir_args = ["--target-dir", "target.tmp"];
+ let target_dir = intermediates_dir.join("target.tmp");
// cargo clean
- run_cargo(Command::new("cargo").arg("clean").args(target_dir_args))
+ run_cargo(Command::new("cargo").arg("clean").arg("--target-dir").arg(&target_dir))
.context("Running cargo clean")?;
let default_target = "x86_64-unknown-linux-gnu";
@@ -498,7 +528,8 @@ fn generate_cargo_out(cfg: &VariantConfig) -> Result<CargoOutput> {
.envs(envs.clone())
.args(["build", "--target", default_target])
.args(verbose_args)
- .args(target_dir_args)
+ .arg("--target-dir")
+ .arg(&target_dir)
.args(&workspace_args)
.args(&feature_args),
)?;
@@ -510,7 +541,8 @@ fn generate_cargo_out(cfg: &VariantConfig) -> Result<CargoOutput> {
.envs(envs.clone())
.args(["build", "--target", default_target, "--tests"])
.args(verbose_args)
- .args(target_dir_args)
+ .arg("--target-dir")
+ .arg(&target_dir)
.args(&workspace_args)
.args(&feature_args),
)?;
@@ -519,7 +551,8 @@ fn generate_cargo_out(cfg: &VariantConfig) -> Result<CargoOutput> {
Command::new("cargo")
.envs(envs)
.args(["test", "--target", default_target])
- .args(target_dir_args)
+ .arg("--target-dir")
+ .arg(&target_dir)
.args(&workspace_args)
.args(&feature_args)
.args(["--", "--list"]),
@@ -764,7 +797,8 @@ fn apply_patch_file(output_path: &Path, patch_path: &Path) -> Result<()> {
.output()
.context("Running patch")?;
if !patch_output.status.success() {
- eprintln!("WARNING: failed to apply patch {:?}", patch_path);
+ // These errors will cause the cargo_embargo command to fail, but not yet!
+ return Err(anyhow!("failed to apply patch {patch_path:?}"));
}
Ok(())
}
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..a978d8902
--- /dev/null
+++ b/tools/external_crates/Cargo.lock
@@ -0,0 +1,3035 @@
+# 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 = "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",
+ "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/crate_health/Cargo.toml b/tools/external_crates/crate_health/Cargo.toml
new file mode 100644
index 000000000..186385e26
--- /dev/null
+++ b/tools/external_crates/crate_health/Cargo.toml
@@ -0,0 +1,26 @@
+[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"] }
+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..5b400d7c5
--- /dev/null
+++ b/tools/external_crates/crate_health/src/android_bp.rs
@@ -0,0 +1,119 @@
+// 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>> {
+ 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 test_path = krate.staging_path();
+ pool.execute(move || {
+ tx.send((crate_name, crate_version, generate_android_bp(&repo_root, &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>,
+ staging_path: &impl AsRef<Path>,
+) -> Result<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{}",
+ staging_path.as_ref().display(),
+ from_utf8(&generate_android_bp_output.stdout)?,
+ from_utf8(&generate_android_bp_output.stderr)?
+ );
+ }
+ Ok(generate_android_bp_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()))
+}
+
+pub fn maybe_build_cargo_embargo(repo_root: &impl AsRef<Path>, force_rebuild: bool) -> Result<()> {
+ if !force_rebuild
+ && repo_root.as_ref().join("out/host/linux-x86/bin/cargo_embargo").exists()
+ && repo_root.as_ref().join("out/host/linux-x86/bin/bpfmt").exists()
+ {
+ Ok(())
+ } else {
+ println!("Rebuilding cargo_embargo");
+ build_cargo_embargo(repo_root)
+ }
+}
+
+pub fn build_cargo_embargo(repo_root: &impl AsRef<Path>) -> Result<()> {
+ let status = Command::new("/usr/bin/bash")
+ .args(["-c", "source build/envsetup.sh && lunch aosp_cf_x86_64_phone-trunk_staging-eng && m cargo_embargo bpfmt"])
+ .current_dir(repo_root).spawn().context("Failed to spawn build of cargo embargo and bpfmt")?.wait().context("Failed to wait on child process building cargo embargo and bpfmt")?;
+ match status.success() {
+ true => Ok(()),
+ false => Err(anyhow!(
+ "Building cargo embargo and bpfmt failed with exit code {}",
+ status.code().map(|code| { format!("{}", code) }).unwrap_or("(unknown)".to_string())
+ )),
+ }
+}
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..7a4ffdb94
--- /dev/null
+++ b/tools/external_crates/crate_health/src/bin/health_report.rs
@@ -0,0 +1,53 @@
+// 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::{
+ default_output_dir, default_repo_root, maybe_build_cargo_embargo, 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();
+
+ maybe_build_cargo_embargo(&args.repo_root, false)?;
+
+ 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()?;
+ cc.diff_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..900d9aad5
--- /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::{
+ default_output_dir, default_repo_root, maybe_build_cargo_embargo, 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();
+
+ maybe_build_cargo_embargo(&args.repo_root, false)?;
+
+ 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..413b5bc1e
--- /dev/null
+++ b/tools/external_crates/crate_health/src/crate_collection.rs
@@ -0,0 +1,99 @@
+// 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);
+ }
+ Ok(())
+ }
+ pub fn diff_android_bps(&mut self) -> Result<()> {
+ for krate in self.crates.values_mut() {
+ krate.diff_android_bp()?;
+ }
+ 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..c2d93a949
--- /dev/null
+++ b/tools/external_crates/crate_health/src/crate_type.rs
@@ -0,0 +1,370 @@
+// 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::{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 semver::Version;
+
+use crate::{
+ copy_dir, 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 {
+ self.relpath().join("Android.bp")
+ }
+ pub fn cargo_embargo_json(&self) -> PathBuf {
+ 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 patch_dir(&self) -> PathBuf {
+ self.staging_path().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.root().join(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())?;
+ }
+ Ok(())
+ }
+
+ pub fn diff_android_bp(&mut self) -> Result<()> {
+ self.set_diff_output(
+ diff_android_bp(
+ &self.android_bp(),
+ &self.staging_path().join("Android.bp"),
+ &self.root(),
+ )
+ .context("Failed to diff Android.bp".to_string())?,
+ );
+ Ok(())
+ }
+
+ pub fn apply_patches(&mut self) -> Result<()> {
+ let patch_dir_absolute = self.root().join(self.patch_dir());
+ if patch_dir_absolute.exists() {
+ 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"
+ || entry.file_name() == "rules.mk.diff"
+ {
+ continue;
+ }
+ let entry_path = entry.path();
+ let output = Command::new("patch")
+ .args(["-p1", "-l", "--no-backup-if-mismatch", "-i"])
+ .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) {
+ self.generate_android_bp_output.replace(c2a_output);
+ }
+ pub fn set_diff_output(&mut self, diff_output: 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.root.join(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.root().join(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(())
+ }
+}
+
+pub fn diff_android_bp(
+ 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()?)
+}
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..c6e66a0b6
--- /dev/null
+++ b/tools/external_crates/crate_health/src/lib.rs
@@ -0,0 +1,110 @@
+// 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 std::process::Command;
+use std::str::from_utf8;
+
+use anyhow::{anyhow, Context, Result};
+use semver::Version;
+use thiserror::Error;
+
+pub use self::crate_type::{diff_android_bp, 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::PseudoCrate;
+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, maybe_build_cargo_embargo};
+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()))
+}
+
+// The copy_dir crate doesn't handle symlinks.
+pub fn copy_dir(src: &impl AsRef<Path>, dst: &impl AsRef<Path>) -> Result<()> {
+ let output =
+ Command::new("cp").arg("--archive").arg(src.as_ref()).arg(dst.as_ref()).output()?;
+ if !output.status.success() {
+ return Err(anyhow!(
+ "Failed to copy {} to {}\nstdout:\n{}\nstderr:\n{}",
+ src.as_ref().display(),
+ dst.as_ref().display(),
+ from_utf8(&output.stdout)?,
+ from_utf8(&output.stderr)?
+ ));
+ }
+ Ok(())
+}
diff --git a/tools/external_crates/crate_health/src/main.rs b/tools/external_crates/crate_health/src/main.rs
new file mode 100644
index 000000000..15d8865bf
--- /dev/null
+++ b/tools/external_crates/crate_health/src/main.rs
@@ -0,0 +1,281 @@
+// Copyright (C) 2024 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, process::Command, str::from_utf8};
+
+use anyhow::{anyhow, Result};
+use clap::{Parser, Subcommand};
+use crate_health::{
+ default_repo_root, maybe_build_cargo_embargo, migrate, CrateCollection, Migratable,
+ NameAndVersionMap, NamedAndVersioned,
+};
+
+#[derive(Parser)]
+struct Cli {
+ #[command(subcommand)]
+ command: Cmd,
+
+ #[arg(long, default_value_os_t=default_repo_root().unwrap_or(PathBuf::from(".")))]
+ repo_root: PathBuf,
+
+ /// Rebuild cargo_embargo and bpfmt, even if they are already present in the out directory.
+ #[arg(long, default_value_t = false)]
+ rebuild_cargo_embargo: bool,
+}
+
+#[derive(Subcommand)]
+enum Cmd {
+ /// Check the health of a crate, and whether it is safe to migrate.
+ MigrationHealth {
+ /// The crate name. Also the directory name in external/rust/crates
+ crate_name: String,
+ },
+ /// Migrate a crate from external/rust/crates to the monorepo.
+ Migrate {
+ /// The crate name. Also the directory name in external/rust/crates
+ crate_name: String,
+ },
+ Regenerate {
+ /// The crate name.
+ crate_name: String,
+ },
+ /// Run pre-upload checks.
+ PreuploadCheck {
+ /// List of changed files
+ files: Vec<String>,
+ },
+}
+
+static IGNORED_FILES: &'static [&'static str] = &[
+ ".appveyor.yml",
+ ".bazelci",
+ ".bazelignore",
+ ".bazelrc",
+ ".bazelversion",
+ ".buildkite",
+ ".cargo",
+ ".cargo-checksum.json",
+ ".cargo_vcs_info.json",
+ ".circleci",
+ ".cirrus.yml",
+ ".clang-format",
+ ".clang-tidy",
+ ".clippy.toml",
+ ".clog.toml",
+ ".clog.toml",
+ ".codecov.yaml",
+ ".codecov.yml",
+ ".editorconfig",
+ ".gcloudignore",
+ ".gdbinit",
+ ".git",
+ ".git-blame-ignore-revs",
+ ".git-ignore-revs",
+ ".gitallowed",
+ ".gitattributes",
+ ".github",
+ ".gitignore",
+ ".idea",
+ ".ignore",
+ ".istanbul.yml",
+ ".mailmap",
+ ".md-inc.toml",
+ ".mdl-style.rb",
+ ".mdlrc",
+ ".pylintrc",
+ ".pylintrc-examples",
+ ".pylintrc-tests",
+ ".reuse",
+ ".rspec",
+ ".rustfmt.toml",
+ ".shellcheckrc",
+ ".standard-version",
+ ".tarpaulin.toml",
+ ".tokeignore",
+ ".travis.yml",
+ ".versionrc",
+ ".vim",
+ ".vscode",
+ ".yapfignore",
+ ".yardopts",
+ "BUILD",
+ "Cargo.lock",
+ "Cargo.lock.saved",
+ "Cargo.toml.orig",
+ "OWNERS",
+ // rules.mk related files that we won't migrate.
+ "cargo2rulesmk.json",
+ "CleanSpec.mk",
+ "rules.mk",
+ // cargo_embargo intermediates.
+ "Android.bp.orig",
+ "cargo.metadata",
+ "cargo.out",
+ "target.tmp",
+];
+
+fn main() -> Result<()> {
+ let args = Cli::parse();
+
+ maybe_build_cargo_embargo(&args.repo_root, args.rebuild_cargo_embargo)?;
+
+ match args.command {
+ Cmd::MigrationHealth { crate_name } => {
+ if args
+ .repo_root
+ .join("external/rust/android-crates-io/crates")
+ .join(&crate_name)
+ .exists()
+ {
+ return Err(anyhow!(
+ "Crate {} already exists in external/rust/android-crates-io/crates",
+ crate_name
+ ));
+ }
+
+ let mut cc = CrateCollection::new(&args.repo_root);
+ cc.add_from(&PathBuf::from("external/rust/crates").join(&crate_name), None::<&&str>)?;
+ cc.map_field_mut().retain(|_nv, krate| krate.is_crates_io());
+ if cc.map_field().len() != 1 {
+ return Err(anyhow!(
+ "Expected a single crate version for {}, but found {}. Crates with multiple versions are not supported yet.",
+ crate_name,
+ cc.map_field().len()
+ ));
+ }
+
+ cc.stage_crates()?;
+ cc.generate_android_bps()?;
+ cc.diff_android_bps()?;
+
+ let krate = cc.map_field().values().next().unwrap();
+ println!(
+ "Found {} v{} in {}",
+ krate.name(),
+ krate.version(),
+ krate.relpath().display()
+ );
+ let migratable;
+ if !krate.is_android_bp_healthy() {
+ if krate.is_migration_denied() {
+ println!("This crate is on the migration denylist");
+ }
+ if !krate.root().join(krate.android_bp()).exists() {
+ println!("There is no Android.bp file in {}", krate.relpath().display());
+ }
+ if !krate.cargo_embargo_json().exists() {
+ println!(
+ "There is no cargo_embargo.json file in {}",
+ krate.relpath().display()
+ );
+ } else if !krate.generate_android_bp_success() {
+ println!(
+ "cargo_embargo execution did not succeed for {}",
+ krate.relpath().display()
+ );
+ } else if !krate.android_bp_unchanged() {
+ println!(
+ "Running cargo_embargo on {} produced changes to the Android.bp file:",
+ krate.relpath().display()
+ );
+ println!(
+ "{}",
+ from_utf8(
+ &krate
+ .android_bp_diff()
+ .ok_or(anyhow!("No Android.bp diff found"))?
+ .stdout
+ )?
+ );
+ }
+ migratable = false;
+ } else {
+ let migration = migrate(
+ &args.repo_root,
+ &PathBuf::from("external/rust/crates").join(&crate_name),
+ &"out/rust-crate-migration-report",
+ )?;
+ let compatible_pairs = migration.compatible_pairs().collect::<Vec<_>>();
+ if compatible_pairs.len() != 1 {
+ return Err(anyhow!("Couldn't find a compatible version to migrate to",));
+ }
+ let pair = compatible_pairs.first().unwrap();
+ if pair.source.version() != pair.dest.version() {
+ println!(
+ "Source and destination versions are different: {} -> {}",
+ pair.source.version(),
+ pair.dest.version()
+ );
+ }
+ if !pair.dest.is_migratable() {
+ if !pair.dest.patch_success() {
+ println!("Patches did not apply successfully to the migrated crate");
+ // TODO: Show errors.
+ }
+ if !pair.dest.generate_android_bp_success() {
+ println!("cargo_embargo execution did not succeed for the migrated crate");
+ } else if pair.dest.android_bp_unchanged() {
+ println!("Running cargo_embargo for the migrated crate produced changes to the Android.bp file:");
+ println!(
+ "{}",
+ from_utf8(
+ &pair
+ .dest
+ .android_bp_diff()
+ .ok_or(anyhow!("No Android.bp diff found"))?
+ .stdout
+ )?
+ );
+ }
+ }
+
+ let diff_status = Command::new("diff")
+ .args(["-u", "-r", "-w", "--no-dereference"])
+ .args(IGNORED_FILES.iter().map(|ignored| format!("--exclude={}", ignored)))
+ .arg(pair.source.relpath())
+ .arg(pair.dest.staging_path())
+ .current_dir(&args.repo_root)
+ .spawn()?
+ .wait()?;
+ if !diff_status.success() {
+ println!(
+ "Found differences between {} and {}",
+ pair.source.relpath().display(),
+ pair.dest.staging_path().display()
+ );
+ }
+ println!("All diffs:");
+ Command::new("diff")
+ .args(["-u", "-r", "-w", "-q", "--no-dereference"])
+ .arg(pair.source.relpath())
+ .arg(pair.dest.staging_path())
+ .current_dir(&args.repo_root)
+ .spawn()?
+ .wait()?;
+
+ migratable = pair.dest.is_migratable() && diff_status.success()
+ }
+
+ println!(
+ "The crate is {}",
+ if krate.is_android_bp_healthy() && migratable { "healthy" } else { "UNHEALTHY" }
+ );
+ }
+ Cmd::Migrate { crate_name: _ } => todo!(),
+ Cmd::Regenerate { crate_name: _ } => todo!(),
+ Cmd::PreuploadCheck { files: _ } => todo!(),
+ }
+
+ Ok(())
+}
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..318a4c01e
--- /dev/null
+++ b/tools/external_crates/crate_health/src/migration.rs
@@ -0,0 +1,128 @@
+// 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_link, remove_dir_all},
+ os::unix::fs::symlink,
+ path::{Path, PathBuf},
+ process::Output,
+};
+
+use anyhow::{anyhow, Context, Result};
+use glob::glob;
+
+use crate::{
+ copy_dir, crate_type::diff_android_bp, most_recent_version, CompatibleVersionPair, Crate,
+ CrateCollection, Migratable, NameAndVersionMap, PseudoCrate, VersionMatch,
+};
+
+static CUSTOMIZATIONS: &'static [&'static str] =
+ &["*.bp", "cargo_embargo.json", "patches", "METADATA", "TEST_MAPPING", "MODULE_LICENSE_*"];
+
+static SYMLINKS: &'static [&'static str] = &["LICENSE", "NOTICE"];
+
+impl<'a> CompatibleVersionPair<'a, Crate> {
+ pub fn copy_customizations(&self) -> Result<()> {
+ let dest_dir_absolute = self.dest.root().join(self.dest.staging_path());
+ 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 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 {
+ let dest_file = dest_dir_absolute.join(&filename);
+ if dest_file.exists() {
+ return Err(anyhow!("Destination file {} exists", dest_file.display()));
+ }
+ copy(&entry, dest_dir_absolute.join(filename)).context(format!(
+ "Failed to copy {} to {}",
+ entry.display(),
+ dest_dir_absolute.display()
+ ))?;
+ }
+ }
+ }
+ for link in SYMLINKS {
+ let src_path = self.source.path().join(link);
+ if src_path.is_symlink() {
+ let dest = read_link(src_path)?;
+ if dest.exists() {
+ return Err(anyhow!(
+ "Can't symlink {} -> {} because destination exists",
+ link,
+ dest.display(),
+ ));
+ }
+ symlink(dest, dest_dir_absolute.join(link))?;
+ }
+ }
+ Ok(())
+ }
+ pub fn diff_android_bps(&self) -> Result<Output> {
+ diff_android_bp(
+ &self.source.android_bp(),
+ &self.dest.staging_path().join("Android.bp"),
+ &self.source.root(),
+ )
+ .context("Failed to diff Android.bp".to_string())
+ }
+}
+
+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 = PseudoCrate::new(source.repo_root().join(pseudo_crate_dir));
+ if pseudo_crate.get_path().exists() {
+ remove_dir_all(pseudo_crate.get_path())
+ .context(format!("Failed to remove {}", pseudo_crate.get_path().display()))?;
+ }
+ pseudo_crate.init(
+ 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("vendor"), Some(pseudo_crate_dir))?;
+
+ let mut version_match = VersionMatch::new(source, dest)?;
+
+ version_match.stage_crates()?;
+ version_match.copy_customizations()?;
+ version_match.apply_patches()?;
+ version_match.generate_android_bps()?;
+ version_match.diff_android_bps()?;
+
+ 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..55472c0be
--- /dev/null
+++ b/tools/external_crates/crate_health/src/pseudo_crate.rs
@@ -0,0 +1,125 @@
+// 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, PathBuf},
+ 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 struct PseudoCrate {
+ // Absolute path to pseudo-crate.
+ path: PathBuf,
+}
+
+impl PseudoCrate {
+ pub fn new<P: Into<PathBuf>>(path: P) -> PseudoCrate {
+ PseudoCrate { path: path.into() }
+ }
+ pub fn init<'a>(
+ &self,
+ crates: impl Iterator<Item = &'a (impl NamedAndVersioned + 'a)>,
+ ) -> Result<()> {
+ if self.path.exists() {
+ return Err(anyhow!(
+ "Can't init pseudo-crate because {} already exists",
+ self.path.display()
+ ));
+ }
+ ensure_exists_and_empty(&self.path)?;
+
+ 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 = self.path.join("Cargo.toml");
+ write(&cargo_toml, tt.render("cargo_toml", &CargoToml { deps })?)?;
+
+ create_dir(self.path.join("src")).context("Failed to create src dir")?;
+ write(self.path.join("src/lib.rs"), "// Nothing").context("Failed to create src/lib.rs")?;
+
+ self.vendor()
+
+ // TODO: Run "cargo deny"
+ }
+ pub fn get_path(&self) -> &Path {
+ self.path.as_path()
+ }
+ pub fn add(&self, krate: &impl NamedAndVersioned) -> Result<()> {
+ let status = Command::new("cargo")
+ .args(["add", format!("{}@={}", krate.name(), krate.version()).as_str()])
+ .current_dir(&self.path)
+ .spawn()
+ .context("Failed to spawn 'cargo add'")?
+ .wait()
+ .context("Failed to wait on 'cargo add'")?;
+ if !status.success() {
+ return Err(anyhow!("Failed to run 'cargo add {}@{}'", krate.name(), krate.version()));
+ }
+ Ok(())
+ }
+ pub fn vendor(&self) -> Result<()> {
+ let output = Command::new("cargo").args(["vendor"]).current_dir(&self.path).output()?;
+ if !output.status.success() {
+ return Err(anyhow!(
+ "cargo vendor failed with exit code {}\nstdout:\n{}\nstderr:\n{}",
+ output
+ .status
+ .code()
+ .map(|code| { format!("{}", code) })
+ .unwrap_or("(unknown)".to_string()),
+ from_utf8(&output.stdout)?,
+ from_utf8(&output.stderr)?
+ ));
+ }
+ 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..488bc589f
--- /dev/null
+++ b/tools/external_crates/crate_health/src/reports.rs
@@ -0,0 +1,350 @@
+// 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, CompatibleVersionPair, 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.root().join(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 migratable_table<'a>(
+ &self,
+ crate_pairs: impl Iterator<Item = CompatibleVersionPair<'a, Crate>>,
+ ) -> Result<String> {
+ let mut table = Table::new(&[&"Crate", &"Old Version", &"New Version", &"Path"]);
+ for crate_pair in crate_pairs {
+ let source = crate_pair.source;
+ let dest = crate_pair.dest;
+ let dest_version = if source.version() == dest.version() {
+ "".to_string()
+ } else {
+ dest.version().to_string()
+ };
+ table.add_row(&[
+ &linkify(&source.name(), &source.crates_io_url()),
+ &source.version().to_string(),
+ &dest_version,
+ &source.aosp_url().map_or(format!("{}", source.relpath().display()), |url| {
+ linkify(&source.relpath().display(), &url)
+ }),
+ ]);
+ }
+ 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.root().join(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.migratable_table(m.migratable())?,
+ 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..206cfad21
--- /dev/null
+++ b/tools/external_crates/crate_health/src/templates/Cargo.toml.template
@@ -0,0 +1,10 @@
+[package]
+name = "android-pseudo-crate"
+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..49f7ad1ff
--- /dev/null
+++ b/tools/external_crates/crate_health/src/version_match.rs
@@ -0,0 +1,427 @@
+// 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<()> {
+ for pair in self.compatible_and_eligible() {
+ pair.copy_customizations()?;
+ }
+ Ok(())
+ }
+ pub fn stage_crates(&mut self) -> Result<()> {
+ for pair in self.compatible_and_eligible() {
+ pair.dest.stage_crate()?;
+ }
+ Ok(())
+ }
+ pub fn apply_patches(&mut self) -> Result<()> {
+ 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<()> {
+ 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);
+ }
+ Ok(())
+ }
+
+ pub fn diff_android_bps(&mut self) -> Result<()> {
+ let mut results = BTreeMap::new();
+ for pair in self.compatible_and_eligible() {
+ results.insert_or_error(NameAndVersion::from(pair.dest), pair.diff_android_bps()?)?;
+ }
+ 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_diff_output(output);
+ }
+ 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/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/src/repr/protobuf/converter.cpp b/vndk/tools/header-checker/src/repr/protobuf/converter.cpp
index c418d80c1..3bf620d47 100644
--- a/vndk/tools/header-checker/src/repr/protobuf/converter.cpp
+++ b/vndk/tools/header-checker/src/repr/protobuf/converter.cpp
@@ -55,22 +55,22 @@ bool IRDiffToProtobufConverter::AddVTableLayoutDiff(
abi_dump:: VTableLayout *new_vtable =
vtable_layout_diff_protobuf->mutable_new_vtable();
if (old_vtable == nullptr || new_vtable == nullptr ||
- !SetIRToProtobufVTableLayout(old_vtable,
- vtable_layout_diff_ir->GetOldVTable()) ||
- !SetIRToProtobufVTableLayout(new_vtable,
- vtable_layout_diff_ir->GetNewVTable())) {
+ !IRToProtobufConverter::ConvertVTableLayoutIR(
+ old_vtable, vtable_layout_diff_ir->GetOldVTable()) ||
+ !IRToProtobufConverter::ConvertVTableLayoutIR(
+ new_vtable, vtable_layout_diff_ir->GetNewVTable())) {
return false;
}
return true;
}
-template <typename T>
static bool CopyBaseSpecifiersDiffIRToProtobuf(
- google::protobuf::RepeatedPtrField<T> *dst,
+ google::protobuf::RepeatedPtrField<abi_dump::CXXBaseSpecifier> *dst,
const std::vector<CXXBaseSpecifierIR> &bases_ir) {
for (auto &&base_ir : bases_ir) {
- T *added_base = dst->Add();
- if (!SetIRToProtobufBaseSpecifier(added_base, base_ir)) {
+ abi_dump::CXXBaseSpecifier *added_base = dst->Add();
+ if (!IRToProtobufConverter::ConvertCXXBaseSpecifierIR(added_base,
+ base_ir)) {
return false;
}
}
@@ -102,10 +102,9 @@ bool IRDiffToProtobufConverter::AddRecordFields(
} else {
field = record_diff_protobuf->add_fields_added();
}
- if (field == nullptr) {
+ if (!IRToProtobufConverter::ConvertRecordFieldIR(field, record_field_ir)) {
return false;
}
- SetIRToProtobufRecordField(field, record_field_ir);
}
return true;
}
@@ -126,10 +125,10 @@ bool IRDiffToProtobufConverter::AddRecordFieldDiffs(
if (old_field == nullptr || new_field == nullptr) {
return false;
}
- SetIRToProtobufRecordField(old_field,
- record_field_diff_ir.GetOldField());
- SetIRToProtobufRecordField(new_field,
- record_field_diff_ir.GetNewField());
+ IRToProtobufConverter::ConvertRecordFieldIR(
+ old_field, record_field_diff_ir.GetOldField());
+ IRToProtobufConverter::ConvertRecordFieldIR(
+ new_field, record_field_diff_ir.GetNewField());
}
return true;
}
@@ -203,7 +202,8 @@ static bool AddEnumFields(
const std::vector<const EnumFieldIR *> &enum_fields) {
for (auto &&enum_field : enum_fields) {
abi_dump::EnumFieldDecl *added_enum_field = dst->Add();
- if (!SetIRToProtobufEnumField(added_enum_field, enum_field)) {
+ if (!IRToProtobufConverter::ConvertEnumFieldIR(added_enum_field,
+ enum_field)) {
return false;
}
}
@@ -218,10 +218,12 @@ static bool AddEnumFieldDiffs(
if (field_diff_protobuf == nullptr) {
return false;
}
- if (!SetIRToProtobufEnumField(field_diff_protobuf->mutable_old_field(),
- field_diff_ir.GetOldField()) ||
- !SetIRToProtobufEnumField(field_diff_protobuf->mutable_new_field(),
- field_diff_ir.GetNewField())) {
+ if (!IRToProtobufConverter::ConvertEnumFieldIR(
+ field_diff_protobuf->mutable_old_field(),
+ field_diff_ir.GetOldField()) ||
+ !IRToProtobufConverter::ConvertEnumFieldIR(
+ field_diff_protobuf->mutable_new_field(),
+ field_diff_ir.GetNewField())) {
return false;
}
}
diff --git a/vndk/tools/header-checker/src/repr/protobuf/converter.h b/vndk/tools/header-checker/src/repr/protobuf/converter.h
index 191ac0f48..7325951aa 100644
--- a/vndk/tools/header-checker/src/repr/protobuf/converter.h
+++ b/vndk/tools/header-checker/src/repr/protobuf/converter.h
@@ -20,8 +20,6 @@
#include "repr/protobuf/abi_diff.h"
#include "repr/protobuf/abi_dump.h"
-#include <llvm/Support/raw_ostream.h>
-
namespace header_checker {
namespace repr {
@@ -190,6 +188,7 @@ inline VTableComponentIR::Kind VTableComponentKindProtobufToIR(
assert(false);
}
+// Convert IR to the messages defined in abi_dump.proto.
class IRToProtobufConverter {
private:
static bool AddTemplateInformation(
@@ -211,6 +210,21 @@ class IRToProtobufConverter {
const EnumTypeIR *enum_ir);
public:
+ static bool ConvertRecordFieldIR(
+ abi_dump::RecordFieldDecl *record_field_protobuf,
+ const RecordFieldIR *record_field_ir);
+
+ static bool ConvertCXXBaseSpecifierIR(
+ abi_dump::CXXBaseSpecifier *base_specifier_protobuf,
+ const CXXBaseSpecifierIR &base_specifier_ir);
+
+ static bool ConvertVTableLayoutIR(
+ abi_dump::VTableLayout *vtable_layout_protobuf,
+ const VTableLayoutIR &vtable_layout_ir);
+
+ static bool ConvertEnumFieldIR(abi_dump::EnumFieldDecl *enum_field_protobuf,
+ const EnumFieldIR *enum_field_ir);
+
static abi_dump::EnumType ConvertEnumTypeIR(const EnumTypeIR *enump);
static abi_dump::RecordType ConvertRecordTypeIR(const RecordTypeIR *recordp);
@@ -257,6 +271,7 @@ class IRToProtobufConverter {
const ElfObjectIR *elf_object_ir);
};
+// Convert IR to the messages defined in abi_diff.proto.
class IRDiffToProtobufConverter {
private:
static bool AddTypeInfoDiff(
@@ -298,76 +313,6 @@ class IRDiffToProtobufConverter {
const GlobalVarDiffIR *global_var_diffp);
};
-inline void SetIRToProtobufRecordField(
- abi_dump::RecordFieldDecl *record_field_protobuf,
- const RecordFieldIR *record_field_ir) {
- record_field_protobuf->set_field_name(record_field_ir->GetName());
- record_field_protobuf->set_referenced_type(
- record_field_ir->GetReferencedType());
- record_field_protobuf->set_access(
- AccessIRToProtobuf(record_field_ir->GetAccess()));
- record_field_protobuf->set_field_offset(record_field_ir->GetOffset());
- if (record_field_ir->IsBitField()) {
- record_field_protobuf->set_is_bit_field(true);
- record_field_protobuf->set_bit_width(record_field_ir->GetBitWidth());
- }
-}
-
-inline bool SetIRToProtobufBaseSpecifier(
- abi_dump::CXXBaseSpecifier *base_specifier_protobuf,
- const CXXBaseSpecifierIR &base_specifier_ir) {
- if (!base_specifier_protobuf) {
- llvm::errs() << "Protobuf base specifier not valid\n";
- return false;
- }
- base_specifier_protobuf->set_referenced_type(
- base_specifier_ir.GetReferencedType());
- base_specifier_protobuf->set_is_virtual(
- base_specifier_ir.IsVirtual());
- base_specifier_protobuf->set_access(
- AccessIRToProtobuf(base_specifier_ir.GetAccess()));
- return true;
-}
-
-inline bool SetIRToProtobufVTableLayout(
- abi_dump::VTableLayout *vtable_layout_protobuf,
- const VTableLayoutIR &vtable_layout_ir) {
- if (vtable_layout_protobuf == nullptr) {
- llvm::errs() << "vtable layout protobuf not valid\n";
- return false;
- }
- for (auto &&vtable_component_ir : vtable_layout_ir.GetVTableComponents()) {
- abi_dump::VTableComponent *added_vtable_component =
- vtable_layout_protobuf->add_vtable_components();
- if (!added_vtable_component) {
- llvm::errs() << "Couldn't add vtable component\n";
- return false;
- }
- added_vtable_component->set_kind(
- VTableComponentKindIRToProtobuf(vtable_component_ir.GetKind()));
- added_vtable_component->set_component_value(vtable_component_ir.GetValue());
- added_vtable_component->set_mangled_component_name(
- vtable_component_ir.GetName());
- added_vtable_component->set_is_pure(vtable_component_ir.GetIsPure());
- }
- return true;
-}
-
-inline bool SetIRToProtobufEnumField(
- abi_dump::EnumFieldDecl *enum_field_protobuf,
- const EnumFieldIR *enum_field_ir) {
- if (enum_field_protobuf == nullptr) {
- return true;
- }
- enum_field_protobuf->set_name(enum_field_ir->GetName());
- // The "enum_field_value" in the .proto is a signed 64-bit integer. An
- // unsigned integer >= (1 << 63) is represented with a negative integer in the
- // dump file. Despite the wrong representation, the diff result isn't affected
- // because every integer has a unique representation.
- enum_field_protobuf->set_enum_field_value(enum_field_ir->GetSignedValue());
- return true;
-}
-
} // namespace repr
} // namespace header_checker
diff --git a/vndk/tools/header-checker/src/repr/protobuf/ir_dumper.cpp b/vndk/tools/header-checker/src/repr/protobuf/ir_dumper.cpp
index 38b2adfa4..317cd773a 100644
--- a/vndk/tools/header-checker/src/repr/protobuf/ir_dumper.cpp
+++ b/vndk/tools/header-checker/src/repr/protobuf/ir_dumper.cpp
@@ -59,33 +59,88 @@ bool IRToProtobufConverter::AddTypeInfo(
return true;
}
+bool IRToProtobufConverter::ConvertRecordFieldIR(
+ abi_dump::RecordFieldDecl *record_field_protobuf,
+ const RecordFieldIR *record_field_ir) {
+ if (!record_field_protobuf) {
+ llvm::errs() << "Invalid record field\n";
+ return false;
+ }
+ record_field_protobuf->set_field_name(record_field_ir->GetName());
+ record_field_protobuf->set_referenced_type(
+ record_field_ir->GetReferencedType());
+ record_field_protobuf->set_access(
+ AccessIRToProtobuf(record_field_ir->GetAccess()));
+ record_field_protobuf->set_field_offset(record_field_ir->GetOffset());
+ if (record_field_ir->IsBitField()) {
+ record_field_protobuf->set_is_bit_field(true);
+ record_field_protobuf->set_bit_width(record_field_ir->GetBitWidth());
+ }
+ return true;
+}
+
bool IRToProtobufConverter::AddRecordFields(
- abi_dump::RecordType *record_protobuf,
- const RecordTypeIR *record_ir) {
- // Iterate through the fields and create corresponding ones for the protobuf
- // record
+ abi_dump::RecordType *record_protobuf, const RecordTypeIR *record_ir) {
for (auto &&field_ir : record_ir->GetFields()) {
abi_dump::RecordFieldDecl *added_field = record_protobuf->add_fields();
- if (!added_field) {
- llvm::errs() << "Couldn't add record field\n";
+ if (!IRToProtobufConverter::ConvertRecordFieldIR(added_field, &field_ir)) {
+ return false;
}
- SetIRToProtobufRecordField(added_field, &field_ir);
}
return true;
}
+bool IRToProtobufConverter::ConvertCXXBaseSpecifierIR(
+ abi_dump::CXXBaseSpecifier *base_specifier_protobuf,
+ const CXXBaseSpecifierIR &base_specifier_ir) {
+ if (!base_specifier_protobuf) {
+ llvm::errs() << "Protobuf base specifier not valid\n";
+ return false;
+ }
+ base_specifier_protobuf->set_referenced_type(
+ base_specifier_ir.GetReferencedType());
+ base_specifier_protobuf->set_is_virtual(base_specifier_ir.IsVirtual());
+ base_specifier_protobuf->set_access(
+ AccessIRToProtobuf(base_specifier_ir.GetAccess()));
+ return true;
+}
+
bool IRToProtobufConverter::AddBaseSpecifiers(
abi_dump::RecordType *record_protobuf, const RecordTypeIR *record_ir) {
for (auto &&base_ir : record_ir->GetBases()) {
abi_dump::CXXBaseSpecifier *added_base =
record_protobuf->add_base_specifiers();
- if (!SetIRToProtobufBaseSpecifier(added_base, base_ir)) {
+ if (!ConvertCXXBaseSpecifierIR(added_base, base_ir)) {
return false;
}
}
return true;
}
+bool IRToProtobufConverter::ConvertVTableLayoutIR(
+ abi_dump::VTableLayout *vtable_layout_protobuf,
+ const VTableLayoutIR &vtable_layout_ir) {
+ if (vtable_layout_protobuf == nullptr) {
+ llvm::errs() << "vtable layout protobuf not valid\n";
+ return false;
+ }
+ for (auto &&vtable_component_ir : vtable_layout_ir.GetVTableComponents()) {
+ abi_dump::VTableComponent *added_vtable_component =
+ vtable_layout_protobuf->add_vtable_components();
+ if (!added_vtable_component) {
+ llvm::errs() << "Couldn't add vtable component\n";
+ return false;
+ }
+ added_vtable_component->set_kind(
+ VTableComponentKindIRToProtobuf(vtable_component_ir.GetKind()));
+ added_vtable_component->set_component_value(vtable_component_ir.GetValue());
+ added_vtable_component->set_mangled_component_name(
+ vtable_component_ir.GetName());
+ added_vtable_component->set_is_pure(vtable_component_ir.GetIsPure());
+ }
+ return true;
+}
+
bool IRToProtobufConverter::AddVTableLayout(
abi_dump::RecordType *record_protobuf,
const RecordTypeIR *record_ir) {
@@ -96,7 +151,7 @@ bool IRToProtobufConverter::AddVTableLayout(
const VTableLayoutIR &vtable_layout_ir = record_ir->GetVTableLayout();
abi_dump::VTableLayout *vtable_layout_protobuf =
record_protobuf->mutable_vtable_layout();
- if (!SetIRToProtobufVTableLayout(vtable_layout_protobuf, vtable_layout_ir)) {
+ if (!ConvertVTableLayoutIR(vtable_layout_protobuf, vtable_layout_ir)) {
return false;
}
return true;
@@ -124,11 +179,12 @@ abi_dump::RecordType IRToProtobufConverter::ConvertRecordTypeIR(
return added_record_type;
}
-
abi_dump::ElfObject IRToProtobufConverter::ConvertElfObjectIR(
const ElfObjectIR *elf_object_ir) {
abi_dump::ElfObject elf_object_protobuf;
elf_object_protobuf.set_name(elf_object_ir->GetName());
+ elf_object_protobuf.set_binding(
+ ElfSymbolBindingIRToProtobuf(elf_object_ir->GetBinding()));
return elf_object_protobuf;
}
@@ -136,6 +192,8 @@ abi_dump::ElfFunction IRToProtobufConverter::ConvertElfFunctionIR(
const ElfFunctionIR *elf_function_ir) {
abi_dump::ElfFunction elf_function_protobuf;
elf_function_protobuf.set_name(elf_function_ir->GetName());
+ elf_function_protobuf.set_binding(
+ ElfSymbolBindingIRToProtobuf(elf_function_ir->GetBinding()));
return elf_function_protobuf;
}
@@ -194,11 +252,27 @@ abi_dump::FunctionDecl IRToProtobufConverter::ConvertFunctionIR(
return added_function;
}
+bool IRToProtobufConverter::ConvertEnumFieldIR(
+ abi_dump::EnumFieldDecl *enum_field_protobuf,
+ const EnumFieldIR *enum_field_ir) {
+ if (enum_field_protobuf == nullptr) {
+ llvm::errs() << "Invalid enum field\n";
+ return false;
+ }
+ enum_field_protobuf->set_name(enum_field_ir->GetName());
+ // The "enum_field_value" in the .proto is a signed 64-bit integer. An
+ // unsigned integer >= (1 << 63) is represented with a negative integer in the
+ // dump file. Despite the wrong representation, the diff result isn't affected
+ // because every integer has a unique representation.
+ enum_field_protobuf->set_enum_field_value(enum_field_ir->GetSignedValue());
+ return true;
+}
+
bool IRToProtobufConverter::AddEnumFields(abi_dump::EnumType *enum_protobuf,
const EnumTypeIR *enum_ir) {
for (auto &&field : enum_ir->GetFields()) {
abi_dump::EnumFieldDecl *enum_fieldp = enum_protobuf->add_enum_fields();
- if (!SetIRToProtobufEnumField(enum_fieldp, &field)) {
+ if (!ConvertEnumFieldIR(enum_fieldp, &field)) {
return false;
}
}
@@ -337,9 +411,7 @@ bool ProtobufIRDumper::AddElfFunctionIR(const ElfFunctionIR *elf_function) {
if (!added_elf_function) {
return false;
}
- added_elf_function->set_name(elf_function->GetName());
- added_elf_function->set_binding(
- ElfSymbolBindingIRToProtobuf(elf_function->GetBinding()));
+ *added_elf_function = ConvertElfFunctionIR(elf_function);
return true;
}
@@ -348,9 +420,7 @@ bool ProtobufIRDumper::AddElfObjectIR(const ElfObjectIR *elf_object) {
if (!added_elf_object) {
return false;
}
- added_elf_object->set_name(elf_object->GetName());
- added_elf_object->set_binding(
- ElfSymbolBindingIRToProtobuf(elf_object->GetBinding()));
+ *added_elf_object = ConvertElfObjectIR(elf_object);
return true;
}
diff --git a/vndk/tools/header-checker/src/repr/protobuf/ir_reader.cpp b/vndk/tools/header-checker/src/repr/protobuf/ir_reader.cpp
index 773571460..47c4f3761 100644
--- a/vndk/tools/header-checker/src/repr/protobuf/ir_reader.cpp
+++ b/vndk/tools/header-checker/src/repr/protobuf/ir_reader.cpp
@@ -22,6 +22,7 @@
#include <memory>
#include <google/protobuf/text_format.h>
+#include <llvm/Support/raw_ostream.h>
namespace header_checker {