From 967d7ef3c7f381d122057dc2ed45394e96f619f9 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 7 May 2026 11:00:37 +0200 Subject: [PATCH 1/7] [ci] Override MAUI Android target framework version MAUI net11.0 currently generates net11.0-android36.1, but dotnet/android now produces Android 37 packs only. Copy a checked-in Directory.Build.Override.props into the MAUI checkout so the integration leg packs net11.0-android37.0 until dotnet/maui updates net11.0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build-tools/automation/azure-pipelines.yaml | 7 +++++++ build-tools/automation/maui/Directory.Build.Override.props | 6 ++++++ 2 files changed, 13 insertions(+) create mode 100644 build-tools/automation/maui/Directory.Build.Override.props diff --git a/build-tools/automation/azure-pipelines.yaml b/build-tools/automation/azure-pipelines.yaml index 29836acff14..a9f8b5b21de 100644 --- a/build-tools/automation/azure-pipelines.yaml +++ b/build-tools/automation/azure-pipelines.yaml @@ -164,6 +164,13 @@ extends: -androidVersion $(ANDROID_PACK_VERSION) displayName: Update MAUI's Android dependency + - task: CopyFiles@2 + displayName: Override MAUI Android target framework version + inputs: + SourceFolder: $(Build.SourcesDirectory)/android/build-tools/automation/maui + Contents: Directory.Build.Override.props + TargetFolder: $(Build.SourcesDirectory)/maui + - task: DotNetCoreCLI@2 displayName: Update Android SDK band in Workloads.csproj inputs: diff --git a/build-tools/automation/maui/Directory.Build.Override.props b/build-tools/automation/maui/Directory.Build.Override.props new file mode 100644 index 00000000000..b65c1a6c898 --- /dev/null +++ b/build-tools/automation/maui/Directory.Build.Override.props @@ -0,0 +1,6 @@ + + + 37.0 + 37.0 + + From df8ce5879860003a7bb043c503f961eb5464f18f Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 7 May 2026 11:58:47 +0200 Subject: [PATCH 2/7] [ci] Apply MAUI Android 37 patch MAUI's Cake script deletes Directory.Build.Override.props when no platform switch is passed, so copying an override file does not survive until dotnet-pack. Apply a checked-in patch directly to the MAUI checkout instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build-tools/automation/azure-pipelines.yaml | 10 +++--- .../maui/Directory.Build.Override.props | 6 ---- .../maui/android37-target-framework.diff | 35 +++++++++++++++++++ 3 files changed, 39 insertions(+), 12 deletions(-) delete mode 100644 build-tools/automation/maui/Directory.Build.Override.props create mode 100644 build-tools/automation/maui/android37-target-framework.diff diff --git a/build-tools/automation/azure-pipelines.yaml b/build-tools/automation/azure-pipelines.yaml index a9f8b5b21de..f9685c8de71 100644 --- a/build-tools/automation/azure-pipelines.yaml +++ b/build-tools/automation/azure-pipelines.yaml @@ -164,12 +164,10 @@ extends: -androidVersion $(ANDROID_PACK_VERSION) displayName: Update MAUI's Android dependency - - task: CopyFiles@2 - displayName: Override MAUI Android target framework version - inputs: - SourceFolder: $(Build.SourcesDirectory)/android/build-tools/automation/maui - Contents: Directory.Build.Override.props - TargetFolder: $(Build.SourcesDirectory)/maui + - script: >- + git -C "$(Build.SourcesDirectory)/maui" + apply "$(Build.SourcesDirectory)/android/build-tools/automation/maui/android37-target-framework.diff" + displayName: Update MAUI Android target framework version - task: DotNetCoreCLI@2 displayName: Update Android SDK band in Workloads.csproj diff --git a/build-tools/automation/maui/Directory.Build.Override.props b/build-tools/automation/maui/Directory.Build.Override.props deleted file mode 100644 index b65c1a6c898..00000000000 --- a/build-tools/automation/maui/Directory.Build.Override.props +++ /dev/null @@ -1,6 +0,0 @@ - - - 37.0 - 37.0 - - diff --git a/build-tools/automation/maui/android37-target-framework.diff b/build-tools/automation/maui/android37-target-framework.diff new file mode 100644 index 00000000000..bc657d38e0f --- /dev/null +++ b/build-tools/automation/maui/android37-target-framework.diff @@ -0,0 +1,35 @@ +diff --git a/Directory.Build.props b/Directory.Build.props +index 35fb59d68a..338a393eb6 100644 +--- a/Directory.Build.props ++++ b/Directory.Build.props +@@ -162,13 +162,13 @@ + 26.2 + 26.2 + 26.2 +- 36.1 ++ 37.0 + + 26.2 + 26.2 + 26.2 + 26.2 +- 36.1 ++ 37.0 + 10.0.19041.0 + 10.0.20348.0 + 7.0 +diff --git a/eng/Versions.props b/eng/Versions.props +index b8590e25520..057bb5018a 100644 +--- a/eng/Versions.props ++++ b/eng/Versions.props +@@ -205,8 +205,8 @@ + + + +- +- ++ ++ + + + From a0c4e12de8d4015521bd6128017c6f6d27e8868a Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 8 May 2026 19:45:50 +0200 Subject: [PATCH 3/7] Drop NativeAOT libc++ link dependency Remove the explicit NativeAOT final-link dependency on libc++/libc++abi and keep the Android NativeAOT link guarded with -nostdlib++. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.Android.Sdk.NativeAOT.targets | 43 ++++--- .../Utilities/NativeRuntimeComponents.cs | 4 - src/native/clr/host/bridge-processing.cc | 84 ++++++++++---- src/native/clr/host/gc-bridge.cc | 45 ++++++-- src/native/clr/host/host-shared.cc | 10 +- .../clr/host/internal-pinvokes-shared.cc | 18 ++- src/native/clr/host/os-bridge.cc | 106 +++++++++++++----- .../include/host/bridge-processing-shared.hh | 22 ++-- .../clr/include/host/bridge-processing.hh | 11 -- src/native/clr/include/host/gc-bridge.hh | 26 +++-- .../clr/include/host/host-environment.hh | 16 ++- src/native/clr/include/host/os-bridge.hh | 2 +- .../include/runtime-base/android-system.hh | 33 ++++++ src/native/clr/include/runtime-base/util.hh | 66 ++++++----- src/native/clr/include/shared/log_types.hh | 47 ++++++++ .../clr/runtime-base/android-system-shared.cc | 20 +++- src/native/clr/runtime-base/logger.cc | 52 ++++++--- src/native/clr/runtime-base/util.cc | 17 ++- src/native/clr/shared/helpers.cc | 12 +- .../include/runtime-base/jni-wrappers.hh | 12 +- .../common/include/runtime-base/strings.hh | 42 ++++--- .../include/runtime-base/timing-internal.hh | 21 ++-- .../common/include/runtime-base/timing.hh | 22 ++-- src/native/common/include/shared/cpp-util.hh | 70 +++--------- src/native/nativeaot/host/CMakeLists.txt | 1 + .../nativeaot/host/bridge-processing.cc | 36 ++++-- src/native/nativeaot/host/cxx-shims.cc | 62 ++++++++++ src/native/nativeaot/host/host.cc | 19 +++- .../nativeaot/host/internal-pinvoke-stubs.cc | 7 +- .../include/host/bridge-processing.hh | 9 +- src/native/nativeaot/include/host/host.hh | 3 + 31 files changed, 644 insertions(+), 294 deletions(-) create mode 100644 src/native/nativeaot/host/cxx-shims.cc create mode 100644 src/native/nativeaot/include/host/host.hh diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets index a6b22f41b7d..03271b54a9e 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets @@ -67,6 +67,7 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. + false _AndroidBeforeIlcCompile; SetupOSSpecificProps; @@ -85,7 +86,8 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. <_NdkAbi Condition=" '$(RuntimeIdentifier)' == 'android-arm64' ">aarch64 <_NdkAbi Condition=" '$(RuntimeIdentifier)' == 'android-x64' ">x86_64 <_NDKApiLevel Condition=" '$(RuntimeIdentifier)' == 'android-arm64' ">$(AndroidNdkApiLevel_Arm64) - <_NDKApiLevel Condition=" '$(RuntimeIdentifier)' == 'android-x64' ">$(AndroidNdkAPiLevel_X64) + <_NDKApiLevel Condition=" '$(RuntimeIdentifier)' == 'android-x64' ">$(AndroidNdkApiLevel_X64) + <_NDKApiLevel Condition=" '$(_NDKApiLevel)' == '' ">21 <_NdkSysrootAbi>$(_NdkAbi)-linux-android <_NdkPrebuiltAbi Condition=" $([MSBuild]::IsOSPlatform('windows')) ">windows-x86_64 <_NdkPrebuiltAbi Condition=" $([MSBuild]::IsOSPlatform('osx')) ">darwin-x86_64 @@ -98,6 +100,7 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. <_NdkWrapperScriptExt Condition=" $([MSBuild]::IsOSPlatform('windows')) ">.cmd <_NdkSysrootDir>$(_AndroidNdkDirectory)toolchains/llvm/prebuilt/$(_NdkPrebuiltAbi)/sysroot/usr/lib/$(_NdkSysrootAbi)/ <_NdkBinDir>$(_AndroidNdkDirectory)toolchains/llvm/prebuilt/$(_NdkPrebuiltAbi)/bin/ + <_NdkToolPathPrefix Condition=" !$([MSBuild]::IsOSPlatform('windows')) ">$(_NdkBinDir) - $(_NdkAbi)-linux-android$(_NDKApiLevel)-clang$(_NdkWrapperScriptExt) - $(_NdkAbi)-linux-android$(_NDKApiLevel)-clang$(_NdkWrapperScriptExt) - llvm-objcopy + $(_NdkToolPathPrefix)$(_NdkAbi)-linux-android$(_NDKApiLevel)-clang$(_NdkWrapperScriptExt) + $(_NdkToolPathPrefix)$(_NdkAbi)-linux-android$(_NDKApiLevel)-clang$(_NdkWrapperScriptExt) + $(_NdkToolPathPrefix)llvm-objcopy - llvm-ar + $(_NdkToolPathPrefix)llvm-ar - + false + + + + + - <_NdkLibs Include="$(_NdkSysrootDir)libc++_static.a" /> - <_NdkLibs Include="$(_NdkSysrootDir)libc++abi.a" /> - + - + @@ -301,10 +308,11 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. --> <_AndroidNativeAotSharedLibrary>$(NativeOutputPath)$(NativeBinaryPrefix)$(TargetName).so + <_AndroidNativeAotLinkerResponseFile>$(NativeIntermediateOutputPath)android-nativeaot-link.rsp <_AndroidNativeAotLinkerArgs Include=""$(NativeObject)"" /> @@ -326,8 +334,9 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. - - + + + +#include + #include #include #include @@ -17,9 +20,10 @@ void BridgeProcessingShared::initialize_on_runtime_init (JNIEnv *env, jclass run abort_unless (GCUserPeer_class != nullptr && GCUserPeer_ctor != nullptr, "Failed to load mono.android.GCUserPeer!"); } -BridgeProcessingShared::BridgeProcessingShared (MarkCrossReferencesArgs *args) noexcept +BridgeProcessingShared::BridgeProcessingShared (MarkCrossReferencesArgs *args, const BridgeProcessingCallbacks *host_callbacks) noexcept : env{ OSBridge::ensure_jnienv () }, - cross_refs{ args } + cross_refs{ args }, + callbacks{ host_callbacks != nullptr ? *host_callbacks : BridgeProcessingCallbacks {} } { if (args == nullptr) [[unlikely]] { Helpers::abort_application (LOG_GC, "Cross references argument is a NULL pointer"sv); @@ -32,6 +36,16 @@ BridgeProcessingShared::BridgeProcessingShared (MarkCrossReferencesArgs *args) n if (args->CrossReferenceCount > 0 && args->CrossReferences == nullptr) [[unlikely]] { Helpers::abort_application (LOG_GC, "CrossReferences member of the cross references arguments structure is NULL"sv); } + + if (args->ComponentCount > 0) { + temporary_peers = static_cast(calloc (args->ComponentCount, sizeof (jobject))); + abort_unless (temporary_peers != nullptr, "Failed to allocate GC bridge temporary peer array"); + } +} + +BridgeProcessingShared::~BridgeProcessingShared () noexcept +{ + free (temporary_peers); } void BridgeProcessingShared::process () noexcept @@ -59,8 +73,11 @@ void BridgeProcessingShared::prepare_for_java_collection () noexcept } // With cross references processed, the temporary peer list can be released - for (const auto& [scc, temporary_peer] : temporary_peers) { - env->DeleteLocalRef (temporary_peer); + for (size_t i = 0; i < cross_refs->ComponentCount; i++) { + if (temporary_peers[i] != nullptr) { + env->DeleteLocalRef (temporary_peers[i]); + temporary_peers[i] = nullptr; + } } // Switch global to weak references @@ -79,7 +96,8 @@ void BridgeProcessingShared::prepare_scc_for_java_collection (size_t scc_index, { // Count == 0 case: Some SCCs might have no IGCUserPeers associated with them, so we must create one if (scc.Count == 0) { - temporary_peers [scc_index] = env->NewObject (GCUserPeer_class, GCUserPeer_ctor); + abort_unless (temporary_peers != nullptr, "Temporary peer array must be allocated"); + temporary_peers[scc_index] = env->NewObject (GCUserPeer_class, GCUserPeer_ctor); return; } @@ -98,9 +116,9 @@ CrossReferenceTarget BridgeProcessingShared::select_cross_reference_target (size const StronglyConnectedComponent &scc = cross_refs->Components [scc_index]; if (scc.Count == 0) { - const auto temporary_peer = temporary_peers.find (scc_index); - abort_unless (temporary_peer != temporary_peers.end(), "Temporary peer must be found in the map"); - return { .is_temporary_peer = true, .temporary_peer = temporary_peer->second }; + abort_unless (temporary_peers != nullptr, "Temporary peer array must be allocated"); + abort_unless (temporary_peers[scc_index] != nullptr, "Temporary peer must be found in the array"); + return { .is_temporary_peer = true, .temporary_peer = temporary_peers[scc_index] }; } abort_unless (scc.Contexts [0] != nullptr, "SCC must have at least one context"); @@ -219,6 +237,24 @@ void BridgeProcessingShared::clear_references (jobject handle) noexcept env->CallVoidMethod (handle, clear_method_id); } +bool BridgeProcessingShared::maybe_call_gc_user_peerable_add_managed_reference (JNIEnv *jni_env, jobject from, jobject to) noexcept +{ + if (callbacks.maybe_call_gc_user_peerable_add_managed_reference == nullptr) { + return false; + } + + return callbacks.maybe_call_gc_user_peerable_add_managed_reference (callbacks.context, jni_env, from, to); +} + +bool BridgeProcessingShared::maybe_call_gc_user_peerable_clear_managed_references (JNIEnv *jni_env, jobject handle) noexcept +{ + if (callbacks.maybe_call_gc_user_peerable_clear_managed_references == nullptr) { + return false; + } + + return callbacks.maybe_call_gc_user_peerable_clear_managed_references (callbacks.context, jni_env, handle); +} + void BridgeProcessingShared::take_global_ref (HandleContext &context) noexcept { abort_unless (context.control_block != nullptr, "Control block must not be null"); @@ -317,7 +353,7 @@ void CrossReferenceTarget::mark_refs_added_if_needed () noexcept [[gnu::always_inline]] void BridgeProcessingShared::log_missing_add_references_method ([[maybe_unused]] jclass java_class) noexcept { - log_error (LOG_DEFAULT, "Failed to find monodroidAddReferences method"); + log_write (LOG_DEFAULT, LogLevel::Error, "Failed to find monodroidAddReferences method"); #if DEBUG abort_if_invalid_pointer_argument (java_class, "java_class"); if (!Logger::gc_spew_enabled ()) [[likely]] { @@ -325,7 +361,9 @@ void BridgeProcessingShared::log_missing_add_references_method ([[maybe_unused]] } char *class_name = Host::get_java_class_name_for_TypeManager (java_class); - log_error (LOG_GC, "Missing monodroidAddReferences method for object of class {}", optional_string (class_name)); + char message[256]; + snprintf (message, sizeof (message), "Missing monodroidAddReferences method for object of class %s", optional_string (class_name)); + log_write (LOG_GC, LogLevel::Error, message); free (class_name); #endif } @@ -333,7 +371,7 @@ void BridgeProcessingShared::log_missing_add_references_method ([[maybe_unused]] [[gnu::always_inline]] void BridgeProcessingShared::log_missing_clear_references_method ([[maybe_unused]] jclass java_class) noexcept { - log_error (LOG_DEFAULT, "Failed to find monodroidClearReferences method"); + log_write (LOG_DEFAULT, LogLevel::Error, "Failed to find monodroidClearReferences method"); #if DEBUG abort_if_invalid_pointer_argument (java_class, "java_class"); if (!Logger::gc_spew_enabled ()) [[likely]] { @@ -341,7 +379,9 @@ void BridgeProcessingShared::log_missing_clear_references_method ([[maybe_unused } char *class_name = Host::get_java_class_name_for_TypeManager (java_class); - log_error (LOG_GC, "Missing monodroidClearReferences method for object of class {}", optional_string (class_name)); + char message[256]; + snprintf (message, sizeof (message), "Missing monodroidClearReferences method for object of class %s", optional_string (class_name)); + log_write (LOG_GC, LogLevel::Error, message); free (class_name); #endif } @@ -364,10 +404,9 @@ void BridgeProcessingShared::log_weak_to_gref (jobject weak, jobject handle) noe return; } - OSBridge::_monodroid_gref_log ( - std::format ("take_global_ref wref={:#x} -> handle={:#x}\n"sv, - reinterpret_cast (weak), - reinterpret_cast (handle)).data ()); + char message[128]; + snprintf (message, sizeof (message), "take_global_ref wref=%p -> handle=%p\n", reinterpret_cast(weak), reinterpret_cast(handle)); + OSBridge::_monodroid_gref_log (message); } [[gnu::always_inline]] @@ -377,8 +416,9 @@ void BridgeProcessingShared::log_weak_ref_collected (jobject weak) noexcept return; } - OSBridge::_monodroid_gref_log ( - std::format ("handle {:#x}/W; was collected by a Java GC"sv, reinterpret_cast (weak)).data ()); + char message[128]; + snprintf (message, sizeof (message), "handle %p/W; was collected by a Java GC", reinterpret_cast(weak)); + OSBridge::_monodroid_gref_log (message); } [[gnu::always_inline]] @@ -388,7 +428,9 @@ void BridgeProcessingShared::log_take_weak_global_ref (jobject handle) noexcept return; } - OSBridge::_monodroid_gref_log (std::format ("take_weak_global_ref handle={:#x}\n"sv, reinterpret_cast (handle)).data ()); + char message[128]; + snprintf (message, sizeof (message), "take_weak_global_ref handle=%p\n", reinterpret_cast(handle)); + OSBridge::_monodroid_gref_log (message); } [[gnu::always_inline]] @@ -449,5 +491,7 @@ void BridgeProcessingShared::log_gc_summary () noexcept } } - log_info (LOG_GC, "GC cleanup summary: {} objects tested - resurrecting {}.", total, alive); + char message[128]; + snprintf (message, sizeof (message), "GC cleanup summary: %zu objects tested - resurrecting %zu.", total, alive); + log_write (LOG_GC, LogLevel::Info, message); } diff --git a/src/native/clr/host/gc-bridge.cc b/src/native/clr/host/gc-bridge.cc index 6bf3a387e96..31cd005a826 100644 --- a/src/native/clr/host/gc-bridge.cc +++ b/src/native/clr/host/gc-bridge.cc @@ -1,3 +1,7 @@ +#include +#include +#include + #include #include #include @@ -45,7 +49,7 @@ void GCBridge::trigger_java_gc (JNIEnv *env) noexcept env->ExceptionDescribe (); env->ExceptionClear (); - log_error (LOG_DEFAULT, "Java GC failed"); + log_write (LOG_DEFAULT, LogLevel::Error, "Java GC failed"); } void GCBridge::mark_cross_references (MarkCrossReferencesArgs *args) noexcept @@ -55,8 +59,9 @@ void GCBridge::mark_cross_references (MarkCrossReferencesArgs *args) noexcept abort_unless (args->CrossReferences != nullptr || args->CrossReferenceCount == 0, "CrossReferences must not be null if CrossReferenceCount is greater than 0"); log_mark_cross_references_args_if_enabled (args); - shared_args.store (args); - shared_args_semaphore.release (); + __atomic_store_n (&shared_args, args, __ATOMIC_RELEASE); + int ret = sem_post (&shared_args_semaphore); + abort_unless (ret == 0, "Failed to release GC bridge semaphore"); } void GCBridge::bridge_processing () noexcept @@ -66,8 +71,13 @@ void GCBridge::bridge_processing () noexcept while (true) { // wait until mark cross references args are set by the GC callback - shared_args_semaphore.acquire (); - MarkCrossReferencesArgs *args = shared_args.load (); + int ret; + do { + ret = sem_wait (&shared_args_semaphore); + } while (ret == -1 && errno == EINTR); + abort_unless (ret == 0, "Failed to acquire GC bridge semaphore"); + + MarkCrossReferencesArgs *args = __atomic_load_n (&shared_args, __ATOMIC_ACQUIRE); bridge_processing_started_callback (args); @@ -78,6 +88,12 @@ void GCBridge::bridge_processing () noexcept } } +auto GCBridge::bridge_processing_thread_entry ([[maybe_unused]] void *arg) noexcept -> void* +{ + bridge_processing (); + return nullptr; +} + [[gnu::always_inline]] void GCBridge::log_mark_cross_references_args_if_enabled (MarkCrossReferencesArgs *args) noexcept { @@ -85,13 +101,16 @@ void GCBridge::log_mark_cross_references_args_if_enabled (MarkCrossReferencesArg return; } - log_info (LOG_GC, "cross references callback invoked with {} sccs and {} xrefs.", args->ComponentCount, args->CrossReferenceCount); + char message[128]; + snprintf (message, sizeof (message), "cross references callback invoked with %zu sccs and %zu xrefs.", args->ComponentCount, args->CrossReferenceCount); + log_write (LOG_GC, LogLevel::Info, message); JNIEnv *env = OSBridge::ensure_jnienv (); for (size_t i = 0; i < args->ComponentCount; ++i) { const StronglyConnectedComponent &scc = args->Components [i]; - log_info (LOG_GC, "group {} with {} objects", i, scc.Count); + snprintf (message, sizeof (message), "group %zu with %zu objects", i, scc.Count); + log_write (LOG_GC, LogLevel::Info, message); for (size_t j = 0; j < scc.Count; ++j) { log_handle_context (env, scc.Contexts [j]); } @@ -104,7 +123,9 @@ void GCBridge::log_mark_cross_references_args_if_enabled (MarkCrossReferencesArg for (size_t i = 0; i < args->CrossReferenceCount; ++i) { size_t source_index = args->CrossReferences [i].SourceGroupIndex; size_t dest_index = args->CrossReferences [i].DestinationGroupIndex; - log_info_nocheck_fmt (LOG_GC, "xref [{}] {} -> {}", i, source_index, dest_index); + char xref_message[128]; + snprintf (xref_message, sizeof (xref_message), "xref [%zu] %zu -> %zu", i, source_index, dest_index); + log_write (LOG_GC, LogLevel::Info, xref_message); } } @@ -118,9 +139,13 @@ void GCBridge::log_handle_context (JNIEnv *env, HandleContext *ctx) noexcept jclass java_class = env->GetObjectClass (handle); if (java_class != nullptr) { char *class_name = Host::get_java_class_name_for_TypeManager (java_class); - log_info (LOG_GC, "gref {:#x} [{}]", reinterpret_cast (handle), class_name); + char message[256]; + snprintf (message, sizeof (message), "gref %p [%s]", reinterpret_cast(handle), optional_string (class_name)); + log_write (LOG_GC, LogLevel::Info, message); free (class_name); } else { - log_info (LOG_GC, "gref {:#x} [unknown class]", reinterpret_cast (handle)); + char message[128]; + snprintf (message, sizeof (message), "gref %p [unknown class]", reinterpret_cast(handle)); + log_write (LOG_GC, LogLevel::Info, message); } } diff --git a/src/native/clr/host/host-shared.cc b/src/native/clr/host/host-shared.cc index 5015a7a7a34..97b24668524 100644 --- a/src/native/clr/host/host-shared.cc +++ b/src/native/clr/host/host-shared.cc @@ -1,3 +1,5 @@ +#include + #include #include @@ -12,13 +14,15 @@ auto HostCommon::get_java_class_name_for_TypeManager (jclass klass) noexcept -> JNIEnv *env = OSBridge::ensure_jnienv (); jstring name = reinterpret_cast (env->CallObjectMethod (klass, Class_getName)); if (name == nullptr) { - log_error (LOG_DEFAULT, "Failed to obtain Java class name for object at {:p}", reinterpret_cast(klass)); + char message[128]; + snprintf (message, sizeof (message), "Failed to obtain Java class name for object at %p", reinterpret_cast(klass)); + log_write (LOG_DEFAULT, LogLevel::Error, message); return nullptr; } const char *mutf8 = env->GetStringUTFChars (name, nullptr); if (mutf8 == nullptr) { - log_error (LOG_DEFAULT, "Failed to convert Java class name to UTF8 (out of memory?)"sv); + log_write (LOG_DEFAULT, LogLevel::Error, "Failed to convert Java class name to UTF8 (out of memory?)"); env->DeleteLocalRef (name); return nullptr; } @@ -34,4 +38,4 @@ auto HostCommon::get_java_class_name_for_TypeManager (jclass klass) noexcept -> } return ret; -} \ No newline at end of file +} diff --git a/src/native/clr/host/internal-pinvokes-shared.cc b/src/native/clr/host/internal-pinvokes-shared.cc index 813e6c64f90..e4fbc06c766 100644 --- a/src/native/clr/host/internal-pinvokes-shared.cc +++ b/src/native/clr/host/internal-pinvokes-shared.cc @@ -56,30 +56,36 @@ void monodroid_log (LogLevel level, LogCategories category, const char *message) switch (level) { case LogLevel::Verbose: case LogLevel::Debug: - log_debug_nocheck (category, std::string_view { message }); + if ((log_categories & category) != 0) { + log_write (category, LogLevel::Debug, message); + } break; case LogLevel::Info: - log_info_nocheck (category, std::string_view { message }); + if ((log_categories & category) != 0) { + log_write (category, LogLevel::Info, message); + } break; case LogLevel::Warn: case LogLevel::Silent: // warn is always printed - log_warn (category, std::string_view { message }); + log_write (category, LogLevel::Warn, message); break; case LogLevel::Error: - log_error (category, std::string_view { message }); + log_write (category, LogLevel::Error, message); break; case LogLevel::Fatal: - log_fatal (category, std::string_view { message }); + log_write (category, LogLevel::Fatal, message); break; default: case LogLevel::Unknown: case LogLevel::Default: - log_info_nocheck (category, std::string_view { message }); + if ((log_categories & category) != 0) { + log_write (category, LogLevel::Info, message); + } break; } } diff --git a/src/native/clr/host/os-bridge.cc b/src/native/clr/host/os-bridge.cc index cc2122e317e..2e04dadffef 100644 --- a/src/native/clr/host/os-bridge.cc +++ b/src/native/clr/host/os-bridge.cc @@ -1,4 +1,6 @@ -#include +#include +#include +#include #include #include @@ -8,6 +10,32 @@ using namespace xamarin::android; +namespace { + constexpr size_t LOG_LINE_BUFFER_SIZE = 512; + + void write_logcat_line (LogCategories category, const char *line, size_t line_len) noexcept + { + char local_buffer[LOG_LINE_BUFFER_SIZE]; + char *buffer = local_buffer; + + if (line_len >= sizeof (local_buffer)) { + buffer = static_cast(malloc (line_len + 1)); + if (buffer == nullptr) { + log_write (category, LogLevel::Debug, ""); + return; + } + } + + memcpy (buffer, line, line_len); + buffer[line_len] = '\0'; + log_write (category, LogLevel::Debug, buffer); + + if (buffer != local_buffer) { + free (buffer); + } + } +} + void OSBridge::initialize_on_onload (JavaVM *vm, JNIEnv *env) noexcept { abort_if_invalid_pointer_argument (env, "env"); @@ -90,38 +118,42 @@ auto OSBridge::_monodroid_weak_gref_dec () noexcept -> int void OSBridge::_write_stack_trace (FILE *to, const char *const from, LogCategories category) noexcept { if (from == nullptr) [[unlikely]] { - log_warn (category, "Unable to write stack trace, managed runtime passed a NULL string."); + log_write (category, LogLevel::Warn, "Unable to write stack trace, managed runtime passed a NULL string."); return; } - const std::string_view trace { from }; - if (trace.empty ()) [[unlikely]] { - log_warn (category, "Empty stack trace passed by the managed runtime."); + if (*from == '\0') [[unlikely]] { + log_write (category, LogLevel::Warn, "Empty stack trace passed by the managed runtime."); return; } - for (const auto segment : std::views::split (trace, '\n')) { - const std::string_view line { segment }; + const char *line = from; + while (line != nullptr && *line != '\0') { + const char *newline = strchr (line, '\n'); + size_t line_len = newline == nullptr ? strlen (line) : static_cast(newline - line); if ((category == LOG_GREF && Logger::gref_to_logcat ()) || (category == LOG_LREF && Logger::lref_to_logcat ())) { - log_debug (category, "{}"sv, line); + write_logcat_line (category, line, line_len); } - if (to == nullptr) { - continue; + if (to != nullptr) { + fwrite (line, sizeof (char), line_len, to); + fputc ('\n', to); + fflush (to); } - fwrite (line.data (), sizeof (std::string_view::value_type), line.length (), to); - fputc ('\n', to); - fflush (to); + if (newline == nullptr) { + break; + } + line = newline + 1; } } void OSBridge::_monodroid_gref_log (const char *message) noexcept { if (Logger::gref_to_logcat ()) { - log_debug (LOG_GREF, "{}"sv, optional_string (message)); + log_write (LOG_GREF, LogLevel::Debug, optional_string (message)); } if (Logger::gref_log () == nullptr) { @@ -133,7 +165,7 @@ void OSBridge::_monodroid_gref_log (const char *message) noexcept } [[gnu::always_inline, gnu::flatten]] -void OSBridge::log_it (LogCategories category, std::string const& line, FILE *to, const char *const from, bool logcat_enabled) noexcept +void OSBridge::log_it (LogCategories category, const char *line, FILE *to, const char *const from, bool logcat_enabled) noexcept { log_write (category, LogLevel::Info, line); @@ -146,7 +178,7 @@ void OSBridge::log_it (LogCategories category, std::string const& line, FILE *to return; } - fwrite (line.c_str (), sizeof (std::string::value_type), line.length (), to); + fwrite (line, sizeof (char), strlen (line), to); fputc ('\n', to); _write_stack_trace (to, from, category); @@ -161,8 +193,11 @@ auto OSBridge::_monodroid_gref_log_new (jobject curHandle, char curType, jobject } int wc = __atomic_load_n (&gc_weak_gref_count, __ATOMIC_RELAXED); - const std::string log_line = std::format ( - "+g+ grefc {} gwrefc {} obj-handle {:p}/{} -> new-handle {:p}/{} from thread '{}'({})"sv, + char log_line[LOG_LINE_BUFFER_SIZE]; + snprintf ( + log_line, + sizeof (log_line), + "+g+ grefc %d gwrefc %d obj-handle %p/%c -> new-handle %p/%c from thread '%s'(%d)", c, wc, reinterpret_cast(curHandle), @@ -185,8 +220,11 @@ void OSBridge::_monodroid_gref_log_delete (jobject handle, char type, const char } int wc = __atomic_load_n (&gc_weak_gref_count, __ATOMIC_RELAXED); - const std::string log_line = std::format ( - "-g- grefc {} gwrefc {} handle {:p}/{} from thread '{}'({})"sv, + char log_line[LOG_LINE_BUFFER_SIZE]; + snprintf ( + log_line, + sizeof (log_line), + "-g- grefc %d gwrefc %d handle %p/%c from thread '%s'(%d)", c, wc, reinterpret_cast(handle), @@ -206,8 +244,11 @@ void OSBridge::_monodroid_weak_gref_new (jobject curHandle, char curType, jobjec } int gc = __atomic_load_n (&gc_gref_count, __ATOMIC_RELAXED); - const std::string log_line = std::format ( - "+w+ grefc {} gwrefc {} obj-handle {:p}/{} -> new-handle {:p}/{} from thread '{}'({})"sv, + char log_line[LOG_LINE_BUFFER_SIZE]; + snprintf ( + log_line, + sizeof (log_line), + "+w+ grefc %d gwrefc %d obj-handle %p/%c -> new-handle %p/%c from thread '%s'(%d)", gc, c, reinterpret_cast(curHandle), @@ -228,8 +269,11 @@ OSBridge::_monodroid_lref_log_new (int lrefc, jobject handle, char type, const c return; } - const std::string log_line = std::format ( - "+l+ lrefc {} handle {:p}/{} from thread '{}'({})"sv, + char log_line[LOG_LINE_BUFFER_SIZE]; + snprintf ( + log_line, + sizeof (log_line), + "+l+ lrefc %d handle %p/%c from thread '%s'(%d)", lrefc, reinterpret_cast(handle), type, @@ -248,8 +292,11 @@ void OSBridge::_monodroid_weak_gref_delete (jobject handle, char type, const cha } int gc = __atomic_load_n (&gc_gref_count, __ATOMIC_RELAXED); - const std::string log_line = std::format ( - "-w- grefc {} gwrefc {} handle {:p}/{} from thread '{}'({})"sv, + char log_line[LOG_LINE_BUFFER_SIZE]; + snprintf ( + log_line, + sizeof (log_line), + "-w- grefc %d gwrefc %d handle %p/%c from thread '%s'(%d)", gc, c, reinterpret_cast(handle), @@ -267,8 +314,11 @@ void OSBridge::_monodroid_lref_log_delete (int lrefc, jobject handle, char type, return; } - const std::string log_line = std::format ( - "-l- lrefc {} handle {:p}/{} from thread '{}'({})"sv, + char log_line[LOG_LINE_BUFFER_SIZE]; + snprintf ( + log_line, + sizeof (log_line), + "-l- lrefc %d handle %p/%c from thread '%s'(%d)", lrefc, reinterpret_cast(handle), type, diff --git a/src/native/clr/include/host/bridge-processing-shared.hh b/src/native/clr/include/host/bridge-processing-shared.hh index 0e0a9a6244c..c9f55b4fc66 100644 --- a/src/native/clr/include/host/bridge-processing-shared.hh +++ b/src/native/clr/include/host/bridge-processing-shared.hh @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include @@ -20,16 +19,26 @@ struct CrossReferenceTarget void mark_refs_added_if_needed () noexcept; }; +struct BridgeProcessingCallbacks +{ + void *context; + bool (*maybe_call_gc_user_peerable_add_managed_reference) (void *context, JNIEnv *env, jobject from, jobject to) noexcept; + bool (*maybe_call_gc_user_peerable_clear_managed_references) (void *context, JNIEnv *env, jobject handle) noexcept; +}; + class BridgeProcessingShared { public: - explicit BridgeProcessingShared (MarkCrossReferencesArgs *args) noexcept; + explicit BridgeProcessingShared (MarkCrossReferencesArgs *args, const BridgeProcessingCallbacks *callbacks = nullptr) noexcept; + ~BridgeProcessingShared () noexcept; + static void initialize_on_runtime_init (JNIEnv *jniEnv, jclass runtimeClass) noexcept; void process () noexcept; private: JNIEnv* env; MarkCrossReferencesArgs *cross_refs; - std::unordered_map temporary_peers; + jobject *temporary_peers = nullptr; + BridgeProcessingCallbacks callbacks; static inline jclass GCUserPeer_class = nullptr; static inline jmethodID GCUserPeer_ctor = nullptr; @@ -50,6 +59,8 @@ private: void clear_references_if_needed (const HandleContext &context) noexcept; void clear_references (jobject handle) noexcept; + bool maybe_call_gc_user_peerable_add_managed_reference (JNIEnv *env, jobject from, jobject to) noexcept; + bool maybe_call_gc_user_peerable_clear_managed_references (JNIEnv *env, jobject handle) noexcept; void log_missing_add_references_method (jclass java_class) noexcept; void log_missing_clear_references_method (jclass java_class) noexcept; @@ -60,9 +71,4 @@ private: void log_gref_delete (jobject handle) noexcept; void log_weak_ref_delete (jobject weak) noexcept; void log_gc_summary () noexcept; - - // These methods must be implemented by every host individually - // Both methods below return `true` if they processed the call - virtual auto maybe_call_gc_user_peerable_add_managed_reference (JNIEnv *env, jobject from, jobject to) noexcept -> bool = 0; - virtual auto maybe_call_gc_user_peerable_clear_managed_references (JNIEnv *env, jobject handle) noexcept -> bool = 0; }; diff --git a/src/native/clr/include/host/bridge-processing.hh b/src/native/clr/include/host/bridge-processing.hh index 2d638c0e6ba..1a2ab64dd1d 100644 --- a/src/native/clr/include/host/bridge-processing.hh +++ b/src/native/clr/include/host/bridge-processing.hh @@ -10,15 +10,4 @@ public: explicit BridgeProcessing (MarkCrossReferencesArgs *args) noexcept : BridgeProcessingShared (args) {} - -private: - auto maybe_call_gc_user_peerable_add_managed_reference ([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject from, [[maybe_unused]] jobject to) noexcept -> bool override final - { - return false; // no-op for CoreCLR, we didn't process the call - } - - auto maybe_call_gc_user_peerable_clear_managed_references ([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject handle) noexcept -> bool override final - { - return false; // no-op for CoreCLR, we didn't process the call - } }; diff --git a/src/native/clr/include/host/gc-bridge.hh b/src/native/clr/include/host/gc-bridge.hh index 80c0ac68132..f9f4972e212 100644 --- a/src/native/clr/include/host/gc-bridge.hh +++ b/src/native/clr/include/host/gc-bridge.hh @@ -1,11 +1,9 @@ #pragma once -#include +#include +#include + #include -#include -#include -#include -#include #include @@ -72,8 +70,14 @@ namespace xamarin::android { GCBridge::bridge_processing_started_callback = bridge_processing_started; GCBridge::bridge_processing_finished_callback = bridge_processing_finished; - bridge_processing_thread = std::thread { GCBridge::bridge_processing }; - bridge_processing_thread.detach (); + int ret = sem_init (&shared_args_semaphore, 0, 0); + abort_unless (ret == 0, "Failed to initialize GC bridge semaphore"); + + ret = pthread_create (&bridge_processing_thread, nullptr, GCBridge::bridge_processing_thread_entry, nullptr); + abort_unless (ret == 0, "Failed to create GC bridge processing thread"); + + ret = pthread_detach (bridge_processing_thread); + abort_unless (ret == 0, "Failed to detach GC bridge processing thread"); return mark_cross_references; } @@ -81,10 +85,9 @@ namespace xamarin::android { static void trigger_java_gc (JNIEnv *env) noexcept; private: - static inline std::thread bridge_processing_thread {}; - - static inline std::binary_semaphore shared_args_semaphore{0}; - static inline std::atomic shared_args; + static inline pthread_t bridge_processing_thread {}; + static inline sem_t shared_args_semaphore {}; + static inline MarkCrossReferencesArgs *shared_args = nullptr; static inline jobject Runtime_instance = nullptr; static inline jmethodID Runtime_gc = nullptr; @@ -93,6 +96,7 @@ namespace xamarin::android { static inline BridgeProcessingFinishedFtn bridge_processing_finished_callback = nullptr; static void bridge_processing () noexcept; + static auto bridge_processing_thread_entry (void *arg) noexcept -> void*; static void mark_cross_references (MarkCrossReferencesArgs *args) noexcept; static void log_mark_cross_references_args_if_enabled (MarkCrossReferencesArgs *args) noexcept; diff --git a/src/native/clr/include/host/host-environment.hh b/src/native/clr/include/host/host-environment.hh index 2c4dc95252d..f1a157cf736 100644 --- a/src/native/clr/include/host/host-environment.hh +++ b/src/native/clr/include/host/host-environment.hh @@ -42,7 +42,11 @@ namespace xamarin::android { static void set_system_property (const char *name, const char *value) noexcept { // TODO: should we **actually** try to set the system property here? Would that even work? Needs testing - log_debug (LOG_DEFAULT, " System property {} = '{}'", optional_string (name), optional_string (value)); + if ((log_categories & LOG_DEFAULT) != 0) { + char message[512]; + snprintf (message, sizeof (message), " System property %s = '%s'", optional_string (name), optional_string (value)); + log_write (LOG_DEFAULT, LogLevel::Debug, message); + } } [[gnu::flatten, gnu::always_inline]] @@ -88,10 +92,16 @@ namespace xamarin::android { static_local_string dir (home_len + relative_path.length ()); Util::path_combine (dir, home.get_string_view (), relative_path); - log_debug (LOG_DEFAULT, "Creating XDG directory: {}"sv, optional_string (dir.get ())); + if ((log_categories & LOG_DEFAULT) != 0) { + char message[512]; + snprintf (message, sizeof (message), "Creating XDG directory: %s", optional_string (dir.get ())); + log_write (LOG_DEFAULT, LogLevel::Debug, message); + } int rv = Util::create_directory (dir.get (), Constants::DEFAULT_DIRECTORY_MODE); if (rv < 0 && errno != EEXIST) { - log_warn (LOG_DEFAULT, "Failed to create XDG directory {}. {}"sv, optional_string (dir.get ()), strerror (errno)); + char message[512]; + snprintf (message, sizeof (message), "Failed to create XDG directory %s. %s", optional_string (dir.get ()), strerror (errno)); + log_write (LOG_DEFAULT, LogLevel::Warn, message); } if (!environment_variable_name.empty ()) { diff --git a/src/native/clr/include/host/os-bridge.hh b/src/native/clr/include/host/os-bridge.hh index ee96c220671..2ba614fb78d 100644 --- a/src/native/clr/include/host/os-bridge.hh +++ b/src/native/clr/include/host/os-bridge.hh @@ -57,7 +57,7 @@ namespace xamarin::android { private: static void _write_stack_trace (FILE *to, const char *const from, LogCategories = LOG_NONE) noexcept; - static void log_it (LogCategories category, std::string const& line, FILE *to, const char *const from, bool logcat_enabled) noexcept; + static void log_it (LogCategories category, const char *line, FILE *to, const char *const from, bool logcat_enabled) noexcept; private: static inline JavaVM *jvm = nullptr; diff --git a/src/native/clr/include/runtime-base/android-system.hh b/src/native/clr/include/runtime-base/android-system.hh index b60871a93c4..a2c03962510 100644 --- a/src/native/clr/include/runtime-base/android-system.hh +++ b/src/native/clr/include/runtime-base/android-system.hh @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -18,6 +19,7 @@ struct BundledProperty; namespace xamarin::android { class AndroidSystem { +#if !defined(XA_HOST_NATIVEAOT) // This optimizes things a little bit. The array is allocated at build time, so we pay no cost for its // allocation and at run time it allows us to skip dynamic memory allocation. inline static std::array single_app_lib_directory{}; @@ -35,6 +37,7 @@ namespace xamarin::android { std::string_view { "x86_64" }, // CPU_KIND_X86_64 std::string_view { "riscv" }, // CPU_KIND_RISCV }; +#endif public: static auto get_gref_gc_threshold () noexcept -> long @@ -60,16 +63,28 @@ namespace xamarin::android { running_in_emulator = yesno; } +#if defined(XA_HOST_NATIVEAOT) + static auto get_primary_override_dir () noexcept -> const char* + { + return primary_override_dir; + } +#else static auto get_primary_override_dir () noexcept -> std::string const& { return primary_override_dir; } +#endif static void set_primary_override_dir (jstring_wrapper& home) noexcept { +#if defined(XA_HOST_NATIVEAOT) + determine_primary_override_dir (home, primary_override_dir, sizeof (primary_override_dir)); +#else primary_override_dir = determine_primary_override_dir (home); +#endif } +#if !defined(XA_HOST_NATIVEAOT) static auto get_native_libraries_dir () noexcept -> std::string const& { return native_libraries_dir; @@ -94,6 +109,7 @@ namespace xamarin::android { log_debug (LOG_DEFAULT, "Creating public update directory: `{}`", override_dir); Util::create_public_directory (override_dir); } +#endif static auto is_embedded_dso_mode_enabled () noexcept -> bool { @@ -129,6 +145,18 @@ namespace xamarin::android { embedded_dso_mode_enabled = yesno; } +#if defined(XA_HOST_NATIVEAOT) + static void determine_primary_override_dir (jstring_wrapper &home, char *buffer, size_t buffer_size) noexcept + { + dynamic_local_string name { home.get_cstr () }; + name.append ("/") + .append (Constants::OVERRIDE_DIRECTORY_NAME) + .append ("/") + .append (Constants::android_lib_abi); + + snprintf (buffer, buffer_size, "%s", name.get ()); + } +#else static auto determine_primary_override_dir (jstring_wrapper &home) noexcept -> std::string { dynamic_local_string name { home.get_cstr () }; @@ -139,16 +167,21 @@ namespace xamarin::android { return {name.get (), name.length ()}; } +#endif private: static inline long max_gref_count = 0; static inline bool running_in_emulator = false; static inline bool embedded_dso_mode_enabled = false; +#if defined(XA_HOST_NATIVEAOT) + static inline char primary_override_dir[SENSIBLE_PATH_MAX] {}; +#else static inline std::string primary_override_dir; static inline std::string native_libraries_dir; #if defined (DEBUG) static inline std::unordered_map bundled_properties; +#endif #endif }; } diff --git a/src/native/clr/include/runtime-base/util.hh b/src/native/clr/include/runtime-base/util.hh index 656a3d4a68a..3082eb90220 100644 --- a/src/native/clr/include/runtime-base/util.hh +++ b/src/native/clr/include/runtime-base/util.hh @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -139,7 +140,9 @@ namespace xamarin::android { { struct stat sbuf; if (fstatat (dirfd, file_name, &sbuf, 0) == -1) { - log_warn (LOG_ASSEMBLY, "Failed to stat file '{}': {}", file_name, std::strerror (errno)); + char message[512]; + snprintf (message, sizeof (message), "Failed to stat file '%s': %s", file_name, std::strerror (errno)); + log_write (LOG_ASSEMBLY, LogLevel::Warn, message); return std::nullopt; } @@ -154,9 +157,15 @@ namespace xamarin::android { [[gnu::flatten, gnu::always_inline]] static void set_environment_variable (const char *name, const char *value) noexcept { - log_debug (LOG_DEFAULT, "Setting environment variable {} = '{}'", optional_string (name), optional_string (value)); + if (should_log (LOG_DEFAULT)) { + char message[512]; + snprintf (message, sizeof (message), "Setting environment variable %s = '%s'", optional_string (name), optional_string (value)); + log_write (LOG_DEFAULT, LogLevel::Debug, message); + } if (::setenv (name, value, 1) < 0) { - log_warn (LOG_DEFAULT, "Failed to set environment variable '{}': {}", name, ::strerror (errno)); + char message[512]; + snprintf (message, sizeof (message), "Failed to set environment variable '%s': %s", optional_string (name), ::strerror (errno)); + log_write (LOG_DEFAULT, LogLevel::Warn, message); } } @@ -178,7 +187,9 @@ namespace xamarin::android { if (createDirectory) { int rv = create_directory (value.get_cstr (), mode); if (rv < 0 && errno != EEXIST) { - log_warn (LOG_DEFAULT, "Failed to create directory '{}' for environment variable '{}'. {}", value.get_string_view (), name, strerror (errno)); + char message[512]; + snprintf (message, sizeof (message), "Failed to create directory '%s' for environment variable '%s'. %s", optional_string (value.get_cstr ()), name.data (), strerror (errno)); + log_write (LOG_DEFAULT, LogLevel::Warn, message); } } set_environment_variable (name, value); @@ -208,33 +219,32 @@ namespace xamarin::android { mmap_info.area = mmap (nullptr, offsetSize, PROT_READ, MAP_PRIVATE, fd, static_cast(offsetPage)); if (mmap_info.area == MAP_FAILED) { - Helpers::abort_application ( - LOG_ASSEMBLY, - std::format ( - "Could not mmap APK fd {}: {}; File={}", - fd, - strerror (errno), - filename - ) - ); + char message[512]; + snprintf (message, sizeof (message), "Could not mmap APK fd %d: %s; File=%s", fd, strerror (errno), filename.data ()); + Helpers::abort_application (LOG_ASSEMBLY, message); } mmap_info.size = offsetSize; file_info.area = pointer_add (mmap_info.area, offsetFromPage); file_info.size = size; - log_info ( - LOG_ASSEMBLY, - " mmap_start: {:<8p}; mmap_end: {:<8p} mmap_len: {:<12} file_start: {:<8p} file_end: {:<8p} file_len: {:<12} apk descriptor: {} file: {}", - mmap_info.area, - pointer_add (mmap_info.area, mmap_info.size), - mmap_info.size, - file_info.area, - pointer_add (file_info.area, file_info.size), - file_info.size, - fd, - filename - ); + if (should_log (LOG_ASSEMBLY)) { + char message[512]; + snprintf ( + message, + sizeof (message), + " mmap_start: %-8p; mmap_end: %-8p mmap_len: %-12zu file_start: %-8p file_end: %-8p file_len: %-12zu apk descriptor: %d file: %s", + mmap_info.area, + pointer_add (mmap_info.area, mmap_info.size), + mmap_info.size, + file_info.area, + pointer_add (file_info.area, file_info.size), + file_info.size, + fd, + filename.data () + ); + log_write (LOG_ASSEMBLY, LogLevel::Info, message); + } return file_info; } @@ -256,7 +266,11 @@ namespace xamarin::android { elf_header->e_ident[EI_MAG1] != ELFMAG1 || elf_header->e_ident[EI_MAG2] != ELFMAG2 || elf_header->e_ident[EI_MAG3] != ELFMAG3) { - log_debug (LOG_ASSEMBLY, "Not an ELF image: {}", file_name); + if (should_log (LOG_ASSEMBLY)) { + char message[512]; + snprintf (message, sizeof (message), "Not an ELF image: %s", file_name.data ()); + log_write (LOG_ASSEMBLY, LogLevel::Debug, message); + } // Not an ELF image, just return what we mmapped before return { map_info.area, map_info.size }; } diff --git a/src/native/clr/include/shared/log_types.hh b/src/native/clr/include/shared/log_types.hh index 7aef6e20456..9ecef42a0ce 100644 --- a/src/native/clr/include/shared/log_types.hh +++ b/src/native/clr/include/shared/log_types.hh @@ -1,8 +1,11 @@ #pragma once #include +#if !defined(XA_HOST_NATIVEAOT) #include #include +#include +#endif #include #include "java-interop-logger.h" @@ -17,12 +20,22 @@ #undef log_info #endif +#if defined(XA_HOST_NATIVEAOT) +#define DO_LOG_FMT(_level, _category_, _fmt_, ...) \ + do { \ + static_assert (sizeof (#__VA_ARGS__) == 1, "NativeAOT logging must use preformatted messages"); \ + if ((log_categories & ((_category_))) != 0) { \ + ::log_ ## _level ## _nocheck ((_category_), _fmt_); \ + } \ + } while (0) +#else #define DO_LOG_FMT(_level, _category_, _fmt_, ...) \ do { \ if ((log_categories & ((_category_))) != 0) { \ ::log_ ## _level ## _nocheck_fmt ((_category_), _fmt_ __VA_OPT__(,) __VA_ARGS__); \ } \ } while (0) +#endif // // For std::format spec, see https://en.cppreference.com/w/cpp/utility/format/spec @@ -54,13 +67,16 @@ namespace xamarin::android { log_write (category, level, message.data ()); } +#if !defined(XA_HOST_NATIVEAOT) template [[gnu::always_inline]] static inline constexpr void log_write_fmt (LogCategories category, LogLevel level, std::format_string fmt, Args&& ...args) { log_write (category, level, std::format (fmt, std::forward(args)...).c_str ()); } +#endif } +#if !defined(XA_HOST_NATIVEAOT) template [[gnu::always_inline]] static inline constexpr void log_debug_nocheck_fmt (LogCategories category, std::format_string fmt, Args&& ...args) { @@ -120,5 +136,36 @@ static inline constexpr void log_fatal_fmt (LogCategories category, std::string_ { log_write (category, xamarin::android::LogLevel::Fatal, message.data ()); } +#else +[[gnu::always_inline]] +static inline constexpr void log_debug_nocheck (LogCategories category, std::string_view const& message) noexcept +{ + log_write (category, xamarin::android::LogLevel::Debug, message.data ()); +} + +[[gnu::always_inline]] +static inline constexpr void log_info_nocheck (LogCategories category, std::string_view const& message) noexcept +{ + log_write (category, xamarin::android::LogLevel::Info, message.data ()); +} + +[[gnu::always_inline]] +static inline constexpr void log_warn_fmt (LogCategories category, std::string_view const& message) noexcept +{ + log_write (category, xamarin::android::LogLevel::Warn, message.data ()); +} + +[[gnu::always_inline]] +static inline constexpr void log_error_fmt (LogCategories category, std::string_view const& message) noexcept +{ + log_write (category, xamarin::android::LogLevel::Error, message.data ()); +} + +[[gnu::always_inline]] +static inline constexpr void log_fatal_fmt (LogCategories category, std::string_view const& message) noexcept +{ + log_write (category, xamarin::android::LogLevel::Fatal, message.data ()); +} +#endif extern unsigned int log_categories; diff --git a/src/native/clr/runtime-base/android-system-shared.cc b/src/native/clr/runtime-base/android-system-shared.cc index 2aa1d54b001..c7ef49464ce 100644 --- a/src/native/clr/runtime-base/android-system-shared.cc +++ b/src/native/clr/runtime-base/android-system-shared.cc @@ -1,3 +1,6 @@ +#include +#include +#include #include #include @@ -36,15 +39,18 @@ AndroidSystem::monodroid__system_property_get (std::string_view const& name, cha char *buf = nullptr; if (sp_value_len < Constants::PROPERTY_VALUE_BUFFER_LEN) { size_t alloc_size = Helpers::add_with_overflow_check (Constants::PROPERTY_VALUE_BUFFER_LEN, 1uz); - log_warn (LOG_DEFAULT, "Buffer to store system property may be too small, will copy only {} bytes", sp_value_len); - buf = new char [alloc_size]; + char message[128]; + snprintf (message, sizeof (message), "Buffer to store system property may be too small, will copy only %zu bytes", sp_value_len); + log_write (LOG_DEFAULT, LogLevel::Warn, message); + buf = static_cast (std::malloc (alloc_size)); + abort_unless (buf != nullptr, "Failed to allocate system property buffer"); } int len = __system_property_get (name.data (), buf ? buf : sp_value); if (buf != nullptr) { strncpy (sp_value, buf, sp_value_len); sp_value [sp_value_len] = '\0'; - delete[] buf; + std::free (buf); } return len; @@ -81,10 +87,14 @@ AndroidSystem::get_max_gref_count_from_system () noexcept -> long } if (*e) { - log_warn (LOG_GC, "Unsupported '{}' value '{}'.", Constants::DEBUG_MONO_MAX_GREFC.data (), override.get ()); + char message[256]; + snprintf (message, sizeof (message), "Unsupported '%s' value '%s'.", Constants::DEBUG_MONO_MAX_GREFC.data (), override.get ()); + log_write (LOG_GC, LogLevel::Warn, message); } - log_warn (LOG_GC, "Overriding max JNI Global Reference count to {}", max); + char message[128]; + snprintf (message, sizeof (message), "Overriding max JNI Global Reference count to %ld", max); + log_write (LOG_GC, LogLevel::Warn, message); } return max; diff --git a/src/native/clr/runtime-base/logger.cc b/src/native/clr/runtime-base/logger.cc index a61b68d4a76..369947ee4d2 100644 --- a/src/native/clr/runtime-base/logger.cc +++ b/src/native/clr/runtime-base/logger.cc @@ -1,10 +1,10 @@ #include #include #include +#include #include #include #include -#include #include #include @@ -22,10 +22,17 @@ using namespace xamarin::android; using std::operator""sv; namespace { - std::string gref_file{}; - std::string lref_file{}; + char *gref_file = nullptr; + char *lref_file = nullptr; bool light_gref = false; bool light_lref = false; + + void set_log_file (char *&log_file, const char *path) noexcept + { + std::free (log_file); + log_file = path == nullptr ? nullptr : strdup (path); + abort_unless (path == nullptr || log_file != nullptr, "Failed to allocate reference log file path"); + } } [[gnu::always_inline]] @@ -52,7 +59,11 @@ auto Logger::open_file (LogCategories category, std::string_view const& custom_p { auto log_and_return = [&category](FILE *f, std::string_view const& path) -> FILE* { if (f != nullptr) { - log_debug (category, "Opened file '{}' for logging.", path); + if ((log_categories & category) != 0) { + char message[512]; + snprintf (message, sizeof (message), "Opened file '%s' for logging.", path.data ()); + log_write (category, LogLevel::Debug, message); + } } return f; }; @@ -62,28 +73,28 @@ auto Logger::open_file (LogCategories category, std::string_view const& custom_p return log_and_return (ret, custom_path); } - std::string p{}; Util::create_public_directory (override_dir); - p.assign (override_dir); - p.append ("/"); - p.append (fallback_filename); + dynamic_local_string p; + p.append (override_dir) + .append ("/") + .append (fallback_filename); - return log_and_return (open_file (p), p); + return log_and_return (open_file (p.get ()), p.get ()); } void Logger::init_reference_logging (std::string_view const& override_dir) noexcept { if ((log_categories & LOG_GREF) != 0 && !light_gref) { - _gref_log = open_file (LOG_GREF, gref_file, override_dir, "grefs.txt"sv); + _gref_log = open_file (LOG_GREF, gref_file == nullptr ? std::string_view {} : std::string_view { gref_file }, override_dir, "grefs.txt"sv); } if ((log_categories & LOG_LREF) != 0 && !light_lref) { // if both lref & gref have files specified, and they're the same path, reuse the FILE*. - if (!lref_file.empty () && strcmp (lref_file.c_str (), !gref_file.empty () ? gref_file.c_str () : "") == 0) { + if (lref_file != nullptr && strcmp (lref_file, gref_file != nullptr ? gref_file : "") == 0) { _lref_log = _gref_log; } else { - _lref_log = open_file (LOG_LREF, lref_file, override_dir, "lrefs.txt"sv); + _lref_log = open_file (LOG_LREF, lref_file == nullptr ? std::string_view {} : std::string_view { lref_file }, override_dir, "lrefs.txt"sv); } } } @@ -162,7 +173,18 @@ Logger::init_logging_categories () noexcept auto file_name = segment.at (offset); if (!file_name.has_value ()) { - log_warn (LOG_DEFAULT, "Unable to set path to {} log file: {}", file_kind, to_string (file_name.error ())); + char message[256]; + std::string_view error = to_string (file_name.error ()); + snprintf ( + message, + sizeof (message), + "Unable to set path to %.*s log file: %.*s", + static_cast(file_kind.length ()), + file_kind.data (), + static_cast(error.length ()), + error.data () + ); + log_write (LOG_DEFAULT, LogLevel::Warn, message); return nullptr; } @@ -171,7 +193,7 @@ Logger::init_logging_categories () noexcept constexpr std::string_view CAT_GREF_EQUALS { "gref=" }; if (set_category (CAT_GREF_EQUALS, param, LOG_GREF, true /* arg_starts_with_name */)) { - gref_file = get_log_file_name ("gref"sv, param, CAT_GREF_EQUALS.length ()); + set_log_file (gref_file, get_log_file_name ("gref"sv, param, CAT_GREF_EQUALS.length ())); continue; } @@ -187,7 +209,7 @@ Logger::init_logging_categories () noexcept constexpr std::string_view CAT_LREF_EQUALS { "lref=" }; if (set_category (CAT_LREF_EQUALS, param, LOG_LREF, true /* arg_starts_with_name */)) { - lref_file = get_log_file_name ("lref"sv, param, CAT_LREF_EQUALS.length ()); + set_log_file (lref_file, get_log_file_name ("lref"sv, param, CAT_LREF_EQUALS.length ())); continue; } diff --git a/src/native/clr/runtime-base/util.cc b/src/native/clr/runtime-base/util.cc index 8f59681a55f..0f1c8799376 100644 --- a/src/native/clr/runtime-base/util.cc +++ b/src/native/clr/runtime-base/util.cc @@ -1,4 +1,5 @@ #include +#include #include #include @@ -58,7 +59,9 @@ Util::create_public_directory (std::string_view const& dir) // Try to change the mode, just in case chmod (dir.data (), 0777); } else { - log_warn (LOG_DEFAULT, "Failed to create directory '{}'. {}"sv, dir, std::strerror (errno)); + char message[512]; + snprintf (message, sizeof (message), "Failed to create directory '%s'. %s", dir.data (), std::strerror (errno)); + log_write (LOG_DEFAULT, LogLevel::Warn, message); } } umask (m); @@ -72,7 +75,9 @@ Util::monodroid_fopen (std::string_view const& filename, std::string_view const& */ FILE *ret = fopen (filename.data (), mode.data ()); if (ret == nullptr) { - log_error (LOG_DEFAULT, "fopen failed for file {}: {}", filename, strerror (errno)); + char message[512]; + snprintf (message, sizeof (message), "fopen failed for file %s: %s", filename.data (), strerror (errno)); + log_write (LOG_DEFAULT, LogLevel::Error, message); return nullptr; } @@ -87,7 +92,9 @@ void Util::set_world_accessable (std::string_view const& path) } while (r == -1 && errno == EINTR); if (r == -1) { - log_error (LOG_DEFAULT, "chmod(\"{}\", 0664) failed: {}", path, strerror (errno)); + char message[512]; + snprintf (message, sizeof (message), "chmod(\"%s\", 0664) failed: %s", path.data (), strerror (errno)); + log_write (LOG_DEFAULT, LogLevel::Error, message); } } @@ -99,7 +106,9 @@ auto Util::set_world_accessible (int fd) noexcept -> bool } while (r == -1 && errno == EINTR); if (r == -1) { - log_error (LOG_DEFAULT, "fchmod() failed: {}"sv, strerror (errno)); + char message[128]; + snprintf (message, sizeof (message), "fchmod() failed: %s", strerror (errno)); + log_write (LOG_DEFAULT, LogLevel::Error, message); return false; } diff --git a/src/native/clr/shared/helpers.cc b/src/native/clr/shared/helpers.cc index 7850592af5a..69c0f6812b8 100644 --- a/src/native/clr/shared/helpers.cc +++ b/src/native/clr/shared/helpers.cc @@ -1,4 +1,5 @@ #include +#include #include #include @@ -11,7 +12,7 @@ using namespace xamarin::android; Helpers::abort_application (LogCategories category, const char *message, bool log_location, std::source_location sloc) noexcept { // Log it, but also... - log_fatal (category, "{}", message); + log_write (category, LogLevel::Fatal, message); // ...let android include it in the tombstone, debuggerd output, stack trace etc android_set_abort_message (message); @@ -33,14 +34,17 @@ Helpers::abort_application (LogCategories category, const char *message, bool lo } } - log_fatal ( - category, - "Abort at {}:{}:{} ('{}')", + char location_message[512]; + snprintf ( + location_message, + sizeof (location_message), + "Abort at %s:%u:%u ('%s')", file_name, sloc.line (), sloc.column (), sloc.function_name () ); + log_write (category, LogLevel::Fatal, location_message); } std::abort (); } diff --git a/src/native/common/include/runtime-base/jni-wrappers.hh b/src/native/common/include/runtime-base/jni-wrappers.hh index 679aed3df59..2978e6e1d33 100644 --- a/src/native/common/include/runtime-base/jni-wrappers.hh +++ b/src/native/common/include/runtime-base/jni-wrappers.hh @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -162,7 +163,11 @@ namespace xamarin::android if (_arr != nullptr) { len = static_cast(_env->GetArrayLength (_arr)); if (len > sizeof (static_wrappers) / sizeof (jstring_wrapper)) { - wrappers = new jstring_wrapper [len]; + wrappers = static_cast (std::malloc (len * sizeof (jstring_wrapper))); + abort_unless (wrappers != nullptr, "Failed to allocate jstring array wrappers"); + for (size_t i = 0; i < len; i++) { + ::new (static_cast(&wrappers [i])) jstring_wrapper (); + } } else { wrappers = static_wrappers; } @@ -175,7 +180,10 @@ namespace xamarin::android ~jstring_array_wrapper () noexcept { if (wrappers != nullptr && wrappers != static_wrappers) { - delete[] wrappers; + for (size_t i = 0; i < len; i++) { + wrappers [i].~jstring_wrapper (); + } + std::free (wrappers); } } diff --git a/src/native/common/include/runtime-base/strings.hh b/src/native/common/include/runtime-base/strings.hh index 8e6bf8cbaea..561b558d7ad 100644 --- a/src/native/common/include/runtime-base/strings.hh +++ b/src/native/common/include/runtime-base/strings.hh @@ -1,10 +1,11 @@ #pragma once -#include #include #include #include #include +#include +#include #include #include #include @@ -205,7 +206,9 @@ namespace xamarin::android { } if (!can_access (start_index)) { - log_error (LOG_DEFAULT, "Cannot convert string to integer, index {} is out of range", start_index); + char message[128]; + snprintf (message, sizeof (message), "Cannot convert string to integer, index %zu is out of range", start_index); + log_write (LOG_DEFAULT, LogLevel::Error, message); return false; } @@ -229,17 +232,23 @@ namespace xamarin::android { } if (out_of_range || errno == ERANGE) { - log_error (LOG_DEFAULT, "Value {} is out of range of this type ({}..{})", reinterpret_cast(s), static_cast(min), static_cast(max)); + char message[256]; + snprintf (message, sizeof (message), "Value %s is out of range of this type (%lld..%llu)", reinterpret_cast(s), static_cast(min), static_cast(max)); + log_write (LOG_DEFAULT, LogLevel::Error, message); return false; } if (endp == s) { - log_error (LOG_DEFAULT, "Value {} does not represent a base {} integer", reinterpret_cast(s), base); + char message[256]; + snprintf (message, sizeof (message), "Value %s does not represent a base %d integer", reinterpret_cast(s), base); + log_write (LOG_DEFAULT, LogLevel::Error, message); return false; } if (*endp != '\0') { - log_error (LOG_DEFAULT, "Value {} has non-numeric characters at the end", reinterpret_cast(s)); + char message[256]; + snprintf (message, sizeof (message), "Value %s has non-numeric characters at the end", reinterpret_cast(s)); + log_write (LOG_DEFAULT, LogLevel::Error, message); return false; } @@ -274,9 +283,6 @@ namespace xamarin::android { template class local_storage { - protected: - using LocalStoreArray = std::array; - public: static constexpr bool has_resize = HasResize; @@ -287,19 +293,19 @@ namespace xamarin::android { init_store (size < MaxStackSize ? MaxStackSize : size); } - virtual ~local_storage () + ~local_storage () { free_store (); } auto get () noexcept -> T* { - return allocated_store == nullptr ? local_store.data () : allocated_store; + return allocated_store == nullptr ? local_store : allocated_store; } auto get () const noexcept -> const T* { - return allocated_store == nullptr ? local_store.data () : allocated_store; + return allocated_store == nullptr ? local_store : allocated_store; } auto size () const noexcept -> size_t @@ -312,7 +318,8 @@ namespace xamarin::android { void init_store (size_t new_size) noexcept { if (new_size > MaxStackSize) { - allocated_store = new T[new_size]; + allocated_store = static_cast (std::malloc (new_size * sizeof (T))); + abort_unless (allocated_store != nullptr, "Failed to allocate local string storage"); } else { allocated_store = nullptr; } @@ -326,11 +333,12 @@ namespace xamarin::android { return; } - delete[] allocated_store; + std::free (allocated_store); + allocated_store = nullptr; } [[gnu::always_inline]] - auto get_local_store () noexcept -> LocalStoreArray& + auto get_local_store () noexcept -> T* { return local_store; } @@ -343,7 +351,7 @@ namespace xamarin::android { private: size_t store_size; - LocalStoreArray local_store; + T local_store[MaxStackSize]; T* allocated_store; }; @@ -416,11 +424,11 @@ namespace xamarin::android { T* new_allocated_store = base::get_allocated_store (); if (old_allocated_store != nullptr) { std::memcpy (new_allocated_store, old_allocated_store, old_size); - delete[] old_allocated_store; + std::free (old_allocated_store); return; } - std::memcpy (new_allocated_store, base::get_local_store ().data (), MaxStackSize); + std::memcpy (new_allocated_store, base::get_local_store (), MaxStackSize); } }; diff --git a/src/native/common/include/runtime-base/timing-internal.hh b/src/native/common/include/runtime-base/timing-internal.hh index 31099b86322..4d52be9c334 100644 --- a/src/native/common/include/runtime-base/timing-internal.hh +++ b/src/native/common/include/runtime-base/timing-internal.hh @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -260,7 +261,9 @@ namespace xamarin::android { // likely we'll run out of memory way, way, way before that happens size_t old_size = events.capacity (); events.reserve (old_size << 1); - log_warn (LOG_TIMING, "Reallocated timing event buffer from {} to {}"sv, old_size, events.size ()); + char message[128]; + snprintf (message, sizeof (message), "Reallocated timing event buffer from %zu to %zu", old_size, events.size ()); + log_write (LOG_TIMING, LogLevel::Warn, message); } } @@ -378,7 +381,9 @@ namespace xamarin::android { { struct timespec t; if (clock_gettime (CLOCK_MONOTONIC_RAW, &t) != 0) [[unlikely]] { - log_warn (LOG_TIMING, "clock_gettime failed for CLOCK_MONOTONIC_RAW: {}"sv, optional_string (strerror (errno))); + char message[128]; + snprintf (message, sizeof (message), "clock_gettime failed for CLOCK_MONOTONIC_RAW: %s", optional_string (strerror (errno))); + log_write (LOG_TIMING, LogLevel::Warn, message); return {}; // Results will be nonsensical, but no point in aborting the app } return time_point (chrono::seconds (t.tv_sec) + chrono::nanoseconds (t.tv_nsec)); @@ -494,11 +499,9 @@ namespace xamarin::android { return; } - log_warn ( - LOG_TIMING, - "Unknown event kind '{}' logged"sv, - static_cast>(kind) - ); + char warning[128]; + snprintf (warning, sizeof (warning), "Unknown event kind '%u' logged", static_cast(kind)); + log_write (LOG_TIMING, LogLevel::Warn, warning); append_desc ("unknown event kind"sv); } @@ -510,7 +513,9 @@ namespace xamarin::android { auto is_valid_event_index (size_t index, std::source_location sloc = std::source_location::current ()) const noexcept -> bool { if (index >= events.capacity ()) [[unlikely]] { - log_warn (LOG_TIMING, "Invalid event index passed to method '{}'"sv, sloc.function_name ()); + char message[256]; + snprintf (message, sizeof (message), "Invalid event index passed to method '%s'", sloc.function_name ()); + log_write (LOG_TIMING, LogLevel::Warn, message); return false; } diff --git a/src/native/common/include/runtime-base/timing.hh b/src/native/common/include/runtime-base/timing.hh index 6e883c240f8..d6d60246096 100644 --- a/src/native/common/include/runtime-base/timing.hh +++ b/src/native/common/include/runtime-base/timing.hh @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -83,17 +84,22 @@ namespace xamarin::android return; } - using namespace std::literals; auto interval = seq->end - seq->start; // nanoseconds - auto text = std::format ( - "{}; elapsed: {}:{}::{}"sv, - message == nullptr ? ""sv : message, - static_cast((std::chrono::duration_cast(interval).count ())), - static_cast((std::chrono::duration_cast(interval)).count ()), - static_cast((interval % 1ms).count ()) + auto seconds = static_cast((std::chrono::duration_cast(interval).count ())); + auto milliseconds = static_cast((std::chrono::duration_cast(interval)).count ()); + auto nanoseconds = static_cast((interval % std::chrono::milliseconds (1)).count ()); + char text[256]; + snprintf ( + text, + sizeof (text), + "%s; elapsed: %llu:%llu::%llu", + message == nullptr ? "" : message, + static_cast(seconds), + static_cast(milliseconds), + static_cast(nanoseconds) ); - log_write (LOG_TIMING, level, text.c_str ()); + log_write (LOG_TIMING, level, text); } private: diff --git a/src/native/common/include/shared/cpp-util.hh b/src/native/common/include/shared/cpp-util.hh index 6693ac69793..ebdc4d4d720 100644 --- a/src/native/common/include/shared/cpp-util.hh +++ b/src/native/common/include/shared/cpp-util.hh @@ -6,12 +6,9 @@ #include #include #include -#include #include -#include #include #include -#include #include #include @@ -33,49 +30,6 @@ namespace xamarin::android::detail { return ret == -1 ? "Out of memory" : message; } - [[gnu::always_inline]] - static inline std::string get_function_name (const char *signature) - { - using std::operator""sv; - - std::string_view sig { signature }; - if (sig.length () == 0) { - return ""; - } - - auto splitSignature = sig | std::views::split ("::"sv) | std::ranges::to> (); - - std::string ret; - if (splitSignature.size () > 1) { - ret.append (splitSignature [splitSignature.size () - 2]); - ret.append ("::"sv); - } - std::string_view func_name { splitSignature[splitSignature.size () - 1] }; - std::string_view::size_type args_pos = func_name.find ('('); - std::string_view::size_type name_start_pos = func_name.find (' '); - - if (name_start_pos == std::string_view::npos) { - name_start_pos = 0; - } else { - name_start_pos++; // point to after the space which separates return type from name - if (name_start_pos >= func_name.length ()) [[unlikely]] { - name_start_pos = 0; - } - } - - if (args_pos == std::string_view::npos) { - ret.append (func_name.substr (name_start_pos)); - } else { - // If there's a snafu with positions, start from 0 - if (name_start_pos >= args_pos || name_start_pos > func_name.length ()) [[unlikely]] { - name_start_pos = 0; - } - - ret.append (func_name.substr (name_start_pos, args_pos - name_start_pos)); - } - - return ret; - } } template F> @@ -110,11 +64,11 @@ abort_if_invalid_pointer_argument (T *ptr, const char *ptr_name, std::source_loc abort_unless ( ptr != nullptr, [&ptr_name, &sloc] { - return xamarin::android::detail::_format_message ( - "%s: parameter '%s' must be a valid pointer", - xamarin::android::detail::get_function_name (sloc.function_name ()).c_str (), - ptr_name - ); + return xamarin::android::detail::_format_message ( + "%s: parameter '%s' must be a valid pointer", + sloc.function_name (), + ptr_name + ); }, sloc ); @@ -127,11 +81,11 @@ abort_if_negative_integer_argument (int arg, const char *arg_name, std::source_l abort_unless ( arg > 0, [&arg_name, &sloc] { - return xamarin::android::detail::_format_message ( - "%s: parameter '%s' must be a positive integer", - xamarin::android::detail::get_function_name (sloc.function_name ()).c_str (), - arg_name - ); + return xamarin::android::detail::_format_message ( + "%s: parameter '%s' must be a positive integer", + sloc.function_name (), + arg_name + ); }, sloc ); @@ -142,7 +96,9 @@ abort_if_negative_integer_argument (int arg, const char *arg_name, std::source_l [[gnu::always_inline]] inline void pd_log_location (std::source_location sloc = std::source_location::current ()) noexcept { - log_warn (LOG_DEFAULT, "loc: {}:{} ('{}')", sloc.file_name (), sloc.line (), sloc.function_name ()); + char message[512]; + snprintf (message, sizeof (message), "loc: %s:%u ('%s')", sloc.file_name (), sloc.line (), sloc.function_name ()); + __android_log_write (ANDROID_LOG_WARN, "monodroid", message); } namespace xamarin::android diff --git a/src/native/nativeaot/host/CMakeLists.txt b/src/native/nativeaot/host/CMakeLists.txt index 9f4ce16fb25..e072da41620 100644 --- a/src/native/nativeaot/host/CMakeLists.txt +++ b/src/native/nativeaot/host/CMakeLists.txt @@ -28,6 +28,7 @@ set(CLR_SOURCES_PATH "../../clr") set(XAMARIN_MONODROID_SOURCES bridge-processing.cc + cxx-shims.cc host.cc host-environment.cc host-jni.cc diff --git a/src/native/nativeaot/host/bridge-processing.cc b/src/native/nativeaot/host/bridge-processing.cc index b87a49b431b..4b6d77deb8a 100644 --- a/src/native/nativeaot/host/bridge-processing.cc +++ b/src/native/nativeaot/host/bridge-processing.cc @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -6,13 +6,23 @@ using namespace xamarin::android; +const BridgeProcessingCallbacks BridgeProcessing::bridge_processing_callbacks { + .context = nullptr, + .maybe_call_gc_user_peerable_add_managed_reference = &BridgeProcessing::maybe_call_gc_user_peerable_add_managed_reference, + .maybe_call_gc_user_peerable_clear_managed_references = &BridgeProcessing::maybe_call_gc_user_peerable_clear_managed_references, +}; + +BridgeProcessing::BridgeProcessing (MarkCrossReferencesArgs *args) noexcept + : BridgeProcessingShared (args, &bridge_processing_callbacks) +{} + void BridgeProcessing::naot_initialize_on_runtime_init (JNIEnv *env) noexcept { GCUserPeerable_class = env->FindClass ("net/dot/jni/GCUserPeerable"); if (GCUserPeerable_class == nullptr) [[unlikely]] { Helpers::abort_application ( LOG_DEFAULT, - "Failed to find net/dot/jni/GCUserPeerable class while initializing GC bridge processing."sv + "Failed to find net/dot/jni/GCUserPeerable class while initializing GC bridge processing." ); } @@ -21,21 +31,25 @@ void BridgeProcessing::naot_initialize_on_runtime_init (JNIEnv *env) noexcept GCUserPeerable_jiClearManagedReferences = env->GetMethodID (GCUserPeerable_class, "jiClearManagedReferences", "()V"); if (GCUserPeerable_jiAddManagedReference == nullptr || GCUserPeerable_jiClearManagedReferences == nullptr) [[unlikely]] { - constexpr auto ABSENT = "absent"sv; - constexpr auto PRESENT = "present"sv; + constexpr char ABSENT[] = "absent"; + constexpr char PRESENT[] = "present"; + char message[128]; + snprintf ( + message, + sizeof (message), + "Failed to find GCUserPeerable method(s): jiAddManagedReference (%s); jiClearManagedReferences (%s)", + GCUserPeerable_jiAddManagedReference == nullptr ? ABSENT : PRESENT, + GCUserPeerable_jiClearManagedReferences == nullptr ? ABSENT : PRESENT + ); Helpers::abort_application ( LOG_DEFAULT, - std::format ( - "Failed to find GCUserPeerable method(s): jiAddManagedReference ({}); jiClearManagedReferences ({})"sv, - GCUserPeerable_jiAddManagedReference == nullptr ? ABSENT : PRESENT, - GCUserPeerable_jiClearManagedReferences == nullptr ? ABSENT : PRESENT - ) + message ); } } -auto BridgeProcessing::maybe_call_gc_user_peerable_add_managed_reference (JNIEnv *env, jobject from, jobject to) noexcept -> bool +bool BridgeProcessing::maybe_call_gc_user_peerable_add_managed_reference ([[maybe_unused]] void *context, JNIEnv *env, jobject from, jobject to) noexcept { if (!env->IsInstanceOf (from, GCUserPeerable_class)) { return false; @@ -45,7 +59,7 @@ auto BridgeProcessing::maybe_call_gc_user_peerable_add_managed_reference (JNIEnv return true; } -auto BridgeProcessing::maybe_call_gc_user_peerable_clear_managed_references (JNIEnv *env, jobject handle) noexcept -> bool +bool BridgeProcessing::maybe_call_gc_user_peerable_clear_managed_references ([[maybe_unused]] void *context, JNIEnv *env, jobject handle) noexcept { if (!env->IsInstanceOf (handle, GCUserPeerable_class)) { return false; diff --git a/src/native/nativeaot/host/cxx-shims.cc b/src/native/nativeaot/host/cxx-shims.cc new file mode 100644 index 00000000000..79868018251 --- /dev/null +++ b/src/native/nativeaot/host/cxx-shims.cc @@ -0,0 +1,62 @@ +#include +#include + +#include + +namespace std { + const nothrow_t nothrow {}; +} + +static void *allocate (size_t size) noexcept +{ + return malloc (size == 0 ? 1 : size); +} + +static void *allocate_or_abort (size_t size) noexcept +{ + void *ret = allocate (size); + if (ret == nullptr) { + abort (); + } + return ret; +} + +void *operator new (size_t size) +{ + return allocate_or_abort (size); +} + +void *operator new[] (size_t size) +{ + return allocate_or_abort (size); +} + +void *operator new (size_t size, std::nothrow_t const&) noexcept +{ + return allocate (size); +} + +void *operator new[] (size_t size, std::nothrow_t const&) noexcept +{ + return allocate (size); +} + +void operator delete (void *ptr) noexcept +{ + free (ptr); +} + +void operator delete[] (void *ptr) noexcept +{ + free (ptr); +} + +void operator delete (void *ptr, size_t) noexcept +{ + free (ptr); +} + +void operator delete[] (void *ptr, size_t) noexcept +{ + free (ptr); +} diff --git a/src/native/nativeaot/host/host.cc b/src/native/nativeaot/host/host.cc index bf43f0f47de..bfb20678b0a 100644 --- a/src/native/nativeaot/host/host.cc +++ b/src/native/nativeaot/host/host.cc @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -33,12 +35,17 @@ auto HostCommon::Java_JNI_OnLoad (JavaVM *vm, void *reserved) noexcept -> jint if (__jni_on_load_handler_count > 0) { for (uint32_t i = 0; i < __jni_on_load_handler_count; i++) { - log_debug ( - LOG_ASSEMBLY, - "Calling JNI on-load init func '{}' ({:p})", - optional_string (__jni_on_load_handler_names[i]), - reinterpret_cast(__jni_on_load_handlers[i]) - ); + if ((log_categories & LOG_ASSEMBLY) != 0) { + char message[256]; + snprintf ( + message, + sizeof (message), + "Calling JNI on-load init func '%s' (%p)", + optional_string (__jni_on_load_handler_names[i]), + reinterpret_cast(__jni_on_load_handlers[i]) + ); + log_write (LOG_ASSEMBLY, LogLevel::Debug, message); + } __jni_on_load_handlers[i] (vm, reserved); } } diff --git a/src/native/nativeaot/host/internal-pinvoke-stubs.cc b/src/native/nativeaot/host/internal-pinvoke-stubs.cc index 1c59786a9b1..b33316c1e4f 100644 --- a/src/native/nativeaot/host/internal-pinvoke-stubs.cc +++ b/src/native/nativeaot/host/internal-pinvoke-stubs.cc @@ -5,13 +5,12 @@ using namespace xamarin::android; namespace { [[gnu::noreturn]] - void pinvoke_unreachable (std::source_location sloc = std::source_location::current ()) + void pinvoke_unreachable () { Helpers::abort_application ( LOG_DEFAULT, - "The p/invoke is not implemented. This is a stub and should not be called."sv, - true, // log_location - sloc + "The p/invoke is not implemented. This is a stub and should not be called.", + false // log_location ); } } diff --git a/src/native/nativeaot/include/host/bridge-processing.hh b/src/native/nativeaot/include/host/bridge-processing.hh index 17f533aae0b..9e473107520 100644 --- a/src/native/nativeaot/include/host/bridge-processing.hh +++ b/src/native/nativeaot/include/host/bridge-processing.hh @@ -7,17 +7,16 @@ class BridgeProcessing final : public BridgeProcessingShared { public: - explicit BridgeProcessing (MarkCrossReferencesArgs *args) noexcept - : BridgeProcessingShared (args) - {} + explicit BridgeProcessing (MarkCrossReferencesArgs *args) noexcept; static void naot_initialize_on_runtime_init (JNIEnv *env) noexcept; private: - auto maybe_call_gc_user_peerable_add_managed_reference (JNIEnv *env, jobject from, jobject to) noexcept -> bool override final; - auto maybe_call_gc_user_peerable_clear_managed_references (JNIEnv *env, jobject handle) noexcept -> bool override final; + static bool maybe_call_gc_user_peerable_add_managed_reference ([[maybe_unused]] void *context, JNIEnv *env, jobject from, jobject to) noexcept; + static bool maybe_call_gc_user_peerable_clear_managed_references ([[maybe_unused]] void *context, JNIEnv *env, jobject handle) noexcept; private: + static const BridgeProcessingCallbacks bridge_processing_callbacks; static inline jclass GCUserPeerable_class = nullptr; static inline jmethodID GCUserPeerable_jiAddManagedReference = nullptr; static inline jmethodID GCUserPeerable_jiClearManagedReferences = nullptr; diff --git a/src/native/nativeaot/include/host/host.hh b/src/native/nativeaot/include/host/host.hh new file mode 100644 index 00000000000..c582b598bd4 --- /dev/null +++ b/src/native/nativeaot/include/host/host.hh @@ -0,0 +1,3 @@ +#pragma once + +#include "host-nativeaot.hh" From 165cbb9fa85431f6d4db4a2001c62acffde139e9 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Sat, 9 May 2026 01:16:08 +0200 Subject: [PATCH 4/7] Fix NativeAOT trimmable startup exploration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- external/Java.Interop | 2 +- .../JavaInteropRuntime.cs | 25 +++++++++- ...Microsoft.Android.Runtime.NativeAOT.csproj | 4 ++ .../Android.Runtime/AndroidRuntimeInternal.cs | 4 +- src/Mono.Android/Mono.Android.csproj | 1 + .../Microsoft.Android.Sdk.NativeAOT.targets | 6 +++ ...id.Sdk.TypeMap.Trimmable.NativeAOT.targets | 16 +++++-- src/native/clr/host/bridge-processing.cc | 48 ++++++++++++++----- src/native/clr/host/os-bridge.cc | 42 ++++++++-------- .../include/host/bridge-processing-shared.hh | 16 +++++-- .../include/runtime-base/android-system.hh | 1 + .../common/include/runtime-base/strings.hh | 1 + .../nativeaot/host/bridge-processing.cc | 5 +- .../include/host/bridge-processing.hh | 4 +- 14 files changed, 119 insertions(+), 56 deletions(-) diff --git a/external/Java.Interop b/external/Java.Interop index 4abc52e3386..61b30bce4b9 160000 --- a/external/Java.Interop +++ b/external/Java.Interop @@ -1 +1 @@ -Subproject commit 4abc52e338643b747d59cf3880669917989d1b5e +Subproject commit 61b30bce4b96ff9d77e3c76cf6a3f63a6eaade97 diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs index 47bc0e0a673..e28bbdebfdc 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs @@ -61,6 +61,7 @@ static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader, IntPtr langua var settings = new DiagnosticSettings (); settings.AddDebugDotnetLog (); + InitializeTrimmableTypeMapData (); var typeManager = CreateTypeManager (); var options = new NativeAotRuntimeOptions { @@ -75,11 +76,14 @@ static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader, IntPtr langua // Entry point into Mono.Android.dll. Log categories are initialized in JNI_OnLoad. JNIEnvInit.InitializeJniRuntime (runtime, initArgs); + RegisterTrimmableTypeMapNativeMethods (); transition = new JniTransition (jnienv); - var handler = Java.Lang.Thread.DefaultUncaughtExceptionHandler; - Java.Lang.Thread.DefaultUncaughtExceptionHandler = new UncaughtExceptionMarshaler (handler); + if (!RuntimeFeature.TrimmableTypeMap) { + var handler = Java.Lang.Thread.DefaultUncaughtExceptionHandler; + Java.Lang.Thread.DefaultUncaughtExceptionHandler = new UncaughtExceptionMarshaler (handler); + } } catch (Exception e) { AndroidLog.Print (AndroidLogLevel.Error, "JavaInteropRuntime", $"JavaInteropRuntime.init: error: {e}"); @@ -96,4 +100,21 @@ static JniRuntime.JniTypeManager CreateTypeManager () return new ManagedTypeManager (); } + + // Separate method so non-trimmable builds don't try to resolve TypeMapLoader + // from the generated _Microsoft.Android.TypeMaps.dll. + [MethodImpl (MethodImplOptions.NoInlining)] + static void InitializeTrimmableTypeMapData () + { + if (RuntimeFeature.TrimmableTypeMap) { + TypeMapLoader.Initialize (); + } + } + + static void RegisterTrimmableTypeMapNativeMethods () + { + if (RuntimeFeature.TrimmableTypeMap) { + TrimmableTypeMap.RegisterNativeMethods (); + } + } } diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Microsoft.Android.Runtime.NativeAOT.csproj b/src/Microsoft.Android.Runtime.NativeAOT/Microsoft.Android.Runtime.NativeAOT.csproj index 44ab97c6194..1bf541edf09 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Microsoft.Android.Runtime.NativeAOT.csproj +++ b/src/Microsoft.Android.Runtime.NativeAOT/Microsoft.Android.Runtime.NativeAOT.csproj @@ -18,6 +18,10 @@ + + false + all + diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs b/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs index 73d20f6397b..57c9ca7d1d9 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs @@ -17,10 +17,8 @@ static AndroidRuntimeInternal () { if (RuntimeFeature.IsMonoRuntime) { mono_unhandled_exception = MonoUnhandledException; - } else if (RuntimeFeature.IsCoreClrRuntime) { - mono_unhandled_exception = CoreClrUnhandledException; } else { - throw new NotSupportedException ("Internal error: unknown runtime not supported"); + mono_unhandled_exception = CoreClrUnhandledException; } } diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 89bf52ac3b1..e4a833e83ea 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -361,6 +361,7 @@ + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets index 03271b54a9e..a960836497f 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets @@ -16,6 +16,7 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. <_AndroidRuntimePackRuntime>NativeAOT <_AndroidJcwCodegenTarget Condition=" '$(_AndroidJcwCodegenTarget)' == '' ">JavaInterop1 <_AndroidTypeMapImplementation Condition=" '$(_AndroidTypeMapImplementation)' == '' ">managed + <_AndroidEnableObjectReferenceLogging Condition=" '$(_AndroidEnableObjectReferenceLogging)' == '' ">false true @@ -202,6 +203,11 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. + + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets index cc96c228bc7..f1c9a984156 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets @@ -6,15 +6,21 @@ <_TrimmableRuntimeProviderJavaName Condition=" '$(_TrimmableRuntimeProviderJavaName)' == '' ">net.dot.jni.nativeaot.NativeAotRuntimeProvider - + AfterTargets="_AndroidComputeIlcCompileInputs" + BeforeTargets="WriteIlcRspFileForCompilation" + Condition=" '$(_AndroidTypeMapImplementation)' == 'trimmable' "> + + _$(AssemblyName).TypeMap + - - + <_TrimmableTypeMapAssembliesForIlc Include="$(_TypeMapOutputDirectory)*.dll" /> + + + diff --git a/src/native/clr/host/bridge-processing.cc b/src/native/clr/host/bridge-processing.cc index 586c33d8394..26e5431a547 100644 --- a/src/native/clr/host/bridge-processing.cc +++ b/src/native/clr/host/bridge-processing.cc @@ -38,7 +38,7 @@ BridgeProcessingShared::BridgeProcessingShared (MarkCrossReferencesArgs *args, c } if (args->ComponentCount > 0) { - temporary_peers = static_cast(calloc (args->ComponentCount, sizeof (jobject))); + temporary_peers = static_cast(calloc (args->ComponentCount, sizeof (TemporaryPeer))); abort_unless (temporary_peers != nullptr, "Failed to allocate GC bridge temporary peer array"); } } @@ -73,12 +73,11 @@ void BridgeProcessingShared::prepare_for_java_collection () noexcept } // With cross references processed, the temporary peer list can be released - for (size_t i = 0; i < cross_refs->ComponentCount; i++) { - if (temporary_peers[i] != nullptr) { - env->DeleteLocalRef (temporary_peers[i]); - temporary_peers[i] = nullptr; - } + for (size_t i = 0; i < temporary_peer_count; i++) { + env->DeleteLocalRef (temporary_peers [i].peer); + temporary_peers [i].peer = nullptr; } + temporary_peer_count = 0; // Switch global to weak references for (size_t i = 0; i < cross_refs->ComponentCount; i++) { @@ -96,8 +95,7 @@ void BridgeProcessingShared::prepare_scc_for_java_collection (size_t scc_index, { // Count == 0 case: Some SCCs might have no IGCUserPeers associated with them, so we must create one if (scc.Count == 0) { - abort_unless (temporary_peers != nullptr, "Temporary peer array must be allocated"); - temporary_peers[scc_index] = env->NewObject (GCUserPeer_class, GCUserPeer_ctor); + add_temporary_peer (scc_index, env->NewObject (GCUserPeer_class, GCUserPeer_ctor)); return; } @@ -113,18 +111,42 @@ void BridgeProcessingShared::prepare_scc_for_java_collection (size_t scc_index, CrossReferenceTarget BridgeProcessingShared::select_cross_reference_target (size_t scc_index) noexcept { + abort_unless (scc_index < cross_refs->ComponentCount, "SCC index must be within the component array"); const StronglyConnectedComponent &scc = cross_refs->Components [scc_index]; if (scc.Count == 0) { - abort_unless (temporary_peers != nullptr, "Temporary peer array must be allocated"); - abort_unless (temporary_peers[scc_index] != nullptr, "Temporary peer must be found in the array"); - return { .is_temporary_peer = true, .temporary_peer = temporary_peers[scc_index] }; + jobject temporary_peer = find_temporary_peer (scc_index); + abort_unless (temporary_peer != nullptr, "Temporary peer must be found for the SCC"); + return { .is_temporary_peer = true, .temporary_peer = temporary_peer }; } abort_unless (scc.Contexts [0] != nullptr, "SCC must have at least one context"); return { .is_temporary_peer = false, .context = scc.Contexts [0] }; } +void BridgeProcessingShared::add_temporary_peer (size_t scc_index, jobject peer) noexcept +{ + abort_unless (temporary_peers != nullptr, "Temporary peer array must be allocated"); + abort_unless (temporary_peer_count < cross_refs->ComponentCount, "Temporary peer array must have capacity"); + abort_unless (peer != nullptr, "Temporary peer must not be null"); + + temporary_peers [temporary_peer_count++] = { + .scc_index = scc_index, + .peer = peer, + }; +} + +jobject BridgeProcessingShared::find_temporary_peer (size_t scc_index) noexcept +{ + for (size_t i = 0; i < temporary_peer_count; i++) { + if (temporary_peers [i].scc_index == scc_index) { + return temporary_peers [i].peer; + } + } + + return nullptr; +} + // caller must ensure that scc.Count > 1 void BridgeProcessingShared::add_circular_references (const StronglyConnectedComponent &scc) noexcept { @@ -243,7 +265,7 @@ bool BridgeProcessingShared::maybe_call_gc_user_peerable_add_managed_reference ( return false; } - return callbacks.maybe_call_gc_user_peerable_add_managed_reference (callbacks.context, jni_env, from, to); + return callbacks.maybe_call_gc_user_peerable_add_managed_reference (jni_env, from, to); } bool BridgeProcessingShared::maybe_call_gc_user_peerable_clear_managed_references (JNIEnv *jni_env, jobject handle) noexcept @@ -252,7 +274,7 @@ bool BridgeProcessingShared::maybe_call_gc_user_peerable_clear_managed_reference return false; } - return callbacks.maybe_call_gc_user_peerable_clear_managed_references (callbacks.context, jni_env, handle); + return callbacks.maybe_call_gc_user_peerable_clear_managed_references (jni_env, handle); } void BridgeProcessingShared::take_global_ref (HandleContext &context) noexcept diff --git a/src/native/clr/host/os-bridge.cc b/src/native/clr/host/os-bridge.cc index 2e04dadffef..317865e6ded 100644 --- a/src/native/clr/host/os-bridge.cc +++ b/src/native/clr/host/os-bridge.cc @@ -12,28 +12,6 @@ using namespace xamarin::android; namespace { constexpr size_t LOG_LINE_BUFFER_SIZE = 512; - - void write_logcat_line (LogCategories category, const char *line, size_t line_len) noexcept - { - char local_buffer[LOG_LINE_BUFFER_SIZE]; - char *buffer = local_buffer; - - if (line_len >= sizeof (local_buffer)) { - buffer = static_cast(malloc (line_len + 1)); - if (buffer == nullptr) { - log_write (category, LogLevel::Debug, ""); - return; - } - } - - memcpy (buffer, line, line_len); - buffer[line_len] = '\0'; - log_write (category, LogLevel::Debug, buffer); - - if (buffer != local_buffer) { - free (buffer); - } - } } void OSBridge::initialize_on_onload (JavaVM *vm, JNIEnv *env) noexcept @@ -134,7 +112,25 @@ void OSBridge::_write_stack_trace (FILE *to, const char *const from, LogCategori if ((category == LOG_GREF && Logger::gref_to_logcat ()) || (category == LOG_LREF && Logger::lref_to_logcat ())) { - write_logcat_line (category, line, line_len); + char local_buffer[LOG_LINE_BUFFER_SIZE]; + char *message = local_buffer; + + if (line_len >= sizeof (local_buffer)) { + message = static_cast(malloc (line_len + 1)); + if (message == nullptr) { + log_write (category, LogLevel::Debug, ""); + } + } + + if (message != nullptr) { + memcpy (message, line, line_len); + message [line_len] = '\0'; + log_write (category, LogLevel::Debug, message); + } + + if (message != nullptr && message != local_buffer) { + free (message); + } } if (to != nullptr) { diff --git a/src/native/clr/include/host/bridge-processing-shared.hh b/src/native/clr/include/host/bridge-processing-shared.hh index c9f55b4fc66..eed736f9534 100644 --- a/src/native/clr/include/host/bridge-processing-shared.hh +++ b/src/native/clr/include/host/bridge-processing-shared.hh @@ -21,9 +21,14 @@ struct CrossReferenceTarget struct BridgeProcessingCallbacks { - void *context; - bool (*maybe_call_gc_user_peerable_add_managed_reference) (void *context, JNIEnv *env, jobject from, jobject to) noexcept; - bool (*maybe_call_gc_user_peerable_clear_managed_references) (void *context, JNIEnv *env, jobject handle) noexcept; + bool (*maybe_call_gc_user_peerable_add_managed_reference) (JNIEnv *env, jobject from, jobject to) noexcept; + bool (*maybe_call_gc_user_peerable_clear_managed_references) (JNIEnv *env, jobject handle) noexcept; +}; + +struct TemporaryPeer +{ + size_t scc_index; + jobject peer; }; class BridgeProcessingShared @@ -37,7 +42,8 @@ public: private: JNIEnv* env; MarkCrossReferencesArgs *cross_refs; - jobject *temporary_peers = nullptr; + TemporaryPeer *temporary_peers = nullptr; + size_t temporary_peer_count = 0; BridgeProcessingCallbacks callbacks; static inline jclass GCUserPeer_class = nullptr; @@ -50,6 +56,8 @@ private: void add_circular_references (const StronglyConnectedComponent &scc) noexcept; void add_cross_reference (size_t source_index, size_t dest_index) noexcept; CrossReferenceTarget select_cross_reference_target (size_t scc_index) noexcept; + void add_temporary_peer (size_t scc_index, jobject peer) noexcept; + jobject find_temporary_peer (size_t scc_index) noexcept; bool add_reference (jobject from, jobject to) noexcept; void cleanup_after_java_collection () noexcept; diff --git a/src/native/clr/include/runtime-base/android-system.hh b/src/native/clr/include/runtime-base/android-system.hh index a2c03962510..0289b9bfbc0 100644 --- a/src/native/clr/include/runtime-base/android-system.hh +++ b/src/native/clr/include/runtime-base/android-system.hh @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include diff --git a/src/native/common/include/runtime-base/strings.hh b/src/native/common/include/runtime-base/strings.hh index 561b558d7ad..25d3912c2ab 100644 --- a/src/native/common/include/runtime-base/strings.hh +++ b/src/native/common/include/runtime-base/strings.hh @@ -12,6 +12,7 @@ #include #include +#include #if defined(XA_HOST_MONOVM) #include diff --git a/src/native/nativeaot/host/bridge-processing.cc b/src/native/nativeaot/host/bridge-processing.cc index 4b6d77deb8a..98995ab60ed 100644 --- a/src/native/nativeaot/host/bridge-processing.cc +++ b/src/native/nativeaot/host/bridge-processing.cc @@ -7,7 +7,6 @@ using namespace xamarin::android; const BridgeProcessingCallbacks BridgeProcessing::bridge_processing_callbacks { - .context = nullptr, .maybe_call_gc_user_peerable_add_managed_reference = &BridgeProcessing::maybe_call_gc_user_peerable_add_managed_reference, .maybe_call_gc_user_peerable_clear_managed_references = &BridgeProcessing::maybe_call_gc_user_peerable_clear_managed_references, }; @@ -49,7 +48,7 @@ void BridgeProcessing::naot_initialize_on_runtime_init (JNIEnv *env) noexcept } } -bool BridgeProcessing::maybe_call_gc_user_peerable_add_managed_reference ([[maybe_unused]] void *context, JNIEnv *env, jobject from, jobject to) noexcept +bool BridgeProcessing::maybe_call_gc_user_peerable_add_managed_reference (JNIEnv *env, jobject from, jobject to) noexcept { if (!env->IsInstanceOf (from, GCUserPeerable_class)) { return false; @@ -59,7 +58,7 @@ bool BridgeProcessing::maybe_call_gc_user_peerable_add_managed_reference ([[mayb return true; } -bool BridgeProcessing::maybe_call_gc_user_peerable_clear_managed_references ([[maybe_unused]] void *context, JNIEnv *env, jobject handle) noexcept +bool BridgeProcessing::maybe_call_gc_user_peerable_clear_managed_references (JNIEnv *env, jobject handle) noexcept { if (!env->IsInstanceOf (handle, GCUserPeerable_class)) { return false; diff --git a/src/native/nativeaot/include/host/bridge-processing.hh b/src/native/nativeaot/include/host/bridge-processing.hh index 9e473107520..80aed8dad3a 100644 --- a/src/native/nativeaot/include/host/bridge-processing.hh +++ b/src/native/nativeaot/include/host/bridge-processing.hh @@ -12,8 +12,8 @@ public: static void naot_initialize_on_runtime_init (JNIEnv *env) noexcept; private: - static bool maybe_call_gc_user_peerable_add_managed_reference ([[maybe_unused]] void *context, JNIEnv *env, jobject from, jobject to) noexcept; - static bool maybe_call_gc_user_peerable_clear_managed_references ([[maybe_unused]] void *context, JNIEnv *env, jobject handle) noexcept; + static bool maybe_call_gc_user_peerable_add_managed_reference (JNIEnv *env, jobject from, jobject to) noexcept; + static bool maybe_call_gc_user_peerable_clear_managed_references (JNIEnv *env, jobject handle) noexcept; private: static const BridgeProcessingCallbacks bridge_processing_callbacks; From 469e265c050dea5b64a59e06151475185110b40f Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 11 May 2026 10:47:17 +0200 Subject: [PATCH 5/7] [NativeAOT] Restore libc++ drop on top of merged NativeLinker The merge of origin/main brought in PR #11256, which switched native linking from an inline `clang` Exec invocation to the LinkNativeAotSharedLibrary task driving `ld.lld` directly. Taking origin/main's version of Microsoft.Android.Sdk.NativeAOT.targets re-introduced the explicit `libc++_static.a` and `libc++abi.a` includes in @(_NdkLibs), reversing the branch's purpose. Drop those includes. With ld.lld invoked directly, no C++ stdlib is linked unless we list it explicitly, so `-nostdlib++` (a clang driver flag) is not needed. cxx-shims.cc continues to provide the minimal C++ runtime shims required by NativeAOT. Also update the now-stale `LinkStandardCPlusPlusLibrary>false` and `libstdc++compat.a` removal comments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../targets/Microsoft.Android.Sdk.NativeAOT.targets | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets index 6138e7e2e8e..8c57e739209 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets @@ -130,7 +130,8 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. correctly when cross-compiling. --> llvm-ar - + false - <_NdkLibs Include="$(_NdkSysrootDir)libc++_static.a" /> - <_NdkLibs Include="$(_NdkSysrootDir)libc++abi.a" /> - - + From 3f3bc97cb4d7d86dab876269d92dd72b41e92064 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Tue, 12 May 2026 16:15:04 +0200 Subject: [PATCH 6/7] [NativeAOT] Make array typemap anchors public NativeAOT roots array typemap dictionaries from the root typemap assembly, which references the per-assembly __ArrayMapRank* anchor types. Keep those anchors public so the generated metadata is valid across assembly boundaries. Add test coverage for the emitted visibility. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Generator/TypeMapAssemblyEmitter.cs | 8 +++++--- .../Generator/TypeMapModelBuilderTests.cs | 10 +++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 4e74b6e3e54..1cce109d948 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -292,9 +292,11 @@ void EmitAnchorType () } /// - /// Emits private __ArrayMapRank{N} classes used as group type parameters + /// Emits public __ArrayMapRank{N} classes used as group type parameters /// for array TypeMap<T> entries. Each per-assembly typemap owns its - /// rank anchors so array maps stay scoped to the generated assembly. + /// rank anchors so array maps stay scoped to the generated assembly. The root + /// typemap assembly references these anchors, so NativeAOT requires them to be + /// visible across assembly boundaries. /// void EmitRankSentinels (TypeMapAssemblyData model) { @@ -307,7 +309,7 @@ void EmitRankSentinels (TypeMapAssemblyData model) _pe.Metadata.GetOrAddString ("System"), _pe.Metadata.GetOrAddString ("Object")); for (int i = 0; i < model.MaxArrayRank; i++) { _rankAnchorHandles [i] = _pe.Metadata.AddTypeDefinition ( - TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.Class, + TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class, default, _pe.Metadata.GetOrAddString ($"__ArrayMapRank{i + 1}"), objectRef, diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs index ee29afa9466..29eb367c39b 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs @@ -1022,7 +1022,7 @@ public void Build_EmitArrayEntries_MultiplePeers_GetIndependentTrios () public class ArrayEntriesPeBlob { [Fact] - public void FullPipeline_ArrayEntries_DefinesPrivateRankAnchors () + public void FullPipeline_ArrayEntries_DefinesPublicRankAnchors () { var peer = MakeMcwPeer ("foo/Bar", "Foo.Bar", "App"); var outputPath = Path.Combine (Path.GetTempPath (), "ArrSentinels.dll"); @@ -1037,6 +1037,14 @@ public void FullPipeline_ArrayEntries_DefinesPrivateRankAnchors () Assert.Contains ("__ArrayMapRank2", typeDefNames); Assert.Contains ("__ArrayMapRank3", typeDefNames); + var rankTypeDefs = reader.TypeDefinitions + .Select (h => reader.GetTypeDefinition (h)) + .Where (t => reader.GetString (t.Name).StartsWith ("__ArrayMapRank", StringComparison.Ordinal)) + .ToList (); + Assert.All (rankTypeDefs, t => Assert.Equal ( + System.Reflection.TypeAttributes.Public, + t.Attributes & System.Reflection.TypeAttributes.VisibilityMask)); + var rankTypeRefs = reader.TypeReferences .Select (h => reader.GetTypeReference (h)) .Where (t => reader.GetString (t.Name).StartsWith ("__ArrayMapRank", StringComparison.Ordinal)) From 6e8b48a6cccb807bcb81483e14838433a9f732ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 May 2026 19:59:50 +0000 Subject: [PATCH 7/7] Merge main and resolve PR conflicts Agent-Logs-Url: https://github.com/dotnet/android/sessions/5629277d-381f-4db6-a6eb-b68c9031e546 Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> --- .external | 2 +- .github/agents/agentic-workflows.agent.md | 80 +- .github/aw/actions-lock.json | 17 +- .github/skills/android-reviewer/SKILL.md | 3 +- .../references/csharp-rules.md | 2 +- .../references/msbuild-rules.md | 2 +- .../skills/tests/references/test-catalog.md | 6 +- .github/workflows/agentics-maintenance.yml | 582 +++++++ .github/workflows/android-reviewer.lock.yml | 400 +++-- .github/workflows/android-reviewer.md | 3 +- .github/workflows/copilot-setup-steps.yml | 2 +- .github/workflows/nightly-fix-finder.lock.yml | 1502 +++++++++++++++++ .github/workflows/nightly-fix-finder.md | 323 ++++ Configuration.props | 37 +- Documentation/building/configuration.md | 17 - Documentation/building/unix/dependencies.md | 13 +- Documentation/docs-mobile/TOC.yml | 6 - .../msbuild-reference/build-items.md | 1 + .../docs-mobile/building-apps/build-items.md | 3 + .../building-apps/build-properties.md | 53 +- Documentation/docs-mobile/messages/index.md | 16 +- Documentation/docs-mobile/messages/xa1031.md | 28 - Documentation/docs-mobile/messages/xa1032.md | 22 - Documentation/docs-mobile/messages/xa1033.md | 22 - Documentation/docs-mobile/messages/xa1045.md | 32 + Documentation/docs-mobile/messages/xa1046.md | 38 + Documentation/docs-mobile/messages/xa1047.md | 50 + Documentation/docs-mobile/messages/xa3008.md | 26 + Documentation/docs-mobile/messages/xa4217.md | 29 + Documentation/docs-mobile/messages/xa4227.md | 29 + Documentation/docs-mobile/messages/xa4252.md | 43 + Documentation/docs-mobile/messages/xa4253.md | 28 + Documentation/docs-mobile/messages/xa4312.md | 7 +- Documentation/docs-mobile/messages/xa4316.md | 25 + Documentation/docs-mobile/messages/xa4317.md | 31 + Documentation/docs-mobile/messages/xa4318.md | 26 + Documentation/docs-mobile/messages/xa5303.md | 23 + .../Properties/Resources.resx.lcl | 66 +- .../Properties/Resources.resx.lcl | 66 +- .../Properties/Resources.resx.lcl | 66 +- .../Properties/Resources.resx.lcl | 66 +- .../Properties/Resources.resx.lcl | 66 +- .../Properties/Resources.resx.lcl | 66 +- .../Properties/Resources.resx.lcl | 66 +- .../Properties/Resources.resx.lcl | 66 +- .../Properties/Resources.resx.lcl | 66 +- .../Properties/Resources.resx.lcl | 66 +- .../Properties/Resources.resx.lcl | 66 +- .../Properties/Resources.resx.lcl | 66 +- .../Properties/Resources.resx.lcl | 66 +- Makefile | 26 - NuGet.config | 2 + Xamarin.Android.sln | 21 + .../CreateFrameworkList.cs | 73 - .../GenerateMonoDroidIncludes.cs | 74 - .../GenerateProfile.cs | 54 - .../GenerateSupportedPlatforms.cs | 10 - .../automation/azure-pipelines-nightly.yaml | 2 +- .../automation/azure-pipelines-public.yaml | 12 +- .../yaml-templates/build-macos-steps.yaml | 6 + .../yaml-templates/setup-jdk-variables.yaml | 9 +- .../setup-test-environment-public.yaml | 4 +- .../setup-test-environment-steps.yaml | 25 +- .../setup-test-environment.yaml | 2 - .../stage-msbuild-emulator-tests.yaml | 1 - .../yaml-templates/stage-package-tests.yaml | 2 + .../Microsoft.Android.Runtime.proj | 22 +- .../create-packs/Microsoft.Android.Sdk.proj | 2 + build-tools/scripts/DotNet.targets | 11 +- build-tools/scripts/Ndk.projitems.in | 4 - build-tools/scripts/XABuildConfig.cs.in | 6 - build-tools/scripts/XAVersionInfo.targets | 1 - .../AcceptAndroidSdkLicenses.cs | 40 - .../CheckoutExternalGitDependency.cs | 89 - .../CreateFilePaths.cs | 81 - .../DownloadUri.cs | 164 -- .../GitCommitTime.cs | 46 - .../GitDiff.cs | 30 - .../HashFileContents.cs | 119 -- .../NDKInfo.cs | 91 - .../ParseExternalGitDependencies.cs | 70 - .../PrepareInstall.cs | 129 -- .../RunParallelTargets.cs | 69 - .../SetEnvironmentVariable.cs | 27 - .../SystemUnzip.cs | 186 -- .../xaprepare/Application/Context.cs | 5 - .../xaprepare/Application/KnownConditions.cs | 9 +- .../xaprepare/Application/KnownProperties.cs | 11 - .../Application/MonoPkgProgram.MacOS.cs | 75 - .../Application/Properties.Defaults.cs.in | 11 - .../ConfigAndData/BuildAndroidPlatforms.cs | 1 - .../ConfigAndData/Configurables.Linux.cs | 7 - .../ConfigAndData/Configurables.MacOS.cs | 3 - .../ConfigAndData/Configurables.Unix.cs | 3 - .../ConfigAndData/Configurables.Windows.cs | 9 - .../xaprepare/ConfigAndData/Configurables.cs | 36 +- .../Dependencies/AndroidToolchain.cs | 3 +- .../ConfigAndData/Dependencies/MacOS.cs | 9 - .../xaprepare/OperatingSystems/OS.cs | 5 +- .../Scenario_AndroidTestDependencies.cs | 3 +- .../Scenario_EmulatorTestDependencies.cs | 2 +- .../xaprepare/Scenarios/Scenario_Standard.cs | 3 - .../Scenarios/Scenario_UpdateMono.Unix.cs | 33 - .../xaprepare/Steps/Step_Android_SDK_NDK.cs | 56 - .../xaprepare/Steps/Step_GenerateFiles.cs | 8 - .../Steps/Step_Get_Android_BuildTools.cs | 72 - .../Steps/Step_Get_Windows_Binutils.cs | 712 -------- .../Steps/Step_InstallAdoptOpenJDK.Linux.cs | 14 - .../Steps/Step_InstallAdoptOpenJDK.MacOS.cs | 21 - .../Steps/Step_InstallAdoptOpenJDK.Unix.cs | 14 - .../Steps/Step_InstallAdoptOpenJDK.Windows.cs | 44 - .../Steps/Step_InstallAdoptOpenJDK.cs | 296 ---- .../Steps/Step_InstallDotNetPreview.cs | 113 +- .../Steps/Step_InstallGNUBinutils.Linux.cs | 7 - .../Steps/Step_InstallGNUBinutils.MacOS.cs | 7 - .../Steps/Step_InstallGNUBinutils.Unix.cs | 7 - .../Steps/Step_InstallGNUBinutils.Windows.cs | 8 - .../Steps/Step_InstallGNUBinutils.cs | 236 --- .../xaprepare/xaprepare/xaprepare.csproj | 3 +- .../xaprepare/xaprepare/xaprepare.targets | 10 - eng/Version.Details.xml | 32 +- eng/Versions.props | 16 +- samples/NativeAOT/NativeAOT.csproj | 2 +- .../AndroidTestAdapter.cs | 48 +- .../Microsoft.Android.Run.csproj | 1 + src/Microsoft.Android.Run/Program.cs | 10 + .../JavaInteropRuntime.cs | 41 +- .../MarkJavaObjects.cs | 34 +- .../Generator/ExportMethodDispatchEmitter.cs | 609 +++++++ .../ExportMethodDispatchEmitterContext.cs | 228 +++ .../Generator/JcwJavaSourceGenerator.cs | 7 +- .../Generator/JniSignatureHelper.cs | 5 +- .../Generator/ManifestGenerator.cs | 2 +- .../Generator/Model/TypeMapAssemblyData.cs | 84 +- .../Generator/ModelBuilder.cs | 49 +- .../Generator/PEAssemblyBuilder.cs | 307 +++- .../Generator/RootTypeMapAssemblyGenerator.cs | 164 +- .../Generator/TypeMapAssemblyEmitter.cs | 550 ++++-- .../ITrimmableTypeMapLogger.cs | 1 + ...rosoft.Android.Sdk.TrimmableTypeMap.csproj | 3 + .../Scanner/AssemblyIndex.cs | 86 + .../Scanner/ExportParameterKindInfo.cs | 14 + .../Scanner/JavaPeerInfo.cs | 50 +- .../Scanner/JavaPeerScanner.cs | 524 +++++- .../Scanner/MetadataTypeNameResolver.cs | 32 + .../Scanner/ScannerHashingHelper.cs | 84 + .../Scanner/SignatureTypeProvider.cs | 64 + .../TrimmableTypeMapGenerator.cs | 42 +- .../androidtest/TestInstrumentation.cs | 55 +- src/Mono.Android.Export/CallbackCode.cs | 31 +- .../Android.Graphics/AndroidBitmapInfo.cs | 33 + .../Android.Runtime/AndroidEnvironment.cs | 61 +- .../Android.Runtime/AndroidRuntime.cs | 88 +- .../Android.Runtime/AndroidRuntimeInternal.cs | 4 + src/Mono.Android/Android.Runtime/JNIEnv.cs | 2 + .../Android.Runtime/JNIEnvInit.cs | 133 +- src/Mono.Android/Android.Runtime/JValue.cs | 70 + .../Android.Runtime/JavaProxyThrowable.cs | 18 +- src/Mono.Android/Android.Security/KeyChain.cs | 84 + .../Java.Interop/JavaPeerProxy.cs | 36 +- .../JavaMarshalValueManager.cs | 35 +- .../JniRemappingLookup.cs | 99 ++ .../ManagedTypeManager.cs | 10 +- .../RuntimeFeature.cs | 5 + .../SimpleValueManager.cs | 35 - .../TrimmableTypeMap.cs | 92 +- .../TrimmableTypeMapTypeManager.cs | 19 +- src/Mono.Android/Mono.Android.csproj | 2 +- .../PublicAPI/API-35/PublicAPI.Shipped.txt | 24 - .../PublicAPI/API-35/PublicAPI.Unshipped.txt | 24 + .../PublicAPI/API-36.1/PublicAPI.Shipped.txt | 24 - .../API-36.1/PublicAPI.Unshipped.txt | 24 + .../PublicAPI/API-36/PublicAPI.Shipped.txt | 24 - .../PublicAPI/API-36/PublicAPI.Unshipped.txt | 24 + .../PublicAPI/API-37/PublicAPI.Shipped.txt | 24 - .../PublicAPI/API-37/PublicAPI.Unshipped.txt | 24 + .../AndroidClientHandler.Legacy.cs | 1012 ----------- .../AndroidClientHandler.cs | 326 ---- .../AndroidMessageHandler.cs | 135 +- .../Xamarin.Android.Net/AuthDigestSession.cs | 6 +- .../ServerCertificateCustomValidator.cs | 16 - src/Mono.Android/map.csv | 6 +- src/Mono.Android/methodmap.csv | 2 +- .../FixLegacyResourceDesignerStep.cs | 6 +- .../MamJsonToXml.cs | 1 + .../PreserveLists/System.Private.CoreLib.xml | 11 + .../Microsoft.Android.Sdk.After.targets | 1 + .../targets/Microsoft.Android.Sdk.Aot.targets | 1 - ...oft.Android.Sdk.AssemblyResolution.targets | 1 - .../Microsoft.Android.Sdk.CoreCLR.targets | 4 + ...soft.Android.Sdk.DefaultProperties.targets | 4 +- .../Microsoft.Android.Sdk.MonoVM.targets | 4 + ...rosoft.Android.Sdk.NativeAOT.After.targets | 24 + .../Microsoft.Android.Sdk.NativeAOT.targets | 92 +- ...icrosoft.Android.Sdk.RuntimeConfig.targets | 8 + ...crosoft.Android.Sdk.TypeMap.LlvmIr.targets | 7 +- ...id.Sdk.TypeMap.Trimmable.NativeAOT.targets | 29 +- ...soft.Android.Sdk.TypeMap.Trimmable.targets | 51 +- .../Properties/Resources.Designer.cs | 155 +- .../Properties/Resources.cs.resx | 71 +- .../Properties/Resources.de.resx | 71 +- .../Properties/Resources.es.resx | 71 +- .../Properties/Resources.fr.resx | 71 +- .../Properties/Resources.it.resx | 71 +- .../Properties/Resources.ja.resx | 71 +- .../Properties/Resources.ko.resx | 71 +- .../Properties/Resources.pl.resx | 71 +- .../Properties/Resources.pt-BR.resx | 71 +- .../Properties/Resources.resx | 87 +- .../Properties/Resources.ru.resx | 71 +- .../Properties/Resources.tr.resx | 71 +- .../Properties/Resources.zh-Hans.resx | 71 +- .../Properties/Resources.zh-Hant.resx | 71 +- .../Tasks/CheckClientHandlerType.cs | 98 -- .../Tasks/CheckForInvalidResourceFileNames.cs | 8 - .../Tasks/CompressAssemblies.cs | 2 +- .../Tasks/CreateDynamicFeatureManifest.cs | 6 - .../GenerateAdditionalProviderSources.cs | 8 +- .../Tasks/GenerateJniRemappingNativeCode.cs | 6 +- .../GenerateNativeAotBootstrapSources.cs | 4 +- ...ateNativeAotEnvironmentAssemblerSources.cs | 1 - .../GenerateNativeApplicationConfigSources.cs | 19 +- .../Tasks/GenerateTrimmableTypeMap.cs | 55 +- .../Tasks/LinkNativeAotSharedLibrary.cs | 1 - .../Tasks/MavenDownload.cs | 9 +- .../Tasks/MergeRemapXml.cs | 6 +- .../ProcessRuntimePackLibraryDirectories.cs | 3 +- .../Tasks/ResolveAndroidTooling.cs | 12 +- ...dkPathForIlc.cs => SetIlcToolchainPath.cs} | 11 +- .../Tasks/ValidateJavaVersion.cs | 8 +- .../AndroidDependenciesTests.cs | 15 +- .../AndroidGradleProjectTests.cs | 3 +- .../AndroidUpdateResourcesTest.cs | 43 +- .../Xamarin.Android.Build.Tests/AotTests.cs | 34 +- .../AssetPackTests.cs | 8 +- .../BindingBuildTest.cs | 7 +- .../Xamarin.Android.Build.Tests/BuildTest.cs | 10 +- .../Xamarin.Android.Build.Tests/BuildTest2.cs | 112 +- .../Emulator.csproj | 3 + .../EnvironmentContentTests.cs | 83 +- .../IncrementalBuildTest.cs | 148 +- .../ManifestTest.cs | 19 +- .../NativeAotBuildTests.cs | 65 + .../PackagingTest.cs | 75 +- .../Tasks/CheckClientHandlerTypeTests.cs | 143 -- .../Tasks/EnvironmentFilesParserTests.cs | 199 +++ .../Tasks/GeneratePackageManagerJavaTests.cs | 41 +- .../Tasks/GenerateTrimmableTypeMapTests.cs | 67 +- .../Tasks/LinkerTests.cs | 75 +- .../Tasks/MavenDownloadTests.cs | 35 + .../Tasks/ResolveSdksTaskTests.cs | 31 + .../Tasks/ValidateJavaVersionTests.cs | 35 + .../TrimmableTypeMapBuildTests.cs | 285 +++- .../Utilities/BaseTest.cs | 32 + .../Utilities/ProjectExtensions.cs | 3 +- .../Xamarin.Android.Build.Tests/WearTests.cs | 2 +- .../Android/AndroidSdkResolver.cs | 2 - .../XamarinAndroidApplicationProject.cs | 7 +- .../XamarinAndroidWearApplicationProject.cs | 2 +- .../Common/SolutionBuilder.cs | 11 +- ...ildReleaseArm64SimpleDotNet.MonoVM.apkdesc | 41 +- ...ildReleaseArm64XFormsDotNet.MonoVM.apkdesc | 201 +-- .../Utilities/ProjectExtensions.cs | 19 - .../Utilities/DSOWrapperGenerator.cs | 3 +- .../Utilities/EnvironmentBuilder.cs | 15 - .../Utilities/EnvironmentFilesParser.cs | 3 - .../Utilities/JCWGenerator.cs | 11 +- .../LlvmIrGenerator/LlvmIrInstructions.cs | 6 +- .../Utilities/ManifestDocument.cs | 10 +- .../Utilities/MonoAndroidHelper.cs | 5 +- .../Utilities/NativeLinker.cs | 4 +- ...appingReleaseNativeAssemblyGeneratorCLR.cs | 14 +- .../Xamarin.Android.Build.Tasks.csproj | 5 +- .../Xamarin.Android.Build.Tasks.targets | 4 - .../Xamarin.Android.Common.props.in | 39 +- .../Xamarin.Android.Common.targets | 22 +- .../Xamarin.Android.Tooling.targets | 7 + .../AidlCompiler.cs | 11 +- src/aapt2/aapt2.targets | 36 +- src/androidsdk/androidsdk.csproj | 17 + src/androidsdk/androidsdk.targets | 38 + src/binutils/binutils.csproj | 17 + src/binutils/binutils.targets | 96 ++ src/bundletool/bundletool.csproj | 4 + src/bundletool/bundletool.targets | 16 +- src/java-runtime/java-runtime.csproj | 1 + src/manifestmerger/build.gradle | 4 +- src/manifestmerger/manifestmerger.csproj | 4 + src/native/CMakePresets.json.in | 38 +- src/native/native.targets | 10 +- src/native/nativeaot/host/host.cc | 23 + src/openjdk/openjdk.csproj | 13 + src/openjdk/openjdk.targets | 105 ++ src/profiled-aot/dotnet.aotprofile.txt | 1 - src/proguard-android/proguard-android.csproj | 5 + src/r8/r8.csproj | 1 + .../LinkDescTest/MainActivityReplacement.cs | 12 - .../Tests/AotProfileTests.cs | 2 +- .../Tests/BundleToolTests.cs | 2 +- .../Tests/DebuggingTest.cs | 7 +- .../Tests/InstallAndRunTests.cs | 127 +- .../Tests/InstallTests.cs | 18 +- .../Tests/MarshalMethodsGCHangTests.cs | 2 +- .../Tests/MonoAndroidExportTest.cs | 4 +- .../Tests/PerformanceTest.cs | 2 +- .../Tests/SystemApplicationTests.cs | 2 +- .../ScannerComparisonTests.Helpers.cs | 31 +- .../ScannerComparisonTests.cs | 4 +- .../ScannerExportShapesTests.cs | 263 +++ .../ScannerRunner.cs | 14 +- .../UserTypesFixture/UserTypes.cs | 235 +++ .../Generator/FixtureTestBase.cs | 20 +- .../Generator/JcwJavaSourceGeneratorTests.cs | 13 + .../RootTypeMapAssemblyGeneratorTests.cs | 20 + .../TrimmableTypeMapGeneratorTests.cs | 260 ++- .../TypeMapAssemblyGeneratorTests.cs | 841 ++++++++- .../Generator/TypeMapModelBuilderTests.cs | 216 ++- .../Scanner/ConstructorDetectionTests.cs | 75 + .../Scanner/JavaPeerScannerTests.Behavior.cs | 95 +- .../Scanner/JavaPeerScannerTests.EdgeCases.cs | 4 +- .../Scanner/JavaPeerScannerTests.cs | 100 +- .../Scanner/ScannerHashingHelperTests.cs | 32 + .../TestFixtures/StubAttributes.cs | 30 + .../TestFixtures/TestTypes.cs | 181 ++ .../Java.Interop-Tests.NET.csproj | 13 + .../Java.Interop-Tests.targets | 14 +- .../Android.Runtime/AndroidEnvironmentTest.cs | 85 - .../ConstructorActivationTests.cs | 982 +++++++++++ .../Java.Interop/ExportTests.cs | 359 ++++ .../Java.Interop/JnienvTest.cs | 101 +- .../TrimmableTypeMapRuntimeCoverageTests.cs | 254 +++ .../Java.Lang/ObjectTest.cs | 1 - .../Mono.Android.NET-Tests.csproj | 23 +- .../Mono.Android-Tests/TrimmerRoots.xml | 1 - ...dlerTests.cs => AndroidHandlerTestBase.cs} | 105 +- .../AndroidMessageHandlerIntegrationTests.cs | 325 ++++ .../HttpClientIntegrationTests.cs | 1156 ------------- .../TrustManagerMarshallingTests.cs | 61 + .../NUnitInstrumentation.cs | 24 +- .../test/ConstructorActivationBase.java | 24 + .../android/test/ExtendedValueProvider.java | 5 + .../android/test/InterfaceMarshalling.java | 17 + tests/api-compatibility/README.md | 24 +- ...cceptable-breakages-vReference-net11.0.txt | 1 + .../api-compatibility.targets | 28 - 345 files changed, 14963 insertions(+), 8811 deletions(-) create mode 100644 .github/workflows/agentics-maintenance.yml create mode 100644 .github/workflows/nightly-fix-finder.lock.yml create mode 100644 .github/workflows/nightly-fix-finder.md delete mode 100644 Documentation/docs-mobile/messages/xa1031.md delete mode 100644 Documentation/docs-mobile/messages/xa1032.md delete mode 100644 Documentation/docs-mobile/messages/xa1033.md create mode 100644 Documentation/docs-mobile/messages/xa1045.md create mode 100644 Documentation/docs-mobile/messages/xa1046.md create mode 100644 Documentation/docs-mobile/messages/xa1047.md create mode 100644 Documentation/docs-mobile/messages/xa3008.md create mode 100644 Documentation/docs-mobile/messages/xa4217.md create mode 100644 Documentation/docs-mobile/messages/xa4227.md create mode 100644 Documentation/docs-mobile/messages/xa4252.md create mode 100644 Documentation/docs-mobile/messages/xa4253.md create mode 100644 Documentation/docs-mobile/messages/xa4316.md create mode 100644 Documentation/docs-mobile/messages/xa4317.md create mode 100644 Documentation/docs-mobile/messages/xa4318.md create mode 100644 Documentation/docs-mobile/messages/xa5303.md delete mode 100644 build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/CreateFrameworkList.cs delete mode 100644 build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateMonoDroidIncludes.cs delete mode 100644 build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateProfile.cs delete mode 100644 build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/AcceptAndroidSdkLicenses.cs delete mode 100644 build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/CheckoutExternalGitDependency.cs delete mode 100644 build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/CreateFilePaths.cs delete mode 100644 build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/DownloadUri.cs delete mode 100644 build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/GitCommitTime.cs delete mode 100644 build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/GitDiff.cs delete mode 100644 build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/HashFileContents.cs delete mode 100644 build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/NDKInfo.cs delete mode 100644 build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/ParseExternalGitDependencies.cs delete mode 100644 build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/PrepareInstall.cs delete mode 100644 build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/RunParallelTargets.cs delete mode 100644 build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/SetEnvironmentVariable.cs delete mode 100644 build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/SystemUnzip.cs delete mode 100644 build-tools/xaprepare/xaprepare/Application/MonoPkgProgram.MacOS.cs delete mode 100644 build-tools/xaprepare/xaprepare/Scenarios/Scenario_UpdateMono.Unix.cs delete mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_Get_Android_BuildTools.cs delete mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_Get_Windows_Binutils.cs delete mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.Linux.cs delete mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.MacOS.cs delete mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.Unix.cs delete mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.Windows.cs delete mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.cs delete mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.Linux.cs delete mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.MacOS.cs delete mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.Unix.cs delete mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.Windows.cs delete mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.cs create mode 100644 src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs create mode 100644 src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitterContext.cs create mode 100644 src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ExportParameterKindInfo.cs create mode 100644 src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ScannerHashingHelper.cs create mode 100644 src/Mono.Android/Microsoft.Android.Runtime/JniRemappingLookup.cs delete mode 100644 src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.Legacy.cs delete mode 100644 src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/PreserveLists/System.Private.CoreLib.xml create mode 100644 src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.After.targets delete mode 100644 src/Xamarin.Android.Build.Tasks/Tasks/CheckClientHandlerType.cs rename src/Xamarin.Android.Build.Tasks/Tasks/{SetNdkPathForIlc.cs => SetIlcToolchainPath.cs} (57%) create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/CheckClientHandlerTypeTests.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/EnvironmentFilesParserTests.cs create mode 100644 src/androidsdk/androidsdk.csproj create mode 100644 src/androidsdk/androidsdk.targets create mode 100644 src/binutils/binutils.csproj create mode 100644 src/binutils/binutils.targets create mode 100644 src/openjdk/openjdk.csproj create mode 100644 src/openjdk/openjdk.targets create mode 100644 tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerExportShapesTests.cs create mode 100644 tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/ScannerHashingHelperTests.cs delete mode 100644 tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/AndroidEnvironmentTest.cs create mode 100644 tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/ConstructorActivationTests.cs create mode 100644 tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/ExportTests.cs create mode 100644 tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapRuntimeCoverageTests.cs rename tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/{AndroidClientHandlerTests.cs => AndroidHandlerTestBase.cs} (77%) create mode 100644 tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerIntegrationTests.cs delete mode 100644 tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/HttpClientIntegrationTests.cs create mode 100644 tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/TrustManagerMarshallingTests.cs create mode 100644 tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/ConstructorActivationBase.java create mode 100644 tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/ExtendedValueProvider.java create mode 100644 tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/InterfaceMarshalling.java diff --git a/.external b/.external index 5a6ca6fca00..95a3956a53a 100644 --- a/.external +++ b/.external @@ -1 +1 @@ -DevDiv/android-platform-support:main@21730a73e34c7dafbb2db46995e12a1dbc245805 +DevDiv/android-platform-support:main@9c8f9978a9806230f520469b38a9596ed3a607f8 diff --git a/.github/agents/agentic-workflows.agent.md b/.github/agents/agentic-workflows.agent.md index e2e2b2b58be..f7e5eb4f1cd 100644 --- a/.github/agents/agentic-workflows.agent.md +++ b/.github/agents/agentic-workflows.agent.md @@ -19,6 +19,13 @@ This is a **dispatcher agent** that routes your request to the appropriate speci - **Creating shared components**: Routes to `create-shared-agentic-workflow` prompt - **Fixing Dependabot PRs**: Routes to `dependabot` prompt — use this when Dependabot opens PRs that modify generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`). Never merge those PRs directly; instead update the source `.md` files and rerun `gh aw compile --dependabot` to bundle all fixes - **Analyzing test coverage**: Routes to `test-coverage` prompt — consult this whenever the workflow reads, analyzes, or reports on test coverage data from PRs or CI runs +- **Rendering ASCII charts in markdown**: Routes to `asciicharts` guide — consult this whenever the workflow needs compact charts that render reliably in GitHub issues, comments, or discussions +- **CLI commands and triggering workflows**: Routes to `cli-commands` guide — consult this whenever the user asks how to run, compile, debug, or manage workflows from the command line, or when they need the MCP tool equivalent of a `gh aw` command +- **Reducing token consumption / cost optimization**: Routes to `token-optimization` guide — consult this whenever the user asks how to reduce token usage, lower costs, speed up workflows, or measure the impact of prompt changes with experiments +- **Choosing workflow architectures and design patterns**: Routes to `patterns` guide — consult this whenever the user asks for strategy, architecture, operating models, or pattern selection for agentic workflows + +> [!IMPORTANT] +> For architecture/pattern-selection requests, load `https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/patterns.md` first. Workflows may optionally include: @@ -30,7 +37,7 @@ Workflows may optionally include: - Workflow files: `.github/workflows/*.md` and `.github/workflows/**/*.md` - Workflow lock files: `.github/workflows/*.lock.yml` - Shared components: `.github/workflows/shared/*.md` -- Configuration: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/github-agentic-workflows.md +- Configuration: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/github-agentic-workflows.md ## Problems This Solves @@ -52,7 +59,7 @@ When you interact with this agent, it will: ### Create New Workflow **Load when**: User wants to create a new workflow from scratch, add automation, or design a workflow that doesn't exist yet -**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/create-agentic-workflow.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/create-agentic-workflow.md **Use cases**: - "Create a workflow that triages issues" @@ -62,7 +69,7 @@ When you interact with this agent, it will: ### Update Existing Workflow **Load when**: User wants to modify, improve, or refactor an existing workflow -**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/update-agentic-workflow.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/update-agentic-workflow.md **Use cases**: - "Add web-fetch tool to the issue-classifier workflow" @@ -72,7 +79,7 @@ When you interact with this agent, it will: ### Debug Workflow **Load when**: User needs to investigate, audit, debug, or understand a workflow, troubleshoot issues, analyze logs, or fix errors -**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/debug-agentic-workflow.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/debug-agentic-workflow.md **Use cases**: - "Why is this workflow failing?" @@ -82,7 +89,7 @@ When you interact with this agent, it will: ### Upgrade Agentic Workflows **Load when**: User wants to upgrade workflows to a new gh-aw version or fix deprecations -**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/upgrade-agentic-workflows.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/upgrade-agentic-workflows.md **Use cases**: - "Upgrade all workflows to the latest version" @@ -92,7 +99,7 @@ When you interact with this agent, it will: ### Create a Report-Generating Workflow **Load when**: The workflow being created or updated produces reports — recurring status updates, audit summaries, analyses, or any structured output posted as a GitHub issue, discussion, or comment -**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/report.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/report.md **Use cases**: - "Create a weekly CI health report" @@ -102,7 +109,7 @@ When you interact with this agent, it will: ### Create Shared Agentic Workflow **Load when**: User wants to create a reusable workflow component or wrap an MCP server -**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/create-shared-agentic-workflow.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/create-shared-agentic-workflow.md **Use cases**: - "Create a shared component for Notion integration" @@ -112,7 +119,7 @@ When you interact with this agent, it will: ### Fix Dependabot PRs **Load when**: User needs to close or fix open Dependabot PRs that update dependencies in generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`) -**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/dependabot.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/dependabot.md **Use cases**: - "Fix the open Dependabot PRs for npm dependencies" @@ -122,13 +129,58 @@ When you interact with this agent, it will: ### Analyze Test Coverage **Load when**: The workflow reads, analyzes, or reports test coverage — whether triggered by a PR, a schedule, or a slash command. Always consult this prompt before designing the coverage data strategy. -**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/test-coverage.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/test-coverage.md **Use cases**: - "Create a workflow that comments coverage on PRs" - "Analyze coverage trends over time" - "Add a coverage gate that blocks PRs below a threshold" +### Render ASCII Charts in Markdown +**Load when**: The workflow needs in-markdown charts (sparklines, bars, table+trend views) that must align cleanly and render reliably across GitHub surfaces, including mobile. + +**Reference file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/asciicharts.md + +**Use cases**: +- "Show a compact trend chart in an issue comment" +- "Render a dashboard table with sparkline trends" +- "Generate aligned ASCII bars for service metrics" + +### CLI Commands Reference +**Load when**: The user asks how to run, compile, debug, or manage workflows from the command line; needs the MCP tool equivalent of a `gh aw` command; or is in a restricted environment (e.g., Copilot Cloud) without direct CLI access. + +**Reference file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/cli-commands.md + +**Use cases**: +- "How do I trigger workflow X on the main branch?" +- "What's the MCP equivalent of `gh aw logs`?" +- "I'm in Copilot Cloud — how do I compile a workflow?" +- "Show me all available gh aw commands" + +### Token Consumption Optimization +**Load when**: The user asks how to reduce token usage, lower workflow costs, make a workflow faster or cheaper, or measure the impact of prompt or configuration changes. + +**Reference file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/token-optimization.md + +**Use cases**: +- "How do I reduce the token cost of this workflow?" +- "My workflow is too expensive — how do I optimize it?" +- "How do I compare token usage between two runs?" +- "Should I use gh-proxy or the MCP server?" +- "How do I use sub-agents to reduce costs?" +- "How do I measure the impact of a prompt change?" + +### Workflow Pattern Selection +**Load when**: The user asks for architecture, strategy, operating model selection, or pattern recommendations for building agentic workflows. + +**Reference file**: https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/patterns.md + +**Use cases**: +- "Which pattern should I use for multi-repo rollout?" +- "How should I structure this workflow architecture?" +- "What pattern fits slash-command triage?" +- "Should this be DispatchOps or DailyOps?" + ## Instructions When a user interacts with you: @@ -147,6 +199,10 @@ gh aw init # Generate the lock file for a workflow gh aw compile [workflow-name] +# Trigger a workflow on demand (preferred over gh workflow run) +gh aw run # interactive input collection +gh aw run --ref main # run on a specific branch + # Debug workflow runs gh aw logs [workflow-name] gh aw audit @@ -169,10 +225,12 @@ gh aw compile --validate ## Important Notes -- Always reference the instructions file at https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/github-agentic-workflows.md for complete documentation +- Always reference the instructions file at https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/github-agentic-workflows.md for complete documentation - Use the MCP tool `agentic-workflows` when running in GitHub Copilot Cloud - Workflows must be compiled to `.lock.yml` files before running in GitHub Actions - **Bash tools are enabled by default** - Don't restrict bash commands unnecessarily since workflows are sandboxed by the AWF - Follow security best practices: minimal permissions, explicit network access, no template injection -- **Network configuration**: Use ecosystem identifiers (`node`, `python`, `go`, etc.) or explicit FQDNs in `network.allowed`. Bare shorthands like `npm` or `pypi` are **not** valid. See https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/network.md for the full list of valid ecosystem identifiers and domain patterns. +- **Network configuration**: Use ecosystem identifiers (`node`, `python`, `go`, etc.) or explicit FQDNs in `network.allowed`. Bare shorthands like `npm` or `pypi` are **not** valid. See https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/network.md for the full list of valid ecosystem identifiers and domain patterns. - **Single-file output**: When creating a workflow, produce exactly **one** workflow `.md` file. Do not create separate documentation files (architecture docs, runbooks, usage guides, etc.). If documentation is needed, add a brief `## Usage` section inside the workflow file itself. +- **Triggering runs**: Always use `gh aw run ` to trigger a workflow on demand — not `gh workflow run .lock.yml`. `gh aw run` handles workflow resolution by short name, input parsing and validation, and correct run-tracking for agentic workflows. Use `--ref ` to run on a specific branch. +- **CLI commands reference**: For a complete guide on all `gh aw` commands and their MCP tool equivalents (for restricted environments), see https://github.com/github/gh-aw/blob/v0.74.8/.github/aw/cli-commands.md diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 73df5e9d5f2..36f7a0c0a84 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -1,14 +1,19 @@ { "entries": { - "actions/github-script@v9": { + "actions/github-script@v9.0.0": { "repo": "actions/github-script", - "version": "v9", - "sha": "373c709c69115d41ff229c7e5df9f8788daa9553" + "version": "v9.0.0", + "sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3" }, - "github/gh-aw-actions/setup@v0.68.3": { + "github/gh-aw-actions/setup-cli@v0.74.8": { + "repo": "github/gh-aw-actions/setup-cli", + "version": "v0.74.8", + "sha": "efa55847f72aadb03490d955263ff911bf758700" + }, + "github/gh-aw-actions/setup@v0.74.8": { "repo": "github/gh-aw-actions/setup", - "version": "v0.68.3", - "sha": "ba90f2186d7ad780ec640f364005fa24e797b360" + "version": "v0.74.8", + "sha": "efa55847f72aadb03490d955263ff911bf758700" } } } diff --git a/.github/skills/android-reviewer/SKILL.md b/.github/skills/android-reviewer/SKILL.md index f56ab482738..3327beb4d34 100644 --- a/.github/skills/android-reviewer/SKILL.md +++ b/.github/skills/android-reviewer/SKILL.md @@ -19,7 +19,7 @@ Flag severity clearly in every comment: - ⚠️ **warning** — Should fix. Performance issues, missing validation, inconsistency with patterns. - 💡 **suggestion** — Consider changing. Style, readability, optional improvements. -**Every review should produce at least one inline comment.** Even clean PRs have opportunities for improvement — code consolidation, missing edge-case tests, perf micro-optimizations, or documentation gaps. Use 💡 suggestions for these. A review with zero comments appears superficial and misses the chance to share knowledge. Only omit inline comments if the PR is truly trivial (e.g., a 1-line typo fix or dependency bump). +**Every review should produce at least one inline comment.** Even clean PRs have opportunities for improvement — code consolidation, missing edge-case tests, perf micro-optimizations, or documentation gaps. Use 💡 suggestions for these. A review with zero inline comments appears superficial and misses the chance to share knowledge. Only omit inline comments if the PR is truly trivial (e.g., a 1-line typo fix or dependency bump). **Do NOT summarize suggestions only in the review body — post them as inline comments on the relevant line.** If a suggestion cannot be posted inline (e.g., it's about pre-existing code or missing code), either find the closest relevant changed line to attach it to, or omit it entirely rather than burying it in the summary. ## Workflow @@ -100,6 +100,7 @@ Constraints: - **Don't pile on.** If the same issue appears many times, flag it once with a note listing all affected files. - **Don't flag what CI catches.** Skip compiler errors, formatting the linter will catch, etc. - **Avoid false positives.** Verify the concern actually applies given the full context. If unsure, phrase it as a question rather than a firm claim. +- **Verify project context before applying rules.** Some rules are scoped to specific project types (e.g., `netstandard2.0` vs modern .NET, MSBuild tasks vs console apps). Check the project's `TargetFramework`, references, and available utilities before flagging a rule violation. A rule about an extension method is irrelevant if the project doesn't reference the assembly that defines it. ### 7. Post the review diff --git a/.github/skills/android-reviewer/references/csharp-rules.md b/.github/skills/android-reviewer/references/csharp-rules.md index b7b958c8117..029c54a2b88 100644 --- a/.github/skills/android-reviewer/references/csharp-rules.md +++ b/.github/skills/android-reviewer/references/csharp-rules.md @@ -10,7 +10,7 @@ General C# guidance applicable to any .NET repository. |-------|-----------------| | **`#nullable enable`** | New files should have `#nullable enable` at the top with no preceding blank lines — **unless** nullable is already enabled at the project level (for example via the `Nullable` MSBuild property in the project or imported props), in which case it is not needed per-file. | | **Never use `!` (null-forgiving operator)** | The postfix `!` null-forgiving operator (e.g., `foo!.Bar`) is banned. If the value can be null, add a proper null check. If it can't be null, make the type non-nullable. AI-generated code frequently sprinkles `!` to silence warnings — this turns compile-time safety into runtime `NullReferenceException`s. Note: this rule is about the postfix `!` operator, not the logical negation `!` (e.g., `if (!someBool)` or `if (!string.IsNullOrEmpty (s))`). | -| **Use `IsNullOrEmpty()` extension** | Use the `NullableExtensions` instance methods (`str.IsNullOrEmpty()`, `str.IsNullOrWhiteSpace()`) instead of the static `string.IsNullOrEmpty(str)` / `string.IsNullOrWhiteSpace(str)` — they integrate with `[NotNullWhen]` for NRT flow analysis. | +| **Use `IsNullOrEmpty()` extension (netstandard2.0 only)** | In projects targeting `netstandard2.0` (e.g., `Xamarin.Android.Build.Tasks`), use the `NullableExtensions` instance methods (`str.IsNullOrEmpty()`, `str.IsNullOrWhiteSpace()`) instead of the static `string.IsNullOrEmpty(str)` / `string.IsNullOrWhiteSpace(str)` — they provide `[NotNullWhen]` annotations that netstandard2.0's BCL lacks. **This rule does NOT apply to projects targeting modern .NET** (e.g., `net11.0`) — in modern .NET, the static `string.IsNullOrEmpty` is already annotated with `[NotNullWhen(false)]`, so the extension is unnecessary. Verify the project's `TargetFramework` before flagging this. | | **`ArgumentNullException.ThrowIfNull`** | Android-targeted code (.NET 10+) should use `ArgumentNullException.ThrowIfNull(param)`. | --- diff --git a/.github/skills/android-reviewer/references/msbuild-rules.md b/.github/skills/android-reviewer/references/msbuild-rules.md index a2ececcb223..62c9f86b741 100644 --- a/.github/skills/android-reviewer/references/msbuild-rules.md +++ b/.github/skills/android-reviewer/references/msbuild-rules.md @@ -16,7 +16,7 @@ causes broken builds for every .NET Android developer. | **Use `AsyncTask` for background work** | Tasks that need `async`/`await` should extend `AsyncTask` and override `RunTaskAsync()`. It handles `Yield()`, `try`/`finally`, and `Reacquire()` automatically. Use `AsyncTask.Log*` helpers for logging from the background thread — calling `Log.LogMessage` directly can cause IDE hangs. Use full paths on background threads (`Environment.CurrentDirectory` may differ if the task is on another MSBuild node). Leverage the `WhenAll` extension for parallel work over `ITaskItem[]`. | | **Return `!Log.HasLoggedErrors`** | `RunTask()` must return `!Log.HasLoggedErrors`. Do not return `true`/`false` directly — it skips the centralized error-tracking mechanism. | | **Use `Log.LogCoded*` methods** | Errors and warnings must use `Log.LogCodedError("XA####", …)` or `Log.LogCodedWarning("XA####", …)` — never bare `Log.LogError` without a code. Error messages should come from `Properties.Resources`. | -| **XA error codes** | Error codes follow `XA####` (4+ digits). New codes must not collide with existing ones. Check `Properties/Resources.cs.resx` for used codes. Every new code must have a documentation entry and be localized. (Postmortem `#10`) | +| **XA error codes** | Error codes follow `XA####` (4+ digits). New codes must not collide with existing ones. Check `Properties/Resources.cs.resx` for used codes. Every new code must have a corresponding markdown documentation file at `Documentation/docs-mobile/messages/xa####.md` (following the existing format with frontmatter, example messages, issue explanation, and solution), and the code must be added to the table of contents in `Documentation/docs-mobile/messages/index.md`. (Postmortem `#10`) | | **`[Required]` properties** | `[Required]` properties must be non-nullable with a default: `public string Foo { get; set; } = "";` or `public ITaskItem[] Bar { get; set; } = [];`. Non-`[Required]` and `[Output]` properties must be nullable (`string?`, `ITaskItem[]?`). Mark properties `[Required]` when the task crashes without them. (Postmortem `#51`) | | **`UsingTask` for internal tasks** | `` elements for `xa-prep-tasks` and `BootstrapTasks` (internal, not shipped) must use `TaskFactory="TaskHostFactory"` and `Runtime="NET"`. Do NOT add these attributes to shipped task definitions in `Xamarin.Android.Common.targets` or `Microsoft.Android.Sdk/*.targets`. | | **Caching with `RegisterTaskObject`** | Use `BuildEngine4.RegisterTaskObject()` (via the `RegisterTaskObjectAssemblyLocal()` extension method) instead of `static` variables for sharing data between tasks or across builds. Use `as` for casts to avoid `InvalidCastException`. Cache keys should include context that invalidates properly (device target, file path, version). Cache primitive/small values only. | diff --git a/.github/skills/tests/references/test-catalog.md b/.github/skills/tests/references/test-catalog.md index e49e3dab4d3..87f6b599387 100644 --- a/.github/skills/tests/references/test-catalog.md +++ b/.github/skills/tests/references/test-catalog.md @@ -122,7 +122,7 @@ Device: **Yes** | Test Area | Project | Notes | |-----------|---------|-------| | **runtime** (all) | `tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj` | Core runtime tests | -| **networking** | Same project — tests in `Xamarin.Android.Net/` and `System.Net/` | `AndroidMessageHandlerTests`, `AndroidClientHandlerTests`, `HttpClientIntegrationTests` | +| **networking** | Same project — tests in `Xamarin.Android.Net/` and `System.Net/` | `AndroidMessageHandlerTests`, `AndroidMessageHandlerIntegrationTests` | | **java interop (on-device)** | Same project — tests in `Java.Interop/` | `JnienvTest`, `JavaListTest` | | **android app** | Same project — tests in `Android.App/` | `Application`, `Activity` tests | | **android views** | Same project — tests in `Android.Views/` | Layout inflater, view tests | @@ -138,10 +138,10 @@ Device: **Yes** The `Mono.Android.NET-Tests.csproj` dynamically excludes categories based on runtime: - **CoreCLR runtime**: Excludes `CoreCLRIgnore`, `NTLM` -- **NativeAOT runtime**: Excludes `NativeAOTIgnore`, `SSL`, `NTLM`, `AndroidClientHandler`, `Export`, `NativeTypeMap` +- **NativeAOT runtime**: Excludes `NativeAOTIgnore`, `SSL`, `NTLM`, `Export`, `NativeTypeMap` - **LLVM**: Excludes `LLVMIgnore`, `InetAccess`, `NetworkInterfaces` -Other categories: `SSL`, `InetAccess`, `AndroidClientHandler`, `JavaList`, `RuntimeConfig`, `Intune`, `NTLM` +Other categories: `SSL`, `InetAccess`, `JavaList`, `RuntimeConfig`, `Intune`, `NTLM` Command: ```bash diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml new file mode 100644 index 00000000000..b0caa1ff93b --- /dev/null +++ b/.github/workflows/agentics-maintenance.yml @@ -0,0 +1,582 @@ +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.74.8). DO NOT EDIT. +# +# To regenerate this workflow, run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# Alternative regeneration methods: +# make recompile +# +# Or use the gh-aw CLI directly: +# ./gh-aw compile --validate --verbose +# +# The workflow is generated when any workflow uses the 'expires' field +# in create-discussions, create-issues, or create-pull-request safe-outputs configuration. +# Schedule frequency is automatically determined by the shortest expiration time. +# +name: Agentic Maintenance + +on: + schedule: + - cron: "37 0 * * *" # Daily (based on minimum expires: 7 days) + workflow_dispatch: + inputs: + operation: + description: 'Optional maintenance operation to run' + required: false + type: choice + default: '' + options: + - '' + - 'disable' + - 'enable' + - 'update' + - 'upgrade' + - 'safe_outputs' + - 'create_labels' + - 'activity_report' + - 'close_agentic_workflows_issues' + - 'clean_cache_memories' + - 'update_pull_request_branches' + - 'validate' + - 'forecast' + run_url: + description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.' + required: false + type: string + default: '' + workflow_call: + inputs: + operation: + description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, update_pull_request_branches, validate, forecast)' + required: false + type: string + default: '' + run_url: + description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.' + required: false + type: string + default: '' + outputs: + operation_completed: + description: 'The maintenance operation that was completed (empty when none ran or a scheduled job ran)' + value: ${{ jobs.run_operation.outputs.operation || inputs.operation }} + applied_run_url: + description: 'The run URL that safe outputs were applied from' + value: ${{ jobs.apply_safe_outputs.outputs.run_url }} + +permissions: {} + +jobs: + close-expired-entities: + if: ${{ (!(github.event.repository.fork)) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }} + runs-on: ubuntu-slim + permissions: + discussions: write + issues: write + pull-requests: write + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Close expired discussions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_discussions.cjs'); + await main(); + + - name: Close expired issues + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_issues.cjs'); + await main(); + + - name: Close expired pull requests + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_pull_requests.cjs'); + await main(); + + cleanup-cache-memory: + if: ${{ (!(github.event.repository.fork)) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '' || inputs.operation == 'clean_cache_memories') }} + runs-on: ubuntu-slim + permissions: + actions: write + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Cleanup outdated cache-memory entries + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/cleanup_cache_memory.cjs'); + await main(); + + run_operation: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity_report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'update_pull_request_branches' && inputs.operation != 'validate' && inputs.operation != 'forecast' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + actions: write + contents: write + pull-requests: write + outputs: + operation: ${{ steps.record.outputs.operation }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Install gh-aw + uses: github/gh-aw-actions/setup-cli@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + version: v0.74.8 + + - name: Run operation + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_OPERATION: ${{ inputs.operation }} + GH_AW_CMD_PREFIX: gh aw + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/run_operation_update_upgrade.cjs'); + await main(); + + - name: Record outputs + id: record + run: echo "operation=${{ inputs.operation }}" >> "$GITHUB_OUTPUT" + + update_pull_request_branches: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'update_pull_request_branches' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + contents: write + pull-requests: write + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Update pull request branches + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/update_pull_request_branches.cjs'); + await main(); + + apply_safe_outputs: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'safe_outputs' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + actions: read + contents: write + discussions: write + issues: write + pull-requests: write + outputs: + run_url: ${{ steps.record.outputs.run_url }} + steps: + - name: Checkout actions folder + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + actions + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Apply Safe Outputs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_RUN_URL: ${{ inputs.run_url }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/apply_safe_outputs_replay.cjs'); + await main(); + + - name: Record outputs + id: record + run: echo "run_url=${{ inputs.run_url }}" >> "$GITHUB_OUTPUT" + + create_labels: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'create_labels' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Install gh-aw + uses: github/gh-aw-actions/setup-cli@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + version: v0.74.8 + + - name: Create missing labels + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_CMD_PREFIX: gh aw + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/create_labels.cjs'); + await main(); + + activity_report: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity_report' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + timeout-minutes: 120 + permissions: + actions: read + contents: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Install gh-aw + uses: github/gh-aw-actions/setup-cli@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + version: v0.74.8 + + - name: Restore activity report logs cache + id: activity_report_logs_cache + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ./.cache/gh-aw/activity-report-logs + key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-activity-report-logs-${{ github.repository }}- + ${{ runner.os }}-activity-report-logs- + - name: Download activity report logs + timeout-minutes: 20 + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_CMD_PREFIX: gh aw + run: | + ${GH_AW_CMD_PREFIX} logs \ + --repo "${{ github.repository }}" \ + --start-date -1w \ + --count 100 \ + --output ./.cache/gh-aw/activity-report-logs \ + --format markdown \ + > ./.cache/gh-aw/activity-report-logs/report.md + + - name: Save activity report logs cache + if: ${{ always() }} + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ./.cache/gh-aw/activity-report-logs + key: ${{ steps.activity_report_logs_cache.outputs.cache-primary-key }} + + - name: Generate activity report issue + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const fs = require('node:fs'); + const reportPath = './.cache/gh-aw/activity-report-logs/report.md'; + if (!fs.existsSync(reportPath)) { + core.warning('Activity report markdown not found at ' + reportPath + '; skipping issue creation.'); + return; + } + let reportBody = ''; + try { + reportBody = fs.readFileSync(reportPath, 'utf8').trim(); + } catch (error) { + core.warning('Failed to read activity report markdown at ' + reportPath + ': ' + error.message); + return; + } + if (!reportBody) { + core.warning('Activity report markdown is empty at ' + reportPath + '; skipping issue creation.'); + return; + } + const repoSlug = context.repo.owner + '/' + context.repo.repo; + const body = [ + '### Agentic workflow activity report', + '', + 'Repository: ' + repoSlug, + 'Generated at: ' + new Date().toISOString(), + '', + reportBody, + ].join('\n'); + const createdIssue = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: '[aw] agentic status report', + body, + labels: ['agentic-workflows'], + }); + core.info('Created issue #' + createdIssue.data.number + ': ' + createdIssue.data.html_url); + + forecast_report: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'forecast' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + timeout-minutes: 60 + permissions: + actions: read + contents: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Install gh-aw + uses: github/gh-aw-actions/setup-cli@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + version: v0.74.8 + + - name: Restore forecast report logs cache + id: forecast_report_logs_cache + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: .github/aw/logs + key: ${{ runner.os }}-forecast-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-forecast-report-logs-${{ github.repository }}- + ${{ runner.os }}-forecast-report-logs- + + - name: Generate forecast report + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_CMD_PREFIX: gh aw + run: | + mkdir -p ./.cache/gh-aw/forecast + ${GH_AW_CMD_PREFIX} logs --repo "${{ github.repository }}" --start-date -30d --count 1500 > /dev/null + if ! compgen -G ".github/aw/logs/run-*/run_summary.json" > /dev/null; then + echo "::error::Missing run summary cache in .github/aw/logs after gh aw logs warm-up; cannot run forecast." + exit 1 + fi + ${GH_AW_CMD_PREFIX} forecast --repo "${{ github.repository }}" --json 2> >(grep -Fv "forecast is an experimental command and may change without notice" >&2) > ./.cache/gh-aw/forecast/report.json + + - name: Save forecast report logs cache + if: ${{ always() }} + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: .github/aw/logs + key: ${{ steps.forecast_report_logs_cache.outputs.cache-primary-key }} + + - name: Generate forecast issue + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/create_forecast_issue.cjs'); + await main(); + + close_agentic_workflows_issues: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'close_agentic_workflows_issues' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + issues: write + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Close no-repro agentic-workflows issues + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/close_agentic_workflows_issues.cjs'); + await main(); + + validate_workflows: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'validate' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Install gh-aw + uses: github/gh-aw-actions/setup-cli@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + version: v0.74.8 + + - name: Validate workflows and file issue on findings + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_CMD_PREFIX: gh aw + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/run_validate_workflows.cjs'); + await main(); diff --git a/.github/workflows/android-reviewer.lock.yml b/.github/workflows/android-reviewer.lock.yml index 991a336331c..c1b934250da 100644 --- a/.github/workflows/android-reviewer.lock.yml +++ b/.github/workflows/android-reviewer.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"3e10bbb78641f6b19ba032ce6936a3be58d632fd0fd1a6d225d41997e5ca967d","compiler_version":"v0.68.3","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.6"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ba90f2186d7ad780ec640f364005fa24e797b360","version":"v0.68.3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.20"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.20"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.2.19"},{"image":"ghcr.io/github/github-mcp-server:v0.32.0"},{"image":"node:lts-alpine"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"3e10bbb78641f6b19ba032ce6936a3be58d632fd0fd1a6d225d41997e5ca967d","compiler_version":"v0.74.8","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.6"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"efa55847f72aadb03490d955263ff911bf758700","version":"v0.74.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.49"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.49"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.68.3). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.74.8). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -32,20 +32,21 @@ # Custom actions used: # - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 -# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 +# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 +# - github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.20 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 -# - ghcr.io/github/gh-aw-firewall/squid:0.25.20 -# - ghcr.io/github/gh-aw-mcpg:v0.2.19 -# - ghcr.io/github/github-mcp-server:v0.32.0 -# - node:lts-alpine +# - ghcr.io/github/gh-aw-firewall/agent:0.25.49 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.49 +# - ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388 +# - ghcr.io/github/github-mcp-server:v1.0.4 +# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f name: "Android PR Reviewer" -"on": +on: issue_comment: types: - created @@ -70,7 +71,6 @@ jobs: permissions: actions: read contents: read - discussions: write issues: write pull-requests: write outputs: @@ -78,9 +78,12 @@ jobs: comment_id: ${{ steps.add-comment.outputs.comment-id }} comment_repo: ${{ steps.add-comment.outputs.comment-repo }} comment_url: ${{ steps.add-comment.outputs.comment-url }} + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} slash_command: ${{ needs.pre_activation.outputs.matched_command }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} @@ -89,31 +92,37 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Android PR Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-reviewer.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: "claude-opus-4.6" - GH_AW_INFO_VERSION: "1.0.21" - GH_AW_INFO_AGENT_VERSION: "1.0.21" - GH_AW_INFO_CLI_VERSION: "v0.68.3" + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_AGENT_VERSION: "1.0.48" + GH_AW_INFO_CLI_VERSION: "v0.74.8" GH_AW_INFO_WORKFLOW_NAME: "Android PR Reviewer" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","chrome","dotnet","github","java","aka.ms","dev.azure.com","gstatic.com","httpbin.org","microsoft.com","vsassets.io"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.20" + GH_AW_INFO_AWF_VERSION: "v0.25.49" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -123,7 +132,7 @@ jobs: - name: Add eyes reaction for immediate feedback id: react if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.id == github.repository_id - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_REACTION: "eyes" with: @@ -145,11 +154,23 @@ jobs: sparse-checkout: | .github .agents + .claude + .codex + .crush + .gemini + .opencode + .pi sparse-checkout-cone-mode: true fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" - name: Check workflow lock file id: check-lock-file - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_WORKFLOW_FILE: "android-reviewer.lock.yml" GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" @@ -160,9 +181,9 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); await main(); - name: Check compile-agentic version - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.68.3" + GH_AW_COMPILED_VERSION: "v0.74.8" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -171,7 +192,9 @@ jobs: await main(); - name: Compute current body text id: sanitized - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.google.com,*.googleapis.com,*.gradle-enterprise.cloud,*.gvt1.com,*.vsblob.vsassets.io,adoptium.net,aka.ms,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,cdn.azul.com,central.sonatype.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,develocity.apache.org,dist.nuget.org,dl.google.com,dlcdn.apache.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,ge.spockframework.org,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,gradle.org,gstatic.com,host.docker.internal,httpbin.org,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.java.com,www.microsoft.com" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -181,7 +204,7 @@ jobs: - name: Add comment with workflow run link id: add-comment if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.id == github.repository_id - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_WORKFLOW_NAME: "Android PR Reviewer" with: @@ -194,11 +217,11 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} @@ -218,30 +241,33 @@ jobs: Tools: create_pull_request_review_comment(max:50), submit_pull_request_review, missing_tool, missing_data, noop + GH_AW_PROMPT_f31a5b44ece7243e_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_f31a5b44ece7243e_EOF' The following GitHub context information is available for this workflow: - {{#if __GH_AW_GITHUB_ACTOR__ }} + {{#if github.actor}} - **actor**: __GH_AW_GITHUB_ACTOR__ {{/if}} - {{#if __GH_AW_GITHUB_REPOSITORY__ }} + {{#if github.repository}} - **repository**: __GH_AW_GITHUB_REPOSITORY__ {{/if}} - {{#if __GH_AW_GITHUB_WORKSPACE__ }} + {{#if github.workspace}} - **workspace**: __GH_AW_GITHUB_WORKSPACE__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} - - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} + - **issue-number**: #__GH_AW_EXPR_802A9F6A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} - - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} + - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} - - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} + - **pull-request-number**: #__GH_AW_EXPR_463A214A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} - - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{#if github.event.comment.id || github.aw.context.comment_id}} + - **comment-id**: __GH_AW_EXPR_FF1D34CE__ {{/if}} - {{#if __GH_AW_GITHUB_RUN_ID__ }} + {{#if github.run_id}} - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} @@ -257,9 +283,10 @@ jobs: GH_AW_PROMPT_f31a5b44ece7243e_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_ENGINE_ID: "copilot" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -267,18 +294,19 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); await main(); - name: Substitute placeholders - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_MATCHED_COMMAND: ${{ needs.pre_activation.outputs.matched_command }} with: @@ -292,15 +320,16 @@ jobs: return await substitutePlaceholders({ file: process.env.GH_AW_PROMPT, substitutions: { + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, - GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, GH_AW_IS_PR_COMMENT: process.env.GH_AW_IS_PR_COMMENT, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED, GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_MATCHED_COMMAND: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_MATCHED_COMMAND } @@ -320,10 +349,15 @@ jobs: uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: activation + include-hidden-files: true path: | /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw-prompts/prompt-template.txt + /tmp/gh-aw/aw-prompts/prompt-import-tree.json /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base + /tmp/gh-aw/.github/agents if-no-files-found: ignore retention-days: 1 @@ -344,6 +378,7 @@ jobs: agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} has_patch: ${{ steps.collect_output.outputs.has_patch }} inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} @@ -351,15 +386,23 @@ jobs: model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Android PR Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-reviewer.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths run: | @@ -395,7 +438,7 @@ jobs: id: checkout-pr if: | github.event.pull_request || github.event.issue.pull_request - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} with: @@ -406,11 +449,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.48 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.20 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.49 - name: Parse integrity filter lists id: parse-guard-vars env: @@ -418,9 +461,25 @@ jobs: GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.20 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 ghcr.io/github/gh-aw-firewall/squid:0.25.20 ghcr.io/github/gh-aw-mcpg:v0.2.19 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - - name: Write Safe Outputs Config + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.49 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 ghcr.io/github/gh-aw-firewall/squid:0.25.49 ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388 ghcr.io/github/github-mcp-server:v1.0.4 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs @@ -428,7 +487,7 @@ jobs: cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_fe978e84b25bd06b_EOF' {"create_pull_request_review_comment":{"max":50,"side":"RIGHT"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"submit_pull_request_review":{"allowed_events":["COMMENT","REQUEST_CHANGES"],"max":1}} GH_AW_SAFE_OUTPUTS_CONFIG_fe978e84b25bd06b_EOF - - name: Write Safe Outputs Tools + - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { @@ -570,7 +629,7 @@ jobs: } } } - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -626,11 +685,12 @@ jobs: GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} run: | set -eo pipefail - mkdir -p /tmp/gh-aw/mcp-config + mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" # Export gateway environment variables for MCP config and gateway script - export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_PORT="8080" export MCP_GATEWAY_DOMAIN="host.docker.internal" + export MCP_GATEWAY_HOST_DOMAIN="localhost" MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') echo "::add-mask::${MCP_GATEWAY_API_KEY}" export MCP_GATEWAY_API_KEY @@ -640,15 +700,24 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.19' + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + case "${DOCKER_HOST:-}" in + unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; + /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; + * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; + esac + DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.9' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_74c2abb4758e4c28_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" + GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) + cat << GH_AW_MCP_CONFIG_74c2abb4758e4c28_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v0.32.0", + "container": "ghcr.io/github/github-mcp-server:v1.0.4", "env": { "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", @@ -688,36 +757,62 @@ jobs: } } GH_AW_MCP_CONFIG_74c2abb4758e4c28_EOF - - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: - name: activation - path: /tmp/gh-aw - - name: Clean git credentials + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); + - name: Clean credentials continue-on-error: true run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Audit pre-agent workspace + id: pre_agent_audit + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" - name: Execute GitHub Copilot CLI id: agentic_execution # Copilot CLI tool arguments (sorted): timeout-minutes: 20 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.49/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","*.google.com","*.googleapis.com","*.gradle-enterprise.cloud","*.gvt1.com","*.vsblob.vsassets.io","adoptium.net","aka.ms","api.adoptium.net","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.foojay.io","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.apache.org","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","cdn.azul.com","central.sonatype.com","ci.dot.net","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dev.azure.com","develocity.apache.org","dist.nuget.org","dl.google.com","dlcdn.apache.org","docs.github.com","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","download.eclipse.org","download.java.net","download.oracle.com","downloads.gradle-dn.com","ge.spockframework.org","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","gradle.org","gstatic.com","host.docker.internal","httpbin.org","jcenter.bintray.com","jdk.java.net","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","maven-central.storage-download.googleapis.com","maven.apache.org","maven.google.com","maven.oracle.com","maven.pkg.github.com","microsoft.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","patch-diff.githubusercontent.com","pkgs.dev.azure.com","plugins-artifacts.gradle.org","plugins.gradle.org","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","repo.gradle.org","repo.grails.org","repo.maven.apache.org","repo.spring.io","repo1.maven.org","repository.apache.org","s.symcb.com","s.symcd.com","scans-in.gradle.com","security.ubuntu.com","services.gradle.org","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","vsassets.io","www.googleapis.com","www.java.com","www.microsoft.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5","gemini-pro","haiku","any"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"auto":["large"],"claude":["agent","sonnet-6x","haiku","any"],"codex":["agent","gpt-5-codex","gpt-5","any"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"copilot":["agent","gpt-5.4","sonnet","gpt-5","any"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent","gemini-pro","gemini-flash","any"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite","copilot/raptor*mini*"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4.5*","copilot/*sonnet-4-5*","anthropic/*sonnet-4.5*","anthropic/*sonnet-4-5*","copilot/*sonnet-3.7*","copilot/*sonnet-3-7*","anthropic/*sonnet-3.7*","anthropic/*sonnet-3-7*","copilot/*sonnet-3.5*","copilot/*sonnet-3-5*","anthropic/*sonnet-3.5*","anthropic/*sonnet-3-5*"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.49"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.githubusercontent.com,*.google.com,*.googleapis.com,*.gradle-enterprise.cloud,*.gvt1.com,*.vsblob.vsassets.io,adoptium.net,aka.ms,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,cdn.azul.com,central.sonatype.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,develocity.apache.org,dist.nuget.org,dl.google.com,dlcdn.apache.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,ge.spockframework.org,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,gradle.org,gstatic.com,host.docker.internal,httpbin.org,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.java.com,www.microsoft.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.20 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: + AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: claude-opus-4.6 GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.68.3 + GH_AW_VERSION: v0.74.8 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_REF_NAME: ${{ github.ref_name }} @@ -762,7 +857,7 @@ jobs: bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" - name: Redact secrets in logs if: always() - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -788,10 +883,10 @@ jobs: - name: Ingest agent output id: collect_output if: always() - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.google.com,*.googleapis.com,*.gradle-enterprise.cloud,*.gvt1.com,*.vsblob.vsassets.io,adoptium.net,aka.ms,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,cdn.azul.com,central.sonatype.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,develocity.apache.org,dist.nuget.org,dl.google.com,dlcdn.apache.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,ge.spockframework.org,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,gradle.org,gstatic.com,host.docker.internal,httpbin.org,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.java.com,www.microsoft.com" + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.google.com,*.googleapis.com,*.gradle-enterprise.cloud,*.gvt1.com,*.vsblob.vsassets.io,adoptium.net,aka.ms,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,cdn.azul.com,central.sonatype.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,develocity.apache.org,dist.nuget.org,dl.google.com,dlcdn.apache.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,ge.spockframework.org,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,gradle.org,gstatic.com,host.docker.internal,httpbin.org,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.java.com,www.microsoft.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_COMMAND: review @@ -803,7 +898,7 @@ jobs: await main(); - name: Parse agent logs for step summary if: always() - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ with: @@ -815,7 +910,7 @@ jobs: - name: Parse MCP Gateway logs for step summary if: always() id: parse-mcp-gateway - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -828,9 +923,9 @@ jobs: env: AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs run: | - # Fix permissions on firewall logs so they can be uploaded as artifacts + # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts # AWF runs with sudo, creating files owned by root - sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + sudo chmod -R a+rX /tmp/gh-aw/sandbox/firewall 2>/dev/null || true # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) if command -v awf &> /dev/null; then awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" @@ -840,13 +935,23 @@ jobs: - name: Parse token usage for step summary if: always() continue-on-error: true - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); await main(); + - name: Print AWF reflect summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); + await main(); - name: Write agent output placeholder if missing if: always() run: | @@ -868,14 +973,17 @@ jobs: !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/pre-agent-audit.txt /tmp/gh-aw/agent/ /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/safeoutputs.jsonl /tmp/gh-aw/agent_output.json /tmp/gh-aw/aw-*.patch /tmp/gh-aw/aw-*.bundle + /tmp/gh-aw/awf-config.json /tmp/gh-aw/sandbox/firewall/logs/ /tmp/gh-aw/sandbox/firewall/audit/ + /tmp/gh-aw/sandbox/firewall/awf-reflect.json if-no-files-found: ignore conclusion: @@ -894,6 +1002,7 @@ jobs: concurrency: group: "gh-aw-conclusion-android-reviewer" cancel-in-progress: false + queue: max outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -902,11 +1011,17 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Android PR Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-reviewer.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -923,7 +1038,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Process no-op messages id: noop - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" @@ -940,7 +1055,7 @@ jobs: await main(); - name: Log detection run id: detection_runs - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Android PR Reviewer" @@ -956,7 +1071,7 @@ jobs: await main(); - name: Record missing tool id: missing_tool - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" @@ -970,7 +1085,7 @@ jobs: await main(); - name: Record incomplete id: report_incomplete - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" @@ -985,25 +1100,32 @@ jobs: - name: Handle agent failure id: handle_agent_failure if: always() - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Android PR Reviewer" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_WORKFLOW_ID: "android-reviewer" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.effective_tokens_rate_limit_error || 'false' }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" + GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "20" + GH_AW_MAX_EFFECTIVE_TOKENS: "25000000" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1013,7 +1135,7 @@ jobs: await main(); - name: Update reaction comment with completion status id: conclusion - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} @@ -1021,6 +1143,7 @@ jobs: GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_WORKFLOW_NAME: "Android PR Reviewer" GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_SAFE_OUTPUTS_RESULT: ${{ needs.safe_outputs.result }} GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} with: @@ -1047,11 +1170,17 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Android PR Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-reviewer.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1077,7 +1206,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.20 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 ghcr.io/github/gh-aw-firewall/squid:0.25.20 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.49 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 ghcr.io/github/gh-aw-firewall/squid:0.25.49 - name: Check if detection needed id: detection_guard if: always() @@ -1092,10 +1221,10 @@ jobs: echo "run_detection=false" >> "$GITHUB_OUTPUT" echo "Detection skipped: no agent outputs or patches to analyze" fi - - name: Clear MCP configuration for detection + - name: Clear MCP Config for detection if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | - rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" rm -f /home/runner/.copilot/mcp-config.json rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" - name: Prepare threat detection files @@ -1114,7 +1243,7 @@ jobs: ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true - name: Setup threat detection if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: WORKFLOW_NAME: "Android PR Reviewer" WORKFLOW_DESCRIPTION: "No description provided" @@ -1130,33 +1259,52 @@ jobs: run: | mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24' + package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.48 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.20 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.49 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true id: detection_agentic_execution # Copilot CLI tool arguments (sorted): timeout-minutes: 20 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.49/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.49"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.20 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: + AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: claude-opus-4.6 GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.68.3 + GH_AW_VERSION: v0.74.8 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_REF_NAME: ${{ github.ref_name }} GITHUB_SERVER_URL: ${{ github.server_url }} @@ -1177,34 +1325,60 @@ jobs: - name: Parse and conclude threat detection id: detection_conclusion if: always() - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); - await main(); + try { + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError && !detectionExecutionFailed) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } pre_activation: - if: "github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/review ') || startsWith(github.event.comment.body, '/review\n') || github.event.comment.body == '/review') && github.event.issue.pull_request != null || !(github.event_name == 'issue_comment')" + if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/review ') || startsWith(github.event.comment.body, '/review\n') || github.event.comment.body == '/review') && github.event.issue.pull_request != null || !(github.event_name == 'issue_comment'))" runs-on: ubuntu-slim outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_command_position.outputs.command_position_ok == 'true' }} matched_command: ${{ steps.check_command_position.outputs.matched_command }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Android PR Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-reviewer.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Check team membership for command workflow id: check_membership - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_REQUIRED_ROLES: "admin,maintainer,write" with: @@ -1216,7 +1390,7 @@ jobs: await main(); - name: Check command position id: check_command_position - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_COMMANDS: "[\"review\"]" with: @@ -1244,6 +1418,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: "claude-opus-4.6" + GH_AW_ENGINE_VERSION: "1.0.48" GH_AW_WORKFLOW_ID: "android-reviewer" GH_AW_WORKFLOW_NAME: "Android PR Reviewer" outputs: @@ -1256,11 +1431,17 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Android PR Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-reviewer.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1286,10 +1467,11 @@ jobs: echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - name: Process Safe Outputs id: process_safe_outputs - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.google.com,*.googleapis.com,*.gradle-enterprise.cloud,*.gvt1.com,*.vsblob.vsassets.io,adoptium.net,aka.ms,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,cdn.azul.com,central.sonatype.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,develocity.apache.org,dist.nuget.org,dl.google.com,dlcdn.apache.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,ge.spockframework.org,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,gradle.org,gstatic.com,host.docker.internal,httpbin.org,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.java.com,www.microsoft.com" + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.google.com,*.googleapis.com,*.gradle-enterprise.cloud,*.gvt1.com,*.vsblob.vsassets.io,adoptium.net,aka.ms,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,cdn.azul.com,central.sonatype.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dev.azure.com,develocity.apache.org,dist.nuget.org,dl.google.com,dlcdn.apache.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,ge.spockframework.org,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,gradle.org,gstatic.com,host.docker.internal,httpbin.org,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,vsassets.io,www.googleapis.com,www.java.com,www.microsoft.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request_review_comment\":{\"max\":50,\"side\":\"RIGHT\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"submit_pull_request_review\":{\"allowed_events\":[\"COMMENT\",\"REQUEST_CHANGES\"],\"max\":1}}" diff --git a/.github/workflows/android-reviewer.md b/.github/workflows/android-reviewer.md index b5e5eaae11d..94ae75eef5b 100644 --- a/.github/workflows/android-reviewer.md +++ b/.github/workflows/android-reviewer.md @@ -59,6 +59,7 @@ A maintainer commented `/review` on this pull request. Perform a thorough code r - One issue per inline comment. - If the same issue appears many times, flag it once listing all affected files. - Don't flag what CI catches (compiler errors, linter issues). -- Avoid false positives — verify concerns given the full file context. +- Avoid false positives — verify concerns given the full file context and project configuration (TargetFramework, references, available APIs). - **Never submit an APPROVE event.** Use COMMENT for clean PRs and REQUEST_CHANGES when issues are found. - Prioritize: bugs > safety > performance > missing tests > duplication > consistency > documentation. +- **Post suggestions as inline comments, not just in the summary.** If a suggestion can't be posted inline, omit it. diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index a676fcb2443..ab6f9d67623 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -27,7 +27,7 @@ jobs: fetch-depth: 1 - name: Install gh-aw extension - uses: github/gh-aw-actions/setup-cli@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup-cli@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 with: version: v0.68.3 diff --git a/.github/workflows/nightly-fix-finder.lock.yml b/.github/workflows/nightly-fix-finder.lock.yml new file mode 100644 index 00000000000..0343581f039 --- /dev/null +++ b/.github/workflows/nightly-fix-finder.lock.yml @@ -0,0 +1,1502 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"6e8efd250057e35651634be92fa4fc1563142e673ac7d1190f1eb21ed1dab205","compiler_version":"v0.74.8","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.6"} +# gh-aw-manifest: {"version":1,"secrets":["ANDROID_TEAM_PAT","COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"efa55847f72aadb03490d955263ff911bf758700","version":"v0.74.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.49"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.49"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.74.8). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# Nightly scan for random code improvement opportunities, files issues assigned to Copilot +# +# Secrets used: +# - ANDROID_TEAM_PAT +# - COPILOT_GITHUB_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 +# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +# - github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 +# +# Container images used: +# - ghcr.io/github/gh-aw-firewall/agent:0.25.49 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.49 +# - ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388 +# - ghcr.io/github/github-mcp-server:v1.0.4 +# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + +name: "Nightly Fix Finder" +on: + pull_request: + paths: + - .github/workflows/nightly-fix-finder.md + - .github/workflows/nightly-fix-finder.lock.yml + schedule: + - cron: "34 1 * * *" + # Friendly format: daily around 02:00 (scattered) + workflow_dispatch: + inputs: + aw_context: + default: "" + description: "Agent caller context (used internally by Agentic Workflows)." + required: false + type: string + category: + description: Category to scan (leave blank for random) + options: + - "" + - "0 - TODO/FIXME/HACK Comments" + - "1 - Nullable Reference Types" + - "2 - Obsolete API Usage" + - "3 - Performance Anti-patterns" + - "4 - Missing XML Documentation" + - "5 - General Mistakes" + - "6 - Unused Using Directives" + - "7 - Error Handling" + - "8 - String Literals in Error Messages" + required: false + type: choice + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref || github.run_id }}" + cancel-in-progress: true + +run-name: "Nightly Fix Finder" + +jobs: + activation: + needs: pre_activation + if: > + needs.pre_activation.outputs.activated == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) + runs-on: ubuntu-slim + permissions: + actions: read + contents: read + outputs: + body: ${{ steps.sanitized.outputs.body }} + comment_id: "" + comment_repo: "" + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + text: ${{ steps.sanitized.outputs.text }} + title: ${{ steps.sanitized.outputs.title }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Nightly Fix Finder" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/nightly-fix-finder.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Generate agentic run info + id: generate_aw_info + env: + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_MODEL: "claude-opus-4.6" + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_AGENT_VERSION: "1.0.48" + GH_AW_INFO_CLI_VERSION: "v0.74.8" + GH_AW_INFO_WORKFLOW_NAME: "Nightly Fix Finder" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","github","dotnet"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.25.49" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + GH_AW_COMPILED_STRICT: "true" + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + sparse-checkout: | + .github + .agents + .claude + .codex + .crush + .gemini + .opencode + .pi + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" + - name: Check workflow lock file + id: check-lock-file + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_FILE: "nightly-fix-finder.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_COMPILED_VERSION: "v0.74.8" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); + await main(); + - name: Compute current body text + id: sanitized + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + # poutine:ignore untrusted_checkout_exec + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" + { + cat << 'GH_AW_PROMPT_5efaa5e299ab2b6e_EOF' + + GH_AW_PROMPT_5efaa5e299ab2b6e_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_5efaa5e299ab2b6e_EOF' + + Tools: create_issue, assign_to_agent, missing_tool, missing_data, noop + + GH_AW_PROMPT_5efaa5e299ab2b6e_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_5efaa5e299ab2b6e_EOF' + + The following GitHub context information is available for this workflow: + {{#if github.actor}} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if github.repository}} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if github.workspace}} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} + - **issue-number**: #__GH_AW_EXPR_802A9F6A__ + {{/if}} + {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} + - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ + {{/if}} + {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} + - **pull-request-number**: #__GH_AW_EXPR_463A214A__ + {{/if}} + {{#if github.event.comment.id || github.aw.context.comment_id}} + - **comment-id**: __GH_AW_EXPR_FF1D34CE__ + {{/if}} + {{#if github.run_id}} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_5efaa5e299ab2b6e_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + cat << 'GH_AW_PROMPT_5efaa5e299ab2b6e_EOF' + + {{#runtime-import .github/workflows/nightly-fix-finder.md}} + GH_AW_PROMPT_5efaa5e299ab2b6e_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_ENGINE_ID: "copilot" + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: activation + include-hidden-files: true + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw-prompts/prompt-template.txt + /tmp/gh-aw/aw-prompts/prompt-import-tree.json + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base + /tmp/gh-aw/.github/agents + if-no-files-found: ignore + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + contents: read + issues: read + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_WORKFLOW_ID_SANITIZED: nightlyfixfinder + outputs: + agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} + model: ${{ needs.activation.outputs.model }} + model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Nightly Fix Finder" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/nightly-fix-finder.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Set runtime paths + id: set-runtime-paths + run: | + { + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" + } >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} + - env: + INPUT_CATEGORY: ${{ inputs.category }} + name: Collect codebase metrics + run: "mkdir -p /tmp/gh-aw/agent\nif [ -n \"$INPUT_CATEGORY\" ]; then\n CATEGORY_INDEX=\"${INPUT_CATEGORY%%\\ *}\"\nelse\n CATEGORY_INDEX=$(( RANDOM % 9 ))\nfi\n{\n echo \"## Selected Category: $CATEGORY_INDEX\"\n echo \"\"\n\n case $CATEGORY_INDEX in\n 0)\n echo \"## Category 0: TODO/FIXME/HACK Comments\"\n echo \"### Sample TODO/FIXME/HACK comments in src/\"\n grep -rn \"TODO\\|FIXME\\|HACK\\|XXX\" --include=\"*.cs\" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | shuf | head -20 || echo \"None found\"\n echo \"### Total count\"\n grep -rn \"TODO\\|FIXME\\|HACK\\|XXX\" --include=\"*.cs\" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | wc -l || true\n ;;\n 1)\n echo \"## Category 1: Files Missing Nullable Enable\"\n echo \"### C# files in src/ without #nullable enable (sample)\"\n grep -rL '#nullable enable' --include=\"*.cs\" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | shuf | head -20 || echo \"None found\"\n echo \"### Total count\"\n grep -rL '#nullable enable' --include=\"*.cs\" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | wc -l || true\n ;;\n 2)\n echo \"## Category 2: Obsolete API Usage\"\n echo \"### Files using [Obsolete] or #pragma warning disable CS0618 (sample)\"\n grep -rn \"\\[Obsolete\\]\\|CS0618\\|CS0612\" --include=\"*.cs\" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | shuf | head -20 || echo \"None found\"\n ;;\n 3)\n echo \"## Category 3: Performance Anti-patterns\"\n echo \"### String concatenation in loops (+=)\"\n grep -rn '+=' --include=\"*.cs\" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | grep -i 'string\\|str\\|result\\|output\\|sb\\|builder\\|message\\|msg\\|text\\|line\\|path\\|name\\|value' | grep -v '//' | grep -v 'test' | shuf | head -10 || echo \"None found\"\n echo \"\"\n echo \"### Sync-over-async (Task.Result, .Wait(), .GetAwaiter().GetResult())\"\n grep -rn 'Task\\.Result\\|\\.Wait()\\|\\.GetAwaiter()\\.GetResult()' --include=\"*.cs\" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | grep -v '//\\|test\\|Test' | shuf | head -10 || echo \"None found\"\n echo \"\"\n echo \"### Unnecessary LINQ allocations (.ToList(), .ToArray() that may not be needed)\"\n grep -rn '\\.ToList()\\|\\.ToArray()' --include=\"*.cs\" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | grep -v '//\\|test\\|Test' | shuf | head -10 || echo \"None found\"\n echo \"\"\n echo \"### Repeated string.Format or interpolation in loops\"\n grep -rn 'string\\.Format\\|string\\.Concat\\|String\\.Join' --include=\"*.cs\" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | grep -v '//\\|test\\|Test' | shuf | head -10 || echo \"None found\"\n ;;\n 4)\n echo \"## Category 4: Missing XML Documentation (src/Mono.Android/ only)\"\n echo \"### Public declarations in Mono.Android (shipped product) without XML docs\"\n echo \"### NOTE: Excludes Android.Runtime (plumbing), Java.Interop (bridge), and generated code\"\n grep -rn \"public \" --include=\"*.cs\" --exclude-dir=obj --exclude-dir=bin src/Mono.Android/ 2>/dev/null | grep -v \"Designer.cs\" | grep -v \"AssemblyInfo.cs\" | grep -v \"Android.Runtime\" | grep -v \"Java.Interop\" | grep -v \"/obj/\" | shuf | head -20 || echo \"None found\"\n ;;\n 5)\n echo \"## Category 5: General Mistakes\"\n echo \"### Random C# source files in src/ for general review (sample)\"\n find src -name '*.cs' -type f ! -path '*/obj/*' ! -path '*/bin/*' ! -name 'Designer.cs' ! -name 'AssemblyInfo.cs' 2>/dev/null | shuf | head -5\n ;;\n 6)\n echo \"## Category 6: Unused Using Directives\"\n echo \"### Files with many using directives (potential cleanup, sample)\"\n for f in $(find src -name '*.cs' -type f ! -path '*/obj/*' ! -path '*/bin/*' ! -name 'Designer.cs' 2>/dev/null | shuf | head -30); do\n count=$(grep -c \"^using \" \"$f\" 2>/dev/null || true)\n if [ \"${count:-0}\" -gt 10 ]; then\n echo \" $f: $count using directives\"\n fi\n done\n ;;\n 7)\n echo \"## Category 7: Error Handling\"\n echo \"### Bare catch blocks that may swallow exceptions (sample)\"\n grep -rnP \"catch\\s*\\(Exception\\b\" --include=\"*.cs\" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | shuf | head -20 || echo \"None found\"\n ;;\n 8)\n echo \"## Category 8: String Literals in Error Messages\"\n echo \"### Hardcoded error strings that could be in Resources.resx (sample)\"\n grep -rn 'Log\\.\\(Error\\|Warning\\)\\|LogError\\|LogWarning\\|LogCodedError\\|LogCodedWarning' --include=\"*.cs\" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | grep '\"' | grep -v \"Properties.Resources\" | shuf | head -20 || echo \"None found\"\n ;;\n esac\n} > /tmp/gh-aw/agent/scan-results.md\necho \"✅ Category $CATEGORY_INDEX scan complete → /tmp/gh-aw/agent/scan-results.md\"\n" + + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + github.event.pull_request || github.event.issue.pull_request + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.48 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.49 + - name: Parse integrity filter lists + id: parse-guard-vars + env: + GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} + GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} + GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.49 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 ghcr.io/github/gh-aw-firewall/squid:0.25.49 ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388 ghcr.io/github/github-mcp-server:v1.0.4 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + - name: Generate Safe Outputs Config + env: + ANDROID_TEAM_PAT: ${{ secrets.ANDROID_TEAM_PAT }} + run: | + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << GH_AW_SAFE_OUTPUTS_CONFIG_401ff4834d97e164_EOF + {"assign_to_agent":{"github-token":"${ANDROID_TEAM_PAT}","max":1,"model":"claude-opus-4.6","target":"*"},"create_issue":{"close_older_issues":false,"expires":168,"labels":["automated","code-quality"],"max":1,"title_prefix":"[fix-finder] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_401ff4834d97e164_EOF + - name: Generate Safe Outputs Tools + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "assign_to_agent": " CONSTRAINTS: Maximum 1 issue(s) can be assigned to agent.", + "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[fix-finder] \". Labels [\"automated\" \"code-quality\"] will be automatically added." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "assign_to_agent": { + "defaultMax": 1, + "fields": { + "agent": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "issue_number": { + "issueNumberOrTemporaryId": true + }, + "pull_number": { + "optionalPositiveInteger": true + }, + "pull_request_repo": { + "type": "string", + "maxLength": 256 + }, + "repo": { + "type": "string", + "maxLength": 256 + } + }, + "customValidation": "requiresOneOf:issue_number,pull_number" + }, + "create_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "fields": { + "type": "array" + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "parent": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "temporary_id": { + "type": "string" + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } + } + } + } + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="8080" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + export MCP_GATEWAY_HOST_DOMAIN="localhost" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + case "${DOCKER_HOST:-}" in + unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; + /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; + * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; + esac + DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.9' + + mkdir -p /home/runner/.copilot + GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) + cat << GH_AW_MCP_CONFIG_6f25d3e3efe6bf1d_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v1.0.4", + "env": { + "GITHUB_HOST": "\${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "repos,issues" + }, + "guard-policies": { + "allow-only": { + "approval-labels": ${{ steps.parse-guard-vars.outputs.approval_labels }}, + "blocked-users": ${{ steps.parse-guard-vars.outputs.blocked_users }}, + "min-integrity": "none", + "repos": "all", + "trusted-users": ${{ steps.parse-guard-vars.outputs.trusted_users }} + } + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ] + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_6f25d3e3efe6bf1d_EOF + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); + - name: Clean credentials + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Audit pre-agent workspace + id: pre_agent_audit + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + # --allow-tool github + # --allow-tool safeoutputs + # --allow-tool shell(awk:*) + # --allow-tool shell(cat) + # --allow-tool shell(cat:*) + # --allow-tool shell(date) + # --allow-tool shell(date:*) + # --allow-tool shell(echo) + # --allow-tool shell(find src -name "*.cs" -type f) + # --allow-tool shell(grep) + # --allow-tool shell(grep:*) + # --allow-tool shell(head) + # --allow-tool shell(head:*) + # --allow-tool shell(ls) + # --allow-tool shell(printf) + # --allow-tool shell(pwd) + # --allow-tool shell(safeoutputs:*) + # --allow-tool shell(sed:*) + # --allow-tool shell(shuf:*) + # --allow-tool shell(sort) + # --allow-tool shell(sort:*) + # --allow-tool shell(tail) + # --allow-tool shell(tail:*) + # --allow-tool shell(uniq) + # --allow-tool shell(wc) + # --allow-tool shell(wc:*) + # --allow-tool shell(xargs:*) + # --allow-tool shell(yq) + # --allow-tool write + timeout-minutes: 30 + run: | + set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" + (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.49/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","*.vsblob.vsassets.io","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dist.nuget.org","docs.github.com","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","patch-diff.githubusercontent.com","pkgs.dev.azure.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.microsoft.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5","gemini-pro","haiku","any"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"auto":["large"],"claude":["agent","sonnet-6x","haiku","any"],"codex":["agent","gpt-5-codex","gpt-5","any"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"copilot":["agent","gpt-5.4","sonnet","gpt-5","any"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent","gemini-pro","gemini-flash","any"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite","copilot/raptor*mini*"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4.5*","copilot/*sonnet-4-5*","anthropic/*sonnet-4.5*","anthropic/*sonnet-4-5*","copilot/*sonnet-3.7*","copilot/*sonnet-3-7*","anthropic/*sonnet-3.7*","anthropic/*sonnet-3-7*","copilot/*sonnet-3.5*","copilot/*sonnet-3-5*","anthropic/*sonnet-3.5*","anthropic/*sonnet-3-5*"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.49"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi + # shellcheck disable=SC1003 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(awk:*)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(cat:*)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(date:*)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find src -name "*.cs" -type f)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(grep:*)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(head:*)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sed:*)'\'' --allow-tool '\''shell(shuf:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(sort:*)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(tail:*)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(wc:*)'\'' --allow-tool '\''shell(xargs:*)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + AWF_REFLECT_ENABLED: 1 + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: claude-opus-4.6 + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_PHASE: agent + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_VERSION: v0.74.8 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Detect Copilot errors + id: detect-copilot-errors + if: always() + continue-on-error: true + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'ANDROID_TEAM_PAT,COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_ANDROID_TEAM_PAT: ${{ secrets.ANDROID_TEAM_PAT }} + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Append agent step summary + if: always() + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + id: parse-mcp-gateway + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+rX /tmp/gh-aw/sandbox/firewall 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Parse token usage for step summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); + - name: Print AWF reflect summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); + await main(); + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ + /tmp/gh-aw/agent_usage.json + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/pre-agent-audit.txt + /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/aw-*.patch + /tmp/gh-aw/aw-*.bundle + /tmp/gh-aw/awf-config.json + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/audit/ + /tmp/gh-aw/sandbox/firewall/awf-reflect.json + if-no-files-found: ignore + + conclusion: + needs: + - activation + - agent + - detection + - safe_outputs + if: > + always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || + needs.activation.outputs.stale_lock_file_failed == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + concurrency: + group: "gh-aw-conclusion-nightly-fix-finder" + cancel-in-progress: false + queue: max + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Nightly Fix Finder" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/nightly-fix-finder.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Process no-op messages + id: noop + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Nightly Fix Finder" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Log detection run + id: detection_runs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Nightly Fix Finder" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Nightly Fix Finder" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Nightly Fix Finder" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure + id: handle_agent_failure + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Nightly Fix Finder" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "nightly-fix-finder" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.effective_tokens_rate_limit_error || 'false' }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} + GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} + GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" + GH_AW_ASSIGNMENT_ERRORS: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_errors }} + GH_AW_ASSIGNMENT_ERROR_COUNT: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_error_count }} + GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "false" + GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" + GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" + GH_AW_TIMEOUT_MINUTES: "30" + GH_AW_MAX_EFFECTIVE_TOKENS: "25000000" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + + detection: + needs: + - activation + - agent + if: > + always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_reason: ${{ steps.detection_conclusion.outputs.reason }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Nightly Fix Finder" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/nightly-fix-finder.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository for patch context + if: needs.agent.outputs.has_patch == 'true' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + # --- Threat Detection --- + - name: Clean stale firewall files from agent artifact + run: | + rm -rf /tmp/gh-aw/sandbox/firewall/logs + rm -rf /tmp/gh-aw/sandbox/firewall/audit + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.49 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.49 ghcr.io/github/gh-aw-firewall/squid:0.25.49 + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP Config for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + for f in /tmp/gh-aw/aw-*.bundle; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + WORKFLOW_NAME: "Nightly Fix Finder" + WORKFLOW_DESCRIPTION: "Nightly scan for random code improvement opportunities, files issues assigned to Copilot" + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24' + package-manager-cache: false + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.48 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.49 + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" + (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.49/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.49"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi + # shellcheck disable=SC1003 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + AWF_REFLECT_ENABLED: 1 + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: claude-opus-4.6 + GH_AW_PHASE: detection + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_VERSION: v0.74.8 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: detection + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Parse and conclude threat detection + id: detection_conclusion + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} + GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" + with: + script: | + try { + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError && !detectionExecutionFailed) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } + + pre_activation: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + matched_command: '' + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Nightly Fix Finder" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/nightly-fix-finder.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_REQUIRED_ROLES: "admin,maintainer,write" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + await main(); + + safe_outputs: + needs: + - activation + - agent + - detection + if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + timeout-minutes: 15 + env: + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/nightly-fix-finder" + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: "claude-opus-4.6" + GH_AW_ENGINE_VERSION: "1.0.48" + GH_AW_WORKFLOW_ID: "nightly-fix-finder" + GH_AW_WORKFLOW_NAME: "Nightly Fix Finder" + outputs: + assign_to_agent_assigned: ${{ steps.process_safe_outputs.outputs.assign_to_agent_assigned }} + assign_to_agent_assignment_error_count: ${{ steps.process_safe_outputs.outputs.assign_to_agent_assignment_error_count }} + assign_to_agent_assignment_errors: ${{ steps.process_safe_outputs.outputs.assign_to_agent_assignment_errors }} + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} + created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@efa55847f72aadb03490d955263ff911bf758700 # v0.74.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Nightly Fix Finder" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/nightly-fix-finder.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.48" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"assign_to_agent\":{\"github-token\":\"${{ secrets.ANDROID_TEAM_PAT }}\",\"max\":1,\"model\":\"claude-opus-4.6\",\"target\":\"*\"},\"create_issue\":{\"close_older_issues\":false,\"expires\":168,\"labels\":[\"automated\",\"code-quality\"],\"max\":1,\"title_prefix\":\"[fix-finder] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_ASSIGN_TO_AGENT_TOKEN: ${{ secrets.ANDROID_TEAM_PAT }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload Safe Outputs Items + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: safe-outputs-items + path: | + /tmp/gh-aw/safe-output-items.jsonl + /tmp/gh-aw/temporary-id-map.json + if-no-files-found: ignore + diff --git a/.github/workflows/nightly-fix-finder.md b/.github/workflows/nightly-fix-finder.md new file mode 100644 index 00000000000..de65684b6aa --- /dev/null +++ b/.github/workflows/nightly-fix-finder.md @@ -0,0 +1,323 @@ +--- +on: + pull_request: + paths: + - .github/workflows/nightly-fix-finder.md + - .github/workflows/nightly-fix-finder.lock.yml + schedule: + - cron: daily around 02:00 + workflow_dispatch: + inputs: + category: + description: Category to scan (leave blank for random) + options: + - "" + - "0 - TODO/FIXME/HACK Comments" + - "1 - Nullable Reference Types" + - "2 - Obsolete API Usage" + - "3 - Performance Anti-patterns" + - "4 - Missing XML Documentation" + - "5 - General Mistakes" + - "6 - Unused Using Directives" + - "7 - Error Handling" + - "8 - String Literals in Error Messages" + required: false + type: choice +permissions: + contents: read + issues: read +network: + allowed: + - defaults + - github + - dotnet +safe-outputs: + assign-to-agent: + github-token: ${{ secrets.ANDROID_TEAM_PAT }} + model: claude-opus-4.6 + target: "*" + create-issue: + close-older-issues: false + expires: 7d + labels: + - automated + - code-quality + title-prefix: "[fix-finder] " + noop: null + report-failure-as-issue: false +steps: +- env: + INPUT_CATEGORY: ${{ inputs.category }} + name: Collect codebase metrics + run: | + mkdir -p /tmp/gh-aw/agent + if [ -n "$INPUT_CATEGORY" ]; then + CATEGORY_INDEX="${INPUT_CATEGORY%%\ *}" + else + CATEGORY_INDEX=$(( RANDOM % 9 )) + fi + { + echo "## Selected Category: $CATEGORY_INDEX" + echo "" + + case $CATEGORY_INDEX in + 0) + echo "## Category 0: TODO/FIXME/HACK Comments" + echo "### Sample TODO/FIXME/HACK comments in src/" + grep -rn "TODO\|FIXME\|HACK\|XXX" --include="*.cs" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | shuf | head -20 || echo "None found" + echo "### Total count" + grep -rn "TODO\|FIXME\|HACK\|XXX" --include="*.cs" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | wc -l || true + ;; + 1) + echo "## Category 1: Files Missing Nullable Enable" + echo "### C# files in src/ without #nullable enable (sample)" + grep -rL '#nullable enable' --include="*.cs" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | shuf | head -20 || echo "None found" + echo "### Total count" + grep -rL '#nullable enable' --include="*.cs" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | wc -l || true + ;; + 2) + echo "## Category 2: Obsolete API Usage" + echo "### Files using [Obsolete] or #pragma warning disable CS0618 (sample)" + grep -rn "\[Obsolete\]\|CS0618\|CS0612" --include="*.cs" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | shuf | head -20 || echo "None found" + ;; + 3) + echo "## Category 3: Performance Anti-patterns" + echo "### String concatenation in loops (+=)" + grep -rn '+=' --include="*.cs" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | grep -i 'string\|str\|result\|output\|sb\|builder\|message\|msg\|text\|line\|path\|name\|value' | grep -v '//' | grep -v 'test' | shuf | head -10 || echo "None found" + echo "" + echo "### Sync-over-async (Task.Result, .Wait(), .GetAwaiter().GetResult())" + grep -rn 'Task\.Result\|\.Wait()\|\.GetAwaiter()\.GetResult()' --include="*.cs" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | grep -v '//\|test\|Test' | shuf | head -10 || echo "None found" + echo "" + echo "### Unnecessary LINQ allocations (.ToList(), .ToArray() that may not be needed)" + grep -rn '\.ToList()\|\.ToArray()' --include="*.cs" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | grep -v '//\|test\|Test' | shuf | head -10 || echo "None found" + echo "" + echo "### Repeated string.Format or interpolation in loops" + grep -rn 'string\.Format\|string\.Concat\|String\.Join' --include="*.cs" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | grep -v '//\|test\|Test' | shuf | head -10 || echo "None found" + ;; + 4) + echo "## Category 4: Missing XML Documentation (src/Mono.Android/ only)" + echo "### Public declarations in Mono.Android (shipped product) without XML docs" + echo "### NOTE: Excludes Android.Runtime (plumbing), Java.Interop (bridge), and generated code" + grep -rn "public " --include="*.cs" --exclude-dir=obj --exclude-dir=bin src/Mono.Android/ 2>/dev/null | grep -v "Designer.cs" | grep -v "AssemblyInfo.cs" | grep -v "Android.Runtime" | grep -v "Java.Interop" | grep -v "/obj/" | shuf | head -20 || echo "None found" + ;; + 5) + echo "## Category 5: General Mistakes" + echo "### Random C# source files in src/ for general review (sample)" + find src -name '*.cs' -type f ! -path '*/obj/*' ! -path '*/bin/*' ! -name 'Designer.cs' ! -name 'AssemblyInfo.cs' 2>/dev/null | shuf | head -5 + ;; + 6) + echo "## Category 6: Unused Using Directives" + echo "### Files with many using directives (potential cleanup, sample)" + for f in $(find src -name '*.cs' -type f ! -path '*/obj/*' ! -path '*/bin/*' ! -name 'Designer.cs' 2>/dev/null | shuf | head -30); do + count=$(grep -c "^using " "$f" 2>/dev/null || true) + if [ "${count:-0}" -gt 10 ]; then + echo " $f: $count using directives" + fi + done + ;; + 7) + echo "## Category 7: Error Handling" + echo "### Bare catch blocks that may swallow exceptions (sample)" + grep -rnP "catch\s*\(Exception\b" --include="*.cs" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | shuf | head -20 || echo "None found" + ;; + 8) + echo "## Category 8: String Literals in Error Messages" + echo "### Hardcoded error strings that could be in Resources.resx (sample)" + grep -rn 'Log\.\(Error\|Warning\)\|LogError\|LogWarning\|LogCodedError\|LogCodedWarning' --include="*.cs" --exclude-dir=obj --exclude-dir=bin src/ 2>/dev/null | grep '"' | grep -v "Properties.Resources" | shuf | head -20 || echo "None found" + ;; + esac + } > /tmp/gh-aw/agent/scan-results.md + echo "✅ Category $CATEGORY_INDEX scan complete → /tmp/gh-aw/agent/scan-results.md" +description: Nightly scan for random code improvement opportunities, files issues assigned to Copilot +engine: + id: copilot + model: claude-opus-4.6 +strict: true +timeout-minutes: 30 +tools: + bash: + - find src -name "*.cs" -type f + - grep:* + - wc:* + - head:* + - tail:* + - sort:* + - cat:* + - awk:* + - sed:* + - shuf:* + - date:* + - xargs:* + github: + min-integrity: none + toolsets: + - repos + - issues +--- +# Nightly Fix Finder + +You are the Nightly Fix Finder Agent — an expert system that scans the dotnet/android repository each night for random code improvement opportunities and files actionable issues for Copilot to fix. + +## Mission + +Each night, select one scan category, analyze the pre-collected data, find one specific actionable improvement, create a well-scoped issue, and assign Copilot to fix it. + +## Current Context + +- **Repository**: ${{ github.repository }} +- **Run Date**: Each run picks a random category (0-9) using $RANDOM +- **Pre-computed scan results**: `/tmp/gh-aw/agent/scan-results.md` + +## Phase 1: Load Scan Results + +### 1.1 Read Pre-computed Data + +Read `/tmp/gh-aw/agent/scan-results.md` which contains pre-collected metrics for **one randomly selected category**. The file header tells you which category was selected. + +### 1.2 Identify the Category + +The scan results start with `## Selected Category: N` where N is 0-9. The file ONLY contains data for that one category — you MUST work with whatever category was selected: + +| Index | Category | Description | +|-------|----------|-------------| +| 0 | TODO/FIXME/HACK Comments | Find stale TODO/FIXME/HACK comments that should be resolved | +| 1 | Nullable Reference Types | Find C# files missing `#nullable enable` that should be opted in | +| 2 | Obsolete API Usage | Find uses of `[Obsolete]` members that should be updated | +| 3 | Performance Anti-patterns | Find performance issues: sync-over-async, string concat in loops, unnecessary allocations | +| 4 | Missing XML Documentation | Find public APIs without XML doc comments | +| 5 | General Mistakes | Read random source files and find real bugs, logic errors, or code smells | +| 6 | Unused Using Directives | Find files with excessive using directives that could be cleaned up | +| 7 | Error Handling | Find bare `catch (Exception)` blocks that swallow errors | +| 8 | String Literals | Find hardcoded error strings that should be in `Properties.Resources` | + +If the selected category has no actionable findings in the scan results, call `noop` — do NOT switch to a different category. + +## Phase 2: Deep Analysis + +Using the pre-collected sample data for the selected category, pick **one specific, well-scoped improvement**. Then do a deeper investigation: + +1. **Read the actual source file(s)** involved to understand the full context +2. **Verify the issue is real** — not a false positive +3. **Determine the fix** — what specifically needs to change +4. **Scope it appropriately** — one issue should be completable in a single PR + +### Category-Specific Guidance + +#### TODO/FIXME/HACK Comments (Category 0) +- Pick a TODO that is clearly stale or has a concrete action +- Check if the TODO references an old bug number or feature that's already done +- The issue should ask to either implement the TODO or remove it if no longer relevant + +#### Nullable Reference Types (Category 1) +- Pick a file that's a good candidate for `#nullable enable` +- Prefer files in `src/Xamarin.Android.Build.Tasks/` as they follow clear patterns +- Follow the repo's nullable conventions: no `!` operator, use `IsNullOrEmpty()` extension methods +- The issue should reference the repo's nullable guidelines + +#### Obsolete API Usage (Category 2) +- Find calls to deprecated APIs and suggest the modern replacement +- Check if the obsolete message suggests an alternative + +#### Performance Anti-patterns (Category 3) +- Look for real performance issues, not micro-optimizations +- **Sync-over-async**: `Task.Result`, `.Wait()`, `.GetAwaiter().GetResult()` in non-test code can cause deadlocks +- **String concatenation in loops**: `+=` on strings inside loops should use `StringBuilder` +- **Unnecessary allocations**: `.ToList()` or `.ToArray()` where the collection is only enumerated once +- **Repeated formatting**: `string.Format` or interpolation inside tight loops +- Verify the code is actually in a hot path or loop before filing — don't flag one-time startup code + +#### Missing XML Documentation (Category 4) +- **ONLY** look at public APIs in `src/Mono.Android/` — this is the shipped product (Mono.Android.dll) +- **EXCLUDE** `Android.Runtime` namespace (plumbing/bridge types like `InputStreamInvoker`, `JNIEnv`) +- **EXCLUDE** `Java.Interop` namespace (low-level interop, not user-facing) +- Focus on types developers actually use: `Android.App`, `Android.Widget`, `Android.Views`, etc. +- Do NOT file issues for XML docs in build tasks, tools, or test code — those are internal +- The issue should provide example doc comments for the specific APIs + +#### General Mistakes (Category 5) +- Read the randomly selected source files thoroughly +- Look for real bugs: logic errors, off-by-one, null dereferences, race conditions, resource leaks +- Look for code smells: dead code, unreachable branches, copy-paste errors, incorrect exception handling +- Do NOT file issues about formatting, whitespace, or style — those are not actionable +- The issue should describe the actual bug or problem with concrete evidence + +#### Unused Using Directives (Category 6) +- Pick files with >10 using directives that likely have unused ones +- The issue should ask to clean up unnecessary usings + +#### Error Handling (Category 7) +- Find `catch (Exception)` blocks that don't log or rethrow +- The issue should suggest proper error handling patterns + +#### String Literals (Category 8) +- Find hardcoded strings in `LogError`/`LogWarning` calls +- The issue should ask to move them to `Properties.Resources` with proper `XA####` error codes +- **Important**: When new `XA####` error codes are created, the issue MUST also instruct to: + 1. Create a markdown doc file at `Documentation/docs-mobile/messages/xa####.md` (following the existing format: frontmatter with title/description/date/f1_keywords, then sections for Example messages, Issue explanation, and Solution) + 2. Add the new code to the table of contents in `Documentation/docs-mobile/messages/index.md` + +## Phase 3: Create Issue + +Create exactly **one** well-scoped issue using `create_issue`. The issue must be specific enough that Copilot can implement the fix without ambiguity. + +### Issue Template + +Use this structure: + +```markdown +### Problem + +[1-2 sentences describing what's wrong and why it matters] + +### Location + +- **File(s)**: `path/to/file.cs` +- **Line(s)**: [specific lines if applicable] + +### Current Code + +[Show the relevant code snippet] + +### Suggested Fix + +[Describe exactly what should change, with example code if possible] + +### Guidelines + +- [Any repo-specific conventions to follow] +- [Reference to relevant documentation] + +### Acceptance Criteria + +- [ ] [Specific, verifiable criteria] +- [ ] All tests pass +- [ ] No new warnings introduced +``` + +## Phase 4: Assign to Copilot + +After creating the issue, use `assign_to_agent` to assign Copilot to work on it. You **MUST** pass the `issue_number` parameter — use the `temporary_id` from the `create_issue` call (**without** the `#` prefix). The safe-output is configured with `model: "claude-opus-4.6"` so Copilot will use Claude Opus 4.6 to implement the fix. + +Example call sequence: +1. `create_issue` with `temporary_id: "aw_fix123"`, `title`, `body` +2. `assign_to_agent` with `issue_number: "aw_fix123"` + +## Rules + +1. **One issue per run** — Create exactly one issue, not multiple +2. **Be specific** — The issue must be implementable from the description alone +3. **Verify before filing** — Read the actual source to confirm the issue is real +4. **Skip non-actionable findings** — If nothing good is found in any category, call `noop` +5. **Respect repo conventions** — Follow dotnet/android formatting and coding style +6. **Don't duplicate** — Search for existing issues with similar titles before creating + +## Important + +You **MUST** end by calling exactly one set of safe output tools: + +- **`create_issue` + `assign_to_agent`**: When a valid improvement is found +- **`noop`**: When no actionable improvement was found after checking all categories + +```json +{"noop": {"message": "No actionable improvements found today after scanning all 10 categories."}} +``` \ No newline at end of file diff --git a/Configuration.props b/Configuration.props index 51df0fa4e78..fd4f45d1a38 100644 --- a/Configuration.props +++ b/Configuration.props @@ -22,11 +22,9 @@ <_StandardLibraryPath Condition=" '$(TargetFrameworkVersion)' == '' ">$([Microsoft.Build.Utilities.ToolLocationHelper]::GetPathToStandardLibraries('.NETFramework', 'v4.7.2', '')) v4.7.2 v4.7.1 - - 21 - - 24 + + 24 37 37.0 @@ -75,8 +73,6 @@ $(MicrosoftAndroidSdkPackDir)tools\ $(MicrosoftAndroidPacksRootDir)Microsoft.Android.Ref.$(AndroidLatestStableApiLevel)\$(AndroidPackVersion)\analyzers\dotnet\cs\ -j$(HostCpuCount) - mono - --debug=casts $(USERPROFILE) v1.0 $(HOME)\android-archives @@ -101,11 +97,6 @@ armeabi-v7a:arm64-v8a:x86:x86_64 $(MSBuildThisFileDirectory)external\Java.Interop $(MSBuildThisFileDirectory)external\mono - https://download.mono-project.com/archive/6.12.0/macos-10-universal/MonoFramework-MDK-6.12.0.188.macos10.xamarin.universal.pkg - 6.12.0.188 - $(MonoRequiredMinimumVersion) - False - True $(MSBuildThisFileDirectory)external\sqlite $(MSBuildThisFileDirectory)external\libunwind $(BootstrapOutputDirectory)\libunwind @@ -115,20 +106,22 @@ armeabi-v7a;x86 arm64-v8a;x86_64 $(AllSupported32BitTargetAndroidAbis);$(AllSupported64BitTargetAndroidAbis) - - - - - $(XABuildToolsPackagePrefixMacOS) - $(XABuildToolsPackagePrefixWindows) 36 36.0.0 + 04E7F3A72044DE4926FA038FA0E251A37BBA1E1C3FB8BEAB6F8401BFD9EB4BF3 + 5D9AC77FB6FF43D9DA518A337B4FCF8F9097113DF531D99CCEFE80EF7CE8250B + AA1095CB14D83E483818A748A2C06FAAEB8E601561B06A356A119A1B2CA280D3 36.0.0 1.18.3 + A099CFA1543F55593BC2ED16A70A7C67FE54B1747BB7301F37FDFD6D91028E29 + L_18.1.6-8.0.0-1 + 5394EA6C411DA3A08BF0E6A87E1DD1B98BD2562B896C0CACD18E29E2FD4C0660 + + 21.0.8 + jdk-21 + jdk-21.0.8+9 + $(AndroidToolchainDirectory)\$(MicrosoftOpenJDKFolder) $(NUGET_PACKAGES) $(userprofile)\.nuget\packages $(HOME)/.nuget/packages @@ -207,8 +200,6 @@ 3.16.3 - <_Runtime Condition=" '$(HostOS)' != 'Windows' ">$(ManagedRuntime) $(ManagedRuntimeArgs) - <_NUnit>$(_Runtime) $(XAPackagesDir)\nunit.consolerunner\$(NUnitConsoleVersion)\tools\nunit3-console.exe diff --git a/Documentation/building/configuration.md b/Documentation/building/configuration.md index 6d1e059a7b2..75d278b2bd0 100644 --- a/Documentation/building/configuration.md +++ b/Documentation/building/configuration.md @@ -102,13 +102,6 @@ Overridable MSBuild properties include: check when building Mono.Android if set to `True`. The check is performed by default. - * `$(IgnoreMaxMonoVersion)`: Skip the enforcement of the `$(MonoRequiredMaximumVersion)` - property. This is so that developers can run against the latest - and greatest. But the build system can enforce the min and max - versions. The default is `true`, however on CI we use: - - /p:IgnoreMaxMonoVersion=False - * `$(JavaInteropSourceDirectory)`: The Java.Interop source directory to build and reference projects from. By default, this is `external/Java.Interop` directory, maintained by `git submodule update`. @@ -124,16 +117,6 @@ Overridable MSBuild properties include: the number of CPU cores used when **make**(1) executes. By default this uses `-jCOUNT`, where `COUNT` is obtained from `sysctl hw.ncpu`. - * `$(MonoRequiredMinimumVersion)`: The minimum *system* mono version that is - supported in order to allow a build to continue. Policy is to require a - system mono which corresponds vaguely to the [`external/mono`](external) - version. This is not strictly required; older mono versions *may* work, they - just are not tested, and thus not guaranteed or supported. - - * `$(MonoRequiredMaximumVersion)`: The max *system* mono version that is - required. This is so that we can ensure a stable build environment by - making sure we dont install unstable versions. - * `$(MonoSgenBridgeVersion)`: The Mono SGEN Bridge version to support. Valid values include: diff --git a/Documentation/building/unix/dependencies.md b/Documentation/building/unix/dependencies.md index 2577a2b5922..67c3fcfbc9e 100644 --- a/Documentation/building/unix/dependencies.md +++ b/Documentation/building/unix/dependencies.md @@ -3,7 +3,6 @@ Building .NET for Android requires: * [Homebrew](#homebrew) - * [Latest Mono](#mono-sdk) * [The Java Development Kit (JDK)](#jdk) * [Autotools (`autoconf`, `automake`, etc.)](#autotools) * [The Android SDK and NDK](#ndk) @@ -46,15 +45,9 @@ allow Homebrew to be installed: ## Mono MDK -Latest Mono is required to build on [macOS][osx-mono] and Linux. -The build will tell you if your version is outdated. - -[osx-mono]: http://www.mono-project.com/download/#download-mac -[xmlpeek]: https://msdn.microsoft.com/en-us/library/ff598684.aspx - -The minimum Mono version which is checked for can be overridden by the -`$(MonoRequiredMinimumVersion)` MSBuild property, but things may not build. -(This is your warning.) +Mono may be needed by the [API documentation pipeline](../../../build-tools/automation/azure-pipelines-apidocs.yaml) +for running `mdoc`, but it is provisioned independently by that pipeline via `boots`. +It is no longer required for local builds. diff --git a/Documentation/docs-mobile/TOC.yml b/Documentation/docs-mobile/TOC.yml index 35afae71909..20ab4298119 100644 --- a/Documentation/docs-mobile/TOC.yml +++ b/Documentation/docs-mobile/TOC.yml @@ -258,12 +258,6 @@ href: messages/xa1028.md - name: XA1029 href: messages/xa1029.md - - name: XA1031 - href: messages/xa1031.md - - name: XA1032 - href: messages/xa1032.md - - name: XA1033 - href: messages/xa1033.md - name: XA1035 href: messages/xa1035.md - name: XA1036 diff --git a/Documentation/docs-mobile/binding-libs/msbuild-reference/build-items.md b/Documentation/docs-mobile/binding-libs/msbuild-reference/build-items.md index 12c9c97eef5..14ea33bfb9c 100644 --- a/Documentation/docs-mobile/binding-libs/msbuild-reference/build-items.md +++ b/Documentation/docs-mobile/binding-libs/msbuild-reference/build-items.md @@ -173,6 +173,7 @@ hosted in Maven. | - | - | | Version | Required string. The version of the Java library that should be downloaded from Maven. Defaults to `true`. | | Repository | Optional string. Specifies Maven repository to use. Supported values are `Central`, `Google`, or an `https` URL to a Maven repository. Defaults to `Central`. | +| AllowInsecureHttp | Optional boolean. When `Repository` is an `http://` URL, this must be set to `true` to allow the insecure connection. Defaults to `false`. Using HTTPS is strongly recommended for supply-chain security. | | Bind | Optional boolean. Specifies whether the Java library should have a C# binding generated for it. Defaults to `true`. | | Pack | Optional boolean. Specifies whether the Java library should be included in the project output. Defaults to `true`. | diff --git a/Documentation/docs-mobile/building-apps/build-items.md b/Documentation/docs-mobile/building-apps/build-items.md index a978ac0e318..46bd157c07b 100644 --- a/Documentation/docs-mobile/building-apps/build-items.md +++ b/Documentation/docs-mobile/building-apps/build-items.md @@ -296,6 +296,9 @@ The following MSBuild metadata are supported: - `%(Version)`: Required version of the Java library referenced by `%(Include)`. - `%(Repository)`: Optional Maven repository to use. Supported values are `Central` (default), `Google`, or an `https` URL to a Maven repository. +- `%(AllowInsecureHttp)`: Optional boolean. When `%(Repository)` is an `http://` URL, this must be + set to `true` to allow the insecure connection. Defaults to `false`. Using HTTPS is strongly + recommended for supply-chain security. The `` item is translated to [`AndroidLibrary`](#androidlibrary), so any metadata supported by diff --git a/Documentation/docs-mobile/building-apps/build-properties.md b/Documentation/docs-mobile/building-apps/build-properties.md index 764074de9f3..de77355bcbb 100644 --- a/Documentation/docs-mobile/building-apps/build-properties.md +++ b/Documentation/docs-mobile/building-apps/build-properties.md @@ -601,58 +601,9 @@ The default value is `true`. When set to `false`, disables the generation of `Re ## AndroidHttpClientHandlerType -Controls the default -`System.Net.Http.HttpMessageHandler` implementation which will be used by -the `System.Net.Http.HttpClient` default constructor. The value is an -assembly-qualified type name of an `HttpMessageHandler` subclass, suitable -for use with -[`System.Type.GetType(string)`](/dotnet/api/system.type.gettype#System_Type_GetType_System_String_). - -In .NET 6 and newer, this property has effect only when used together -with [`$(UseNativeHttpHandler)=true`][feature-switches]. -The most common values for this property are: - -- `Xamarin.Android.Net.AndroidMessageHandler`: Use the Android Java APIs - to perform HTTP requests. It is similar to the legacy - `Xamarin.Android.Net.AndroidClientHandler` with several improvements. - It supports HTTP 1.1 and TLS 1.2. It is the default HTTP message handler. - -- `System.Net.Http.SocketsHttpHandler, System.Net.Http`: The default message - handler in .NET. It supports HTTP/2, TLS 1.2, and it is the recommended - HTTP message handler to use with [Grpc.Net.Client](https://www.nuget.org/packages/Grpc.Net.Client). - This value is equivalent to `$(UseNativeHttpHandler)=false`. - -- Unset/the empty string, which is equivalent to - `System.Net.Http.HttpClientHandler, System.Net.Http` - - Corresponds to the **Default** option in the Visual Studio - property pages. - - The new project wizard selects this option for new projects when the - **Minimum Android Version** is configured to **Android 4.4.87** or - lower in Visual Studio or when **Target Platforms** is set to **Modern - Development** or **Maximum Compatibility** in Visual Studio for Mac. - -- `System.Net.Http.HttpClientHandler, System.Net.Http`: Use the managed - `HttpMessageHandler`. - - Corresponds to the **Managed** option in the Visual Studio - property pages. - -> [!NOTE] -> In .NET 6, the type you specify must not be -> `Xamarin.Android.Net.AndroidClientHandler` or `System.Net.Http.HttpClientHandler` -> or inherit from either of these classes. If you are migrating from -> "classic" Xamarin.Android, use `AndroidMessageHandler` or derive your -> custom handler from it instead. - > [!NOTE] -> Support for the `$(AndroidHttpClientHandlerType)` property works by setting the -> [`XA_HTTP_CLIENT_HANDLER_TYPE` environment variable](/xamarin/android/deploy-test/environment). -> A `$XA_HTTP_CLIENT_HANDLER_TYPE` value found in a file -> with a Build action of -> [`@(AndroidEnvironment)`](build-items.md#androidenvironment) -> will take precedence. +> This property is not supported in .NET 11. Remove it from your project file +> and use `UseNativeHttpHandler` instead. ## AndroidIgnoreAllJniPreload diff --git a/Documentation/docs-mobile/messages/index.md b/Documentation/docs-mobile/messages/index.md index 14d5a6c0442..1a44e3d2f21 100644 --- a/Documentation/docs-mobile/messages/index.md +++ b/Documentation/docs-mobile/messages/index.md @@ -139,9 +139,6 @@ or 'Help->Report a Problem' in Visual Studio for Mac. + [XA1027](xa1027.md): The 'EnableProguard' MSBuild property is set to 'true' and the 'AndroidLinkTool' MSBuild property is empty, so 'AndroidLinkTool' will default to 'proguard'. + [XA1028](xa1028.md): The 'AndroidEnableProguard' MSBuild property is set to 'true' and the 'AndroidLinkTool' MSBuild property is empty, so 'AndroidLinkTool' will default to 'proguard'. + [XA1029](xa1029.md): The 'AotAssemblies' MSBuild property is deprecated. Edit the project file in a text editor to remove this property, and use the 'RunAOTCompilation' MSBuild property instead. -+ [XA1031](xa1031.md): The 'AndroidHttpClientHandlerType' has an invalid value. -+ [XA1032](xa1032.md):Failed to resolve '{0}' from '{1}'. Please check your `AndroidHttpClientHandlerType` setting. -+ [XA1033](xa1033.md): Could not resolve '{0}'. Please check your `AndroidHttpClientHandlerType` setting. + [XA1035](xa1035.md): The 'BundleAssemblies' property is deprecated and it has no effect on the application build. Equivalent functionality is implemented by the 'AndroidUseAssemblyStore' and 'AndroidEnableAssemblyCompression' properties. + [XA1036](xa1036.md): AndroidManifest.xml //uses-sdk/@android:minSdkVersion '29' does not match the $(SupportedOSPlatformVersion) value '21' in the project file (if there is no $(SupportedOSPlatformVersion) value in the project file, then a default value has been assumed). Either change the value in the AndroidManifest.xml to match the $(SupportedOSPlatformVersion) value, or remove the value in the AndroidManifest.xml (and add a $(SupportedOSPlatformVersion) value to the project file if it doesn't already exist). @@ -150,6 +147,10 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla + [XA1039](xa1039.md): The Android Support libraries are not supported in .NET 9 and later, please migrate to AndroidX. See https://aka.ms/xamarin/androidx for more details. + [XA1040](xa1040.md): The NativeAOT runtime on Android is an experimental feature and not yet suitable for production use. File issues at: https://github.com/dotnet/android/issues + [XA1041](xa1041.md): The MSBuild property 'MonoAndroidAssetPrefix' has an invalid value of 'c:\Foo\Assets'. The value is expected to be a directory path representing the relative location of your Assets or Resources ++ [XA1044](xa1044.md): The MSBuild property '{0}' is not compatible with the {1} runtime. The build cannot continue while this property is enabled. ++ [XA1045](xa1045.md): Input file `{0}` does not start with ``. ++ [XA1046](xa1046.md): Attribute '{0}' in element '{1}' has value '{2}' that cannot be parsed as boolean; {3} line {4}. ++ [XA1047](xa1047.md): Required attribute '{0}' missing from element '{1}'; {2} line {3}. ## XA2xxx: Linker @@ -168,6 +169,7 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla + XA3005: The detected Android NDK version is incompatible with the targeted LLVM configuration. + XA3006: Could not compile native assembly file: {file} + XA3007: Could not link native shared library: {library} ++ XA3008: Failed to extract debug info from '{library}' ## XA4xxx: Code generation @@ -179,6 +181,7 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla + [XA4215](xa4215.md): The Java type \`com.contoso.library1.Class1\` is generated by more than one managed type. Please change the \[Register\] attribute so that the same Java type is not emitted. + [XA4216](xa4216.md): The deployment target '19' is not supported (the minimum is '21'). Please increase the $(SupportedOSPlatformVersion) property value in your project file. + XA4217: Cannot override Kotlin-generated method '{method}' because it is not a valid Java method name. This method can only be overridden from Kotlin. ++ [XA4217](xa4217.md): Architecture '{arch}' has Java types which have no counterparts in template architecture '{templateArch}': {types} + [XA4218](xa4218.md): Unable to find //manifest/application/uses-library at path: {path} + XA4219: Cannot find binding generator for language {language} or {defaultLanguage}. + XA4220: Partial class item '{file}' does not have an associated binding for layout '{layout}'. @@ -188,6 +191,7 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla + XA4224: Malformed full class name '{name}'. Missing class name. + XA4225: Widget '{widget}' in layout '{layout}' has multiple instances with different types. The property type will be set to: {type} + XA4226: Resource item '{file}' does not have the required metadata item '{metadataName}'. ++ [XA4227](xa4227.md): Architecture '{arch}' doesn't match all marshal methods in architecture '{templateArch}'. Please see detailed MSBuild logs for more information. + XA4228: Unable to find specified //activity-alias/@android:targetActivity: '{targetActivity}' + XA4229: Unrecognized \`TransformFile\` root element: {element}. + XA4230: Error parsing XML: {exception} @@ -208,6 +212,8 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla + [XA4248](xa4248.md): Could not find NuGet package '{nugetId}' version '{version}' in lock file. Ensure NuGet Restore has run since this `` was added. + [XA4235](xa4249.md): Maven artifact specification '{artifact}' is invalid. The correct format is 'group_id:artifact_id:version'. + [XA4250](xa4250.md): Manifest-referenced type '{type}' was not found in any scanned assembly. It may be a framework type. ++ [XA4252](xa4252.md): Insecure HTTP Maven repository URL '{url}' is not allowed. Use an HTTPS URL, or set AllowInsecureHttp="true" metadata on the item to override this check. ++ [XA4253](xa4253.md): Generated Java callable wrapper code changed: '{path}' + XA4300: Native library '{library}' will not be bundled because it has an unsupported ABI. + [XA4301](xa4301.md): Apk already contains the item `xxx`. + [XA4302](xa4302.md): Unhandled exception merging \`AndroidManifest.xml\`: {ex} @@ -224,6 +230,9 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla + [XA4313](xa4313.md): Framework assembly has been deprecated. + [XA4314](xa4314.md): `$(Property)` is empty. A value for `$(Property)` should be provided. + [XA4315](xa4315.md): Ignoring {file}. Manifest does not have the required 'package' attribute on the manifest element. ++ [XA4316](xa4316.md): Specified input file '{file}' does not exist. Ignoring. ++ [XA4317](xa4317.md): Input file '{file}' does not start with ``. Skipping. ++ [XA4318](xa4318.md): Input file '{file}' could not be read: {message}. Skipping. ## XA5xxx: GCC and toolchain @@ -240,6 +249,7 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla + [XA5300](xa5300.md): The Android/Java SDK Directory could not be found. + [XA5301](xa5301.md): Failed to generate Java type for class: {managedType} due to MAX_PATH: {exception} + [XA5302](xa5302.md): Two processes may be building this project at once. Lock file exists at path: {path} ++ [XA5303](xa5303.md): Failed to parse 'DescriptorIndex' metadata value '{value}' for assembly '{assembly}'. ## XA6xxx: Internal tools diff --git a/Documentation/docs-mobile/messages/xa1031.md b/Documentation/docs-mobile/messages/xa1031.md deleted file mode 100644 index c9a40bab9da..00000000000 --- a/Documentation/docs-mobile/messages/xa1031.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: .NET for Android error XA1031 -description: XA1031 error code -ms.date: 04/11/2024 -f1_keywords: - - "XA1031" ---- - -# .NET for Android error XA1031 - -## Example messages - -``` -The 'AndroidHttpClientHandlerType' property value 'Foo.Bar.HttpHander, MyApp' must derive from 'System.Net.Http.HttpMessageHandler'. -Please change the value to an assembly-qualifed type name which inherits from '{1}' or remove the property completely. -``` - -``` -The 'AndroidHttpClientHandlerType' property value '.NET for Android.Net.AndroidClientHandler' must not derive from 'System.Net.Htt.HttpClientHandler'. -Please change the value to an assembly-qualifed type name which inherits from 'System.Net.Http.HttpMessageHandler' or remove the property completely. -``` - -## Solution - -Edit your csproj directly and change the 'AndroidHttpClientHandlerType' to -a valid value. - -Valid values can be found at `~/android/deploy-test/building-apps/build-properties.md#AndroidHttpClientHandlerType`. diff --git a/Documentation/docs-mobile/messages/xa1032.md b/Documentation/docs-mobile/messages/xa1032.md deleted file mode 100644 index 7d3f5f57be0..00000000000 --- a/Documentation/docs-mobile/messages/xa1032.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: .NET for Android error XA1032 -description: XA1032 error code -ms.date: 04/11/2024 -f1_keywords: - - "XA1032" ---- - -# .NET for Android error XA1032 - -## Example messages - -``` -Failed to resolve '{0}' from '{1}'. Please check your `AndroidHttpClientHandlerType` setting. -``` - -## Solution - -Edit your csproj directly and change the 'AndroidHttpClientHandlerType' to -a valid value. - -Valid values can be found at `~/android/deploy-test/building-apps/build-properties.md#AndroidHttpClientHandlerType`. diff --git a/Documentation/docs-mobile/messages/xa1033.md b/Documentation/docs-mobile/messages/xa1033.md deleted file mode 100644 index a6ef1de93ec..00000000000 --- a/Documentation/docs-mobile/messages/xa1033.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: .NET for Android error XA1033 -description: XA1033 error code -ms.date: 04/11/2024 -f1_keywords: - - "XA1033" ---- - -# .NET for Android error XA1033 - -## Example messages - -``` -Could not resolve '{0}'. Please check your `AndroidHttpClientHandlerType` setting. -``` - -## Solution - -Edit your csproj directly and change the 'AndroidHttpClientHandlerType' to -a valid value. - -Valid values can be found at `~/android/deploy-test/building-apps/build-properties.md#AndroidHttpClientHandlerType`. diff --git a/Documentation/docs-mobile/messages/xa1045.md b/Documentation/docs-mobile/messages/xa1045.md new file mode 100644 index 00000000000..b49ade8c949 --- /dev/null +++ b/Documentation/docs-mobile/messages/xa1045.md @@ -0,0 +1,32 @@ +--- +title: .NET for Android error XA1045 +description: XA1045 error code +ms.date: 05/15/2026 +f1_keywords: + - "XA1045" +--- + +# .NET for Android error XA1045 + +## Example messages + +``` +error XA1045: Input file `/path/to/jni-remapping.xml` does not start with ``. +``` + +## Issue + +The JNI remapping XML file referenced by the `$(AndroidJniRemapping)` MSBuild +item does not have the expected `` root element. The file must +begin with a `` element for the build system to process it. + +## Solution + +Ensure the JNI remapping XML file starts with a `` root element. +For example: + +```xml + + + +``` diff --git a/Documentation/docs-mobile/messages/xa1046.md b/Documentation/docs-mobile/messages/xa1046.md new file mode 100644 index 00000000000..012580ae97f --- /dev/null +++ b/Documentation/docs-mobile/messages/xa1046.md @@ -0,0 +1,38 @@ +--- +title: .NET for Android error XA1046 +description: XA1046 error code +ms.date: 05/15/2026 +f1_keywords: + - "XA1046" +--- + +# .NET for Android error XA1046 + +## Example messages + +``` +error XA1046: Attribute 'target-method-instance-to-static' in element 'replace-method' has value 'yes' that cannot be parsed as boolean; /path/to/jni-remapping.xml line 5. +``` + +## Issue + +The `target-method-instance-to-static` attribute of a `` +element in the JNI remapping XML file contains a value that cannot be parsed +as a boolean. The attribute requires a valid boolean value such as `true` or +`false`. + +## Solution + +Update the `target-method-instance-to-static` attribute in your JNI remapping +XML file to use a valid boolean value (`true` or `false`): + +```xml + + + +``` diff --git a/Documentation/docs-mobile/messages/xa1047.md b/Documentation/docs-mobile/messages/xa1047.md new file mode 100644 index 00000000000..86a817dd38b --- /dev/null +++ b/Documentation/docs-mobile/messages/xa1047.md @@ -0,0 +1,50 @@ +--- +title: .NET for Android error XA1047 +description: XA1047 error code +ms.date: 05/15/2026 +f1_keywords: + - "XA1047" +--- + +# .NET for Android error XA1047 + +## Example messages + +``` +error XA1047: Required attribute 'from' missing from element 'replace-type'; /path/to/jni-remapping.xml line 3. +error XA1047: Required attribute 'source-type' missing from element 'replace-method'; /path/to/jni-remapping.xml line 7. +``` + +## Issue + +A required attribute is missing from an element in the JNI remapping XML file. +The `` and `` elements each have required +attributes that must be present. + +Required attributes for ``: +- `from` — the original Java type name (e.g. `com/example/OldClass`) +- `to` — the replacement Java type name (e.g. `com/example/NewClass`) + +Required attributes for ``: +- `source-type` — the Java type containing the original method +- `source-method-name` — the name of the original method +- `target-type` — the Java type containing the replacement method +- `target-method-name` — the name of the replacement method +- `target-method-instance-to-static` — whether the replacement converts an instance method to a static method (`true` or `false`) + +## Solution + +Add the missing required attributes to the element in your JNI remapping XML +file. For example: + +```xml + + + + +``` diff --git a/Documentation/docs-mobile/messages/xa3008.md b/Documentation/docs-mobile/messages/xa3008.md new file mode 100644 index 00000000000..b958e94ee90 --- /dev/null +++ b/Documentation/docs-mobile/messages/xa3008.md @@ -0,0 +1,26 @@ +--- +title: .NET for Android error XA3008 +description: XA3008 error code +ms.date: 05/15/2026 +f1_keywords: + - "XA3008" +--- + +# .NET for Android error XA3008 + +## Issue + +This error is emitted when the build system fails to extract debug symbols from +a native shared library using `llvm-objcopy`. The shared library itself was linked +successfully, but separate debug symbol files (`.dbg.so`) could not be created. + +## Solution + +Check that the NDK `llvm-objcopy` tool is installed and functioning correctly. + +## Example messages + +``` +error XA3008: Failed to extract debug info from 'libmonodroid.so' +llvm-objcopy: error: ... +``` diff --git a/Documentation/docs-mobile/messages/xa4217.md b/Documentation/docs-mobile/messages/xa4217.md new file mode 100644 index 00000000000..a3ff160523d --- /dev/null +++ b/Documentation/docs-mobile/messages/xa4217.md @@ -0,0 +1,29 @@ +--- +title: .NET for Android error XA4217 +description: XA4217 error code +ms.date: 05/20/2026 +f1_keywords: + - "XA4217" +--- + +# .NET for Android error XA4217 + +## Example messages + +``` +error XA4217: Architecture 'x86' has Java types which have no counterparts in template architecture 'arm64-v8a': MyNamespace.MyType +``` + +## Issue + +When building for multiple Android architectures, each architecture must +produce the same set of Java callable wrapper types. This error means +one architecture generated Java types that do not exist in the template +(first) architecture. + +## Solution + +Ensure that all target architectures reference the same set of managed +types that derive from `Java.Lang.Object`. If architecture-specific +code is needed, use runtime checks rather than conditional compilation +that adds or removes Java-bound types per architecture. diff --git a/Documentation/docs-mobile/messages/xa4227.md b/Documentation/docs-mobile/messages/xa4227.md new file mode 100644 index 00000000000..5630949538e --- /dev/null +++ b/Documentation/docs-mobile/messages/xa4227.md @@ -0,0 +1,29 @@ +--- +title: .NET for Android error XA4227 +description: XA4227 error code +ms.date: 05/20/2026 +f1_keywords: + - "XA4227" +--- + +# .NET for Android error XA4227 + +## Example messages + +``` +error XA4227: Architecture 'x86' doesn't match all marshal methods in architecture 'arm64-v8a'. Please see detailed MSBuild logs for more information. +``` + +## Issue + +When building for multiple Android architectures, the marshal methods +generated for each architecture must be consistent. This error means +one architecture has marshal methods that do not match those of the +template (first) architecture. + +## Solution + +Ensure that all target architectures reference the same set of managed +types and methods. Enable detailed MSBuild logging (`-v:diag`) to see +exactly which marshal methods differ, then adjust the project so that +all architectures produce the same set of marshal methods. diff --git a/Documentation/docs-mobile/messages/xa4252.md b/Documentation/docs-mobile/messages/xa4252.md new file mode 100644 index 00000000000..72effe92fac --- /dev/null +++ b/Documentation/docs-mobile/messages/xa4252.md @@ -0,0 +1,43 @@ +--- +title: .NET for Android error XA4252 +description: XA4252 error code +ms.date: 05/15/2026 +f1_keywords: + - "XA4252" +--- + +# .NET for Android error XA4252 + +## Example message + +``` +error XA4252: Insecure HTTP Maven repository URL 'http://repo.example.com/maven2/' is not allowed. Use an HTTPS URL, or set AllowInsecureHttp="true" metadata on the item to override this check. +``` + +## Issue + +An `` item specifies a Maven repository URL using `http://` instead of `https://`. +Downloading artifacts over plain HTTP is a security risk because the connection is not encrypted and is +vulnerable to man-in-the-middle attacks and supply-chain compromise. + +This check aligns with default behavior in Gradle (`allowInsecureProtocol`) and Maven (`http://*`) +for defense in depth and supply-chain hardening. + +## Solution + +Use an HTTPS URL for the Maven repository whenever possible: + +```xml + + + +``` + +If the repository does not support HTTPS and you understand the security implications, +set `AllowInsecureHttp="true"` to explicitly opt in to insecure HTTP: + +```xml + + + +``` diff --git a/Documentation/docs-mobile/messages/xa4253.md b/Documentation/docs-mobile/messages/xa4253.md new file mode 100644 index 00000000000..74632be962c --- /dev/null +++ b/Documentation/docs-mobile/messages/xa4253.md @@ -0,0 +1,28 @@ +--- +title: .NET for Android error XA4253 +description: XA4253 error code +ms.date: 05/20/2026 +f1_keywords: + - "XA4253" +--- + +# .NET for Android error XA4253 + +## Example messages + +``` +error XA4253: Generated Java callable wrapper code changed: 'obj/Release/android/src/mono/MonoRuntimeProvider.java' +``` + +## Issue + +The generated Java callable wrapper (JCW) source file was unexpectedly +modified during the build. This usually indicates that an incremental +build detected a change in the generated output that should not have +occurred. + +## Solution + +Perform a clean build of your project (`dotnet clean` followed by +`dotnet build`) to ensure all generated files are up to date. If the +error persists, please file a bug report with a reproduction project. diff --git a/Documentation/docs-mobile/messages/xa4312.md b/Documentation/docs-mobile/messages/xa4312.md index e963b4b09b0..6c011720e4b 100644 --- a/Documentation/docs-mobile/messages/xa4312.md +++ b/Documentation/docs-mobile/messages/xa4312.md @@ -11,6 +11,9 @@ f1_keywords: Referencing an [Android Wear][0] application project from an Android application project is deprecated. +[Wear OS][0] apps should be distributed as [standalone][1] applications +instead. + From a `Foo.Android.csproj` in past versions of .NET for Android, you could reference a `Foo.Wear.csproj` such as: @@ -26,7 +29,7 @@ This would embed `com.foo.wear.apk` *inside* `com.foo.android.apk` in ## Solution Distribute `Foo.Wear.csproj` in the above example as a [standalone][1] -Android Wear application instead. +Wear OS application instead. -[0]: /xamarin/android/wear/get-started/intro-to-wear +[0]: https://developer.android.com/training/wearables [1]: https://developer.android.com/training/wearables/apps/standalone-apps diff --git a/Documentation/docs-mobile/messages/xa4316.md b/Documentation/docs-mobile/messages/xa4316.md new file mode 100644 index 00000000000..e7a44421ce2 --- /dev/null +++ b/Documentation/docs-mobile/messages/xa4316.md @@ -0,0 +1,25 @@ +--- +title: .NET for Android warning XA4316 +description: XA4316 warning code +ms.date: 05/22/2026 +f1_keywords: + - "XA4316" +--- + +# .NET for Android warning XA4316 + +## Example messages + +``` +warning XA4316: Specified input file '{file}' does not exist. Ignoring. +``` + +## Issue + +A remap XML input file specified via the `InputRemapXmlFiles` item group does +not exist on disk. The file is ignored and the build continues. + +## Solution + +Verify that the file path is correct and that the file exists. Remove the +reference if the file is no longer needed. diff --git a/Documentation/docs-mobile/messages/xa4317.md b/Documentation/docs-mobile/messages/xa4317.md new file mode 100644 index 00000000000..b2f6f51781a --- /dev/null +++ b/Documentation/docs-mobile/messages/xa4317.md @@ -0,0 +1,31 @@ +--- +title: .NET for Android warning XA4317 +description: XA4317 warning code +ms.date: 05/22/2026 +f1_keywords: + - "XA4317" +--- + +# .NET for Android warning XA4317 + +## Example messages + +``` +warning XA4317: Input file '{file}' does not start with ''. Skipping. +``` + +## Issue + +A remap XML input file does not have `` as its root element. +Files passed to the remap XML merge must use the `` root element. + +## Solution + +Ensure the XML file has the correct format with `` as the root +element: + +```xml + + + +``` diff --git a/Documentation/docs-mobile/messages/xa4318.md b/Documentation/docs-mobile/messages/xa4318.md new file mode 100644 index 00000000000..96676f78990 --- /dev/null +++ b/Documentation/docs-mobile/messages/xa4318.md @@ -0,0 +1,26 @@ +--- +title: .NET for Android warning XA4318 +description: XA4318 warning code +ms.date: 05/22/2026 +f1_keywords: + - "XA4318" +--- + +# .NET for Android warning XA4318 + +## Example messages + +``` +warning XA4318: Input file '{file}' could not be read: {message}. Skipping. +``` + +## Issue + +A remap XML input file could not be read, for example because it contains +invalid XML or there is a permissions issue. The file is skipped and the build +continues. + +## Solution + +Verify that the file contains valid XML and that the build process has +permission to read it. diff --git a/Documentation/docs-mobile/messages/xa5303.md b/Documentation/docs-mobile/messages/xa5303.md new file mode 100644 index 00000000000..3fe9996bf78 --- /dev/null +++ b/Documentation/docs-mobile/messages/xa5303.md @@ -0,0 +1,23 @@ +--- +title: .NET for Android error XA5303 +description: XA5303 error code +ms.date: 05/20/2026 +f1_keywords: + - "XA5303" +--- + +# .NET for Android error XA5303 + +## Example messages + +``` +error XA5303: Failed to parse 'DescriptorIndex' metadata value 'abc' for assembly 'MyAssembly.dll'. +``` + +## Issue + +The `DescriptorIndex` metadata on an assembly item passed to the `CompressAssemblies` MSBuild task could not be parsed as an unsigned integer. This metadata is required for LZ4 assembly compression. + +## Solution + +This is typically an internal build system error. If you encounter this error, please file a [bug report](https://github.com/dotnet/android/issues/new). diff --git a/Localize/loc/cs/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/cs/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl index 052f0d6dc07..c3bdda1f948 100644 --- a/Localize/loc/cs/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl +++ b/Localize/loc/cs/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl @@ -714,18 +714,6 @@ - - - - - - - - - - - - @@ -894,6 +882,33 @@ + + + `.]]> + + `.]]> + + + + + + + + + + + + + + + + + + + + + + @@ -1032,6 +1047,15 @@ + + + + + + + + + @@ -1422,6 +1446,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Localize/loc/de/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/de/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl index 6c174c5c2b3..651688eb43d 100644 --- a/Localize/loc/de/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl +++ b/Localize/loc/de/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl @@ -714,18 +714,6 @@ - - - - - - - - - - - - @@ -894,6 +882,33 @@ + + + `.]]> + + `.]]> + + + + + + + + + + + + + + + + + + + + + + @@ -1032,6 +1047,15 @@ + + + + + + + + + @@ -1422,6 +1446,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Localize/loc/es/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/es/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl index 99834402d87..545b37eb5c4 100644 --- a/Localize/loc/es/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl +++ b/Localize/loc/es/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl @@ -714,18 +714,6 @@ - - - - - - - - - - - - @@ -894,6 +882,33 @@ + + + `.]]> + + `.]]> + + + + + + + + + + + + + + + + + + + + + + @@ -1032,6 +1047,15 @@ + + + + + + + + + @@ -1422,6 +1446,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Localize/loc/fr/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/fr/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl index 5c80aa261b7..6a9ac17b30b 100644 --- a/Localize/loc/fr/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl +++ b/Localize/loc/fr/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl @@ -714,18 +714,6 @@ - - - - - - - - - - - - @@ -894,6 +882,33 @@ + + + `.]]> + + `.]]> + + + + + + + + + + + + + + + + + + + + + + @@ -1032,6 +1047,15 @@ + + + + + + + + + @@ -1422,6 +1446,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Localize/loc/it/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/it/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl index 8580ae8bd80..13cb560490c 100644 --- a/Localize/loc/it/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl +++ b/Localize/loc/it/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl @@ -714,18 +714,6 @@ - - - - - - - - - - - - @@ -894,6 +882,33 @@ + + + `.]]> + + ''.]]> + + + + + + + + + + + + + + + + + + + + + + @@ -1032,6 +1047,15 @@ + + + + + + + + + @@ -1422,6 +1446,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Localize/loc/ja/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/ja/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl index 127e4f22119..5490abc2578 100644 --- a/Localize/loc/ja/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl +++ b/Localize/loc/ja/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl @@ -714,18 +714,6 @@ - - - - - - - - - - - - @@ -894,6 +882,33 @@ + + + `.]]> + + ` で始まっていません。]]> + + + + + + + + + + + + + + + + + + + + + + @@ -1032,6 +1047,15 @@ + + + + + + + + + @@ -1422,6 +1446,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Localize/loc/ko/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/ko/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl index ff1ef6eeeff..cdbd74995fa 100644 --- a/Localize/loc/ko/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl +++ b/Localize/loc/ko/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl @@ -714,18 +714,6 @@ - - - - - - - - - - - - @@ -894,6 +882,33 @@ + + + `.]]> + + `로 시작하지 않습니다.]]> + + + + + + + + + + + + + + + + + + + + + + @@ -1032,6 +1047,15 @@ + + + + + + + + + @@ -1422,6 +1446,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Localize/loc/pl/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/pl/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl index 298c2fcce77..b7009089d73 100644 --- a/Localize/loc/pl/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl +++ b/Localize/loc/pl/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl @@ -714,18 +714,6 @@ - - - - - - - - - - - - @@ -894,6 +882,33 @@ + + + `.]]> + + `.]]> + + + + + + + + + + + + + + + + + + + + + + @@ -1032,6 +1047,15 @@ + + + + + + + + + @@ -1422,6 +1446,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Localize/loc/pt-BR/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/pt-BR/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl index a0312d0ba8b..f01c6aec120 100644 --- a/Localize/loc/pt-BR/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl +++ b/Localize/loc/pt-BR/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl @@ -714,18 +714,6 @@ - - - - - - - - - - - - @@ -894,6 +882,33 @@ + + + `.]]> + + `.]]> + + + + + + + + + + + + + + + + + + + + + + @@ -1032,6 +1047,15 @@ + + + + + + + + + @@ -1422,6 +1446,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Localize/loc/ru/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/ru/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl index a0db45dd6f3..04eb0bf1f08 100644 --- a/Localize/loc/ru/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl +++ b/Localize/loc/ru/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl @@ -714,18 +714,6 @@ - - - - - - - - - - - - @@ -894,6 +882,33 @@ + + + `.]]> + + `.]]> + + + + + + + + + + + + + + + + + + + + + + @@ -1032,6 +1047,15 @@ + + + + + + + + + @@ -1422,6 +1446,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Localize/loc/tr/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/tr/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl index ed7a7b50902..e8494bf463f 100644 --- a/Localize/loc/tr/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl +++ b/Localize/loc/tr/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl @@ -714,18 +714,6 @@ - - - - - - - - - - - - @@ -894,6 +882,33 @@ + + + `.]]> + + ` ile başlamıyor.]]> + + + + + + + + + + + + + + + + + + + + + + @@ -1032,6 +1047,15 @@ + + + + + + + + + @@ -1422,6 +1446,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Localize/loc/zh-Hans/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/zh-Hans/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl index 4e9b5bb2265..351456a01f6 100644 --- a/Localize/loc/zh-Hans/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl +++ b/Localize/loc/zh-Hans/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl @@ -714,18 +714,6 @@ - - - - - - - - - - - - @@ -894,6 +882,33 @@ + + + `.]]> + + ` 开头。]]> + + + + + + + + + + + + + + + + + + + + + + @@ -1032,6 +1047,15 @@ + + + + + + + + + @@ -1422,6 +1446,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Localize/loc/zh-Hant/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl b/Localize/loc/zh-Hant/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl index 4a65a486cb1..3928cd67c66 100644 --- a/Localize/loc/zh-Hant/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl +++ b/Localize/loc/zh-Hant/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx.lcl @@ -714,18 +714,6 @@ - - - - - - - - - - - - @@ -894,6 +882,33 @@ + + + `.]]> + + ` 開頭。]]> + + + + + + + + + + + + + + + + + + + + + + @@ -1032,6 +1047,15 @@ + + + + + + + + + @@ -1422,6 +1446,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Makefile b/Makefile index ffb2483b506..8ca80967153 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,6 @@ V ?= 0 prefix = /usr/local CONFIGURATION ?= Debug -RUNTIME := $(shell which mono64 2> /dev/null && echo mono64 || echo mono) --debug=casts SOLUTION = Xamarin.Android.sln TEST_TARGETS = build-tools/scripts/RunTests.targets API_LEVEL ?= @@ -93,8 +92,6 @@ include build-tools/scripts/BuildEverything.mk include build-tools/scripts/Packaging.mk run-all-tests: - @echo "PRINTING MONO VERSION" - mono --version _r=0 ; \ $(call MSBUILD_BINLOG,run-all-tests,,Test) $(TEST_TARGETS) /t:RunAllTests || _r=$$? ; \ exit $$_r @@ -135,29 +132,6 @@ prepare: prepare-help: $(call SYSTEM_DOTNET_BINLOG,prepare-help,run) --project "$(PREPARE_PROJECT)" --framework $(PREPARE_NET_FX) -- -h -.PHONY: shutdown-compiler-server -shutdown-compiler-server: - # Ensure the VBCSCompiler.exe process isn't running during the mono update - pgrep -lfi VBCSCompiler.exe 2>/dev/null || true - @pid=`pgrep -lfi VBCSCompiler.exe 2>/dev/null | awk '{ print $$1 }'` ; \ - echo "VBCSCompiler process ID (if running): $$pid" ;\ - if [[ -n "$$pid" ]]; then \ - echo "Terminating the VBCSCompiler '$$pid' server process prior to updating mono" ; \ - exitCode=0 ;\ - kill -HUP $$pid 2>/dev/null || exitCode=$$? ;\ - if [[ $$exitCode -eq 0 ]]; then \ - sleep 2 ;\ - pgrep -lfi VBCSCompiler.exe 2>/dev/null&&echo "ERROR: VBCSCompiler server still exists" || echo "Verified that the VBCSCompiler server process no longer exists" ;\ - else \ - echo "ERROR: Kill command failed with exit code $$exitCode" ;\ - fi \ - fi - -.PHONY: prepare-update-mono -prepare-update-mono: shutdown-compiler-server - $(call SYSTEM_DOTNET_BINLOG,prepare-update-mono,run) --project "$(PREPARE_PROJECT)" --framework $(PREPARE_NET_FX) \ - -- -s:UpdateMono $(_PREPARE_ARGS) - prepare-external-git-dependencies: $(call SYSTEM_DOTNET_BINLOG,prepare-external-git-dependencies,run) --project "$(PREPARE_PROJECT)" --framework $(PREPARE_NET_FX) \ -- -s:PrepareExternalGitDependencies $(_PREPARE_ARGS) diff --git a/NuGet.config b/NuGet.config index dec1baffe92..b3cfba7ae97 100644 --- a/NuGet.config +++ b/NuGet.config @@ -8,6 +8,8 @@ + + diff --git a/Xamarin.Android.sln b/Xamarin.Android.sln index fcee21668da..82205d0743a 100644 --- a/Xamarin.Android.sln +++ b/Xamarin.Android.sln @@ -91,6 +91,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "aapt2", "src\aapt2\aapt2.cs EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "bundletool", "src\bundletool\bundletool.csproj", "{A0AEF446-3368-4591-9DE6-BC3B2B33337D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "binutils", "src\binutils\binutils.csproj", "{B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "openjdk", "src\openjdk\openjdk.csproj", "{7A180B05-DE3F-4D89-9F40-03A8E186AE82}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "androidsdk", "src\androidsdk\androidsdk.csproj", "{3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "jit-times", "tools\jit-times\jit-times.csproj", "{F3CFF31C-037B-450F-B22D-1D6E529B2DCC}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSBuildDeviceIntegration", "tests\MSBuildDeviceIntegration\MSBuildDeviceIntegration.csproj", "{16DB2680-399B-4111-AA26-6CDBBFA334D8}" @@ -301,6 +307,18 @@ Global {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Debug|AnyCPU.Build.0 = Debug|Any CPU {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Release|AnyCPU.ActiveCfg = Release|Any CPU {A0AEF446-3368-4591-9DE6-BC3B2B33337D}.Release|AnyCPU.Build.0 = Release|Any CPU + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Debug|AnyCPU.Build.0 = Debug|Any CPU + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Release|AnyCPU.ActiveCfg = Release|Any CPU + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36}.Release|AnyCPU.Build.0 = Release|Any CPU + {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU + {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Debug|AnyCPU.Build.0 = Debug|Any CPU + {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Release|AnyCPU.ActiveCfg = Release|Any CPU + {7A180B05-DE3F-4D89-9F40-03A8E186AE82}.Release|AnyCPU.Build.0 = Release|Any CPU + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Debug|AnyCPU.Build.0 = Debug|Any CPU + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Release|AnyCPU.ActiveCfg = Release|Any CPU + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1}.Release|AnyCPU.Build.0 = Release|Any CPU {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Debug|AnyCPU.Build.0 = Debug|Any CPU {F3CFF31C-037B-450F-B22D-1D6E529B2DCC}.Release|AnyCPU.ActiveCfg = Release|Any CPU @@ -431,6 +449,9 @@ Global {AF8AC493-40AC-4195-82F6-B08EE4B4E49E} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {0C31DE30-F9DF-4312-BFFE-DCAD558CCF08} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {A0AEF446-3368-4591-9DE6-BC3B2B33337D} = {04E3E11E-B47D-4599-8AFC-50515A95E715} + {B2BC20D1-F468-46A9-B9B0-1C80CC4D4F36} = {04E3E11E-B47D-4599-8AFC-50515A95E715} + {7A180B05-DE3F-4D89-9F40-03A8E186AE82} = {04E3E11E-B47D-4599-8AFC-50515A95E715} + {3E45D81B-4C4A-4E3F-B891-A0D8A3E2F0C1} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {F3CFF31C-037B-450F-B22D-1D6E529B2DCC} = {864062D3-A415-4A6F-9324-5820237BA058} {16DB2680-399B-4111-AA26-6CDBBFA334D8} = {CAB438D8-B0F5-4AF0-BEBD-9E2ADBD7B483} {372E8E3E-29D5-4B4D-88A2-4711CD628C4E} = {05C3B1D6-A4CE-4534-A9E4-E9117591ADF7} diff --git a/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/CreateFrameworkList.cs b/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/CreateFrameworkList.cs deleted file mode 100644 index d654035edb4..00000000000 --- a/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/CreateFrameworkList.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Xml.Linq; - -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace Xamarin.Android.Tools.BootstrapTasks -{ - public class CreateFrameworkList : Task - { - [Required] - public ITaskItem FrameworkListFile { get; set; } - - [Required] - public ITaskItem FrameworkDirectory { get; set; } - - [Required] - public ITaskItem[] FrameworkFiles { get; set; } - - public ITaskItem[] FrameworkFileOverrides { get; set; } - - [Required] - public string Redist { get; set; } - - [Required] - public string Name { get; set; } - - public override bool Execute() - { - var files = new List(FrameworkFiles); - files.Sort ((x, y) => { - var a = Path.GetFileNameWithoutExtension (x.ItemSpec); - var b = Path.GetFileNameWithoutExtension (y.ItemSpec); - return string.Compare (a, b, StringComparison.OrdinalIgnoreCase); - }); - var contents = new XElement ("FileList", - new XAttribute ("Redist", Redist), - new XAttribute ("Name", Name), - files.Select (f => ToFileElement (f))); - contents.Save (FrameworkListFile.ItemSpec); - return true; - } - - XElement ToFileElement (ITaskItem file) - { - var path = Path.Combine (FrameworkDirectory.ItemSpec, file.ItemSpec); - if (!File.Exists (path)) { - path = Path.Combine (FrameworkDirectory.ItemSpec, "Facades", file.ItemSpec); - } - - var assemblyName = AssemblyName.GetAssemblyName (path); - - var taskVersion = Nullable (file.GetMetadata ("Version")); - var overrideVersion = Nullable (FrameworkFileOverrides?.FirstOrDefault (o => o.ItemSpec == file.ItemSpec)?.GetMetadata ("Version")); - var assemblyVersion = assemblyName.Version.ToString (); - var version = taskVersion ?? overrideVersion ?? assemblyVersion; - - var publicKeyToken = string.Join ("", assemblyName.GetPublicKeyToken ().Select(b => b.ToString ("x2"))); - - return new XElement ("File", - new XAttribute ("AssemblyName", assemblyName.Name), - new XAttribute ("Version", version), - new XAttribute ("PublicKeyToken", publicKeyToken), - new XAttribute ("ProcessorArchitecture", "MSIL")); - } - - static string Nullable (string value) => string.IsNullOrEmpty (value) ? null : value; - } -} diff --git a/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateMonoDroidIncludes.cs b/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateMonoDroidIncludes.cs deleted file mode 100644 index bd8f01012be..00000000000 --- a/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateMonoDroidIncludes.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.IO; -using System.Globalization; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace Xamarin.Android.Tools.BootstrapTasks -{ - public class GenerateMonoDroidIncludes : Task - { - [Required] - public ITaskItem [] SourceFiles { get; set; } - - [Required] - public ITaskItem [] DestinationFiles { get; set; } - - public override bool Execute () - { - Log.LogMessage (MessageImportance.Low, $"Task {nameof (GenerateMonoDroidIncludes)}"); - - if (SourceFiles.Length != DestinationFiles.Length) { - Log.LogError ($"{nameof(SourceFiles)}.Length must equal {nameof(DestinationFiles)}.Length."); - return false; - } - - Log.LogMessage (MessageImportance.Low, $"\t{nameof(SourceFiles)} : "); - for (int i = 0; i < SourceFiles.Length; i++) { - var source = SourceFiles [i]; - var destination = DestinationFiles [i]; - Log.LogMessage (MessageImportance.Low, "\t\t{0} -> {1}", source.ItemSpec, destination.ItemSpec); - - var bytes = File.ReadAllBytes (source.ItemSpec); - - //Should be equivalent of "xxd -i %(_EmbeddedBlob.Config) | sed 's/^unsigned /static const unsigned /g' > jni/%(_EmbeddedBlob.Include)" - using (var fs = File.Create (destination.ItemSpec)) - using (var writer = new StreamWriter(fs)) { - var variableName = "monodroid_" + Path.GetFileNameWithoutExtension (source.ItemSpec).Replace ('.', '_'); - writer.Write ("static const unsigned char "); - writer.Write (variableName); - writer.Write ("[] = {"); - - for (int j = 0; j < bytes.Length; j++) { - if (j != 0) - writer.Write (","); - - //12 per line - if (j % 12 == 0) { - writer.WriteLine (); - writer.Write (" "); - } else { - writer.Write (" "); - } - - writer.Write ("0x"); - writer.Write (bytes [j].ToString ("x2", CultureInfo.InvariantCulture)); - } - - //Needs to be a null-terminating string and include a trailing \0 - writer.WriteLine (", 0x00"); - writer.WriteLine ("};"); - - //Length - writer.Write ("static const unsigned int "); - writer.Write (variableName); - writer.Write ("_len = "); - writer.Write (bytes.Length + 1); - writer.WriteLine (";"); - } - } - - return !Log.HasLoggedErrors; - } - } -} diff --git a/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateProfile.cs b/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateProfile.cs deleted file mode 100644 index d5c4b31693b..00000000000 --- a/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateProfile.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Text; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace Xamarin.Android.Tools.BootstrapTasks -{ - public class GenerateProfile : Task - { - [Required] - public ITaskItem[] Files { get; set; } - - [Required] - public ITaskItem OutputFile { get; set; } - - public override bool Execute () - { - Log.LogMessage (MessageImportance.Low, "Task GenerateProfile"); - Log.LogMessage (MessageImportance.Low, "\tOutputFile : {0}", OutputFile); - Log.LogMessage (MessageImportance.Low, "\tFiles : "); - foreach (var file in Files) { - Log.LogMessage (MessageImportance.Low, "\t\t{0}", file.ItemSpec); - } - - var sb = new StringBuilder (); - sb.AppendLine ("using System.Collections.Generic;"); - sb.AppendLine (); - sb.AppendLine ("namespace Xamarin.Android.Tasks {"); - sb.AppendLine ("\tpublic partial class Profile {"); - sb.AppendLine ("\t\t// KEEP THIS SORTED ALPHABETICALLY, CASE-INSENSITIVE"); - sb.AppendLine ("\t\tpublic static readonly string [] SharedRuntimeAssemblies = new []{"); - foreach (var file in Files.Select(x => Path.GetFileName (x.ItemSpec)).Distinct().OrderBy(x => x)) { - sb.AppendFormat ("\t\t\t\"{0}\"," + Environment.NewLine, file); - } - sb.AppendLine ("\t\t};"); - sb.AppendLine ("\t}"); - sb.AppendLine ("}"); - - var newContents = sb.ToString (); - var curContents = ""; - if (File.Exists (OutputFile.ItemSpec)) { - curContents = File.ReadAllText (OutputFile.ItemSpec); - } - if (newContents != curContents) { - File.WriteAllText (OutputFile.ItemSpec, sb.ToString ()); - } - - return !Log.HasLoggedErrors; - } - } -} - diff --git a/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateSupportedPlatforms.cs b/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateSupportedPlatforms.cs index 5d59b50c47c..80947479a24 100644 --- a/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateSupportedPlatforms.cs +++ b/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateSupportedPlatforms.cs @@ -39,18 +39,11 @@ public class GenerateSupportedPlatforms : Task /// public string? TargetApiLevel { get; set; } - /// - /// Minimum API level supported by non-Mono runtimes (CoreCLR/NativeAOT). - /// API levels below this will be conditional on $(UseMonoRuntime) == 'true'. - /// - public string? MinimumNonMonoApiLevel { get; set; } - public override bool Execute () { var minVersion = ToVersion (MinimumApiLevel); var targetVersion = ToVersion (TargetApiLevel); - var minNonMonoVersion = ToVersion (MinimumNonMonoApiLevel); var versions = new AndroidVersions (AndroidApiInfo.Select (ToAndroidVersion)); var targetApiLevel = targetVersion != null && targetVersion.Major > 0 ? targetVersion @@ -98,9 +91,6 @@ public override bool Execute () if (versionCode < targetVersion) { writer.WriteAttributeString ("DefineConstantsOnly", "true"); } - if (minNonMonoVersion != null && versionCode < minNonMonoVersion) { - writer.WriteAttributeString ("Condition", " '$(UseMonoRuntime)' == 'true' "); - } writer.WriteEndElement (); // } writer.WriteStartElement ("SdkSupportedTargetPlatformVersion"); diff --git a/build-tools/automation/azure-pipelines-nightly.yaml b/build-tools/automation/azure-pipelines-nightly.yaml index 2e942eb248d..e95bc47d727 100644 --- a/build-tools/automation/azure-pipelines-nightly.yaml +++ b/build-tools/automation/azure-pipelines-nightly.yaml @@ -138,7 +138,7 @@ stages: avdApiLevel: $(avdApiLevel) avdAbi: $(avdAbi) avdType: $(avdType) - emulatorMSBuildArgs: -p:JavaSdkDirectory=$(JI_JAVA_HOME_DEFAULT) + emulatorMSBuildArgs: -p:JavaSdkDirectory=$(JAVA_HOME) - template: /build-tools/automation/yaml-templates/apk-instrumentation.yaml parameters: diff --git a/build-tools/automation/azure-pipelines-public.yaml b/build-tools/automation/azure-pipelines-public.yaml index 2599f911f2a..f00a09174e2 100644 --- a/build-tools/automation/azure-pipelines-public.yaml +++ b/build-tools/automation/azure-pipelines-public.yaml @@ -72,8 +72,9 @@ stages: - job: mac_build_create_installers displayName: macOS > Build pool: - name: Azure Pipelines - vmImage: $(HostedMacImage) + name: AcesShared + demands: + - ImageOverride -equals ACES_VM_SharedPool_Tahoe os: macOS timeoutInMinutes: 240 cancelTimeoutInMinutes: 5 @@ -276,8 +277,9 @@ stages: parallel: 10 displayName: macOS > Tests > MSBuild pool: - name: Azure Pipelines - vmImage: $(HostedMacImage) + name: AcesShared + demands: + - ImageOverride -equals ACES_VM_SharedPool_Tahoe os: macOS timeoutInMinutes: 240 cancelTimeoutInMinutes: 5 @@ -366,6 +368,7 @@ stages: strategy: parallel: 8 displayName: "macOS > Tests > MSBuild+Emulator" + # NOTE: AcesShared pool cannot boot Android emulators, so these jobs must use hosted images pool: name: Azure Pipelines vmImage: $(HostedMacImageWithEmulator) @@ -379,7 +382,6 @@ stages: parameters: installTestSlicer: true installApkDiff: false - updateMono: true xaprepareScenario: EmulatorTestDependencies use1ESTemplate: false diff --git a/build-tools/automation/yaml-templates/build-macos-steps.yaml b/build-tools/automation/yaml-templates/build-macos-steps.yaml index 5c0af7a6ffe..a578f34addc 100644 --- a/build-tools/automation/yaml-templates/build-macos-steps.yaml +++ b/build-tools/automation/yaml-templates/build-macos-steps.yaml @@ -14,6 +14,12 @@ parameters: steps: - template: /build-tools/automation/yaml-templates/log-disk-space.yaml +- ${{ if ne(parameters.use1ESTemplate, true) }}: + - bash: | + set -e + brew install p7zip + displayName: Install p7zip on macOS + - template: /build-tools/automation/yaml-templates/setup-jdk-variables.yaml parameters: useAgentJdkPath: false diff --git a/build-tools/automation/yaml-templates/setup-jdk-variables.yaml b/build-tools/automation/yaml-templates/setup-jdk-variables.yaml index 8282fcaf6a9..07946b2a35b 100644 --- a/build-tools/automation/yaml-templates/setup-jdk-variables.yaml +++ b/build-tools/automation/yaml-templates/setup-jdk-variables.yaml @@ -13,14 +13,9 @@ steps: } $jdkHomePath=$xaPrepareJdkPath if ("${{ parameters.useAgentJdkPath }}" -eq "true") { - $defaultJdkHomeVarName="JAVA_HOME_$(DefaultJavaSdkMajorVersion)_${agentArch}" - $defaultJdkHomePath=(Get-Item -Path "env:$defaultJdkHomeVarName").Value $jdkHomeVarName="JAVA_HOME_${jdkMajorVersion}_${agentArch}" $jdkHomePath=(Get-Item -Path "env:$jdkHomeVarName").Value } - Write-Host "Setting variable 'JI_JAVA_HOME_DEFAULT' to '$defaultJdkHomePath'" - Write-Host "##vso[task.setvariable variable=JI_JAVA_HOME_DEFAULT]$defaultJdkHomePath" - Write-Host "Setting variable 'JAVA_HOME' and 'JI_JAVA_HOME' to '$jdkHomePath'" + Write-Host "Setting variable 'JAVA_HOME' to '$jdkHomePath'" Write-Host "##vso[task.setvariable variable=JAVA_HOME]$jdkHomePath" - Write-Host "##vso[task.setvariable variable=JI_JAVA_HOME]$jdkHomePath" - displayName: set JAVA_HOME and JI_JAVA_HOME + displayName: set JAVA_HOME diff --git a/build-tools/automation/yaml-templates/setup-test-environment-public.yaml b/build-tools/automation/yaml-templates/setup-test-environment-public.yaml index 4577ed8f35e..85b6c2ff7b0 100644 --- a/build-tools/automation/yaml-templates/setup-test-environment-public.yaml +++ b/build-tools/automation/yaml-templates/setup-test-environment-public.yaml @@ -5,13 +5,12 @@ parameters: configuration: $(XA.Build.Configuration) xaSourcePath: $(System.DefaultWorkingDirectory) jdkMajorVersion: $(DefaultJavaSdkMajorVersion) - useAgentJdkPath: true + useAgentJdkPath: false remove_dotnet: false dotnetVersion: $(DotNetSdkVersion) dotnetQuality: $(DotNetSdkQuality) installTestSlicer: false installApkDiff: true - updateMono: false androidSdkPlatforms: $(DefaultTestSdkPlatforms) repositoryAlias: 'self' commit: '' @@ -36,7 +35,6 @@ steps: dotnetQuality: ${{ parameters.dotnetQuality }} installTestSlicer: ${{ parameters.installTestSlicer }} installApkDiff: ${{ parameters.installApkDiff }} - updateMono: ${{ parameters.updateMono }} androidSdkPlatforms: ${{ parameters.androidSdkPlatforms }} xaprepareScenario: ${{ parameters.xaprepareScenario }} use1ESTemplate: false diff --git a/build-tools/automation/yaml-templates/setup-test-environment-steps.yaml b/build-tools/automation/yaml-templates/setup-test-environment-steps.yaml index 4f401c02773..2f1a0b73b81 100644 --- a/build-tools/automation/yaml-templates/setup-test-environment-steps.yaml +++ b/build-tools/automation/yaml-templates/setup-test-environment-steps.yaml @@ -11,7 +11,6 @@ parameters: dotnetQuality: $(DotNetSdkQuality) installTestSlicer: false installApkDiff: true - updateMono: false androidSdkPlatforms: $(DefaultTestSdkPlatforms) xaprepareScenario: AndroidTestDependencies # Use 'EmulatorTestDependencies' for agents that need the emulator installed use1ESTemplate: true @@ -36,15 +35,7 @@ steps: custom: build-server arguments: shutdown -- ${{ if eq(parameters.updateMono, true) }}: - - template: /build-tools/automation/yaml-templates/run-xaprepare.yaml - parameters: - displayName: run xaprepare-UpdateMono - arguments: --s=UpdateMono --auto-provision=yes --auto-provision-uses-sudo=yes - condition: and(succeeded(), eq(variables['agent.os'], 'Darwin')) - xaSourcePath: ${{ parameters.xaSourcePath }} - -# Install p7zip on Linux for public agents +# Install p7zip on Linux/macOS for public agents - ${{ if ne(parameters.use1ESTemplate, true) }}: - bash: | set -e @@ -53,11 +44,25 @@ steps: displayName: Install p7zip on Linux condition: and(succeeded(), eq(variables['agent.os'], 'Linux')) + - bash: | + set -e + brew install p7zip + displayName: Install p7zip on macOS + condition: and(succeeded(), eq(variables['agent.os'], 'Darwin')) + - template: /build-tools/automation/yaml-templates/run-xaprepare.yaml parameters: arguments: --s=${{ parameters.xaprepareScenario }} --android-sdk-platforms="${{ parameters.androidSdkPlatforms }}" xaSourcePath: ${{ parameters.xaSourcePath }} +- template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml + parameters: + displayName: install OpenJDK and accept Android SDK licenses + xaSourcePath: ${{ parameters.xaSourcePath }} + project: ${{ parameters.xaSourcePath }}/src/androidsdk/androidsdk.csproj + arguments: -c ${{ parameters.configuration }} -bl:${{ parameters.xaSourcePath }}/bin/Test${{ parameters.configuration }}/androidsdk.binlog + continueOnError: false + - task: DotNetCoreCLI@2 displayName: build Xamarin.Android.Tools.BootstrapTasks.csproj inputs: diff --git a/build-tools/automation/yaml-templates/setup-test-environment.yaml b/build-tools/automation/yaml-templates/setup-test-environment.yaml index a1470c8c220..b20a666e62b 100644 --- a/build-tools/automation/yaml-templates/setup-test-environment.yaml +++ b/build-tools/automation/yaml-templates/setup-test-environment.yaml @@ -8,7 +8,6 @@ parameters: dotnetQuality: $(DotNetSdkQuality) installTestSlicer: false installApkDiff: true - updateMono: false androidSdkPlatforms: $(DefaultTestSdkPlatforms) repositoryAlias: 'self' commit: '' @@ -41,7 +40,6 @@ steps: dotnetQuality: ${{ parameters.dotnetQuality }} installTestSlicer: ${{ parameters.installTestSlicer }} installApkDiff: ${{ parameters.installApkDiff }} - updateMono: ${{ parameters.updateMono }} androidSdkPlatforms: ${{ parameters.androidSdkPlatforms }} xaprepareScenario: ${{ parameters.xaprepareScenario }} use1ESTemplate: ${{ parameters.use1ESTemplate }} diff --git a/build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yaml b/build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yaml index ca8ee7f94dc..abf8564a24d 100644 --- a/build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yaml +++ b/build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yaml @@ -38,7 +38,6 @@ stages: parameters: installTestSlicer: true installApkDiff: false - updateMono: true xaSourcePath: ${{ parameters.xaSourcePath }} repositoryAlias: ${{ parameters.repositoryAlias }} commit: ${{ parameters.commit }} diff --git a/build-tools/automation/yaml-templates/stage-package-tests.yaml b/build-tools/automation/yaml-templates/stage-package-tests.yaml index 47a7a9db5ed..d68f11e2b13 100644 --- a/build-tools/automation/yaml-templates/stage-package-tests.yaml +++ b/build-tools/automation/yaml-templates/stage-package-tests.yaml @@ -10,6 +10,7 @@ stages: dependsOn: mac_build jobs: # Check - "Xamarin.Android (Package Tests macOS > Tests > APKs .NET)" + # NOTE: AcesShared pool cannot boot Android emulators, so APK test jobs must use hosted images - job: mac_apk_tests_net_1 displayName: macOS > Tests > APKs 1 pool: @@ -110,6 +111,7 @@ stages: - template: /build-tools/automation/yaml-templates/fail-on-issue.yaml + # NOTE: AcesShared pool cannot boot Android emulators, so APK test jobs must use hosted images - job: mac_apk_tests_net_2 displayName: macOS > Tests > APKs 2 pool: diff --git a/build-tools/create-packs/Microsoft.Android.Runtime.proj b/build-tools/create-packs/Microsoft.Android.Runtime.proj index 93452ea06b6..d20f8f00eaa 100644 --- a/build-tools/create-packs/Microsoft.Android.Runtime.proj +++ b/build-tools/create-packs/Microsoft.Android.Runtime.proj @@ -78,12 +78,6 @@ projects that use the Microsoft.Android.Runtimes framework in .NET 6+. - - - - - - @@ -96,12 +90,26 @@ projects that use the Microsoft.Android.Runtimes framework in .NET 6+. + + + + + - + + + + + + + + + + diff --git a/build-tools/create-packs/Microsoft.Android.Sdk.proj b/build-tools/create-packs/Microsoft.Android.Sdk.proj index e9e8af48982..ea9becb0dce 100644 --- a/build-tools/create-packs/Microsoft.Android.Sdk.proj +++ b/build-tools/create-packs/Microsoft.Android.Sdk.proj @@ -75,6 +75,8 @@ core workload SDK packs imported by WorkloadManifest.targets. + + diff --git a/build-tools/scripts/DotNet.targets b/build-tools/scripts/DotNet.targets index 10885cd529d..173cfda700a 100644 --- a/build-tools/scripts/DotNet.targets +++ b/build-tools/scripts/DotNet.targets @@ -16,7 +16,16 @@ /> - + + + + + + @NDK_X86_API_NET@ @NDK_X86_64_API@ @NDK_X86_64_API_NET@ - @NDK_ARM64_V8A_API_NON_MONO@ - @NDK_X86_64_API_NON_MONO@ @@ -32,7 +30,6 @@ Condition=" $(AndroidSupportedTargetJitAbisForConditionalChecks.Contains (':arm64-v8a:')) "> $(AndroidNdkApiLevel_ArmV8a) $(AndroidNdkApiLevel_Arm64) - $(AndroidNdkApiLevelNonMono_Arm64) android-arm64 True True @@ -55,7 +52,6 @@ Condition=" $(AndroidSupportedTargetJitAbisForConditionalChecks.Contains (':x86_64:')) "> $(AndroidNdkApiLevel_X86_64) $(AndroidNdkApiLevel_X64) - $(AndroidNdkApiLevelNonMono_X64) android-x64 True True diff --git a/build-tools/scripts/XABuildConfig.cs.in b/build-tools/scripts/XABuildConfig.cs.in index 5b8fd6b22a1..3c8ce65db0f 100644 --- a/build-tools/scripts/XABuildConfig.cs.in +++ b/build-tools/scripts/XABuildConfig.cs.in @@ -15,7 +15,6 @@ namespace Xamarin.Android.Tools public const string XamarinAndroidBranch = "@XAMARIN_ANDROID_BRANCH@"; public const string AndroidSdkBuildToolsVersion = "@SDK_BUILD_TOOLS_VERSION@"; public static readonly Version AndroidMinimumDotNetApiLevel = new Version (@ANDROID_DEFAULT_MINIMUM_DOTNET_API_LEVEL@, @ANDROID_DEFAULT_MINIMUM_DOTNET_API_LEVEL_MINOR@); - public static readonly Version AndroidMinimumNonMonoApiLevel = new Version (@ANDROID_DEFAULT_MINIMUM_NONMONO_API_LEVEL@, @ANDROID_DEFAULT_MINIMUM_NONMONO_API_LEVEL_MINOR@); public static readonly Version AndroidLatestStableApiLevel = new Version (@ANDROID_LATEST_STABLE_API_LEVEL@, @ANDROID_LATEST_STABLE_API_LEVEL_MINOR@); public static readonly Version AndroidLatestUnstableApiLevel = new Version (@ANDROID_LATEST_UNSTABLE_API_LEVEL@, @ANDROID_LATEST_UNSTABLE_API_LEVEL_MINOR@); public static readonly Version AndroidDefaultTargetDotnetApiLevel = new Version (@ANDROID_DEFAULT_TARGET_DOTNET_API_LEVEL@, @ANDROID_DEFAULT_TARGET_DOTNET_API_LEVEL_MINOR@); @@ -37,10 +36,5 @@ namespace Xamarin.Android.Tools { AndroidTargetArch.X86, @NDK_X86_API@ }, { AndroidTargetArch.X86_64, @NDK_X86_64_API@ }, }; - - public static readonly Dictionary ArchToApiLevelNonMono = new () { - { AndroidTargetArch.Arm64, @NDK_ARM64_V8A_NONMONO_API@ }, - { AndroidTargetArch.X86_64, @NDK_X86_64_NONMONO_API@ }, - }; } } diff --git a/build-tools/scripts/XAVersionInfo.targets b/build-tools/scripts/XAVersionInfo.targets index e21107348e5..72083c497a4 100644 --- a/build-tools/scripts/XAVersionInfo.targets +++ b/build-tools/scripts/XAVersionInfo.targets @@ -10,7 +10,6 @@ - diff --git a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/AcceptAndroidSdkLicenses.cs b/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/AcceptAndroidSdkLicenses.cs deleted file mode 100644 index 467fe24310e..00000000000 --- a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/AcceptAndroidSdkLicenses.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace Xamarin.Android.BuildTools.PrepTasks -{ - public class AcceptAndroidSdkLicenses : Task - { - [Required] - public string AndroidSdkDirectory { get; set; } - - public string JavaSdkDirectory { get; set; } - - public override bool Execute () - { - Log.LogMessage (MessageImportance.Low, $"Task {nameof (AcceptAndroidSdkLicenses)}"); - Log.LogMessage (MessageImportance.Low, $" {nameof (AndroidSdkDirectory)}: {AndroidSdkDirectory}"); - Log.LogMessage (MessageImportance.Low, $" {nameof (JavaSdkDirectory)}: {JavaSdkDirectory}"); - - var licdir = Path.Combine (Path.Combine (AndroidSdkDirectory, "licenses")); - Directory.CreateDirectory (licdir); - - if (!string.IsNullOrEmpty (JavaSdkDirectory)) { - Environment.SetEnvironmentVariable ("JAVA_HOME", JavaSdkDirectory); - } - - string _; - var path = Which.GetProgramLocation ("sdkmanager", out _, new [] { Path.Combine (AndroidSdkDirectory, "tools", "bin") }); - var psi = new ProcessStartInfo (path, "--licenses") { UseShellExecute = false, RedirectStandardInput = true }; - var proc = Process.Start (psi); - for (int i = 0; i < 10; i++) - proc.StandardInput.WriteLine ('y'); - proc.WaitForExit (); - - return true; - } - } -} diff --git a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/CheckoutExternalGitDependency.cs b/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/CheckoutExternalGitDependency.cs deleted file mode 100644 index a7da2478199..00000000000 --- a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/CheckoutExternalGitDependency.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.IO; -using Microsoft.Build.Framework; - -namespace Xamarin.Android.BuildTools.PrepTasks -{ - public class CheckoutExternalGitDependency : Git - { - [Required] - public ITaskItem ExternalGitDependency { get; set; } - - protected override bool LogTaskMessages { - get { return false; } - } - - string commit; - string owner; - string name; - string singleLine; - - public override bool Execute () - { - commit = ExternalGitDependency.ItemSpec; - owner = ExternalGitDependency.GetMetadata ("Owner"); - name = ExternalGitDependency.GetMetadata ("Name"); - string destination = Path.Combine (GetWorkingDirectory (), name); - - if (!Directory.Exists (destination)) { - Clone (destination); - } - - WorkingDirectory.ItemSpec = destination; - Fetch (); - CheckoutCommit (); - - return !Log.HasLoggedErrors; - } - - void Clone (string destination) - { - string ghToken = Environment.GetEnvironmentVariable("GH_AUTH_SECRET"); - if (!string.IsNullOrEmpty (ghToken)) { - Arguments = $"clone https://{ghToken}@github.com/{owner}/{name} --progress \"{destination}\""; - } else { - if (IsHttps ()) { - // Just use a plain git clone for https - Arguments = $"clone https://github.com/{owner}/{name} --progress \"{destination}\""; - } else { - // Fallback to SSH URI - Arguments = $"clone git@github.com:{owner}/{name} --progress \"{destination}\""; - } - } - - base.Execute (); - } - - void Fetch () - { - Arguments = $"fetch --all --no-recurse-submodules --progress"; - base.Execute (); - } - - void CheckoutCommit () - { - Arguments = $"checkout {commit} --force --progress"; - base.Execute (); - } - - bool IsHttps () - { - Arguments = "config --get remote.origin.url"; - base.Execute (); - return singleLine != null && singleLine.Contains ("https://"); - } - - protected override void LogToolCommand(string message) - { - // Do nothing - } - - protected override void LogEventsFromTextOutput (string singleLine, MessageImportance messageImportance) - { - if (!string.IsNullOrEmpty (singleLine)) - this.singleLine = singleLine; - - base.LogEventsFromTextOutput (singleLine, messageImportance); - } - } -} diff --git a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/CreateFilePaths.cs b/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/CreateFilePaths.cs deleted file mode 100644 index 5f9637d1477..00000000000 --- a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/CreateFilePaths.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; - -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace Xamarin.Android.BuildTools.PrepTasks -{ - public class CreateFilePaths : Task - { - [Required] - public string[] SourceFileNames { get; set; } - - [Required] - public string[] SourceDirectories { get; set; } - - [Required] - public string[] DestinationDirectories { get; set; } - - [Output] - public ITaskItem[] FullSourceFilePaths { get; set; } - - [Output] - public ITaskItem[] FullDestinationFilePaths { get; set; } - - public override bool Execute () - { - if (SourceFileNames.Length != SourceDirectories.Length || SourceFileNames.Length != DestinationDirectories.Length) - Log.LogError ("Input paramters must be arrays of the same size"); - else - DoExecute (); - - return !Log.HasLoggedErrors; - } - - void DoExecute () - { - var sourcePaths = new List (); - var destinationPaths = new List (); - - for (int i = 0; i < SourceFileNames.Length; i++) { - string sourceFile = SourceFileNames [i].Trim (); - string sourceDir = SourceDirectories [i].Trim (); - string destDir = DestinationDirectories [i].Trim (); - bool canContinue = true; - - canContinue &= AssertNotEmpty (sourceFile, nameof (SourceFileNames), i); - canContinue &= AssertNotEmpty (sourceDir, nameof (SourceDirectories), i); - canContinue &= AssertNotEmpty (destDir, nameof (DestinationDirectories), i); - - if (!canContinue) - continue; - - string[] parts = sourceFile.Split (':'); - if (parts.Length > 2) { - Log.LogError ($"Too many colons in {sourceFile} (SourceFileNames[{i}]), the format is 'file[:dest/file]'"); - continue; - } - - sourcePaths.Add (new TaskItem (Path.Combine (sourceDir, parts [0]))); - destinationPaths.Add (new TaskItem (Path.Combine (destDir, parts.Length == 1 ? parts [0] : parts [1]))); - } - - FullSourceFilePaths = sourcePaths.ToArray (); - FullDestinationFilePaths = destinationPaths.ToArray (); - - bool AssertNotEmpty (string s, string name, int i) - { - if (String.IsNullOrEmpty (s)) { - Log.LogError ($"Element {i} of input array {name} must not be an empty/whitespace-only string"); - return false; - } - - return true; - } - } - } -} diff --git a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/DownloadUri.cs b/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/DownloadUri.cs deleted file mode 100644 index 5c9eb47fead..00000000000 --- a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/DownloadUri.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Security.Cryptography.X509Certificates; -using System.Threading; -using System.Threading.Tasks; - -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -using TTask = System.Threading.Tasks.Task; -using MTask = Microsoft.Build.Utilities.Task; - -namespace Xamarin.Android.BuildTools.PrepTasks { - - public class DownloadUri : MTask, ICancelableTask - { - const int DefaultMaxRetries = 3; - static readonly TimeSpan[] RetryDelays = { - TimeSpan.FromSeconds (5), - TimeSpan.FromSeconds (15), - TimeSpan.FromSeconds (30), - }; - - public DownloadUri () - { - } - - [Required] - public string[] SourceUris { get; set; } - - [Required, Output] - public ITaskItem[] DestinationFiles { get; set; } - - public string HashHeader { get; set; } - - public int MaxRetries { get; set; } = DefaultMaxRetries; - - CancellationTokenSource cancellationTokenSource; - - public void Cancel () - { - cancellationTokenSource?.Cancel (); - } - - public override bool Execute () - { - if (SourceUris.Length != DestinationFiles.Length) { - Log.LogError ("SourceUris.Length must equal DestinationFiles.Length."); - return false; - } - - var source = cancellationTokenSource = new CancellationTokenSource (); - var tasks = new Task [SourceUris.Length]; - - // Configure cert revocation checking in a fail-open state to avoid intermittent - // failures on macOS when the CRL/OCSP endpoint is unreachable. - // Matches the approach in dotnet/arcade's DownloadFile task: - // https://github.com/dotnet/arcade/blob/a07b621/src/Microsoft.DotNet.Arcade.Sdk/src/DownloadFile.cs#L122-L145 - var handler = new SocketsHttpHandler (); - handler.SslOptions.CertificateChainPolicy = new X509ChainPolicy { - RevocationMode = X509RevocationMode.Online, - RevocationFlag = X509RevocationFlag.ExcludeRoot, - VerificationFlags = - X509VerificationFlags.IgnoreCertificateAuthorityRevocationUnknown | - X509VerificationFlags.IgnoreEndRevocationUnknown, - VerificationTimeIgnored = true, - }; - - using (var client = new HttpClient (handler)) { - client.Timeout = TimeSpan.FromHours (3); - for (int i = 0; i < SourceUris.Length; ++i) { - #pragma warning disable CA2025 // Ensure tasks using 'IDisposable' instances complete before the instances are disposed - tasks [i] = DownloadFile (client, source, SourceUris [i], DestinationFiles [i]); - #pragma warning restore CA2025 - } - TTask.WaitAll (tasks, source.Token); - } - - DestinationFiles = tasks.Select (t => t.Result).ToArray (); - - return !Log.HasLoggedErrors; - } - - async Task DownloadFile (HttpClient client, CancellationTokenSource source, string uri, ITaskItem destinationFile) - { - if (!string.IsNullOrEmpty (HashHeader)) { - var hashSuffix = await CheckHashHeader (client, source, uri); - if (!string.IsNullOrEmpty (hashSuffix)) { - var directory = Path.GetDirectoryName (destinationFile.ItemSpec); - var fileName = Path.GetFileNameWithoutExtension (destinationFile.ItemSpec); - var extension = Path.GetExtension (destinationFile.ItemSpec); - destinationFile.ItemSpec = Path.Combine (directory, fileName + "-" + hashSuffix + extension); - Log.LogMessage (MessageImportance.Normal, $"Hash found using '{HashHeader}', destination file changing to '{destinationFile}'."); - } - } - if (File.Exists (destinationFile.ItemSpec)) { - Log.LogMessage (MessageImportance.Normal, $"Skipping uri '{uri}' as destination file already exists '{destinationFile}'."); - return destinationFile; - } - var dp = Path.GetDirectoryName (destinationFile.ItemSpec); - var dn = Path.GetFileName (destinationFile.ItemSpec); - var tempPath = Path.Combine (dp, "." + dn + ".download"); - Directory.CreateDirectory(dp); - - int retries = Math.Max (0, MaxRetries); - for (int attempt = 0; attempt <= retries; attempt++) { - Log.LogMessage (MessageImportance.Normal, $"Downloading `{uri}` to `{tempPath}` (attempt {attempt + 1} of {retries + 1})."); - try { - using (var r = await client.GetAsync (uri, HttpCompletionOption.ResponseHeadersRead, source.Token)) { - r.EnsureSuccessStatusCode (); - using (var s = await r.Content.ReadAsStreamAsync ()) - using (var o = File.Create (tempPath)) { - await s.CopyToAsync (o, 4096, source.Token); - } - } - Log.LogMessage (MessageImportance.Low, $"mv '{tempPath}' '{destinationFile}'."); - File.Move (tempPath, destinationFile.ItemSpec); - return destinationFile; - } - catch (Exception e) when (attempt < retries && !source.IsCancellationRequested) { - var delay = attempt < RetryDelays.Length ? RetryDelays [attempt] : RetryDelays [RetryDelays.Length - 1]; - Log.LogWarning ("Failed to download URL `{0}` (attempt {1} of {2}): {3}. Retrying in {4} seconds.", - uri, attempt + 1, retries + 1, e.Message, (int) delay.TotalSeconds); - try { - File.Delete (tempPath); - } catch { - } - try { - await TTask.Delay (delay, source.Token); - } catch (OperationCanceledException) { - break; - } - } - catch (Exception e) { - Log.LogError ("Unable to download URL `{0}` to `{1}`: {2}", uri, destinationFile, e.Message); - Log.LogErrorFromException (e); - } - } - return destinationFile; - } - - async Task CheckHashHeader (HttpClient client, CancellationTokenSource source, string uri) - { - var request = new HttpRequestMessage (HttpMethod.Head, uri); - using (var response = await client.SendAsync (request, source.Token)) { - response.EnsureSuccessStatusCode (); - if (response.Headers.TryGetValues (HashHeader, out var values)) { - foreach (var value in values) { - Log.LogMessage (MessageImportance.Low, $"{HashHeader}: {value}"); - - //Current format: `x-goog-hash: crc32c=8HATIw==` - if (!string.IsNullOrWhiteSpace (value)) { - return value.Trim (); - } - } - } - } - - return null; - } - } -} diff --git a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/GitCommitTime.cs b/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/GitCommitTime.cs deleted file mode 100644 index bc2e7bc69cf..00000000000 --- a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/GitCommitTime.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; - -using Microsoft.Build.Framework; - -namespace Xamarin.Android.BuildTools.PrepTasks -{ - public sealed class GitCommitTime : Git - { - [Output] - public string Time { get; set; } - - protected override bool LogTaskMessages { - get { return false; } - } - - public GitCommitTime () - { - } - - public override bool Execute () - { - Log.LogMessage (MessageImportance.Low, $"Task {nameof (GitCommitTime)}"); - Log.LogMessage (MessageImportance.Low, $" {nameof (WorkingDirectory)}: {WorkingDirectory.ItemSpec}"); - - base.Execute (); - - Log.LogMessage (MessageImportance.Low, $" [Output] {nameof (Time)}: {Time}"); - - return !Log.HasLoggedErrors; - } - - protected override string GenerateCommandLineCommands () - { - //NOTE: this command needs to return a string that is valid to pass to DateTime.Parse() - // The MSBuild task requires this: https://docs.microsoft.com/en-us/visualstudio/msbuild/touch-task - return "log -1 --format=%cd --date=format-local:\"%Y/%m/%d %H:%M:%S\""; - } - - protected override void LogEventsFromTextOutput (string singleLine, MessageImportance messageImportance) - { - if (string.IsNullOrEmpty (singleLine)) - return; - Time = singleLine; - } - } -} diff --git a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/GitDiff.cs b/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/GitDiff.cs deleted file mode 100644 index a59a540d67c..00000000000 --- a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/GitDiff.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; - -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -using IOFile = System.IO.File; - -namespace Xamarin.Android.BuildTools.PrepTasks -{ - public sealed class GitDiff : Git - { - protected override bool LogTaskMessages { - get { return false; } - } - - protected override bool PreserveOutput { - get { return false; } - } - - protected override string GenerateCommandLineCommands () - { - return "diff " + Arguments; - } - } -} diff --git a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/HashFileContents.cs b/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/HashFileContents.cs deleted file mode 100644 index 5cefc7e7518..00000000000 --- a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/HashFileContents.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; - -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace Xamarin.Android.BuildTools.PrepTasks -{ - public class HashFileContents : Task - { - [Required] - public ITaskItem[] Files { get; set; } - - - public string HashAlgorithm { get; set; } = "SHA1"; - - public int AbbreviatedHashLength { get; set; } = 8; - - [Output] - // Specifies %(Hashes.Target), %(Hashes.AbbreviatedHash). Hash is %(Hashes.Identity)). - public ITaskItem[] Hashes { get; set; } - - [Output] - public string CompleteHash { get; set; } - - [Output] - public string AbbreviatedCompleteHash { get; set; } - - public override bool Execute () - { - Log.LogMessage (MessageImportance.Low, $"Task {nameof (HashFileContents)}"); - Log.LogMessage (MessageImportance.Low, $" {nameof (AbbreviatedHashLength)}: {AbbreviatedHashLength}"); - Log.LogMessage (MessageImportance.Low, $" {nameof (HashAlgorithm)}: {HashAlgorithm}"); - Log.LogMessage (MessageImportance.Low, $" {nameof (Files)}:"); - foreach (var e in Files) { - Log.LogMessage (MessageImportance.Low, $" {e.ItemSpec}"); - } - - ProcessFiles (); - - Log.LogMessage (MessageImportance.Low, $" [Output] {nameof (AbbreviatedCompleteHash)}: {AbbreviatedCompleteHash}"); - Log.LogMessage (MessageImportance.Low, $" [Output] {nameof (CompleteHash)}: {CompleteHash}"); - Log.LogMessage (MessageImportance.Low, $" [Output] {nameof (Hashes)}:"); - foreach (var e in Hashes) { - Log.LogMessage (MessageImportance.Low, $" {e.GetMetadata ("Target")}: {e.ItemSpec}:"); - } - return !Log.HasLoggedErrors; - } - - void ProcessFiles () - { - var hashes = new List (Files.Length); - byte[] block = new byte [4096]; - using (var complete = CreateHashAlgorithm (HashAlgorithm)) { - foreach (var file in Files) { - var hash = ProcessFile (complete, block, file.ItemSpec); - var e = new TaskItem (hash); - e.SetMetadata ("Target", Path.GetFullPath (file.ItemSpec)); - e.SetMetadata ("AbbreviatedHash", hash.Substring (0, AbbreviatedHashLength)); - hashes.Add (e); - } - complete.TransformFinalBlock (block, 0, 0); - CompleteHash = FormatHash (complete.Hash); - AbbreviatedCompleteHash = CompleteHash.Substring (0, AbbreviatedHashLength); - } - Hashes = hashes.ToArray (); - } - - string ProcessFile (HashAlgorithm complete, byte[] block, string path) - { - using (var memoryStream = new MemoryStream ()) { - - //Read the file into a MemoryStream, ignoring newlines - using (var file = File.OpenRead (path)) { - int readByte; - while ((readByte = file.ReadByte()) != -1) { - byte b = (byte)readByte; - if (b != '\r' && b != '\n') { - memoryStream.WriteByte (b); - } - } - } - memoryStream.Seek (0, SeekOrigin.Begin); - - using (var fileHash = CreateHashAlgorithm (HashAlgorithm)) { - int read; - while ((read = memoryStream.Read (block, 0, block.Length)) > 0) { - complete.TransformBlock (block, 0, read, block, 0); - fileHash.TransformBlock (block, 0, read, block, 0); - } - fileHash.TransformFinalBlock (block, 0, 0); - return FormatHash (fileHash.Hash); - } - } - } - - string FormatHash (byte[] hash) - { - return string.Join ("", hash.Select (b => b.ToString ("x2"))); - } - - #pragma warning disable CA5350 // used for content hashing, not security - static System.Security.Cryptography.HashAlgorithm CreateHashAlgorithm (string name) => - name.ToUpperInvariant () switch { - "SHA1" => SHA1.Create (), - "SHA256" => SHA256.Create (), - "SHA384" => SHA384.Create (), - "SHA512" => SHA512.Create (), - _ => throw new NotSupportedException ($"Hash algorithm '{name}' is not supported."), - }; - #pragma warning restore CA5350 - } -} - diff --git a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/NDKInfo.cs b/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/NDKInfo.cs deleted file mode 100644 index b7f1aee7246..00000000000 --- a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/NDKInfo.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.IO; -using System.Text; - -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace Xamarin.Android.BuildTools.PrepTasks -{ - public class NDKInfo : Task - { - [Required] - public string NDKDirectory { get; set; } - - [Output] - public string NDKRevision { get; set; } - - [Output] - public string NDKVersionMajor { get; set; } - - [Output] - public string NDKVersionMinor { get; set; } - - [Output] - public string NDKVersionMicro { get; set; } - - [Output] - public string NDKMinimumApiAvailable { get; set; } - - public override bool Execute () - { - string props = Path.Combine (NDKDirectory, "source.properties"); - - if (!File.Exists (props)) - Log.LogError ($"NDK version file not found at {props}"); - else - GatherInfo (props); - - return !Log.HasLoggedErrors; - } - - void GatherInfo (string props) - { - string[] lines = File.ReadAllLines (props); - - foreach (string l in lines) { - string line = l.Trim (); - string[] parts = line.Split (new char[] {'='}, 2); - if (parts.Length != 2) - continue; - - if (String.Compare ("Pkg.Revision", parts [0].Trim (), StringComparison.Ordinal) != 0) - continue; - - string rev = parts [1].Trim (); - NDKRevision = rev; - - Version ver; - if (!Version.TryParse (rev, out ver)) { - Log.LogError ($"Unable to parse NDK revision '{rev}' as a valid version string"); - return; - } - - NDKVersionMajor = ver.Major.ToString (); - NDKVersionMinor = ver.Minor.ToString (); - NDKVersionMicro = ver.Build.ToString (); - break; - } - - int minimumApi = Int32.MaxValue; - string platforms = Path.Combine (NDKDirectory, "platforms"); - foreach (string p in Directory.EnumerateDirectories (platforms, "android-*", SearchOption.TopDirectoryOnly)) { - string pdir = Path.GetFileName (p); - string[] parts = pdir.Split (new char[] { '-' }, 2); - if (parts.Length != 2) - continue; - - int api; - if (!Int32.TryParse (parts [1].Trim (), out api)) - continue; - - if (api >= minimumApi) - continue; - - minimumApi = api; - } - - NDKMinimumApiAvailable = minimumApi.ToString (); - } - } -} diff --git a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/ParseExternalGitDependencies.cs b/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/ParseExternalGitDependencies.cs deleted file mode 100644 index 71a89b40252..00000000000 --- a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/ParseExternalGitDependencies.cs +++ /dev/null @@ -1,70 +0,0 @@ - -using System.Collections.Generic; -using System.IO; -using System.Text.RegularExpressions; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace Xamarin.Android.BuildTools.PrepTasks -{ - public class ParseExternalGitDependencies : Task - { - [Required] - public string ExternalFilePath { get; set; } - - /* %(ExternalGitDependencies.Owner) - Repo owner - * %(ExternalGitDependencies.Name) - Repo name - * %(ExternalGitDependencies.Branch) - Branch name - * %(ExternalGitDependencies.Identity) - Commit hash - */ - [Output] - public ITaskItem[] ExternalGitDependencies { get; set; } - - static readonly Regex externalRegex = new Regex (@" -^ -\s* -(?\#.*) -| -( - \s* - (?[^/]+) - / - (?[^:]+) - : - (?[^@]+) - @ - (?.*) -) -$ -", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace); - - public override bool Execute () - { - if (!File.Exists (ExternalFilePath)) { - Log.LogError($"Unable to find dependency file at: {ExternalFilePath}"); - return false; - } - - string[] unparsedExternals = File.ReadAllLines (ExternalFilePath); - var externals = new List (unparsedExternals.Length); - - foreach (string external in unparsedExternals) { - Match match = externalRegex.Match (external); - if (match != null && match.Success) { - if (match.Groups["comment"].Success) { - // Ignore matching lines which start with '#'. - continue; - } - var e = new TaskItem (match.Groups["commit"].Value); - e.SetMetadata ("Owner", match.Groups["owner"].Value); - e.SetMetadata ("Name", match.Groups["repo"].Value); - e.SetMetadata ("Branch", match.Groups["branch"].Value); - externals.Add (e); - } - } - - ExternalGitDependencies = externals.ToArray (); - return !Log.HasLoggedErrors; - } - } -} diff --git a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/PrepareInstall.cs b/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/PrepareInstall.cs deleted file mode 100644 index 04a456866f3..00000000000 --- a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/PrepareInstall.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace Xamarin.Android.BuildTools.PrepTasks -{ - public class PrepareInstall : Task - { - [Required] - public ITaskItem Program { get; set; } - - public bool UseSudo { get; set; } - public string HostOS { get; set; } - public string HostOSName { get; set; } - - [Output] - public string InstallCommand { get; set; } - - [Output] - public ITaskItem DownloadUrl { get; set; } - - string Sudo; - string SudoBrew; - - public override bool Execute () - { - Log.LogMessage (MessageImportance.Low, $"Task {nameof (PrepareInstall)}"); - Log.LogMessage (MessageImportance.Low, $" {nameof (HostOS)}: {HostOS}"); - Log.LogMessage (MessageImportance.Low, $" {nameof (HostOSName)}: {HostOSName}"); - Log.LogMessage (MessageImportance.Low, $" {nameof (Program)}: {Program}"); - Log.LogMessage (MessageImportance.Low, $" {nameof (UseSudo)}: {UseSudo}"); - - SetSudo (); - SetDownloadUrl (); - SetInstallCommand (); - - Log.LogMessage (MessageImportance.Low, $" [Output] {nameof (DownloadUrl)}: {DownloadUrl} [Url: {DownloadUrl?.GetMetadata ("Url")}]"); - Log.LogMessage (MessageImportance.Low, $" [Output] {nameof (InstallCommand)}: {InstallCommand}"); - - return !Log.HasLoggedErrors; - } - - string GetHostProperty (string property) - { - return Which.GetHostProperty (Program, property, HostOS, HostOSName); - } - - void SetSudo () - { - if (!UseSudo || string.Equals ("Windows", HostOS, StringComparison.OrdinalIgnoreCase)) - return; - - Sudo = "sudo "; - - if (!string.Equals ("Darwin", HostOS, StringComparison.OrdinalIgnoreCase)) { - return; - } - string brewFilename; - var brewPath = Which.GetProgramLocation ("brew", out brewFilename); - if (string.IsNullOrEmpty (brewPath)) { - return; - } - var brewVersion = Which.GetProgramVersion (HostOS, $"{brewPath} --version"); - if (brewVersion < new Version (1, 1)) { - SudoBrew = "sudo "; - return; - } - } - - void SetDownloadUrl () - { - var minUrl = GetHostProperty ("MinimumUrl"); - if (string.IsNullOrEmpty (minUrl)) - return; - - DownloadUrl = new TaskItem (GetFilenameFromUrl (minUrl)); - DownloadUrl.SetMetadata ("Url", minUrl); - } - - string GetFilenameFromUrl (string url) - { - var u = new Uri (url); - var p = u.AbsolutePath; - var s = p.LastIndexOf ('/'); - if (s >= 0 && p.Length > (s+1)) { - return p.Substring (s+1); - } - return Program.ItemSpec + ".bin"; - } - - void SetInstallCommand () - { - var install = GetHostProperty ("Install"); - if (install != null) { - InstallCommand = Sudo + install; - return; - } - if (string.Equals (HostOS, "Darwin", StringComparison.OrdinalIgnoreCase)) { - string tap = Program.GetMetadata ("HomebrewTap"); - if (!string.IsNullOrEmpty (tap)) { - tap = $"{SudoBrew}brew tap '{tap}';"; - } - - var brew = Program.GetMetadata ("Homebrew"); - if (!string.IsNullOrEmpty (brew)) { - InstallCommand = $"{tap}{SudoBrew}brew install '{brew}'"; - } - return; - } - // TODO: other platforms - var min = Program.GetMetadata ("MinimumVersion"); - var ver = ""; - if (!string.IsNullOrEmpty (min)) { - ver = $", version >= {min}"; - var max = Program.GetMetadata ("MaximumVersion"); - if (!string.IsNullOrEmpty (max)) { - ver += $" and <= {max}"; - } - } - Log.LogError ($"Missing dependency detected. For {HostOS} we do not know how to install program `{Program.ItemSpec}`{ver}."); - } - } -} - diff --git a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/RunParallelTargets.cs b/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/RunParallelTargets.cs deleted file mode 100644 index 8b496cfff76..00000000000 --- a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/RunParallelTargets.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; - -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace Xamarin.Android.BuildTools.PrepTasks.Xamarin.Android.BuildTools.PrepTasks -{ - public class RunParallelTargets : Task - { - [Required] - public ITaskItem ProjectFile {get; set;} - - public string Configuration {get; set;} - - public string MSBuildBinPath {get; set;} - - public string MSBuildBinaryLogParameterPrefix {get; set;} - - public ITaskItem[] Targets {get; set;} - - public override bool Execute () - { - if (Targets?.Length == 0) - return true; - - var processes = new Process [Targets.Length]; - for (int i = 0; i < Targets.Length; ++i) { - processes[i] = CreateProcess (Targets[i].ItemSpec); - } - - bool success = true; - for (int i = 0; i < processes.Length; ++i) { - processes[i].WaitForExit (); - if (processes[i].ExitCode != 0) { - Log.LogError ($"Execution of target {Targets [i].ItemSpec} exited with code {processes [i].ExitCode}."); - success = false; - } - } - return success; - } - - Process CreateProcess (string target) - { - var binlogOption = ""; - if (!string.IsNullOrEmpty (MSBuildBinaryLogParameterPrefix)) { - var date = DateTime.Now.ToString ("yyyyMMddTHHmmss"); - // -$([System.DateTime]::Now.ToString ("yyyyMMddTHHmmss"))-Target-%(Identity).binlog - binlogOption = $"{MSBuildBinaryLogParameterPrefix}-{date}-Target-{target}.binlog\""; - } - var config = string.IsNullOrEmpty (Configuration) ? "" : $"/p:Configuration={Configuration}"; - var command = $"{config} \"{ProjectFile.ItemSpec}\" {binlogOption} /t:{target}"; - var msbuild = (string.IsNullOrEmpty (MSBuildBinPath) || Path.DirectorySeparatorChar == '/') - ? "msbuild" - : Path.Combine (MSBuildBinPath, "msbuild"); - var parameters = new ProcessStartInfo (msbuild, command) { - CreateNoWindow = true, - UseShellExecute = false, - WindowStyle = ProcessWindowStyle.Hidden, - }; - var process = Process.Start (parameters); - return process; - } - } -} diff --git a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/SetEnvironmentVariable.cs b/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/SetEnvironmentVariable.cs deleted file mode 100644 index e46cd9f3d92..00000000000 --- a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/SetEnvironmentVariable.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -using System; - -namespace Xamarin.Android.BuildTools.PrepTasks -{ - public class SetEnvironmentVariable : Task - { - [Required] - public string Name { get; set; } - - [Required] - public string Value { get; set; } - - public override bool Execute () - { - Log.LogMessage (MessageImportance.Low, $"Task {nameof (SetEnvironmentVariable)}"); - Log.LogMessage (MessageImportance.Low, $" {nameof (Name)}: {Name}"); - Log.LogMessage (MessageImportance.Low, $" {nameof (Value)}: {Value}"); - - Environment.SetEnvironmentVariable(Name, Value); - - return !Log.HasLoggedErrors; - } - } -} diff --git a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/SystemUnzip.cs b/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/SystemUnzip.cs deleted file mode 100644 index 3a661ec9117..00000000000 --- a/build-tools/xa-prep-tasks/Xamarin.Android.BuildTools.PrepTasks/SystemUnzip.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -using TTask = System.Threading.Tasks.Task; -using MTask = Microsoft.Build.Utilities.Task; - -namespace Xamarin.Android.BuildTools.PrepTasks { - - public class SystemUnzip : MTask - { - [Required] - public ITaskItem[] SourceFiles { get; set; } - - public string SourceEntryGlob { get; set; } - - public string EntryNameEncoding { get; set; } - - public string HostOS { get; set; } - - public string TempUnzipDir { get; set; } - - [Required] - public ITaskItem DestinationFolder { get; set; } - - string[] SourceEntryGlobParts; - - public override bool Execute () - { - Log.LogMessage (MessageImportance.Low, $"{nameof (SystemUnzip)}:"); - Log.LogMessage (MessageImportance.Low, $" {nameof (DestinationFolder)}: {DestinationFolder.ItemSpec}"); - Log.LogMessage (MessageImportance.Low, $" {nameof (EntryNameEncoding)}: {EntryNameEncoding}"); - Log.LogMessage (MessageImportance.Low, $" {nameof (HostOS)}: {HostOS}"); - - Log.LogMessage (MessageImportance.Low, $" {nameof (SourceEntryGlob)}: {SourceEntryGlob}"); - Log.LogMessage (MessageImportance.Low, $" {nameof (SourceFiles)}:"); - for (int i = 0; i < SourceFiles.Length; ++i) { - var sf = SourceFiles [i].ItemSpec; - var rp = SourceFiles [i].GetMetadata ("DestDir"); - rp = string.IsNullOrEmpty (rp) - ? "" - : " [ " + rp + " ]"; - Log.LogMessage (MessageImportance.Low, " {0}{1}", sf, rp); - } - - if (File.Exists (DestinationFolder.ItemSpec)) { - Log.LogError ($"{nameof (DestinationFolder)} must be a directory!"); - return false; - } - - SourceEntryGlobParts = (SourceEntryGlob ?? "*").Split ('/', '\\'); - - Directory.CreateDirectory (DestinationFolder.ItemSpec); - - var tempDir = TempUnzipDir ?? Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ()); - Directory.CreateDirectory (tempDir); - Log.LogMessage (MessageImportance.Low, $" Extracting into temporary directory: {tempDir}"); - - var encoding = string.IsNullOrEmpty (EntryNameEncoding) - ? null - : Encoding.GetEncoding (EntryNameEncoding); - - var tasks = new TTask [SourceFiles.Length]; - for (int i = 0; i < SourceFiles.Length; ++i) { - var td = tempDir; - var sourceFile = SourceFiles [i].ItemSpec; - var relativeDestDir = SourceFiles [i].GetMetadata ("DestDir"); - var enc = encoding; - var destFolder = DestinationFolder.ItemSpec; - tasks [i] = TTask.Run (() => ExtractFile (td, sourceFile, relativeDestDir, destFolder, enc)); - } - - TTask.WaitAll (tasks); - - Directory.Delete (tempDir, recursive: true); - - return !Log.HasLoggedErrors; - } - - // Ignore CS1998 because there's no async System.IO APIs to use here. - // Instead, we're using Tasks so that we can extract multiple files - // in parallel via Task.Run() and Task.WaitAll(). -#pragma warning disable 1998 - async TTask ExtractFile (string tempDir, string sourceFile, string relativeDestDir, string destinationFolder, Encoding encoding) - { - var tempName = Path.GetRandomFileName (); - var nestedTemp = Path.Combine (tempDir, tempName); - Directory.CreateDirectory (nestedTemp); - - relativeDestDir = relativeDestDir?.Replace ('\\', Path.DirectorySeparatorChar); - - bool isWindows = string.Equals (HostOS, "Windows", StringComparison.OrdinalIgnoreCase); - if (isWindows) { - ZipFile.ExtractToDirectory (sourceFile, nestedTemp, encoding); - } else { - var start = new ProcessStartInfo ("unzip", $"\"{sourceFile}\" -d \"{nestedTemp}\"") { - CreateNoWindow = true, - UseShellExecute = false, - }; - Log.LogMessage (MessageImportance.Low, $"unzip \"{sourceFile}\" -d \"{nestedTemp}\""); - var p = Process.Start (start); - p.WaitForExit (); - } - - var entries = GetExtractedSourceDirectories (nestedTemp); - - var seenDestFiles = new HashSet (); - - // "merge" directories from `name`/within `sourceFile` and `destinationFolder`. - // If we did e.g. `mv foo/lib destination/lib` *and* `destination/lib` *already exists*, - // we'd create `destination/lib/lib`, which isn't intended. - // If we did e.g. `mv foo/lib destination`, **mv**(1) may *overwrite* `destination/lib` - // if it already exists, which *also* isn't intended. - // If `destination/lib/example` and `sourceFile` contains a `lib/another` entry, - // then we want to create a `destination/lib/another` file. - foreach (var entry in entries) { - var name = Path.GetFileName (entry); - var destDir = string.IsNullOrEmpty (relativeDestDir) - ? destinationFolder - : Path.Combine (destinationFolder, relativeDestDir); - destDir = Path.Combine (destDir, name); - foreach (var file in Directory.EnumerateFiles (entry, "*", SearchOption.AllDirectories)) { - var relPath = file.Substring (entry.Length + 1); - var dest = Path.Combine (destDir, relPath); - seenDestFiles.Add (dest); - var destMdb = dest + ".mdb"; - if (File.Exists (destMdb) && !seenDestFiles.Contains (destMdb)) { - Log.LogMessage (MessageImportance.Low, $"rm \"{destMdb}\""); - File.Delete (destMdb); - } - var destPdb = Path.ChangeExtension (dest, ".pdb"); - if (File.Exists (destPdb) && !seenDestFiles.Contains (destPdb)) { - Log.LogMessage (MessageImportance.Low, $"rm \"{destPdb}\""); - File.Delete (destPdb); - } - Directory.CreateDirectory (Path.GetDirectoryName (dest)); - Log.LogMessage (MessageImportance.Low, $"mv '{file}' '{dest}'"); - if (Directory.Exists (entry)) { - ProcessStartInfo psi; - if (isWindows) { - psi = new ProcessStartInfo ("cmd", $@"/C move ""{file}"" ""{dest}""") { - CreateNoWindow = true, - UseShellExecute = false, - }; - } else { - psi = new ProcessStartInfo ("/bin/mv", $@"""{file}"" ""{dest}"""); - } - using (var p = Process.Start (psi)) { - p.WaitForExit (); - } - } - else { - if (File.Exists (dest)) - File.Delete (dest); - File.Move (file, dest); - } - // Don't attempt to set write/access time on linked files. - var destFileInfo = new FileInfo (dest); - if (!destFileInfo.Attributes.HasFlag (FileAttributes.ReparsePoint)) { - File.SetLastWriteTimeUtc (dest, DateTime.UtcNow); - } - } - } - } - - IEnumerable GetExtractedSourceDirectories (string root) - { - var entries = Directory.EnumerateDirectories (root, SourceEntryGlobParts [0], SearchOption.TopDirectoryOnly); - for (int i = 1; i < SourceEntryGlobParts.Length; ++i) { - entries = entries - .SelectMany (e => Directory.EnumerateDirectories (e, SourceEntryGlobParts [i], SearchOption.TopDirectoryOnly)); - } - return entries; - } -#pragma warning restore 1998 - } -} - diff --git a/build-tools/xaprepare/xaprepare/Application/Context.cs b/build-tools/xaprepare/xaprepare/Application/Context.cs index 2b634fc9976..d88afc51e21 100644 --- a/build-tools/xaprepare/xaprepare/Application/Context.cs +++ b/build-tools/xaprepare/xaprepare/Application/Context.cs @@ -292,11 +292,6 @@ public string DebugFileExtension { /// public string? LocalDotNetSdkArchive { get; set; } - /// - /// Set by if the archive has been downloaded and validated. - /// - public bool BuildToolsArchiveDownloaded { get; set; } - /// /// Determines whether or not we are running on a hosted azure pipelines agent. /// These agents have certain limitations, the most pressing being the amount of available storage. diff --git a/build-tools/xaprepare/xaprepare/Application/KnownConditions.cs b/build-tools/xaprepare/xaprepare/Application/KnownConditions.cs index 38904f5a1af..089e6036301 100644 --- a/build-tools/xaprepare/xaprepare/Application/KnownConditions.cs +++ b/build-tools/xaprepare/xaprepare/Application/KnownConditions.cs @@ -2,20 +2,13 @@ namespace Xamarin.Android.Prepare { public enum KnownConditions { - /// - /// If this condition is set, then Mono upgrade will be performed. It is unset by default because Mono upgrade - /// requires application restart or we may crash. Default: unset. - /// - AllowMonoUpdate, - /// /// If set, the outdated or missing programs will be installed. Default: set. /// AllowProgramInstallation, /// - /// Ignore missing programs and do not signal an error. This is useful in scenarios when we want to update - /// only a single program (e.g. the UpdateMono scenario) but not the rest. Default: unset. + /// Ignore missing programs and do not signal an error. Default: unset. /// IgnoreMissingPrograms, diff --git a/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs b/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs index 9e055ee2651..8e40419bb0b 100644 --- a/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs +++ b/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs @@ -6,7 +6,6 @@ static class KnownProperties public const string AndroidCmakeVersion = "AndroidCmakeVersion"; public const string AndroidCmakeVersionPath = "AndroidCmakeVersionPath"; public const string AndroidMinimumDotNetApiLevel = "AndroidMinimumDotNetApiLevel"; - public const string AndroidMinimumNonMonoApiLevel = "AndroidMinimumNonMonoApiLevel"; public const string AndroidDefaultTargetDotnetApiLevel = "AndroidDefaultTargetDotnetApiLevel"; public const string AndroidLatestStableApiLevel = "AndroidLatestStableApiLevel"; public const string AndroidLatestUnstableApiLevel = "AndroidLatestUnstableApiLevel"; @@ -36,18 +35,12 @@ static class KnownProperties public const string EmulatorVersion = "EmulatorVersion"; public const string EmulatorPkgRevision = "EmulatorPkgRevision"; public const string HostOS = "HostOS"; - public const string IgnoreMaxMonoVersion = "IgnoreMaxMonoVersion"; - public const string IgnoreMinMonoVersion = "IgnoreMinMonoVersion"; public const string JavaInteropFullPath = "JavaInteropFullPath"; public const string JavaSdkDirectory = "JavaSdkDirectory"; public const string JdkIncludePath = "JdkIncludePath"; public const string LibZipSourceFullPath = "LibZipSourceFullPath"; - public const string ManagedRuntime = "ManagedRuntime"; public const string MicrosoftAndroidSdkOutDir = "MicrosoftAndroidSdkOutDir"; public const string MonoCecilVersion = "MonoCecilVersion"; - public const string MonoDarwinPackageUrl = "MonoDarwinPackageUrl"; - public const string MonoRequiredMinimumVersion = "MonoRequiredMinimumVersion"; - public const string MonoRequiredMaximumVersion = "MonoRequiredMaximumVersion"; public const string MonoRuntimeFlavorDirName = "_MonoRuntimeFlavorDirName"; public const string MonoSourceFullPath = "MonoSourceFullPath"; public const string NativeRuntimeOutputRootDir = "NativeRuntimeOutputRootDir"; @@ -59,10 +52,6 @@ static class KnownProperties public const string TestOutputDirectory = "TestOutputDirectory"; public const string XABuildToolsFolder = "XABuildToolsFolder"; public const string XABuildToolsVersion = "XABuildToolsVersion"; - public const string XABuildToolsPackagePrefixMacOS = "XABuildToolsPackagePrefixMacOS"; - public const string XABuildToolsPackagePrefixWindows = "XABuildToolsPackagePrefixWindows"; - public const string XABuildToolsPackagePrefixLinux = "XABuildToolsPackagePrefixLinux"; - public const string XABuildToolsPackagePrefix = "XABuildToolsPackagePrefix"; public const string XABinRelativeInstallPrefix = "XABinRelativeInstallPrefix"; public const string XAInstallPrefix = "XAInstallPrefix"; public const string XAPackagesDir = "XAPackagesDir"; diff --git a/build-tools/xaprepare/xaprepare/Application/MonoPkgProgram.MacOS.cs b/build-tools/xaprepare/xaprepare/Application/MonoPkgProgram.MacOS.cs deleted file mode 100644 index 4d36a523533..00000000000 --- a/build-tools/xaprepare/xaprepare/Application/MonoPkgProgram.MacOS.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; - -namespace Xamarin.Android.Prepare -{ - class MonoPkgProgram : PkgProgram, IBuildInventoryItem - { - public string BuildToolName => "MonoFramework-MDK"; - public string BuildToolVersion => CurrentVersion; - - public MonoPkgProgram (string name, string packageId, Uri? packageUrl = null) - : base (name, packageId, packageUrl) - { - ExecutableName = "mono"; - } - - public override bool CanInstall () - { - // We do not want to return `false` here if Mono updates are disallowed - we want Install to be called to - // show the error message as returning `false` here might prevent other programs from updating, and there's - // no good reason for this. - if (!Context.Instance.CheckCondition (KnownConditions.AllowMonoUpdate)) - return base.CanInstall (); - return true; - } - - public override async Task Install () - { - if (Context.Instance.CheckCondition (KnownConditions.AllowMonoUpdate)) { - bool installSucceeded = await base.Install (); - if (installSucceeded) { - await DetermineCurrentVersion (); - AddToInventory (); - } - return installSucceeded; - } - - Log.ErrorLine ($"Mono needs to be updated but updates are disallowed in this scenario. Please run prepare with the '/s:{Scenario_UpdateMono.MyName}' parameter to update Mono."); - return false; - } - - protected override bool CheckWhetherInstalled () - { - IgnoreMaximumVersion = true; - IgnoreMinimumVersion = false; - return base.CheckWhetherInstalled (); - } - - protected override async Task DetermineCurrentVersion () - { - // Mono is special in that its package does not contain the full version, so we have to get it from the - // --version output instead. - SkipPkgUtilVersionCheck = true; - return await base.DetermineCurrentVersion (); - } - -#pragma warning disable 1998 - protected override async Task AfterDetect (bool installed) - { - if (!installed) - return; - - AddToInventory (); - } -#pragma warning restore 1998 - - public void AddToInventory () - { - if (!string.IsNullOrEmpty (BuildToolName) && !string.IsNullOrEmpty (BuildToolVersion) && !Context.Instance.BuildToolsInventory.ContainsKey (BuildToolName)) { - Context.Instance.BuildToolsInventory.Add (BuildToolName, BuildToolVersion); - } - } - } -} diff --git a/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in b/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in index 3fed29ec076..cf6cee1fa49 100644 --- a/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in +++ b/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in @@ -10,7 +10,6 @@ namespace Xamarin.Android.Prepare properties.Add (KnownProperties.AndroidCmakeVersion, StripQuotes ("@AndroidCmakeVersion@")); properties.Add (KnownProperties.AndroidCmakeVersionPath, StripQuotes (@"@AndroidCmakeVersionPath@")); properties.Add (KnownProperties.AndroidMinimumDotNetApiLevel, StripQuotes ("@AndroidMinimumDotNetApiLevel@")); - properties.Add (KnownProperties.AndroidMinimumNonMonoApiLevel, StripQuotes ("@AndroidMinimumNonMonoApiLevel@")); properties.Add (KnownProperties.AndroidDefaultTargetDotnetApiLevel, StripQuotes ("@AndroidDefaultTargetDotnetApiLevel@")); properties.Add (KnownProperties.AndroidLatestStableApiLevel, StripQuotes ("@AndroidLatestStableApiLevel@")); properties.Add (KnownProperties.AndroidLatestUnstableApiLevel, StripQuotes ("@AndroidLatestUnstableApiLevel@")); @@ -40,19 +39,13 @@ namespace Xamarin.Android.Prepare properties.Add (KnownProperties.EmulatorVersion, StripQuotes ("@EmulatorVersion@")); properties.Add (KnownProperties.EmulatorPkgRevision, StripQuotes ("@EmulatorPkgRevision@")); properties.Add (KnownProperties.HostOS, StripQuotes ("@HostOS@")); - properties.Add (KnownProperties.IgnoreMaxMonoVersion, StripQuotes ("@IgnoreMaxMonoVersion@")); - properties.Add (KnownProperties.IgnoreMinMonoVersion, StripQuotes ("@IgnoreMinMonoVersion@")); properties.Add (KnownProperties.JavaInteropFullPath, StripQuotes (@"@JavaInteropFullPath@")); properties.Add (KnownProperties.JavaSdkDirectory, StripQuotes (@"@JavaSdkDirectory@")); properties.Add (KnownProperties.JdkIncludePath, StripQuotes (@"@JdkIncludePath@")); properties.Add (KnownProperties.LibZipSourceFullPath, StripQuotes (@"@LibZipSourceFullPath@")); - properties.Add (KnownProperties.ManagedRuntime, StripQuotes (@"@ManagedRuntime@")); properties.Add (KnownProperties.MicrosoftAndroidSdkOutDir, StripQuotes (@"@MicrosoftAndroidSdkOutDir@")); properties.Add (KnownProperties.MonoCecilVersion, StripQuotes ("@MonoCecilVersion@")); - properties.Add (KnownProperties.MonoDarwinPackageUrl, StripQuotes ("@MonoDarwinPackageUrl@")); properties.Add (KnownProperties.MonoRuntimeFlavorDirName, StripQuotes ("@_MonoRuntimeFlavorDirName@")); - properties.Add (KnownProperties.MonoRequiredMinimumVersion, StripQuotes ("@MonoRequiredMinimumVersion@")); - properties.Add (KnownProperties.MonoRequiredMaximumVersion, StripQuotes ("@MonoRequiredMaximumVersion@")); properties.Add (KnownProperties.MonoSourceFullPath, StripQuotes (@"@MonoSourceFullPath@")); properties.Add (KnownProperties.NativeRuntimeOutputRootDir, StripQuotes (@"@NativeRuntimeOutputRootDir@")); properties.Add (KnownProperties.NinjaPath, StripQuotes (@"@NinjaPath@")); @@ -63,10 +56,6 @@ namespace Xamarin.Android.Prepare properties.Add (KnownProperties.TestOutputDirectory, StripQuotes (@"@TestOutputDirectory@")); properties.Add (KnownProperties.XABuildToolsFolder, StripQuotes (@"@XABuildToolsFolder@")); properties.Add (KnownProperties.XABuildToolsVersion, StripQuotes ("@XABuildToolsVersion@")); - properties.Add (KnownProperties.XABuildToolsPackagePrefixMacOS, StripQuotes ("@XABuildToolsPackagePrefixMacOS@")); - properties.Add (KnownProperties.XABuildToolsPackagePrefixWindows, StripQuotes ("@XABuildToolsPackagePrefixWindows@")); - properties.Add (KnownProperties.XABuildToolsPackagePrefixLinux, StripQuotes ("@XABuildToolsPackagePrefixLinux@")); - properties.Add (KnownProperties.XABuildToolsPackagePrefix, StripQuotes ("@XABuildToolsPackagePrefix@")); properties.Add (KnownProperties.XABinRelativeInstallPrefix, StripQuotes (@"@XABinRelativeInstallPrefix@")); properties.Add (KnownProperties.XAInstallPrefix, StripQuotes (@"@XAInstallPrefix@")); properties.Add (KnownProperties.XAPackagesDir, StripQuotes (@"@XAPackagesDir@")); diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/BuildAndroidPlatforms.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/BuildAndroidPlatforms.cs index 75fd5eb30a3..d2dd2054524 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/BuildAndroidPlatforms.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/BuildAndroidPlatforms.cs @@ -10,7 +10,6 @@ class BuildAndroidPlatforms public static string NdkMinimumAPI => Context.Instance.Properties.GetRequiredValue (KnownProperties.AndroidMinimumDotNetApiLevel); public static string NdkMinimumAPILegacy32 => NdkMinimumAPI; - public static string NdkMinimumNonMonoAPI => Context.Instance.Properties.GetRequiredValue (KnownProperties.AndroidMinimumNonMonoApiLevel); public static readonly List AllPlatforms = new List { new AndroidPlatform (apiName: "", apiLevel: 1, platformID: "1"), diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Linux.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Linux.cs index 3c8c33b2a26..be98ee37cb6 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Linux.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Linux.cs @@ -4,13 +4,6 @@ namespace Xamarin.Android.Prepare { partial class Configurables { - const string AdoptOpenJDKUpdate = "345"; - const string AdoptOpenJDKBuild = "b01"; - - const string JetBrainsOpenJDKOperatingSystem = "linux-x64"; - const string MicrosoftOpenJDKOperatingSystem = "linux-x64"; - const string AdoptOpenJDKOperatingSystem = "x64_linux"; - partial class Defaults { public const string NativeLibraryExtension = ".so"; diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.MacOS.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.MacOS.cs index 0ef308b7101..3ea71d15b40 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.MacOS.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.MacOS.cs @@ -1,12 +1,9 @@ using System; -using System.Runtime.InteropServices; namespace Xamarin.Android.Prepare { partial class Configurables { - static string MicrosoftOpenJDKOperatingSystem = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "macos-aarch64": "macos-x64"; - partial class Defaults { public const string NativeLibraryExtension = ".dylib"; diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Unix.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Unix.cs index 0b74c9d285c..01b91fbcf68 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Unix.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Unix.cs @@ -5,9 +5,6 @@ namespace Xamarin.Android.Prepare { partial class Configurables { - const string MicrosoftOpenJDKFileExtension = "tar.gz"; - const string AdoptOpenJDKArchiveExtension = "tar.gz"; - partial class Defaults { public const string DefaultCompiler = "cc"; diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Windows.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Windows.cs index 77e92649628..e4b80c85147 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Windows.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Windows.cs @@ -5,15 +5,6 @@ namespace Xamarin.Android.Prepare { partial class Configurables { - const string AdoptOpenJDKUpdate = "345"; - const string AdoptOpenJDKBuild = "b01"; - - const string JetBrainsOpenJDKOperatingSystem = "windows-x64"; - const string MicrosoftOpenJDKOperatingSystem = "windows-x64"; - const string MicrosoftOpenJDKFileExtension = "zip"; - const string AdoptOpenJDKOperatingSystem = "x64_windows"; - const string AdoptOpenJDKArchiveExtension = "zip"; - partial class Defaults { public const string NativeLibraryExtension = ".dll"; diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.cs index 3acf50d5774..a48f23b95f7 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.cs @@ -15,40 +15,26 @@ namespace Xamarin.Android.Prepare // partial class Configurables { - const string BinutilsVersion = "L_18.1.6-8.0.0-1"; - const string MicrosoftOpenJDKVersion = "21.0.8"; - const string MicrosoftOpenJDKRelease = "21.0.8"; - const string MicrosoftOpenJDKRootDirName = "jdk-21.0.8+9"; static Context ctx => Context.Instance; public static partial class Urls { - // https://aka.ms/download-jdk/microsoft-jdk-17.0.11-linux-x64.tar.gz - // https://aka.ms/download-jdk/microsoft-jdk-17.0.11-macOS-x64.tar.gz or https://aka.ms/download-jdk/microsoft-jdk-17.0.11-macos-aarch64.pkg - // https://aka.ms/download-jdk/microsoft-jdk-17.0.11-windows-x64.zip - public static readonly Uri MicrosoftOpenJDK = new Uri ($"https://aka.ms/download-jdk/microsoft-jdk-{MicrosoftOpenJDKVersion}-{MicrosoftOpenJDKOperatingSystem}.{MicrosoftOpenJDKFileExtension}"); - /// /// Base URL for all Android SDK and NDK downloads. Used in /// public static readonly Uri AndroidToolchain_AndroidUri = new Uri ("https://dl.google.com/android/repository/"); - - public static Uri BinutilsArchive = new Uri ($"https://github.com/dotnet/android-native-tools/releases/download/{BinutilsVersion}/xamarin-android-toolchain-{BinutilsVersion}.7z"); } public static partial class Defaults { - public static readonly string BinutilsVersion = Configurables.BinutilsVersion; public static readonly char[] PropertyListSeparator = new [] { ':' }; public static readonly string JdkFolder = "jdk-21"; public static readonly Version MicrosoftMinOpenJDKVersion = new Version (17, 0); public static readonly Version MicrosoftOpenJDKVersion = new Version (Configurables.MicrosoftOpenJDKVersion); - public static readonly Version MicrosoftOpenJDKRelease = new Version (Configurables.MicrosoftOpenJDKRelease); - public static readonly string MicrosoftOpenJDKRootDirName = Configurables.MicrosoftOpenJDKRootDirName; public const string DotNetTestRuntimeVersion = "3.1.11"; @@ -161,6 +147,17 @@ public static partial class Defaults { "x86_64", "x86_64" }, }; + /// + /// ABIs that support the NativeAOT runtime. Used to determine which ABIs + /// need the higher API-level CRT/sysroot files in the NativeAOT runtime pack. + /// When adding a new ABI here, also update SupportNativeAOT in + /// build-tools/scripts/Ndk.projitems.in. + /// + public static readonly HashSet NativeAotSupportedAbis = new (StringComparer.Ordinal) { + "arm64-v8a", + "x86_64", + }; + public static readonly List NDKTools = new List { // Tools prefixed with architecture triple new NDKTool (name: "as", prefixed: true), @@ -201,10 +198,6 @@ public static partial class Paths public static string MonoAndroidFrameworksRootDir => GetCachedPath (ref monoAndroidFrameworksRootDir, () => Path.Combine (XAInstallPrefix, MonoAndroidFrameworksSubDir)); public static string InstallMSBuildDir => GetCachedPath (ref installMSBuildDir, () => ctx.Properties.GetRequiredValue (KnownProperties.MicrosoftAndroidSdkOutDir)); - // OpenJDK - public static string OpenJDKInstallDir => GetCachedPath (ref openJDKInstallDir, () => Path.Combine (ctx.Properties.GetRequiredValue (KnownProperties.AndroidToolchainDirectory), Defaults.JdkFolder)); - public static string OpenJDKCacheDir => GetCachedPath (ref openJDKCacheDir, () => ctx.Properties.GetRequiredValue (KnownProperties.AndroidToolchainCacheDirectory)); - // .NET 6+ public static string NetcoreAppRuntimeAndroidARM => GetCachedPath (ref netcoreAppRuntimeAndroidARM, () => GetNetcoreAppRuntimePath (ctx, "arm")); public static string NetcoreAppRuntimeAndroidARM64 => GetCachedPath (ref netcoreAppRuntimeAndroidARM64, () => GetNetcoreAppRuntimePath (ctx, "arm64")); @@ -243,9 +236,6 @@ public static partial class Paths public static string AndroidClangRootDirectory => GetCachedPath (ref androidClangRootDirectory, () => Path.Combine (AndroidToolchainRootDirectory, "lib", "clang")); public static string AndroidToolchainBinDirectory => GetCachedPath (ref androidToolchainBinDirectory, () => Path.Combine (AndroidToolchainRootDirectory, "bin")); public static string AndroidToolchainSysrootLibDirectory => GetCachedPath (ref androidToolchainSysrootLibDirectory, () => Path.Combine (AndroidToolchainRootDirectory, "sysroot", "usr", "lib")); - public static string WindowsBinutilsInstallDir => GetCachedPath (ref windowsBinutilsInstallDir, () => Path.Combine (InstallMSBuildDir, "binutils")); - public static string HostBinutilsInstallDir => GetCachedPath (ref hostBinutilsInstallDir, () => Path.Combine (InstallMSBuildDir, ctx.Properties.GetRequiredValue (KnownProperties.HostOS), "binutils")); - public static string BinutilsCacheDir => ctx.Properties.GetRequiredValue (KnownProperties.AndroidToolchainCacheDirectory); public static string AndroidBuildToolsCacheDir => ctx.Properties.GetRequiredValue (KnownProperties.AndroidToolchainCacheDirectory); // not really configurables, merely convenience aliases for more frequently used paths that come from properties @@ -302,11 +292,7 @@ static string GetCachedPath (ref string? variable, Func creator) static string? installMSBuildDir; static string? monoAndroidFrameworksRootDir; static string? externalJavaInteropDir; - static string? openJDKInstallDir; - static string? openJDKCacheDir; static string? configurationPropsGeneratedPath; - static string? windowsBinutilsInstallDir; - static string? hostBinutilsInstallDir; static string? netcoreAppRuntimeAndroidARM; static string? netcoreAppRuntimeAndroidARM64; static string? netcoreAppRuntimeAndroidX86; diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs index 1c67c51d241..bb20004668c 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs @@ -25,7 +25,6 @@ public AndroidToolchain () string EmulatorPkgRevision = GetRequiredProperty (KnownProperties.EmulatorPkgRevision); string XABuildToolsFolder = GetRequiredProperty (KnownProperties.XABuildToolsFolder); string XABuildToolsVersion = GetRequiredProperty (KnownProperties.XABuildToolsVersion); - string XABuildToolsPackagePrefix = Context.Instance.Properties [KnownProperties.XABuildToolsPackagePrefix] ?? String.Empty; string XAPlatformToolsVersion = GetRequiredProperty (KnownProperties.XAPlatformToolsVersion); string XAPlatformToolsPackagePrefix = Context.Instance.Properties [KnownProperties.XAPlatformToolsPackagePrefix] ?? String.Empty; bool isArm64Apple = Context.Instance.OS.Flavor == "macOS" && RuntimeInformation.OSArchitecture == Architecture.Arm64; @@ -107,7 +106,7 @@ public AndroidToolchain () buildToolName: $"android-ndk-r{AndroidNdkVersion}", buildToolVersion: AndroidPkgRevision ), - new AndroidToolchainComponent ($"{XABuildToolsPackagePrefix}build-tools_r{XABuildToolsVersion}_{altOsTag}", + new AndroidToolchainComponent ($"build-tools_r{XABuildToolsVersion}_{altOsTag}", destDir: Path.Combine ("build-tools", XABuildToolsFolder), isMultiVersion: true, buildToolName: "android-sdk-build-tools", diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/MacOS.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/MacOS.cs index ed755d6d38c..e92fac0645d 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/MacOS.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/MacOS.cs @@ -23,15 +23,6 @@ protected override void InitializeDependencies () { Dependencies.AddRange (programs); - if (Context.Instance.CheckCondition (KnownConditions.AllowMonoUpdate)) { - Dependencies.Add ( - new MonoPkgProgram ("Mono", "com.xamarin.mono-MDK.pkg", new Uri (Context.Instance.Properties.GetRequiredValue (KnownProperties.MonoDarwinPackageUrl))) { - MinimumVersion = Context.Instance.Properties.GetRequiredValue (KnownProperties.MonoRequiredMinimumVersion), - MaximumVersion = Context.Instance.Properties.GetRequiredValue (KnownProperties.MonoRequiredMaximumVersion), - } - ); - } - // Allow using git from $PATH if it has the right version (bool success, string bv) = Utilities.GetProgramVersion (git.Name); if (success && Version.TryParse (bv, out Version? gitVersion) && diff --git a/build-tools/xaprepare/xaprepare/OperatingSystems/OS.cs b/build-tools/xaprepare/xaprepare/OperatingSystems/OS.cs index 122b6611bcc..91c4247851f 100644 --- a/build-tools/xaprepare/xaprepare/OperatingSystems/OS.cs +++ b/build-tools/xaprepare/xaprepare/OperatingSystems/OS.cs @@ -134,10 +134,7 @@ abstract class OS : AppObject /// protected virtual bool InitOS () { - JavaHome = Environment.GetEnvironmentVariable ("JI_JAVA_HOME") ?? String.Empty; - if (string.IsNullOrEmpty (JavaHome)) { - JavaHome = Context.Instance.Properties.GetValue (KnownProperties.JavaSdkDirectory)?.Trim () ?? String.Empty; - } + JavaHome = Context.Instance.Properties.GetValue (KnownProperties.JavaSdkDirectory)?.Trim () ?? String.Empty; if (String.IsNullOrEmpty (JavaHome)) { var androidToolchainDirectory = Context.Instance.Properties.GetValue (KnownProperties.AndroidToolchainDirectory)?.Trim () ?? String.Empty; JavaHome = Path.Combine (androidToolchainDirectory, Configurables.Defaults.JdkFolder); diff --git a/build-tools/xaprepare/xaprepare/Scenarios/Scenario_AndroidTestDependencies.cs b/build-tools/xaprepare/xaprepare/Scenarios/Scenario_AndroidTestDependencies.cs index 455750413e0..d3f58521063 100644 --- a/build-tools/xaprepare/xaprepare/Scenarios/Scenario_AndroidTestDependencies.cs +++ b/build-tools/xaprepare/xaprepare/Scenarios/Scenario_AndroidTestDependencies.cs @@ -8,7 +8,7 @@ partial class Scenario_AndroidTestDependencies : ScenarioNoStandardEndSteps protected virtual AndroidToolchainComponentType AndroidSdkNdkType => AndroidToolchainComponentType.CoreDependency; public Scenario_AndroidTestDependencies () - : base ("AndroidTestDependencies", "Install Android SDK, OpenJDK and .NET preview test dependencies.") + : base ("AndroidTestDependencies", "Install Android SDK and .NET preview test dependencies.") {} protected Scenario_AndroidTestDependencies (string name, string description) @@ -18,7 +18,6 @@ protected Scenario_AndroidTestDependencies (string name, string description) protected override void AddSteps (Context context) { Steps.Add (new Step_InstallDotNetPreview ()); - Steps.Add (new Step_InstallMicrosoftOpenJDK (allowJIJavaHomeMatch: true)); Steps.Add (new Step_Android_SDK_NDK (AndroidSdkNdkType)); // disable installation of missing programs... diff --git a/build-tools/xaprepare/xaprepare/Scenarios/Scenario_EmulatorTestDependencies.cs b/build-tools/xaprepare/xaprepare/Scenarios/Scenario_EmulatorTestDependencies.cs index 4710b58ee2b..5370f0c6364 100644 --- a/build-tools/xaprepare/xaprepare/Scenarios/Scenario_EmulatorTestDependencies.cs +++ b/build-tools/xaprepare/xaprepare/Scenarios/Scenario_EmulatorTestDependencies.cs @@ -8,7 +8,7 @@ partial class Scenario_EmulatorTestDependencies : Scenario_AndroidTestDependenci protected override AndroidToolchainComponentType AndroidSdkNdkType => AndroidToolchainComponentType.CoreDependency | AndroidToolchainComponentType.EmulatorDependency; public Scenario_EmulatorTestDependencies () - : base ("EmulatorTestDependencies", "Install Android SDK (with emulator), OpenJDK, and .NET preview test dependencies.") + : base ("EmulatorTestDependencies", "Install Android SDK (with emulator) and .NET preview test dependencies.") {} } } diff --git a/build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.cs b/build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.cs index 35946068248..b2b701f82db 100644 --- a/build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.cs +++ b/build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.cs @@ -19,13 +19,10 @@ protected override void AddSteps (Context context) throw new ArgumentNullException (nameof (context)); Steps.Add (new Step_InstallDotNetPreview ()); - Steps.Add (new Step_InstallMicrosoftOpenJDK ()); Steps.Add (new Step_Android_SDK_NDK ()); Steps.Add (new Step_GenerateFiles (atBuildStart: true)); Steps.Add (new Step_PrepareProps ()); - Steps.Add (new Step_InstallGNUBinutils ()); Steps.Add (new Step_GenerateCGManifest ()); - Steps.Add (new Step_Get_Android_BuildTools ()); } protected override void AddEndSteps (Context context) diff --git a/build-tools/xaprepare/xaprepare/Scenarios/Scenario_UpdateMono.Unix.cs b/build-tools/xaprepare/xaprepare/Scenarios/Scenario_UpdateMono.Unix.cs deleted file mode 100644 index cd2c0146821..00000000000 --- a/build-tools/xaprepare/xaprepare/Scenarios/Scenario_UpdateMono.Unix.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Xamarin.Android.Prepare -{ - [Scenario (isDefault: false)] - class Scenario_UpdateMono : ScenarioNoStandardEndSteps - { - public const string MyName = "UpdateMono"; - - public Scenario_UpdateMono () - : base (MyName, "Perform basic detection steps AND update Mono if necessary") - {} - - protected override void AddSteps (Context context) - { - // Allow automatic provisioning... - context.AutoProvision = true; - - // ...and let it use sudo, because without it it's useless... - context.AutoProvisionUsesSudo = true; - - // ...no new steps here, just enable Mono updates... - context.SetCondition (KnownConditions.AllowMonoUpdate, true); - - // ...and disable installation of other programs... - context.SetCondition (KnownConditions.AllowProgramInstallation, false); - - // ...but do not signal an error when any are missing... - context.SetCondition (KnownConditions.IgnoreMissingPrograms, true); - } - } -} diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs b/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs index d987b23c878..629269adff9 100644 --- a/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs +++ b/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -70,10 +68,6 @@ protected override async Task Execute (Context context) toolchain.Components.ForEach (c => Check (context, packageCacheDir, sdkRoot, c, toInstall, 4)); if (toInstall.Count == 0) { - if (!AcceptLicenses (context, sdkRoot)) { - Log.ErrorLine ("Failed to accept Android SDK licenses"); - return false; - } WritePackageXmls (sdkRoot); return GatherNDKInfo (context); } @@ -114,61 +108,11 @@ protected override async Task Execute (Context context) await Unpack (context, tempDir, p); } - if (!AcceptLicenses (context, sdkRoot)) { - Log.ErrorLine ("Failed to accept Android SDK licenses"); - return false; - } - WritePackageXmls (sdkRoot); return GatherNDKInfo (context); } - bool AcceptLicenses (Context context, string sdkRoot) - { - string[] sdkManagerPaths = new[]{ - Path.Combine (sdkRoot, "cmdline-tools", context.Properties [KnownProperties.CommandLineToolsFolder] ?? String.Empty, "bin", "sdkmanager"), - Path.Combine (sdkRoot, "cmdline-tools", "latest", "bin", "sdkmanager"), - }; - string sdkManager = ""; - foreach (var sdkManagerPath in sdkManagerPaths) { - sdkManager = context.OS.Which (sdkManagerPath, required: false); - if (!string.IsNullOrEmpty (sdkManager)) - break; - } - if (sdkManager.Length == 0) - throw new InvalidOperationException ("sdkmanager not found"); - string jdkDir = context.OS.JavaHome; - - Log.Todo ("Modify ProcessRunner to allow standard input writing and switch to it here"); - // var runner = new ProcessRunner (sdkManager, "--licenses"); - // runner.StartInfoCallback = (ProcessStartInfo psi) => { - // if (!String.IsNullOrEmpty (jdkDir)) - // psi.EnvironmentVariables.Add ("JAVA_HOME", jdkDir); - // psi.RedirectStandardInput = true; - // }; - - var psi = new ProcessStartInfo (sdkManager, "--licenses") { - UseShellExecute = false, - RedirectStandardInput = true - }; - if (!String.IsNullOrEmpty (jdkDir) && !psi.EnvironmentVariables.ContainsKey ("JAVA_HOME")) - psi.EnvironmentVariables.Add ("JAVA_HOME", jdkDir); - - Log.DebugLine ($"Starting {psi.FileName} {psi.Arguments}"); - Process? proc = Process.Start (psi); - if (proc != null) { - for (int i = 0; i < 10; i++) - proc.StandardInput.WriteLine ('y'); - - proc.WaitForExit (); - } else { - Log.DebugLine ("Failed to start process"); - } - - return true; - } - bool GatherNDKInfo (Context context) { if (!CopyRedistributableFiles (context)) { diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_GenerateFiles.cs b/build-tools/xaprepare/xaprepare/Steps/Step_GenerateFiles.cs index 3198bc73d29..b7f75ef39f1 100644 --- a/build-tools/xaprepare/xaprepare/Steps/Step_GenerateFiles.cs +++ b/build-tools/xaprepare/xaprepare/Steps/Step_GenerateFiles.cs @@ -124,8 +124,6 @@ GeneratedFile GetCmakePresetsCommon (Context context, string sourcesDir) { "@NDK_ARM64_V8A_API_NET@", BuildAndroidPlatforms.NdkMinimumAPI }, { "@NDK_X86_API_NET@", BuildAndroidPlatforms.NdkMinimumAPILegacy32 }, { "@NDK_X86_64_API_NET@", BuildAndroidPlatforms.NdkMinimumAPI }, - { "@NDK_ARM64_V8A_NONMONO_API_NET@", BuildAndroidPlatforms.NdkMinimumNonMonoAPI }, - { "@NDK_X86_64_NONMONO_API_NET@", BuildAndroidPlatforms.NdkMinimumNonMonoAPI }, { "@XA_BUILD_CONFIGURATION@", context.Configuration }, { "@XA_TEST_OUTPUT_DIR@", Utilities.EscapePathSeparators (props.GetRequiredValue (KnownProperties.TestOutputDirectory)) }, }; @@ -197,14 +195,10 @@ GeneratedFile Get_XABuildConfig_cs (Context context) { "@NDK_ARM64_V8A_API@", BuildAndroidPlatforms.NdkMinimumAPI.ToString () }, { "@NDK_X86_API@", BuildAndroidPlatforms.NdkMinimumAPILegacy32.ToString ().ToString () }, { "@NDK_X86_64_API@", BuildAndroidPlatforms.NdkMinimumAPI.ToString ().ToString () }, - { "@NDK_ARM64_V8A_NONMONO_API@", BuildAndroidPlatforms.NdkMinimumNonMonoAPI }, - { "@NDK_X86_64_NONMONO_API@", BuildAndroidPlatforms.NdkMinimumNonMonoAPI }, { "@XA_SUPPORTED_ABIS@", context.Properties.GetRequiredValue (KnownProperties.AndroidSupportedTargetJitAbis).Replace (':', ';') }, { "@SDK_BUILD_TOOLS_VERSION@", context.Properties.GetRequiredValue (KnownProperties.XABuildToolsFolder) }, { "@ANDROID_DEFAULT_MINIMUM_DOTNET_API_LEVEL@", GetMajor (context.Properties.GetRequiredValue (KnownProperties.AndroidMinimumDotNetApiLevel)) }, { "@ANDROID_DEFAULT_MINIMUM_DOTNET_API_LEVEL_MINOR@", GetMinor (context.Properties.GetRequiredValue (KnownProperties.AndroidMinimumDotNetApiLevel)) }, - { "@ANDROID_DEFAULT_MINIMUM_NONMONO_API_LEVEL@", GetMajor (context.Properties.GetRequiredValue (KnownProperties.AndroidMinimumNonMonoApiLevel)) }, - { "@ANDROID_DEFAULT_MINIMUM_NONMONO_API_LEVEL_MINOR@", GetMinor (context.Properties.GetRequiredValue (KnownProperties.AndroidMinimumNonMonoApiLevel)) }, { "@ANDROID_DEFAULT_TARGET_DOTNET_API_LEVEL@", GetMajor (context.Properties.GetRequiredValue (KnownProperties.AndroidDefaultTargetDotnetApiLevel)) }, { "@ANDROID_DEFAULT_TARGET_DOTNET_API_LEVEL_MINOR@", GetMinor (context.Properties.GetRequiredValue (KnownProperties.AndroidDefaultTargetDotnetApiLevel)) }, { "@ANDROID_LATEST_STABLE_API_LEVEL@", GetMajor (context.Properties.GetRequiredValue (KnownProperties.AndroidLatestStableApiLevel)) }, @@ -256,8 +250,6 @@ GeneratedFile Get_Ndk_projitems (Context context) { "@NDK_X86_API_NET@", BuildAndroidPlatforms.NdkMinimumAPI.ToString () }, { "@NDK_X86_64_API@", BuildAndroidPlatforms.NdkMinimumAPI.ToString () }, { "@NDK_X86_64_API_NET@", BuildAndroidPlatforms.NdkMinimumAPI.ToString () }, - { "@NDK_ARM64_V8A_API_NON_MONO@", BuildAndroidPlatforms.NdkMinimumNonMonoAPI }, - { "@NDK_X86_64_API_NON_MONO@", BuildAndroidPlatforms.NdkMinimumNonMonoAPI }, }; return new GeneratedPlaceholdersFile ( diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_Get_Android_BuildTools.cs b/build-tools/xaprepare/xaprepare/Steps/Step_Get_Android_BuildTools.cs deleted file mode 100644 index 21078bcfd34..00000000000 --- a/build-tools/xaprepare/xaprepare/Steps/Step_Get_Android_BuildTools.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using System.Net; - -namespace Xamarin.Android.Prepare -{ - partial class Step_Get_Android_BuildTools : StepWithDownloadProgress - { - List<(string package, string prefix)> packages = new List<(string package, string prefix)>(); - - public Step_Get_Android_BuildTools () - : base ("Downloading build-tools archive") - { - string XABuildToolsVersion = Context.Instance.Properties [KnownProperties.XABuildToolsVersion] ?? String.Empty; - string XABuildToolsPackagePrefixMacOS = Context.Instance.Properties [KnownProperties.XABuildToolsPackagePrefixMacOS] ?? string.Empty; - string XABuildToolsPackagePrefixWindows = Context.Instance.Properties [KnownProperties.XABuildToolsPackagePrefixWindows] ?? string.Empty; - string XABuildToolsPackagePrefixLinux = Context.Instance.Properties [KnownProperties.XABuildToolsPackagePrefixLinux] ?? string.Empty; - - packages.Add ((package: $"build-tools_r{XABuildToolsVersion}_macosx.zip", prefix: XABuildToolsPackagePrefixMacOS)); - packages.Add ((package: $"build-tools_r{XABuildToolsVersion}_windows.zip", prefix: XABuildToolsPackagePrefixWindows)); - packages.Add ((package: $"build-tools_r{XABuildToolsVersion}_linux.zip", prefix: XABuildToolsPackagePrefixLinux)); - } - - protected override async Task Execute (Context context) - { - bool success = true; - foreach (var package in packages) { - success &= await DownloadBuildToolsPackage (context, package.prefix + package.package, package.package); - if (!success) { - Log.InfoLine ($"build-tools package '{package.package}' not present"); - return false; - } - } - - context.BuildToolsArchiveDownloaded = success; - return true; - } - - async Task DownloadBuildToolsPackage (Context context, string packageName, string localPackageName) - { - string localArchivePath = Path.Combine (Configurables.Paths.AndroidBuildToolsCacheDir, localPackageName); - Uri url = new Uri (AndroidToolchain.AndroidUri, packageName); - - if (Utilities.FileExists (localArchivePath)) { - Log.StatusLine ($"build-tools already downloaded ({localArchivePath})"); - return true; - } - - Log.StatusLine ($"Downloading {packageName} from ", url.ToString (), tailColor: ConsoleColor.White); - (bool success, ulong size, HttpStatusCode status) = await Utilities.GetDownloadSizeWithStatus (url); - if (!success) { - if (status == HttpStatusCode.NotFound) - Log.ErrorLine ("build-tools URL not found"); - else - Log.ErrorLine ("Failed to obtain build-tools size. HTTP status code: {status} ({(int)status})"); - return false; - } - DownloadStatus downloadStatus = Utilities.SetupDownloadStatus (context, size, context.InteractiveSession); - Log.StatusLine ($" {context.Characters.Link} {url}", ConsoleColor.White); - await Download (context, url, localArchivePath, "build-tools", Path.GetFileName (localArchivePath), downloadStatus); - - if (!File.Exists (localArchivePath)) { - Log.ErrorLine ($"Download of build-tools from {url} failed"); - return false; - } - - return true; - } - } -} diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_Get_Windows_Binutils.cs b/build-tools/xaprepare/xaprepare/Steps/Step_Get_Windows_Binutils.cs deleted file mode 100644 index a5457b27d46..00000000000 --- a/build-tools/xaprepare/xaprepare/Steps/Step_Get_Windows_Binutils.cs +++ /dev/null @@ -1,712 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading.Tasks; - -using Force.Crc32; - -namespace Xamarin.Android.Prepare -{ - class Step_Get_Windows_Binutils : Step - { - const int EndFileChunkSize = 65535 + 22; // Maximum comment size + EOCD size - const uint EOCDSignature = 0x06054b50; - const uint CDHeaderSignature = 0x02014b50; - const uint LFHeaderSignature = 0x04034b50; - - class EOCD - { - public uint Signature; // Signature (0x06054b50) - public ushort DiskNumber; // number of this disk - public ushort CDStartDisk; // number of the disk with the start of the central directory - public ushort TotalEntriesThisDisk; // total number of entries in the central directory on this disk - public ushort TotalEntries; // total number of entries in the central directory - public uint CDSize; // size of the central directory - public uint CDOffset; // offset of start of central directory with respect to the starting disk number - public ushort CommentLength; // .ZIP file comment length - }; -#nullable disable - class CDHeader - { - public uint Signature; // 0x02014b50 - public ushort VersionMadeBy; - public ushort VersionNeededToExtract; - public ushort GeneralPurposeBitFlag; - public ushort CompressionMethod; - public ushort LastModFileTime; - public ushort LastModFileDate; - public uint CRC32; - public uint CompressedSize; - public uint UncompressedSize; - public ushort FileNameLength; - public ushort ExtraFieldLength; - public ushort FileCommentLength; - public ushort DiskNumberStart; - public ushort InternalFileAttributes; - public uint ExternalFileAttributes; - public uint RelativeOffsetOfLocalHeader; - public string FileName; - public byte[] ExtraField; - public string FileComment; - }; - - class LFHeader - { - public uint Signature; // 0x04034b50 - public ushort VersionNeededToExtract; - public ushort GeneralPurposeBitFlag; - public ushort CompressionMethod; - public ushort LastModFileTime; - public ushort LastModFileDate; - public uint CRC32; - public uint CompressedSize; - public uint UncompressedSize; - public ushort FileNameLength; - public ushort ExtraFieldLength; - public string FileName; - public byte[] ExtraField; - }; -#nullable enable - - public Step_Get_Windows_Binutils () - : base ("Downloading NDK tools for Windows") - {} - - protected override async Task Execute (Context context) - { - string ndkVersion = BuildAndroidPlatforms.AndroidNdkVersion; - string baseArchivePath = $"android-ndk-r{ndkVersion}/toolchains/llvm/prebuilt/windows-x86_64/bin"; - - var neededFiles = new Dictionary (StringComparer.OrdinalIgnoreCase) { - { $"{baseArchivePath}/libwinpthread-1.dll", String.Empty }, - }; - - foreach (var kvp in Configurables.Defaults.AndroidToolchainPrefixes) { - string archPrefix = kvp.Value; - foreach (NDKTool ndkTool in Configurables.Defaults.NDKTools) { - string sourcePath = $"{baseArchivePath}/{archPrefix}-{ndkTool.Name}.exe"; - string destPath; - - if (ndkTool.DestinationName.Length == 0) { - destPath = String.Empty; - } else { - destPath = $"{baseArchivePath}/{archPrefix}-{ndkTool.DestinationName}.exe"; - } - - neededFiles [sourcePath] = destPath; - } - } - - string destinationDirectory = Configurables.Paths.WindowsBinutilsInstallDir; - int existingFiles = 0; - foreach (var kvp in neededFiles) { - string f = GetDestinationFile (kvp); - string file = Path.Combine (destinationDirectory, Path.GetFileName (f)); - string stampFile = GetStampFile (f, destinationDirectory, ndkVersion); - if (File.Exists (file)) { - Log.DebugLine ($"{file} exists"); - if (File.Exists (stampFile)) { - existingFiles++; - } else { - Log.DebugLine ($"Stamp file {stampFile} does not exist, will need to download the executable again"); - } - } - } - - if (existingFiles == neededFiles.Count) { - Log.StatusLine ("All Windows binutils binaries already downloaded."); - return true; - } - - bool result = await FetchFiles ( - neededFiles, - destinationDirectory, - new Uri (AndroidToolchain.AndroidUri, $"android-ndk-r{ndkVersion}-windows-x86_64.zip") - ); - - if (!result) - return false; - - StampFiles (neededFiles, destinationDirectory, ndkVersion); - return true; - } - - string GetDestinationFile (KeyValuePair kvp) - { - return kvp.Value.Length == 0 ? kvp.Key : kvp.Value; - } - - void StampFiles (Dictionary neededFiles, string destinationDirectory, string ndkVersion) - { - var now = DateTime.UtcNow; - foreach (var kvp in neededFiles) { - string file = GetDestinationFile (kvp); - File.WriteAllText (GetStampFile (file, destinationDirectory, ndkVersion), now.ToString ()); - } - } - - string GetStampFile (string file, string destinationDirectory, string ndkVersion) - { - return Path.Combine (destinationDirectory, $"{Path.GetFileName (file)}.{ndkVersion}"); - } - - async Task FetchFiles (Dictionary neededFiles, string destinationDirectory, Uri url) - { - Utilities.CreateDirectory (destinationDirectory); - using (HttpClient httpClient = Utilities.CreateHttpClient ()) { - bool success; - long size; - - Log.StatusLine ($"Accessing {url}"); - (success, size) = await GetFileSize (httpClient, url); - if (!success) - return false; - - Log.DebugLine ($" File size: {size}"); - - EOCD? eocd; - (success, eocd) = await GetEOCD (httpClient, url, size); - if (!success || eocd == null) { - Log.ErrorLine ("Failed to find the End of Central Directory record"); - return false; - } - - if (eocd.DiskNumber != 0) - throw new InvalidOperationException ("Multi-disk ZIP archives not supported"); - - Log.DebugLine ($" Central Directory offset: {eocd.CDOffset} (0x{eocd.CDOffset:x})"); - Log.DebugLine ($" Central Directory size: {eocd.CDSize} (0x{eocd.CDSize})"); - Log.DebugLine ($" Total Entries: {eocd.TotalEntries}"); - - Stream? cd; - (success, cd) = await ReadCD (httpClient, url, eocd.CDOffset, eocd.CDSize, size); - if (!success || cd == null) { - Log.ErrorLine ("Failed to read the Central Directory"); - return false; - } - - Log.StatusLine ("Files:"); - if (!await ProcessEntries (httpClient, url, eocd, cd, neededFiles, destinationDirectory)) - return false; - } - - return true; - } - - async Task ProcessEntries (HttpClient httpClient, Uri url, EOCD eocd, Stream centralDirectory, Dictionary neededFiles, string destinationDirectory) - { - long foundEntries = 0; - var foundFiles = new HashSet (StringComparer.OrdinalIgnoreCase); - - using (var br = new BinaryReader (centralDirectory)) { - long nread = 0; - long nentries = 1; - - while (nread < centralDirectory.Length && nentries <= eocd.TotalEntries) { - (bool success, CDHeader? cdh) = ReadCDHeader (br, centralDirectory.Length, ref nread); - nentries++; - if (!success || cdh == null) { - Log.ErrorLine ($"Failed to read a Central Directory file header for entry {nentries}"); - return false; - } - - if (!neededFiles.TryGetValue (cdh.FileName, out string? destinationFileName)) - continue; - - foundFiles.Add (cdh.FileName); - - if (!await ReadEntry (httpClient, url, cdh, br, destinationDirectory, destinationFileName)) - return false; - foundEntries++; - } - } - - if (foundEntries < neededFiles.Count) { - Log.ErrorLine (); - Log.ErrorLine ($"Could not find all required binaries. Found {foundEntries} out of {neededFiles.Count}, the missing files are:"); - foreach (string file in neededFiles.Keys) { - if (foundFiles.Contains (file)) { - continue; - } - Log.StatusLine ($" {Context.Instance.Characters.Bullet} {file} "); - } - - return false; - } - - return true; - } - - async Task ReadEntry (HttpClient httpClient, Uri url, CDHeader cdh, BinaryReader br, string destinationDirectory, string destinationFileName) - { - Context context = Context.Instance; - string destFileName = Path.GetFileName (destinationFileName.Length == 0 ? cdh.FileName : destinationFileName); - string destFilePath = Path.Combine (destinationDirectory, destFileName); - string compressedFilePath = Path.Combine (destinationDirectory, $"{destFilePath}.deflated"); - Log.Status ($" {context.Characters.Bullet} {Path.GetFileName (cdh.FileName)} "); - Log.Status ($"{context.Characters.RightArrow}", ConsoleColor.Cyan); - Log.StatusLine ($" {Utilities.GetRelativePath (BuildPaths.XamarinAndroidSourceRoot, destFilePath)}"); - Log.DebugLine ($" {cdh.FileName} (offset: {cdh.RelativeOffsetOfLocalHeader})"); - - (bool success, Stream? contentStream) = await ReadFileData (httpClient, url, cdh); - if (!success || contentStream == null) { - Log.ErrorLine ("Failed to read file data"); - return false; - } - - using (var destFile = new BinaryWriter (File.OpenWrite (compressedFilePath))) { - using (var fbr = new BinaryReader (contentStream)) { - if (!await DownloadAndExtract (fbr, contentStream, destFile, compressedFilePath)) - return CleanupAndReturn (false); - } - } - - return CleanupAndReturn (true); - - bool CleanupAndReturn (bool retval) - { - if (File.Exists (compressedFilePath)) - File.Delete (compressedFilePath); - - return retval; - } - } - - async Task DownloadAndExtract (BinaryReader fbr, Stream contentStream, BinaryWriter destFile, string destFileName) - { - long fread = 0; - (bool success, LFHeader? lfh) = ReadLFHeader (fbr, contentStream.Length, ref fread); - if (!success || lfh == null) { - Log.ErrorLine ("Failed to read local file header"); - return false; - } - - uint dread = 0; - var buffer = new byte [8192]; - while (fread <= contentStream.Length && dread < lfh.CompressedSize) { - uint toRead; - if (lfh.CompressedSize - dread < buffer.Length) - toRead = lfh.CompressedSize - dread; - else - toRead = (uint)buffer.Length; - - int bread = await contentStream.ReadAsync (buffer, 0, (int)toRead); - if (bread == 0) - break; - destFile.Write (buffer, 0, bread); - fread += bread; - dread += (uint)bread; - } - - destFile.Flush (); - destFile.Close (); - destFile.Dispose (); - Extract (destFileName, lfh.CRC32); - - if (dread != lfh.CompressedSize) - Log.ErrorLine ($" Invalid data size: expected {lfh.CompressedSize} bytes, read {dread} bytes"); - - return true; - } - - void Extract (string compressedFilePath, uint crc32FromHeader) - { - if (String.IsNullOrEmpty (compressedFilePath)) { - throw new ArgumentException ("must not be null or empty", nameof (compressedFilePath)); - } - - string outputFile = Path.Combine (Path.GetDirectoryName (compressedFilePath) ?? String.Empty, Path.GetFileNameWithoutExtension (compressedFilePath) ?? String.Empty); - using (var fs = File.OpenRead (compressedFilePath)) { - using (var dfs = File.OpenWrite (outputFile)) { - Extract (fs, dfs, crc32FromHeader); - } - } - } - - void Extract (Stream src, Stream dest, uint crc32FromHeader) - { - uint fileCRC = 0; - int fread = 0; - var crc32 = new CRC32 (); - var buffer = new byte [8192]; - - using (var iis = new DeflateStream (src, CompressionMode.Decompress)) { - while (true) { - fread = iis.Read (buffer, 0, buffer.Length); - if (fread <= 0) - break; - - fileCRC = crc32.Append (fileCRC, buffer, 0, fread); - dest.Write (buffer, 0, fread); - } - dest.Flush (); - } - - if (fileCRC != crc32FromHeader) - Log.ErrorLine ($" Invalid CRC32: expected 0x{crc32FromHeader:x}, got 0x{fileCRC:x}"); - } - - async Task<(bool success, Stream? data)> ReadFileData (HttpClient httpClient, Uri url, CDHeader cdh) - { - long fileOffset = cdh.RelativeOffsetOfLocalHeader; - long dataSize = - cdh.CompressedSize + - 30 + // local file header size, the static portion - cdh.FileName.Length + // They're the same in both haders - cdh.ExtraFieldLength + // This may differ between headers... - 16384; // ...so we add some extra padding - - var req = new HttpRequestMessage (HttpMethod.Get, url); - req.Headers.ConnectionClose = true; - req.Headers.Range = new RangeHeaderValue (fileOffset, fileOffset + dataSize); - - HttpResponseMessage resp = await httpClient.SendAsync (req).ConfigureAwait (false); - - if (!resp.IsSuccessStatusCode) { - Log.ErrorLine ($"Failed to read file data: HTTP error {resp.StatusCode}"); - return (false, null); - } - - Stream s = await resp.Content.ReadAsStreamAsync ().ConfigureAwait (false); - if (s.Length < dataSize) { - Log.ErrorLine ($"Failed to read file data: invalid data length ({s.Length} < {dataSize})"); - s.Dispose (); - return (false, null); - } - - return (true, s); - } - - (bool success, LFHeader? lfh) ReadLFHeader (BinaryReader cdr, long dataLength, ref long nread) - { - var lfh = new LFHeader (); - - bool worked; - lfh.Signature = ReadUInt (cdr, dataLength, ref nread, out worked); - if (!worked || lfh.Signature != LFHeaderSignature) { - Log.ErrorLine ($"Invalid signature ({lfh.Signature:x} != {LFHeaderSignature:x})"); - goto failed; - } - - lfh.VersionNeededToExtract = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - lfh.GeneralPurposeBitFlag = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - lfh.CompressionMethod = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - lfh.LastModFileTime = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - lfh.LastModFileDate = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - lfh.CRC32 = ReadUInt (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - lfh.CompressedSize = ReadUInt (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - lfh.UncompressedSize = ReadUInt (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - lfh.FileNameLength = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - lfh.ExtraFieldLength = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - byte[]? bytes = ReadBytes (cdr, lfh.FileNameLength, dataLength, ref nread, out worked); - if (!worked || bytes == null) { - goto failed; - } - - lfh.FileName = Encoding.ASCII.GetString (bytes); - if (!worked) { - goto failed; - } - - if (lfh.ExtraFieldLength > 0) { - lfh.ExtraField = ReadBytes (cdr, lfh.ExtraFieldLength, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - } - - return (true, lfh); - - failed: - return (false, null); - } - - (bool success, CDHeader? cdh) ReadCDHeader (BinaryReader cdr, long dataLength, ref long nread) - { - var cdh = new CDHeader (); - - bool worked; - cdh.Signature = ReadUInt (cdr, dataLength, ref nread, out worked); - if (!worked || cdh.Signature != CDHeaderSignature) { - Log.ErrorLine ($"Invalid signature ({cdh.Signature:x} != {CDHeaderSignature:x})"); - goto failed; - } - - cdh.VersionMadeBy = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - cdh.VersionNeededToExtract = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - cdh.GeneralPurposeBitFlag = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - cdh.CompressionMethod = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - cdh.LastModFileTime = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - cdh.LastModFileDate = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - cdh.CRC32 = ReadUInt (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - cdh.CompressedSize = ReadUInt (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - cdh.UncompressedSize = ReadUInt (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - cdh.FileNameLength = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - cdh.ExtraFieldLength = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - cdh.FileCommentLength = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - cdh.DiskNumberStart = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - cdh.InternalFileAttributes = ReadUShort (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - cdh.ExternalFileAttributes = ReadUInt (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - cdh.RelativeOffsetOfLocalHeader = ReadUInt (cdr, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - - byte[]? bytes = ReadBytes (cdr, cdh.FileNameLength, dataLength, ref nread, out worked); - if (!worked || bytes == null) { - goto failed; - } - - cdh.FileName = Encoding.ASCII.GetString (bytes); - if (!worked) { - goto failed; - } - - if (cdh.ExtraFieldLength > 0) { - cdh.ExtraField = ReadBytes (cdr, cdh.ExtraFieldLength, dataLength, ref nread, out worked); - if (!worked) { - goto failed; - } - } - - if (cdh.FileCommentLength > 0) { - bytes = ReadBytes (cdr, cdh.FileCommentLength, dataLength, ref nread, out worked); - if (!worked || bytes == null) { - goto failed; - } - cdh.FileComment = Encoding.ASCII.GetString (bytes); - } - - return (true, cdh); - - failed: - return (false, null); - } - - ushort ReadUShort (BinaryReader br, long dataLength, ref long nread, out bool success) - { - success = false; - if (dataLength - nread < 2) - return 0; - - ushort ret = br.ReadUInt16 (); - nread += 2; - - success = true; - return ret; - } - - uint ReadUInt (BinaryReader br, long dataLength, ref long nread, out bool success) - { - success = false; - if (dataLength - nread < 4) - return 0; - - uint ret = br.ReadUInt32 (); - nread += 4; - - success = true; - return ret; - } - - byte[]? ReadBytes (BinaryReader br, int neededBytes, long dataLength, ref long nread, out bool success) - { - success = false; - if (dataLength - nread < neededBytes) - return null; - - byte[] ret = br.ReadBytes (neededBytes); - nread += neededBytes; - - success = true; - return ret; - } - - async Task<(bool success, Stream? cd)> ReadCD (HttpClient httpClient, Uri url, uint cdOffset, uint cdSize, long fileSize) - { - long fileOffset = cdOffset; - var req = new HttpRequestMessage (HttpMethod.Get, url); - req.Headers.ConnectionClose = true; - req.Headers.Range = new RangeHeaderValue (fileOffset, fileOffset + cdSize); - - HttpResponseMessage resp = await httpClient.SendAsync (req).ConfigureAwait (false); - if (!resp.IsSuccessStatusCode) { - Log.ErrorLine ($"Failed to read Central Directory: HTTP error {resp.StatusCode}"); - return (false, null); - } - - Stream s = await resp.Content.ReadAsStreamAsync ().ConfigureAwait (false); - if (s.Length < cdSize) { - Log.ErrorLine ($"Failed to read Central Directory: invalid data length ({s.Length} < {cdSize})"); - s.Dispose (); - return (false, null); - } - - return (true, s); - } - - async Task<(bool success, EOCD? eocd)> GetEOCD (HttpClient httpClient, Uri url, long fileSize) - { - long fileOffset = fileSize - EndFileChunkSize; - var req = new HttpRequestMessage (HttpMethod.Get, url); - req.Headers.ConnectionClose = true; - req.Headers.Range = new RangeHeaderValue (fileOffset, fileSize); - - HttpResponseMessage resp = await httpClient.SendAsync (req).ConfigureAwait (false); - if (!resp.IsSuccessStatusCode) - return (false, null); - - using (var eocdStream = await resp.Content.ReadAsStreamAsync ().ConfigureAwait (false)) { - using (var sr = new BinaryReader (eocdStream)) { - byte[] expected = {0x50, 0x4b, 0x05, 0x06}; - int expectedPos = 0; - - for (int i = 0; i < eocdStream.Length; i++) { - byte b = sr.ReadByte (); - if (b != expected [expectedPos]) { - expectedPos = 0; - continue; - } - - if (expectedPos == expected.Length - 1) { - // We've found the signature - var eocd = new EOCD (); - eocd.Signature = 0x06054b50; - eocd.DiskNumber = sr.ReadUInt16 (); - eocd.CDStartDisk = sr.ReadUInt16 (); - eocd.TotalEntriesThisDisk = sr.ReadUInt16 (); - eocd.TotalEntries = sr.ReadUInt16 (); - eocd.CDSize = sr.ReadUInt32 (); - eocd.CDOffset = sr.ReadUInt32 (); - eocd.CommentLength = sr.ReadUInt16 (); - - return (true, eocd); - } - - expectedPos++; - if (expectedPos >= expected.Length) - expectedPos = 0; - } - } - } - - return (false, null); - } - - async Task<(bool success, long size)> GetFileSize (HttpClient httpClient, Uri url) - { - var req = new HttpRequestMessage (HttpMethod.Head, url); - req.Headers.ConnectionClose = true; - - HttpResponseMessage resp = await httpClient.SendAsync (req).ConfigureAwait (false); - if (!resp.IsSuccessStatusCode || !resp.Content.Headers.ContentLength.HasValue) - return (false, 0); - - return (true, resp.Content.Headers.ContentLength.Value); - } - } -} diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.Linux.cs b/build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.Linux.cs deleted file mode 100644 index dd174847e31..00000000000 --- a/build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.Linux.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; - -namespace Xamarin.Android.Prepare -{ - partial class Step_InstallOpenJDK - { - void MoveContents (string sourceDir, string destinationDir) - { - Utilities.MoveDirectoryContentsRecursively (sourceDir, destinationDir); - } - } -} diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.MacOS.cs b/build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.MacOS.cs deleted file mode 100644 index 8e7e38d1635..00000000000 --- a/build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.MacOS.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; - -namespace Xamarin.Android.Prepare -{ - partial class Step_InstallOpenJDK - { - void MoveContents (string sourceDir, string destinationDir) - { - string realSourceDir = Path.Combine (sourceDir, "Contents", "Home"); - Utilities.MoveDirectoryContentsRecursively (realSourceDir, destinationDir); - - var xattr_d = new ProcessRunner ("xattr", "-d", "-r", "com.apple.quarantine", ".") { - EchoStandardError = true, - WorkingDirectory = destinationDir, - }; - xattr_d.Run (); - } - } -} diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.Unix.cs b/build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.Unix.cs deleted file mode 100644 index 24b5768a07a..00000000000 --- a/build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.Unix.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; - -namespace Xamarin.Android.Prepare -{ - partial class Step_InstallOpenJDK - { - async Task Unpack (string fullArchivePath, string destinationDirectory, bool cleanDestinationBeforeUnpacking = false) - { - return await Utilities.Unpack (fullArchivePath, destinationDirectory, cleanDestinatioBeforeUnpacking: true); - } - } -} diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.Windows.cs b/build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.Windows.cs deleted file mode 100644 index 3f92f5614da..00000000000 --- a/build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.Windows.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; - -namespace Xamarin.Android.Prepare -{ - partial class Step_InstallOpenJDK - { - async Task Unpack (string fullArchivePath, string destinationDirectory, bool cleanDestinationBeforeUnpacking = false) - { - if (cleanDestinationBeforeUnpacking) - Utilities.DeleteDirectorySilent (destinationDirectory); - Utilities.CreateDirectory (destinationDirectory); - - var sevenZip = new SevenZipRunner (Context.Instance); - Log.DebugLine ($"Uncompressing {fullArchivePath} to {destinationDirectory}"); - if (!await sevenZip.Extract (fullArchivePath, destinationDirectory)) { - Log.DebugLine ($"Failed to decompress {fullArchivePath}"); - return false; - } - - if (fullArchivePath.EndsWith ("tar.gz", StringComparison.OrdinalIgnoreCase)) { - // On Windows we don't have Tar available and the Windows package is a .tar.gz - // 7zip can unpack tar.gz but it's a two-stage process - first it decompresses the package, then it can be - // invoked again to extract the actual tar contents. - string tarPath = Path.Combine (destinationDirectory, Path.GetFileNameWithoutExtension (fullArchivePath)); - bool ret = await sevenZip.Extract (tarPath, destinationDirectory); - Utilities.DeleteFileSilent (tarPath); - - if (!ret) { - Log.DebugLine ($"Failed to extract TAR contents from {tarPath}"); - return false; - } - } - - return true; - } - - void MoveContents (string sourceDir, string destinationDir) - { - Utilities.MoveDirectoryContentsRecursively (sourceDir, destinationDir); - } - } -} diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.cs b/build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.cs deleted file mode 100644 index 92cacaa7c1f..00000000000 --- a/build-tools/xaprepare/xaprepare/Steps/Step_InstallAdoptOpenJDK.cs +++ /dev/null @@ -1,296 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Threading.Tasks; -using System.Xml.Linq; - -namespace Xamarin.Android.Prepare -{ - abstract partial class Step_InstallOpenJDK : StepWithDownloadProgress, IBuildInventoryItem - { - const string XAVersionInfoFile = "xa_jdk_version.txt"; - const string URLQueryFilePathField = "file_path"; - - static readonly char[] QuerySeparator = new char[] { ';', '&' }; - - // Paths relative to JDK installation root, just for a cursory check whether we have a sane JDK instance - // NOTE: file extensions are not necessary here - static readonly List jdkFiles = new List { - Path.Combine ("bin", "java"), - Path.Combine ("bin", "javac"), - Path.Combine ("include", "jni.h"), - }; - - bool AllowJIJavaHomeMatch = false; - - public Step_InstallOpenJDK (string description, bool allowJIJavaHomeMatch = false) - : base (description) - { - AllowJIJavaHomeMatch = allowJIJavaHomeMatch; - } - - protected abstract string ProductName {get;} - protected abstract string JdkInstallDir {get;} - protected abstract Version JdkVersion {get;} - protected abstract Version JdkRelease {get;} - protected abstract Uri JdkUrl {get;} - protected abstract string JdkCacheDir {get;} - protected abstract string RootDirName {get;} - public string BuildToolName => ProductName; - public string BuildToolVersion => JdkVersion.ToString (); - - protected override async Task Execute (Context context) - { - AddToInventory (); - - string jdkInstallDir = JdkInstallDir; - if (OpenJDKExistsAndIsValid (jdkInstallDir, out string? installedVersion)) { - Log.Status ($"{ProductName} version "); - Log.Status (installedVersion ?? "Unknown", ConsoleColor.Yellow); - Log.StatusLine (" already installed in: ", jdkInstallDir, tailColor: ConsoleColor.Cyan); - return true; - } - - // Check for a JDK installed on CI to use for test jobs - var jiJavaHomeVarValue = Environment.GetEnvironmentVariable ("JI_JAVA_HOME"); - if (AllowJIJavaHomeMatch && Directory.Exists (jiJavaHomeVarValue) && JdkFilesExist (jiJavaHomeVarValue)) { - Log.StatusLine ("Skipping JDK install for test job, JDK exists at: ", jdkInstallDir, tailColor: ConsoleColor.Cyan); - return true; - } - - Log.StatusLine ($"{ProductName} {JdkVersion} r{JdkRelease} will be installed to {jdkInstallDir}"); - Uri jdkURL = JdkUrl; - if (jdkURL == null) - throw new InvalidOperationException ($"{ProductName} URL must not be null"); - - string? packageName = GetPackageName (jdkURL); - - if (String.IsNullOrEmpty (packageName)) { - Log.ErrorLine ($"Unable to extract file name from {ProductName} URL"); - return false; - } - - string localPackagePath = Path.Combine (JdkCacheDir, packageName); - if (!await DownloadOpenJDK (context, localPackagePath, jdkURL)) - return false; - - string tempDir = $"{jdkInstallDir}.temp"; - try { - if (!await Unpack (localPackagePath, tempDir, cleanDestinationBeforeUnpacking: true)) { - Log.ErrorLine ($"Failed to install {ProductName}"); - return false; - } - - string rootDir = Path.Combine (tempDir, RootDirName); - if (!Directory.Exists (rootDir)) { - Log.ErrorLine ($"${ProductName} root directory not found after unpacking: {RootDirName}"); - return false; - } - - MoveContents (rootDir, jdkInstallDir); - File.WriteAllText (Path.Combine (jdkInstallDir, XAVersionInfoFile), $"{JdkRelease}{Environment.NewLine}"); - } finally { - Utilities.DeleteDirectorySilent (tempDir); - // Clean up zip after extraction if running on a hosted azure pipelines agent. - if (context.IsRunningOnHostedAzureAgent) - Utilities.DeleteFileSilent (localPackagePath); - } - - return true; - } - - string? GetPackageName (Uri jdkURL) - { - string[] queryParams = jdkURL.Query.TrimStart ('?').Split (QuerySeparator, StringSplitOptions.RemoveEmptyEntries); - if (queryParams.Length == 0) { - if (jdkURL.Segments.Length > 0) { - return jdkURL.Segments [jdkURL.Segments.Length-1]; - } - Log.ErrorLine ($"Unable to extract file name from {ProductName} URL as it contains no query component"); - return null; - } - - string? packageName = null; - foreach (string p in queryParams) { - if (!p.StartsWith (URLQueryFilePathField, StringComparison.Ordinal)) { - continue; - } - - int idx = p.IndexOf ('='); - if (idx < 0) { - Log.DebugLine ($"{ProductName} URL query field '{URLQueryFilePathField}' has no value, unable to detect file name"); - break; - } - - packageName = p.Substring (idx + 1).Trim (); - } - return packageName; - } - - async Task DownloadOpenJDK (Context context, string localPackagePath, Uri url) - { - if (File.Exists (localPackagePath)) { - Log.StatusLine ($"{ProductName} archive already downloaded"); - return true; - } - - Log.StatusLine ($"Downloading {ProductName} from ", url.ToString (), tailColor: ConsoleColor.White); - (bool success, ulong size, HttpStatusCode status) = await Utilities.GetDownloadSizeWithStatus (url); - if (!success) { - if (status == HttpStatusCode.NotFound) - Log.ErrorLine ($"{ProductName} archive URL not found"); - else - Log.ErrorLine ($"Failed to obtain {ProductName} size. HTTP status code: {status} ({(int)status})"); - return false; - } - - DownloadStatus downloadStatus = Utilities.SetupDownloadStatus (context, size, context.InteractiveSession); - Log.StatusLine ($" {context.Characters.Link} {url}", ConsoleColor.White); - await Download (context, url, localPackagePath, ProductName, Path.GetFileName (localPackagePath), downloadStatus); - - if (!File.Exists (localPackagePath)) { - Log.ErrorLine ($"Download of {ProductName} from {url} failed."); - return false; - } - - return true; - } - - bool OpenJDKExistsAndIsValid (string installDir, out string? installedVersion) - { - installedVersion = null; - if (!Directory.Exists (installDir)) { - Log.DebugLine ($"{ProductName} directory {installDir} does not exist"); - return false; - } - - string corettoVersionFile = Path.Combine (installDir, "version.txt"); - if (File.Exists (corettoVersionFile)) { - Log.DebugLine ($"Corretto version file {corettoVersionFile} found, will replace Corretto with {ProductName}"); - return false; - } - - string openJDKReleaseFile = Path.Combine (installDir, "release"); - if (!File.Exists (openJDKReleaseFile)) { - Log.DebugLine ($"{ProductName} release file {openJDKReleaseFile} does not exist, cannot determine version"); - return false; - } - - string[] lines = File.ReadAllLines (openJDKReleaseFile); - if (lines == null || lines.Length == 0) { - Log.DebugLine ($"{ProductName} release file {openJDKReleaseFile} is empty, cannot determine version"); - return false; - } - - string? cv = null; - foreach (string l in lines) { - string line = l.Trim (); - if (!line.StartsWith ("JAVA_VERSION=", StringComparison.Ordinal)) { - continue; - } - - cv = line.Substring (line.IndexOf ('=') + 1).Trim ('"'); - cv = cv.Replace ("_", "."); - break; - } - - if (String.IsNullOrEmpty (cv)) { - Log.DebugLine ($"Unable to find version of {ProductName} in release file {openJDKReleaseFile}"); - return false; - } - - installedVersion = cv; - string xaVersionFile = Path.Combine (installDir, XAVersionInfoFile); - if (!File.Exists (xaVersionFile)) { - Log.DebugLine ($"Unable to find .NET for Android version file {xaVersionFile}"); - return false; - } - - lines = File.ReadAllLines (xaVersionFile); - if (lines == null || lines.Length == 0) { - Log.DebugLine ($".NET for Android version file {xaVersionFile} is empty, cannot determine release version"); - return false; - } - - string rv = lines[0].Trim (); - if (String.IsNullOrEmpty (rv)) { - Log.DebugLine ($".NET for Android version file {xaVersionFile} does not contain release version information"); - return false; - } - - if (!Version.TryParse (cv, out Version? cversion) || cversion == null) { - Log.DebugLine ($"Unable to parse {ProductName} version from: {cv}"); - return false; - } - - if (cversion != JdkVersion) { - Log.DebugLine ($"Invalid {ProductName} version. Need {JdkVersion}, found {cversion}"); - return false; - } - - if (!Version.TryParse (rv, out cversion)) { - Log.DebugLine ($"Unable to parse {ProductName} release version from: {rv}"); - return false; - } - - if (cversion != JdkRelease) { - Log.DebugLine ($"Invalid {ProductName} version. Need {JdkRelease}, found {cversion}"); - return false; - } - - return JdkFilesExist (installDir); - } - - bool JdkFilesExist (string installDir) - { - foreach (string f in jdkFiles) { - string file = Path.Combine (installDir, f); - if (!File.Exists (file)) { - bool foundExe = false; - foreach (string exe in Utilities.FindExecutable (f)) { - file = Path.Combine (installDir, exe); - if (File.Exists (file)) { - foundExe = true; - break; - } - } - - if (!foundExe) { - Log.DebugLine ($"JDK file {file} missing from {ProductName}"); - return false; - } - } - } - - return true; - } - - public void AddToInventory () - { - if (!string.IsNullOrEmpty (BuildToolName) && !string.IsNullOrEmpty (BuildToolVersion) && !Context.Instance.BuildToolsInventory.ContainsKey (BuildToolName)) { - Context.Instance.BuildToolsInventory.Add (BuildToolName, BuildToolVersion); - } - } - } - - class Step_InstallMicrosoftOpenJDK : Step_InstallOpenJDK { - - const string _ProductName = "Microsoft OpenJDK"; - - public Step_InstallMicrosoftOpenJDK (bool allowJIJavaHomeMatch = false) - : base ($"Installing {_ProductName}", allowJIJavaHomeMatch) - { - } - - protected override string ProductName => _ProductName; - protected override string JdkInstallDir => Configurables.Paths.OpenJDKInstallDir; - protected override Version JdkVersion => Configurables.Defaults.MicrosoftOpenJDKVersion; - protected override Version JdkRelease => Configurables.Defaults.MicrosoftOpenJDKRelease; - protected override Uri JdkUrl => Configurables.Urls.MicrosoftOpenJDK; - protected override string JdkCacheDir => Configurables.Paths.OpenJDKCacheDir; - protected override string RootDirName => Configurables.Defaults.MicrosoftOpenJDKRootDirName; - } -} diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.cs b/build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.cs index cface28e4f6..7d53a9b5330 100644 --- a/build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.cs +++ b/build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Net; using System.Threading.Tasks; namespace Xamarin.Android.Prepare @@ -136,40 +134,7 @@ async Task DownloadDotNetInstallScript (Context context, string dotnetScri } } - async Task DownloadDotNetArchive (Context context, string archiveDestinationPath, Uri archiveUrl) - { - Log.StatusLine ("Downloading dotnet archive..."); - - (bool success, ulong size, HttpStatusCode status) = await Utilities.GetDownloadSizeWithStatus (archiveUrl); - if (!success) { - if (status == HttpStatusCode.NotFound) { - Log.InfoLine ($"dotnet archive URL {archiveUrl} not found"); - return false; - } else { - Log.WarningLine ($"Failed to obtain dotnet archive size. HTTP status code: {status} ({(int)status})"); - } - - return false; - } - - string tempArchiveDestinationPath = archiveDestinationPath + "-tmp"; - Utilities.DeleteFile (tempArchiveDestinationPath); - - DownloadStatus downloadStatus = Utilities.SetupDownloadStatus (context, size, context.InteractiveSession); - Log.StatusLine ($" {context.Characters.Link} {archiveUrl}", ConsoleColor.White); - await Download (context, archiveUrl, tempArchiveDestinationPath, "dotnet archive", Path.GetFileName (archiveUrl.LocalPath), downloadStatus); - - if (!File.Exists (tempArchiveDestinationPath)) { - return false; - } - - Utilities.CopyFile (tempArchiveDestinationPath, archiveDestinationPath); - Utilities.DeleteFile (tempArchiveDestinationPath); - - return true; - } - - string[] GetInstallationScriptArgs (string version, string dotnetPath, string dotnetScriptPath, bool onlyGetUrls, bool runtimeOnly) + string[] GetInstallationScriptArgs (string version, string dotnetPath, string dotnetScriptPath, bool runtimeOnly) { List args; if (Context.IsWindows) { @@ -180,10 +145,6 @@ string[] GetInstallationScriptArgs (string version, string dotnetPath, string do if (runtimeOnly) { args.AddRange (new string [] { "-Runtime", "dotnet" }); } - if (onlyGetUrls) { - args.Add ("-DryRun"); - } - return args.ToArray (); } @@ -192,12 +153,8 @@ string[] GetInstallationScriptArgs (string version, string dotnetPath, string do }; if (runtimeOnly) { - args.AddRange (new string [] { "-Runtime", "dotnet" }); - } - if (onlyGetUrls) { - args.Add ("--dry-run"); + args.AddRange (new string [] { "--runtime", "dotnet" }); } - return args.ToArray (); } @@ -216,13 +173,18 @@ async Task InstallDotNetFromLocalArchiveAsync (Context context, string dot return await Utilities.Unpack (archivePath, dotnetPath); } + // Standardize on dotnet/arcade's usage of the dotnet-install scripts: + // invoke dotnet-install.{sh,ps1} directly with --version/--install-dir + // rather than harvesting URLs and downloading/extracting the archive + // ourselves. This matches Arcade's eng/common/tools.sh and our CI + // (build-tools/automation/yaml-templates/use-dot-net.yaml), lets the + // script perform its built-in SHA-512 verification of the archive, and + // relies on the script's own "already installed" check for incremental + // re-runs (no need to re-download when the requested version is present). async Task InstallDotNetAsync (Context context, string dotnetPath, string version, bool useCachedInstallScript, bool runtimeOnly = false) { string cacheDir = context.Properties.GetRequiredValue (KnownProperties.AndroidToolchainCacheDirectory); - // Always delete the bin/$(Configuration)/dotnet/ directory - Utilities.DeleteDirectory (dotnetPath); - Uri dotnetScriptUrl = Configurables.Urls.DotNetInstallScript; string scriptFileName = Path.GetFileName (dotnetScriptUrl.LocalPath); string cachedDotnetScriptPath = Path.Combine (cacheDir, scriptFileName); @@ -230,63 +192,16 @@ async Task InstallDotNetAsync (Context context, string dotnetPath, string return false; } + Directory.CreateDirectory (dotnetPath); string dotnetScriptPath = Path.Combine (dotnetPath, scriptFileName); Utilities.CopyFile (cachedDotnetScriptPath, dotnetScriptPath); var type = runtimeOnly ? "runtime" : "SDK"; + Log.StatusLine ($"Installing dotnet {type} '{version}'..."); - Log.StatusLine ($"Discovering download URLs for dotnet {type} '{version}'..."); string scriptCommand = Context.IsWindows ? "powershell.exe" : "bash"; - string[] scriptArgs = GetInstallationScriptArgs (version, dotnetPath, dotnetScriptPath, onlyGetUrls: true, runtimeOnly: runtimeOnly); - string scriptReply = Utilities.GetStringFromStdout (scriptCommand, scriptArgs); - var archiveUrls = new List (); - - char[] fieldSplitChars = new char[] { ':' }; - foreach (string l in scriptReply.Split (new char[] { '\n' })) { - string line = l.Trim (); - - if (!line.StartsWith ("dotnet-install: URL #", StringComparison.OrdinalIgnoreCase)) { - continue; - } - - string[] parts = line.Split (fieldSplitChars, 3); - if (parts.Length < 3) { - Log.WarningLine ($"dotnet-install URL line has unexpected number of parts. Expected 3, got {parts.Length}"); - Log.WarningLine ($"Line: {line}"); - continue; - } - - archiveUrls.Add (parts[2].Trim ()); - } - - if (archiveUrls.Count == 0) { - Log.WarningLine ("No dotnet archive URLs discovered, attempting to run the installation script"); - scriptArgs = GetInstallationScriptArgs (version, dotnetPath, dotnetScriptPath, onlyGetUrls: false, runtimeOnly: runtimeOnly); - return Utilities.RunCommand (scriptCommand, scriptArgs); - } - - string? archivePath = null; - foreach (string url in archiveUrls) { - var archiveUrl = new Uri (url); - string archiveDestinationPath = Path.Combine (cacheDir, Path.GetFileName (archiveUrl.LocalPath)); - - if (File.Exists (archiveDestinationPath)) { - archivePath = archiveDestinationPath; - break; - } - - if (await DownloadDotNetArchive (context, archiveDestinationPath, archiveUrl)) { - archivePath = archiveDestinationPath; - break; - } - } - - if (String.IsNullOrEmpty (archivePath)) { - return false; - } - - Log.StatusLine ($"Installing dotnet {type} '{version}'..."); - return await Utilities.Unpack (archivePath, dotnetPath); + string[] scriptArgs = GetInstallationScriptArgs (version, dotnetPath, dotnetScriptPath, runtimeOnly); + return Utilities.RunCommand (scriptCommand, scriptArgs); } bool TestDotNetSdk (string dotnetTool) diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.Linux.cs b/build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.Linux.cs deleted file mode 100644 index 9a6e6af9c4d..00000000000 --- a/build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.Linux.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Xamarin.Android.Prepare -{ - partial class Step_InstallGNUBinutils - { - const string HostName = "linux"; - } -} diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.MacOS.cs b/build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.MacOS.cs deleted file mode 100644 index 45df1095a9e..00000000000 --- a/build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.MacOS.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Xamarin.Android.Prepare -{ - partial class Step_InstallGNUBinutils - { - const string HostName = "darwin"; - } -} diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.Unix.cs b/build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.Unix.cs deleted file mode 100644 index 1036b769564..00000000000 --- a/build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.Unix.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Xamarin.Android.Prepare -{ - partial class Step_InstallGNUBinutils - { - const string[]? ExecutableExtensions = null; - } -} diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.Windows.cs b/build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.Windows.cs deleted file mode 100644 index 06928984303..00000000000 --- a/build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.Windows.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Xamarin.Android.Prepare -{ - partial class Step_InstallGNUBinutils - { - const string HostName = "windows"; - static readonly string[]? ExecutableExtensions = WindowsExtensions; - } -} diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.cs b/build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.cs deleted file mode 100644 index e819576574f..00000000000 --- a/build-tools/xaprepare/xaprepare/Steps/Step_InstallGNUBinutils.cs +++ /dev/null @@ -1,236 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Threading.Tasks; - -namespace Xamarin.Android.Prepare -{ - partial class Step_InstallGNUBinutils : StepWithDownloadProgress - { - static readonly string[]? WindowsExtensions = {".exe", ".cmd"}; - static readonly string ProductName = $".NET for Android Toolchain {Configurables.Defaults.BinutilsVersion}"; - - public Step_InstallGNUBinutils () - : base ("Install .NET for Android Toolchain") - {} - - protected override async Task Execute (Context context) - { - string hostDestinationDirectory = Configurables.Paths.HostBinutilsInstallDir; - string windowsDestinationDirectory = Configurables.Paths.WindowsBinutilsInstallDir; - - bool hostHaveAll = HaveAllBinutils (hostDestinationDirectory); - bool windowsHaveAll = HaveAllBinutils (windowsDestinationDirectory, WindowsExtensions); - - if (hostHaveAll && windowsHaveAll) { - Log.StatusLine ("All Binutils are already installed"); - return true; - } - - string packageName = Path.GetFileName (Configurables.Urls.BinutilsArchive.LocalPath); - string localArchivePath = Path.Combine (Configurables.Paths.BinutilsCacheDir, packageName); - - if (!await DownloadBinutils (context, localArchivePath, Configurables.Urls.BinutilsArchive)) { - return false; - } - - string tempDir = Path.Combine (Path.GetTempPath (), "xaprepare-binutils"); - Utilities.DeleteDirectorySilent (tempDir); - Utilities.CreateDirectory (tempDir); - - Log.DebugLine ($"Unpacking {ProductName} archive {localArchivePath} into {tempDir}"); - if (!await Utilities.Unpack (localArchivePath, tempDir, cleanDestinatioBeforeUnpacking: true)) { - return false; - } - - if (!hostHaveAll) { - CopyToDestination (context, "Host", tempDir, hostDestinationDirectory, executableExtensions: ExecutableExtensions); - } - - if (!windowsHaveAll) { - CopyToDestination (context, "Windows", tempDir, windowsDestinationDirectory, "windows", WindowsExtensions); - } - - return true; - } - - - - bool CopyToDestination (Context context, string label, string sourceDir, string destinationDir, string osName = HostName, string[]? executableExtensions = null) - { - bool isWindows = osName == "windows"; - - Log.StatusLine (); - Log.StatusLine ($"Installing for {label}:"); - - string osSourcePath = Path.Combine (sourceDir, osName); - string sourcePath = Path.Combine (osSourcePath, "bin"); - string symbolArchiveDir = Path.Combine (destinationDir, "windows-toolchain-pdb"); - foreach (var kvp in Configurables.Defaults.AndroidToolchainPrefixes) { - string prefix = kvp.Value; - CopyTools (prefix); - } - CopyTools (String.Empty); - CopyLibraries (); - - return true; - - void CopyLibraries () - { - if (isWindows) { - return; - } - - string libSourcePath = Path.Combine (osSourcePath, "lib"); - string libDestPath = Path.Combine (destinationDir, "lib"); - foreach (string file in Directory.EnumerateFiles (libSourcePath)) { - Utilities.CopyFileToDir (file, libDestPath); - } - } - - void CopyTools (string prefix) - { - bool copyPrefixed = !String.IsNullOrEmpty (prefix); - foreach (NDKTool tool in Configurables.Defaults.NDKTools) { - if (tool.Prefixed != copyPrefixed) { - continue; - } - - string toolSourcePath = GetToolPath (sourcePath, prefix, tool, executableExtensions, throwOnMissing: true); - string toolName = Path.GetFileName (toolSourcePath); - string toolDestinationPath = Path.Combine (destinationDir, "bin", toolName); - string versionMarkerPath = GetVersionMarker (toolDestinationPath); - - Log.StatusLine ($" {context.Characters.Bullet} Installing ", toolName, tailColor: ConsoleColor.White); - Utilities.CopyFile (toolSourcePath, toolDestinationPath); - File.WriteAllText (versionMarkerPath, DateTime.UtcNow.ToString ()); - - if (!isWindows) { - continue; - } - - // Copy PDBs and corresponding EXEs to a folder to be zipped up for symbol archiving - string toolSourcePdbPath = Path.ChangeExtension (toolSourcePath, ".pdb"); - if (!File.Exists (toolSourcePdbPath)) { - continue; - } - - toolDestinationPath = Path.Combine (symbolArchiveDir, toolName); - string toolDestinationPdbPath = Path.ChangeExtension (toolDestinationPath, ".pdb"); - - Log.StatusLine ($" {context.Characters.Bullet} Copying symbols for ", toolName, tailColor: ConsoleColor.White); - Utilities.CopyFile (toolSourcePath, toolDestinationPath); - Utilities.CopyFile (toolSourcePdbPath, toolDestinationPdbPath); - } - // Copy PDB files for tools that have been renamed - if (isWindows) { - Utilities.CopyFile (Path.Combine (sourcePath, "lld.pdb"), Path.Combine (symbolArchiveDir, "lld.pdb")); - Utilities.CopyFile (Path.Combine (sourcePath, "llvm-objcopy.pdb"), Path.Combine (symbolArchiveDir, "llvm-objcopy.pdb")); - } - } - } - - async Task DownloadBinutils (Context context, string localPackagePath, Uri url) - { - if (Utilities.FileExists (localPackagePath)) { - Log.StatusLine ($"{ProductName} archive already downloaded"); - return true; - } - - Log.StatusLine ($"Downloading {ProductName} from ", url.ToString (), tailColor: ConsoleColor.White); - (bool success, ulong size, HttpStatusCode status) = await Utilities.GetDownloadSizeWithStatus (url); - if (!success) { - if (status == HttpStatusCode.NotFound) { - Log.ErrorLine ($"{ProductName} archive URL not found"); - return false; - } - Log.WarningLine ($"Failed to obtain {ProductName} size. HTTP status code: {status} ({(int)status})"); - } - - DownloadStatus downloadStatus = Utilities.SetupDownloadStatus (context, size, context.InteractiveSession); - Log.StatusLine ($" {context.Characters.Link} {url}", ConsoleColor.White); - await Download (context, url, localPackagePath, ProductName, Path.GetFileName (localPackagePath), downloadStatus); - - if (!File.Exists (localPackagePath)) { - Log.ErrorLine ($"Download of {ProductName} from {url} failed."); - return false; - } - - return true; - } - - bool HaveAllBinutils (string dir, string[]? executableExtensions = null) - { - Log.DebugLine ("Checking if all binutils are installed in {dir}"); - foreach (var kvp in Configurables.Defaults.AndroidToolchainPrefixes) { - string prefix = kvp.Value; - if (!CheckToolsExist (prefix)) { - return false; - } - } - - return CheckToolsExist (String.Empty); - - bool CheckToolsExist (string prefix) - { - bool checkPrefixed = !String.IsNullOrEmpty (prefix); - foreach (NDKTool tool in Configurables.Defaults.NDKTools) { - if (tool.Prefixed != checkPrefixed) { - continue; - } - string toolPath = GetToolPath (dir, prefix, tool, executableExtensions); - string toolName = Path.GetFileName (toolPath); - string versionMarkerPath = GetVersionMarker (toolPath); - - Log.DebugLine ($"Checking {toolName}"); - if (!Utilities.FileExists (toolPath)) { - Log.DebugLine ($"Binutils tool {toolPath} does not exist"); - return false; - } - - if (!Utilities.FileExists (versionMarkerPath)) { - Log.DebugLine ($"Binutils tool {toolPath} exists, but its version is incorrect"); - return false; - } - } - - return true; - } - } - - string GetToolPath (string sourcePath, string prefix, NDKTool tool, string[]? executableExtensions = null, bool throwOnMissing = false) - { - string baseName = $"{(String.IsNullOrEmpty (tool.DestinationName) ? tool.Name : tool.DestinationName)}"; - - if (!String.IsNullOrEmpty (prefix)) { - baseName = $"{prefix}-{baseName}"; - } - - if (executableExtensions == null || executableExtensions.Length == 0) { - return Path.Combine (sourcePath, baseName); - } - - foreach (string executableExtension in executableExtensions) { - string binary = Path.Combine (sourcePath, $"{baseName}{executableExtension}"); - Console.WriteLine ($"Checking: {binary}"); - if (!Utilities.FileExists (binary)) { - continue; - } - - return binary; - } - - if (throwOnMissing) { - string extensions = String.Join (",", executableExtensions); - throw new InvalidOperationException ($"Failed to find binary file '{baseName}{{{extensions}}}'"); - } - - return baseName; - } - - string GetVersionMarker (string toolPath) - { - return $"{toolPath}.{Configurables.Defaults.BinutilsVersion}"; - } - } -} diff --git a/build-tools/xaprepare/xaprepare/xaprepare.csproj b/build-tools/xaprepare/xaprepare/xaprepare.csproj index 18618401823..9675958b227 100644 --- a/build-tools/xaprepare/xaprepare/xaprepare.csproj +++ b/build-tools/xaprepare/xaprepare/xaprepare.csproj @@ -30,7 +30,6 @@ - @@ -43,7 +42,7 @@ - + diff --git a/build-tools/xaprepare/xaprepare/xaprepare.targets b/build-tools/xaprepare/xaprepare/xaprepare.targets index 5575897219a..98a2eca9ee2 100644 --- a/build-tools/xaprepare/xaprepare/xaprepare.targets +++ b/build-tools/xaprepare/xaprepare/xaprepare.targets @@ -46,7 +46,6 @@ - @@ -75,17 +74,12 @@ - - - - - @@ -96,10 +90,6 @@ - - - - diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 9e06a2dfc37..1ced58aafc6 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,20 +1,20 @@ - + https://github.com/dotnet/dotnet - 3c45e4f3e8b0e9069e8b740c7cfa610075c8ca34 + c5d1f734662eb718e7431a4e679a29e47c380f04 - + https://github.com/dotnet/dotnet - 3c45e4f3e8b0e9069e8b740c7cfa610075c8ca34 + c5d1f734662eb718e7431a4e679a29e47c380f04 - + https://github.com/dotnet/dotnet - 3c45e4f3e8b0e9069e8b740c7cfa610075c8ca34 + c5d1f734662eb718e7431a4e679a29e47c380f04 - + https://github.com/dotnet/dotnet - 3c45e4f3e8b0e9069e8b740c7cfa610075c8ca34 + c5d1f734662eb718e7431a4e679a29e47c380f04 https://github.com/dotnet/dotnet @@ -30,23 +30,23 @@ 2b2a06c8a4d45ec0781266ffa3f3852a052f0fbb - + https://github.com/dotnet/android - e1d3646df9cb50b2a0924f5b67fa78f9750ae489 + d549e1dc4e2a083b08b4f24cb5495e81b99d79b5 - + https://github.com/dotnet/dotnet - 3c45e4f3e8b0e9069e8b740c7cfa610075c8ca34 + c5d1f734662eb718e7431a4e679a29e47c380f04 - + https://github.com/dotnet/dotnet - 3c45e4f3e8b0e9069e8b740c7cfa610075c8ca34 + c5d1f734662eb718e7431a4e679a29e47c380f04 - + https://github.com/microsoft/testfx - 65a70b6c827eccb94609be7511e97b0a002dd136 + 487756d8e1c8bc639c4631aedf77b47d2cd10848 diff --git a/eng/Versions.props b/eng/Versions.props index 2dfce5d2459..e52d1f95529 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -1,25 +1,25 @@ - 11.0.100-preview.5.26251.112 + 11.0.100-preview.5.26268.112 $(MicrosoftNETSdkPackageVersion) - 11.0.0-preview.5.26251.112 - 11.0.0-preview.5.26251.112 + 11.0.0-preview.5.26268.112 + 11.0.0-preview.5.26268.112 7.0.0-beta.22103.1 11.0.0-beta.26060.102 - 11.0.0-beta.26251.112 + 11.0.0-beta.26268.112 11.0.100-preview.4.26215.121 11.0.100-preview.4.26215.121 $(MicrosoftNETWorkloadMonoToolChainCurrentManifest110100preview4PackageVersion) $(MicrosoftNETWorkloadEmscriptenCurrentManifest110100preview4PackageVersion) - 11.0.100-preview.5.26251.112 - 0.11.5-preview.26251.112 - 4.3.0-preview.26252.2 + 11.0.100-preview.5.26268.112 + 0.11.5-preview.26268.112 + 4.3.0-preview.26265.5 10.0.7 11.0.0-preview.1.26104.118 - 36.1.53 + 36.1.69 $(MicrosoftNETSdkAndroidManifest100100PackageVersion) diff --git a/samples/NativeAOT/NativeAOT.csproj b/samples/NativeAOT/NativeAOT.csproj index b971a851f9e..0b7ffd0da11 100644 --- a/samples/NativeAOT/NativeAOT.csproj +++ b/samples/NativeAOT/NativeAOT.csproj @@ -1,7 +1,7 @@ $(DotNetAndroidTargetFramework) - 21 + 24 Exe enable enable diff --git a/src/Microsoft.Android.Run/AndroidTestAdapter.cs b/src/Microsoft.Android.Run/AndroidTestAdapter.cs index bd0983ec671..bd63505f0ab 100644 --- a/src/Microsoft.Android.Run/AndroidTestAdapter.cs +++ b/src/Microsoft.Android.Run/AndroidTestAdapter.cs @@ -1,4 +1,5 @@ using System.Xml.Linq; +using Microsoft.Testing.Extensions.TrxReport.Abstractions; using Microsoft.Testing.Platform.Capabilities.TestFramework; using Microsoft.Testing.Platform.Extensions; using Microsoft.Testing.Platform.Extensions.Messages; @@ -76,17 +77,30 @@ async Task RunAndReportAsync (ExecuteRequestContext context, SessionUid sessionU var testResults = ParseTrxFile (localTrxPath); foreach (var result in testResults) { + // Build the failure message including stack trace for non-TRX consumers + var failureMessage = result.ErrorMessage ?? "Test failed"; + if (!string.IsNullOrEmpty (result.StackTrace)) + failureMessage += "\n" + result.StackTrace; + var stateProperty = result.Outcome switch { TrxOutcome.Passed => (IProperty) new PassedTestNodeStateProperty (), - TrxOutcome.Failed => new FailedTestNodeStateProperty (result.ErrorMessage ?? "Test failed"), + TrxOutcome.Failed => new FailedTestNodeStateProperty (failureMessage), TrxOutcome.NotExecuted => new SkippedTestNodeStateProperty (result.ErrorMessage), _ => new PassedTestNodeStateProperty (), }; + var properties = new List { stateProperty }; + + // Add TRX report properties required by ITrxReportCapability + if (!string.IsNullOrEmpty (result.ClassName)) + properties.Add (new TrxFullyQualifiedTypeNameProperty (result.ClassName)); + if (result.Outcome == TrxOutcome.Failed && (!string.IsNullOrEmpty (result.ErrorMessage) || !string.IsNullOrEmpty (result.StackTrace))) + properties.Add (new TrxExceptionProperty (result.ErrorMessage, result.StackTrace)); + var testNode = new TestNode { Uid = new TestNodeUid (result.FullyQualifiedName), DisplayName = result.TestName, - Properties = new PropertyBag (stateProperty), + Properties = new PropertyBag (properties.ToArray ()), }; await context.MessageBus.PublishAsync (this, new TestNodeUpdateMessage (sessionUid, testNode)); @@ -235,18 +249,14 @@ static List ParseTrxFile (string trxPath) ? $"{className}.{testName}" : testName; - // Extract error message if present + // Extract error message and stack trace if present string? errorMessage = null; + string? stackTrace = null; var outputElement = unitTestResult.Element (ns + "Output"); var errorInfo = outputElement?.Element (ns + "ErrorInfo"); if (errorInfo != null) { - var message = errorInfo.Element (ns + "Message")?.Value; - var stackTrace = errorInfo.Element (ns + "StackTrace")?.Value; - errorMessage = message; - if (!string.IsNullOrEmpty (stackTrace)) - errorMessage = string.IsNullOrEmpty (errorMessage) - ? stackTrace - : $"{errorMessage}\n{stackTrace}"; + errorMessage = errorInfo.Element (ns + "Message")?.Value; + stackTrace = errorInfo.Element (ns + "StackTrace")?.Value; } var trxOutcome = outcome switch { @@ -256,7 +266,7 @@ static List ParseTrxFile (string trxPath) _ => TrxOutcome.Passed, }; - results.Add (new TrxTestResult (fullyQualifiedName, testName, trxOutcome, errorMessage)); + results.Add (new TrxTestResult (fullyQualifiedName, testName, className, trxOutcome, errorMessage, stackTrace)); } } @@ -284,10 +294,22 @@ enum TrxOutcome record TrxTestResult ( string FullyQualifiedName, string TestName, + string? ClassName, TrxOutcome Outcome, - string? ErrorMessage); + string? ErrorMessage, + string? StackTrace); class AndroidTestCapabilities : ITestFrameworkCapabilities { - public IReadOnlyCollection Capabilities { get; } = []; + public IReadOnlyCollection Capabilities { get; } = [new AndroidTrxReportCapability ()]; +} + +class AndroidTrxReportCapability : ITrxReportCapability +{ + public bool IsSupported => true; + + public void Enable () + { + // No-op: TRX properties are always added to test nodes. + } } diff --git a/src/Microsoft.Android.Run/Microsoft.Android.Run.csproj b/src/Microsoft.Android.Run/Microsoft.Android.Run.csproj index b33678f9fcf..1f2466451e5 100644 --- a/src/Microsoft.Android.Run/Microsoft.Android.Run.csproj +++ b/src/Microsoft.Android.Run/Microsoft.Android.Run.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Microsoft.Android.Run/Program.cs b/src/Microsoft.Android.Run/Program.cs index 2da984571d2..7b95b9288b5 100644 --- a/src/Microsoft.Android.Run/Program.cs +++ b/src/Microsoft.Android.Run/Program.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using Microsoft.Testing.Extensions; using Mono.Options; using Xamarin.Android.Tools; @@ -317,6 +318,13 @@ async Task RunDotnetTestAsync (List mtpArgs) // since MTP needs them to set up the test communication channel. mtpArgs.AddRange (["--server", "dotnettestcli", "--dotnet-test-pipe", validatedDotnetTestPipe]); + // MTP defaults its working directory to the DLL location (SDK tools directory), + // not Environment.CurrentDirectory. Pass --results-directory explicitly so TRX + // reports are written to the project directory, matching dotnet test conventions. + if (!mtpArgs.Contains ("--results-directory")) { + mtpArgs.AddRange (["--results-directory", Path.Combine (Environment.CurrentDirectory, "TestResults")]); + } + var testApplicationBuilder = await Microsoft.Testing.Platform.Builder.TestApplication.CreateBuilderAsync (mtpArgs.ToArray ()); var adapter = new AndroidTestAdapter ( @@ -330,6 +338,8 @@ async Task RunDotnetTestAsync (List mtpArgs) _ => new AndroidTestCapabilities (), (_, _) => adapter); + testApplicationBuilder.AddTrxReportProvider (); + using var testApplication = await testApplicationBuilder.BuildAsync (); return await testApplication.RunAsync (); } diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs index e28bbdebfdc..ad0f484adaf 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs @@ -1,6 +1,5 @@ using Android.Runtime; using Java.Interop; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Microsoft.Android.Runtime; @@ -56,27 +55,23 @@ static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader, IntPtr langua // This needs to be called first, since it sets up locations, environment variables, logging etc XA_Host_NativeAOT_OnInit (language, filesDir, cacheDir, ref initArgs); - JNIEnvInit.InitializeJniRuntimeEarly (initArgs); + JNIEnvInit.InitializeBeforeRuntimeCreation (initArgs); var settings = new DiagnosticSettings (); settings.AddDebugDotnetLog (); - InitializeTrimmableTypeMapData (); - var typeManager = CreateTypeManager (); - var options = new NativeAotRuntimeOptions { EnvironmentPointer = jnienv, ClassLoader = new JniObjectReference (classLoader, JniObjectReferenceType.Global), - TypeManager = typeManager, - ValueManager = new JavaMarshalValueManager (), + TypeManager = JNIEnvInit.CreateTypeManager (initArgs), + ValueManager = JNIEnvInit.CreateValueManager (), JniGlobalReferenceLogWriter = settings.GrefLog, JniLocalReferenceLogWriter = settings.LrefLog, }; runtime = options.CreateJreVM (); - // Entry point into Mono.Android.dll. Log categories are initialized in JNI_OnLoad. - JNIEnvInit.InitializeJniRuntime (runtime, initArgs); - RegisterTrimmableTypeMapNativeMethods (); + // Entry point into Mono.Android.dll for NativeAOT-specific JNI runtime initialization. + JNIEnvInit.InitializeNativeAotRuntime (runtime, initArgs); transition = new JniTransition (jnienv); @@ -91,30 +86,4 @@ static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader, IntPtr langua } transition.Dispose (); } - - static JniRuntime.JniTypeManager CreateTypeManager () - { - if (RuntimeFeature.TrimmableTypeMap) { - return new TrimmableTypeMapTypeManager (); - } - - return new ManagedTypeManager (); - } - - // Separate method so non-trimmable builds don't try to resolve TypeMapLoader - // from the generated _Microsoft.Android.TypeMaps.dll. - [MethodImpl (MethodImplOptions.NoInlining)] - static void InitializeTrimmableTypeMapData () - { - if (RuntimeFeature.TrimmableTypeMap) { - TypeMapLoader.Initialize (); - } - } - - static void RegisterTrimmableTypeMapNativeMethods () - { - if (RuntimeFeature.TrimmableTypeMap) { - TrimmableTypeMap.RegisterNativeMethods (); - } - } } diff --git a/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs b/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs index dc39f615a08..55bc6530e61 100644 --- a/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs +++ b/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs @@ -16,11 +16,10 @@ public class MarkJavaObjects : BaseMarkHandler public override void Initialize (LinkContext context, MarkContext markContext) { base.Initialize (context, markContext); - context.TryGetCustomData ("AndroidHttpClientHandlerType", out string androidHttpClientHandlerType); context.TryGetCustomData ("AndroidCustomViewMapFile", out string androidCustomViewMapFile); var customViewMap = MonoAndroidHelper.LoadCustomViewMapFile (androidCustomViewMapFile); - markContext.RegisterMarkAssemblyAction (assembly => ProcessAssembly (assembly, androidHttpClientHandlerType, customViewMap)); + markContext.RegisterMarkAssemblyAction (assembly => ProcessAssembly (assembly, customViewMap)); markContext.RegisterMarkTypeAction (type => ProcessType (type)); } @@ -29,28 +28,16 @@ bool IsActiveFor (AssemblyDefinition assembly) if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) return false; - return assembly.MainModule.HasTypeReference ("System.Net.Http.HttpMessageHandler") || - assembly.MainModule.HasTypeReference ("Java.Lang.Object") || + return assembly.MainModule.HasTypeReference ("Java.Lang.Object") || assembly.MainModule.HasTypeReference ("Android.Util.IAttributeSet"); } - public void ProcessAssembly (AssemblyDefinition assembly, string androidHttpClientHandlerType, Dictionary> customViewMap) + public void ProcessAssembly (AssemblyDefinition assembly, Dictionary> customViewMap) { if (!IsActiveFor (assembly)) return; foreach (var type in assembly.MainModule.Types) { - // Custom HttpMessageHandler - if (!string.IsNullOrEmpty (androidHttpClientHandlerType) && - androidHttpClientHandlerType.StartsWith (type.Name, StringComparison.Ordinal)) { - var assemblyQualifiedName = type.GetPartialAssemblyQualifiedName (Context); - if (assemblyQualifiedName == androidHttpClientHandlerType) { - Annotations.Mark (type); - PreservePublicParameterlessConstructors (type); - continue; - } - } - // Continue if not an IJavaObject if (!type.ImplementsIJavaObject (cache)) continue; @@ -118,21 +105,6 @@ void PreserveJavaObjectImplementation (TypeDefinition type) PreserveInterfaces (type); } - void PreservePublicParameterlessConstructors (TypeDefinition type) - { - if (!type.HasMethods) - return; - - foreach (var constructor in type.Methods) - { - if (!constructor.IsConstructor || constructor.IsStatic || !constructor.IsPublic || constructor.HasParameters) - continue; - - PreserveMethod (type, constructor); - break; // We can stop when found - } - } - void PreserveAttributeSetConstructor (TypeDefinition type) { if (!type.HasMethods) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs new file mode 100644 index 00000000000..90c8707e133 --- /dev/null +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs @@ -0,0 +1,609 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using TrackedInstructionEncoder = Microsoft.Android.Sdk.TrimmableTypeMap.PEAssemblyBuilder.TrackedInstructionEncoder; + +namespace Microsoft.Android.Sdk.TrimmableTypeMap; + +sealed class ExportMethodDispatchEmitter +{ + readonly PEAssemblyBuilder _pe; + readonly ExportMethodDispatchEmitterContext _context; + + public ExportMethodDispatchEmitter (PEAssemblyBuilder pe, ExportMethodDispatchEmitterContext context) + { + _pe = pe ?? throw new ArgumentNullException (nameof (pe)); + _context = context ?? throw new ArgumentNullException (nameof (context)); + } + + public MethodDefinitionHandle EmitUcoMethod (UcoMethodData uco) + { + var exportMethodDispatch = GetRequiredExportMethodDispatch (uco); + var jniParams = JniSignatureHelper.ParseParameterTypes (uco.JniSignature); + var returnKind = JniSignatureHelper.ParseReturnType (uco.JniSignature); + int paramCount = 2 + jniParams.Count; + bool isVoid = returnKind == JniParamKind.Void; + var exportMethodDispatchLocals = CreateExportMethodDispatchLocals (exportMethodDispatch, isVoid, returnKind); + + // UCO wrapper signature: uses JNI ABI types (byte for boolean) + Action encodeSig = sig => sig.MethodSignature ().Parameters (paramCount, + rt => { if (isVoid) rt.Void (); else JniSignatureHelper.EncodeClrType (rt.Type (), returnKind); }, + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().IntPtr (); + for (int j = 0; j < jniParams.Count; j++) { + JniSignatureHelper.EncodeClrType (p.AddParameter ().Type (), jniParams [j]); + } + }); + + var callbackTypeHandle = _pe.ResolveTypeRef (uco.CallbackType); + var callbackRef = AddExportMethodDispatchRef (uco, callbackTypeHandle); + + // Wrap the dispatch in the standard BeginMarshalMethod/try/catch/finally pattern so + // managed exceptions thrown from the [Export] body are routed through + // JniRuntime.OnUserUnhandledException — matching the legacy LLVM-IR contract + // (Mono.Android.Export/CallbackCode.cs) and the trimmable UCO ctor wrapper. + var handle = _pe.EmitBody (uco.WrapperName, + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, + encodeSig, + (encoder, cfb) => { + EmitWrappedExportMethodDispatch (encoder, cfb, uco, callbackTypeHandle, callbackRef, + jniParams, returnKind, exportMethodDispatchLocals); + }, + exportMethodDispatchLocals.EncodeLocals); + + AddUnmanagedCallersOnlyAttribute (handle); + return handle; + } + + void EmitWrappedExportMethodDispatch (TrackedInstructionEncoder encoder, ControlFlowBuilder cfb, + UcoMethodData uco, EntityHandle callbackTypeHandle, MemberReferenceHandle callbackRef, + List jniParams, JniParamKind returnKind, ExportMethodDispatchLocals locals) + { + bool isVoid = returnKind == JniParamKind.Void; + var tryStart = encoder.DefineLabel (); + var catchStart = encoder.DefineLabel (); + var finallyStart = encoder.DefineLabel (); + var afterAll = encoder.DefineLabel (); + var endCatch = encoder.DefineLabel (); + + // Preamble: if (!BeginMarshalMethod(jnienv, out envp, out runtime)) goto afterAll; + // On the false path, the ABI return local is zero-initialized (InitLocals=true) so + // it returns the appropriate default (0 / IntPtr.Zero) for the JNI return kind. + encoder.LoadArgument (0); + encoder.LoadLocalAddress (0); + encoder.LoadLocalAddress (1); + encoder.Call (_context.BeginMarshalMethodRef, parameterCount: 3, returnsValue: true); + encoder.Branch (ILOpCode.Brfalse, afterAll); + + // TRY: dispatch + (if non-void) store ABI return value to the survival local. + encoder.MarkLabel (tryStart); + EmitExportMethodDispatch (encoder, uco, callbackTypeHandle, callbackRef, jniParams, returnKind, locals); + if (!isVoid) { + encoder.StoreLocal (locals.AbiReturnLocalIndex); + } + encoder.Branch (ILOpCode.Leave, afterAll); + + // CATCH (System.Exception e): runtime?.OnUserUnhandledException(ref envp, e); + encoder.MarkLabel (catchStart, stackDepth: 1); + encoder.StoreLocal (2); + encoder.LoadLocal (1); + encoder.Branch (ILOpCode.Brfalse, endCatch); + encoder.LoadLocal (1); + encoder.LoadLocalAddress (0); + encoder.LoadLocal (2); + encoder.Callvirt (_context.OnUserUnhandledExceptionRef, parameterCount: 2); + encoder.MarkLabel (endCatch); + encoder.Branch (ILOpCode.Leave, afterAll); + + // FINALLY: EndMarshalMethod(ref envp); + encoder.MarkLabel (finallyStart); + encoder.LoadLocalAddress (0); + encoder.Call (_context.EndMarshalMethodRef, parameterCount: 1); + encoder.OpCode (ILOpCode.Endfinally); + + // AFTER: load ABI return (if non-void) and return. + encoder.MarkLabel (afterAll); + if (!isVoid) { + encoder.LoadLocal (locals.AbiReturnLocalIndex); + } + encoder.Return (returnsValue: !isVoid); + + cfb.AddCatchRegion (tryStart, catchStart, catchStart, finallyStart, _context.ExceptionRef); + cfb.AddFinallyRegion (tryStart, finallyStart, finallyStart, afterAll); + } + + sealed class ExportMethodDispatchLocals + { + public ExportMethodDispatchLocals (Dictionary arrayParameterLocals, int returnLocalIndex, int abiReturnLocalIndex, Action encodeLocals) + { + ArrayParameterLocals = arrayParameterLocals; + ReturnLocalIndex = returnLocalIndex; + AbiReturnLocalIndex = abiReturnLocalIndex; + EncodeLocals = encodeLocals; + } + + public Dictionary ArrayParameterLocals { get; } + + /// Local that holds the managed return value across array copy-backs (-1 if not needed). + public int ReturnLocalIndex { get; } + + /// Local that holds the JNI ABI return value across try/finally so it survives 'leave' (-1 if void). + public int AbiReturnLocalIndex { get; } + + public Action EncodeLocals { get; } + + public bool HasArrayParameters => ArrayParameterLocals.Count > 0; + } + + static ExportMethodDispatchData GetRequiredExportMethodDispatch (UcoMethodData uco) + { + return uco.ExportMethodDispatch ?? throw new InvalidOperationException ($"ExportMethodDispatchEmitter only supports UCO methods with ExportMethodDispatch metadata."); + } + + ExportMethodDispatchLocals CreateExportMethodDispatchLocals (ExportMethodDispatchData exportMethodDispatch, bool isVoid, JniParamKind returnKind) + { + // Local layout (fixed prefix shared with the UCO ctor wrapper): + // 0 = JniTransition envp (valuetype) + // 1 = JniRuntime? runtime (class) + // 2 = Exception e (class) + // Then: + // 3..N = managed array-param copy-back locals (one per array parameter) + // (next) = managed return temp — only when there are array params and return is non-void + // (next) = ABI return temp — only when return is non-void; survives try/finally → afterAll + var arrayParameterLocals = new Dictionary (); + var arrayLocalTypes = new List (); + int nextLocalIndex = 3; + + for (int i = 0; i < exportMethodDispatch.ParameterTypes.Count; i++) { + if (!IsManagedArrayType (exportMethodDispatch.ParameterTypes [i].ManagedTypeName)) { + continue; + } + + arrayParameterLocals.Add (i, nextLocalIndex++); + arrayLocalTypes.Add (exportMethodDispatch.ParameterTypes [i]); + } + + int returnLocalIndex = -1; + TypeRefData? managedReturnType = null; + if (arrayParameterLocals.Count > 0 && !isVoid) { + returnLocalIndex = nextLocalIndex++; + managedReturnType = exportMethodDispatch.ReturnType; + } + + int abiReturnLocalIndex = -1; + if (!isVoid) { + abiReturnLocalIndex = nextLocalIndex++; + } + + return new ExportMethodDispatchLocals ( + arrayParameterLocals, + returnLocalIndex, + abiReturnLocalIndex, + blob => EncodeAllLocals (blob, arrayLocalTypes, managedReturnType, isVoid, returnKind)); + } + + void EncodeAllLocals (BlobBuilder blob, IReadOnlyList arrayLocalTypes, + TypeRefData? managedReturnType, bool isVoid, JniParamKind returnKind) + { + int total = 3 + arrayLocalTypes.Count + (managedReturnType is not null ? 1 : 0) + (isVoid ? 0 : 1); + + blob.WriteByte (0x07); // LOCAL_SIG + blob.WriteCompressedInteger (total); + + // 0: JniTransition (valuetype) + blob.WriteByte (0x11); + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_context.JniTransitionRef)); + // 1: JniRuntime (class) + blob.WriteByte (0x12); + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_context.JniRuntimeRef)); + // 2: Exception (class) + blob.WriteByte (0x12); + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_context.ExceptionRef)); + + // 3..N: managed array-parameter copy-back locals + foreach (var localType in arrayLocalTypes) { + EncodeManagedType (new SignatureTypeEncoder (blob), localType); + } + + // Managed return temp (managed type — same encoding as method parameters) + if (managedReturnType is not null) { + EncodeManagedType (new SignatureTypeEncoder (blob), managedReturnType); + } + + // ABI return temp (JNI ABI type — byte for boolean, IntPtr for object handles, etc.) + if (!isVoid) { + JniSignatureHelper.EncodeClrType (new SignatureTypeEncoder (blob), returnKind); + } + } + + static bool IsManagedArrayType (string managedTypeName) + => managedTypeName.EndsWith ("[]", StringComparison.Ordinal); + + MemberReferenceHandle AddExportMethodDispatchRef (UcoMethodData uco, EntityHandle callbackTypeHandle) + { + var exportMethodDispatch = GetRequiredExportMethodDispatch (uco); + + return _pe.AddMemberRef (callbackTypeHandle, exportMethodDispatch.ManagedMethodName, + sig => sig.MethodSignature (isInstanceMethod: !exportMethodDispatch.IsStatic).Parameters (exportMethodDispatch.ParameterTypes.Count, + rt => { + if (exportMethodDispatch.ReturnType.ManagedTypeName == "System.Void") { + rt.Void (); + } else { + EncodeManagedType (rt.Type (), exportMethodDispatch.ReturnType); + } + }, + p => { + for (int i = 0; i < exportMethodDispatch.ParameterTypes.Count; i++) { + EncodeManagedType (p.AddParameter ().Type (), exportMethodDispatch.ParameterTypes [i]); + } + })); + } + + void EmitExportMethodDispatch (TrackedInstructionEncoder encoder, UcoMethodData uco, EntityHandle callbackTypeHandle, + MemberReferenceHandle callbackRef, List jniParams, JniParamKind returnKind, + ExportMethodDispatchLocals exportMethodDispatchLocals) + { + var exportMethodDispatch = GetRequiredExportMethodDispatch (uco); + int managedParameterCount = exportMethodDispatch.ParameterTypes.Count; + bool returnsManagedValue = exportMethodDispatch.ReturnType.ManagedTypeName != "System.Void"; + + if (!exportMethodDispatch.IsStatic) { + encoder.LoadArgument (1); + encoder.LoadConstantI4 (0); + EmitManagedTypeToken (encoder, callbackTypeHandle); + encoder.Call (_context.JavaLangObjectGetObjectRef, parameterCount: 3, returnsValue: true); + encoder.CastClass (callbackTypeHandle); + } + + for (int i = 0; i < managedParameterCount; i++) { + LoadManagedArgument (encoder, + exportMethodDispatch.ParameterTypes [i], + GetExportMethodDispatchParameterKind (exportMethodDispatch, i), + jniParams [i], + 2 + i); + + if (exportMethodDispatchLocals.ArrayParameterLocals.TryGetValue (i, out var localIndex)) { + encoder.StoreLocal (localIndex); + encoder.LoadLocal (localIndex); + } + } + + if (exportMethodDispatch.IsStatic) { + encoder.Call (callbackRef, managedParameterCount, returnsManagedValue); + } else { + encoder.Callvirt (callbackRef, managedParameterCount, returnsManagedValue); + } + + EmitManagedArrayCopyBacks (encoder, exportMethodDispatch, returnKind, exportMethodDispatchLocals); + ConvertManagedReturnValue (encoder, exportMethodDispatch.ReturnType, exportMethodDispatch.ReturnKind, returnKind); + } + + static ExportParameterKindInfo GetExportMethodDispatchParameterKind (ExportMethodDispatchData exportMethodDispatch, int index) + => index < exportMethodDispatch.ParameterKinds.Count ? exportMethodDispatch.ParameterKinds [index] : ExportParameterKindInfo.Unspecified; + + void EmitManagedArrayCopyBacks (TrackedInstructionEncoder encoder, ExportMethodDispatchData exportMethodDispatch, JniParamKind returnKind, ExportMethodDispatchLocals exportMethodDispatchLocals) + { + if (!exportMethodDispatchLocals.HasArrayParameters) { + return; + } + + if (returnKind != JniParamKind.Void) { + encoder.StoreLocal (exportMethodDispatchLocals.ReturnLocalIndex); + } + + foreach (var kvp in exportMethodDispatchLocals.ArrayParameterLocals) { + var skipCopy = encoder.DefineLabel (); + encoder.LoadLocal (kvp.Value); + encoder.Branch (ILOpCode.Brfalse_s, skipCopy); + encoder.LoadLocal (kvp.Value); + EmitManagedArrayElementTypeToken (encoder, exportMethodDispatch.ParameterTypes [kvp.Key]); + encoder.LoadArgument (2 + kvp.Key); + encoder.Call (_context.JniEnvCopyArrayRef, parameterCount: 3); + encoder.MarkLabel (skipCopy); + } + + if (returnKind != JniParamKind.Void) { + encoder.LoadLocal (exportMethodDispatchLocals.ReturnLocalIndex); + } + } + + /// + /// Emits IL that loads JNI argument onto the + /// stack and converts it to the managed type expected by the user-visible + /// method or constructor parameter. Handles primitives (with byte → bool + /// conversion for System.Boolean), strings, arrays, [Export] + /// parameter kinds (streams / XML parsers), and object peers via + /// Java.Lang.Object.GetObject (IntPtr, JniHandleOwnership, Type). + /// + internal void LoadManagedArgument (TrackedInstructionEncoder encoder, TypeRefData managedType, ExportParameterKindInfo exportKind, JniParamKind jniKind, int argumentIndex) + { + string managedTypeName = managedType.ManagedTypeName; + + ThrowIfUnsupportedManagedType (managedTypeName); + + if (TryEmitExportParameterArgument (encoder, exportKind, argumentIndex)) { + return; + } + + if (TryEmitPrimitiveManagedArgument (encoder, managedTypeName, argumentIndex)) { + return; + } + + if (jniKind != JniParamKind.Object) { + encoder.LoadArgument (argumentIndex); + return; + } + + if (IsManagedArrayType (managedTypeName)) { + encoder.LoadArgument (argumentIndex); + encoder.LoadConstantI4 (0); + EmitManagedArrayElementTypeToken (encoder, managedType); + encoder.Call (_context.JniEnvGetArrayRef, parameterCount: 3, returnsValue: true); + encoder.CastClass (ResolveManagedTypeHandle (managedType)); + return; + } + + EmitManagedObjectArgument (encoder, managedType, argumentIndex); + } + + void ConvertManagedReturnValue (TrackedInstructionEncoder encoder, TypeRefData managedReturnType, ExportParameterKindInfo exportKind, JniParamKind returnKind) + { + string managedReturnTypeName = managedReturnType.ManagedTypeName; + + if (returnKind == JniParamKind.Void) { + return; + } + + if (returnKind != JniParamKind.Object) { + if (managedReturnTypeName == "System.Boolean") { + encoder.OpCode (ILOpCode.Conv_u1); + } + return; + } + + if (managedReturnTypeName == "System.String") { + encoder.Call (_context.JniEnvNewStringRef, parameterCount: 1, returnsValue: true); + return; + } + + if (managedReturnTypeName == "System.Void") { + return; + } + + if (IsManagedArrayType (managedReturnTypeName)) { + EmitManagedArrayReturn (encoder, managedReturnType); + return; + } + + if (TryEmitExportParameterReturn (encoder, exportKind)) { + return; + } + + // Reference-type returns that need dedicated marshalling. Mirrors the + // SymbolKind dispatch in legacy Mono.Android.Export/CallbackCode.cs: + // - CharSequence.ToLocalJniHandle handles 'string'-as-ICharSequence, + // not just IJavaObject-derived peers. + // - JavaList/JavaDictionary/JavaCollection.ToLocalJniHandle wrap raw + // managed collections without a Java peer. + if (managedReturnTypeName == "Java.Lang.ICharSequence") { + encoder.Call (_context.CharSequenceToLocalJniHandleRef, parameterCount: 1, returnsValue: true); + return; + } + if (managedReturnTypeName == "System.Collections.IList") { + encoder.Call (_context.JavaListToLocalJniHandleRef, parameterCount: 1, returnsValue: true); + return; + } + if (managedReturnTypeName == "System.Collections.IDictionary") { + encoder.Call (_context.JavaDictionaryToLocalJniHandleRef, parameterCount: 1, returnsValue: true); + return; + } + if (managedReturnTypeName == "System.Collections.ICollection") { + encoder.Call (_context.JavaCollectionToLocalJniHandleRef, parameterCount: 1, returnsValue: true); + return; + } + + encoder.CastClass (_context.IJavaObjectRef); + encoder.Call (_context.JniEnvToLocalJniHandleRef, parameterCount: 1, returnsValue: true); + } + + void ThrowIfUnsupportedManagedType (string managedTypeName) + { + if (managedTypeName.EndsWith ("&", StringComparison.Ordinal) || managedTypeName.EndsWith ("*", StringComparison.Ordinal)) { + throw new NotSupportedException ($"[Export] methods with by-ref or pointer signature types are not supported: '{managedTypeName}'."); + } + + var nonArrayTypeName = managedTypeName; + while (nonArrayTypeName.EndsWith ("[]", StringComparison.Ordinal)) { + nonArrayTypeName = nonArrayTypeName.Substring (0, nonArrayTypeName.Length - 2); + } + + if (nonArrayTypeName.StartsWith ("!", StringComparison.Ordinal) || nonArrayTypeName.IndexOf ('<') >= 0) { + throw new NotSupportedException ($"[Export] methods with generic signature types are not supported: '{managedTypeName}'."); + } + } + + bool TryEmitExportParameterArgument (TrackedInstructionEncoder encoder, ExportParameterKindInfo exportKind, int argumentIndex) + { + switch (exportKind) { + case ExportParameterKindInfo.InputStream: + encoder.LoadArgument (argumentIndex); + encoder.LoadConstantI4 (0); + encoder.Call (_context.InputStreamInvokerFromJniHandleRef, parameterCount: 2, returnsValue: true); + return true; + case ExportParameterKindInfo.OutputStream: + encoder.LoadArgument (argumentIndex); + encoder.LoadConstantI4 (0); + encoder.Call (_context.OutputStreamInvokerFromJniHandleRef, parameterCount: 2, returnsValue: true); + return true; + case ExportParameterKindInfo.XmlPullParser: + encoder.LoadArgument (argumentIndex); + encoder.LoadConstantI4 (0); + encoder.Call (_context.XmlPullParserReaderFromJniHandleRef, parameterCount: 2, returnsValue: true); + return true; + case ExportParameterKindInfo.XmlResourceParser: + encoder.LoadArgument (argumentIndex); + encoder.LoadConstantI4 (0); + encoder.Call (_context.XmlResourceParserReaderFromJniHandleRef, parameterCount: 2, returnsValue: true); + return true; + default: + return false; + } + } + + bool TryEmitPrimitiveManagedArgument (TrackedInstructionEncoder encoder, string managedTypeName, int argumentIndex) + { + switch (managedTypeName) { + case "System.Boolean": + encoder.LoadArgument (argumentIndex); + encoder.LoadConstantI4 (0); + encoder.OpCode (ILOpCode.Cgt_un); + return true; + case "System.Byte": + case "System.SByte": + case "System.Char": + case "System.Int16": + case "System.UInt16": + case "System.Int32": + case "System.UInt32": + case "System.Int64": + case "System.UInt64": + case "System.Single": + case "System.Double": + case "System.IntPtr": + encoder.LoadArgument (argumentIndex); + return true; + case "System.String": + encoder.LoadArgument (argumentIndex); + encoder.LoadConstantI4 (0); + encoder.Call (_context.JniEnvGetStringRef, parameterCount: 2, returnsValue: true); + return true; + default: + return false; + } + } + + void EmitManagedObjectArgument (TrackedInstructionEncoder encoder, TypeRefData managedType, int argumentIndex) + { + EntityHandle managedTypeHandle = default; + if (managedType.ManagedTypeName != "System.Object") { + managedTypeHandle = ResolveManagedTypeHandle (managedType); + } + encoder.LoadArgument (argumentIndex); + encoder.LoadConstantI4 (0); + if (managedType.ManagedTypeName == "System.Object") { + encoder.OpCode (ILOpCode.Ldnull); + } else { + EmitManagedTypeToken (encoder, managedTypeHandle); + } + encoder.Call (_context.JavaLangObjectGetObjectRef, parameterCount: 3, returnsValue: true); + + if (managedType.ManagedTypeName != "System.Object") { + encoder.CastClass (managedTypeHandle); + } + } + + void EmitManagedArrayReturn (TrackedInstructionEncoder encoder, TypeRefData managedReturnType) + { + var nonNullArray = encoder.DefineLabel (); + var done = encoder.DefineLabel (); + + encoder.OpCode (ILOpCode.Dup); + encoder.Branch (ILOpCode.Brtrue_s, nonNullArray); + encoder.OpCode (ILOpCode.Pop); + encoder.LoadConstantI4 (0); + encoder.BranchPreservingStack (ILOpCode.Br_s, done); + encoder.MarkLabel (nonNullArray); + EmitManagedArrayElementTypeToken (encoder, managedReturnType); + encoder.Call (_context.JniEnvNewArrayRef, parameterCount: 2, returnsValue: true); + encoder.MarkLabel (done); + } + + bool TryEmitExportParameterReturn (TrackedInstructionEncoder encoder, ExportParameterKindInfo exportKind) + { + switch (exportKind) { + case ExportParameterKindInfo.InputStream: + encoder.Call (_context.InputStreamAdapterToLocalJniHandleRef, parameterCount: 1, returnsValue: true); + return true; + case ExportParameterKindInfo.OutputStream: + encoder.Call (_context.OutputStreamAdapterToLocalJniHandleRef, parameterCount: 1, returnsValue: true); + return true; + case ExportParameterKindInfo.XmlPullParser: + encoder.Call (_context.XmlReaderPullParserToLocalJniHandleRef, parameterCount: 1, returnsValue: true); + return true; + case ExportParameterKindInfo.XmlResourceParser: + encoder.Call (_context.XmlReaderResourceParserToLocalJniHandleRef, parameterCount: 1, returnsValue: true); + return true; + default: + return false; + } + } + + void EmitManagedTypeToken (TrackedInstructionEncoder encoder, EntityHandle typeHandle) + { + encoder.LoadToken (typeHandle); + encoder.Call (_context.GetTypeFromHandleRef, parameterCount: 1, returnsValue: true); + } + + void EmitManagedArrayElementTypeToken (TrackedInstructionEncoder encoder, TypeRefData arrayType) + { + var elementType = arrayType with { + ManagedTypeName = arrayType.ManagedTypeName.Substring (0, arrayType.ManagedTypeName.Length - 2), + }; + EmitManagedTypeToken (encoder, ResolveManagedTypeHandle (elementType)); + } + + EntityHandle ResolveManagedTypeHandle (TypeRefData managedType) + { + if (IsManagedArrayType (managedType.ManagedTypeName)) { + var blob = new BlobBuilder (); + EncodeManagedType (new SignatureTypeEncoder (blob), managedType); + return _pe.Metadata.AddTypeSpecification (_pe.Metadata.GetOrAddBlob (blob)); + } + + return _pe.ResolveTypeRef (managedType); + } + + void EncodeManagedType (SignatureTypeEncoder encoder, TypeRefData managedType) + { + string managedTypeName = managedType.ManagedTypeName; + + ThrowIfUnsupportedManagedType (managedTypeName); + if (managedTypeName.EndsWith ("[]", StringComparison.Ordinal)) { + EncodeManagedType (encoder.SZArray (), managedType with { + ManagedTypeName = managedTypeName.Substring (0, managedTypeName.Length - 2), + }); + return; + } + + switch (managedTypeName) { + case "System.Boolean": encoder.Boolean (); return; + case "System.Byte": encoder.Byte (); return; + case "System.SByte": encoder.SByte (); return; + case "System.Char": encoder.Char (); return; + case "System.Int16": encoder.Int16 (); return; + case "System.UInt16": encoder.UInt16 (); return; + case "System.Int32": encoder.Int32 (); return; + case "System.UInt32": encoder.UInt32 (); return; + case "System.Int64": encoder.Int64 (); return; + case "System.UInt64": encoder.UInt64 (); return; + case "System.Single": encoder.Single (); return; + case "System.Double": encoder.Double (); return; + case "System.String": encoder.String (); return; + case "System.Object": encoder.Object (); return; + case "System.IntPtr": encoder.IntPtr (); return; + } + + var typeHandle = ResolveManagedTypeHandle (managedType); + encoder.Type (typeHandle, isValueType: managedType.IsEnum); + } + + void AddUnmanagedCallersOnlyAttribute (MethodDefinitionHandle handle) + { + _pe.Metadata.AddCustomAttribute (handle, _context.UcoAttrCtorRef, _context.UcoAttrBlobHandle); + } + +} diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitterContext.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitterContext.cs new file mode 100644 index 00000000000..95afef57d86 --- /dev/null +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitterContext.cs @@ -0,0 +1,228 @@ +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +namespace Microsoft.Android.Sdk.TrimmableTypeMap; + +/// +/// Holds pre-resolved metadata references needed by +/// for generating [Export] method dispatch IL. Created once per emit pass and reused +/// for all export methods. +/// +sealed class ExportMethodDispatchEmitterContext +{ + public static ExportMethodDispatchEmitterContext Create ( + PEAssemblyBuilder pe, + TypeReferenceHandle iJavaPeerableRef, + TypeReferenceHandle jniHandleOwnershipRef, + TypeReferenceHandle jniEnvRef, + TypeReferenceHandle systemTypeRef, + MemberReferenceHandle getTypeFromHandleRef, + MemberReferenceHandle ucoAttrCtorRef, + BlobHandle ucoAttrBlobHandle, + TypeReferenceHandle jniTransitionRef, + TypeReferenceHandle jniRuntimeRef, + TypeReferenceHandle exceptionRef, + MemberReferenceHandle beginMarshalMethodRef, + MemberReferenceHandle endMarshalMethodRef, + MemberReferenceHandle onUserUnhandledExceptionRef) + { + var metadata = pe.Metadata; + var iJavaObjectRef = metadata.AddTypeReference (pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("IJavaObject")); + var javaLangObjectRef = metadata.AddTypeReference (pe.MonoAndroidRef, + metadata.GetOrAddString ("Java.Lang"), metadata.GetOrAddString ("Object")); + var systemArrayRef = metadata.AddTypeReference (pe.SystemRuntimeRef, + metadata.GetOrAddString ("System"), metadata.GetOrAddString ("Array")); + var systemStreamRef = metadata.AddTypeReference (pe.SystemRuntimeRef, + metadata.GetOrAddString ("System.IO"), metadata.GetOrAddString ("Stream")); + var systemXmlRef = pe.FindOrAddAssemblyRef ("System.Xml.ReaderWriter"); + var systemXmlReaderRef = metadata.AddTypeReference (systemXmlRef, + metadata.GetOrAddString ("System.Xml"), metadata.GetOrAddString ("XmlReader")); + var inputStreamInvokerRef = metadata.AddTypeReference (pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("InputStreamInvoker")); + var outputStreamInvokerRef = metadata.AddTypeReference (pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("OutputStreamInvoker")); + var inputStreamAdapterRef = metadata.AddTypeReference (pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("InputStreamAdapter")); + var outputStreamAdapterRef = metadata.AddTypeReference (pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("OutputStreamAdapter")); + var xmlPullParserReaderRef = metadata.AddTypeReference (pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("XmlPullParserReader")); + var xmlResourceParserReaderRef = metadata.AddTypeReference (pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("XmlResourceParserReader")); + var xmlReaderPullParserRef = metadata.AddTypeReference (pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("XmlReaderPullParser")); + var xmlReaderResourceParserRef = metadata.AddTypeReference (pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("XmlReaderResourceParser")); + var charSequenceRef = metadata.AddTypeReference (pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("CharSequence")); + var iCharSequenceRef = metadata.AddTypeReference (pe.MonoAndroidRef, + metadata.GetOrAddString ("Java.Lang"), metadata.GetOrAddString ("ICharSequence")); + var javaListRef = metadata.AddTypeReference (pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("JavaList")); + var javaDictionaryRef = metadata.AddTypeReference (pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("JavaDictionary")); + var javaCollectionRef = metadata.AddTypeReference (pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("JavaCollection")); + var systemCollectionsIListRef = metadata.AddTypeReference (pe.SystemRuntimeRef, + metadata.GetOrAddString ("System.Collections"), metadata.GetOrAddString ("IList")); + var systemCollectionsIDictionaryRef = metadata.AddTypeReference (pe.SystemRuntimeRef, + metadata.GetOrAddString ("System.Collections"), metadata.GetOrAddString ("IDictionary")); + var systemCollectionsICollectionRef = metadata.AddTypeReference (pe.SystemRuntimeRef, + metadata.GetOrAddString ("System.Collections"), metadata.GetOrAddString ("ICollection")); + + return new ExportMethodDispatchEmitterContext { + IJavaObjectRef = iJavaObjectRef, + GetTypeFromHandleRef = getTypeFromHandleRef, + JniEnvGetStringRef = pe.AddMemberRef (jniEnvRef, "GetString", + sig => sig.MethodSignature ().Parameters (2, + rt => rt.Type ().String (), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (jniHandleOwnershipRef, true); + })), + JniEnvGetArrayRef = pe.AddMemberRef (jniEnvRef, "GetArray", + sig => sig.MethodSignature ().Parameters (3, + rt => rt.Type ().Type (systemArrayRef, false), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (jniHandleOwnershipRef, true); + p.AddParameter ().Type ().Type (systemTypeRef, false); + })), + JniEnvCopyArrayRef = pe.AddMemberRef (jniEnvRef, "CopyArray", + sig => sig.MethodSignature ().Parameters (3, + rt => rt.Void (), + p => { + p.AddParameter ().Type ().Type (systemArrayRef, false); + p.AddParameter ().Type ().Type (systemTypeRef, false); + p.AddParameter ().Type ().IntPtr (); + })), + JniEnvNewArrayRef = pe.AddMemberRef (jniEnvRef, "NewArray", + sig => sig.MethodSignature ().Parameters (2, + rt => rt.Type ().IntPtr (), + p => { + p.AddParameter ().Type ().Type (systemArrayRef, false); + p.AddParameter ().Type ().Type (systemTypeRef, false); + })), + JniEnvNewStringRef = pe.AddMemberRef (jniEnvRef, "NewString", + sig => sig.MethodSignature ().Parameters (1, + rt => rt.Type ().IntPtr (), + p => p.AddParameter ().Type ().String ())), + JniEnvToLocalJniHandleRef = pe.AddMemberRef (jniEnvRef, "ToLocalJniHandle", + sig => sig.MethodSignature ().Parameters (1, + rt => rt.Type ().IntPtr (), + p => p.AddParameter ().Type ().Type (iJavaObjectRef, false))), + JavaLangObjectGetObjectRef = pe.AddMemberRef (javaLangObjectRef, "GetObject", + sig => sig.MethodSignature ().Parameters (3, + rt => rt.Type ().Type (iJavaPeerableRef, false), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (jniHandleOwnershipRef, true); + p.AddParameter ().Type ().Type (systemTypeRef, false); + })), + InputStreamInvokerFromJniHandleRef = pe.AddMemberRef (inputStreamInvokerRef, "FromJniHandle", + sig => sig.MethodSignature ().Parameters (2, + rt => rt.Type ().Type (systemStreamRef, false), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (jniHandleOwnershipRef, true); + })), + OutputStreamInvokerFromJniHandleRef = pe.AddMemberRef (outputStreamInvokerRef, "FromJniHandle", + sig => sig.MethodSignature ().Parameters (2, + rt => rt.Type ().Type (systemStreamRef, false), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (jniHandleOwnershipRef, true); + })), + InputStreamAdapterToLocalJniHandleRef = pe.AddMemberRef (inputStreamAdapterRef, "ToLocalJniHandle", + sig => sig.MethodSignature ().Parameters (1, + rt => rt.Type ().IntPtr (), + p => p.AddParameter ().Type ().Type (systemStreamRef, false))), + OutputStreamAdapterToLocalJniHandleRef = pe.AddMemberRef (outputStreamAdapterRef, "ToLocalJniHandle", + sig => sig.MethodSignature ().Parameters (1, + rt => rt.Type ().IntPtr (), + p => p.AddParameter ().Type ().Type (systemStreamRef, false))), + XmlPullParserReaderFromJniHandleRef = pe.AddMemberRef (xmlPullParserReaderRef, "FromJniHandle", + sig => sig.MethodSignature ().Parameters (2, + rt => rt.Type ().Type (systemXmlReaderRef, false), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (jniHandleOwnershipRef, true); + })), + XmlResourceParserReaderFromJniHandleRef = pe.AddMemberRef (xmlResourceParserReaderRef, "FromJniHandle", + sig => sig.MethodSignature ().Parameters (2, + rt => rt.Type ().Type (systemXmlReaderRef, false), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (jniHandleOwnershipRef, true); + })), + XmlReaderPullParserToLocalJniHandleRef = pe.AddMemberRef (xmlReaderPullParserRef, "ToLocalJniHandle", + sig => sig.MethodSignature ().Parameters (1, + rt => rt.Type ().IntPtr (), + p => p.AddParameter ().Type ().Type (systemXmlReaderRef, false))), + XmlReaderResourceParserToLocalJniHandleRef = pe.AddMemberRef (xmlReaderResourceParserRef, "ToLocalJniHandle", + sig => sig.MethodSignature ().Parameters (1, + rt => rt.Type ().IntPtr (), + p => p.AddParameter ().Type ().Type (systemXmlReaderRef, false))), + CharSequenceToLocalJniHandleRef = pe.AddMemberRef (charSequenceRef, "ToLocalJniHandle", + sig => sig.MethodSignature ().Parameters (1, + rt => rt.Type ().IntPtr (), + p => p.AddParameter ().Type ().Type (iCharSequenceRef, false))), + JavaListToLocalJniHandleRef = pe.AddMemberRef (javaListRef, "ToLocalJniHandle", + sig => sig.MethodSignature ().Parameters (1, + rt => rt.Type ().IntPtr (), + p => p.AddParameter ().Type ().Type (systemCollectionsIListRef, false))), + JavaDictionaryToLocalJniHandleRef = pe.AddMemberRef (javaDictionaryRef, "ToLocalJniHandle", + sig => sig.MethodSignature ().Parameters (1, + rt => rt.Type ().IntPtr (), + p => p.AddParameter ().Type ().Type (systemCollectionsIDictionaryRef, false))), + JavaCollectionToLocalJniHandleRef = pe.AddMemberRef (javaCollectionRef, "ToLocalJniHandle", + sig => sig.MethodSignature ().Parameters (1, + rt => rt.Type ().IntPtr (), + p => p.AddParameter ().Type ().Type (systemCollectionsICollectionRef, false))), + UcoAttrCtorRef = ucoAttrCtorRef, + UcoAttrBlobHandle = ucoAttrBlobHandle, + JniTransitionRef = jniTransitionRef, + JniRuntimeRef = jniRuntimeRef, + ExceptionRef = exceptionRef, + BeginMarshalMethodRef = beginMarshalMethodRef, + EndMarshalMethodRef = endMarshalMethodRef, + OnUserUnhandledExceptionRef = onUserUnhandledExceptionRef, + }; + } + + public required TypeReferenceHandle IJavaObjectRef { get; init; } + public required MemberReferenceHandle GetTypeFromHandleRef { get; init; } + public required MemberReferenceHandle JniEnvGetStringRef { get; init; } + public required MemberReferenceHandle JniEnvGetArrayRef { get; init; } + public required MemberReferenceHandle JniEnvCopyArrayRef { get; init; } + public required MemberReferenceHandle JniEnvNewArrayRef { get; init; } + public required MemberReferenceHandle JniEnvNewStringRef { get; init; } + public required MemberReferenceHandle JniEnvToLocalJniHandleRef { get; init; } + public required MemberReferenceHandle JavaLangObjectGetObjectRef { get; init; } + public required MemberReferenceHandle InputStreamInvokerFromJniHandleRef { get; init; } + public required MemberReferenceHandle OutputStreamInvokerFromJniHandleRef { get; init; } + public required MemberReferenceHandle InputStreamAdapterToLocalJniHandleRef { get; init; } + public required MemberReferenceHandle OutputStreamAdapterToLocalJniHandleRef { get; init; } + public required MemberReferenceHandle XmlPullParserReaderFromJniHandleRef { get; init; } + public required MemberReferenceHandle XmlResourceParserReaderFromJniHandleRef { get; init; } + public required MemberReferenceHandle XmlReaderPullParserToLocalJniHandleRef { get; init; } + public required MemberReferenceHandle XmlReaderResourceParserToLocalJniHandleRef { get; init; } + public required MemberReferenceHandle CharSequenceToLocalJniHandleRef { get; init; } + public required MemberReferenceHandle JavaListToLocalJniHandleRef { get; init; } + public required MemberReferenceHandle JavaDictionaryToLocalJniHandleRef { get; init; } + public required MemberReferenceHandle JavaCollectionToLocalJniHandleRef { get; init; } + public required MemberReferenceHandle UcoAttrCtorRef { get; init; } + + public required BlobHandle UcoAttrBlobHandle { get; init; } + + // Marshal-method wrapper plumbing — mirrors the UCO ctor wrapper used by + // TypeMapAssemblyEmitter so that managed exceptions thrown from [Export] method + // bodies surface as Java exceptions instead of crashing the runtime. + public required TypeReferenceHandle JniTransitionRef { get; init; } + public required TypeReferenceHandle JniRuntimeRef { get; init; } + public required TypeReferenceHandle ExceptionRef { get; init; } + public required MemberReferenceHandle BeginMarshalMethodRef { get; init; } + public required MemberReferenceHandle EndMarshalMethodRef { get; init; } + public required MemberReferenceHandle OnUserUnhandledExceptionRef { get; init; } +} diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JcwJavaSourceGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JcwJavaSourceGenerator.cs index 0d2b15f803c..517d04d0364 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JcwJavaSourceGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JcwJavaSourceGenerator.cs @@ -249,7 +249,7 @@ static void WriteMethods (JavaPeerInfo type, TextWriter writer) throwsClause = $"\n\t\tthrows {string.Join (", ", method.ThrownNames)}"; } - if (method.Connector != null) { + if (method.Connector != null && !method.IsExport) { writer.Write ($$""" @Override @@ -262,13 +262,14 @@ static void WriteMethods (JavaPeerInfo type, TextWriter writer) """); } else { string access = method.IsExport && method.JavaAccess != null ? method.JavaAccess : "public"; + string staticKeyword = method.IsStatic ? "static " : ""; writer.Write ($$""" - {{access}} {{javaReturnType}} {{method.JniName}} ({{parameters}}){{throwsClause}} + {{access}} {{staticKeyword}}{{javaReturnType}} {{method.JniName}} ({{parameters}}){{throwsClause}} { {{registerNativesLine}} {{returnPrefix}}{{method.NativeCallbackName}} ({{args}}); } - {{access}} native {{javaReturnType}} {{method.NativeCallbackName}} ({{parameters}}); + {{access}} {{staticKeyword}}native {{javaReturnType}} {{method.NativeCallbackName}} ({{parameters}}); """); } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs index c9b620ff18f..afa9ea3c3d9 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs @@ -109,14 +109,15 @@ static JniParamKind ParseSingleType (string sig, ref int i) /// Encodes a JNI type as its CLR equivalent for [UnmanagedCallersOnly] UCO wrapper signatures. /// /// - /// JNI boolean (Z) maps to byte (unsigned, blittable for the JNI ABI). + /// JNI boolean (Z) maps to byte and JNI char (C) maps to ushort, + /// preserving the JNI ABI with blittable UCO parameter types. /// public static void EncodeClrType (SignatureTypeEncoder encoder, JniParamKind kind) { switch (kind) { case JniParamKind.Boolean: encoder.Byte (); break; // JNI jboolean is unsigned byte; blittable for UCO case JniParamKind.Byte: encoder.SByte (); break; - case JniParamKind.Char: encoder.Char (); break; + case JniParamKind.Char: encoder.UInt16 (); break; case JniParamKind.Short: encoder.Int16 (); break; case JniParamKind.Int: encoder.Int32 (); break; case JniParamKind.Long: encoder.Int64 (); break; diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs index e83e7120ff6..4220238e44f 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs @@ -148,7 +148,7 @@ XDocument CreateDefaultManifest () /// /// Manifest templates may use compat JNI names (e.g., "android.apptests.App") - /// but the trimmable path generates JCWs with CRC-based names (e.g., "crc64.../App"). + /// but the trimmable path generates JCWs with hashed package names (e.g., "crc64.../App"). /// This method rewrites any compat name references to the actual JCW name so the /// Android runtime can find the class. /// diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs index b9126586bf4..9038aba56f8 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs @@ -142,6 +142,13 @@ sealed class JavaPeerProxyData /// public bool IsGenericDefinition { get; init; } + /// + /// True when the Java stub must not call RegisterNatives from a static initializer because + /// the type can be instantiated before the runtime is fully ready (for example Application + /// or Instrumentation subclasses). + /// + public bool CannotRegisterInStaticConstructor { get; init; } + /// /// Whether this proxy needs ACW support (RegisterNatives + UCO wrappers + IAndroidCallableWrapper). /// @@ -177,11 +184,19 @@ sealed record TypeRefData /// Assembly containing the type, e.g., "Mono.Android". /// public required string AssemblyName { get; init; } + + /// + /// True if this type — or, for array types, the element type — is an enum. + /// Used by the IL emitter to encode the type as ELEMENT_TYPE_VALUETYPE + /// rather than ELEMENT_TYPE_CLASS in member references and signatures. + /// + public bool IsEnum { get; init; } } /// /// An [UnmanagedCallersOnly] static wrapper for a marshal method. -/// Body: load all args → call n_* callback → ret. +/// Body: either forward to an existing n_* callback or dispatch directly to the +/// managed export target when the trimmable path can avoid dynamic callback generation. /// sealed record UcoMethodData { @@ -191,7 +206,7 @@ sealed record UcoMethodData public required string WrapperName { get; init; } /// - /// Name of the n_* callback to call, e.g., "n_OnCreate". + /// Java/JNI-visible native method name, e.g., "n_OnCreate". /// public required string CallbackMethodName { get; init; } @@ -204,6 +219,53 @@ sealed record UcoMethodData /// JNI method signature, e.g., "(Landroid/os/Bundle;)V". Used to determine CLR parameter types. /// public required string JniSignature { get; init; } + + /// + /// Optional [Export]-only metadata for wrappers that dispatch directly to the + /// managed export target instead of forwarding to a generated n_* callback. + /// + public ExportMethodDispatchData? ExportMethodDispatch { get; init; } + + /// + /// True when this wrapper performs the static [Export] direct-dispatch path. + /// + public bool UsesExportMethodDispatch => ExportMethodDispatch != null; +} + +sealed record ExportMethodDispatchData +{ + /// + /// Managed method name on the callback type that should be invoked for [Export]. + /// + public required string ManagedMethodName { get; init; } + + /// + /// Managed parameter types for the target method, including the defining assembly. + /// + public IReadOnlyList ParameterTypes { get; init; } = []; + + /// + /// Per-parameter [ExportParameter] kinds for legacy callback marshalling. + /// + public IReadOnlyList ParameterKinds { get; init; } = []; + + /// + /// Managed return type for the target method, including the defining assembly. + /// + public TypeRefData ReturnType { get; init; } = new () { + ManagedTypeName = "System.Void", + AssemblyName = "System.Runtime", + }; + + /// + /// [ExportParameter] kind applied to the return value, if any. + /// + public ExportParameterKindInfo ReturnKind { get; init; } + + /// + /// Whether the managed target method is static. + /// + public bool IsStatic { get; init; } } /// @@ -228,6 +290,24 @@ sealed record UcoConstructorData /// JNI constructor signature, e.g., "(Landroid/content/Context;)V". Used for RegisterNatives registration. /// public required string JniSignature { get; init; } + + /// + /// when the UCO codegen can statically prove the managed + /// type defines a matching user-visible ctor with this signature. When + /// , the codegen must use the legacy activation-ctor + /// `(IntPtr, JniHandleOwnership)` path instead of emitting a member ref to + /// a (potentially non-existent) user ctor. + /// + public required bool HasMatchingManagedCtor { get; init; } + + /// + /// Managed parameter types of the matching user-visible ctor, in declaration + /// order. Empty for `()V`. Non-empty when + /// is and the ctor takes parameters; the emitter uses + /// this to build the member ref signature and to marshal each JNI argument + /// to the corresponding managed type before calling the user ctor. + /// + public IReadOnlyList ManagedParameterTypes { get; init; } = []; } /// diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs index 7547c5ac38f..86ff851d804 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs @@ -16,13 +16,6 @@ static class ModelBuilder { const string ProxyTypeSuffix = "_Proxy"; - // Workaround for https://github.com/dotnet/runtime/issues/127004 - // When true, all TypeMap entries are emitted as 2-arg (unconditional) to avoid the - // trimmer bug that strips TypeMapAssociation attributes when a TypeMap attribute - // references the same type. Set to false once the runtime bug is fixed to re-enable - // 3-arg conditional entries that allow unused framework bindings to be trimmed away. - const bool ForceUnconditionalEntries = true; - static readonly HashSet EssentialRuntimeTypes = new (StringComparer.Ordinal) { "java/lang/Object", "java/lang/Class", @@ -32,6 +25,9 @@ static class ModelBuilder "java/lang/RuntimeException", "java/lang/Error", "java/lang/Thread", + // Queried during NativeAOT JavaInteropRuntime.init before user code can + // reference the managed interface, so the managed→JNI mapping must survive. + "java/lang/Thread$UncaughtExceptionHandler", }; /// @@ -189,13 +185,7 @@ static void EmitPeers (TypeMapAssemblyData model, string jniName, } // Base JNI name entry → alias holder (self-referencing trim target, kept alive by associations) - // When ForceUnconditionalEntries is true we MUST emit this as 2-arg (unconditional) just - // like BuildEntry does: dotnet/runtime#127004 strips the TypeMapAssociation that keeps the - // holder alive when a TypeMap entry references the same type, leaving the dictionary key - // missing at runtime and breaking hierarchy lookups for essential types like - // java/lang/String and java/lang/Object. - bool aliasBaseUnconditional = ForceUnconditionalEntries - || EssentialRuntimeTypes.Contains (jniName) + bool aliasBaseUnconditional = EssentialRuntimeTypes.Contains (jniName) || peersForName.Any (IsUnconditionalEntry); model.Entries.Add (new TypeMapAttributeData { JniName = jniName, @@ -239,7 +229,7 @@ static bool IsUnconditionalEntry (JavaPeerInfo peer) // User-defined ACW types (not MCW bindings, not interfaces) are unconditional // because Android can instantiate them from Java at any time. - if (!peer.DoNotGenerateAcw && !peer.IsInterface) { + if (!peer.IsFrameworkAssembly && !peer.DoNotGenerateAcw && !peer.IsInterface) { return true; } @@ -298,6 +288,7 @@ static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer, string jniName, Hash }, IsAcw = isAcw, IsGenericDefinition = peer.IsGenericDefinition, + CannotRegisterInStaticConstructor = peer.CannotRegisterInStaticConstructor, }; if (peer.InvokerTypeName != null) { @@ -346,6 +337,14 @@ static void BuildUcoMethods (JavaPeerInfo peer, JavaPeerProxyData proxy) AssemblyName = !mm.DeclaringAssemblyName.IsNullOrEmpty () ? mm.DeclaringAssemblyName : peer.AssemblyName, }, JniSignature = mm.JniSignature, + ExportMethodDispatch = mm.IsExport ? new ExportMethodDispatchData { + ManagedMethodName = mm.ManagedMethodName, + ParameterTypes = mm.ManagedParameterTypes, + ParameterKinds = mm.ManagedParameterExportKinds, + ReturnType = mm.ManagedReturnType, + ReturnKind = mm.ManagedReturnExportKind, + IsStatic = mm.IsStatic, + } : null, }); ucoIndex++; } @@ -357,7 +356,18 @@ static void BuildUcoConstructors (JavaPeerInfo peer, JavaPeerProxyData proxy) return; } + // Abstract types are never directly instantiated from Java — the ACW + // constructor's getClass() guard prevents activation. Skip generating + // UCO constructor wrappers for them. + if (peer.IsAbstract) { + return; + } + foreach (var ctor in peer.JavaConstructors) { + if (ctor.SuperArgumentsString != null && !ctor.HasMatchingManagedCtor) { + throw new InvalidOperationException ( + $"Trimmable typemap cannot generate Java constructor wrapper '{ctor.JniSignature}' for '{peer.ManagedTypeName}' because no matching user-visible managed constructor was found."); + } proxy.UcoConstructors.Add (new UcoConstructorData { WrapperName = $"nctor_{ctor.ConstructorIndex}_uco", JniSignature = ctor.JniSignature, @@ -365,6 +375,8 @@ static void BuildUcoConstructors (JavaPeerInfo peer, JavaPeerProxyData proxy) ManagedTypeName = peer.ManagedTypeName, AssemblyName = peer.AssemblyName, }, + ManagedParameterTypes = ctor.ManagedParameterTypes, + HasMatchingManagedCtor = ctor.HasMatchingManagedCtor, }); } } @@ -404,9 +416,7 @@ static TypeMapAttributeData BuildEntry (JavaPeerInfo peer, JavaPeerProxyData? pr proxyRef = AssemblyQualify (peer.ManagedTypeName, peer.AssemblyName); } - // When ForceUnconditionalEntries is true, always emit 2-arg (unconditional) TypeMap - // attributes to work around https://github.com/dotnet/runtime/issues/127004. - bool isUnconditional = ForceUnconditionalEntries || IsUnconditionalEntry (peer); + bool isUnconditional = IsUnconditionalEntry (peer); string? targetRef = null; if (!isUnconditional) { targetRef = AssemblyQualify (peer.ManagedTypeName, peer.AssemblyName); @@ -438,6 +448,9 @@ static void EmitArrayEntries (TypeMapAssemblyData model, string jniName, List sealed class PEAssemblyBuilder { - const int DefaultMaxStack = 32; + const int MinimumMaxStack = 8; + const int MaxStackSafetyPadding = 4; // Mono.Android strong name public key token (84e04ff9cfb79065) static readonly byte [] MonoAndroidPublicKeyToken = { 0x84, 0xe0, 0x4f, 0xf9, 0xcf, 0xb7, 0x90, 0x65 }; @@ -260,7 +261,7 @@ TypeDefinitionHandle GetOrCreateSizedType (int size) /// Emits a method body and definition in one call. /// public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, - Action encodeSig, Action emitIL) + Action encodeSig, Action emitIL) => EmitBody (name, attrs, encodeSig, emitIL, encodeLocals: null, useBranches: false); /// @@ -277,12 +278,12 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, /// and . /// public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, - Action encodeSig, Action emitIL, + Action encodeSig, Action emitIL, Action? encodeLocals) => EmitBody (name, attrs, encodeSig, emitIL, encodeLocals, useBranches: false); public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, - Action encodeSig, Action emitIL, + Action encodeSig, Action emitIL, Action? encodeLocals, bool useBranches) { _sigBlob.Clear (); @@ -300,16 +301,16 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, _codeBlob.Clear (); ControlFlowBuilder? cfb = useBranches ? new ControlFlowBuilder () : null; - var encoder = new InstructionEncoder (_codeBlob, cfb); + var encoder = new TrackedInstructionEncoder (new InstructionEncoder (_codeBlob, cfb)); emitIL (encoder); while (ILBuilder.Count % 4 != 0) { ILBuilder.WriteByte (0); } var bodyEncoder = new MethodBodyStreamEncoder (ILBuilder); - int bodyOffset = localSigHandle.IsNil - ? bodyEncoder.AddMethodBody (encoder) - : bodyEncoder.AddMethodBody (encoder, maxStack: DefaultMaxStack, localSigHandle, MethodBodyAttributes.InitLocals); + int bodyOffset = bodyEncoder.AddMethodBody (encoder.Encoder, encoder.MaxStackWithPadding, localSigHandle, + localSigHandle.IsNil ? default : MethodBodyAttributes.InitLocals, + encoder.HasDynamicStackAllocation); return Metadata.AddMethodDefinition ( attrs, MethodImplAttributes.IL, @@ -327,7 +328,7 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, /// public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, Action encodeSig, - Action emitIL, + Action emitIL, Action? encodeLocals) { _sigBlob.Clear (); @@ -345,16 +346,16 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, _codeBlob.Clear (); var cfb = new ControlFlowBuilder (); - var encoder = new InstructionEncoder (_codeBlob, cfb); + var encoder = new TrackedInstructionEncoder (new InstructionEncoder (_codeBlob, cfb)); emitIL (encoder, cfb); while (ILBuilder.Count % 4 != 0) { ILBuilder.WriteByte (0); } var bodyEncoder = new MethodBodyStreamEncoder (ILBuilder); - int bodyOffset = localSigHandle.IsNil - ? bodyEncoder.AddMethodBody (encoder) - : bodyEncoder.AddMethodBody (encoder, maxStack: DefaultMaxStack, localSigHandle, MethodBodyAttributes.InitLocals); + int bodyOffset = bodyEncoder.AddMethodBody (encoder.Encoder, encoder.MaxStackWithPadding, localSigHandle, + localSigHandle.IsNil ? default : MethodBodyAttributes.InitLocals, + encoder.HasDynamicStackAllocation); return Metadata.AddMethodDefinition ( attrs, MethodImplAttributes.IL, @@ -415,8 +416,8 @@ public void EmitIgnoresAccessChecksToAttribute (List assemblyNames) p => p.AddParameter ().Type ().String ()), encoder => { encoder.LoadArgument (0); - encoder.Call (baseAttrCtorRef); - encoder.OpCode (ILOpCode.Ret); + encoder.Call (baseAttrCtorRef, parameterCount: 0, isInstance: true); + encoder.Return (); }); Metadata.AddTypeDefinition ( @@ -432,4 +433,280 @@ public void EmitIgnoresAccessChecksToAttribute (List assemblyNames) Metadata.AddCustomAttribute (EntityHandle.AssemblyDefinition, ctorDef, blob); } } + + public sealed class TrackedInstructionEncoder + { + int currentStack; + int maxStack; + + public InstructionEncoder Encoder { get; } + public bool HasDynamicStackAllocation { get; private set; } + + public int MaxStackWithPadding { + get { + long padded = (long) maxStack + MaxStackSafetyPadding; + padded = Math.Max (MinimumMaxStack, padded); + return padded > ushort.MaxValue ? ushort.MaxValue : (int) padded; + } + } + + public TrackedInstructionEncoder (InstructionEncoder encoder) + { + Encoder = encoder; + } + + public LabelHandle DefineLabel () => Encoder.DefineLabel (); + + public void MarkLabel (LabelHandle label, int stackDepth = -1) + { + Encoder.MarkLabel (label); + if (stackDepth >= 0) { + SetStack (stackDepth); + } + } + + public void Branch (ILOpCode code, LabelHandle label) + { + switch (code) { + case ILOpCode.Brfalse: + case ILOpCode.Brfalse_s: + case ILOpCode.Brtrue: + case ILOpCode.Brtrue_s: + Encoder.Branch (code, label); + Pop (1); + break; + case ILOpCode.Leave: + case ILOpCode.Leave_s: + Encoder.Branch (code, label); + SetStack (0); + break; + case ILOpCode.Br: + case ILOpCode.Br_s: + throw new NotSupportedException ($"Branch opcode '{code}' preserves the evaluation stack and is not supported by the maxstack tracker."); + default: + throw new NotSupportedException ($"Branch opcode '{code}' is not supported by the maxstack tracker."); + } + } + + public void BranchPreservingStack (ILOpCode code, LabelHandle label) + { + switch (code) { + case ILOpCode.Br: + case ILOpCode.Br_s: + // Unconditional branches preserve the current stack; Branch() rejects + // them because most callers need stack-depth tracking to change. + Encoder.Branch (code, label); + break; + default: + throw new NotSupportedException ($"Branch opcode '{code}' does not preserve the evaluation stack."); + } + } + + public void LoadArgument (int argumentIndex) + { + Encoder.LoadArgument (argumentIndex); + Push (1); + } + + public void LoadLocal (int slotIndex) + { + Encoder.LoadLocal (slotIndex); + Push (1); + } + + public void LoadLocalAddress (int slotIndex) + { + Encoder.LoadLocalAddress (slotIndex); + Push (1); + } + + public void StoreLocal (int slotIndex) + { + Encoder.StoreLocal (slotIndex); + Pop (1); + } + + public void LoadConstantI4 (int value) + { + Encoder.LoadConstantI4 (value); + Push (1); + } + + public void LoadString (UserStringHandle handle) + { + Encoder.LoadString (handle); + Push (1); + } + + public void LoadToken (EntityHandle handle) + { + Encoder.OpCode (ILOpCode.Ldtoken); + Encoder.Token (handle); + Push (1); + } + + public void LoadStaticFieldAddress (FieldDefinitionHandle handle) + { + Encoder.OpCode (ILOpCode.Ldsflda); + Encoder.Token (handle); + Push (1); + } + + public void LoadFunction (MethodDefinitionHandle handle) + { + Encoder.OpCode (ILOpCode.Ldftn); + Encoder.Token (handle); + Push (1); + } + + public void SizeOf (EntityHandle type) + { + Encoder.OpCode (ILOpCode.Sizeof); + Encoder.Token (type); + Push (1); + } + + public void CastClass (EntityHandle type) + { + Encoder.OpCode (ILOpCode.Castclass); + Encoder.Token (type); + } + + public void NewArray (EntityHandle type) + { + Encoder.OpCode (ILOpCode.Newarr); + Encoder.Token (type); + Pop (1); + Push (1); + } + + public void StoreObject (EntityHandle type) + { + Encoder.OpCode (ILOpCode.Stobj); + Encoder.Token (type); + Pop (2); + } + + public void Call (EntityHandle method, int parameterCount, bool returnsValue = false, bool isInstance = false) + { + Encoder.OpCode (ILOpCode.Call); + Encoder.Token (method); + ApplyCallStackDelta (parameterCount, returnsValue, isInstance); + } + + public void Callvirt (EntityHandle method, int parameterCount, bool returnsValue = false) + { + Encoder.OpCode (ILOpCode.Callvirt); + Encoder.Token (method); + ApplyCallStackDelta (parameterCount, returnsValue, isInstance: true); + } + + public void NewObject (EntityHandle constructor, int parameterCount) + { + Encoder.OpCode (ILOpCode.Newobj); + Encoder.Token (constructor); + Pop (parameterCount); + Push (1); + } + + public void Return (bool returnsValue = false) + { + Encoder.OpCode (ILOpCode.Ret); + if (returnsValue) { + Pop (1); + } + SetStack (0); + } + + public void Throw () + { + Encoder.OpCode (ILOpCode.Throw); + Pop (1); + SetStack (0); + } + + public void PopValue () + { + Encoder.OpCode (ILOpCode.Pop); + Pop (1); + } + + public void OpCode (ILOpCode code) + { + Encoder.OpCode (code); + switch (code) { + case ILOpCode.Add: + case ILOpCode.Cgt_un: + case ILOpCode.Mul: + Pop (1); + break; + case ILOpCode.Conv_u1: + break; + case ILOpCode.Dup: + Push (1); + break; + case ILOpCode.Endfinally: + SetStack (0); + break; + case ILOpCode.Ldarg_0: + case ILOpCode.Ldarg_1: + case ILOpCode.Ldarg_2: + case ILOpCode.Ldloc_0: + case ILOpCode.Ldloc_1: + case ILOpCode.Ldnull: + Push (1); + break; + case ILOpCode.Localloc: + HasDynamicStackAllocation = true; + Pop (1); + Push (1); + break; + case ILOpCode.Pop: + case ILOpCode.Stloc_0: + case ILOpCode.Stloc_1: + Pop (1); + break; + case ILOpCode.Stelem_ref: + Pop (3); + break; + default: + throw new NotSupportedException ($"Opcode '{code}' is not supported by the maxstack tracker. Use an explicit tracked helper."); + } + } + + void ApplyCallStackDelta (int parameterCount, bool returnsValue, bool isInstance) + { + Pop (parameterCount + (isInstance ? 1 : 0)); + if (returnsValue) { + Push (1); + } + } + + void Push (int count) + { + if (count <= 0) { + return; + } + SetStack (currentStack + count); + } + + void Pop (int count) + { + if (count <= 0) { + return; + } + if (currentStack < count) { + throw new InvalidOperationException ($"IL evaluation stack underflow while computing maxstack. Current depth is {currentStack}, pop count is {count}."); + } + SetStack (currentStack - count); + } + + void SetStack (int depth) + { + currentStack = depth; + if (currentStack > maxStack) { + maxStack = currentStack; + } + } + } } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs index 5d5f7434d49..f9a688ebdc9 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs @@ -5,6 +5,8 @@ using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; +using TrackedInstructionEncoder = Microsoft.Android.Sdk.TrimmableTypeMap.PEAssemblyBuilder.TrackedInstructionEncoder; + namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// @@ -238,14 +240,25 @@ static void EmitTypeMapLoader (PEAssemblyBuilder pe, EntityHandle anchorTypeHand var externalDictArrayTypeSpec = MakeIReadOnlyDictArrayTypeSpec (pe, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true); if (useSharedTypemapUniverse) { - var initializeRef = AddInitializeSingleWithArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); - EmitInitializeWithSingleTypeMap (pe, anchorTypeHandle, getExternalMemberRef, getProxyMemberRef, - initializeRef, externalDictTypeSpec, externalDictArrayTypeSpec, perAssemblyTypeMapNames, maxArrayRank); + if (maxArrayRank > 0) { + var initializeRef = AddInitializeSingleWithArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); + EmitInitializeWithSingleTypeMap (pe, anchorTypeHandle, getExternalMemberRef, getProxyMemberRef, + initializeRef, externalDictTypeSpec, externalDictArrayTypeSpec, perAssemblyTypeMapNames, maxArrayRank); + } else { + var initializeRef = AddInitializeSingleNoArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); + EmitInitializeWithSingleTypeMapNoArrays (pe, anchorTypeHandle, getExternalMemberRef, getProxyMemberRef, initializeRef); + } } else { - var initializeRef = AddInitializeAggregateWithArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); var proxyDictTypeSpec = MakeIReadOnlyDictTypeSpec (pe, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false); - EmitInitializeWithAggregateTypeMap (pe, perAssemblyTypeMapNames, getExternalMemberRef, getProxyMemberRef, - initializeRef, externalDictTypeSpec, proxyDictTypeSpec, externalDictArrayTypeSpec, iReadOnlyDictOpenRef, systemTypeRef, maxArrayRank); + if (maxArrayRank > 0) { + var initializeRef = AddInitializeAggregateWithArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); + EmitInitializeWithAggregateTypeMap (pe, perAssemblyTypeMapNames, getExternalMemberRef, getProxyMemberRef, + initializeRef, externalDictTypeSpec, proxyDictTypeSpec, externalDictArrayTypeSpec, iReadOnlyDictOpenRef, systemTypeRef, maxArrayRank); + } else { + var initializeRef = AddInitializeAggregateNoArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); + EmitInitializeWithAggregateTypeMapNoArrays (pe, perAssemblyTypeMapNames, getExternalMemberRef, getProxyMemberRef, + initializeRef, externalDictTypeSpec, proxyDictTypeSpec, iReadOnlyDictOpenRef, systemTypeRef); + } } } @@ -291,9 +304,8 @@ static void EmitInitializeWithAggregateTypeMap (PEAssemblyBuilder pe, encoder.LoadLocal (0); encoder.LoadLocal (1); EmitArrayMapsByAssemblyAndRankOrNull (pe, encoder, perAssemblyTypeMapNames, getExternalMemberRef, externalDictTypeSpec, externalDictArrayTypeSpec, maxArrayRank); - encoder.OpCode (ILOpCode.Call); - encoder.Token (initializeRef); - encoder.OpCode (ILOpCode.Ret); + encoder.Call (initializeRef, parameterCount: 3); + encoder.Return (); }, encodeLocals: localsSig => { localsSig.WriteByte (0x07); // LOCAL_SIG @@ -307,25 +319,87 @@ static void EmitInitializeWithAggregateTypeMap (PEAssemblyBuilder pe, }); } - static void EmitNewArrayLocal (InstructionEncoder encoder, int count, TypeSpecificationHandle elemSpec, int slot) + static void EmitNewArrayLocal (TrackedInstructionEncoder encoder, int count, TypeSpecificationHandle elemSpec, int slot) { encoder.LoadConstantI4 (count); - encoder.OpCode (ILOpCode.Newarr); - encoder.Token (elemSpec); + encoder.NewArray (elemSpec); encoder.StoreLocal (slot); } - static void EmitFillArrayLocal (InstructionEncoder encoder, int count, EntityHandle[] specs, int slot) + static void EmitFillArrayLocal (TrackedInstructionEncoder encoder, int count, EntityHandle[] specs, int slot) { for (int i = 0; i < count; i++) { encoder.LoadLocal (slot); encoder.LoadConstantI4 (i); - encoder.OpCode (ILOpCode.Call); - encoder.Token (specs [i]); + encoder.Call (specs [i], parameterCount: 0, returnsValue: true); encoder.OpCode (ILOpCode.Stelem_ref); } } + /// + /// Aggregate IL emit without array maps. Calls the 2-arg overload: + /// TrimmableTypeMap.Initialize(typeMaps[], proxyMaps[]). + /// + static void EmitInitializeWithAggregateTypeMapNoArrays (PEAssemblyBuilder pe, + IReadOnlyList perAssemblyTypeMapNames, + MemberReferenceHandle getExternalMemberRef, MemberReferenceHandle getProxyMemberRef, + MemberReferenceHandle initializeRef, + TypeSpecificationHandle externalDictTypeSpec, TypeSpecificationHandle proxyDictTypeSpec, + TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef) + { + var count = perAssemblyTypeMapNames.Count; + + var getExternalSpecs = new EntityHandle [count]; + var getProxySpecs = new EntityHandle [count]; + for (int i = 0; i < count; i++) { + var asmRef = pe.FindOrAddAssemblyRef (perAssemblyTypeMapNames [i]); + var perAsmAnchorRef = pe.Metadata.AddTypeReference (asmRef, + default, pe.Metadata.GetOrAddString ("__TypeMapAnchor")); + getExternalSpecs [i] = MakeGenericMethodSpec (pe, getExternalMemberRef, perAsmAnchorRef); + getProxySpecs [i] = MakeGenericMethodSpec (pe, getProxyMemberRef, perAsmAnchorRef); + } + + pe.EmitBody ("Initialize", + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, + sig => sig.MethodSignature ().Parameters (0, rt => rt.Void (), p => { }), + encoder => { + EmitNewArrayLocal (encoder, count, externalDictTypeSpec, slot: 0); + EmitFillArrayLocal (encoder, count, getExternalSpecs, slot: 0); + + EmitNewArrayLocal (encoder, count, proxyDictTypeSpec, slot: 1); + EmitFillArrayLocal (encoder, count, getProxySpecs, slot: 1); + + encoder.LoadLocal (0); + encoder.LoadLocal (1); + encoder.Call (initializeRef, parameterCount: 2); + encoder.Return (); + }, + encodeLocals: localsSig => { + localsSig.WriteByte (0x07); // LOCAL_SIG + localsSig.WriteCompressedInteger (2); + localsSig.WriteByte (0x1D); + EncodeIReadOnlyDictType (localsSig, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true); + localsSig.WriteByte (0x1D); + EncodeIReadOnlyDictType (localsSig, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false); + }); + } + + /// MemberRef for TrimmableTypeMap.Initialize(typeMaps[], proxyMaps[]) (2-arg, no array maps). + static MemberReferenceHandle AddInitializeAggregateNoArraysRef (PEAssemblyBuilder pe, TypeReferenceHandle trimmableTypeMapRef, + TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef) + { + var blob = new BlobBuilder (64); + blob.WriteByte (0x00); // DEFAULT (static) + blob.WriteCompressedInteger (2); // parameter count + blob.WriteByte (0x01); // return type: void + blob.WriteByte (0x1D); + EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true); + blob.WriteByte (0x1D); + EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false); + return pe.Metadata.AddMemberReference (trimmableTypeMapRef, + pe.Metadata.GetOrAddString ("Initialize"), pe.Metadata.GetOrAddBlob (blob)); + } + /// MemberRef for TrimmableTypeMap.Initialize(typeMaps[], proxyMaps[], arrayMapsByAssemblyAndRank[][]). static MemberReferenceHandle AddInitializeAggregateWithArraysRef (PEAssemblyBuilder pe, TypeReferenceHandle trimmableTypeMapRef, TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef) @@ -368,17 +442,50 @@ static void EmitInitializeWithSingleTypeMap (PEAssemblyBuilder pe, EntityHandle sig => sig.MethodSignature ().Parameters (0, rt => rt.Void (), p => { }), encoder => { // TrimmableTypeMap.Initialize(GetExternal(), GetProxy(), arrayMapsByAssemblyAndRank-or-null) - encoder.OpCode (ILOpCode.Call); - encoder.Token (getExternalSpec); - encoder.OpCode (ILOpCode.Call); - encoder.Token (getProxySpec); + encoder.Call (getExternalSpec, parameterCount: 0, returnsValue: true); + encoder.Call (getProxySpec, parameterCount: 0, returnsValue: true); EmitArrayMapsByAssemblyAndRankOrNull (pe, encoder, perAssemblyTypeMapNames, getExternalMemberRef, externalDictTypeSpec, externalDictArrayTypeSpec, maxArrayRank); - encoder.OpCode (ILOpCode.Call); - encoder.Token (initializeRef); - encoder.OpCode (ILOpCode.Ret); + encoder.Call (initializeRef, parameterCount: 3); + encoder.Return (); }); } + /// + /// Shared-universe IL emit without array maps. Calls the simpler 2-arg overload: + /// TrimmableTypeMap.Initialize(typeMap, proxyMap). + /// + static void EmitInitializeWithSingleTypeMapNoArrays (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, + MemberReferenceHandle getExternalMemberRef, MemberReferenceHandle getProxyMemberRef, + MemberReferenceHandle initializeRef) + { + var getExternalSpec = MakeGenericMethodSpec (pe, getExternalMemberRef, anchorTypeHandle); + var getProxySpec = MakeGenericMethodSpec (pe, getProxyMemberRef, anchorTypeHandle); + + pe.EmitBody ("Initialize", + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, + sig => sig.MethodSignature ().Parameters (0, rt => rt.Void (), p => { }), + encoder => { + encoder.Call (getExternalSpec, parameterCount: 0, returnsValue: true); + encoder.Call (getProxySpec, parameterCount: 0, returnsValue: true); + encoder.Call (initializeRef, parameterCount: 2); + encoder.Return (); + }); + } + + /// MemberRef for TrimmableTypeMap.Initialize(typeMap, proxyMap) (2-arg, no array maps). + static MemberReferenceHandle AddInitializeSingleNoArraysRef (PEAssemblyBuilder pe, TypeReferenceHandle trimmableTypeMapRef, + TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef) + { + var blob = new BlobBuilder (64); + blob.WriteByte (0x00); // DEFAULT (static) + blob.WriteCompressedInteger (2); // parameter count + blob.WriteByte (0x01); // return type: void + EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true); + EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false); + return pe.Metadata.AddMemberReference (trimmableTypeMapRef, + pe.Metadata.GetOrAddString ("Initialize"), pe.Metadata.GetOrAddBlob (blob)); + } + /// MemberRef for TrimmableTypeMap.Initialize(typeMap, proxyMap, arrayMapsByAssemblyAndRank[][]). static MemberReferenceHandle AddInitializeSingleWithArraysRef (PEAssemblyBuilder pe, TypeReferenceHandle trimmableTypeMapRef, TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef) @@ -401,7 +508,7 @@ static MemberReferenceHandle AddInitializeSingleWithArraysRef (PEAssemblyBuilder /// IReadOnlyDictionary<string, Type>?[assemblyCount][maxArrayRank] /// (when > 0) or ldnull. /// - static void EmitArrayMapsByAssemblyAndRankOrNull (PEAssemblyBuilder pe, InstructionEncoder encoder, + static void EmitArrayMapsByAssemblyAndRankOrNull (PEAssemblyBuilder pe, TrackedInstructionEncoder encoder, IReadOnlyList perAssemblyTypeMapNames, MemberReferenceHandle getExternalMemberRef, TypeSpecificationHandle externalDictTypeSpec, TypeSpecificationHandle externalDictArrayTypeSpec, @@ -413,8 +520,7 @@ static void EmitArrayMapsByAssemblyAndRankOrNull (PEAssemblyBuilder pe, Instruct } encoder.LoadConstantI4 (perAssemblyTypeMapNames.Count); - encoder.OpCode (ILOpCode.Newarr); - encoder.Token (externalDictArrayTypeSpec); + encoder.NewArray (externalDictArrayTypeSpec); for (int i = 0; i < perAssemblyTypeMapNames.Count; i++) { var asmRef = pe.FindOrAddAssemblyRef (perAssemblyTypeMapNames [i]); encoder.OpCode (ILOpCode.Dup); @@ -424,21 +530,19 @@ static void EmitArrayMapsByAssemblyAndRankOrNull (PEAssemblyBuilder pe, Instruct } } - static void EmitArrayMapsByRank (PEAssemblyBuilder pe, InstructionEncoder encoder, + static void EmitArrayMapsByRank (PEAssemblyBuilder pe, TrackedInstructionEncoder encoder, AssemblyReferenceHandle assemblyRef, MemberReferenceHandle getExternalMemberRef, TypeSpecificationHandle externalDictTypeSpec, int maxArrayRank) { encoder.LoadConstantI4 (maxArrayRank); - encoder.OpCode (ILOpCode.Newarr); - encoder.Token (externalDictTypeSpec); + encoder.NewArray (externalDictTypeSpec); for (int r = 0; r < maxArrayRank; r++) { var rankRef = pe.Metadata.AddTypeReference (assemblyRef, default, pe.Metadata.GetOrAddString ($"__ArrayMapRank{r + 1}")); var rankSpec = MakeGenericMethodSpec (pe, getExternalMemberRef, rankRef); encoder.OpCode (ILOpCode.Dup); encoder.LoadConstantI4 (r); - encoder.OpCode (ILOpCode.Call); - encoder.Token (rankSpec); + encoder.Call (rankSpec, parameterCount: 0, returnsValue: true); encoder.OpCode (ILOpCode.Stelem_ref); } } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 1cce109d948..4418bb616fd 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -5,6 +5,8 @@ using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; +using TrackedInstructionEncoder = Microsoft.Android.Sdk.TrimmableTypeMap.PEAssemblyBuilder.TrackedInstructionEncoder; + namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// @@ -85,7 +87,9 @@ sealed class TypeMapAssemblyEmitter TypeReferenceHandle _jniObjectReferenceOptionsRef; TypeReferenceHandle _iAndroidCallableWrapperRef; TypeReferenceHandle _jniEnvRef; + TypeReferenceHandle _javaLangObjectRef; TypeReferenceHandle _systemTypeRef; + TypeReferenceHandle _systemArrayRef; TypeReferenceHandle _runtimeTypeHandleRef; TypeReferenceHandle _jniTypeRef; TypeReferenceHandle _notSupportedExceptionRef; @@ -98,7 +102,13 @@ sealed class TypeMapAssemblyEmitter MemberReferenceHandle _notSupportedExceptionCtorRef; MemberReferenceHandle _jniObjectReferenceCtorRef; MemberReferenceHandle _jniEnvDeleteRefRef; + MemberReferenceHandle _jniEnvGetStringRef; + MemberReferenceHandle _jniEnvGetArrayRef; + MemberReferenceHandle _javaLangObjectGetObjectRef; MemberReferenceHandle _shouldSkipActivationRef; + MemberReferenceHandle _getActivationPeerRef; + MemberReferenceHandle _setActivationPeerReferenceRef; + MemberReferenceHandle _markActivationPeerReplaceableRef; MemberReferenceHandle _waitForBridgeProcessingRef; MemberReferenceHandle _androidEnvironmentUnhandledExceptionRef; MemberReferenceHandle _ucoAttrCtorRef; @@ -129,6 +139,8 @@ sealed class TypeMapAssemblyEmitter EntityHandle _anchorTypeHandle; + ExportMethodDispatchEmitter? _exportMethodDispatchEmitter; + // Per-rank array sentinel TypeDefs, 0-indexed by (rank - 1). Empty when array entries // aren't emitted. EntityHandle [] _rankAnchorHandles = []; @@ -226,6 +238,8 @@ void EmitTypeReferences () metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("JniHandleOwnership")); _jniEnvRef = metadata.AddTypeReference (_pe.MonoAndroidRef, metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("JNIEnv")); + _javaLangObjectRef = metadata.AddTypeReference (_pe.MonoAndroidRef, + metadata.GetOrAddString ("Java.Lang"), metadata.GetOrAddString ("Object")); _jniObjectReferenceRef = metadata.AddTypeReference (_javaInteropRef, metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniObjectReference")); _jniObjectReferenceTypeRef = metadata.AddTypeReference (_javaInteropRef, @@ -236,6 +250,8 @@ void EmitTypeReferences () metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("IAndroidCallableWrapper")); _systemTypeRef = metadata.AddTypeReference (_pe.SystemRuntimeRef, metadata.GetOrAddString ("System"), metadata.GetOrAddString ("Type")); + _systemArrayRef = metadata.AddTypeReference (_pe.SystemRuntimeRef, + metadata.GetOrAddString ("System"), metadata.GetOrAddString ("Array")); _runtimeTypeHandleRef = metadata.AddTypeReference (_pe.SystemRuntimeRef, metadata.GetOrAddString ("System"), metadata.GetOrAddString ("RuntimeTypeHandle")); _jniTypeRef = metadata.AddTypeReference (_javaInteropRef, @@ -357,12 +373,56 @@ void EmitMemberReferences () p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true); })); + _jniEnvGetStringRef = _pe.AddMemberRef (_jniEnvRef, "GetString", + sig => sig.MethodSignature ().Parameters (2, + rt => rt.Type ().String (), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true); + })); + + _jniEnvGetArrayRef = _pe.AddMemberRef (_jniEnvRef, "GetArray", + sig => sig.MethodSignature ().Parameters (3, + rt => rt.Type ().Type (_systemArrayRef, false), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true); + p.AddParameter ().Type ().Type (_systemTypeRef, false); + })); + + _javaLangObjectGetObjectRef = _pe.AddMemberRef (_javaLangObjectRef, "GetObject", + sig => sig.MethodSignature ().Parameters (3, + rt => rt.Type ().Type (_iJavaPeerableRef, false), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true); + p.AddParameter ().Type ().Type (_systemTypeRef, false); + })); + // JavaPeerProxy.ShouldSkipActivation(IntPtr) -> bool (static method) _shouldSkipActivationRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "ShouldSkipActivation", sig => sig.MethodSignature ().Parameters (1, rt => rt.Type ().Boolean (), p => { p.AddParameter ().Type ().IntPtr (); })); + _getActivationPeerRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "GetActivationPeer", + sig => sig.MethodSignature ().Parameters (1, + rt => rt.Type ().Type (_iJavaPeerableRef, false), + p => { p.AddParameter ().Type ().IntPtr (); })); + + _setActivationPeerReferenceRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "SetActivationPeerReference", + sig => sig.MethodSignature ().Parameters (2, + rt => rt.Void (), + p => { + p.AddParameter ().Type ().Type (_iJavaPeerableRef, false); + p.AddParameter ().Type ().IntPtr (); + })); + + _markActivationPeerReplaceableRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "MarkActivationPeerReplaceable", + sig => sig.MethodSignature ().Parameters (1, + rt => rt.Void (), + p => p.AddParameter ().Type ().IntPtr ())); + _waitForBridgeProcessingRef = _pe.AddMemberRef (_androidRuntimeInternalRef, "WaitForBridgeProcessing", sig => sig.MethodSignature ().Parameters (0, rt => rt.Void (), p => { })); @@ -512,6 +572,34 @@ void EmitTypeMapAssociationAttributeCtorRef () })); } + ExportMethodDispatchEmitterContext CreateExportMethodDispatchEmitterContext () + { + return ExportMethodDispatchEmitterContext.Create ( + _pe, + _iJavaPeerableRef, + _jniHandleOwnershipRef, + _jniEnvRef, + _systemTypeRef, + _getTypeFromHandleRef, + _ucoAttrCtorRef, + _ucoAttrBlobHandle, + _jniTransitionRef, + _jniRuntimeRef, + _exceptionRef, + _beginMarshalMethodRef, + _endMarshalMethodRef, + _onUserUnhandledExceptionRef + ); + } + + ExportMethodDispatchEmitter GetExportMethodDispatchEmitter () + { + // [Export] is a niche feature; create the emitter lazily so we only pay + // for it in assemblies that actually contain export-attributed methods. + _exportMethodDispatchEmitter ??= new ExportMethodDispatchEmitter (_pe, CreateExportMethodDispatchEmitterContext ()); + return _exportMethodDispatchEmitter; + } + void EmitProxyType (JavaPeerProxyData proxy, Dictionary wrapperHandles) { if (proxy.IsAcw) { @@ -580,19 +668,17 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary { encoder.OpCode (ILOpCode.Ldnull); - encoder.OpCode (ILOpCode.Ret); + encoder.Return (returnsValue: true); }); } @@ -736,9 +824,8 @@ void EmitCreateInstanceGenericDefinition () { EmitCreateInstanceBody (encoder => { encoder.LoadString (_pe.Metadata.GetOrAddUserString ("Cannot create instance of open generic type.")); - encoder.OpCode (ILOpCode.Newobj); - encoder.Token (_notSupportedExceptionCtorRef); - encoder.OpCode (ILOpCode.Throw); + encoder.NewObject (_notSupportedExceptionCtorRef, parameterCount: 1); + encoder.Throw (); }); } @@ -748,9 +835,8 @@ void EmitCreateInstanceViaNewobj (EntityHandle typeRef) EmitCreateInstanceBody (encoder => { encoder.OpCode (ILOpCode.Ldarg_1); encoder.OpCode (ILOpCode.Ldarg_2); - encoder.OpCode (ILOpCode.Newobj); - encoder.Token (ctorRef); - encoder.OpCode (ILOpCode.Ret); + encoder.NewObject (ctorRef, parameterCount: 2); + encoder.Return (returnsValue: true); }); } @@ -758,19 +844,17 @@ void EmitCreateInstanceInheritedCtor (EntityHandle targetTypeRef, ActivationCtor { var baseActivationCtorRef = AddActivationCtorRef (_pe.ResolveTypeRef (activationCtor.DeclaringType)); EmitCreateInstanceBody (encoder => { - encoder.OpCode (ILOpCode.Ldtoken); - encoder.Token (targetTypeRef); - encoder.Call (_getTypeFromHandleRef); - encoder.Call (_getUninitializedObjectRef); - encoder.OpCode (ILOpCode.Castclass); - encoder.Token (targetTypeRef); + encoder.LoadToken (targetTypeRef); + encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); + encoder.Call (_getUninitializedObjectRef, parameterCount: 1, returnsValue: true); + encoder.CastClass (targetTypeRef); encoder.OpCode (ILOpCode.Dup); encoder.OpCode (ILOpCode.Ldarg_1); encoder.OpCode (ILOpCode.Ldarg_2); - encoder.Call (baseActivationCtorRef); + encoder.Call (baseActivationCtorRef, parameterCount: 2, isInstance: true); - encoder.OpCode (ILOpCode.Ret); + encoder.Return (returnsValue: true); }); } @@ -791,22 +875,21 @@ void EmitCreateInstanceViaJavaInteropNewobj (EntityHandle typeRef) encoder.LoadLocalAddress (0); encoder.OpCode (ILOpCode.Ldarg_1); // handle encoder.LoadConstantI4 (0); // JniObjectReferenceType.Invalid - encoder.Call (_jniObjectReferenceCtorRef); + encoder.Call (_jniObjectReferenceCtorRef, parameterCount: 2, isInstance: true); // var result = new TargetType(ref jniRef, JniObjectReferenceOptions.Copy); encoder.LoadLocalAddress (0); encoder.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy - encoder.OpCode (ILOpCode.Newobj); - encoder.Token (ctorRef); + encoder.NewObject (ctorRef, parameterCount: 2); encoder.StoreLocal (1); // save result // JNIEnv.DeleteRef(handle, ownership); encoder.OpCode (ILOpCode.Ldarg_1); // handle encoder.OpCode (ILOpCode.Ldarg_2); // ownership - encoder.Call (_jniEnvDeleteRefRef); + encoder.Call (_jniEnvDeleteRefRef, parameterCount: 2); encoder.LoadLocal (1); // load result - encoder.OpCode (ILOpCode.Ret); + encoder.Return (returnsValue: true); }); } @@ -825,12 +908,10 @@ void EmitCreateInstanceInheritedJavaInteropCtor (EntityHandle targetTypeRef, Act EncodeJniObjectReferenceLocal, encoder => { // var obj = (TargetType)RuntimeHelpers.GetUninitializedObject(typeof(TargetType)); - encoder.OpCode (ILOpCode.Ldtoken); - encoder.Token (targetTypeRef); - encoder.Call (_getTypeFromHandleRef); - encoder.Call (_getUninitializedObjectRef); - encoder.OpCode (ILOpCode.Castclass); - encoder.Token (targetTypeRef); + encoder.LoadToken (targetTypeRef); + encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); + encoder.Call (_getUninitializedObjectRef, parameterCount: 1, returnsValue: true); + encoder.CastClass (targetTypeRef); // dup obj (one copy for the call, one for the return) encoder.OpCode (ILOpCode.Dup); @@ -839,19 +920,19 @@ void EmitCreateInstanceInheritedJavaInteropCtor (EntityHandle targetTypeRef, Act encoder.LoadLocalAddress (0); encoder.OpCode (ILOpCode.Ldarg_1); // handle encoder.LoadConstantI4 (0); // JniObjectReferenceType.Invalid - encoder.Call (_jniObjectReferenceCtorRef); + encoder.Call (_jniObjectReferenceCtorRef, parameterCount: 2, isInstance: true); // obj.BaseCtor(ref jniRef, JniObjectReferenceOptions.Copy); encoder.LoadLocalAddress (0); encoder.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy - encoder.Call (baseCtorRef); + encoder.Call (baseCtorRef, parameterCount: 2, isInstance: true); // JNIEnv.DeleteRef(handle, ownership); encoder.OpCode (ILOpCode.Ldarg_1); // handle encoder.OpCode (ILOpCode.Ldarg_2); // ownership - encoder.Call (_jniEnvDeleteRefRef); + encoder.Call (_jniEnvDeleteRefRef, parameterCount: 2); - encoder.OpCode (ILOpCode.Ret); + encoder.Return (returnsValue: true); }); } @@ -889,7 +970,7 @@ MemberReferenceHandle AddJavaInteropActivationCtorRef (EntityHandle declaringTyp })); } - void EmitCreateInstanceBody (Action emitIL) + void EmitCreateInstanceBody (Action emitIL) { _pe.EmitBody ("CreateInstance", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, @@ -902,7 +983,7 @@ void EmitCreateInstanceBody (Action emitIL) emitIL); } - void EmitCreateInstanceBodyWithLocals (Action encodeLocals, Action emitIL) + void EmitCreateInstanceBodyWithLocals (Action encodeLocals, Action emitIL) { _pe.EmitBody ("CreateInstance", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, @@ -927,6 +1008,18 @@ MemberReferenceHandle AddActivationCtorRef (EntityHandle declaringTypeRef) })); } + MemberReferenceHandle AddManagedCtorRef (EntityHandle declaringTypeRef, IReadOnlyList parameterTypes) + { + var blob = new BlobBuilder (32); + blob.WriteByte (0x20); // HASTHIS + blob.WriteCompressedInteger (parameterTypes.Count); + blob.WriteByte (0x01); // ELEMENT_TYPE_VOID + foreach (var parameterType in parameterTypes) { + WriteManagedTypeSignature (blob, parameterType.ManagedTypeName, parameterType.AssemblyName); + } + return _pe.Metadata.AddMemberReference (declaringTypeRef, _pe.Metadata.GetOrAddString (".ctor"), _pe.Metadata.GetOrAddBlob (blob)); + } + MethodDefinitionHandle EmitUcoMethod (UcoMethodData uco, JavaPeerProxyData proxy) { var jniParams = JniSignatureHelper.ParseParameterTypes (uco.JniSignature); @@ -963,7 +1056,7 @@ MethodDefinitionHandle EmitUcoMethod (UcoMethodData uco, JavaPeerProxyData proxy (encoder, cfb) => EmitUcoForwarderBody (encoder, cfb, returnKind, enc => { for (int p = 0; p < paramCount; p++) enc.LoadArgument (p); - enc.Call (callbackRef); + enc.Call (callbackRef, paramCount, returnsValue: !isVoid); }), blob => EncodeUcoForwarderLegacyLocals (blob, returnKind)); @@ -971,14 +1064,14 @@ MethodDefinitionHandle EmitUcoMethod (UcoMethodData uco, JavaPeerProxyData proxy return handle; } - void EmitUcoForwarderBody (InstructionEncoder encoder, ControlFlowBuilder cfb, JniParamKind returnKind, Action emitCallback) + void EmitUcoForwarderBody (TrackedInstructionEncoder encoder, ControlFlowBuilder cfb, JniParamKind returnKind, Action emitCallback) { bool isVoid = returnKind == JniParamKind.Void; var tryStart = encoder.DefineLabel (); var catchStart = encoder.DefineLabel (); var afterAll = encoder.DefineLabel (); - encoder.Call (_waitForBridgeProcessingRef); + encoder.Call (_waitForBridgeProcessingRef, parameterCount: 0); encoder.MarkLabel (tryStart); emitCallback (encoder); if (!isVoid) { @@ -986,17 +1079,17 @@ void EmitUcoForwarderBody (InstructionEncoder encoder, ControlFlowBuilder cfb, J } encoder.Branch (ILOpCode.Leave, afterAll); - encoder.MarkLabel (catchStart); + encoder.MarkLabel (catchStart, stackDepth: 1); encoder.StoreLocal (isVoid ? 0 : 1); encoder.LoadLocal (isVoid ? 0 : 1); - encoder.Call (_androidEnvironmentUnhandledExceptionRef); + encoder.Call (_androidEnvironmentUnhandledExceptionRef, parameterCount: 1); encoder.Branch (ILOpCode.Leave, afterAll); encoder.MarkLabel (afterAll); if (!isVoid) { encoder.LoadLocal (0); } - encoder.OpCode (ILOpCode.Ret); + encoder.Return (returnsValue: !isVoid); cfb.AddCatchRegion (tryStart, catchStart, catchStart, afterAll, _exceptionRef); } @@ -1020,9 +1113,8 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy $"UCO constructor wrapper requires an activation ctor for '{uco.TargetType.ManagedTypeName}'"); // UCO constructor wrappers must match the JNI native method signature exactly. - // Only jnienv (arg 0) and self (arg 1) are used — the constructor parameters - // are not forwarded because we create the managed peer using the - // activation ctor (IntPtr, JniHandleOwnership), not the user-visible constructor. + // jnienv and self are followed by the Java constructor parameters, which are + // forwarded when scanner metadata can identify the matching managed constructor. var jniParams = JniSignatureHelper.ParseParameterTypes (uco.JniSignature); int paramCount = 2 + jniParams.Count; @@ -1037,20 +1129,65 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy // Open generic types can't be activated because Java construction cannot provide the type arguments. if (proxy.IsGenericDefinition) { - var openGenericHandle = _pe.EmitBody (uco.WrapperName, - MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, - encodeSig, - (encoder, cfb) => EmitUcoConstructorBodyWithMarshal (encoder, cfb, enc => { + var openGenericHandle = EmitUcoConstructorBody (uco.WrapperName, encodeSig, + enc => { enc.LoadString (_pe.Metadata.GetOrAddUserString ("Constructing instances of generic types from Java is not supported, as the type parameters cannot be determined.")); - enc.OpCode (ILOpCode.Newobj); - enc.Token (_notSupportedExceptionCtorRef); - enc.OpCode (ILOpCode.Throw); - }), + enc.NewObject (_notSupportedExceptionCtorRef, parameterCount: 1); + enc.Throw (); + }, EncodeUcoConstructorLocals_Standard); AddUnmanagedCallersOnlyAttribute (openGenericHandle); return openGenericHandle; } + if (proxy.InvokerType != null) { + var invokerTypeRef = _pe.ResolveTypeRef (proxy.InvokerType); + MethodDefinitionHandle invokerHandle; + if (proxy.InvokerActivationCtorStyle == ActivationCtorStyle.JavaInterop) { + var ctorRef = AddJavaInteropActivationCtorRef (invokerTypeRef); + invokerHandle = EmitUcoConstructorBody (uco.WrapperName, encodeSig, + enc => { + enc.LoadLocalAddress (3); // jniRef + enc.LoadArgument (1); // self + enc.LoadConstantI4 (0); // JniObjectReferenceType.Invalid + enc.Call (_jniObjectReferenceCtorRef, parameterCount: 2, isInstance: true); + + enc.LoadLocalAddress (3); // ref jniRef + enc.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy + enc.NewObject (ctorRef, parameterCount: 2); + enc.PopValue (); + + enc.LoadArgument (1); // self + enc.Call (_markActivationPeerReplaceableRef, parameterCount: 1); + }, + EncodeUcoConstructorLocals_JavaInterop); + } else { + var ctorRef = AddActivationCtorRef (invokerTypeRef); + invokerHandle = EmitUcoConstructorBody (uco.WrapperName, encodeSig, + enc => { + enc.LoadArgument (1); // self + enc.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer + enc.NewObject (ctorRef, parameterCount: 2); + enc.PopValue (); + + enc.LoadArgument (1); // self + enc.Call (_markActivationPeerReplaceableRef, parameterCount: 1); + }, + EncodeUcoConstructorLocals_Standard); + } + AddUnmanagedCallersOnlyAttribute (invokerHandle); + return invokerHandle; + } + + if (uco.HasMatchingManagedCtor) { + var ctorRef = AddManagedCtorRef (targetTypeRef, uco.ManagedParameterTypes); + var managedCtorHandle = EmitUcoConstructorBody (uco.WrapperName, encodeSig, + enc => EmitManagedConstructorActivation (enc, targetTypeRef, ctorRef, uco.ManagedParameterTypes, jniParams), + blob => EncodeUcoConstructorLocals_DefaultConstructor (blob, targetTypeRef)); + AddUnmanagedCallersOnlyAttribute (managedCtorHandle); + return managedCtorHandle; + } + MethodDefinitionHandle handle; if (activationCtor.Style == ActivationCtorStyle.JavaInterop) { var ctorRef = AddJavaInteropActivationCtorRef ( @@ -1061,36 +1198,33 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy // 1: JniRuntime? (runtime) — out-parameter for BeginMarshalMethod // 2: Exception (e) — catch variable // 3: JniObjectReference (jniRef) — needed for JavaInterop-style activation - handle = _pe.EmitBody (uco.WrapperName, - MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, - encodeSig, - (encoder, cfb) => EmitUcoConstructorBodyWithMarshal (encoder, cfb, enc => { + handle = EmitUcoConstructorBody (uco.WrapperName, encodeSig, + enc => { if (!activationCtor.IsOnLeafType) { - enc.OpCode (ILOpCode.Ldtoken); - enc.Token (targetTypeRef); - enc.Call (_getTypeFromHandleRef); - enc.Call (_getUninitializedObjectRef); - enc.OpCode (ILOpCode.Castclass); - enc.Token (targetTypeRef); + enc.LoadToken (targetTypeRef); + enc.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); + enc.Call (_getUninitializedObjectRef, parameterCount: 1, returnsValue: true); + enc.CastClass (targetTypeRef); } enc.LoadLocalAddress (3); // jniRef enc.LoadArgument (1); // self enc.LoadConstantI4 (0); // JniObjectReferenceType.Invalid - enc.Call (_jniObjectReferenceCtorRef); + enc.Call (_jniObjectReferenceCtorRef, parameterCount: 2, isInstance: true); if (activationCtor.IsOnLeafType) { enc.LoadLocalAddress (3); // ref jniRef enc.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy - enc.OpCode (ILOpCode.Newobj); - enc.Token (ctorRef); - enc.OpCode (ILOpCode.Pop); + enc.NewObject (ctorRef, parameterCount: 2); + enc.PopValue (); } else { enc.LoadLocalAddress (3); // ref jniRef enc.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy - enc.Call (ctorRef); + enc.Call (ctorRef, parameterCount: 2, isInstance: true); } - }), + enc.LoadArgument (1); // self + enc.Call (_markActivationPeerReplaceableRef, parameterCount: 1); + }, EncodeUcoConstructorLocals_JavaInterop); } else { var ctorRef = AddActivationCtorRef ( @@ -1100,35 +1234,83 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy // 0: JniTransition (envp) — out-parameter for BeginMarshalMethod // 1: JniRuntime? (runtime) — out-parameter for BeginMarshalMethod // 2: Exception (e) — catch variable - handle = _pe.EmitBody (uco.WrapperName, - MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, - encodeSig, - (encoder, cfb) => EmitUcoConstructorBodyWithMarshal (encoder, cfb, enc => { + handle = EmitUcoConstructorBody (uco.WrapperName, encodeSig, + enc => { if (activationCtor.IsOnLeafType) { enc.LoadArgument (1); // self enc.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer - enc.OpCode (ILOpCode.Newobj); - enc.Token (ctorRef); - enc.OpCode (ILOpCode.Pop); + enc.NewObject (ctorRef, parameterCount: 2); + enc.PopValue (); } else { - enc.OpCode (ILOpCode.Ldtoken); - enc.Token (targetTypeRef); - enc.Call (_getTypeFromHandleRef); - enc.Call (_getUninitializedObjectRef); - enc.OpCode (ILOpCode.Castclass); - enc.Token (targetTypeRef); + enc.LoadToken (targetTypeRef); + enc.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); + enc.Call (_getUninitializedObjectRef, parameterCount: 1, returnsValue: true); + enc.CastClass (targetTypeRef); enc.LoadArgument (1); // self enc.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer - enc.Call (ctorRef); + enc.Call (ctorRef, parameterCount: 2, isInstance: true); } - }), + enc.LoadArgument (1); // self + enc.Call (_markActivationPeerReplaceableRef, parameterCount: 1); + }, EncodeUcoConstructorLocals_Standard); } AddUnmanagedCallersOnlyAttribute (handle); return handle; } + MethodDefinitionHandle EmitUcoConstructorBody ( + string wrapperName, + Action encodeSig, + Action emitActivation, + Action encodeLocals) + { + return _pe.EmitBody (wrapperName, + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, + encodeSig, + (encoder, cfb) => EmitUcoConstructorBodyWithMarshal (encoder, cfb, emitActivation), + encodeLocals); + } + + void EmitManagedConstructorActivation ( + TrackedInstructionEncoder enc, + EntityHandle targetTypeRef, + MemberReferenceHandle ctorRef, + IReadOnlyList managedParameterTypes, + IReadOnlyList jniParams) + { + var havePeer = enc.DefineLabel (); + + enc.LoadArgument (1); + enc.Call (_getActivationPeerRef, parameterCount: 1, returnsValue: true); + enc.CastClass (targetTypeRef); + enc.StoreLocal (4); + + enc.LoadLocal (4); + enc.Branch (ILOpCode.Brtrue, havePeer); + + enc.LoadToken (targetTypeRef); + enc.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); + enc.Call (_getUninitializedObjectRef, parameterCount: 1, returnsValue: true); + enc.CastClass (targetTypeRef); + enc.StoreLocal (4); + + enc.LoadLocal (4); + enc.LoadArgument (1); // self + enc.Call (_setActivationPeerReferenceRef, parameterCount: 2); + + enc.MarkLabel (havePeer); + enc.LoadLocal (4); + for (int i = 0; i < managedParameterTypes.Count; i++) { + EmitManagedConstructorArgument (enc, managedParameterTypes [i], jniParams [i], i + 2); + } + enc.Call (ctorRef, managedParameterTypes.Count, isInstance: true); + + enc.LoadArgument (1); // self + enc.Call (_markActivationPeerReplaceableRef, parameterCount: 1); + } + /// /// Emits the common try/catch/finally marshal-method wrapper pattern used by all /// non-generic UCO constructor bodies: @@ -1145,7 +1327,7 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy /// Locals 0 (JniTransition envp) and 1 (JniRuntime? runtime) must be declared by the caller. /// Local 2 (Exception e) must also be declared. Any activation-specific locals start at index 3. /// - void EmitUcoConstructorBodyWithMarshal (InstructionEncoder encoder, ControlFlowBuilder cfb, Action emitActivation) + void EmitUcoConstructorBodyWithMarshal (TrackedInstructionEncoder encoder, ControlFlowBuilder cfb, Action emitActivation) { var skipLabel = encoder.DefineLabel (); var tryStart = encoder.DefineLabel (); @@ -1158,13 +1340,13 @@ void EmitUcoConstructorBodyWithMarshal (InstructionEncoder encoder, ControlFlowB encoder.LoadArgument (0); // jnienv encoder.LoadLocalAddress (0); // out JniTransition (local 0) encoder.LoadLocalAddress (1); // out JniRuntime? (local 1) - encoder.Call (_beginMarshalMethodRef); + encoder.Call (_beginMarshalMethodRef, parameterCount: 3, returnsValue: true); encoder.Branch (ILOpCode.Brfalse, afterAll); // TRY — check ShouldSkipActivation, then run activation code. encoder.MarkLabel (tryStart); encoder.LoadArgument (1); // self (IntPtr) - encoder.Call (_shouldSkipActivationRef); + encoder.Call (_shouldSkipActivationRef, parameterCount: 1, returnsValue: true); encoder.Branch (ILOpCode.Brtrue, skipLabel); emitActivation (encoder); @@ -1173,27 +1355,26 @@ void EmitUcoConstructorBodyWithMarshal (InstructionEncoder encoder, ControlFlowB encoder.Branch (ILOpCode.Leave, afterAll); // CATCH (System.Exception e) - encoder.MarkLabel (catchStart); + encoder.MarkLabel (catchStart, stackDepth: 1); encoder.StoreLocal (2); // e = exception (local 2) encoder.LoadLocal (1); // load runtime (__r) encoder.Branch (ILOpCode.Brfalse, endCatch); encoder.LoadLocal (1); // __r for callvirt encoder.LoadLocalAddress (0); // ref envp encoder.LoadLocal (2); // e - encoder.OpCode (ILOpCode.Callvirt); - encoder.Token (_onUserUnhandledExceptionRef); + encoder.Callvirt (_onUserUnhandledExceptionRef, parameterCount: 2); encoder.MarkLabel (endCatch); encoder.Branch (ILOpCode.Leave, afterAll); // FINALLY encoder.MarkLabel (finallyStart); encoder.LoadLocalAddress (0); // ref envp - encoder.Call (_endMarshalMethodRef); + encoder.Call (_endMarshalMethodRef, parameterCount: 1); encoder.OpCode (ILOpCode.Endfinally); // AFTER (both finallyEnd and the early-return target) encoder.MarkLabel (afterAll); - encoder.OpCode (ILOpCode.Ret); + encoder.Return (); // Register exception regions: // Catch region: try [tryStart, catchStart), handler [catchStart, finallyStart) @@ -1202,6 +1383,124 @@ void EmitUcoConstructorBodyWithMarshal (InstructionEncoder encoder, ControlFlowB cfb.AddFinallyRegion (tryStart, finallyStart, finallyStart, afterAll); } + void EmitManagedConstructorArgument (TrackedInstructionEncoder encoder, TypeRefData managedType, JniParamKind jniKind, int argumentIndex) + { + if (managedType.ManagedTypeName == "System.Boolean") { + encoder.LoadArgument (argumentIndex); + encoder.LoadConstantI4 (0); + encoder.OpCode (ILOpCode.Cgt_un); + return; + } + + if (jniKind != JniParamKind.Object) { + encoder.LoadArgument (argumentIndex); + return; + } + + if (managedType.ManagedTypeName == "System.String") { + encoder.LoadArgument (argumentIndex); + encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer + encoder.Call (_jniEnvGetStringRef, parameterCount: 2, returnsValue: true); + return; + } + + if (TryGetSzArrayElementType (managedType.ManagedTypeName, out var elementType)) { + var arrayType = ResolveManagedTypeHandle (managedType.ManagedTypeName, managedType.AssemblyName); + var elementTypeHandle = ResolveManagedTypeHandle (elementType, managedType.AssemblyName); + + encoder.LoadArgument (argumentIndex); + encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer + encoder.LoadToken (elementTypeHandle); + encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); + encoder.Call (_jniEnvGetArrayRef, parameterCount: 3, returnsValue: true); + encoder.CastClass (arrayType); + return; + } + + encoder.LoadArgument (argumentIndex); + encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer + if (managedType.ManagedTypeName == "System.Object") { + encoder.OpCode (ILOpCode.Ldnull); + encoder.Call (_javaLangObjectGetObjectRef, parameterCount: 3, returnsValue: true); + return; + } + + var managedTypeHandle = ResolveManagedTypeHandle (managedType.ManagedTypeName, managedType.AssemblyName); + encoder.LoadToken (managedTypeHandle); + encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); + encoder.Call (_javaLangObjectGetObjectRef, parameterCount: 3, returnsValue: true); + encoder.CastClass (managedTypeHandle); + } + + EntityHandle ResolveManagedTypeHandle (string managedType, string defaultAssemblyName) + { + if (TryGetSzArrayElementType (managedType, out var elementType)) { + var blob = new BlobBuilder (32); + blob.WriteByte (0x1D); // ELEMENT_TYPE_SZARRAY + WriteManagedTypeSignature (blob, elementType, defaultAssemblyName); + return _pe.Metadata.AddTypeSpecification (_pe.Metadata.GetOrAddBlob (blob)); + } + + return _pe.ResolveTypeRef (new TypeRefData { + ManagedTypeName = managedType, + AssemblyName = GetAssemblyNameForManagedType (managedType, defaultAssemblyName), + }); + } + + static bool TryGetSzArrayElementType (string managedType, out string elementType) + { + if (managedType.EndsWith ("[]", StringComparison.Ordinal)) { + elementType = managedType.Substring (0, managedType.Length - 2); + return true; + } + + elementType = ""; + return false; + } + + void WriteManagedTypeSignature (BlobBuilder blob, string managedType, string defaultAssemblyName) + { + if (TryGetSzArrayElementType (managedType, out var elementType)) { + blob.WriteByte (0x1D); // ELEMENT_TYPE_SZARRAY + WriteManagedTypeSignature (blob, elementType, defaultAssemblyName); + return; + } + + switch (managedType) { + case "System.Boolean": blob.WriteByte (0x02); return; + case "System.Char": blob.WriteByte (0x03); return; + case "System.SByte": blob.WriteByte (0x04); return; + case "System.Byte": blob.WriteByte (0x05); return; + case "System.Int16": blob.WriteByte (0x06); return; + case "System.UInt16": blob.WriteByte (0x07); return; + case "System.Int32": blob.WriteByte (0x08); return; + case "System.UInt32": blob.WriteByte (0x09); return; + case "System.Int64": blob.WriteByte (0x0A); return; + case "System.UInt64": blob.WriteByte (0x0B); return; + case "System.Single": blob.WriteByte (0x0C); return; + case "System.Double": blob.WriteByte (0x0D); return; + case "System.String": blob.WriteByte (0x0E); return; + case "System.Object": blob.WriteByte (0x1C); return; + } + + var typeHandle = ResolveManagedTypeHandle (managedType, defaultAssemblyName); + blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (typeHandle)); + } + + static string GetAssemblyNameForManagedType (string managedType, string defaultAssemblyName) + { + if (managedType.StartsWith ("System.", StringComparison.Ordinal)) { + return "System.Runtime"; + } + if (managedType.StartsWith ("Android.", StringComparison.Ordinal) || + managedType.StartsWith ("Java.", StringComparison.Ordinal) || + managedType.StartsWith ("Javax.", StringComparison.Ordinal)) { + return "Mono.Android"; + } + return defaultAssemblyName; + } + /// /// LOCAL_SIG for UCO constructors without JavaInterop-style activation. /// Locals: 0=JniTransition, 1=JniRuntime, 2=Exception. @@ -1243,6 +1542,31 @@ void EncodeUcoConstructorLocals_JavaInterop (BlobBuilder blob) blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniObjectReferenceRef)); } + /// + /// LOCAL_SIG for UCO constructors that invoke a no-arg managed constructor. + /// Locals: 0=JniTransition, 1=JniRuntime, 2=Exception, 3=JniObjectReference, 4=target type. + /// + void EncodeUcoConstructorLocals_DefaultConstructor (BlobBuilder blob, EntityHandle targetTypeRef) + { + blob.WriteByte (0x07); // LOCAL_SIG + blob.WriteCompressedInteger (5); + // local 0: JniTransition (valuetype) + blob.WriteByte (0x11); // ELEMENT_TYPE_VALUETYPE + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniTransitionRef)); + // local 1: JniRuntime (class) + blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniRuntimeRef)); + // local 2: Exception (class) + blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_exceptionRef)); + // local 3: JniObjectReference (valuetype) + blob.WriteByte (0x11); // ELEMENT_TYPE_VALUETYPE + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniObjectReferenceRef)); + // local 4: target type (class) + blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (targetTypeRef)); + } + void EmitRegisterNatives (JavaPeerProxyData proxy, Dictionary wrapperHandles) { @@ -1262,7 +1586,7 @@ void EmitRegisterNatives (JavaPeerProxyData proxy, sig => sig.MethodSignature (isInstanceMethod: true).Parameters (1, rt => rt.Void (), p => p.AddParameter ().Type ().Type (_jniTypeRef, false)), - encoder => encoder.OpCode (ILOpCode.Ret)); + encoder => encoder.Return ()); return; } @@ -1285,8 +1609,7 @@ void EmitRegisterNatives (JavaPeerProxyData proxy, encoder => { // stackalloc JniNativeMethod[N] encoder.LoadConstantI4 (methodCount); - encoder.OpCode (ILOpCode.Sizeof); - encoder.Token (_jniNativeMethodRef); + encoder.SizeOf (_jniNativeMethodRef); encoder.OpCode (ILOpCode.Mul); encoder.OpCode (ILOpCode.Localloc); encoder.StoreLocal (0); @@ -1296,53 +1619,46 @@ void EmitRegisterNatives (JavaPeerProxyData proxy, encoder.LoadLocal (0); if (i > 0) { encoder.LoadConstantI4 (i); - encoder.OpCode (ILOpCode.Sizeof); - encoder.Token (_jniNativeMethodRef); + encoder.SizeOf (_jniNativeMethodRef); encoder.OpCode (ILOpCode.Mul); encoder.OpCode (ILOpCode.Add); } // byte* name — ldsflda of deduplicated field - encoder.OpCode (ILOpCode.Ldsflda); - encoder.Token (nameFields [i]); + encoder.LoadStaticFieldAddress (nameFields [i]); // byte* signature - encoder.OpCode (ILOpCode.Ldsflda); - encoder.Token (sigFields [i]); + encoder.LoadStaticFieldAddress (sigFields [i]); // IntPtr functionPointer - encoder.OpCode (ILOpCode.Ldftn); - encoder.Token (validRegs [i].Wrapper); + encoder.LoadFunction (validRegs [i].Wrapper); // Construct the struct on the evaluation stack and store it // at the destination address. This matches the Roslyn pattern: // newobj JniNativeMethod::.ctor(byte*, byte*, IntPtr) // stobj JniNativeMethod - encoder.OpCode (ILOpCode.Newobj); - encoder.Token (_jniNativeMethodCtorRef); - encoder.OpCode (ILOpCode.Stobj); - encoder.Token (_jniNativeMethodRef); + encoder.NewObject (_jniNativeMethodCtorRef, parameterCount: 3); + encoder.StoreObject (_jniNativeMethodRef); } // JniObjectReference peerRef = jniType.PeerReference // JniType is a sealed reference type, so use ldarg + callvirt encoder.LoadArgument (1); - encoder.OpCode (ILOpCode.Callvirt); - encoder.Token (_jniTypePeerReferenceRef); + encoder.Callvirt (_jniTypePeerReferenceRef, parameterCount: 0, returnsValue: true); encoder.StoreLocal (1); // new ReadOnlySpan(methods, count) encoder.LoadLocalAddress (2); encoder.LoadLocal (0); encoder.LoadConstantI4 (methodCount); - encoder.Call (_readOnlySpanOfJniNativeMethodCtorRef); + encoder.Call (_readOnlySpanOfJniNativeMethodCtorRef, parameterCount: 2, isInstance: true); // JniEnvironment.Types.RegisterNatives(peerRef, span) encoder.LoadLocal (1); encoder.LoadLocal (2); - encoder.Call (_jniEnvTypesRegisterNativesRef); + encoder.Call (_jniEnvTypesRegisterNativesRef, parameterCount: 2); - encoder.OpCode (ILOpCode.Ret); + encoder.Return (); }, encodeLocals: localSig => { localSig.WriteByte (0x07); // IMAGE_CEE_CS_CALLCONV_LOCAL_SIG diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/ITrimmableTypeMapLogger.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/ITrimmableTypeMapLogger.cs index 94e046c732f..8b03d89a772 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/ITrimmableTypeMapLogger.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/ITrimmableTypeMapLogger.cs @@ -12,4 +12,5 @@ public interface ITrimmableTypeMapLogger void LogGeneratedJcwFilesInfo (int sourceCount); void LogRootingManifestReferencedTypeInfo (string javaTypeName, string managedTypeName); void LogManifestReferencedTypeNotFoundWarning (string javaTypeName); + void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeName); } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj index 249bdc8def1..99399656317 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj @@ -5,6 +5,7 @@ $(TargetFrameworkNETStandard) enable Nullable + true Microsoft.Android.Sdk.TrimmableTypeMap true ..\..\product.snk @@ -18,6 +19,8 @@ + + diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs index bfd2db7feac..64ca498c814 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs @@ -33,6 +33,21 @@ sealed class AssemblyIndex : IDisposable /// public Dictionary AttributesByType { get; } = new (); + /// + /// Type references grouped by referenced assembly name. + /// + public Dictionary> ReferencedTypeNamesByAssembly { get; } = new (StringComparer.OrdinalIgnoreCase); + + /// + /// True iff the assembly's metadata mentions + /// Java.Interop.JniAddNativeMethodRegistrationAttribute (as a + /// TypeReference or TypeDefinition). The trimmable typemap forbids that + /// attribute (XA4251); this flag lets the scanner short-circuit the + /// per-method attribute walk for the overwhelmingly common case of + /// assemblies that don't use it. + /// + public bool MayUseJniAddNativeMethodRegistrationAttribute { get; private set; } + AssemblyIndex (PEReader peReader, MetadataReader reader, string assemblyName) { this.peReader = peReader; @@ -51,9 +66,36 @@ public static AssemblyIndex Create (PEReader peReader, string assemblyName) void Build () { + const string JniAddNativeMethodRegistrationAttribute = "JniAddNativeMethodRegistrationAttribute"; + const string JavaInteropNamespace = "Java.Interop"; + + // Cheap first pass over TypeReferences / TypeDefinitions to decide whether + // the assembly is even capable of carrying [JniAddNativeMethodRegistration]. + // The per-method attribute walk in the scanner can then skip entirely for + // the common case where the attribute is neither imported nor declared here. + foreach (var trHandle in Reader.TypeReferences) { + var typeReference = Reader.GetTypeReference (trHandle); + if (TryGetTypeReferenceAssemblyName (typeReference, out var assemblyName)) { + if (!ReferencedTypeNamesByAssembly.TryGetValue (assemblyName, out var typeNames)) { + typeNames = new HashSet (StringComparer.Ordinal); + ReferencedTypeNamesByAssembly [assemblyName] = typeNames; + } + typeNames.Add (MetadataTypeNameResolver.GetTypeFromReference (Reader, trHandle, rawTypeKind: 0)); + } + + if (IsTypeReferenceMatch (typeReference, Reader, JavaInteropNamespace, JniAddNativeMethodRegistrationAttribute)) { + MayUseJniAddNativeMethodRegistrationAttribute = true; + } + } + foreach (var typeHandle in Reader.TypeDefinitions) { var typeDef = Reader.GetTypeDefinition (typeHandle); + if (!MayUseJniAddNativeMethodRegistrationAttribute && + IsTypeDefinitionMatch (typeDef, Reader, JavaInteropNamespace, JniAddNativeMethodRegistrationAttribute)) { + MayUseJniAddNativeMethodRegistrationAttribute = true; + } + var fullName = MetadataTypeNameResolver.GetFullName (typeDef, Reader); if (fullName.Length == 0) { continue; @@ -73,6 +115,22 @@ void Build () } } + bool TryGetTypeReferenceAssemblyName (TypeReference typeReference, [NotNullWhen (true)] out string? assemblyName) + { + var scope = typeReference.ResolutionScope; + while (scope.Kind == HandleKind.TypeReference) { + scope = Reader.GetTypeReference ((TypeReferenceHandle) scope).ResolutionScope; + } + if (scope.Kind == HandleKind.AssemblyReference) { + var assemblyReference = Reader.GetAssemblyReference ((AssemblyReferenceHandle) scope); + assemblyName = Reader.GetString (assemblyReference.Name); + return true; + } + + assemblyName = null; + return false; + } + (RegisterInfo? register, TypeAttributeInfo? attrs) ParseAttributes (TypeDefinition typeDef) { RegisterInfo? registerInfo = null; @@ -219,6 +277,32 @@ bool ImplementsJniNameProviderAttribute (CustomAttribute ca) return null; } + internal static bool IsCustomAttributeMatch (CustomAttribute ca, MetadataReader reader, string attributeNamespace, string attributeName) + { + if (ca.Constructor.Kind == HandleKind.MemberReference) { + var memberRef = reader.GetMemberReference ((MemberReferenceHandle)ca.Constructor); + if (memberRef.Parent.Kind == HandleKind.TypeReference) { + return IsTypeReferenceMatch (reader.GetTypeReference ((TypeReferenceHandle)memberRef.Parent), reader, attributeNamespace, attributeName); + } + if (memberRef.Parent.Kind == HandleKind.TypeDefinition) { + return IsTypeDefinitionMatch (reader.GetTypeDefinition ((TypeDefinitionHandle)memberRef.Parent), reader, attributeNamespace, attributeName); + } + } else if (ca.Constructor.Kind == HandleKind.MethodDefinition) { + var methodDef = reader.GetMethodDefinition ((MethodDefinitionHandle)ca.Constructor); + var declaringType = reader.GetTypeDefinition (methodDef.GetDeclaringType ()); + return IsTypeDefinitionMatch (declaringType, reader, attributeNamespace, attributeName); + } + return false; + } + + static bool IsTypeReferenceMatch (TypeReference typeRef, MetadataReader reader, string typeNamespace, string typeName) => + reader.GetString (typeRef.Name) == typeName && + reader.GetString (typeRef.Namespace) == typeNamespace; + + static bool IsTypeDefinitionMatch (TypeDefinition typeDef, MetadataReader reader, string typeNamespace, string typeName) => + reader.GetString (typeDef.Name) == typeName && + reader.GetString (typeDef.Namespace) == typeNamespace; + internal RegisterInfo ParseRegisterAttribute (CustomAttribute ca) { return ParseRegisterInfo (DecodeAttribute (ca)); @@ -582,6 +666,8 @@ sealed record ExportInfo { public IReadOnlyList? ThrownNames { get; init; } public string? SuperArgumentsString { get; init; } + public IReadOnlyList ParameterKinds { get; init; } = []; + public ExportParameterKindInfo ReturnKind { get; init; } } class TypeAttributeInfo (string attributeName) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ExportParameterKindInfo.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ExportParameterKindInfo.cs new file mode 100644 index 00000000000..15497a95bf7 --- /dev/null +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ExportParameterKindInfo.cs @@ -0,0 +1,14 @@ +namespace Microsoft.Android.Sdk.TrimmableTypeMap; + +/// +/// Identifies a special [ExportParameter] marshalling kind applied to +/// a parameter or return value of an [Export] method. +/// +enum ExportParameterKindInfo +{ + Unspecified = 0, + InputStream = 1, + OutputStream = 2, + XmlPullParser = 3, + XmlResourceParser = 4, +} diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs index ee285b42798..2005799d1c3 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs @@ -43,6 +43,16 @@ public sealed record JavaPeerInfo /// public required string AssemblyName { get; init; } + /// + /// True when the type belongs to a framework assembly. + /// + public bool IsFrameworkAssembly { get; init; } + + /// + /// True when per-rank array typemap entries should be generated for this peer. + /// + public bool GenerateArrayEntries { get; set; } = true; + /// /// JNI name of the base Java type, e.g., "android/app/Activity" for a type /// that extends Activity. Null for java/lang/Object or types without a Java base. @@ -190,7 +200,7 @@ public sealed record MarshalMethodInfo /// /// The native callback method name, e.g., "n_onCreate". - /// This is the actual method the UCO wrapper delegates to. + /// This is the Java/JNI-visible native method name that the generated JCW calls. /// public required string NativeCallbackName { get; init; } @@ -224,6 +234,34 @@ public sealed record MarshalMethodInfo /// public string? SuperArgumentsString { get; init; } + /// + /// Managed method parameter types, in declaration order. + /// + internal IReadOnlyList ManagedParameterTypes { get; init; } = []; + + /// + /// Per-parameter [ExportParameter] kinds for legacy callback marshalling. + /// + internal IReadOnlyList ManagedParameterExportKinds { get; init; } = []; + + /// + /// Managed return type, including the defining assembly. + /// + internal TypeRefData ManagedReturnType { get; init; } = new () { + ManagedTypeName = "System.Void", + AssemblyName = "System.Runtime", + }; + + /// + /// [ExportParameter] kind applied to the return value, if any. + /// + internal ExportParameterKindInfo ManagedReturnExportKind { get; init; } + + /// + /// Whether the managed target method is static. + /// + public bool IsStatic { get; init; } + /// /// True if this method was collected from an implemented interface /// (Pass 4: CollectInterfaceMethodImplementations), not from the type itself. @@ -267,6 +305,16 @@ public sealed record JavaConstructorInfo /// Null for [Register] constructors. /// public string? SuperArgumentsString { get; init; } + + /// + /// Managed constructor parameter types, in declaration order. + /// + internal IReadOnlyList ManagedParameterTypes { get; init; } = []; + + /// + /// True when this Java constructor has a matching public managed constructor on the target type. + /// + public bool HasMatchingManagedCtor { get; init; } } /// diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs index dda7460271c..884f95521db 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs @@ -6,6 +6,7 @@ using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; + namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// @@ -16,8 +17,23 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// public sealed class JavaPeerScanner : IDisposable { + enum HashedPackageNamingPolicy { + Crc64, + LowercaseCrc64, + } + readonly Dictionary assemblyCache = new (StringComparer.Ordinal); readonly Dictionary<(string typeName, string assemblyName), ActivationCtorInfo> activationCtorCache = new (); + readonly ITrimmableTypeMapLogger? logger; + readonly HashedPackageNamingPolicy packageNamingPolicy; + readonly HashSet frameworkAssemblyNames; + + public JavaPeerScanner (string? packageNamingPolicy = null, ITrimmableTypeMapLogger? logger = null, HashSet? frameworkAssemblyNames = null) + { + this.packageNamingPolicy = ParsePackageNamingPolicy (packageNamingPolicy); + this.logger = logger; + this.frameworkAssemblyNames = frameworkAssemblyNames ?? new HashSet (StringComparer.OrdinalIgnoreCase); + } /// /// Resolves a type name + assembly name to a TypeDefinitionHandle + AssemblyIndex. @@ -94,9 +110,33 @@ public List Scan (IReadOnlyList<(string Name, PEReader Reader)> as ScanAssembly (index, resultsByQualifiedName); } ForceUnconditionalCrossReferences (resultsByQualifiedName, assemblyCache); + MarkFrameworkArrayEntryPeers (resultsByQualifiedName.Values); return new List (resultsByQualifiedName.Values); } + void MarkFrameworkArrayEntryPeers (IEnumerable peers) + { + var referencedFrameworkTypes = new HashSet (StringComparer.Ordinal); + foreach (var index in assemblyCache.Values) { + if (frameworkAssemblyNames.Contains (index.AssemblyName)) { + continue; + } + foreach (var frameworkAssemblyName in frameworkAssemblyNames) { + if (index.ReferencedTypeNamesByAssembly.TryGetValue (frameworkAssemblyName, out var typeNames)) { + referencedFrameworkTypes.UnionWith (typeNames); + } + } + } + + foreach (var peer in peers) { + if (!peer.IsFrameworkAssembly) { + continue; + } + + peer.GenerateArrayEntries = referencedFrameworkTypes.Contains (peer.ManagedTypeName); + } + } + /// /// Scans all loaded assemblies for assembly-level manifest attributes. /// Must be called after . @@ -168,6 +208,19 @@ void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string A continue; } + // [JniAddNativeMethodRegistrationAttribute] is not supported by the trimmable typemap + // by design (see XA4251). Detect the attribute *before* any per-type filters below + // (array type, no JNI name, etc.) so the diagnostic fires uniformly regardless of + // whether the type would otherwise have ended up in the typemap. + // + // Skip the per-method walk entirely for the overwhelmingly common case where + // the assembly doesn't even reference the attribute type — the per-assembly + // flag was computed cheaply in AssemblyIndex.Build. + if (index.MayUseJniAddNativeMethodRegistrationAttribute && + HasJniAddNativeMethodRegistrationAttribute (typeDef, index)) { + logger?.LogJniAddNativeMethodRegistrationAttributeError (MetadataTypeNameResolver.GetFullName (typeDef, index.Reader)); + } + // Determine the JNI name and whether this is a known Java peer. // Priority: // 1. [Register] attribute → use JNI name from attribute @@ -253,6 +306,8 @@ void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string A ManagedTypeNamespace = ExtractNamespace (fullName), ManagedTypeShortName = ExtractShortName (fullName), AssemblyName = index.AssemblyName, + IsFrameworkAssembly = frameworkAssemblyNames.Contains (index.AssemblyName), + GenerateArrayEntries = !frameworkAssemblyNames.Contains (index.AssemblyName), BaseJavaName = baseJavaName, ImplementedInterfaceJavaNames = implementedInterfaces, IsInterface = isInterface, @@ -262,7 +317,7 @@ void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string A IsUnconditional = isUnconditional, CannotRegisterInStaticConstructor = cannotRegisterInStaticConstructor, MarshalMethods = marshalMethods, - JavaConstructors = BuildJavaConstructors (marshalMethods), + JavaConstructors = BuildJavaConstructors (marshalMethods, typeDef, index), JavaFields = exportFields, ActivationCtor = activationCtor, InvokerTypeName = invokerTypeName, @@ -293,8 +348,16 @@ void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string A } AddMarshalMethod (methods, registerInfo, methodDef, index, exportInfo); - var sig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default); - registeredMethodKeys.Add ($"{index.Reader.GetString (methodDef.Name)}({string.Join (",", sig.ParameterTypes)})"); + // Only [Register]-direct (and [JniConstructorSignature]) registrations + // should preempt Pass 3 base-override detection. [Export]/[ExportField] + // are orthogonal to a [Register]-driven override on the same method — + // e.g., `[Export("foo")] public override void OnCreate(...)` needs both + // the [Register]-driven override entry (Get*Handler connector) AND the + // [Export]-driven entry. Skip the dedup key for [Export]/[ExportField]. + if (exportInfo is null) { + var sig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default); + registeredMethodKeys.Add ($"{index.Reader.GetString (methodDef.Name)}({string.Join (",", sig.ParameterTypes)})"); + } } // Pass 2: collect [Register] from properties (attribute is on the property, not the getter) @@ -338,6 +401,23 @@ void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string A return (methods, fields); } + static bool HasJniAddNativeMethodRegistrationAttribute (TypeDefinition typeDef, AssemblyIndex index) + { + const string JniAddNativeMethodRegistrationAttribute = "JniAddNativeMethodRegistrationAttribute"; + const string JavaInteropNamespace = "Java.Interop"; + + foreach (var methodHandle in typeDef.GetMethods ()) { + var methodDef = index.Reader.GetMethodDefinition (methodHandle); + foreach (var attrHandle in methodDef.GetCustomAttributes ()) { + var attr = index.Reader.GetCustomAttribute (attrHandle); + if (AssemblyIndex.IsCustomAttributeMatch (attr, index.Reader, JavaInteropNamespace, JniAddNativeMethodRegistrationAttribute)) { + return true; + } + } + } + return false; + } + /// /// For each virtual override method on that wasn't already /// collected (no direct [Register]), walks up the base type hierarchy to find a @@ -669,14 +749,149 @@ void CollectBaseConstructorChain (TypeDefinition typeDef, AssemblyIndex index, string? TryResolveJniObjectDescriptor (string managedType) { foreach (var index in assemblyCache.Values) { - if (index.TypesByFullName.TryGetValue (managedType, out var handle) && - index.RegisterInfoByType.TryGetValue (handle, out var registerInfo)) { - return $"L{registerInfo.JniName};"; + if (index.TypesByFullName.TryGetValue (managedType, out var handle)) { + if (index.RegisterInfoByType.TryGetValue (handle, out var registerInfo)) { + return $"L{registerInfo.JniName};"; + } + + // User peer types (extend a Java peer but lack [Register]) + // get a CRC64-based JNI name in ScanAssembly. Mirror that here + // so [Export]/[ExportField] signatures referring to such types + // emit the correct peer descriptor instead of falling back to + // java/lang/Object. + var typeDef = index.Reader.GetTypeDefinition (handle); + if (ExtendsJavaPeer (typeDef, index)) { + var (jniName, _) = ComputeAutoJniNames (typeDef, index); + return $"L{jniName};"; + } } } return null; } + /// + /// Resolves a `typeof(X)` argument captured as an assembly-qualified name + /// (e.g. "Java.IO.IOException, Mono.Android, ...") to its JNI internal + /// name (java/io/IOException). Returns null when the type cannot be + /// found among the loaded assemblies or has no [Register] attribute. + /// + string? ResolveTypeOfArgumentToJniName (string assemblyQualifiedName) + { + var commaIdx = assemblyQualifiedName.IndexOf (','); + var typeName = (commaIdx >= 0 ? assemblyQualifiedName.Substring (0, commaIdx) : assemblyQualifiedName).Trim (); + var descriptor = TryResolveJniObjectDescriptor (typeName); + if (descriptor is null || descriptor.Length < 3) { + return null; + } + // Strip leading 'L' and trailing ';' to get "java/io/IOException". + return descriptor.Substring (1, descriptor.Length - 2); + } + + /// + /// If resolves to an enum type, returns the + /// JNI descriptor of its underlying primitive ("I", "B", "S", "J"). Otherwise + /// returns null. Mirrors legacy CallbackCode behavior, where enum parameters + /// are passed via their underlying integer JNI ABI rather than as objects. + /// + string? TryResolveEnumUnderlyingDescriptor (string managedType, string? assemblyName = null) + { + var typeDef = TryFindEnumTypeDefinition (managedType, assemblyName); + if (typeDef is null) { + return null; + } + + return GetEnumUnderlyingPrimitiveDescriptor (typeDef.Value.typeDef, typeDef.Value.index); + } + + /// + /// Returns true if , or — for array types — + /// its element type, resolves to an enum. The IL emitter uses this to encode + /// the type as a valuetype rather than a class in signatures and member refs. + /// + bool IsEnumOrEnumArray (string managedType, string? assemblyName = null) + { + while (managedType.EndsWith ("[]", StringComparison.Ordinal)) { + managedType = managedType.Substring (0, managedType.Length - 2); + } + + return TryFindEnumTypeDefinition (managedType, assemblyName) is not null; + } + + (TypeDefinition typeDef, AssemblyIndex index)? TryFindEnumTypeDefinition (string managedType, string? assemblyName = null) + { + // Prefer the typed assembly hint so two assemblies with same-named types + // (one enum, one not) resolve deterministically — assemblyCache + // enumeration order is non-deterministic. + if (assemblyName is { Length: > 0 } && + assemblyCache.TryGetValue (assemblyName, out var hintedIndex) && + hintedIndex.TypesByFullName.TryGetValue (managedType, out var hintedHandle)) { + var hintedDef = hintedIndex.Reader.GetTypeDefinition (hintedHandle); + if (IsEnumType (hintedDef, hintedIndex)) { + return (hintedDef, hintedIndex); + } + // Hinted assembly had a same-named non-enum; keep scanning. + } + + foreach (var index in assemblyCache.Values) { + if (!index.TypesByFullName.TryGetValue (managedType, out var handle)) { + continue; + } + + var typeDef = index.Reader.GetTypeDefinition (handle); + if (IsEnumType (typeDef, index)) { + return (typeDef, index); + } + } + + return null; + } + + /// + /// Returns with set + /// when the managed type — or, for arrays, the element type — resolves to an + /// enum. Used to thread enum-ness from the scanner to the emitter so that + /// signatures and member refs encode the type as a valuetype. + /// + TypeRefData EnrichTypeRefWithEnumInfo (TypeRefData type) + { + if (type.IsEnum || string.IsNullOrEmpty (type.ManagedTypeName)) { + return type; + } + + return IsEnumOrEnumArray (type.ManagedTypeName, type.AssemblyName) ? type with { IsEnum = true } : type; + } + + static bool IsEnumType (TypeDefinition typeDef, AssemblyIndex index) + { + var baseType = typeDef.BaseType; + if (baseType.IsNil) { + return false; + } + + var baseFullName = baseType.Kind switch { + HandleKind.TypeReference => MetadataTypeNameResolver.GetTypeFromReference (index.Reader, (TypeReferenceHandle) baseType, rawTypeKind: 0), + HandleKind.TypeDefinition => MetadataTypeNameResolver.GetTypeFromDefinition (index.Reader, (TypeDefinitionHandle) baseType, rawTypeKind: 0), + _ => null, + }; + + return baseFullName == "System.Enum"; + } + + static string GetEnumUnderlyingPrimitiveDescriptor (TypeDefinition typeDef, AssemblyIndex index) + { + foreach (var fieldHandle in typeDef.GetFields ()) { + var field = index.Reader.GetFieldDefinition (fieldHandle); + if ((field.Attributes & System.Reflection.FieldAttributes.Static) != 0) { + continue; + } + + var sig = field.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null); + return TryGetPrimitiveJniDescriptor (sig) ?? "I"; + } + + return "I"; + } + /// /// Walks the base type hierarchy collecting constructors that have [Register] attributes. /// Stops after the first base type with DoNotGenerateAcw=true (matching legacy CecilImporter). @@ -776,8 +991,11 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index, continue; } - // Found a matching base method — check if it has [Register] - if (TryGetMethodRegisterInfo (baseMethodDef, baseIndex, out var registerInfo, out _) && registerInfo is not null) { + // Found a matching base method — check if it has [Register]. + // [Export] / [ExportField] are AttributeUsage(Inherited=false), so a + // derived override must NOT inherit a base [Export] registration — + // only [Register]-driven entries propagate through inheritance. + if (TryGetMethodRegisterInfo (baseMethodDef, baseIndex, out var registerInfo, out var exportInfo) && registerInfo is not null && exportInfo is null) { return (registerInfo, baseTypeName, baseAssemblyName); } } @@ -881,7 +1099,7 @@ static bool HaveIdenticalParameterTypes (MethodDefinition method1, MethodDefinit return true; } - static void AddMarshalMethod (List methods, RegisterInfo registerInfo, MethodDefinition methodDef, AssemblyIndex index, ExportInfo? exportInfo = null, bool isInterfaceImplementation = false) + void AddMarshalMethod (List methods, RegisterInfo registerInfo, MethodDefinition methodDef, AssemblyIndex index, ExportInfo? exportInfo = null, bool isInterfaceImplementation = false) { // Skip methods that are just the JNI name (type-level [Register]) if (registerInfo.Signature is null && registerInfo.Connector is null) { @@ -891,12 +1109,27 @@ static void AddMarshalMethod (List methods, RegisterInfo regi bool isConstructor = registerInfo.JniName == "" || registerInfo.JniName == ".ctor"; bool isExport = exportInfo is not null; string managedName = index.Reader.GetString (methodDef.Name); + var managedSig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default); string jniSignature = registerInfo.Signature ?? "()V"; + // Only decode TypeRefData signatures for [Export] methods — they need precise + // managed type + assembly metadata for direct dispatch IL generation. + var managedTypeSig = isExport + ? methodDef.DecodeSignature (TypeRefSignatureTypeProvider.Instance, index) + : default; + var parameterKinds = exportInfo?.ParameterKinds ?? CreateDefaultExportKinds (managedSig.ParameterTypes.Length); + string declaringTypeName = ""; string declaringAssemblyName = ""; ParseConnectorDeclaringType (registerInfo.Connector, out declaringTypeName, out declaringAssemblyName); + var managedParameterTypes = new List (); + if (isExport) { + foreach (var parameterType in managedTypeSig.ParameterTypes) { + managedParameterTypes.Add (EnrichTypeRefWithEnumInfo (parameterType)); + } + } + methods.Add (new MarshalMethodInfo { JniName = registerInfo.JniName, JniSignature = jniSignature, @@ -905,6 +1138,14 @@ static void AddMarshalMethod (List methods, RegisterInfo regi DeclaringTypeName = declaringTypeName, DeclaringAssemblyName = declaringAssemblyName, NativeCallbackName = GetNativeCallbackName (registerInfo.Connector, managedName, isConstructor), + ManagedParameterTypes = managedParameterTypes, + ManagedParameterExportKinds = parameterKinds, + ManagedReturnType = isExport ? EnrichTypeRefWithEnumInfo (managedTypeSig.ReturnType) : new TypeRefData { + ManagedTypeName = managedSig.ReturnType, + AssemblyName = "System.Runtime", + }, + ManagedReturnExportKind = exportInfo?.ReturnKind ?? ExportParameterKindInfo.Unspecified, + IsStatic = (methodDef.Attributes & MethodAttributes.Static) == MethodAttributes.Static, IsConstructor = isConstructor, IsExport = isExport, IsInterfaceImplementation = isInterfaceImplementation, @@ -936,7 +1177,7 @@ static string GetJavaAccess (MethodAttributes access) return registerJniName; } - // Fall back to already-scanned results (component-attributed or CRC64-computed peers) + // Fall back to already-scanned results (component-attributed or hashed-package peers) if (results.TryGetValue ((baseTypeName, baseIndex.AssemblyName), out var basePeer)) { return basePeer.JavaName; } @@ -1048,6 +1289,21 @@ bool TryGetMethodRegisterInfo (MethodDefinition methodDef, AssemblyIndex index, thrownNames.Add (s); } } + } else if (named.Name == "Throws" && named.Value is ImmutableArray> throwsTypes) { + // Throws is `Type[]` in source, but the metadata blob serializes each + // `typeof(X)` as a string (assembly-qualified type name) routed through + // our CustomAttributeTypeProvider's GetTypeFromSerializedName. Resolve + // each to its [Register]-driven JNI internal name so the runtime can + // emit `throws` clauses on the generated Java method. + thrownNames ??= new List (throwsTypes.Length); + foreach (var item in throwsTypes) { + if (item.Value is string aqn) { + var jni = ResolveTypeOfArgumentToJniName (aqn); + if (jni is not null) { + thrownNames.Add (jni); + } + } + } } else if (named.Name == "SuperArgumentsString" && named.Value is string superArgs) { superArguments = superArgs; } @@ -1059,24 +1315,100 @@ bool TryGetMethodRegisterInfo (MethodDefinition methodDef, AssemblyIndex index, string resolvedExportName = exportName ?? throw new InvalidOperationException ("Export name should not be null at this point."); // Build JNI signature from method signature - var sig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default); - var jniSig = BuildJniSignatureFromManaged (sig); + var sig = methodDef.DecodeSignature (TypeRefSignatureTypeProvider.Instance, index); + var (parameterKinds, returnKind) = GetExportParameterKinds (methodDef, index, sig.ParameterTypes.Length); + var jniSig = BuildJniSignatureFromManaged (sig, parameterKinds, returnKind); return ( new RegisterInfo { JniName = resolvedExportName, Signature = jniSig, Connector = null, DoNotGenerateAcw = false }, - new ExportInfo { ThrownNames = thrownNames, SuperArgumentsString = superArguments } + new ExportInfo { + ThrownNames = thrownNames, + SuperArgumentsString = superArguments, + ParameterKinds = parameterKinds, + ReturnKind = returnKind, + } ); } - string BuildJniSignatureFromManaged (MethodSignature sig) + static List CreateDefaultExportKinds (int parameterCount) + { + var kinds = new List (parameterCount); + for (int i = 0; i < parameterCount; i++) { + kinds.Add (ExportParameterKindInfo.Unspecified); + } + return kinds; + } + + static (List parameterKinds, ExportParameterKindInfo returnKind) GetExportParameterKinds (MethodDefinition methodDef, AssemblyIndex index, int parameterCount) + { + var parameterKinds = CreateDefaultExportKinds (parameterCount); + var returnKind = ExportParameterKindInfo.Unspecified; + + foreach (var parameterHandle in methodDef.GetParameters ()) { + var parameter = index.Reader.GetParameter (parameterHandle); + var kind = GetExportParameterKind (parameter, index); + if (kind == ExportParameterKindInfo.Unspecified) { + continue; + } + + if (parameter.SequenceNumber == 0) { + returnKind = kind; + } else { + int parameterIndex = parameter.SequenceNumber - 1; + if (parameterIndex >= 0 && parameterIndex < parameterKinds.Count) { + parameterKinds [parameterIndex] = kind; + } + } + } + + return (parameterKinds, returnKind); + } + + static ExportParameterKindInfo GetExportParameterKind (Parameter parameter, AssemblyIndex index) + { + foreach (var caHandle in parameter.GetCustomAttributes ()) { + var ca = index.Reader.GetCustomAttribute (caHandle); + var attrName = AssemblyIndex.GetCustomAttributeName (ca, index.Reader); + if (attrName != "ExportParameterAttribute") { + continue; + } + + var value = index.DecodeAttribute (ca); + if (value.FixedArguments.Length > 0 && TryConvertExportParameterKind (value.FixedArguments [0].Value, out var ctorKind)) { + return ctorKind; + } + + foreach (var named in value.NamedArguments) { + if (named.Name == "Kind" && TryConvertExportParameterKind (named.Value, out var namedKind)) { + return namedKind; + } + } + } + + return ExportParameterKindInfo.Unspecified; + } + + static bool TryConvertExportParameterKind (object? value, out ExportParameterKindInfo kind) + { + if (value is int i && Enum.IsDefined (typeof (ExportParameterKindInfo), i)) { + kind = (ExportParameterKindInfo) i; + return true; + } + + kind = ExportParameterKindInfo.Unspecified; + return false; + } + + string BuildJniSignatureFromManaged (MethodSignature sig, IReadOnlyList parameterKinds, ExportParameterKindInfo returnKind) { var sb = new System.Text.StringBuilder (); sb.Append ('('); - foreach (var param in sig.ParameterTypes) { - sb.Append (ManagedTypeToJniDescriptor (param)); + for (int i = 0; i < sig.ParameterTypes.Length; i++) { + var exportKind = i < parameterKinds.Count ? parameterKinds [i] : ExportParameterKindInfo.Unspecified; + sb.Append (ManagedTypeToJniDescriptor (sig.ParameterTypes [i], exportKind)); } sb.Append (')'); - sb.Append (ManagedTypeToJniDescriptor (sig.ReturnType)); + sb.Append (ManagedTypeToJniDescriptor (sig.ReturnType, returnKind)); return sb.ToString (); } @@ -1088,8 +1420,8 @@ string BuildJniSignatureFromManaged (MethodSignature sig) (RegisterInfo registerInfo, ExportInfo exportInfo) ParseExportFieldAsMethod (CustomAttribute ca, MethodDefinition methodDef, AssemblyIndex index) { var managedName = index.Reader.GetString (methodDef.Name); - var sig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default); - var jniSig = BuildJniSignatureFromManaged (sig); + var sig = methodDef.DecodeSignature (TypeRefSignatureTypeProvider.Instance, index); + var jniSig = BuildJniSignatureFromManaged (sig, CreateDefaultExportKinds (sig.ParameterTypes.Length), ExportParameterKindInfo.Unspecified); return ( new RegisterInfo { JniName = managedName, Signature = jniSig, Connector = "__export__", DoNotGenerateAcw = false }, @@ -1102,23 +1434,56 @@ string BuildJniSignatureFromManaged (MethodSignature sig) /// via their [Register] attribute, falling back to "Ljava/lang/Object;" only /// for types that cannot be resolved (used by [Export] signature computation). /// - string ManagedTypeToJniDescriptor (string managedType) + string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindInfo exportKind = ExportParameterKindInfo.Unspecified) { - var primitive = TryGetPrimitiveJniDescriptor (managedType); + if (exportKind != ExportParameterKindInfo.Unspecified) { + return exportKind switch { + ExportParameterKindInfo.InputStream => "Ljava/io/InputStream;", + ExportParameterKindInfo.OutputStream => "Ljava/io/OutputStream;", + ExportParameterKindInfo.XmlPullParser => "Lorg/xmlpull/v1/XmlPullParser;", + ExportParameterKindInfo.XmlResourceParser => "Landroid/content/res/XmlResourceParser;", + _ => "Ljava/lang/Object;", + }; + } + + var primitive = TryGetPrimitiveJniDescriptor (managedType.ManagedTypeName); if (primitive is not null) { return primitive; } - if (managedType.EndsWith ("[]")) { - return $"[{ManagedTypeToJniDescriptor (managedType.Substring (0, managedType.Length - 2))}"; + if (managedType.ManagedTypeName.EndsWith ("[]", StringComparison.Ordinal)) { + return $"[{ManagedTypeToJniDescriptor (managedType with { ManagedTypeName = managedType.ManagedTypeName.Substring (0, managedType.ManagedTypeName.Length - 2) })}"; } // Try to resolve as a Java peer type with [Register] - var resolved = TryResolveJniObjectDescriptor (managedType); + var resolved = TryResolveJniObjectDescriptor (managedType.ManagedTypeName); if (resolved is not null) { return resolved; } + // Well-known interface types that legacy CallbackCode mapped explicitly + // to their canonical Java type. ICharSequence is in Mono.Android but is + // not annotated with [Register]; the non-generic collection interfaces + // live in System.Collections (no Java peer at all) and are wrapped at + // runtime by JavaList/JavaDictionary/JavaCollection. + var wellKnown = managedType.ManagedTypeName switch { + "Java.Lang.ICharSequence" => "Ljava/lang/CharSequence;", + "System.Collections.IList" => "Ljava/util/List;", + "System.Collections.IDictionary" => "Ljava/util/Map;", + "System.Collections.ICollection" => "Ljava/util/Collection;", + _ => null, + }; + if (wellKnown is not null) { + return wellKnown; + } + + // Enum parameters use their underlying primitive JNI ABI (matches legacy + // CallbackCode behavior). + var enumDescriptor = TryResolveEnumUnderlyingDescriptor (managedType.ManagedTypeName, managedType.AssemblyName); + if (enumDescriptor is not null) { + return enumDescriptor; + } + return "Ljava/lang/Object;"; } @@ -1377,12 +1742,12 @@ bool ExtendsJavaPeer (TypeDefinition typeDef, AssemblyIndex index) /// /// Compute both JNI name and compat JNI name for a type without [Register] or component Name. - /// JNI name uses CRC64 hash of "namespace:assemblyName" for the package. + /// JNI name uses the selected package naming policy hash for "namespace:assemblyName". /// Compat JNI name uses the raw managed namespace (lowercased). /// If a declaring type has [Register], its JNI name is used as prefix for both. /// Generic backticks are replaced with _. /// - static (string jniName, string compatJniName) ComputeAutoJniNames (TypeDefinition typeDef, AssemblyIndex index) + (string jniName, string compatJniName) ComputeAutoJniNames (TypeDefinition typeDef, AssemblyIndex index) { var (typeName, parentJniName, ns) = ComputeTypeNameParts (typeDef, index); @@ -1391,7 +1756,7 @@ bool ExtendsJavaPeer (TypeDefinition typeDef, AssemblyIndex index) return (name, name); } - var packageName = GetCrc64PackageName (ns, index.AssemblyName); + var packageName = GetHashedPackageName (ns, index.AssemblyName); var jniName = $"{packageName}/{typeName}"; string compatName = ns.Length == 0 @@ -1406,7 +1771,7 @@ bool ExtendsJavaPeer (TypeDefinition typeDef, AssemblyIndex index) /// registered JNI name or the outermost namespace. /// Matches JavaNativeTypeManager.ToJniName behavior: walks up declaring types /// and if a parent has [Register] or a component attribute JNI name, uses that - /// as prefix instead of computing CRC64 from the namespace. + /// as prefix instead of computing hashed package names from the namespace. /// static (string typeName, string? parentJniName, string ns) ComputeTypeNameParts (TypeDefinition typeDef, AssemblyIndex index) { @@ -1511,16 +1876,32 @@ static void ParseConnectorDeclaringType (string? connector, out string declaring declaringAssemblyName = nextComma >= 0 ? rest.Substring (0, nextComma).Trim () : rest.Trim (); } - static string GetCrc64PackageName (string ns, string assemblyName) + string GetHashedPackageName (string ns, string assemblyName) { // Only Mono.Android preserves the namespace directly if (assemblyName == "Mono.Android") { return ns.ToLowerInvariant ().Replace ('.', '/'); } - var data = System.Text.Encoding.UTF8.GetBytes ($"{ns}:{assemblyName}"); - var hash = System.IO.Hashing.Crc64.Hash (data); - return $"crc64{BitConverter.ToString (hash).Replace ("-", "").ToLowerInvariant ()}"; + return packageNamingPolicy switch { + HashedPackageNamingPolicy.LowercaseCrc64 => "crc64" + ScannerHashingHelper.ToLegacyCrc64 (ns, assemblyName), + HashedPackageNamingPolicy.Crc64 => "scrc64" + ScannerHashingHelper.ToCrc64 (ns, assemblyName), + _ => throw new InvalidOperationException ($"Unsupported package naming policy: {packageNamingPolicy}"), + }; + } + + static HashedPackageNamingPolicy ParsePackageNamingPolicy (string? packageNamingPolicy) + { + if (packageNamingPolicy.IsNullOrEmpty ()) { + return HashedPackageNamingPolicy.Crc64; + } + if (string.Equals (packageNamingPolicy, "Crc64", StringComparison.OrdinalIgnoreCase)) { + return HashedPackageNamingPolicy.Crc64; + } + if (string.Equals (packageNamingPolicy, "LowercaseCrc64", StringComparison.OrdinalIgnoreCase)) { + return HashedPackageNamingPolicy.LowercaseCrc64; + } + throw new ArgumentException ($"Unsupported AndroidPackageNamingPolicy value '{packageNamingPolicy}' for trimmable typemap. Supported values are 'Crc64' and 'LowercaseCrc64'.", nameof (packageNamingPolicy)); } static string ExtractNamespace (string fullName) @@ -1541,7 +1922,7 @@ static string ExtractShortName (string fullName) return (lastPlus >= 0 ? typePart.Slice (lastPlus + 1) : typePart).ToString (); } - static List BuildJavaConstructors (List marshalMethods) + List BuildJavaConstructors (List marshalMethods, TypeDefinition typeDef, AssemblyIndex index) { var ctors = new List (); int ctorIndex = 0; @@ -1549,16 +1930,89 @@ static List BuildJavaConstructors (List if (!mm.IsConstructor) { continue; } + // Try to find a managed ctor whose signature matches the JNI ctor. + // Unsupported managed parameter shapes fail in model building for [Export] + // constructors; non-[Export] registrations keep the legacy activation fallback. + var managedParams = TryGetMatchingPublicConstructorParameterTypes (typeDef, mm.JniSignature, index); ctors.Add (new JavaConstructorInfo { JniSignature = mm.JniSignature, ConstructorIndex = ctorIndex, SuperArgumentsString = mm.SuperArgumentsString, + HasMatchingManagedCtor = managedParams != null, + ManagedParameterTypes = managedParams ?? [], }); ctorIndex++; } return ctors; } + /// + /// Attempts to find a managed instance constructor on + /// whose parameters match the supplied JNI signature, and returns its managed + /// parameter types. Returns when no compatible + /// constructor exists. + /// + IReadOnlyList? TryGetMatchingPublicConstructorParameterTypes (TypeDefinition typeDef, string jniSignature, AssemblyIndex index) + { + var jniParams = JniSignatureHelper.ParseParameters (jniSignature); + foreach (var methodHandle in typeDef.GetMethods ()) { + var methodDef = index.Reader.GetMethodDefinition (methodHandle); + if ((methodDef.Attributes & MethodAttributes.Static) != 0) { + continue; + } + var name = index.Reader.GetString (methodDef.Name); + if (name != ".ctor") { + continue; + } + if ((methodDef.Attributes & MethodAttributes.MemberAccessMask) != MethodAttributes.Public) { + continue; + } + var sig = methodDef.DecodeSignature (TypeRefSignatureTypeProvider.Instance, genericContext: index); + if (sig.ParameterTypes.Length != jniParams.Count) { + continue; + } + // Skip ctors whose managed parameter signatures are not supported by the + // trimmable [Export]-style argument marshaller (generic instantiations, + // by-ref, pointers). Returning null here makes EmitUcoConstructor fall + // back to the legacy `(IntPtr, JniHandleOwnership)` activation ctor, + // which matches the legacy LLVM-IR behaviour for these shapes. + bool unsupportedParam = false; + foreach (var p in sig.ParameterTypes) { + var paramTypeName = p.ManagedTypeName; + if (paramTypeName.IndexOf ('<') >= 0 || paramTypeName.EndsWith ("&", StringComparison.Ordinal) || paramTypeName.EndsWith ("*", StringComparison.Ordinal)) { + unsupportedParam = true; + break; + } + } + if (unsupportedParam) { + continue; + } + if (!ManagedConstructorParametersMatchJniSignature (sig.ParameterTypes, jniParams)) { + continue; + } + // If multiple overloads with the same JNI-compatible signature exist, match + // the first public constructor in metadata order, like TypeManager.Activate. + return [.. sig.ParameterTypes]; + } + return null; + } + + bool ManagedConstructorParametersMatchJniSignature (IReadOnlyList managedParams, IReadOnlyList jniParams) + { + if (managedParams.Count != jniParams.Count) { + return false; + } + + for (int i = 0; i < managedParams.Count; i++) { + var managedDescriptor = ManagedTypeToJniDescriptor (managedParams [i]); + if (!string.Equals (managedDescriptor, jniParams [i].JniType, StringComparison.Ordinal)) { + return false; + } + } + + return true; + } + /// /// Checks a single method for [ExportField] and adds a JavaFieldInfo if found. /// Called inline during Pass 1 to avoid a separate iteration. @@ -1584,8 +2038,8 @@ void CollectExportField (MethodDefinition methodDef, AssemblyIndex index, List.Shared.Rent (byteCount); + try { + int bytesWritten = GetNamespaceAssemblyUtf8Bytes (ns, assemblyName, rented.AsSpan (0, byteCount)); + ulong crc = ulong.MaxValue; + ulong length = 0; + Crc64Helper.HashCore (rented, 0, bytesWritten, ref crc, ref length); + Span hash = stackalloc byte [8]; + BinaryPrimitives.WriteUInt64LittleEndian (hash, crc ^ length); + return ToHexString (hash); + } finally { + ArrayPool.Shared.Return (rented); + } + } + + internal static string ToCrc64 (string ns, string assemblyName) + { + const int stackallocThresholdBytes = 256; + int byteCount = GetNamespaceAssemblyUtf8ByteCount (ns, assemblyName); + Span utf8Buffer = byteCount <= stackallocThresholdBytes + ? stackalloc byte [stackallocThresholdBytes] + : new byte [byteCount]; + + int bytesWritten = GetNamespaceAssemblyUtf8Bytes (ns, assemblyName, utf8Buffer.Slice (0, byteCount)); + Span hash = stackalloc byte [8]; + System.IO.Hashing.Crc64.Hash (utf8Buffer.Slice (0, bytesWritten), hash); + ulong hashValue = BinaryPrimitives.ReadUInt64LittleEndian (hash); + BinaryPrimitives.WriteUInt64LittleEndian (hash, hashValue ^ (ulong) bytesWritten); + return ToHexString (hash); + } + + static int GetNamespaceAssemblyUtf8ByteCount (string ns, string assemblyName) + { + return System.Text.Encoding.UTF8.GetByteCount (ns) + 1 + System.Text.Encoding.UTF8.GetByteCount (assemblyName); + } + + static unsafe int GetNamespaceAssemblyUtf8Bytes (string ns, string assemblyName, Span destination) + { + int bytesWritten = 0; + fixed (char* nsPtr = ns) + fixed (byte* destinationPtr = destination) { + bytesWritten += System.Text.Encoding.UTF8.GetBytes (nsPtr, ns.Length, destinationPtr, destination.Length); + } + + destination [bytesWritten++] = (byte) ':'; + + fixed (char* assemblyNamePtr = assemblyName) + fixed (byte* destinationPtr = destination) { + bytesWritten += System.Text.Encoding.UTF8.GetBytes (assemblyNamePtr, assemblyName.Length, destinationPtr + bytesWritten, destination.Length - bytesWritten); + } + + return bytesWritten; + } + + static string ToHexString (ReadOnlySpan hash) + { + const int maxStackCharLength = 128; + int charLength = hash.Length * 2; + Span chars = charLength <= maxStackCharLength + ? stackalloc char [charLength] + : new char [charLength]; + + for (int i = 0, j = 0; i < hash.Length; i += 1, j += 2) { + byte b = hash [i]; + chars [j] = GetHexValue (b / 16); + chars [j + 1] = GetHexValue (b % 16); + } + + return ((ReadOnlySpan) chars).ToString (); + } + + static char GetHexValue (int value) => (char) (value < 10 ? value + '0' : value - 10 + 'a'); +} diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs index 87ed078adf2..779e4f76f70 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Immutable; +using System.Linq; using System.Reflection.Metadata; namespace Microsoft.Android.Sdk.TrimmableTypeMap; @@ -64,3 +65,66 @@ public string GetGenericInstantiation (string genericType, ImmutableArray signature) => "delegate*"; } + +sealed class TypeRefSignatureTypeProvider : ISignatureTypeProvider +{ + public static readonly TypeRefSignatureTypeProvider Instance = new (); + + public TypeRefData GetPrimitiveType (PrimitiveTypeCode typeCode) => new () { + ManagedTypeName = SignatureTypeProvider.Instance.GetPrimitiveType (typeCode), + AssemblyName = "System.Runtime", + }; + + public TypeRefData GetTypeFromDefinition (MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) + => MetadataTypeNameResolver.GetTypeRefFromDefinition (reader, handle, reader.GetString (reader.GetAssemblyDefinition ().Name), rawTypeKind); + + public TypeRefData GetTypeFromReference (MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) + => MetadataTypeNameResolver.GetTypeRefFromReference (reader, handle, reader.GetString (reader.GetAssemblyDefinition ().Name), rawTypeKind); + + public TypeRefData GetTypeFromSpecification (MetadataReader reader, AssemblyIndex genericContext, TypeSpecificationHandle handle, byte rawTypeKind) + { + var typeSpec = reader.GetTypeSpecification (handle); + return typeSpec.DecodeSignature (this, genericContext); + } + + public TypeRefData GetSZArrayType (TypeRefData elementType) => elementType with { + ManagedTypeName = $"{elementType.ManagedTypeName}[]", + }; + + public TypeRefData GetArrayType (TypeRefData elementType, ArrayShape shape) => elementType with { + ManagedTypeName = $"{elementType.ManagedTypeName}[{new string (',', shape.Rank - 1)}]", + }; + + public TypeRefData GetByReferenceType (TypeRefData elementType) => elementType with { + ManagedTypeName = $"{elementType.ManagedTypeName}&", + }; + + public TypeRefData GetPointerType (TypeRefData elementType) => elementType with { + ManagedTypeName = $"{elementType.ManagedTypeName}*", + }; + + public TypeRefData GetPinnedType (TypeRefData elementType) => elementType; + public TypeRefData GetModifiedType (TypeRefData modifier, TypeRefData unmodifiedType, bool isRequired) => unmodifiedType; + + public TypeRefData GetGenericInstantiation (TypeRefData genericType, ImmutableArray typeArguments) + { + return genericType with { + ManagedTypeName = $"{genericType.ManagedTypeName}<{string.Join (",", typeArguments.Select (t => t.ManagedTypeName))}>", + }; + } + + public TypeRefData GetGenericTypeParameter (AssemblyIndex genericContext, int index) => new () { + ManagedTypeName = $"!{index}", + AssemblyName = genericContext.AssemblyName, + }; + + public TypeRefData GetGenericMethodParameter (AssemblyIndex genericContext, int index) => new () { + ManagedTypeName = $"!!{index}", + AssemblyName = genericContext.AssemblyName, + }; + + public TypeRefData GetFunctionPointerType (MethodSignature signature) => new () { + ManagedTypeName = "delegate*", + AssemblyName = "System.Runtime", + }; +} diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs index e25293ff982..5a391f520b6 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs @@ -33,6 +33,7 @@ public TrimmableTypeMapResult Execute ( bool useSharedTypemapUniverse = false, ManifestConfig? manifestConfig = null, XDocument? manifestTemplate = null, + string? packageNamingPolicy = null, int maxArrayRank = 0) { _ = assemblies ?? throw new ArgumentNullException (nameof (assemblies)); @@ -41,7 +42,8 @@ public TrimmableTypeMapResult Execute ( if (maxArrayRank < 0) { throw new ArgumentOutOfRangeException (nameof (maxArrayRank), maxArrayRank, "Must be >= 0."); } - var (allPeers, assemblyManifestInfo) = ScanAssemblies (assemblies); + + var (allPeers, assemblyManifestInfo) = ScanAssemblies (assemblies, packageNamingPolicy, frameworkAssemblyNames); if (allPeers.Count == 0) { logger.LogNoJavaPeerTypesFound (); return new TrimmableTypeMapResult ([], [], allPeers); @@ -51,10 +53,12 @@ public TrimmableTypeMapResult Execute ( PropagateDeferredRegistrationToBaseClasses (allPeers); PropagateCannotRegisterToDescendants (allPeers); - var generatedAssemblies = GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion, useSharedTypemapUniverse, maxArrayRank); - var jcwPeers = allPeers.Where (p => - !frameworkAssemblyNames.Contains (p.AssemblyName) - || p.JavaName.StartsWith ("mono/", StringComparison.Ordinal)).ToList (); + var generatedAssemblies = GenerateTypeMapAssemblies ( + allPeers, + systemRuntimeVersion, + useSharedTypemapUniverse, + maxArrayRank); + var jcwPeers = allPeers.Where (ShouldGenerateJcw).ToList (); logger.LogGeneratingJcwFilesInfo (jcwPeers.Count, allPeers.Count); var generatedJavaSources = GenerateJcwJavaSources (jcwPeers); @@ -97,6 +101,24 @@ internal static List CollectApplicationRegistrationTypes (List allPeers, AssemblyManifestInfo assemblyManifestInfo, ManifestConfig config, XDocument? manifestTemplate) { @@ -133,16 +155,20 @@ GeneratedManifest GenerateManifest (List allPeers, AssemblyManifes return new GeneratedManifest (doc, providerNames.Count > 0 ? providerNames.ToArray () : []); } - (List peers, AssemblyManifestInfo manifestInfo) ScanAssemblies (IReadOnlyList<(string Name, PEReader Reader)> assemblies) + (List peers, AssemblyManifestInfo manifestInfo) ScanAssemblies (IReadOnlyList<(string Name, PEReader Reader)> assemblies, string? packageNamingPolicy, HashSet frameworkAssemblyNames) { - using var scanner = new JavaPeerScanner (); + using var scanner = new JavaPeerScanner (packageNamingPolicy, logger, frameworkAssemblyNames); var peers = scanner.Scan (assemblies); var manifestInfo = scanner.ScanAssemblyManifestInfo (); logger.LogJavaPeerScanInfo (assemblies.Count, peers.Count); return (peers, manifestInfo); } - List GenerateTypeMapAssemblies (List allPeers, Version systemRuntimeVersion, bool useSharedTypemapUniverse, int maxArrayRank) + List GenerateTypeMapAssemblies ( + List allPeers, + Version systemRuntimeVersion, + bool useSharedTypemapUniverse, + int maxArrayRank) { List<(string AssemblyName, List Peers)> peersByAssembly; diff --git a/src/Microsoft.Android.Templates/androidtest/TestInstrumentation.cs b/src/Microsoft.Android.Templates/androidtest/TestInstrumentation.cs index f05fa175843..207ce008ac2 100644 --- a/src/Microsoft.Android.Templates/androidtest/TestInstrumentation.cs +++ b/src/Microsoft.Android.Templates/androidtest/TestInstrumentation.cs @@ -20,41 +20,38 @@ public override void OnCreate(Bundle? arguments) Start(); } - public override void OnStart() + public override async void OnStart() { base.OnStart(); - Task.Run(async () => + var consumer = new ResultConsumer(this); + var bundle = new Bundle(); + try { - var consumer = new ResultConsumer(this); - var bundle = new Bundle(); - try - { - var writeablePath = Application.Context.GetExternalFilesDir(null)?.AbsolutePath ?? Path.GetTempPath(); - var resultsPath = Path.Combine(writeablePath, "TestResults"); - var builder = await TestApplication.CreateBuilderAsync([ - "--results-directory", resultsPath, - "--report-trx" - ]); - builder.AddMSTest(() => [GetType().Assembly]); - builder.AddTrxReportProvider(); - builder.TestHost.AddDataConsumer(_ => consumer); + var writeablePath = Application.Context.GetExternalFilesDir(null)?.AbsolutePath ?? Path.GetTempPath(); + var resultsPath = Path.Combine(writeablePath, "TestResults"); + var builder = await TestApplication.CreateBuilderAsync([ + "--results-directory", resultsPath, + "--report-trx" + ]); + builder.AddMSTest(() => [GetType().Assembly]); + builder.AddTrxReportProvider(); + builder.TestHost.AddDataConsumer(_ => consumer); - using ITestApplication app = await builder.BuildAsync(); - await app.RunAsync(); + using ITestApplication app = await builder.BuildAsync(); + await app.RunAsync(); - bundle.PutInt("passed", consumer.Passed); - bundle.PutInt("failed", consumer.Failed); - bundle.PutInt("skipped", consumer.Skipped); - bundle.PutString("resultsPath", consumer.TrxReportPath); - Finish(Result.Ok, bundle); - } - catch (Exception ex) - { - bundle.PutString("error", ex.ToString()); - Finish(Result.Canceled, bundle); - } - }); + bundle.PutInt("passed", consumer.Passed); + bundle.PutInt("failed", consumer.Failed); + bundle.PutInt("skipped", consumer.Skipped); + bundle.PutString("resultsPath", consumer.TrxReportPath); + Finish(Result.Ok, bundle); + } + catch (Exception ex) + { + bundle.PutString("error", ex.ToString()); + Finish(Result.Canceled, bundle); + } } class ResultConsumer(Instrumentation instrumentation) : IDataConsumer diff --git a/src/Mono.Android.Export/CallbackCode.cs b/src/Mono.Android.Export/CallbackCode.cs index a648a2b768c..16ffc454962 100644 --- a/src/Mono.Android.Export/CallbackCode.cs +++ b/src/Mono.Android.Export/CallbackCode.cs @@ -167,7 +167,7 @@ public CodeExpression PrepareCallback (CodeExpression arg) return GetCallbackPrep (type, parameter_kind, arg); } - public CodeExpression CleanupCallback (CodeExpression arg, CodeExpression orgArg) + public CodeItem CleanupCallback (CodeExpression arg, CodeExpression orgArg) { return GetCallbackCleanup (type, arg, orgArg); } @@ -190,7 +190,7 @@ public static CodeExpression GetCallbackPrep (Type type, ExportParameterKind pki // ArraySymbol: // return new string[] { String.Format ("{0}[] {1} = ({0}[]) JNIEnv.GetArray ({2}, JniHandleOwnership.DoNotTransfer, typeof ({3}));", ElementType, var_name, SymbolTable.GetNativeName (var_name), sym.FullName) }; case SymbolKind.Array: - return new CodeMethodCall (jnienv_getarray, arg, do_not_transfer_literal, new CodeLiteral (type)).CastTo (type); + return new CodeMethodCall (jnienv_getarray.MakeGenericMethod (type.GetElementType ()), arg).CastTo (type); // CharSequenceSymbol: // return new string[] { String.Format ("Java.Lang.ICharSequence {0} = Java.Lang.Object.GetObject ({1}, JniHandleOwnership.DoNotTransfer);", var_name, SymbolTable.GetNativeName (var_name)) }; @@ -239,7 +239,7 @@ public static CodeExpression GetCallbackPrep (Type type, ExportParameterKind pki return arg; } - public static CodeExpression GetCallbackCleanup (Type type, CodeExpression arg, CodeExpression orgArg) + public static CodeItem GetCallbackCleanup (Type type, CodeExpression arg, CodeExpression orgArg) { switch (GetKind (type)) { // ArraySymbol: @@ -249,13 +249,14 @@ public static CodeExpression GetCallbackCleanup (Type type, CodeExpression arg, // return result; case SymbolKind.Array: MethodInfo copyArrayMethod; - switch (Type.GetTypeCode (type)) { + Type elementType = type.GetElementType (); + switch (Type.GetTypeCode (elementType)) { case TypeCode.Empty: case TypeCode.DBNull: throw new NotSupportedException ("Only primitive types and IJavaObject is supported in array type in callback method parameter or return value"); case TypeCode.Object: - if (typeof (IJavaObject).IsAssignableFrom (type)) - copyArrayMethod = typeof (JNIEnv).GetMethod ("CopyArray", new Type [] { typeof (IJavaObject), typeof (IntPtr) }); + if (typeof (IJavaObject).IsAssignableFrom (elementType)) + copyArrayMethod = typeof (JNIEnv).GetMethod ("CopyArray", new Type [] { typeof (IJavaObject[]), typeof (IntPtr) }); else goto case TypeCode.Empty; break; @@ -263,7 +264,13 @@ public static CodeExpression GetCallbackCleanup (Type type, CodeExpression arg, copyArrayMethod = typeof (JNIEnv).GetMethod ("CopyArray", new Type [] { type, typeof (IntPtr) }); break; } - return new CodeWhen (arg.IsNull, arg, new CodeMethodCall (copyArrayMethod, arg, orgArg)); + if (copyArrayMethod == null) + throw new NotSupportedException ($"JNIEnv.CopyArray does not support array type '{type}'."); + CodeBlock copyArrayBlock = new CodeBlock (); + copyArrayBlock.Add (new CodeMethodCall (copyArrayMethod, arg, orgArg)); + return new CodeIf (CodeExpression.Not (arg.IsNull)) { + TrueBlock = copyArrayBlock, + }; // CharSequenceSymbol: // return new string[] { String.Format ("Java.Lang.ICharSequence {0} = Java.Lang.Object.GetObject ({1}, JniHandleOwnership.DoNotTransfer);", var_name, SymbolTable.GetNativeName (var_name)) }; @@ -711,10 +718,12 @@ CodeMethod GenerateNativeCallbackDelegate (string generatedMethodName) // sw.WriteLine ("{0}\t{1} {2};", indent, Parameters.HasCleanup ? RetVal.NativeType + " __ret =" : "return", RetVal.ToNative (opt, call)); var callArgs = new List (); for (int i = 0; i < parameter_type_infos.Count; i++) { - if (parameter_type_infos [i].NeedsPrep) - callArgs.Add (parameter_type_infos [i].PrepareCallback (mgen.GetArg (i + 2))); - else + if (parameter_type_infos [i].NeedsPrep) { + var preparedArg = parameter_type_infos [i].PrepareCallback (mgen.GetArg (i + 2)); + callArgs.Add (builder.DeclareVariable (method.GetParameters () [i].ParameterType, preparedArg)); + } else { callArgs.Add (parameter_type_infos [i].FromNative (mgen.GetArg (i + 2))); + } } CodeMethodCall call; if (method.IsStatic) @@ -740,7 +749,7 @@ CodeMethod GenerateNativeCallbackDelegate (string generatedMethodName) // sw.WriteLine ("{0}\t{1}", indent, cleanup); var callbackCleanup = new List (); for (int i = 0; i < parameter_type_infos.Count; i++) - builder.CurrentBlock.Add (parameter_type_infos [i].CleanupCallback (callArgs [i], mgen.GetArg (i))); + builder.CurrentBlock.Add (parameter_type_infos [i].CleanupCallback (callArgs [i], mgen.GetArg (i + 2))); //if (!IsVoid && Parameters.HasCleanup) // sw.WriteLine ("{0}\treturn __ret;", indent); diff --git a/src/Mono.Android/Android.Graphics/AndroidBitmapInfo.cs b/src/Mono.Android/Android.Graphics/AndroidBitmapInfo.cs index 053b896a6ac..6898e2d8280 100644 --- a/src/Mono.Android/Android.Graphics/AndroidBitmapInfo.cs +++ b/src/Mono.Android/Android.Graphics/AndroidBitmapInfo.cs @@ -3,33 +3,61 @@ namespace Android.Graphics { #if ANDROID_8 + /// + /// Provides information about the pixel buffer of an Android . + /// This struct corresponds to the native AndroidBitmapInfo type from the Android NDK. + /// + /// + /// + /// Use this struct with to inspect the dimensions, + /// stride, and pixel format of a bitmap's underlying pixel buffer. + /// + /// + /// See the Android NDK Bitmap documentation + /// for more information about the native type. + /// + /// public struct AndroidBitmapInfo : IEquatable { internal uint width, height, stride; internal int format; internal uint flags; + /// + /// Gets the width of the bitmap in pixels. + /// public uint Width { get {return width;} } + /// + /// Gets the height of the bitmap in pixels. + /// public uint Height { get {return height;} } + /// + /// Gets the number of bytes between rows in the pixel buffer. + /// public uint Stride { get {return stride;} } + /// + /// Gets the pixel format of the bitmap. + /// public Format Format { get {return (Format) format;} } + /// public override int GetHashCode () { return (int) width ^ (int) height ^ (int) stride ^ format ^ (int) flags; } + /// public override bool Equals (object value) { if (!(value is AndroidBitmapInfo)) @@ -37,6 +65,11 @@ public override bool Equals (object value) return Equals ((AndroidBitmapInfo) value); } + /// + /// Determines whether the specified is equal to this instance. + /// + /// The to compare with this instance. + /// if the specified value is equal to this instance; otherwise, . public bool Equals (AndroidBitmapInfo value) { return value.width == width && diff --git a/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs b/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs index 1c3c0262be6..cd487e8e3e4 100644 --- a/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs +++ b/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs @@ -26,8 +26,6 @@ public static partial class AndroidEnvironment { static IX509TrustManager? sslTrustManager; static KeyStore? certStore; static object lock_ = new object (); - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - static Type? httpMessageHandlerType; static void SetupTrustManager () { @@ -253,66 +251,9 @@ static void DetectCPUAndArchitecture (out ushort builtForCPU, out ushort running // This is invoked by // System.Net.Http.dll!System.Net.Http.HttpClient.cctor // DO NOT REMOVE - [DynamicDependency (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor, typeof (Xamarin.Android.Net.AndroidMessageHandler))] static HttpMessageHandler GetHttpMessageHandler () { - [UnconditionalSuppressMessage ("Trimming", "IL2057", Justification = "Preserved by the MarkJavaObjects trimmer step.")] - [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - static Type? TypeGetType (string typeName) => - Type.GetType (typeName, throwOnError: false); - - if (httpMessageHandlerType is null) { - var handlerTypeName = Environment.GetEnvironmentVariable ("XA_HTTP_CLIENT_HANDLER_TYPE")?.Trim (); - Type? handlerType = null; - if (!String.IsNullOrEmpty (handlerTypeName)) - handlerType = TypeGetType (handlerTypeName); - - if (handlerType is null || !IsAcceptableHttpMessageHandlerType (handlerType)) { - handlerType = GetFallbackHttpMessageHandlerType (); - } - - httpMessageHandlerType = handlerType; - } - - return (HttpMessageHandler) Activator.CreateInstance (httpMessageHandlerType) - ?? throw new InvalidOperationException ($"Could not create an instance of HTTP message handler type {httpMessageHandlerType.AssemblyQualifiedName}"); - } - - static bool IsAcceptableHttpMessageHandlerType (Type handlerType) - { - if (Extends (handlerType, "System.Net.Http.HttpClientHandler, System.Net.Http")) { - // It's not possible to construct HttpClientHandler in this method because it would cause infinite recursion - // as HttpClientHandler's constructor calls the GetHttpMessageHandler function - Logger.Log (LogLevel.Warn, "MonoAndroid", $"The type {handlerType.AssemblyQualifiedName} cannot be used as the native HTTP handler because it is derived from System.Net.Htt.HttpClientHandler. Use a type that extends System.Net.Http.HttpMessageHandler instead."); - return false; - } - if (!Extends (handlerType, "System.Net.Http.HttpMessageHandler, System.Net.Http")) { - Logger.Log (LogLevel.Warn, "MonoAndroid", $"The type {handlerType.AssemblyQualifiedName} set as the default HTTP handler is invalid. Use a type that extends System.Net.Http.HttpMessageHandler."); - return false; - } - - return true; - } - - static bool Extends ( - Type handlerType, - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - string baseTypeName) - { - var baseType = Type.GetType (baseTypeName, throwOnError: false); - return baseType?.IsAssignableFrom (handlerType) ?? false; + return new Xamarin.Android.Net.AndroidMessageHandler (); } - - [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - static Type GetFallbackHttpMessageHandlerType () - { - const string typeName = "Xamarin.Android.Net.AndroidMessageHandler, Mono.Android"; - var handlerType = Type.GetType (typeName, throwOnError: false) - ?? throw new InvalidOperationException ($"The {typeName} was not found. The type was probably linked away."); - - Logger.Log (LogLevel.Info, "MonoAndroid", $"Using {typeName} as the native HTTP message handler."); - return handlerType; - } - } } diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 6995158bd7d..918b8377b54 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -3,9 +3,7 @@ using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Versioning; -using System.Text; using System.Threading; using System.Reflection; @@ -313,13 +311,6 @@ public override void DeleteWeakGlobalReference (ref JniObjectReference value) } class AndroidTypeManager : JniRuntime.JniTypeManager { - struct JniRemappingReplacementMethod - { - public string target_type; - public string target_name; - public bool is_static; - }; - bool jniAddNativeMethodRegistrationAttributePresent; const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; @@ -363,84 +354,17 @@ protected override IEnumerable GetSimpleReferences (Type type) protected override IReadOnlyList? GetStaticMethodFallbackTypesCore (string jniSimpleReference) { - ReadOnlySpan name = jniSimpleReference; - int slash = name.LastIndexOf ('/'); - var desugarType = new StringBuilder (jniSimpleReference.Length + "Desugar".Length); - if (slash > 0) { - desugarType.Append (name.Slice (0, slash+1)) - .Append ("Desugar") - .Append (name.Slice (slash+1)); - } else { - desugarType.Append ("Desugar").Append (name); - } - - var typeWithPrefix = desugarType.ToString (); - var typeWithSuffix = $"{jniSimpleReference}$-CC"; - - var replacements = new[]{ - GetReplacementTypeCore (typeWithPrefix) ?? typeWithPrefix, - GetReplacementTypeCore (typeWithSuffix) ?? typeWithSuffix, - }; - - if (Logger.LogAssembly) { - var message = $"Remapping type `{jniSimpleReference}` to one one of {{ `{replacements[0]}`, `{replacements[1]}` }}"; - Logger.Log (LogLevel.Debug, "monodroid-assembly", message); - } - return replacements; + return JniRemappingLookup.GetStaticMethodFallbackTypes (jniSimpleReference, useReplacementTypes: true); } protected override string? GetReplacementTypeCore (string jniSimpleReference) { - if (!JNIEnvInit.jniRemappingInUse) { - return null; - } - - IntPtr ret = RuntimeNativeMethods._monodroid_lookup_replacement_type (jniSimpleReference); - if (ret == IntPtr.Zero) { - return null; - } - - return Marshal.PtrToStringAnsi (ret); + return JniRemappingLookup.GetReplacementType (jniSimpleReference); } protected override JniRuntime.ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSourceType, string jniMethodName, string jniMethodSignature) { - if (!JNIEnvInit.jniRemappingInUse) { - return null; - } - - IntPtr retInfo = RuntimeNativeMethods._monodroid_lookup_replacement_method_info (jniSourceType, jniMethodName, jniMethodSignature); - if (retInfo == IntPtr.Zero) { - return null; - } - - var method = new JniRemappingReplacementMethod (); - method = Marshal.PtrToStructure(retInfo); - var newSignature = jniMethodSignature; - - int? paramCount = null; - if (method.is_static) { - paramCount = JniMemberSignature.GetParameterCountFromMethodSignature (jniMethodSignature) + 1; - newSignature = $"(L{jniSourceType};" + jniMethodSignature.Substring ("(".Length); - } - - if (Logger.LogAssembly) { - var message = $"Remapping method `{jniSourceType}.{jniMethodName}{jniMethodSignature}` to " + - $"`{method.target_type}.{method.target_name}{newSignature}`; " + - $"param-count: {paramCount}; instance-to-static? {method.is_static}"; - Logger.Log (LogLevel.Debug, "monodroid-assembly", message); - } - - return new JniRuntime.ReplacementMethodInfo { - SourceJniType = jniSourceType, - SourceJniMethodName = jniMethodName, - SourceJniMethodSignature = jniMethodSignature, - TargetJniType = method.target_type, - TargetJniMethodName = method.target_name, - TargetJniMethodSignature = newSignature, - TargetJniMethodParameterCount = paramCount, - TargetJniMethodInstanceToStatic = method.is_static, - }; + return JniRemappingLookup.GetReplacementMethodInfo (jniSourceType, jniMethodName, jniMethodSignature); } [return: DynamicallyAccessedMembers (Constructors)] @@ -908,7 +832,11 @@ internal void RemovePeer (IJavaPeerable value, IntPtr hash) return null; } - public override void ActivatePeer (IJavaPeerable? self, JniObjectReference reference, ConstructorInfo cinfo, object? []? argumentValues) + public override void ActivatePeer ( + JniObjectReference reference, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type type, + ConstructorInfo cinfo, + object?[]? argumentValues) { Java.Interop.TypeManager.Activate (reference.Handle, cinfo, argumentValues); } diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs b/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs index 57c9ca7d1d9..9fec2a26df2 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs @@ -17,6 +17,10 @@ static AndroidRuntimeInternal () { if (RuntimeFeature.IsMonoRuntime) { mono_unhandled_exception = MonoUnhandledException; + } else if (RuntimeFeature.IsCoreClrRuntime) { + mono_unhandled_exception = CoreClrUnhandledException; + } else if (RuntimeFeature.IsNativeAotRuntime) { + mono_unhandled_exception = CoreClrUnhandledException; } else { mono_unhandled_exception = CoreClrUnhandledException; } diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 20b635cfb70..ebfc81cc39b 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -138,6 +138,8 @@ internal static void PropagateUncaughtException (IntPtr env, IntPtr javaThreadPt MonoDroidUnhandledException (innerException ?? javaException); } else if (RuntimeFeature.IsCoreClrRuntime) { ExceptionHandling.RaiseAppDomainUnhandledExceptionEvent (innerException ?? javaException); + } else if (RuntimeFeature.IsNativeAotRuntime) { + ExceptionHandling.RaiseAppDomainUnhandledExceptionEvent (innerException ?? javaException); } else { throw new NotSupportedException ("Internal error: unknown runtime not supported"); } diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index 2e8898e4616..3d339d16eb7 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -97,65 +97,48 @@ internal static void NativeAotInitializeMaxGrefGet () } } - // This is needed to initialize e.g. logging before anything else (useful with e.g. gref - // logging where runtime creation causes several grefs to be created and logged without - // stack traces because logging categories on the managed side aren't yet set) - internal static void InitializeJniRuntimeEarly (JnienvInitializeArgs args) + internal static void InitializeBeforeRuntimeCreation (JnienvInitializeArgs args) { - Logger.SetLogCategories ((LogCategories)args.logCategories); + InitializeCommonState (args); + InitializeTrimmableTypeMapDataIfNeeded (); } // NOTE: should have different name than `Initialize` to avoid: // * Assertion at /__w/1/s/src/mono/mono/metadata/icall.c:6258, condition `!only_unmanaged_callers_only' not met - internal static void InitializeJniRuntime (JniRuntime runtime, JnienvInitializeArgs args) + // Only used for NativeAOT after the runtime has been created. MonoVM and CoreCLR use Initialize(). + internal static void InitializeNativeAotRuntime (JniRuntime runtime, JnienvInitializeArgs args) { + if (!RuntimeFeature.IsNativeAotRuntime) { + throw new NotSupportedException ("JNIEnvInit.InitializeNativeAotRuntime can only be used to initialize NativeAOT."); + } + if (RuntimeFeature.IsMonoRuntime || RuntimeFeature.IsCoreClrRuntime) { + throw new NotSupportedException ("Internal error: NativeAOT cannot be enabled with MonoVM or CoreCLR."); + } + androidRuntime = runtime; JniRuntime.SetCurrent (runtime); + RegisterTrimmableTypeMapNativeMethodsIfNeeded (); SetSynchronizationContext (); } + // Only used for MonoVM and CoreCLR. NativeAOT uses InitializeNativeAotRuntime(). [UnmanagedCallersOnly] internal static unsafe void Initialize (JnienvInitializeArgs* args) { - // Should not be allowed - if (RuntimeFeature.IsMonoRuntime && RuntimeFeature.IsCoreClrRuntime) { - throw new NotSupportedException ("Internal error: both RuntimeFeature.IsMonoRuntime and RuntimeFeature.IsCoreClrRuntime are enabled"); + if (RuntimeFeature.IsNativeAotRuntime) { + throw new NotSupportedException ("JNIEnvInit.Initialize cannot be used to initialize NativeAOT."); + } + if (RuntimeFeature.IsMonoRuntime == RuntimeFeature.IsCoreClrRuntime) { + throw new NotSupportedException ("Internal error: exactly one of RuntimeFeature.IsMonoRuntime or RuntimeFeature.IsCoreClrRuntime must be enabled."); } IntPtr total_timing_sequence = IntPtr.Zero; IntPtr partial_timing_sequence = IntPtr.Zero; - Logger.SetLogCategories ((LogCategories)args->logCategories); - - gref_gc_threshold = args->grefGcThreshold; + InitializeBeforeRuntimeCreation (*args); - jniRemappingInUse = args->jniRemappingInUse; - MarshalMethodsEnabled = args->marshalMethodsEnabled; - java_class_loader = args->grefLoader; - - BoundExceptionType = (BoundExceptionType)args->ioExceptionType; - if (RuntimeFeature.TrimmableTypeMap) { - InitializeTrimmableTypeMapData (); - } - - JniRuntime.JniTypeManager typeManager; - JniRuntime.JniValueManager? valueManager = null; - if (RuntimeFeature.TrimmableTypeMap) { - typeManager = new TrimmableTypeMapTypeManager (); - valueManager = new JavaMarshalValueManager (); - } else if (RuntimeFeature.ManagedTypeMap) { - typeManager = new ManagedTypeManager (); - } else { - typeManager = new AndroidTypeManager (args->jniAddNativeMethodRegistrationAttributePresent != 0); - } - if (RuntimeFeature.IsMonoRuntime) { - valueManager = new AndroidValueManager (); - } else if (RuntimeFeature.IsCoreClrRuntime) { - // Note: this will be removed once trimmable typemap is the only supported option for CoreCLR runtime - valueManager ??= new JavaMarshalValueManager (); - } else { - throw new NotSupportedException ("Internal error: unknown runtime not supported"); - } + JniRuntime.JniTypeManager typeManager = CreateTypeManager (*args); + JniRuntime.JniValueManager valueManager = CreateValueManager (); androidRuntime = new AndroidRuntime ( args->env, args->javaVm, @@ -165,18 +148,7 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) args->jniAddNativeMethodRegistrationAttributePresent != 0 ); JniRuntime.SetCurrent (androidRuntime); - if (RuntimeFeature.TrimmableTypeMap) { - // TypeMapLoader.Initialize() only loads managed typemap data. Registering - // mono.android.Runtime natives requires JniRuntime.Current and its ClassLoader. - TrimmableTypeMap.RegisterNativeMethods (); - } - - grefIGCUserPeer_class = args->grefIGCUserPeer; - grefGCUserPeerable_class = args->grefGCUserPeerable; - - PropagateExceptions = args->brokenExceptionTransitions == 0; - - JavaNativeTypeManager.PackageNamingPolicy = (PackageNamingPolicy)args->packageNamingPolicy; + RegisterTrimmableTypeMapNativeMethodsIfNeeded (); if (args->managedMarshalMethodsLookupEnabled) { delegate* unmanaged getFunctionPointer = &ManagedMarshalMethodsLookupTable.GetFunctionPointer; @@ -196,6 +168,65 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) [UnmanagedCallConv (CallConvs = new[] { typeof (CallConvCdecl) })] private static unsafe partial void xamarin_app_init (IntPtr env, delegate* unmanaged get_function_pointer); + internal static JniRuntime.JniTypeManager CreateTypeManager (JnienvInitializeArgs args) + { + if (RuntimeFeature.TrimmableTypeMap) { + return new TrimmableTypeMapTypeManager (); + } + + if (RuntimeFeature.IsNativeAotRuntime || RuntimeFeature.ManagedTypeMap) { + return new ManagedTypeManager (); + } + + return new AndroidTypeManager (args.jniAddNativeMethodRegistrationAttributePresent != 0); + } + + internal static JniRuntime.JniValueManager CreateValueManager () + { + if (RuntimeFeature.IsMonoRuntime) { + return new AndroidValueManager (); + } + + if (RuntimeFeature.IsCoreClrRuntime || RuntimeFeature.IsNativeAotRuntime) { + return new JavaMarshalValueManager (); + } + + throw new NotSupportedException ("Internal error: unknown runtime not supported"); + } + + static void InitializeCommonState (JnienvInitializeArgs args) + { + Logger.SetLogCategories ((LogCategories)args.logCategories); + + gref_gc_threshold = args.grefGcThreshold; + jniRemappingInUse = args.jniRemappingInUse; + MarshalMethodsEnabled = args.marshalMethodsEnabled; + java_class_loader = args.grefLoader; + + BoundExceptionType = (BoundExceptionType)args.ioExceptionType; + grefIGCUserPeer_class = args.grefIGCUserPeer; + grefGCUserPeerable_class = args.grefGCUserPeerable; + PropagateExceptions = args.brokenExceptionTransitions == 0; + + JavaNativeTypeManager.PackageNamingPolicy = (PackageNamingPolicy)args.packageNamingPolicy; + } + + static void InitializeTrimmableTypeMapDataIfNeeded () + { + if (RuntimeFeature.TrimmableTypeMap) { + InitializeTrimmableTypeMapData (); + } + } + + static void RegisterTrimmableTypeMapNativeMethodsIfNeeded () + { + if (RuntimeFeature.TrimmableTypeMap) { + // TypeMapLoader.Initialize() only loads managed typemap data. Registering + // mono.android.Runtime natives requires JniRuntime.Current and its ClassLoader. + TrimmableTypeMap.RegisterNativeMethods (); + } + } + // Separate method so the JIT doesn't try to resolve TypeMapLoader (from _Microsoft.Android.TypeMaps.dll) // when compiling JNIEnvInit.Initialize() in non-trimmable builds where that assembly isn't present. [MethodImpl (MethodImplOptions.NoInlining)] diff --git a/src/Mono.Android/Android.Runtime/JValue.cs b/src/Mono.Android/Android.Runtime/JValue.cs index 5adece43f03..db7821d928b 100644 --- a/src/Mono.Android/Android.Runtime/JValue.cs +++ b/src/Mono.Android/Android.Runtime/JValue.cs @@ -3,6 +3,16 @@ namespace Android.Runtime { + /// + /// Represents a JNI jvalue union, used to pass arguments to Java methods + /// via JNI function calls. + /// + /// + /// + /// Each constructor corresponds to one of the JNI primitive types or an object reference. + /// See the JNI Type Specification. + /// + /// [StructLayout(LayoutKind.Explicit)] public struct JValue { #pragma warning disable 0414 @@ -17,62 +27,122 @@ public struct JValue { [FieldOffset(0)] IntPtr l; #pragma warning restore 0414 + /// + /// A representing a JNI NULL object reference. + /// public static JValue Zero = new JValue (IntPtr.Zero); + /// + /// Creates a from a value, + /// corresponding to the JNI jboolean type. + /// + /// The Boolean value. public JValue (bool value) { this = new JValue (); z = value; } + /// + /// Creates a from an value, + /// corresponding to the JNI jbyte type. + /// + /// The signed byte value. public JValue (sbyte value) { this = new JValue (); b = value; } + /// + /// Creates a from a value, + /// corresponding to the JNI jchar type. + /// + /// The character value. public JValue (char value) { this = new JValue (); c = value; } + /// + /// Creates a from a value, + /// corresponding to the JNI jshort type. + /// + /// The short integer value. public JValue (short value) { this = new JValue (); s = value; } + /// + /// Creates a from an value, + /// corresponding to the JNI jint type. + /// + /// The integer value. public JValue (int value) { this = new JValue (); i = value; } + /// + /// Creates a from a value, + /// corresponding to the JNI jlong type. + /// + /// The long integer value. public JValue (long value) { this = new JValue (); j = value; } + /// + /// Creates a from a value, + /// corresponding to the JNI jfloat type. + /// + /// The single-precision floating-point value. public JValue (float value) { this = new JValue (); f = value; } + /// + /// Creates a from a value, + /// corresponding to the JNI jdouble type. + /// + /// The double-precision floating-point value. public JValue (double value) { this = new JValue (); d = value; } + /// + /// Creates a from an value, + /// corresponding to a JNI jobject reference. + /// + /// The raw JNI object reference handle. public JValue (IntPtr value) { this = new JValue (); l = value; } + /// + /// Creates a from an instance, + /// corresponding to a JNI jobject reference. + /// + /// + /// The Java object instance. If , the resulting + /// will contain . + /// + /// + /// This constructor extracts the from the + /// provided object, or passes if the object is . + /// public JValue (IJavaObject value) { this = new JValue (); diff --git a/src/Mono.Android/Android.Runtime/JavaProxyThrowable.cs b/src/Mono.Android/Android.Runtime/JavaProxyThrowable.cs index d221bf48054..a8b48686157 100644 --- a/src/Mono.Android/Android.Runtime/JavaProxyThrowable.cs +++ b/src/Mono.Android/Android.Runtime/JavaProxyThrowable.cs @@ -39,7 +39,7 @@ public static JavaProxyThrowable Create (Exception innerException) return proxy; } - (int lineNumber, string? methodName, string? className) GetFrameInfo (StackFrame? managedFrame, MethodBase? managedMethod) + (int lineNumber, string? methodName, string? className) GetFrameInfo (StackFrame? managedFrame, DiagnosticMethodInfo? managedMethod) { string? methodName = null; string? className = null; @@ -47,7 +47,7 @@ public static JavaProxyThrowable Create (Exception innerException) if (managedFrame == null) { if (managedMethod != null) { methodName = managedMethod.Name; - className = managedMethod.DeclaringType?.FullName; + className = managedMethod.DeclaringTypeName; } return (-1, methodName, className); @@ -69,7 +69,7 @@ public static JavaProxyThrowable Create (Exception innerException) methodName = managedMethod.Name; } - return (lineNumber, methodName, managedMethod.DeclaringType?.FullName); + return (lineNumber, methodName, managedMethod.DeclaringTypeName); } string frameString = managedFrame.ToString (); @@ -116,14 +116,6 @@ public static JavaProxyThrowable Create (Exception innerException) void TranslateStackTrace () { - // FIXME: https://github.com/xamarin/xamarin-android/issues/8724 - // StackFrame.GetMethod() will return null under NativeAOT; - // However, you can still get useful information from StackFrame.ToString(): - // MainActivity.OnCreate() + 0x37 at offset 55 in file:line:column :0:0 - [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "StackFrame.GetMethod() is \"best attempt\", we handle null & exceptions")] - static MethodBase? StackFrameGetMethod (StackFrame frame) => - frame.GetMethod (); - var trace = new StackTrace (InnerException, fNeedFileInfo: true); if (trace.FrameCount <= 0) { return; @@ -145,10 +137,10 @@ void TranslateStackTrace () const string Unknown = "Unknown"; for (int i = 0; i < frames.Length; i++) { StackFrame managedFrame = frames[i]; - MethodBase? managedMethod = StackFrameGetMethod (managedFrame); + DiagnosticMethodInfo? methodInfo = DiagnosticMethodInfo.Create (managedFrame); // https://developer.android.com/reference/java/lang/StackTraceElement?hl=en#StackTraceElement(java.lang.String,%20java.lang.String,%20java.lang.String,%20int) - (int lineNumber, string? methodName, string? declaringClass) = GetFrameInfo (managedFrame, managedMethod); + (int lineNumber, string? methodName, string? declaringClass) = GetFrameInfo (managedFrame, methodInfo); var throwableFrame = new StackTraceElement ( declaringClass: declaringClass ?? Unknown, methodName: methodName ?? Unknown, diff --git a/src/Mono.Android/Android.Security/KeyChain.cs b/src/Mono.Android/Android.Security/KeyChain.cs index 711da0cf6c9..86a648f137b 100644 --- a/src/Mono.Android/Android.Security/KeyChain.cs +++ b/src/Mono.Android/Android.Security/KeyChain.cs @@ -8,6 +8,20 @@ namespace Android.Security { public partial class KeyChain { + /// + /// Retrieves an with its associated private key from the Android system KeyChain. + /// + /// The Android context used to access the KeyChain. + /// The alias of the private key and certificate chain to retrieve. + /// + /// An containing the certificate and private key, + /// or if the private key or certificate chain is not available for the given alias. + /// + /// + /// This method combines and + /// into a single call that returns a .NET suitable for use with + /// or other TLS APIs. + /// public static X509Certificate2? GetX509Certificate2WithPrivateKey (Android.Content.Context context, string alias) { var privateKey = KeyChain.GetPrivateKey (context, alias); @@ -26,6 +40,22 @@ public partial class KeyChain return certificate; } + /// + /// Displays the system UI for the user to select a private key alias, filtering by URI. + /// + /// The activity to use as the parent for the certificate selection UI. + /// The acceptable types of asymmetric keys, or to allow any type. + /// The acceptable certificate issuers for the certificate matching the private key, or to allow any issuer. + /// The URI to filter by, or to allow the user to choose any alias. + /// The initial alias to preselect if available, or for no preselection. + /// + /// A representing the asynchronous operation. The result is the alias chosen by the user, + /// or if the user cancelled the selection. + /// + /// + /// This is an async wrapper around . + /// This overload requires Android API 23 or later. + /// [SupportedOSPlatform("android23.0")] public static async Task ChoosePrivateKeyAliasAsync ( Android.App.Activity activity, @@ -39,6 +69,22 @@ public partial class KeyChain return await tcs.Task; } + /// + /// Displays the system UI for the user to select a private key alias, filtering by host and port. + /// + /// The activity to use as the parent for the certificate selection UI. + /// The acceptable types of asymmetric keys, or to allow any type. + /// The acceptable certificate issuers for the certificate matching the private key, or to allow any issuer. + /// The host name of the server requesting the certificate, or for no host filtering. + /// The port number of the server requesting the certificate, or -1 if unavailable. + /// The initial alias to preselect if available, or for no preselection. + /// + /// A representing the asynchronous operation. The result is the alias chosen by the user, + /// or if the user cancelled the selection. + /// + /// + /// This is an async wrapper around . + /// public static async Task ChoosePrivateKeyAliasAsync ( Android.App.Activity activity, string[]? keyTypes, @@ -52,6 +98,25 @@ public partial class KeyChain return await tcs.Task; } + /// + /// Displays the system UI for the user to select a certificate, then retrieves the corresponding + /// with its private key, filtering by URI. + /// + /// The activity to use as the parent for the certificate selection UI. + /// The acceptable types of asymmetric keys, or to allow any type. + /// The acceptable certificate issuers for the certificate matching the private key, or to allow any issuer. + /// The URI to filter by, or to allow the user to choose any alias. + /// The initial alias to preselect if available, or for no preselection. + /// + /// A representing the asynchronous operation. The result is an + /// containing the certificate and private key, or if the user cancelled the selection + /// or the certificate could not be retrieved. + /// + /// + /// This method combines + /// and into a single call for a one-step TLS client certificate workflow. + /// This overload requires Android API 23 or later. + /// [SupportedOSPlatform("android23.0")] public static async Task ChooseX509Certificate2WithPrivateKeyAsync ( Android.App.Activity activity, @@ -68,6 +133,25 @@ public partial class KeyChain return GetX509Certificate2WithPrivateKey (activity, alias); } + /// + /// Displays the system UI for the user to select a certificate, then retrieves the corresponding + /// with its private key, filtering by host and port. + /// + /// The activity to use as the parent for the certificate selection UI. + /// The acceptable types of asymmetric keys, or to allow any type. + /// The acceptable certificate issuers for the certificate matching the private key, or to allow any issuer. + /// The host name of the server requesting the certificate, or for no host filtering. + /// The port number of the server requesting the certificate, or -1 if unavailable. + /// The initial alias to preselect if available, or for no preselection. + /// + /// A representing the asynchronous operation. The result is an + /// containing the certificate and private key, or if the user cancelled the selection + /// or the certificate could not be retrieved. + /// + /// + /// This method combines + /// and into a single call for a one-step TLS client certificate workflow. + /// public static async Task ChooseX509Certificate2WithPrivateKeyAsync ( Android.App.Activity activity, string[]? keyTypes, diff --git a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs index 88f7fa32244..fec5337d347 100644 --- a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs +++ b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs @@ -93,15 +93,45 @@ protected JavaPeerProxy ( /// already registered the peer). /// public static bool ShouldSkipActivation (IntPtr jniSelf) + { + var reference = new JniObjectReference (jniSelf, JniObjectReferenceType.Invalid); + var peer = JniEnvironment.Runtime.ValueManager.PeekPeer (reference); + if (peer != null && !IsActivationPeer (peer)) { + return true; + } + return JniEnvironment.WithinNewObjectScope; + } + + public static IJavaPeerable? GetActivationPeer (IntPtr jniSelf) + { + var reference = new JniObjectReference (jniSelf, JniObjectReferenceType.Invalid); + var peer = JniEnvironment.Runtime.ValueManager.PeekPeer (reference); + return peer != null && IsActivationPeer (peer) ? peer : null; + } + + public static void SetActivationPeerReference (IJavaPeerable peer, IntPtr jniSelf) + { + var reference = new JniObjectReference (jniSelf, JniObjectReferenceType.Invalid); + peer.SetPeerReference (reference); + peer.SetJniIdentityHashCode (JniEnvironment.References.GetIdentityHashCode (reference)); + } + + public static void MarkActivationPeerReplaceable (IntPtr jniSelf) { var reference = new JniObjectReference (jniSelf, JniObjectReferenceType.Invalid); var peer = JniEnvironment.Runtime.ValueManager.PeekPeer (reference); if (peer == null) { - return false; + return; } + + peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable); + } + + static bool IsActivationPeer (IJavaPeerable peer) + { var state = peer.JniManagedPeerState; - return (state & JniManagedPeerStates.Activatable) != JniManagedPeerStates.Activatable - && (state & JniManagedPeerStates.Replaceable) != JniManagedPeerStates.Replaceable; + return (state & JniManagedPeerStates.Activatable) == JniManagedPeerStates.Activatable + || (state & JniManagedPeerStates.Replaceable) == JniManagedPeerStates.Replaceable; } } diff --git a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs index 210f6f343a0..e07fda8e225 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs @@ -249,39 +249,12 @@ public override void FinalizePeer (IJavaPeerable value) value.Finalized (); } - public override void ActivatePeer (IJavaPeerable? self, JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues) + public override void ActivatePeer (JniObjectReference reference, [DynamicallyAccessedMembers (Constructors)] Type type, ConstructorInfo cinfo, object?[]? argumentValues) { - try { - ActivateViaReflection (reference, cinfo, argumentValues); - } catch (Exception e) { - var m = string.Format ( - CultureInfo.InvariantCulture, - "Could not activate {{ PeerReference={0} IdentityHashCode=0x{1} Java.Type={2} }} for managed type '{3}'.", - reference, - GetJniIdentityHashCode (reference).ToString ("x", CultureInfo.InvariantCulture), - JniEnvironment.Types.GetJniTypeNameFromInstance (reference), - cinfo.DeclaringType?.FullName); - Debug.WriteLine (m); - - throw new NotSupportedException (m, e); - } - } - - void ActivateViaReflection (JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues) - { - var declType = GetDeclaringType (cinfo); - -#pragma warning disable IL2072 - var self = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (declType); -#pragma warning restore IL2072 - self.SetPeerReference (reference); - - cinfo.Invoke (self, argumentValues); + if (RuntimeFeature.TrimmableTypeMap) + throw new PlatformNotSupportedException ("Activating Java peers is not supported when TrimmableTypeMap is enabled."); - [UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = "🤷‍♂️")] - [return: DynamicallyAccessedMembers (Constructors)] - Type GetDeclaringType (ConstructorInfo cinfo) => - cinfo.DeclaringType ?? throw new NotSupportedException ("Do not know the type to create!"); + base.ActivatePeer (reference, type, cinfo, argumentValues); } public override List GetSurfacedPeers () diff --git a/src/Mono.Android/Microsoft.Android.Runtime/JniRemappingLookup.cs b/src/Mono.Android/Microsoft.Android.Runtime/JniRemappingLookup.cs new file mode 100644 index 00000000000..f67e26eaee1 --- /dev/null +++ b/src/Mono.Android/Microsoft.Android.Runtime/JniRemappingLookup.cs @@ -0,0 +1,99 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Android.Runtime; +using Java.Interop; + +namespace Microsoft.Android.Runtime; + +static class JniRemappingLookup +{ + struct JniRemappingReplacementMethod + { + public string? target_type; + public string? target_name; + public bool is_static; + } + + internal static IReadOnlyList GetStaticMethodFallbackTypes (string jniSimpleReference, bool useReplacementTypes) + { + int slash = jniSimpleReference.LastIndexOf ('/'); + var desugarType = slash > 0 + ? $"{jniSimpleReference.Substring (0, slash + 1)}Desugar{jniSimpleReference.Substring (slash + 1)}" + : $"Desugar{jniSimpleReference}"; + + var typeWithPrefix = $"{desugarType}$_CC"; + var typeWithSuffix = $"{jniSimpleReference}$-CC"; + + var replacements = new[] { + useReplacementTypes ? GetReplacementType (typeWithPrefix) ?? typeWithPrefix : typeWithPrefix, + useReplacementTypes ? GetReplacementType (typeWithSuffix) ?? typeWithSuffix : typeWithSuffix, + }; + + if (useReplacementTypes && Logger.LogAssembly) { + var message = $"Remapping type `{jniSimpleReference}` to one of {{ `{replacements [0]}`, `{replacements [1]}` }}"; + Logger.Log (LogLevel.Debug, "monodroid-assembly", message); + } + + return replacements; + } + + internal static string? GetReplacementType (string? jniSimpleReference) + { + if (jniSimpleReference is null || !JNIEnvInit.jniRemappingInUse) { + return null; + } + + IntPtr ret = RuntimeNativeMethods._monodroid_lookup_replacement_type (jniSimpleReference); + if (ret == IntPtr.Zero) { + return null; + } + + return Marshal.PtrToStringAnsi (ret); + } + + internal static JniRuntime.ReplacementMethodInfo? GetReplacementMethodInfo (string jniSourceType, string jniMethodName, string jniMethodSignature) + { + if (!JNIEnvInit.jniRemappingInUse) { + return null; + } + + IntPtr retInfo = RuntimeNativeMethods._monodroid_lookup_replacement_method_info (jniSourceType, jniMethodName, jniMethodSignature); + if (retInfo == IntPtr.Zero) { + return null; + } + + var method = Marshal.PtrToStructure (retInfo); + var targetType = method.target_type ?? throw new InvalidOperationException ( + $"JNI remapping entry for `{jniSourceType}.{jniMethodName}{jniMethodSignature}` is missing a target type."); + var targetName = method.target_name ?? throw new InvalidOperationException ( + $"JNI remapping entry for `{jniSourceType}.{jniMethodName}{jniMethodSignature}` is missing a target method name."); + var newSignature = jniMethodSignature; + + int? paramCount = null; + if (method.is_static) { + paramCount = JniMemberSignature.GetParameterCountFromMethodSignature (jniMethodSignature) + 1; + newSignature = $"(L{jniSourceType};" + jniMethodSignature.Substring ("(".Length); + } + + if (Logger.LogAssembly) { + var message = $"Remapping method `{jniSourceType}.{jniMethodName}{jniMethodSignature}` to " + + $"`{targetType}.{targetName}{newSignature}`; " + + $"param-count: {paramCount}; instance-to-static? {method.is_static}"; + Logger.Log (LogLevel.Debug, "monodroid-assembly", message); + } + + return new JniRuntime.ReplacementMethodInfo { + SourceJniType = jniSourceType, + SourceJniMethodName = jniMethodName, + SourceJniMethodSignature = jniMethodSignature, + TargetJniType = targetType, + TargetJniMethodName = targetName, + TargetJniMethodSignature = newSignature, + TargetJniMethodParameterCount = paramCount, + TargetJniMethodInstanceToStatic = method.is_static, + }; + } +} diff --git a/src/Mono.Android/Microsoft.Android.Runtime/ManagedTypeManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/ManagedTypeManager.cs index fc5b73154dc..454bab0e1bb 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/ManagedTypeManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/ManagedTypeManager.cs @@ -154,15 +154,7 @@ protected override IEnumerable GetSimpleReferences (Type type) protected override IReadOnlyList? GetStaticMethodFallbackTypesCore (string jniSimpleReference) { - int slash = jniSimpleReference.LastIndexOf ('/'); - var desugarType = slash > 0 - ? $"{jniSimpleReference.Substring (0, slash + 1)}Desugar{jniSimpleReference.Substring (slash + 1)}" - : $"Desugar{jniSimpleReference}"; - - return new[] { - $"{desugarType}$_CC", - $"{jniSimpleReference}$-CC", - }; + return JniRemappingLookup.GetStaticMethodFallbackTypes (jniSimpleReference, useReplacementTypes: false); } static int CountMethods (ReadOnlySpan methodsSpan) diff --git a/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs b/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs index 0493790657d..5d20f9e5ac4 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs @@ -8,6 +8,7 @@ static class RuntimeFeature const bool ManagedTypeMapEnabledByDefault = false; const bool IsMonoRuntimeEnabledByDefault = true; const bool IsCoreClrRuntimeEnabledByDefault = false; + const bool IsNativeAotRuntimeEnabledByDefault = false; const bool IsAssignableFromCheckEnabledByDefault = true; const bool StartupHookSupportEnabledByDefault = true; const bool TrimmableTypeMapEnabledByDefault = false; @@ -28,6 +29,10 @@ static class RuntimeFeature internal static bool IsCoreClrRuntime { get; } = AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (IsCoreClrRuntime)}", out bool isEnabled) ? isEnabled : IsCoreClrRuntimeEnabledByDefault; + [FeatureSwitchDefinition ($"{FeatureSwitchPrefix}{nameof (IsNativeAotRuntime)}")] + internal static bool IsNativeAotRuntime { get; } = + AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (IsNativeAotRuntime)}", out bool isEnabled) ? isEnabled : IsNativeAotRuntimeEnabledByDefault; + [FeatureSwitchDefinition ($"{FeatureSwitchPrefix}{nameof (IsAssignableFromCheck)}")] internal static bool IsAssignableFromCheck { get; } = AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (IsAssignableFromCheck)}", out bool isEnabled) ? isEnabled : IsAssignableFromCheckEnabledByDefault; diff --git a/src/Mono.Android/Microsoft.Android.Runtime/SimpleValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/SimpleValueManager.cs index 57d8ef5f84d..bcccf6d6fd1 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/SimpleValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/SimpleValueManager.cs @@ -207,41 +207,6 @@ public override void FinalizePeer (IJavaPeerable value) value.Finalized (); } - public override void ActivatePeer (IJavaPeerable? self, JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues) - { - try { - ActivateViaReflection (reference, cinfo, argumentValues); - } catch (Exception e) { - var m = string.Format ( - CultureInfo.InvariantCulture, - "Could not activate {{ PeerReference={0} IdentityHashCode=0x{1} Java.Type={2} }} for managed type '{3}'.", - reference, - GetJniIdentityHashCode (reference).ToString ("x", CultureInfo.InvariantCulture), - JniEnvironment.Types.GetJniTypeNameFromInstance (reference), - cinfo.DeclaringType?.FullName); - Debug.WriteLine (m); - - throw new NotSupportedException (m, e); - } - } - - void ActivateViaReflection (JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues) - { - var declType = GetDeclaringType (cinfo); - -#pragma warning disable IL2072 - var self = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (declType); -#pragma warning restore IL2072 - self.SetPeerReference (reference); - - cinfo.Invoke (self, argumentValues); - - [UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = "🤷‍♂️")] - [return: DynamicallyAccessedMembers (Constructors)] - Type GetDeclaringType (ConstructorInfo cinfo) => - cinfo.DeclaringType ?? throw new NotSupportedException ("Do not know the type to create!"); - } - public override List GetSurfacedPeers () { if (RegisteredInstances == null) diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs index 58455d8ea79..a574c00a066 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs @@ -23,6 +23,7 @@ public class TrimmableTypeMap static readonly JavaPeerProxy s_noPeerSentinel = new MissingJavaPeerProxy (); static TrimmableTypeMap? s_instance; static bool s_nativeMethodsRegistered; + static JniMethodInfo? s_classGetInterfacesMethod; internal static TrimmableTypeMap Instance => s_instance ?? throw new InvalidOperationException ( @@ -31,6 +32,7 @@ public class TrimmableTypeMap readonly ITypeMap _typeMap; readonly ConcurrentDictionary _proxyCache = new (); readonly ConcurrentDictionary _jniProxyCache = new (StringComparer.Ordinal); + readonly ConcurrentDictionary<(string ClassName, Type TargetType), JavaPeerProxy> _interfaceProxyCache = new (); TrimmableTypeMap (ITypeMap typeMap) { @@ -255,6 +257,19 @@ internal bool TryGetJniNameForManagedType (Type managedType, [NotNullWhen (true) } } + // When targetType is an interface, also check the Java interfaces + // at each level. getInterfaces() only returns directly declared + // interfaces so we must call it at each class in the hierarchy. + // This handles the case where an intermediate class entry (e.g., + // X509ExtendedTrustManager) was trimmed but the Java interface + // entry (e.g., X509TrustManager) survives. + if (targetType is { IsInterface: true } && className != null) { + var result = GetProxyForJavaInterfaces (self, jniClass, className, targetType); + if (result != null) { + return result; + } + } + var super = JniEnvironment.Types.GetSuperclass (jniClass); JniObjectReference.Dispose (ref jniClass); jniClass = super; @@ -266,6 +281,70 @@ internal bool TryGetJniNameForManagedType (Type managedType, [NotNullWhen (true) return null; } + static JavaPeerProxy? GetProxyForJavaInterfaces (TrimmableTypeMap self, JniObjectReference jniClass, string className, Type targetType) + { + var proxy = self._interfaceProxyCache.GetOrAdd ( + (className, targetType), + _ => TryMatchInterfaces (self, jniClass, targetType) ?? s_noPeerSentinel); + return ReferenceEquals (proxy, s_noPeerSentinel) ? null : proxy; + } + + // getInterfaces() returns only directly declared interfaces (not transitive), + // so we recurse into super-interfaces to find the matching TypeMap entry. + static JavaPeerProxy? TryMatchInterfaces (TrimmableTypeMap self, JniObjectReference jniClass, Type targetType) + { + var interfaces = JniEnvironment.InstanceMethods.CallObjectMethod (jniClass, GetClassGetInterfacesMethod ()); + try { + if (!interfaces.IsValid) { + return null; + } + + int count = JniEnvironment.Arrays.GetArrayLength (interfaces); + for (int i = 0; i < count; i++) { + var iface = JniEnvironment.Arrays.GetObjectArrayElement (interfaces, i); + try { + var ifaceName = JniEnvironment.Types.GetJniTypeNameFromClass (iface); + if (ifaceName != null) { + var proxy = self.GetProxyForJniClass (ifaceName, targetType); + if (proxy != null && TargetTypeMatches (targetType, proxy.TargetType)) { + return proxy; + } + } + + // Recurse into super-interfaces + var result = TryMatchInterfaces (self, iface, targetType); + if (result != null) { + return result; + } + } finally { + JniObjectReference.Dispose (ref iface); + } + } + } finally { + JniObjectReference.Dispose (ref interfaces); + } + + return null; + } + + static JniMethodInfo GetClassGetInterfacesMethod () + { + var method = s_classGetInterfacesMethod; + if (method != null) { + return method; + } + + var classClass = JniEnvironment.Types.FindClass ("java/lang/Class"); + try { + method = JniEnvironment.InstanceMethods.GetMethodID (classClass, "getInterfaces", "()[Ljava/lang/Class;"); + } finally { + JniObjectReference.Dispose (ref classClass); + } + + var previous = Interlocked.CompareExchange (ref s_classGetInterfacesMethod, method, null); + return previous ?? method; + } + static JavaPeerProxy? TryGetProxyFromTargetType (TrimmableTypeMap self, IntPtr handle, Type? targetType) { if (targetType is null) { @@ -316,16 +395,11 @@ internal bool TryGetJniNameForManagedType (Type managedType, [NotNullWhen (true) /// closed subclasses of an open generic class peer. /// /// - /// Implementers of an open generic interface peer are intentionally - /// not matched here: walks only the - /// JNI class chain (getSuperclass), never JNI interfaces, so the - /// proxy returned from that walk is always a class peer. Matching on - /// Type.GetInterfaces() would also force a trimmer + /// Open generic interface peers are intentionally not matched here: + /// matching on Type.GetInterfaces() would force a trimmer /// DynamicallyAccessedMembers(Interfaces) annotation up the chain - /// (ultimately into Java.Interop's CreatePeer API). If we ever need - /// to discover interface peers, the generator should emit an explicit - /// implementer→interface map so runtime can avoid reflection over - /// interface lists. + /// (ultimately into Java.Interop's CreatePeer API). Interface peer + /// discovery is handled from the Java class metadata instead. /// internal static bool TargetTypeMatches (Type targetType, Type proxyTargetType) { diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs index 2253be9a160..dea8cb2dcb8 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs @@ -95,14 +95,17 @@ protected override IEnumerable GetSimpleReferences (Type type) protected override IReadOnlyList? GetStaticMethodFallbackTypesCore (string jniSimpleReference) { - int slash = jniSimpleReference.LastIndexOf ('/'); - var desugarType = slash > 0 - ? $"{jniSimpleReference.Substring (0, slash + 1)}Desugar{jniSimpleReference.Substring (slash + 1)}" - : $"Desugar{jniSimpleReference}"; - return new[] { - $"{desugarType}$_CC", - $"{jniSimpleReference}$-CC", - }; + return JniRemappingLookup.GetStaticMethodFallbackTypes (jniSimpleReference, useReplacementTypes: true); + } + + protected override string? GetReplacementTypeCore (string jniSimpleReference) + { + return JniRemappingLookup.GetReplacementType (jniSimpleReference); + } + + protected override JniRuntime.ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSourceType, string jniMethodName, string jniMethodSignature) + { + return JniRemappingLookup.GetReplacementMethodInfo (jniSourceType, jniMethodName, jniMethodSignature); } public override void RegisterNativeMembers ( diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index ea0c64a2ace..d2622d1bfff 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -362,6 +362,7 @@ + @@ -378,7 +379,6 @@ - diff --git a/src/Mono.Android/PublicAPI/API-35/PublicAPI.Shipped.txt b/src/Mono.Android/PublicAPI/API-35/PublicAPI.Shipped.txt index d71147178cd..51b11d6abcc 100644 --- a/src/Mono.Android/PublicAPI/API-35/PublicAPI.Shipped.txt +++ b/src/Mono.Android/PublicAPI/API-35/PublicAPI.Shipped.txt @@ -98928,8 +98928,6 @@ override System.Drawing.SizeFConverter.CreateInstance(System.ComponentModel.ITyp override System.Drawing.SizeFConverter.GetCreateInstanceSupported(System.ComponentModel.ITypeDescriptorContext! context) -> bool override System.Drawing.SizeFConverter.GetProperties(System.ComponentModel.ITypeDescriptorContext! context, object! value, System.Attribute![]! attributes) -> System.ComponentModel.PropertyDescriptorCollection? override System.Drawing.SizeFConverter.GetPropertiesSupported(System.ComponentModel.ITypeDescriptorContext! context) -> bool -override Xamarin.Android.Net.AndroidClientHandler.Dispose(bool disposing) -> void -override Xamarin.Android.Net.AndroidClientHandler.SendAsync(System.Net.Http.HttpRequestMessage! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! override Xamarin.Android.Net.AndroidHttpResponseMessage.Dispose(bool disposing) -> void override Xamarin.Android.Net.AndroidMessageHandler.Dispose(bool disposing) -> void override Xamarin.Android.Net.AndroidMessageHandler.SendAsync(System.Net.Http.HttpRequestMessage! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! @@ -129692,14 +129690,6 @@ virtual Org.XmlPull.V1.XmlPullParserFactory.NewSerializer() -> Org.XmlPull.V1.IX virtual Org.XmlPull.V1.XmlPullParserFactory.SetFeature(string? name, bool state) -> void virtual Org.XmlPull.V1.XmlPullParserFactory.Validating.get -> bool virtual Org.XmlPull.V1.XmlPullParserFactory.Validating.set -> void -virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureCustomSSLSocketFactory(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.SSLSocketFactory? -virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureKeyManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.KeyManagerFactory? -virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureKeyStore(Java.Security.KeyStore? keyStore) -> Java.Security.KeyStore? -virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureTrustManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.TrustManagerFactory? -virtual Xamarin.Android.Net.AndroidClientHandler.GetJavaProxy(System.Uri! destination, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! -virtual Xamarin.Android.Net.AndroidClientHandler.GetSSLHostnameVerifier(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.IHostnameVerifier? -virtual Xamarin.Android.Net.AndroidClientHandler.SetupRequest(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! conn) -> System.Threading.Tasks.Task! -virtual Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOutput(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! httpConnection, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! virtual Xamarin.Android.Net.AndroidMessageHandler.ConfigureCustomSSLSocketFactory(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.SSLSocketFactory? virtual Xamarin.Android.Net.AndroidMessageHandler.ConfigureKeyManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.KeyManagerFactory? virtual Xamarin.Android.Net.AndroidMessageHandler.ConfigureKeyStore(Java.Security.KeyStore? keyStore) -> Java.Security.KeyStore? @@ -129708,20 +129698,6 @@ virtual Xamarin.Android.Net.AndroidMessageHandler.GetJavaProxy(System.Uri! desti virtual Xamarin.Android.Net.AndroidMessageHandler.GetSSLHostnameVerifier(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.IHostnameVerifier? virtual Xamarin.Android.Net.AndroidMessageHandler.SetupRequest(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! conn) -> System.Threading.Tasks.Task! virtual Xamarin.Android.Net.AndroidMessageHandler.WriteRequestContentToOutput(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! httpConnection, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! -Xamarin.Android.Net.AndroidClientHandler -Xamarin.Android.Net.AndroidClientHandler.AndroidClientHandler() -> void -Xamarin.Android.Net.AndroidClientHandler.AssertSelf() -> void -Xamarin.Android.Net.AndroidClientHandler.ConnectTimeout.get -> System.TimeSpan -Xamarin.Android.Net.AndroidClientHandler.ConnectTimeout.set -> void -Xamarin.Android.Net.AndroidClientHandler.PreAuthenticationData.get -> Xamarin.Android.Net.AuthenticationData? -Xamarin.Android.Net.AndroidClientHandler.PreAuthenticationData.set -> void -Xamarin.Android.Net.AndroidClientHandler.ProxyAuthenticationRequested.get -> bool -Xamarin.Android.Net.AndroidClientHandler.ReadTimeout.get -> System.TimeSpan -Xamarin.Android.Net.AndroidClientHandler.ReadTimeout.set -> void -Xamarin.Android.Net.AndroidClientHandler.RequestedAuthentication.get -> System.Collections.Generic.IList? -Xamarin.Android.Net.AndroidClientHandler.RequestNeedsAuthorization.get -> bool -Xamarin.Android.Net.AndroidClientHandler.TrustedCerts.get -> System.Collections.Generic.IList? -Xamarin.Android.Net.AndroidClientHandler.TrustedCerts.set -> void Xamarin.Android.Net.AndroidHttpResponseMessage Xamarin.Android.Net.AndroidHttpResponseMessage.AndroidHttpResponseMessage() -> void Xamarin.Android.Net.AndroidHttpResponseMessage.AndroidHttpResponseMessage(Java.Net.URL! javaUrl, Java.Net.HttpURLConnection! httpConnection) -> void diff --git a/src/Mono.Android/PublicAPI/API-35/PublicAPI.Unshipped.txt b/src/Mono.Android/PublicAPI/API-35/PublicAPI.Unshipped.txt index 4f7e6a01587..a42c999affc 100644 --- a/src/Mono.Android/PublicAPI/API-35/PublicAPI.Unshipped.txt +++ b/src/Mono.Android/PublicAPI/API-35/PublicAPI.Unshipped.txt @@ -1,3 +1,27 @@ #nullable enable +REMOVED Xamarin.Android.Net.AndroidClientHandler +REMOVED Xamarin.Android.Net.AndroidClientHandler.AndroidClientHandler() -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.AssertSelf() -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.ConnectTimeout.get -> System.TimeSpan +REMOVED Xamarin.Android.Net.AndroidClientHandler.ConnectTimeout.set -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.PreAuthenticationData.get -> Xamarin.Android.Net.AuthenticationData? +REMOVED Xamarin.Android.Net.AndroidClientHandler.PreAuthenticationData.set -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.ProxyAuthenticationRequested.get -> bool +REMOVED Xamarin.Android.Net.AndroidClientHandler.ReadTimeout.get -> System.TimeSpan +REMOVED Xamarin.Android.Net.AndroidClientHandler.ReadTimeout.set -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.RequestedAuthentication.get -> System.Collections.Generic.IList? +REMOVED Xamarin.Android.Net.AndroidClientHandler.RequestNeedsAuthorization.get -> bool +REMOVED Xamarin.Android.Net.AndroidClientHandler.TrustedCerts.get -> System.Collections.Generic.IList? +REMOVED Xamarin.Android.Net.AndroidClientHandler.TrustedCerts.set -> void +REMOVED override Xamarin.Android.Net.AndroidClientHandler.Dispose(bool disposing) -> void +REMOVED override Xamarin.Android.Net.AndroidClientHandler.SendAsync(System.Net.Http.HttpRequestMessage! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureCustomSSLSocketFactory(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.SSLSocketFactory? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureKeyManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.KeyManagerFactory? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureKeyStore(Java.Security.KeyStore? keyStore) -> Java.Security.KeyStore? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureTrustManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.TrustManagerFactory? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.GetJavaProxy(System.Uri! destination, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.GetSSLHostnameVerifier(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.IHostnameVerifier? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.SetupRequest(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! conn) -> System.Threading.Tasks.Task! +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOutput(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! httpConnection, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.get -> bool Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.set -> void diff --git a/src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Shipped.txt b/src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Shipped.txt index a3ae260f2da..d0971103894 100644 --- a/src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Shipped.txt +++ b/src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Shipped.txt @@ -57783,20 +57783,6 @@ System.Drawing.SizeFConverter System.Drawing.SizeFConverter.SizeFConverter() -> void System.IO.AndroidExtensions System.Linq.Extensions -Xamarin.Android.Net.AndroidClientHandler -Xamarin.Android.Net.AndroidClientHandler.AndroidClientHandler() -> void -Xamarin.Android.Net.AndroidClientHandler.AssertSelf() -> void -Xamarin.Android.Net.AndroidClientHandler.ConnectTimeout.get -> System.TimeSpan -Xamarin.Android.Net.AndroidClientHandler.ConnectTimeout.set -> void -Xamarin.Android.Net.AndroidClientHandler.PreAuthenticationData.get -> Xamarin.Android.Net.AuthenticationData? -Xamarin.Android.Net.AndroidClientHandler.PreAuthenticationData.set -> void -Xamarin.Android.Net.AndroidClientHandler.ProxyAuthenticationRequested.get -> bool -Xamarin.Android.Net.AndroidClientHandler.ReadTimeout.get -> System.TimeSpan -Xamarin.Android.Net.AndroidClientHandler.ReadTimeout.set -> void -Xamarin.Android.Net.AndroidClientHandler.RequestedAuthentication.get -> System.Collections.Generic.IList? -Xamarin.Android.Net.AndroidClientHandler.RequestNeedsAuthorization.get -> bool -Xamarin.Android.Net.AndroidClientHandler.TrustedCerts.get -> System.Collections.Generic.IList? -Xamarin.Android.Net.AndroidClientHandler.TrustedCerts.set -> void Xamarin.Android.Net.AndroidHttpResponseMessage Xamarin.Android.Net.AndroidHttpResponseMessage.AndroidHttpResponseMessage() -> void Xamarin.Android.Net.AndroidHttpResponseMessage.AndroidHttpResponseMessage(Java.Net.URL! javaUrl, Java.Net.HttpURLConnection! httpConnection) -> void @@ -102829,8 +102815,6 @@ override System.Drawing.SizeFConverter.CreateInstance(System.ComponentModel.ITyp override System.Drawing.SizeFConverter.GetCreateInstanceSupported(System.ComponentModel.ITypeDescriptorContext! context) -> bool override System.Drawing.SizeFConverter.GetProperties(System.ComponentModel.ITypeDescriptorContext! context, object! value, System.Attribute![]! attributes) -> System.ComponentModel.PropertyDescriptorCollection? override System.Drawing.SizeFConverter.GetPropertiesSupported(System.ComponentModel.ITypeDescriptorContext! context) -> bool -override Xamarin.Android.Net.AndroidClientHandler.Dispose(bool disposing) -> void -override Xamarin.Android.Net.AndroidClientHandler.SendAsync(System.Net.Http.HttpRequestMessage! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! override Xamarin.Android.Net.AndroidHttpResponseMessage.Dispose(bool disposing) -> void override Xamarin.Android.Net.AndroidMessageHandler.Dispose(bool disposing) -> void override Xamarin.Android.Net.AndroidMessageHandler.SendAsync(System.Net.Http.HttpRequestMessage! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! @@ -134160,14 +134144,6 @@ virtual Org.XmlPull.V1.XmlPullParserFactory.NewSerializer() -> Org.XmlPull.V1.IX virtual Org.XmlPull.V1.XmlPullParserFactory.SetFeature(string? name, bool state) -> void virtual Org.XmlPull.V1.XmlPullParserFactory.Validating.get -> bool virtual Org.XmlPull.V1.XmlPullParserFactory.Validating.set -> void -virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureCustomSSLSocketFactory(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.SSLSocketFactory? -virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureKeyManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.KeyManagerFactory? -virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureKeyStore(Java.Security.KeyStore? keyStore) -> Java.Security.KeyStore? -virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureTrustManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.TrustManagerFactory? -virtual Xamarin.Android.Net.AndroidClientHandler.GetJavaProxy(System.Uri! destination, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! -virtual Xamarin.Android.Net.AndroidClientHandler.GetSSLHostnameVerifier(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.IHostnameVerifier? -virtual Xamarin.Android.Net.AndroidClientHandler.SetupRequest(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! conn) -> System.Threading.Tasks.Task! -virtual Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOutput(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! httpConnection, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! virtual Xamarin.Android.Net.AndroidMessageHandler.ConfigureCustomSSLSocketFactory(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.SSLSocketFactory? virtual Xamarin.Android.Net.AndroidMessageHandler.ConfigureKeyManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.KeyManagerFactory? virtual Xamarin.Android.Net.AndroidMessageHandler.ConfigureKeyStore(Java.Security.KeyStore? keyStore) -> Java.Security.KeyStore? diff --git a/src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Unshipped.txt b/src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Unshipped.txt index 4f7e6a01587..a42c999affc 100644 --- a/src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Unshipped.txt +++ b/src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Unshipped.txt @@ -1,3 +1,27 @@ #nullable enable +REMOVED Xamarin.Android.Net.AndroidClientHandler +REMOVED Xamarin.Android.Net.AndroidClientHandler.AndroidClientHandler() -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.AssertSelf() -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.ConnectTimeout.get -> System.TimeSpan +REMOVED Xamarin.Android.Net.AndroidClientHandler.ConnectTimeout.set -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.PreAuthenticationData.get -> Xamarin.Android.Net.AuthenticationData? +REMOVED Xamarin.Android.Net.AndroidClientHandler.PreAuthenticationData.set -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.ProxyAuthenticationRequested.get -> bool +REMOVED Xamarin.Android.Net.AndroidClientHandler.ReadTimeout.get -> System.TimeSpan +REMOVED Xamarin.Android.Net.AndroidClientHandler.ReadTimeout.set -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.RequestedAuthentication.get -> System.Collections.Generic.IList? +REMOVED Xamarin.Android.Net.AndroidClientHandler.RequestNeedsAuthorization.get -> bool +REMOVED Xamarin.Android.Net.AndroidClientHandler.TrustedCerts.get -> System.Collections.Generic.IList? +REMOVED Xamarin.Android.Net.AndroidClientHandler.TrustedCerts.set -> void +REMOVED override Xamarin.Android.Net.AndroidClientHandler.Dispose(bool disposing) -> void +REMOVED override Xamarin.Android.Net.AndroidClientHandler.SendAsync(System.Net.Http.HttpRequestMessage! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureCustomSSLSocketFactory(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.SSLSocketFactory? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureKeyManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.KeyManagerFactory? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureKeyStore(Java.Security.KeyStore? keyStore) -> Java.Security.KeyStore? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureTrustManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.TrustManagerFactory? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.GetJavaProxy(System.Uri! destination, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.GetSSLHostnameVerifier(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.IHostnameVerifier? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.SetupRequest(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! conn) -> System.Threading.Tasks.Task! +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOutput(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! httpConnection, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.get -> bool Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.set -> void diff --git a/src/Mono.Android/PublicAPI/API-36/PublicAPI.Shipped.txt b/src/Mono.Android/PublicAPI/API-36/PublicAPI.Shipped.txt index 44e2f0ea6d6..7af16e4c6d2 100644 --- a/src/Mono.Android/PublicAPI/API-36/PublicAPI.Shipped.txt +++ b/src/Mono.Android/PublicAPI/API-36/PublicAPI.Shipped.txt @@ -98928,8 +98928,6 @@ override System.Drawing.SizeFConverter.CreateInstance(System.ComponentModel.ITyp override System.Drawing.SizeFConverter.GetCreateInstanceSupported(System.ComponentModel.ITypeDescriptorContext! context) -> bool override System.Drawing.SizeFConverter.GetProperties(System.ComponentModel.ITypeDescriptorContext! context, object! value, System.Attribute![]! attributes) -> System.ComponentModel.PropertyDescriptorCollection? override System.Drawing.SizeFConverter.GetPropertiesSupported(System.ComponentModel.ITypeDescriptorContext! context) -> bool -override Xamarin.Android.Net.AndroidClientHandler.Dispose(bool disposing) -> void -override Xamarin.Android.Net.AndroidClientHandler.SendAsync(System.Net.Http.HttpRequestMessage! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! override Xamarin.Android.Net.AndroidHttpResponseMessage.Dispose(bool disposing) -> void override Xamarin.Android.Net.AndroidMessageHandler.Dispose(bool disposing) -> void override Xamarin.Android.Net.AndroidMessageHandler.SendAsync(System.Net.Http.HttpRequestMessage! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! @@ -129692,14 +129690,6 @@ virtual Org.XmlPull.V1.XmlPullParserFactory.NewSerializer() -> Org.XmlPull.V1.IX virtual Org.XmlPull.V1.XmlPullParserFactory.SetFeature(string? name, bool state) -> void virtual Org.XmlPull.V1.XmlPullParserFactory.Validating.get -> bool virtual Org.XmlPull.V1.XmlPullParserFactory.Validating.set -> void -virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureCustomSSLSocketFactory(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.SSLSocketFactory? -virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureKeyManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.KeyManagerFactory? -virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureKeyStore(Java.Security.KeyStore? keyStore) -> Java.Security.KeyStore? -virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureTrustManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.TrustManagerFactory? -virtual Xamarin.Android.Net.AndroidClientHandler.GetJavaProxy(System.Uri! destination, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! -virtual Xamarin.Android.Net.AndroidClientHandler.GetSSLHostnameVerifier(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.IHostnameVerifier? -virtual Xamarin.Android.Net.AndroidClientHandler.SetupRequest(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! conn) -> System.Threading.Tasks.Task! -virtual Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOutput(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! httpConnection, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! virtual Xamarin.Android.Net.AndroidMessageHandler.ConfigureCustomSSLSocketFactory(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.SSLSocketFactory? virtual Xamarin.Android.Net.AndroidMessageHandler.ConfigureKeyManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.KeyManagerFactory? virtual Xamarin.Android.Net.AndroidMessageHandler.ConfigureKeyStore(Java.Security.KeyStore? keyStore) -> Java.Security.KeyStore? @@ -129708,20 +129698,6 @@ virtual Xamarin.Android.Net.AndroidMessageHandler.GetJavaProxy(System.Uri! desti virtual Xamarin.Android.Net.AndroidMessageHandler.GetSSLHostnameVerifier(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.IHostnameVerifier? virtual Xamarin.Android.Net.AndroidMessageHandler.SetupRequest(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! conn) -> System.Threading.Tasks.Task! virtual Xamarin.Android.Net.AndroidMessageHandler.WriteRequestContentToOutput(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! httpConnection, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! -Xamarin.Android.Net.AndroidClientHandler -Xamarin.Android.Net.AndroidClientHandler.AndroidClientHandler() -> void -Xamarin.Android.Net.AndroidClientHandler.AssertSelf() -> void -Xamarin.Android.Net.AndroidClientHandler.ConnectTimeout.get -> System.TimeSpan -Xamarin.Android.Net.AndroidClientHandler.ConnectTimeout.set -> void -Xamarin.Android.Net.AndroidClientHandler.PreAuthenticationData.get -> Xamarin.Android.Net.AuthenticationData? -Xamarin.Android.Net.AndroidClientHandler.PreAuthenticationData.set -> void -Xamarin.Android.Net.AndroidClientHandler.ProxyAuthenticationRequested.get -> bool -Xamarin.Android.Net.AndroidClientHandler.ReadTimeout.get -> System.TimeSpan -Xamarin.Android.Net.AndroidClientHandler.ReadTimeout.set -> void -Xamarin.Android.Net.AndroidClientHandler.RequestedAuthentication.get -> System.Collections.Generic.IList? -Xamarin.Android.Net.AndroidClientHandler.RequestNeedsAuthorization.get -> bool -Xamarin.Android.Net.AndroidClientHandler.TrustedCerts.get -> System.Collections.Generic.IList? -Xamarin.Android.Net.AndroidClientHandler.TrustedCerts.set -> void Xamarin.Android.Net.AndroidHttpResponseMessage Xamarin.Android.Net.AndroidHttpResponseMessage.AndroidHttpResponseMessage() -> void Xamarin.Android.Net.AndroidHttpResponseMessage.AndroidHttpResponseMessage(Java.Net.URL! javaUrl, Java.Net.HttpURLConnection! httpConnection) -> void diff --git a/src/Mono.Android/PublicAPI/API-36/PublicAPI.Unshipped.txt b/src/Mono.Android/PublicAPI/API-36/PublicAPI.Unshipped.txt index 7d138d0c17f..43ea83561b1 100644 --- a/src/Mono.Android/PublicAPI/API-36/PublicAPI.Unshipped.txt +++ b/src/Mono.Android/PublicAPI/API-36/PublicAPI.Unshipped.txt @@ -1,4 +1,28 @@ #nullable enable +REMOVED Xamarin.Android.Net.AndroidClientHandler +REMOVED Xamarin.Android.Net.AndroidClientHandler.AndroidClientHandler() -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.AssertSelf() -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.ConnectTimeout.get -> System.TimeSpan +REMOVED Xamarin.Android.Net.AndroidClientHandler.ConnectTimeout.set -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.PreAuthenticationData.get -> Xamarin.Android.Net.AuthenticationData? +REMOVED Xamarin.Android.Net.AndroidClientHandler.PreAuthenticationData.set -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.ProxyAuthenticationRequested.get -> bool +REMOVED Xamarin.Android.Net.AndroidClientHandler.ReadTimeout.get -> System.TimeSpan +REMOVED Xamarin.Android.Net.AndroidClientHandler.ReadTimeout.set -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.RequestedAuthentication.get -> System.Collections.Generic.IList? +REMOVED Xamarin.Android.Net.AndroidClientHandler.RequestNeedsAuthorization.get -> bool +REMOVED Xamarin.Android.Net.AndroidClientHandler.TrustedCerts.get -> System.Collections.Generic.IList? +REMOVED Xamarin.Android.Net.AndroidClientHandler.TrustedCerts.set -> void +REMOVED override Xamarin.Android.Net.AndroidClientHandler.Dispose(bool disposing) -> void +REMOVED override Xamarin.Android.Net.AndroidClientHandler.SendAsync(System.Net.Http.HttpRequestMessage! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureCustomSSLSocketFactory(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.SSLSocketFactory? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureKeyManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.KeyManagerFactory? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureKeyStore(Java.Security.KeyStore? keyStore) -> Java.Security.KeyStore? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureTrustManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.TrustManagerFactory? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.GetJavaProxy(System.Uri! destination, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.GetSSLHostnameVerifier(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.IHostnameVerifier? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.SetupRequest(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! conn) -> System.Threading.Tasks.Task! +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOutput(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! httpConnection, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! abstract Android.App.AppFunctions.AppFunctionService.OnExecuteFunction(Android.App.AppFunctions.ExecuteAppFunctionRequest! request, string! callingPackage, Android.Content.PM.SigningInfo! callingPackageSigningInfo, Android.OS.CancellationSignal! cancellationSignal, Android.OS.IOutcomeReceiver! callback) -> void abstract Android.Media.TV.Ads.TvAdService.OnCreateSession(string! serviceId, string! type) -> Android.Media.TV.Ads.TvAdService.Session? abstract Android.Media.TV.Ads.TvAdService.Session.OnRelease() -> void diff --git a/src/Mono.Android/PublicAPI/API-37/PublicAPI.Shipped.txt b/src/Mono.Android/PublicAPI/API-37/PublicAPI.Shipped.txt index a3ae260f2da..d0971103894 100644 --- a/src/Mono.Android/PublicAPI/API-37/PublicAPI.Shipped.txt +++ b/src/Mono.Android/PublicAPI/API-37/PublicAPI.Shipped.txt @@ -57783,20 +57783,6 @@ System.Drawing.SizeFConverter System.Drawing.SizeFConverter.SizeFConverter() -> void System.IO.AndroidExtensions System.Linq.Extensions -Xamarin.Android.Net.AndroidClientHandler -Xamarin.Android.Net.AndroidClientHandler.AndroidClientHandler() -> void -Xamarin.Android.Net.AndroidClientHandler.AssertSelf() -> void -Xamarin.Android.Net.AndroidClientHandler.ConnectTimeout.get -> System.TimeSpan -Xamarin.Android.Net.AndroidClientHandler.ConnectTimeout.set -> void -Xamarin.Android.Net.AndroidClientHandler.PreAuthenticationData.get -> Xamarin.Android.Net.AuthenticationData? -Xamarin.Android.Net.AndroidClientHandler.PreAuthenticationData.set -> void -Xamarin.Android.Net.AndroidClientHandler.ProxyAuthenticationRequested.get -> bool -Xamarin.Android.Net.AndroidClientHandler.ReadTimeout.get -> System.TimeSpan -Xamarin.Android.Net.AndroidClientHandler.ReadTimeout.set -> void -Xamarin.Android.Net.AndroidClientHandler.RequestedAuthentication.get -> System.Collections.Generic.IList? -Xamarin.Android.Net.AndroidClientHandler.RequestNeedsAuthorization.get -> bool -Xamarin.Android.Net.AndroidClientHandler.TrustedCerts.get -> System.Collections.Generic.IList? -Xamarin.Android.Net.AndroidClientHandler.TrustedCerts.set -> void Xamarin.Android.Net.AndroidHttpResponseMessage Xamarin.Android.Net.AndroidHttpResponseMessage.AndroidHttpResponseMessage() -> void Xamarin.Android.Net.AndroidHttpResponseMessage.AndroidHttpResponseMessage(Java.Net.URL! javaUrl, Java.Net.HttpURLConnection! httpConnection) -> void @@ -102829,8 +102815,6 @@ override System.Drawing.SizeFConverter.CreateInstance(System.ComponentModel.ITyp override System.Drawing.SizeFConverter.GetCreateInstanceSupported(System.ComponentModel.ITypeDescriptorContext! context) -> bool override System.Drawing.SizeFConverter.GetProperties(System.ComponentModel.ITypeDescriptorContext! context, object! value, System.Attribute![]! attributes) -> System.ComponentModel.PropertyDescriptorCollection? override System.Drawing.SizeFConverter.GetPropertiesSupported(System.ComponentModel.ITypeDescriptorContext! context) -> bool -override Xamarin.Android.Net.AndroidClientHandler.Dispose(bool disposing) -> void -override Xamarin.Android.Net.AndroidClientHandler.SendAsync(System.Net.Http.HttpRequestMessage! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! override Xamarin.Android.Net.AndroidHttpResponseMessage.Dispose(bool disposing) -> void override Xamarin.Android.Net.AndroidMessageHandler.Dispose(bool disposing) -> void override Xamarin.Android.Net.AndroidMessageHandler.SendAsync(System.Net.Http.HttpRequestMessage! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! @@ -134160,14 +134144,6 @@ virtual Org.XmlPull.V1.XmlPullParserFactory.NewSerializer() -> Org.XmlPull.V1.IX virtual Org.XmlPull.V1.XmlPullParserFactory.SetFeature(string? name, bool state) -> void virtual Org.XmlPull.V1.XmlPullParserFactory.Validating.get -> bool virtual Org.XmlPull.V1.XmlPullParserFactory.Validating.set -> void -virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureCustomSSLSocketFactory(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.SSLSocketFactory? -virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureKeyManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.KeyManagerFactory? -virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureKeyStore(Java.Security.KeyStore? keyStore) -> Java.Security.KeyStore? -virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureTrustManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.TrustManagerFactory? -virtual Xamarin.Android.Net.AndroidClientHandler.GetJavaProxy(System.Uri! destination, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! -virtual Xamarin.Android.Net.AndroidClientHandler.GetSSLHostnameVerifier(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.IHostnameVerifier? -virtual Xamarin.Android.Net.AndroidClientHandler.SetupRequest(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! conn) -> System.Threading.Tasks.Task! -virtual Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOutput(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! httpConnection, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! virtual Xamarin.Android.Net.AndroidMessageHandler.ConfigureCustomSSLSocketFactory(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.SSLSocketFactory? virtual Xamarin.Android.Net.AndroidMessageHandler.ConfigureKeyManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.KeyManagerFactory? virtual Xamarin.Android.Net.AndroidMessageHandler.ConfigureKeyStore(Java.Security.KeyStore? keyStore) -> Java.Security.KeyStore? diff --git a/src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt b/src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt index 4f7e6a01587..a42c999affc 100644 --- a/src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt +++ b/src/Mono.Android/PublicAPI/API-37/PublicAPI.Unshipped.txt @@ -1,3 +1,27 @@ #nullable enable +REMOVED Xamarin.Android.Net.AndroidClientHandler +REMOVED Xamarin.Android.Net.AndroidClientHandler.AndroidClientHandler() -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.AssertSelf() -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.ConnectTimeout.get -> System.TimeSpan +REMOVED Xamarin.Android.Net.AndroidClientHandler.ConnectTimeout.set -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.PreAuthenticationData.get -> Xamarin.Android.Net.AuthenticationData? +REMOVED Xamarin.Android.Net.AndroidClientHandler.PreAuthenticationData.set -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.ProxyAuthenticationRequested.get -> bool +REMOVED Xamarin.Android.Net.AndroidClientHandler.ReadTimeout.get -> System.TimeSpan +REMOVED Xamarin.Android.Net.AndroidClientHandler.ReadTimeout.set -> void +REMOVED Xamarin.Android.Net.AndroidClientHandler.RequestedAuthentication.get -> System.Collections.Generic.IList? +REMOVED Xamarin.Android.Net.AndroidClientHandler.RequestNeedsAuthorization.get -> bool +REMOVED Xamarin.Android.Net.AndroidClientHandler.TrustedCerts.get -> System.Collections.Generic.IList? +REMOVED Xamarin.Android.Net.AndroidClientHandler.TrustedCerts.set -> void +REMOVED override Xamarin.Android.Net.AndroidClientHandler.Dispose(bool disposing) -> void +REMOVED override Xamarin.Android.Net.AndroidClientHandler.SendAsync(System.Net.Http.HttpRequestMessage! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureCustomSSLSocketFactory(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.SSLSocketFactory? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureKeyManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.KeyManagerFactory? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureKeyStore(Java.Security.KeyStore? keyStore) -> Java.Security.KeyStore? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.ConfigureTrustManagerFactory(Java.Security.KeyStore? keyStore) -> Javax.Net.Ssl.TrustManagerFactory? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.GetJavaProxy(System.Uri! destination, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.GetSSLHostnameVerifier(Javax.Net.Ssl.HttpsURLConnection! connection) -> Javax.Net.Ssl.IHostnameVerifier? +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.SetupRequest(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! conn) -> System.Threading.Tasks.Task! +REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOutput(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! httpConnection, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.get -> bool Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.set -> void diff --git a/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.Legacy.cs b/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.Legacy.cs deleted file mode 100644 index 542d85e2bd7..00000000000 --- a/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.Legacy.cs +++ /dev/null @@ -1,1012 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -using Android.OS; -using Android.Runtime; -using Java.IO; -using Java.Net; -using Java.Security; -using Java.Security.Cert; -using Javax.Net.Ssl; - -namespace Xamarin.Android.Net -{ - /// - /// A custom implementation of which internally uses - /// (or its HTTPS incarnation) to send HTTP requests. - /// - /// - /// Instance of this class is used to configure instance - /// in the following way: - /// - /// - /// var handler = new AndroidClientHandler { - /// UseCookies = true, - /// AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, - /// }; - /// - /// var httpClient = new HttpClient (handler); - /// var response = httpClient.GetAsync ("http://example.com")?.Result as AndroidHttpResponseMessage; - /// - /// - /// The class supports pre-authentication of requests albeit in a slightly "manual" way. Namely, whenever a request to a server requiring authentication - /// is made and no authentication credentials are provided in the property (which is usually the case on the first - /// request), the property will return true and the property will - /// contain all the authentication information gathered from the server. The application must then fill in the blanks (i.e. the credentials) and re-send - /// the request configured to perform pre-authentication. The reason for this manual process is that the underlying Java HTTP client API supports only a - /// single, VM-wide, authentication handler which cannot be configured to handle credentials for several requests. AndroidClientHandler, therefore, implements - /// the authentication in managed .NET code. Message handler supports both Basic and Digest authentication. If an authentication scheme that's not supported - /// by AndroidClientHandler is requested by the server, the application can provide its own authentication module (, - /// ) to handle the protocol authorization. - /// AndroidClientHandler also supports requests to servers with "invalid" (e.g. self-signed) SSL certificates. Since this process is a bit convoluted using - /// the Java APIs, AndroidClientHandler defines two ways to handle the situation. First, easier, is to store the necessary certificates (either CA or server certificates) - /// in the collection or, after deriving a custom class from AndroidClientHandler, by overriding one or more methods provided for this purpose - /// (, and ). The former method should be sufficient - /// for most use cases, the latter allows the application to provide fully customized key store, trust manager and key manager, if needed. Note that the instance of - /// AndroidClientHandler configured to accept an "invalid" certificate from the particular server will most likely fail to validate certificates from other servers (even - /// if they use a certificate with a fully validated trust chain) unless you store the CA certificates from your Android system in along with - /// the self-signed certificate(s). - /// - public class AndroidClientHandler : HttpClientHandler - { - sealed class RequestRedirectionState - { - public Uri? NewUrl; - public int RedirectCounter; - public HttpMethod? Method; - public bool MethodChanged; - } - - internal const string LOG_APP = "monodroid-net"; - - const string GZIP_ENCODING = "gzip"; - const string DEFLATE_ENCODING = "deflate"; - const string IDENTITY_ENCODING = "identity"; - - static readonly IDictionary headerSeparators = new Dictionary { - ["User-Agent"] = " ", - }; - - static readonly HashSet known_content_headers = new HashSet (StringComparer.OrdinalIgnoreCase) { - "Allow", - "Content-Disposition", - "Content-Encoding", - "Content-Language", - "Content-Length", - "Content-Location", - "Content-MD5", - "Content-Range", - "Content-Type", - "Expires", - "Last-Modified" - }; - - static readonly List authModules = new List { - new AuthModuleBasic (), - new AuthModuleDigest () - }; - - bool disposed; - - // Now all hail Java developers! Get this... HttpURLClient defaults to accepting AND - // uncompressing the gzip content encoding UNLESS you set the Accept-Encoding header to ANY - // value. So if we set it to 'gzip' below we WILL get gzipped stream but HttpURLClient will NOT - // uncompress it any longer, doh. And they don't support 'deflate' so we need to handle it ourselves. - bool decompress_here; - - /// - /// - /// Gets or sets the pre authentication data for the request. This property must be set by the application - /// before the request is made. Generally the value can be taken from - /// after the initial request, without any authentication data, receives the authorization request from the - /// server. The application must then store credentials in instance of and - /// assign the instance to this propery before retrying the request. - /// - /// - /// The property is never set by AndroidClientHandler. - /// - /// - /// The pre authentication data. - public AuthenticationData? PreAuthenticationData { get; set; } - - /// - /// If the website requires authentication, this property will contain data about each scheme supported - /// by the server after the response. Note that unauthorized request will return a valid response - you - /// need to check the status code and and (re)configure AndroidClientHandler instance accordingly by providing - /// both the credentials and the authentication scheme by setting the - /// property. If AndroidClientHandler is not able to detect the kind of authentication scheme it will store an - /// instance of with its property - /// set to AuthenticationScheme.Unsupported and the application will be responsible for providing an - /// instance of which handles this kind of authorization scheme - /// ( - /// - public IList ? RequestedAuthentication { get; private set; } - - /// - /// Server authentication response indicates that the request to authorize comes from a proxy if this property is true. - /// All the instances of stored in the property will - /// have their preset to the same value as this property. - /// - public bool ProxyAuthenticationRequested { get; private set; } - - /// - /// If true then the server requested authorization and the application must use information - /// found in to set the value of - /// - public bool RequestNeedsAuthorization { - get { return RequestedAuthentication?.Count > 0; } - } - - /// - /// - /// If the request is to the server protected with a self-signed (or otherwise untrusted) SSL certificate, the request will - /// fail security chain verification unless the application provides either the CA certificate of the entity which issued the - /// server's certificate or, alternatively, provides the server public key. Whichever the case, the certificate(s) must be stored - /// in this property in order for AndroidClientHandler to configure the request to accept the server certificate. - /// AndroidClientHandler uses a custom and to configure the connection. - /// If, however, the application requires finer control over the SSL configuration (e.g. it implements its own TrustManager) then - /// it should leave this property empty and instead derive a custom class from AndroidClientHandler and override, as needed, the - /// , and methods - /// instead - /// - /// The trusted certs. - public IList ? TrustedCerts { get; set; } - - /// - /// - /// Specifies the connection read timeout. - /// - /// - /// Since there's no way for the handler to access - /// directly, this property should be set by the calling party to the same desired value. Value of this - /// property will be passed to the native Java HTTP client, unless it is set to - /// - /// - /// The default value is 24 hours, much higher than the documented value of and the same as the value of iOS-specific - /// NSUrlSessionHandler. - /// - /// - public TimeSpan ReadTimeout { get; set; } = TimeSpan.FromHours (24); - - /// - /// - /// Specifies the connect timeout - /// - /// - /// The native Java client supports two separate timeouts - one for reading from the connection () and another for establishing the connection. This property sets the value of - /// the latter timeout, unless it is set to in which case the - /// native Java client defaults are used. - /// - /// - /// The default value is 120 seconds. - /// - /// - public TimeSpan ConnectTimeout { get; set; } = TimeSpan.FromHours (24); - - protected override void Dispose (bool disposing) - { - disposed = true; - - base.Dispose (disposing); - } - - protected void AssertSelf () - { - if (!disposed) - return; - throw new ObjectDisposedException (nameof (AndroidClientHandler)); - } - - string EncodeUrl (Uri url) - { - if (url == null) - return String.Empty; - - // UriBuilder takes care of encoding everything properly - var bldr = new UriBuilder (url); - if (url.IsDefaultPort) - bldr.Port = -1; // Avoids adding :80 or :443 to the host name in the result - - // bldr.Uri.ToString () would ruin the good job UriBuilder did - return bldr.ToString (); - } - - /// - /// Returns a custom host name verifier for a HTTPS connection. By default it returns null and - /// thus the connection uses whatever host name verification mechanism the operating system defaults to. - /// Override in your class to define custom host name verification behavior. The overriding class should - /// not set the property directly on the passed - /// - /// - /// Instance of IHostnameVerifier to be used for this HTTPS connection - /// HTTPS connection object. - protected virtual IHostnameVerifier? GetSSLHostnameVerifier (HttpsURLConnection connection) - { - return null; - } - - /// - /// Creates, configures and processes an asynchronous request to the indicated resource. - /// - /// Task in which the request is executed - /// Request provided by - /// Cancellation token. - protected override async Task SendAsync (HttpRequestMessage request, CancellationToken cancellationToken) - { - AssertSelf (); - if (request == null) - throw new ArgumentNullException (nameof (request)); - - if (!request.RequestUri.IsAbsoluteUri) - throw new ArgumentException ("Must represent an absolute URI", "request"); - - var redirectState = new RequestRedirectionState { - NewUrl = request.RequestUri, - RedirectCounter = 0, - Method = request.Method - }; - while (true) { - URL java_url = new URL (EncodeUrl (redirectState.NewUrl)); - URLConnection? java_connection; - if (UseProxy) { - var javaProxy = await GetJavaProxy (redirectState.NewUrl, cancellationToken).ConfigureAwait (continueOnCapturedContext: false); - // When you use the parameter Java.Net.Proxy.NoProxy the system proxy is overriden. Leave the parameter out to respect the default settings. - java_connection = javaProxy == Java.Net.Proxy.NoProxy ? java_url.OpenConnection () : java_url.OpenConnection (javaProxy); - } else { - // In this case the consumer of this class has explicitly chosen to not use a proxy, so bypass the default proxy. The default value of UseProxy is true. - java_connection = java_url.OpenConnection (Java.Net.Proxy.NoProxy); - } - - var httpsConnection = java_connection as HttpsURLConnection; - if (httpsConnection != null) { - IHostnameVerifier? hnv = GetSSLHostnameVerifier (httpsConnection); - if (hnv != null) - httpsConnection.HostnameVerifier = hnv; - } - - if (ConnectTimeout != TimeSpan.Zero) - java_connection!.ConnectTimeout = checked ((int)ConnectTimeout.TotalMilliseconds); - - if (ReadTimeout != TimeSpan.Zero) - java_connection!.ReadTimeout = checked ((int)ReadTimeout.TotalMilliseconds); - - try { - HttpURLConnection httpConnection = await SetupRequestInternal (request, java_connection!).ConfigureAwait (continueOnCapturedContext: false); - HttpResponseMessage? response = await ProcessRequest (request, java_url, httpConnection, cancellationToken, redirectState).ConfigureAwait (continueOnCapturedContext: false); - if (response != null) - return response; - - if (redirectState.NewUrl == null) - throw new InvalidOperationException ("Request redirected but no new URI specified"); - request.Method = redirectState.Method; - } catch (Java.Net.SocketTimeoutException ex) when (JNIEnv.ShouldWrapJavaException (ex)) { - throw new WebException (ex.Message, ex, WebExceptionStatus.Timeout, null); - } catch (Java.Net.UnknownServiceException ex) when (JNIEnv.ShouldWrapJavaException (ex)) { - throw new WebException (ex.Message, ex, WebExceptionStatus.ProtocolError, null); - } catch (Java.Lang.SecurityException ex) when (JNIEnv.ShouldWrapJavaException (ex)) { - throw new WebException (ex.Message, ex, WebExceptionStatus.SecureChannelFailure, null); - } catch (Java.IO.IOException ex) when (JNIEnv.ShouldWrapJavaException (ex)) { - throw new WebException (ex.Message, ex, WebExceptionStatus.UnknownError, null); - } - } - } - - protected virtual async Task GetJavaProxy (Uri destination, CancellationToken cancellationToken) - { - var proxy = Java.Net.Proxy.NoProxy; - - if (destination == null || Proxy == null) { - return proxy; - } - - Uri puri = Proxy.GetProxy (destination); - if (puri == null) { - return proxy; - } - - proxy = await Task .Run (() => { - // Let the Java code resolve the address, if necessary - var addr = new Java.Net.InetSocketAddress (puri.Host, puri.Port); - return new Java.Net.Proxy (Java.Net.Proxy.Type.Http, addr); - }, cancellationToken); - - return proxy; - } - - Task ProcessRequest (HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState) - { - cancellationToken.ThrowIfCancellationRequested (); - httpConnection.InstanceFollowRedirects = false; // We handle it ourselves - RequestedAuthentication = null; - ProxyAuthenticationRequested = false; - - return DoProcessRequest (request, javaUrl, httpConnection, cancellationToken, redirectState); - } - - Task DisconnectAsync (HttpURLConnection httpConnection) - { - return Task.Run (() => httpConnection?.Disconnect ()); - } - - Task ConnectAsync (HttpURLConnection httpConnection, CancellationToken ct) - { - return Task.Run (() => { - try { - using (ct.Register(() => DisconnectAsync(httpConnection).ContinueWith(t => { - if (t.Exception != null) Logger.Log(LogLevel.Info, LOG_APP, $"Disconnection exception: {t.Exception}"); - }, TaskScheduler.Default))) - httpConnection?.Connect (); - } catch (Exception ex) { - if (ct.IsCancellationRequested) { - Logger.Log (LogLevel.Info, LOG_APP, $"Exception caught while cancelling connection: {ex}"); - ct.ThrowIfCancellationRequested (); - } - throw; - } - }, ct); - } - - protected virtual async Task WriteRequestContentToOutput (HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken) - { - using (var stream = await request.Content.ReadAsStreamAsync ().ConfigureAwait (false)) { - await stream.CopyToAsync(httpConnection.OutputStream!, 4096, cancellationToken).ConfigureAwait(false); - - // - // Rewind the stream to beginning in case the HttpContent implementation - // will be accessed again (e.g. after redirect) and it keeps its stream - // open behind the scenes instead of recreating it on the next call to - // ReadAsStreamAsync. If we don't rewind it, the ReadAsStreamAsync - // call above will throw an exception as we'd be attempting to read an - // already "closed" stream (that is one whose Position is set to its - // end). - // - // This is not a perfect solution since the HttpContent may do weird - // things in its implementation, but it's better than copying the - // content into a buffer since we have no way of knowing how the data is - // read or generated and also we don't want to keep potentially large - // amounts of data in memory (which would happen if we read the content - // into a byte[] buffer and kept it cached for re-use on redirect). - // - // See https://bugzilla.xamarin.com/show_bug.cgi?id=55477 - // - if (stream.CanSeek) - stream.Seek (0, SeekOrigin.Begin); - } - } - - async Task DoProcessRequest (HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState) - { - if (Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, $"{this}.DoProcessRequest ()"); - - if (cancellationToken.IsCancellationRequested) { - if(Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, " cancelled"); - - cancellationToken.ThrowIfCancellationRequested (); - } - - try { - if (Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, $" connecting"); - - await ConnectAsync (httpConnection, cancellationToken).ConfigureAwait (continueOnCapturedContext: false); - if (Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, $" connected"); - } catch (Java.Net.ConnectException ex) { - if (Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, $"Connection exception {ex}"); - // Wrap it nicely in a "standard" exception so that it's compatible with HttpClientHandler - throw new WebException (ex.Message, ex, WebExceptionStatus.ConnectFailure, null); - } - - if (cancellationToken.IsCancellationRequested) { - if(Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, " cancelled"); - - await DisconnectAsync (httpConnection).ConfigureAwait (continueOnCapturedContext: false); - cancellationToken.ThrowIfCancellationRequested (); - } - - CancellationTokenRegistration cancelRegistration = default (CancellationTokenRegistration); - HttpStatusCode statusCode = HttpStatusCode.OK; - Uri? connectionUri = null; - - try { - cancelRegistration = cancellationToken.Register (() => { - DisconnectAsync (httpConnection).ContinueWith (t => { - if (t.Exception != null) - Logger.Log (LogLevel.Info, LOG_APP, $"Disconnection exception: {t.Exception}"); - }, TaskScheduler.Default); - }, useSynchronizationContext: false); - - if (httpConnection.DoOutput) - await WriteRequestContentToOutput (request, httpConnection, cancellationToken); - - statusCode = await Task.Run (() => (HttpStatusCode)httpConnection.ResponseCode, cancellationToken).ConfigureAwait (false); - connectionUri = new Uri (httpConnection.URL?.ToString ()!); - } finally { - cancelRegistration.Dispose (); - } - - if (cancellationToken.IsCancellationRequested) { - await DisconnectAsync (httpConnection).ConfigureAwait (continueOnCapturedContext: false); - cancellationToken.ThrowIfCancellationRequested(); - } - - // If the request was redirected we need to put the new URL in the request - request.RequestUri = connectionUri; - var ret = new AndroidHttpResponseMessage (javaUrl, httpConnection) { - RequestMessage = request, - ReasonPhrase = httpConnection.ResponseMessage, - StatusCode = statusCode, - }; - - if (Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, $"Status code: {statusCode}"); - - if (!IsErrorStatusCode (statusCode)) { - if (Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, $"Reading..."); - ret.Content = GetContent (httpConnection, httpConnection.InputStream!); - } else { - if (Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, $"Status code is {statusCode}, reading..."); - // For 400 >= response code <= 599 the Java client throws the FileNotFound exception when attempting to read from the input stream. - // Instead we try to read the error stream and return an empty string if the error stream isn't readable. - ret.Content = GetErrorContent (httpConnection, new StringContent (String.Empty, Encoding.ASCII)); - } - - bool disposeRet; - if (HandleRedirect (statusCode, httpConnection, redirectState, out disposeRet)) { - if (redirectState.MethodChanged) { - // If a redirect uses GET but the original request used POST with content, then the redirected - // request will fail with an exception. - // There's also no way to send content using GET (except in the URL, of course), so discarding - // request.Content is what we should do. - // - // See https://github.com/xamarin/xamarin-android/issues/1282 - if (redirectState.Method == HttpMethod.Get) { - if (Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, $"Discarding content on redirect"); - request.Content = null; - } - } - - CopyHeaders (httpConnection, ret); - ParseCookies (ret, connectionUri); - - if (disposeRet) { - ret.Dispose (); - ret = null!; - } - - // We don't want to pass the authorization header onto the next location - request.Headers.Authorization = null; - - return ret; - } - - switch (statusCode) { - case HttpStatusCode.Unauthorized: - case HttpStatusCode.ProxyAuthenticationRequired: - // We don't resend the request since that would require new set of credentials if the - // ones provided in Credentials are invalid (or null) and that, in turn, may require asking the - // user which is not something that should be taken care of by us and in this - // context. The application should be responsible for this. - // HttpClientHandler throws an exception in this instance, but I think it's not a good - // idea. We'll return the response message with all the information required by the - // application to fill in the blanks and provide the requested credentials instead. - // - // We return the body of the response too, but the Java client will throw - // a FileNotFound exception if we attempt to access the input stream. - // Instead we try to read the error stream and return an default message if the error stream isn't readable. - ret.Content = GetErrorContent (httpConnection, new StringContent ("Unauthorized", Encoding.ASCII)); - CopyHeaders (httpConnection, ret); - - if (ret.Headers.WwwAuthenticate != null) { - ProxyAuthenticationRequested = false; - CollectAuthInfo (ret.Headers.WwwAuthenticate); - } else if (ret.Headers.ProxyAuthenticate != null) { - ProxyAuthenticationRequested = true; - CollectAuthInfo (ret.Headers.ProxyAuthenticate); - } - - ret.RequestedAuthentication = RequestedAuthentication; - return ret; - } - - CopyHeaders (httpConnection, ret); - ParseCookies (ret, connectionUri); - - if (Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, $"Returning"); - return ret; - } - - HttpContent GetErrorContent (HttpURLConnection httpConnection, HttpContent fallbackContent) - { - var contentStream = httpConnection.ErrorStream; - - if (contentStream != null) { - return GetContent (httpConnection, contentStream); - } - - return fallbackContent; - } - - HttpContent GetContent (URLConnection httpConnection, Stream contentStream) - { - Stream inputStream = new BufferedStream (contentStream); - if (decompress_here) { - var encodings = httpConnection.ContentEncoding?.Split (','); - if (encodings != null) { - if (encodings.Contains (GZIP_ENCODING, StringComparer.OrdinalIgnoreCase)) - inputStream = new GZipStream (inputStream, CompressionMode.Decompress); - else if (encodings.Contains (DEFLATE_ENCODING, StringComparer.OrdinalIgnoreCase)) - inputStream = new DeflateStream (inputStream, CompressionMode.Decompress); - } - } - return new StreamContent (inputStream); - } - - bool HandleRedirect (HttpStatusCode redirectCode, HttpURLConnection httpConnection, RequestRedirectionState redirectState, out bool disposeRet) - { - if (!AllowAutoRedirect) { - disposeRet = false; - return true; // We shouldn't follow and there's no data to fetch, just return - } - disposeRet = true; - - redirectState.NewUrl = null; - redirectState.MethodChanged = false; - switch (redirectCode) { - case HttpStatusCode.MultipleChoices: // 300 - break; - - case HttpStatusCode.Moved: // 301 - case HttpStatusCode.Redirect: // 302 - case HttpStatusCode.SeeOther: // 303 - redirectState.MethodChanged = redirectState.Method != HttpMethod.Get; - redirectState.Method = HttpMethod.Get; - break; - - case HttpStatusCode.NotModified: // 304 - disposeRet = false; - return true; // Not much happening here, just return and let the client decide - // what to do with the response - - case HttpStatusCode.TemporaryRedirect: // 307 - break; - - default: - if ((int)redirectCode >= 300 && (int)redirectCode < 400) - throw new InvalidOperationException ($"HTTP Redirection status code {redirectCode} ({(int)redirectCode}) not supported"); - return false; - } - - var headers = httpConnection.HeaderFields; - IList ? locationHeader = null; - string? location = null; - - if (headers?.TryGetValue ("Location", out locationHeader) == true && locationHeader != null && locationHeader.Count > 0) { - if (locationHeader.Count == 1) { - location = locationHeader [0]?.Trim (); - } else { - if (Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, $"More than one location header for HTTP {redirectCode} redirect. Will use the first non-empty one."); - - foreach (string l in locationHeader) { - location = l?.Trim (); - if (!String.IsNullOrEmpty (location)) - break; - } - } - } - - if (String.IsNullOrEmpty (location)) { - // As per https://tools.ietf.org/html/rfc7231#section-6.4.1 the reponse isn't required to contain the Location header and the - // client should act accordingly. Since it is not documented what the action in this case should be, we're following what - // Xamarin.iOS does and simply return the content of the request as if it wasn't a redirect. - // It is not clear what to do if there is a Location header but its value is empty, so - // we assume the same action here. - disposeRet = false; - return true; - } - - redirectState.RedirectCounter++; - if (redirectState.RedirectCounter >= MaxAutomaticRedirections) - throw new WebException ($"Maximum automatic redirections exceeded (allowed {MaxAutomaticRedirections}, redirected {redirectState.RedirectCounter} times)"); - - Uri redirectUrl; - try { - if (Logger.LogNet) - Logger.Log (LogLevel.Debug, LOG_APP, $"Raw redirect location: {location}"); - - var baseUrl = new Uri (httpConnection.URL?.ToString ()!); - if (location? [0] == '/') { - // Shortcut for the '/' and '//' cases, simplifies logic since URI won't treat - // such URLs as relative and we'd have to work around it in the `else` block - // below. - redirectUrl = new Uri (baseUrl, location); - } else { - // Special case (from https://tools.ietf.org/html/rfc3986#section-5.4.1) not - // handled by the Uri class: scheme:host - // - // This is a valid URI (should be treated as `scheme://host`) but URI throws an - // exception about DOS path being malformed IF the part before colon is just one - // character long... We could replace the scheme with the original request's one, but - // that would NOT be the right thing to do since it is not what the redirecting server - // meant. The fix doesn't belong here, but rather in the Uri class. So we'll throw... - - redirectUrl = new Uri (location!, UriKind.RelativeOrAbsolute); - if (!redirectUrl.IsAbsoluteUri) - redirectUrl = new Uri (baseUrl, location); - } - - if (Logger.LogNet) - Logger.Log (LogLevel.Debug, LOG_APP, $"Cooked redirect location: {redirectUrl}"); - } catch (Exception ex) { - throw new WebException ($"Invalid redirect URI received: {location}", ex); - } - - UriBuilder? builder = null; - if (!String.IsNullOrEmpty (httpConnection.URL?.Ref) && String.IsNullOrEmpty (redirectUrl.Fragment)) { - if (Logger.LogNet) - Logger.Log (LogLevel.Debug, LOG_APP, $"Appending fragment '{httpConnection.URL?.Ref}' to redirect URL '{redirectUrl}'"); - - builder = new UriBuilder (redirectUrl) { - Fragment = httpConnection.URL?.Ref - }; - } - - redirectState.NewUrl = builder == null ? redirectUrl : builder.Uri; - if (Logger.LogNet) - Logger.Log (LogLevel.Debug, LOG_APP, $"Request redirected to {redirectState.NewUrl}"); - - return true; - } - - bool IsErrorStatusCode (HttpStatusCode statusCode) - { - return (int)statusCode >= 400 && (int)statusCode <= 599; - } - - void CollectAuthInfo (HttpHeaderValueCollection headers) - { - var authData = new List (headers.Count); - - foreach (AuthenticationHeaderValue ahv in headers) { - var data = new AuthenticationData { - Scheme = GetAuthScheme (ahv.Scheme), - Challenge = $"{ahv.Scheme} {ahv.Parameter}", - UseProxyAuthentication = ProxyAuthenticationRequested - }; - authData.Add (data); - } - - RequestedAuthentication = authData.AsReadOnly (); - } - - AuthenticationScheme GetAuthScheme (string scheme) - { - if (String.Compare ("basic", scheme, StringComparison.OrdinalIgnoreCase) == 0) - return AuthenticationScheme.Basic; - if (String.Compare ("digest", scheme, StringComparison.OrdinalIgnoreCase) == 0) - return AuthenticationScheme.Digest; - - return AuthenticationScheme.Unsupported; - } - - void ParseCookies (AndroidHttpResponseMessage ret, Uri connectionUri) - { - IEnumerable cookieHeaderValue; - if (!UseCookies || CookieContainer == null || !ret.Headers.TryGetValues ("Set-Cookie", out cookieHeaderValue) || cookieHeaderValue == null) { - if (Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, $"No cookies"); - return; - } - - try { - if (Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, $"Parsing cookies"); - CookieContainer.SetCookies (connectionUri, String.Join (",", cookieHeaderValue)); - } catch (Exception ex) { - // We don't want to terminate the response because of a bad cookie, hence just reporting - // the issue. We might consider adding a virtual method to let the user handle the - // issue, but not sure if it's really needed. Set-Cookie header will be part of the - // header collection so the user can always examine it if they spot an error. - if (Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, $"Failed to parse cookies in the server response. {ex.GetType ()}: {ex.Message}"); - } - } - - void CopyHeaders (HttpURLConnection httpConnection, HttpResponseMessage response) - { - var headers = httpConnection.HeaderFields; - foreach (var key in headers!.Keys) { - if (key == null) // First header entry has null key, it corresponds to the response message - continue; - - HttpHeaders item_headers; - - if (known_content_headers.Contains (key)) { - item_headers = response.Content.Headers; - } else { - item_headers = response.Headers; - } - item_headers.TryAddWithoutValidation (key, headers [key]); - } - } - - /// - /// Configure the before the request is sent. This method is meant to be overriden - /// by applications which need to perform some extra configuration steps on the connection. It is called with all - /// the request headers set, pre-authentication performed (if applicable) but before the request body is set - /// (e.g. for POST requests). The default implementation in AndroidClientHandler does nothing. - /// - /// Request data - /// Pre-configured connection instance - protected virtual Task SetupRequest (HttpRequestMessage request, HttpURLConnection conn) - { - AssertSelf (); - - return Task.CompletedTask; - } - - /// - /// Configures the key store. The parameter is set to instance of - /// created using the type and with populated with certificates provided in the - /// property. AndroidClientHandler implementation simply returns the instance passed in the parameter - /// - /// The key store. - /// Key store to configure. - protected virtual KeyStore? ConfigureKeyStore (KeyStore? keyStore) - { - AssertSelf (); - - return keyStore; - } - - /// - /// Create and configure an instance of . The parameter is set to the - /// return value of the method, so it might be null if the application overrode the method and provided - /// no key store. It will not be null when the default implementation is used. The application can return null here since - /// KeyManagerFactory is not required for the custom SSL configuration, but it might be used by the application to implement a more advanced - /// mechanism of key management. - /// - /// The key manager factory or null. - /// Key store. - protected virtual KeyManagerFactory? ConfigureKeyManagerFactory (KeyStore? keyStore) - { - AssertSelf (); - - return null; - } - - /// - /// Create and configure an instance of . The parameter is set to the - /// return value of the method, so it might be null if the application overrode the method and provided - /// no key store. It will not be null when the default implementation is used. The application can return null from this - /// method in which case AndroidClientHandler will create its own instance of the trust manager factory provided that the - /// list contains at least one valid certificate. If there are no valid certificates and this method returns null, no custom - /// trust manager will be created since that would make all the HTTPS requests fail. - /// - /// The trust manager factory. - /// Key store. - protected virtual TrustManagerFactory? ConfigureTrustManagerFactory (KeyStore? keyStore) - { - AssertSelf (); - - return null; - } - - void AppendEncoding (string encoding, ref List ? list) - { - if (list == null) - list = new List (); - if (list.Contains (encoding)) - return; - list.Add (encoding); - } - - async Task SetupRequestInternal (HttpRequestMessage request, URLConnection conn) - { - if (conn == null) - throw new ArgumentNullException (nameof (conn)); - var httpConnection = conn.JavaCast (); - if (httpConnection == null) - throw new InvalidOperationException ($"Unsupported URL scheme {conn.URL?.Protocol}"); - - try { - httpConnection.RequestMethod = request.Method.ToString (); - } catch (Java.Net.ProtocolException ex) when (JNIEnv.ShouldWrapJavaException (ex)) { - throw new WebException (ex.Message, ex, WebExceptionStatus.ProtocolError, null); - } - - // SSL context must be set up as soon as possible, before adding any content or - // headers. Otherwise Java won't use the socket factory - SetupSSL (httpConnection as HttpsURLConnection); - if (request.Content != null) - AddHeaders (httpConnection, request.Content.Headers); - AddHeaders (httpConnection, request.Headers); - - List ? accept_encoding = null; - - decompress_here = false; - if ((AutomaticDecompression & DecompressionMethods.GZip) != 0) { - AppendEncoding (GZIP_ENCODING, ref accept_encoding); - decompress_here = true; - } - - if ((AutomaticDecompression & DecompressionMethods.Deflate) != 0) { - AppendEncoding (DEFLATE_ENCODING, ref accept_encoding); - decompress_here = true; - } - - if (AutomaticDecompression == DecompressionMethods.None) { - accept_encoding?.Clear (); - AppendEncoding (IDENTITY_ENCODING, ref accept_encoding); // Turns off compression for the Java client - } - - if (accept_encoding?.Count > 0) - httpConnection.SetRequestProperty ("Accept-Encoding", String.Join (",", accept_encoding)); - - if (UseCookies && CookieContainer != null) { - string cookieHeaderValue = CookieContainer.GetCookieHeader (request.RequestUri); - if (!String.IsNullOrEmpty (cookieHeaderValue)) - httpConnection.SetRequestProperty ("Cookie", cookieHeaderValue); - } - - HandlePreAuthentication (httpConnection); - await SetupRequest (request, httpConnection).ConfigureAwait (continueOnCapturedContext: false);; - SetupRequestBody (httpConnection, request); - - return httpConnection; - } - - /// - /// Configure and return a custom for the passed HTTPS . If the class overriding the method returns anything but the default - /// null, the SSL setup code will not call the nor the - /// methods used to configure a custom trust manager which is - /// then used to create a default socket factory. - /// Deriving class must perform all the key manager and trust manager configuration to ensure proper - /// operation of the returned socket factory. - /// - /// Instance of SSLSocketFactory ready to use with the HTTPS connection. - /// HTTPS connection to return socket factory for - protected virtual SSLSocketFactory? ConfigureCustomSSLSocketFactory (HttpsURLConnection connection) - { - return null; - } - - void SetupSSL (HttpsURLConnection? httpsConnection) - { - if (httpsConnection == null) - return; - - var socketFactory = ConfigureCustomSSLSocketFactory (httpsConnection); - if (socketFactory != null) { - httpsConnection.SSLSocketFactory = socketFactory; - return; - } - - // Context: https://github.com/xamarin/xamarin-android/issues/1615 - int apiLevel = (int)Build.VERSION.SdkInt; - if (apiLevel >= 16 && apiLevel <= 20) { - httpsConnection.SSLSocketFactory = new OldAndroidSSLSocketFactory (); - return; - } - - var keyStore = KeyStore.GetInstance (KeyStore.DefaultType); - keyStore?.Load (null, null); - bool gotCerts = TrustedCerts?.Count > 0; - if (gotCerts) { - for (int i = 0; i < TrustedCerts!.Count; i++) { - Certificate cert = TrustedCerts [i]; - if (cert == null) - continue; - keyStore?.SetCertificateEntry ($"ca{i}", cert); - } - } - keyStore = ConfigureKeyStore (keyStore); - var kmf = ConfigureKeyManagerFactory (keyStore); - var tmf = ConfigureTrustManagerFactory (keyStore); - - if (tmf == null) { - // If there are no certs and no trust manager factory, we can't use a custom manager - // because it will cause all the HTTPS requests to fail because of unverified trust - // chain - if (!gotCerts) - return; - - tmf = TrustManagerFactory.GetInstance (TrustManagerFactory.DefaultAlgorithm); - tmf?.Init (keyStore); - } - - var context = SSLContext.GetInstance ("TLS"); - context?.Init (kmf?.GetKeyManagers (), tmf?.GetTrustManagers (), null); - httpsConnection.SSLSocketFactory = context?.SocketFactory; - } - - void HandlePreAuthentication (HttpURLConnection httpConnection) - { - var data = PreAuthenticationData; - if (!PreAuthenticate || data == null) - return; - - var creds = data.UseProxyAuthentication ? Proxy?.Credentials : Credentials; - if (creds == null) { - if (Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, $"Authentication using scheme {data.Scheme} requested but no credentials found. No authentication will be performed"); - return; - } - - var auth = data.Scheme == AuthenticationScheme.Unsupported ? data.AuthModule : authModules.Find (m => m?.Scheme == data.Scheme); - if (auth == null) { - if (Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, $"Authentication module for scheme '{data.Scheme}' not found. No authentication will be performed"); - return; - } - - Authorization authorization = auth.Authenticate (data.Challenge!, httpConnection, creds); - if (authorization == null) { - if (Logger.LogNet) - Logger.Log (LogLevel.Info, LOG_APP, $"Authorization module {auth.GetType ()} for scheme {data.Scheme} returned no authorization"); - return; - } - - if (Logger.LogNet) { - var header = data.UseProxyAuthentication ? "Proxy-Authorization" : "Authorization"; - Logger.Log (LogLevel.Info, LOG_APP, $"Authentication header '{header}' will be set to '{authorization.Message}'"); - } - httpConnection.SetRequestProperty (data.UseProxyAuthentication ? "Proxy-Authorization" : "Authorization", authorization.Message); - } - - static string GetHeaderSeparator (string name) => headerSeparators.TryGetValue (name, out var value) ? value : ","; - - void AddHeaders (HttpURLConnection conn, HttpHeaders headers) - { - if (headers == null) - return; - - foreach (KeyValuePair> header in headers) { - conn.SetRequestProperty (header.Key, header.Value != null ? String.Join (GetHeaderSeparator (header.Key), header.Value) : String.Empty); - } - } - - void SetupRequestBody (HttpURLConnection httpConnection, HttpRequestMessage request) - { - if (request.Content == null) { - // Pilfered from System.Net.Http.HttpClientHandler:SendAync - if (HttpMethod.Post.Equals (request.Method) || HttpMethod.Put.Equals (request.Method) || HttpMethod.Delete.Equals (request.Method)) { - // Explicitly set this to make sure we're sending a "Content-Length: 0" header. - // This fixes the issue that's been reported on the forums: - // http://forums.xamarin.com/discussion/17770/length-required-error-in-http-post-since-latest-release - httpConnection.SetRequestProperty ("Content-Length", "0"); - } - return; - } - - httpConnection.DoOutput = true; - long? contentLength = request.Content.Headers.ContentLength; - if (contentLength != null) - httpConnection.SetFixedLengthStreamingMode ((int)contentLength); - else - httpConnection.SetChunkedStreamingMode (0); - } - } -} diff --git a/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.cs b/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.cs deleted file mode 100644 index d8f84093ca7..00000000000 --- a/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.cs +++ /dev/null @@ -1,326 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -using Android.OS; -using Android.Runtime; -using Java.IO; -using Java.Net; -using Java.Security; -using Java.Security.Cert; -using Javax.Net.Ssl; - -namespace Xamarin.Android.Net -{ - /// - /// A custom implementation of which internally uses - /// (or its HTTPS incarnation) to send HTTP requests. - /// - /// - /// Instance of this class is used to configure instance - /// in the following way: - /// - /// - /// var handler = new AndroidClientHandler { - /// UseCookies = true, - /// AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, - /// }; - /// - /// var httpClient = new HttpClient (handler); - /// var response = httpClient.GetAsync ("http://example.com")?.Result as AndroidHttpResponseMessage; - /// - /// - /// The class supports pre-authentication of requests albeit in a slightly "manual" way. Namely, whenever a request to a server requiring authentication - /// is made and no authentication credentials are provided in the property (which is usually the case on the first - /// request), the property will return true and the property will - /// contain all the authentication information gathered from the server. The application must then fill in the blanks (i.e. the credentials) and re-send - /// the request configured to perform pre-authentication. The reason for this manual process is that the underlying Java HTTP client API supports only a - /// single, VM-wide, authentication handler which cannot be configured to handle credentials for several requests. AndroidClientHandler, therefore, implements - /// the authentication in managed .NET code. Message handler supports both Basic and Digest authentication. If an authentication scheme that's not supported - /// by AndroidClientHandler is requested by the server, the application can provide its own authentication module (, - /// ) to handle the protocol authorization. - /// AndroidClientHandler also supports requests to servers with "invalid" (e.g. self-signed) SSL certificates. Since this process is a bit convoluted using - /// the Java APIs, AndroidClientHandler defines a way to handle the situation. It can store the necessary certificates (either CA or server certificates) - /// in the collection. If, however, the application requires finer control over the SSL configuration (e.g. it implements its own - /// TrustManager) then it should derive a custom class from instead of using AndroidClientHandler. - /// Note that the instance of AndroidClientHandler configured to accept an "invalid" certificate from the particular server will most likely fail to validate - /// certificates from other servers (even if they use a certificate with a fully validated trust chain) unless you store the CA certificates from your Android - /// system in along with the self-signed certificate(s). - /// - [Obsolete("AndroidClientHandler has been deprecated. Use AndroidMessageHandler instead.")] - public class AndroidClientHandler : HttpClientHandler - { - internal const string LOG_APP = "monodroid-net"; - AndroidMessageHandler _underlyingHander; - - bool disposed; - - public AndroidClientHandler () - { - _underlyingHander = GetUnderlyingHandler () as AndroidMessageHandler ?? throw new InvalidOperationException ("Unknown underlying handler. Only AndroidMessageHandler is supported for AndroidClientHandler"); - } - - /// - /// - /// Gets or sets the pre authentication data for the request. This property must be set by the application - /// before the request is made. Generally the value can be taken from - /// after the initial request, without any authentication data, receives the authorization request from the - /// server. The application must then store credentials in instance of and - /// assign the instance to this propery before retrying the request. - /// - /// - /// The property is never set by AndroidClientHandler. - /// - /// - /// The pre authentication data. - public AuthenticationData? PreAuthenticationData - { - get { return _underlyingHander.PreAuthenticationData; } - set { _underlyingHander.PreAuthenticationData = value; } - } - - /// - /// If the website requires authentication, this property will contain data about each scheme supported - /// by the server after the response. Note that unauthorized request will return a valid response - you - /// need to check the status code and and (re)configure AndroidClientHandler instance accordingly by providing - /// both the credentials and the authentication scheme by setting the - /// property. If AndroidClientHandler is not able to detect the kind of authentication scheme it will store an - /// instance of with its property - /// set to AuthenticationScheme.Unsupported and the application will be responsible for providing an - /// instance of which handles this kind of authorization scheme - /// ( - /// - public IList ? RequestedAuthentication - { - get { return _underlyingHander.RequestedAuthentication; } - } - - /// - /// Server authentication response indicates that the request to authorize comes from a proxy if this property is true. - /// All the instances of stored in the property will - /// have their preset to the same value as this property. - /// - public bool ProxyAuthenticationRequested - { - get { return _underlyingHander.ProxyAuthenticationRequested; } - } - - /// - /// If true then the server requested authorization and the application must use information - /// found in to set the value of - /// - public bool RequestNeedsAuthorization - { - get { return _underlyingHander.RequestNeedsAuthorization; } - } - - /// - /// - /// If the request is to the server protected with a self-signed (or otherwise untrusted) SSL certificate, the request will - /// fail security chain verification unless the application provides either the CA certificate of the entity which issued the - /// server's certificate or, alternatively, provides the server public key. Whichever the case, the certificate(s) must be stored - /// in this property in order for AndroidClientHandler to configure the request to accept the server certificate. - /// AndroidClientHandler uses a custom and to configure the connection. - /// If, however, the application requires finer control over the SSL configuration (e.g. it implements its own TrustManager) then - /// it should derive a custom class from instead of using AndroidClientHandler. - /// - /// The trusted certs. - public IList ? TrustedCerts - { - get { return _underlyingHander.TrustedCerts; } - set { _underlyingHander.TrustedCerts = value; } - } - - /// - /// - /// Specifies the connection read timeout. - /// - /// - /// Since there's no way for the handler to access - /// directly, this property should be set by the calling party to the same desired value. Value of this - /// property will be passed to the native Java HTTP client, unless it is set to - /// - /// - /// The default value is 24 hours, much higher than the documented value of and the same as the value of iOS-specific - /// NSUrlSessionHandler. - /// - /// - public TimeSpan ReadTimeout - { - get { return _underlyingHander.ReadTimeout; } - set { _underlyingHander.ReadTimeout = value; } - } - - /// - /// - /// Specifies the connect timeout - /// - /// - /// The native Java client supports two separate timeouts - one for reading from the connection () and another for establishing the connection. This property sets the value of - /// the latter timeout, unless it is set to in which case the - /// native Java client defaults are used. - /// - /// - /// The default value is 120 seconds. - /// - /// - public TimeSpan ConnectTimeout - { - get { return _underlyingHander.ConnectTimeout; } - set { _underlyingHander.ConnectTimeout = value; } - } - - protected override void Dispose (bool disposing) - { - disposed = true; - - base.Dispose (disposing); - } - - protected void AssertSelf () - { - if (!disposed) - return; - throw new ObjectDisposedException (nameof (AndroidClientHandler)); - } - - /// - /// Returns a custom host name verifier for a HTTPS connection. By default it returns null and - /// thus the connection uses whatever host name verification mechanism the operating system defaults to. - /// Override in your class to define custom host name verification behavior. The overriding class should - /// not set the property directly on the passed - /// - /// - /// Instance of IHostnameVerifier to be used for this HTTPS connection - /// HTTPS connection object. - protected virtual IHostnameVerifier? GetSSLHostnameVerifier (HttpsURLConnection connection) - { - return _underlyingHander.GetSSLHostnameVerifierInternal (connection); - } - - /// - /// Creates, configures and processes an asynchronous request to the indicated resource. - /// - /// Task in which the request is executed - /// Request provided by - /// Cancellation token. - protected override async Task SendAsync (HttpRequestMessage request, CancellationToken cancellationToken) - { - AssertSelf (); - return await base.SendAsync (request, cancellationToken); - } - - protected virtual async Task GetJavaProxy (Uri destination, CancellationToken cancellationToken) - { - return await _underlyingHander.GetJavaProxyInternal (destination, cancellationToken); - } - - protected virtual async Task WriteRequestContentToOutput (HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken) - { - await _underlyingHander.WriteRequestContentToOutputInternal (request, httpConnection, cancellationToken); - } - - /// - /// Configure the before the request is sent. This method is meant to be overriden - /// by applications which need to perform some extra configuration steps on the connection. It is called with all - /// the request headers set, pre-authentication performed (if applicable) but before the request body is set - /// (e.g. for POST requests). The default implementation in AndroidClientHandler does nothing. - /// - /// Request data - /// Pre-configured connection instance - protected virtual Task SetupRequest (HttpRequestMessage request, HttpURLConnection conn) - { - return _underlyingHander.SetupRequestInternal (request, conn); - } - - /// - /// Configures the key store. The parameter is set to instance of - /// created using the type and with populated with certificates provided in the - /// property. AndroidClientHandler implementation simply returns the instance passed in the parameter - /// - /// The key store. - /// Key store to configure. - protected virtual KeyStore? ConfigureKeyStore (KeyStore? keyStore) - { - AssertSelf (); - - return _underlyingHander.ConfigureKeyStoreInternal (keyStore); - } - - /// - /// Create and configure an instance of . The parameter is set to the - /// return value of the method, so it might be null if the application overrode the method and provided - /// no key store. It will not be null when the default implementation is used. The application can return null here since - /// KeyManagerFactory is not required for the custom SSL configuration, but it might be used by the application to implement a more advanced - /// mechanism of key management. - /// - /// The key manager factory or null. - /// Key store. - protected virtual KeyManagerFactory? ConfigureKeyManagerFactory (KeyStore? keyStore) - { - AssertSelf (); - - return _underlyingHander.ConfigureKeyManagerFactoryInternal (keyStore); - } - - /// - /// Create and configure an instance of . The parameter is set to the - /// return value of the method, so it might be null if the application overrode the method and provided - /// no key store. It will not be null when the default implementation is used. The application can return null from this - /// method in which case AndroidClientHandler will create its own instance of the trust manager factory provided that the - /// list contains at least one valid certificate. If there are no valid certificates and this method returns null, no custom - /// trust manager will be created since that would make all the HTTPS requests fail. - /// - /// The trust manager factory. - /// Key store. - protected virtual TrustManagerFactory? ConfigureTrustManagerFactory (KeyStore? keyStore) - { - AssertSelf (); - - return _underlyingHander.ConfigureTrustManagerFactoryInternal (keyStore); - } - - /// - /// Configure and return a custom for the passed HTTPS . If the class overriding the method returns anything but the default - /// null, the SSL setup code will not call the nor the - /// methods used to configure a custom trust manager which is - /// then used to create a default socket factory. - /// Deriving class must perform all the key manager and trust manager configuration to ensure proper - /// operation of the returned socket factory. - /// - /// Instance of SSLSocketFactory ready to use with the HTTPS connection. - /// HTTPS connection to return socket factory for - protected virtual SSLSocketFactory? ConfigureCustomSSLSocketFactory (HttpsURLConnection connection) - { - return _underlyingHander.ConfigureCustomSSLSocketFactoryInternal (connection); - } - - [DynamicDependency (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor, typeof (AndroidMessageHandler))] - object? GetUnderlyingHandler () - { - var fieldName = "_nativeUnderlyingHandler"; - const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; - FieldInfo? field = typeof (HttpClientHandler).GetField (fieldName, flags) ?? - typeof (HttpMessageHandler).GetField (fieldName, flags); - if (field == null) { - throw new InvalidOperationException ($"Field '{fieldName}' is missing from type '{GetType ()}'."); - } - - return field.GetValue (this); - } - } -} diff --git a/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs b/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs index 403f5a0ab2d..9dd491ac41e 100644 --- a/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs +++ b/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs @@ -149,10 +149,34 @@ public void Reset () bool decompress_here => _acceptEncoding is not null && _acceptEncoding != IDENTITY_ENCODING; string? _acceptEncoding; + /// + /// Gets a value indicating whether the handler supports automatic response content decompression. + /// Always returns for . + /// public bool SupportsAutomaticDecompression => true; + + /// + /// Gets a value indicating whether the handler supports proxy settings. + /// Always returns for . + /// public bool SupportsProxy => true; + + /// + /// Gets a value indicating whether the handler supports configuration settings for the + /// and properties. + /// Always returns for . + /// public bool SupportsRedirectConfiguration => true; + /// + /// Gets or sets the type of decompression method used by the handler for automatic + /// decompression of the HTTP content response. + /// + /// + /// Supported methods are , + /// , and . + /// Set to to disable automatic decompression. + /// public DecompressionMethods AutomaticDecompression { get => _decompressionMethods; @@ -181,6 +205,10 @@ public DecompressionMethods AutomaticDecompression } } + /// + /// Gets or sets the cookie container used to store server cookies. + /// + /// The value specified is . public CookieContainer CookieContainer { get => _cookieContainer ?? (_cookieContainer = new CookieContainer ()); @@ -196,21 +224,55 @@ public CookieContainer CookieContainer // NOTE: defaults here are based on: // https://github.com/dotnet/runtime/blob/f3b77e64b87895aa7e697f321eb6d4151a4333df/src/libraries/Common/src/System/Net/Http/HttpHandlerDefaults.cs + /// + /// Gets or sets a value that indicates whether the handler uses the + /// property to store server cookies and uses these cookies when sending requests. The default value + /// is . + /// public bool UseCookies { get; set; } = true; + /// + /// Gets or sets a value that indicates whether the handler sends an Authorization header with the + /// request. The default value is . + /// public bool PreAuthenticate { get; set; } = false; + /// + /// Gets or sets a value that indicates whether the handler uses a proxy for requests. The default + /// value is . + /// public bool UseProxy { get; set; } = true; + /// + /// Gets or sets the proxy information used by the handler. + /// public IWebProxy? Proxy { get; set; } + /// + /// Gets or sets authentication information used by this handler. + /// public ICredentials? Credentials { get; set; } + /// + /// Gets or sets a value that indicates whether the handler should follow redirection responses. + /// The default value is . + /// public bool AllowAutoRedirect { get; set; } = true; + /// + /// Gets or sets a value that indicates how client certificates are provided. The default value is + /// . + /// public ClientCertificateOption ClientCertificateOptions { get; set; } = ClientCertificateOption.Manual; private X509CertificateCollection? _clientCertificates; + + /// + /// Gets or sets the collection of client certificates used by the handler to authenticate the client. + /// + /// + /// is not set to . + /// public X509CertificateCollection? ClientCertificates { get @@ -225,16 +287,35 @@ public X509CertificateCollection? ClientCertificates set => _clientCertificates = value; } + /// + /// Gets or sets the credentials to use when authenticating with the proxy. + /// public ICredentials? DefaultProxyCredentials { get; set; } + /// + /// Gets or sets the maximum number of concurrent connections allowed per server. The default value + /// is . + /// public int MaxConnectionsPerServer { get; set; } = int.MaxValue; + /// + /// Gets or sets the maximum length, in kilobytes (1024 bytes), of the response headers. The default + /// value is 64 KB. + /// public int MaxResponseHeadersLength { get; set; } = 64; // Units in K (1024) bytes. + /// + /// Gets or sets a value that indicates whether the certificate revocation list is checked during + /// validation. The default value is . + /// public bool CheckCertificateRevocationList { get; set; } = false; ServerCertificateCustomValidator? _serverCertificateCustomValidator = null; + /// + /// Gets or sets a callback to validate the server certificate. When set, the callback is invoked + /// during the TLS handshake to perform custom certificate validation. + /// public Func? ServerCertificateCustomValidationCallback { get => _serverCertificateCustomValidator?.Callback; @@ -249,15 +330,34 @@ public X509CertificateCollection? ClientCertificates } } + /// + /// Gets or sets the TLS/SSL protocols used by the handler. The default value is + /// | on Android API 29+, + /// or on earlier versions. + /// + /// + /// See the + /// Android SSLSocket documentation for details on supported protocols by API level. + /// // See: https://developer.android.com/reference/javax/net/ssl/SSLSocket#protocols public SslProtocols SslProtocols { get; set; } = (int)Build.VERSION.SdkInt >= 29 ? SslProtocols.Tls13 | SslProtocols.Tls12 : SslProtocols.Tls12; + /// + /// Gets or sets a custom property collection for the handler. This can be used to pass + /// additional metadata or configuration to the underlying transport. + /// public IDictionary? Properties { get; set; } int maxAutomaticRedirections = 50; + /// + /// Gets or sets the maximum number of allowed HTTP redirects. The default value is 50. + /// + /// + /// The specified value is less than or equal to 0. + /// public int MaxAutomaticRedirections { get => maxAutomaticRedirections; @@ -421,9 +521,6 @@ string EncodeUrl (Uri url) return _serverCertificateCustomValidator?.HostnameVerifier; } - internal IHostnameVerifier? GetSSLHostnameVerifierInternal (HttpsURLConnection connection) - => GetSSLHostnameVerifier (connection); - /// /// Creates, configures and processes an asynchronous request to the indicated resource. /// @@ -514,9 +611,6 @@ string EncodeUrl (Uri url) } } - internal Task SendAsyncInternal (HttpRequestMessage request, CancellationToken cancellationToken) - => SendAsync (request, cancellationToken); - protected virtual async Task GetJavaProxy (Uri destination, CancellationToken cancellationToken) { var proxy = Java.Net.Proxy.NoProxy; @@ -539,9 +633,6 @@ string EncodeUrl (Uri url) return proxy; } - internal Task GetJavaProxyInternal (Uri destination, CancellationToken cancellationToken) - => GetJavaProxy (destination, cancellationToken); - Task ProcessRequest (HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState) { cancellationToken.ThrowIfCancellationRequested (); @@ -615,9 +706,6 @@ protected virtual async Task WriteRequestContentToOutput (HttpRequestMessage req } } - internal Task WriteRequestContentToOutputInternal (HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken) - => WriteRequestContentToOutput (request, httpConnection, cancellationToken); - async Task DoProcessRequest (HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState) { if (Logger.LogNet) @@ -912,8 +1000,14 @@ bool HandleRedirect (HttpStatusCode redirectCode, HttpURLConnection httpConnecti // meant. The fix doesn't belong here, but rather in the Uri class. So we'll throw... redirectUrl = new Uri (location!, UriKind.RelativeOrAbsolute); - if (!redirectUrl.IsAbsoluteUri) + if (!redirectUrl.IsAbsoluteUri) { redirectUrl = new Uri (baseUrl, location); + } else if (string.Equals (baseUrl.Scheme, "https", StringComparison.OrdinalIgnoreCase) + && !string.Equals (redirectUrl.Scheme, "https", StringComparison.OrdinalIgnoreCase)) { + + disposeRet = false; // let the client decide what to do next + return true; + } } if (Logger.LogNet) @@ -1046,9 +1140,6 @@ protected virtual Task SetupRequest (HttpRequestMessage request, HttpURLConnecti return Task.CompletedTask; } - internal Task SetupRequestInternal (HttpRequestMessage request, HttpURLConnection conn) - => SetupRequest (request, conn); - /// /// Configures the key store. The parameter is set to instance of /// created using the type and with populated with certificates provided in the @@ -1063,9 +1154,6 @@ internal Task SetupRequestInternal (HttpRequestMessage request, HttpURLConnectio return keyStore; } - internal KeyStore? ConfigureKeyStoreInternal (KeyStore? keyStore) - => ConfigureKeyStore (keyStore); - /// /// Create and configure an instance of . The parameter is set to the /// return value of the method, so it might be null if the application overrode the method and provided @@ -1082,9 +1170,6 @@ internal Task SetupRequestInternal (HttpRequestMessage request, HttpURLConnectio return null; } - internal KeyManagerFactory? ConfigureKeyManagerFactoryInternal (KeyStore? keyStore) - => ConfigureKeyManagerFactoryInternal (keyStore); - /// /// Create and configure an instance of . The parameter is set to the /// return value of the method, so it might be null if the application overrode the method and provided @@ -1102,9 +1187,6 @@ internal Task SetupRequestInternal (HttpRequestMessage request, HttpURLConnectio return null; } - internal TrustManagerFactory? ConfigureTrustManagerFactoryInternal (KeyStore? keyStore) - => ConfigureTrustManagerFactory (keyStore); - async Task SetupRequestInternal (HttpRequestMessage request, URLConnection conn) { if (conn == null) @@ -1158,9 +1240,6 @@ internal Task SetupRequestInternal (HttpRequestMessage request, HttpURLConnectio return null; } - internal SSLSocketFactory? ConfigureCustomSSLSocketFactoryInternal (HttpsURLConnection connection) - => ConfigureCustomSSLSocketFactoryInternal (connection); - void SetupSSL (HttpsURLConnection? httpsConnection, HttpRequestMessage requestMessage) { if (httpsConnection == null) diff --git a/src/Mono.Android/Xamarin.Android.Net/AuthDigestSession.cs b/src/Mono.Android/Xamarin.Android.Net/AuthDigestSession.cs index e099c98095f..41fdcfc20b5 100644 --- a/src/Mono.Android/Xamarin.Android.Net/AuthDigestSession.cs +++ b/src/Mono.Android/Xamarin.Android.Net/AuthDigestSession.cs @@ -113,10 +113,8 @@ public bool Parse (string challenge) { var uri = new Uri (webRequest.URL?.ToString ()!); string ha2 = $"{webRequest.RequestMethod}:{uri.PathAndQuery}"; - if (QOP == "auth-int") { - // TODO - // ha2 += String.Format (":{0}", hentity); - } + // Note: auth-int QOP (RFC 7616) is not supported; it would require + // hashing the entity body which is not available here. return HashToHexString (ha2); } diff --git a/src/Mono.Android/Xamarin.Android.Net/ServerCertificateCustomValidator.cs b/src/Mono.Android/Xamarin.Android.Net/ServerCertificateCustomValidator.cs index f1508cd11e0..e70788754b1 100644 --- a/src/Mono.Android/Xamarin.Android.Net/ServerCertificateCustomValidator.cs +++ b/src/Mono.Android/Xamarin.Android.Net/ServerCertificateCustomValidator.cs @@ -180,25 +180,9 @@ private static IX509TrustManager FindX509TrustManager(ITrustManager[] trustManag index = i; return x509TrustManager; } - - // On API 21-23, the default Java trust manager is TrustManagerImpl from Conscrypt. The class implements X509TrustManager - // but the .NET pattern matching will fail in this case and we need to cast it explicitly. - int apiLevel = (int)Build.VERSION.SdkInt; - if (apiLevel <= 23) { - if (IsTrustManagerImpl (trustManager)) { - index = i; - return trustManager.JavaCast (); - } - } } throw new InvalidOperationException($"Could not find {nameof(IX509TrustManager)} in {nameof(ITrustManager)} array."); - - static bool IsTrustManagerImpl (ITrustManager trustManager) - { - var javaClassName = JNIEnv.GetClassNameFromInstance (trustManager.Handle); - return javaClassName.Equals ("com/android/org/conscrypt/TrustManagerImpl", StringComparison.Ordinal); - } } private static ITrustManager[] ModifyTrustManagersArray (ITrustManager[] trustManagers, int originalTrustManagerIndex, IX509TrustManager replacement) diff --git a/src/Mono.Android/map.csv b/src/Mono.Android/map.csv index 9288c1f7144..ad20547d9b8 100644 --- a/src/Mono.Android/map.csv +++ b/src/Mono.Android/map.csv @@ -478,9 +478,9 @@ E,36,android/app/appfunctions/AppFunctionException.ERROR_ENTERPRISE_POLICY_DISAL E,36,android/app/appfunctions/AppFunctionException.ERROR_FUNCTION_NOT_FOUND,1003,Android.App.AppFunctions.AppFunctionError,FunctionNotFound,remove, E,36,android/app/appfunctions/AppFunctionException.ERROR_INVALID_ARGUMENT,1001,Android.App.AppFunctions.AppFunctionError,InvalidArgument,remove, E,36,android/app/appfunctions/AppFunctionException.ERROR_SYSTEM_ERROR,2000,Android.App.AppFunctions.AppFunctionError,SystemError,remove, -E,36,android/app/appfunctions/AppFunctionManager.APP_FUNCTION_STATE_DEFAULT,0,Android.App.AppFunctions.AppFunctionState,Default,remove, -E,36,android/app/appfunctions/AppFunctionManager.APP_FUNCTION_STATE_DISABLED,2,Android.App.AppFunctions.AppFunctionState,Disabled,remove, -E,36,android/app/appfunctions/AppFunctionManager.APP_FUNCTION_STATE_ENABLED,1,Android.App.AppFunctions.AppFunctionState,Enabled,remove, +E,36,android/app/appfunctions/AppFunctionManager.APP_FUNCTION_STATE_DEFAULT,0,Android.App.AppFunctions.AppFunctionEnabledState,Default,remove, +E,36,android/app/appfunctions/AppFunctionManager.APP_FUNCTION_STATE_DISABLED,2,Android.App.AppFunctions.AppFunctionEnabledState,Disabled,remove, +E,36,android/app/appfunctions/AppFunctionManager.APP_FUNCTION_STATE_ENABLED,1,Android.App.AppFunctions.AppFunctionEnabledState,Enabled,remove, E,37,android/app/appfunctions/AppFunctionMetadata.SCOPE_ACTIVITY,1,Android.App.AppFunctions.AppFunctionMetadataScope,Activity,remove, E,37,android/app/appfunctions/AppFunctionMetadata.SCOPE_GLOBAL,0,Android.App.AppFunctions.AppFunctionMetadataScope,Global,remove, E,37,android/app/AppInteractionAttribution.INTERACTION_TYPE_OTHER,0,Android.App.AppInteractionAttributionInteractionType,Other,remove, diff --git a/src/Mono.Android/methodmap.csv b/src/Mono.Android/methodmap.csv index 2b452d40b49..bab127e450d 100644 --- a/src/Mono.Android/methodmap.csv +++ b/src/Mono.Android/methodmap.csv @@ -4100,7 +4100,7 @@ 36,android.app.appfunctions,AppFunctionException,getErrorCategory,return,Android.App.AppFunctions.AppFunctionErrorCategory 36,android.app.appfunctions,AppFunctionException,getErrorCode,return,Android.App.AppFunctions.AppFunctionError 36,android.app.appfunctions,AppFunctionException,writeToParcel,flags,Android.OS.ParcelableWriteFlags -36,android.app.appfunctions,AppFunctionManager,setAppFunctionEnabled,newEnabledState,Android.App.AppFunctions.AppFunctionState +36,android.app.appfunctions,AppFunctionManager,setAppFunctionEnabled,newEnabledState,Android.App.AppFunctions.AppFunctionEnabledState 36,android.app.appfunctions,ExecuteAppFunctionRequest,writeToParcel,flags,Android.OS.ParcelableWriteFlags 36,android.app.appfunctions,ExecuteAppFunctionResponse,writeToParcel,flags,Android.OS.ParcelableWriteFlags 36,android.app,ApplicationStartInfo,getStartComponent,return,Android.App.ApplicationStartInfoStartComponent diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs index 7028aea33e0..ad4efecf40b 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs @@ -150,7 +150,11 @@ string GetFixupKey (Instruction instruction, string designerFullName) try { var resolved = Cache.Resolve (fieldRef); canResolve = resolved != null; - } catch (Exception) { + } catch (AssemblyResolutionException) { + // Expected when the field's declaring assembly is not available + LogMessage ($" Could not resolve field reference {fieldRef.FullName} (assembly not available)."); + } catch (Exception ex) { + LogMessage ($" Unexpected error resolving field reference {fieldRef.FullName}: {ex}"); } if (canResolve) return null; diff --git a/src/Xamarin.Android.Build.Tasks/MamJsonToXml.cs b/src/Xamarin.Android.Build.Tasks/MamJsonToXml.cs index a8088043482..f8930cacdae 100644 --- a/src/Xamarin.Android.Build.Tasks/MamJsonToXml.cs +++ b/src/Xamarin.Android.Build.Tasks/MamJsonToXml.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using System.IO; diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/PreserveLists/System.Private.CoreLib.xml b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/PreserveLists/System.Private.CoreLib.xml new file mode 100644 index 00000000000..4532fab2a83 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/PreserveLists/System.Private.CoreLib.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets index bfaf48a1bec..67781a1b912 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets @@ -33,4 +33,5 @@ This file is imported *after* the Microsoft.NET.Sdk/Sdk.targets. + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets index 3537c75fb4b..2a214899948 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets @@ -83,7 +83,6 @@ They run in a context of an inner build with a single $(RuntimeIdentifier). runs in an inner build and `_RuntimePackLibraryDirectory` items aren't carried over to it. --> diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets index 60133cb930a..efb8c209f7b 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets @@ -144,7 +144,6 @@ _ResolveAssemblies MSBuild target. are never taken into consideration in any context. --> diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.CoreCLR.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.CoreCLR.targets index 705237961ab..27a57454aeb 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.CoreCLR.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.CoreCLR.targets @@ -30,6 +30,10 @@ This file contains the CoreCLR-specific MSBuild logic for .NET for Android. Value="true" Trim="true" /> + Assets Resource true - Xamarin.Android.Net.AndroidMessageHandler true true false @@ -33,7 +32,7 @@ $(AndroidMinimumSupportedApiLevel) - + $(SupportedOSPlatformVersion).0 @@ -136,7 +135,6 @@ false false true - false true false true diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.MonoVM.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.MonoVM.targets index 8b49df77580..ab4f888295e 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.MonoVM.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.MonoVM.targets @@ -17,6 +17,10 @@ This file contains the MonoVM-specific MSBuild logic for .NET for Android. Value="false" Trim="true" /> + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.After.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.After.targets new file mode 100644 index 00000000000..3413a3e713f --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.After.targets @@ -0,0 +1,24 @@ + + + + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets index 8c57e739209..d1e9000c579 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets @@ -7,7 +7,7 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. --> - + @@ -15,6 +15,7 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. <_AndroidRuntimePackRuntime>NativeAOT + <_AndroidUseWorkloadNativeLinker Condition=" '$(_AndroidUseWorkloadNativeLinker)' == '' ">true <_AndroidJcwCodegenTarget Condition=" '$(_AndroidJcwCodegenTarget)' == '' ">JavaInterop1 <_AndroidTypeMapImplementation Condition=" '$(_AndroidTypeMapImplementation)' == '' ">managed @@ -51,6 +52,10 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. Value="false" Trim="true" /> + @@ -126,19 +131,10 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. $(_NdkAbi)-linux-android$(_NDKApiLevel)-clang$(_NdkWrapperScriptExt) $(_NdkAbi)-linux-android$(_NDKApiLevel)-clang$(_NdkWrapperScriptExt) llvm-objcopy - - llvm-ar false - - - @@ -155,7 +151,8 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. <_ExtraTrimmerArgs>$(_ExtraTrimmerArgs) --notrimwarn - + + @@ -268,7 +265,6 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. @@ -293,9 +289,9 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. <_AndroidNativeAotSharedLibrary>$(NativeOutputPath)$(NativeBinaryPrefix)$(TargetName).so - + + + + + <_NativeAotRuntimePackAsset Include="@(RuntimePackAsset->WithMetadataValue('Filename', 'libnaot-android.$(Configuration.ToLower())-static-$(Configuration.ToLower())'))" /> + + + <_NativeAotRuntimePackNativeDir>@(_NativeAotRuntimePackAsset->'%(RootDir)%(Directory)') + <_NativeAotLinkerBinDir>$(AndroidBinUtilsDirectory) + + + + <_NdkApiSysrootDir>$(_NdkSysrootDir)$(_NDKApiLevel)/ <_NdkClangResourceDir>$(_AndroidNdkDirectory)toolchains/llvm/prebuilt/$(_NdkPrebuiltAbi)/lib/clang + <_NativeAotLinkerBinDir>$(_NdkBinDir) - - - <_NativeAotCrtStartFiles Include="$(_NdkApiSysrootDir)crtbegin_so.o"> + + + <_NativeAotCrtStartFiles Include="$(_NativeAotRuntimePackNativeDir)/crtbegin_so.o"> @(_PrivateBuildTargetAbi) + <_NativeAotCrtEndFiles Include="$(_NativeAotRuntimePackNativeDir)/crtend_so.o"> + @(_PrivateBuildTargetAbi) + + <_NativeAotLibSearchPaths Include="$(_NativeAotRuntimePackNativeDir)" /> + <_NativeAotCompilerRtLibs Include="$(_NativeAotRuntimePackNativeDir)/libclang_rt.builtins-$(_NdkAbi)-android.a" /> + <_NativeAotCompilerRtLibs Include="$(_NativeAotRuntimePackNativeDir)/libunwind.a" /> + - + + + <_NativeAotCrtStartFiles Include="$(_NdkApiSysrootDir)crtbegin_so.o"> + @(_PrivateBuildTargetAbi) + <_NativeAotCrtEndFiles Include="$(_NdkApiSysrootDir)crtend_so.o"> @(_PrivateBuildTargetAbi) - - <_NativeAotLibSearchPaths Include="$(_NdkApiSysrootDir)" /> <_NativeAotLibSearchPaths Include="$(_NdkSysrootDir)" /> + <_NativeAotCompilerRtLibs Include="$(_NdkClangResourceDir)/**/libclang_rt.builtins-$(_NdkAbi)-android.a" /> + <_NativeAotCompilerRtLibs Include="$(_NdkClangResourceDir)/**/$(_NdkAbi)/libunwind.a" /> + - - + + <_NativeAotLinkLibraries Include="@(NativeLibrary)" /> - <_NativeAotLinkLibraries Include="@(_NdkLibs)" /> - - <_NativeAotAdditionalObjects Include="@(_PrivateJniInitFuncsNativeObjectFile)" /> <_NativeAotAdditionalObjects Include="@(_PrivateEnvironmentNativeObjectFile)" /> - - <_NativeAotSystemLibraries Include="dl" /> <_NativeAotSystemLibraries Include="z" /> <_NativeAotSystemLibraries Include="log" /> <_NativeAotSystemLibraries Include="m" /> <_NativeAotSystemLibraries Include="c" /> + - - <_NativeAotCompilerRtLibs Include="$(_NdkClangResourceDir)/**/libclang_rt.builtins-$(_NdkAbi)-android.a" /> - <_NativeAotCompilerRtLibs Include="$(_NdkClangResourceDir)/**/$(_NdkAbi)/libunwind.a" /> + + + <_NativeAotLinkLibraries Include="@(RuntimePackAsset->WithMetadataValue('Filename', 'libnaot-android.$(Configuration.ToLower())-static-$(Configuration.ToLower())'))" /> + <_NativeAotLinkLibraries Include="$(_NativeAotRuntimePackNativeDir)libc++_static.a" /> + <_NativeAotLinkLibraries Include="$(_NativeAotRuntimePackNativeDir)libc++abi.a" /> + + + <_NativeAotLinkLibraries Include="@(_NdkLibs)" /> $(NativeBinaryPrefix)$(TargetName).so PreserveNewest + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets index 2cfc5e71200..276cc236459 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets @@ -17,6 +17,9 @@ See: https://github.com/dotnet/runtime/blob/b13715b6984889a709ba29ea8a1961db469f On non-trimmed (Debug) builds, default to true so that debug.mono.log=gref continues to work as expected. --> <_AndroidEnableObjectReferenceLogging Condition=" '$(_AndroidEnableObjectReferenceLogging)' == '' And '$(PublishTrimmed)' == 'true' ">false <_AndroidEnableObjectReferenceLogging Condition=" '$(_AndroidEnableObjectReferenceLogging)' == '' And '$(PublishTrimmed)' != 'true' ">true + + <_AndroidEnableDiagnosticCrashReporting Condition=" '$(_AndroidEnableDiagnosticCrashReporting)' == '' ">true @@ -65,6 +68,11 @@ See: https://github.com/dotnet/runtime/blob/b13715b6984889a709ba29ea8a1961db469f Value="$(_AndroidEnableObjectReferenceLogging)" Trim="true" /> + + value: https://github.com/dotnet/sdk/blob/a5393731b5b7b225692fff121f747fbbc9e8b140/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ILLink.targets#L147 --> - <_TrimmerCustomData Include="AndroidHttpClientHandlerType" Value="$(AndroidHttpClientHandlerType)" /> <_TrimmerCustomData Include="AndroidCustomViewMapFile" Value="$(_OuterCustomViewMapFile)" /> <_TrimmerCustomData Include="SystemIOHashingAssemblyPath" Value="$(_SystemIOHashingAssemblyPath)" /> @@ -198,7 +196,10 @@ AfterStep="CleanStep" Type="Microsoft.Android.Sdk.ILLink.TypeMappingStep" /> - + + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets index f1c9a984156..ee3026152c4 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets @@ -6,21 +6,32 @@ <_TrimmableRuntimeProviderJavaName Condition=" '$(_TrimmableRuntimeProviderJavaName)' == '' ">net.dot.jni.nativeaot.NativeAotRuntimeProvider - + BeforeTargets="_AndroidComputeIlcCompileInputs" + DependsOnTargets="_ReadGeneratedTrimmableTypeMapAssemblies"> - _$(AssemblyName).TypeMap + $(_TypeMapAssemblyName) - <_TrimmableTypeMapAssembliesForIlc Include="$(_TypeMapOutputDirectory)*.dll" /> - - - + <_TrimmableTypeMapIlcAssemblies Include="@(_GeneratedTypeMapAssembliesFromList)" /> + <_TrimmableTypeMapFrameworkIlcAssemblies Include="@(ResolvedFrameworkAssemblies->'$(_TypeMapOutputDirectory)_%(Filename).TypeMap.dll')" /> + <_TrimmableTypeMapFrameworkIlcAssemblies Include="@(PrivateSdkAssemblies->'$(_TypeMapOutputDirectory)_%(Filename).TypeMap.dll')" /> + <_TrimmableTypeMapFrameworkIlcAssemblies Include="@(ReferencePath->'$(_TypeMapOutputDirectory)_%(Filename).TypeMap.dll')" + Condition=" '%(ReferencePath.FrameworkAssembly)' == 'true' " /> + + <_TrimmableTypeMapUnmanagedEntryPointAssemblies Include="@(_TrimmableTypeMapIlcAssemblies)" /> + <_TrimmableTypeMapUnmanagedEntryPointAssemblies Remove="$(_TypeMapOutputDirectory)$(_TypeMapAssemblyName).dll" /> + <_TrimmableTypeMapUnmanagedEntryPointAssemblies Remove="$(_TypeMapOutputDirectory)_Java.Interop.TypeMap.dll;$(_TypeMapOutputDirectory)_Mono.Android.TypeMap.dll" /> + <_TrimmableTypeMapUnmanagedEntryPointAssemblies Remove="@(_TrimmableTypeMapFrameworkIlcAssemblies)" /> + + + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets index e499a05dcfa..b040052577f 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets @@ -12,6 +12,8 @@ <_TypeMapAssemblyName>_Microsoft.Android.TypeMaps + <_TrimmableTypeMapPackageNamingPolicy Condition=" '$(_AndroidPackageNamingPolicySetByUser)' == 'true' ">$(AndroidPackageNamingPolicy) + <_TrimmableTypeMapPackageNamingPolicy Condition=" '$(_TrimmableTypeMapPackageNamingPolicy)' == '' ">Crc64 @@ -20,7 +22,7 @@ <_TypeMapBaseOutputDir>$(_TypeMapBaseOutputDir.Replace('\','/')) <_TypeMapOutputDirectory>$(_TypeMapBaseOutputDir)typemap/ <_TypeMapJavaOutputDirectory>$(_TypeMapBaseOutputDir)typemap/java - + <_TypeMapAssembliesListFile>$(_TypeMapOutputDirectory)typemap-assemblies.txt @@ -33,9 +35,16 @@ Value="true" Trim="true" /> + + + true + + + + + + + BeforeTargets="_GeneratePackageManagerJava" + DependsOnTargets="_ReadGeneratedTrimmableTypeMapAssemblies"> <_AdditionalNativeConfigResolvedAssemblies Remove="@(_AdditionalNativeConfigResolvedAssemblies)" /> - <_AdditionalNativeConfigResolvedAssemblies Include="$(_TypeMapOutputDirectory)*.dll" /> + <_AdditionalNativeConfigResolvedAssemblies Include="@(_GeneratedTypeMapAssembliesFromList)" /> @@ -127,7 +161,7 @@ + DependsOnTargets="_ReadGeneratedTrimmableTypeMapAssemblies;_DefineBuildTargetAbis"> <_TypeMapFirstAbi Condition=" '$(AndroidSupportedAbis)' != '' ">$([System.String]::Copy('$(AndroidSupportedAbis)').Split(';')[0]) <_TypeMapFirstAbi Condition=" '$(_TypeMapFirstAbi)' == '' ">arm64-v8a @@ -142,13 +176,13 @@ included in the assembly store hash table, and keep _RemoveRegisterAttribute source/destination counts in sync. --> - <_ResolvedAssemblies Include="$(_TypeMapOutputDirectory)*.dll"> + <_ResolvedAssemblies Include="@(_GeneratedTypeMapAssembliesFromList)"> $(_TypeMapFirstAbi) $(_TypeMapFirstRid) $(_TypeMapFirstAbi)/%(Filename)%(Extension) $(_TypeMapFirstAbi)/ - <_ShrunkAssemblies Include="$(_TypeMapOutputDirectory)*.dll"> + <_ShrunkAssemblies Include="@(_GeneratedTypeMapAssembliesFromList)"> $(_TypeMapFirstAbi) $(_TypeMapFirstRid) $(_TypeMapFirstAbi)/%(Filename)%(Extension) @@ -189,7 +223,6 @@ OutputDirectory="$(IntermediateOutputPath)android" TargetName="$(TargetName)" Environments="@(_EnvironmentFiles)" - HttpClientHandlerType="$(AndroidHttpClientHandlerType)" EnableSGenConcurrent="$(AndroidEnableSGenConcurrent)" /> diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs b/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs index ff837114b9d..af79c555b11 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs @@ -827,44 +827,6 @@ public static string XA1030 { } } - /// - /// Looks up a localized string similar to The 'AndroidHttpClientHandlerType' property value '{0}' must derive from '{1}'. - ///Please change the value to an assembly-qualifed type name which inherits from '{1}' or remove the property completely.. - /// - public static string XA1031 { - get { - return ResourceManager.GetString("XA1031", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The 'AndroidHttpClientHandlerType' property value '{0}' must not derive from 'System.Net.Htt.HttpClientHandler'. - ///Please change the value to an assembly-qualifed type name which inherits from 'System.Net.Http.HttpMessageHandler' or remove the property completely.. - /// - public static string XA1031_HCH { - get { - return ResourceManager.GetString("XA1031_HCH", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to resolve '{0}' from '{1}'. Please check your `AndroidHttpClientHandlerType` setting.. - /// - public static string XA1032 { - get { - return ResourceManager.GetString("XA1032", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Could not resolve '{0}'. Please check your `AndroidHttpClientHandlerType` setting.. - /// - public static string XA1033 { - get { - return ResourceManager.GetString("XA1033", resourceCulture); - } - } - /// /// Looks up a localized string similar to Your project references '{0}' which uses the `_Microsoft.Android.Resource.Designer` assembly, but you do not have this feature enabled. Please set the `AndroidUseDesignerAssembly` MSBuild property to `true` in your project file.. /// @@ -965,6 +927,33 @@ public static string XA1044 { } } + /// + /// Looks up a localized string similar to Input file `{0}` does not start with `<replacements/>`.. + /// + public static string XA1045 { + get { + return ResourceManager.GetString("XA1045", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Attribute '{0}' in element '{1}' has value '{2}' that cannot be parsed as boolean; {3} line {4}.. + /// + public static string XA1046 { + get { + return ResourceManager.GetString("XA1046", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Required attribute '{0}' missing from element '{1}'; {2} line {3}.. + /// + public static string XA1047 { + get { + return ResourceManager.GetString("XA1047", resourceCulture); + } + } + /// /// Looks up a localized string similar to Use of AppDomain.CreateDomain() detected in assembly: {0}. .NET 6 and higher will only support a single AppDomain, so this API will no longer be available in .NET for Android once .NET 6 is released.. /// @@ -1091,6 +1080,15 @@ public static string XA3007 { } } + /// + /// Looks up a localized string similar to Failed to extract debug info from '{0}'{1}. + /// + public static string XA3008 { + get { + return ResourceManager.GetString("XA3008", resourceCulture); + } + } + /// /// Looks up a localized string similar to Failed to generate Java type for class: {0} due to {1}. /// @@ -1190,6 +1188,15 @@ public static string XA4216_TargetSdkVersion { } } + /// + /// Looks up a localized string similar to Architecture '{0}' has Java types which have no counterparts in template architecture '{1}': {2}. + /// + public static string XA4217 { + get { + return ResourceManager.GetString("XA4217", resourceCulture); + } + } + /// /// Looks up a localized string similar to Unable to find //manifest/application/uses-library at path: {0}. /// @@ -1271,6 +1278,15 @@ public static string XA4226 { } } + /// + /// Looks up a localized string similar to Architecture '{0}' doesn't match all marshal methods in architecture '{1}'. Please see detailed MSBuild logs for more information.. + /// + public static string XA4227 { + get { + return ResourceManager.GetString("XA4227", resourceCulture); + } + } + /// /// Looks up a localized string similar to Unable to find specified //activity-alias/@android:targetActivity: '{0}'. /// @@ -1462,6 +1478,33 @@ public static string XA4250 { return ResourceManager.GetString("XA4250", resourceCulture); } } + + /// + /// Looks up a localized string similar to Insecure HTTP Maven repository URL '{0}' is not allowed. Use an HTTPS URL, or set AllowInsecureHttp="true" metadata on the item to override this check.. + /// + public static string XA4252 { + get { + return ResourceManager.GetString("XA4252", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Generated Java callable wrapper code changed: '{0}'. + /// + public static string XA4253 { + get { + return ResourceManager.GetString("XA4253", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type '{0}' uses [JniAddNativeMethodRegistrationAttribute], which is not supported by the trimmable type map. To work around this, do not target the trimmable type map (for example, by switching to the 'llvm-ir' type map implementation), and please report this scenario at https://github.com/dotnet/android/issues so the team can evaluate whether to support it.. + /// + public static string XA4251 { + get { + return ResourceManager.GetString("XA4251", resourceCulture); + } + } /// /// Looks up a localized string similar to Native library '{0}' will not be bundled because it has an unsupported ABI. Move this file to a directory with a valid Android ABI name such as 'libs/armeabi-v7a/'.. @@ -1645,6 +1688,33 @@ public static string XA4315 { } } + /// + /// Looks up a localized string similar to Specified input file '{0}' does not exist. Ignoring.. + /// + public static string XA4316 { + get { + return ResourceManager.GetString("XA4316", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Input file '{0}' does not start with '<replacements/>'. Skipping.. + /// + public static string XA4317 { + get { + return ResourceManager.GetString("XA4317", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Input file '{0}' could not be read: {1}. Skipping.. + /// + public static string XA4318 { + get { + return ResourceManager.GetString("XA4318", resourceCulture); + } + } + /// /// Looks up a localized string similar to Missing Android NDK toolchains directory '{0}'. Please install the Android NDK.. /// @@ -1802,6 +1872,15 @@ public static string XA5302 { } } + /// + /// Looks up a localized string similar to Failed to parse 'DescriptorIndex' metadata value '{0}' for assembly '{1}'.. + /// + public static string XA5303 { + get { + return ResourceManager.GetString("XA5303", resourceCulture); + } + } + /// /// Looks up a localized string similar to Could not find Android Resource '{0}'. Please update @(AndroidResource) to add the missing resource.. /// diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.cs.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.cs.resx index 327b9e29efc..87f9dbcc3f1 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.cs.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.cs.resx @@ -478,30 +478,6 @@ In this message, the term "binding" means a piece of generated code that makes i Vlastnost MSBuild RunAOTCompilation se podporuje jen tehdy, když je povolené trimování. Upravte soubor projektu v textovém editoru, aby se vlastnost PublishTrimmed pro tuto konfiguraci sestavení nastavila na true. The following are literal names and should not be translated: 'RunAOTCompilation', 'PublishTrimmed' - - Hodnota vlastnosti AndroidHttpClientHandlerType {0} musí být odvozena od {1}. -Změňte hodnotu na název typu kvalifikovaného sestavení, který dědí z: {1}, případně vlastnost úplně odeberte. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - A type from which the AndroidHttpClientHandlerType should derive . - - - Hodnota vlastnosti AndroidHttpClientHandlerType {0} nesmí být odvozena od System.Net.Htt.HttpClientHandler. -Změňte prosím hodnotu na název typu kvalifikovaného sestavení, který dědí z System.Net.Http.HttpMessageHandler, nebo tuto vlastnost úplně odeberte. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', 'System.Net.Htt.HttpClientHandler', 'System.Net.Http.HttpMessageHandler', -{0} - The value of the property. - - - Nepovedlo se přeložit {0} z(e) {1}. Zkontrolujte nastavení AndroidHttpClientHandlerType. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - The name of the assembly. - - - Nepovedlo se přeložit {0}. Zkontrolujte nastavení AndroidHttpClientHandlerType. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. - Váš projekt odkazuje na literál {0}, který používá sestavení _Microsoft.Android.Resource.Designer, ale tuto funkci nemáte povolenou. Nastavte vlastnost MSBuild AndroidUseDesignerAssembly v souboru projektu na hodnotu true. The following are literal names and should not be translated: '_Microsoft.Android.Resource.Desinger', 'AndroidUseDesignerAssembly', 'true' @@ -589,6 +565,10 @@ Buď změňte hodnotu v souboru AndroidManifest.xml tak, aby odpovídala hodnot Nepovedlo se připojit nativní sdílenou knihovnu: {0}{1} The '{1}' placeholder is replaced with full output of the failed command, starting and ending with a newline. + + Failed to extract debug info from '{0}'{1} + '{0}' is the native shared library filename. '{1}' is the full output of the failed command, starting and ending with a newline. + Kvůli {1} se nepovedlo vygenerovat typ Javy pro třídu {0}. {0} - The managed type name @@ -646,6 +626,12 @@ Buď změňte hodnotu v souboru AndroidManifest.xml tak, aby odpovídala hodnot The following are literal names and should not be translated: AndroidManifest.xml, //uses-sdk/@android:targetSdkVersion , API-{1} {0} - The target SDK version number {1} - The API version number + + + Architecture '{0}' has Java types which have no counterparts in template architecture '{1}': {2} + {0} - The target architecture name +{1} - The template architecture name +{2} - Comma-separated list of Java type names that have no counterparts Nepodařilo se najít položku //manifest/application/uses-library v cestě: {0} @@ -697,6 +683,11 @@ In this mesage, the term "layout" means an Android UI layout. Položka prostředku {0} nemá požadovanou položku metadat {1}. {0} - The name of the Android layout resource file {1} - The name of the metadata item + + + Architecture '{0}' doesn't match all marshal methods in architecture '{1}'. Please see detailed MSBuild logs for more information. + {0} - The target architecture name +{1} - The template architecture name Nepovedlo se najít zadané //activity-alias/@android:targetActivity: {0} @@ -920,6 +911,12 @@ Pokud chcete pro sestavení z příkazového řádku použít vlastní cestu JDK Tento projekt možná sestavují dva procesy najednou. Na následující cestě existuje soubor zámku: {0} + + Failed to parse 'DescriptorIndex' metadata value '{0}' for assembly '{1}'. + +{0} - The DescriptorIndex metadata value that could not be parsed +{1} - The assembly ItemSpec + Tento kód byl vygenerován pomocí nástroje. @@ -1015,6 +1012,18 @@ Pokud chcete pro sestavení z příkazového řádku použít vlastní cestu JDK {1} - The name of the .NET runtime (e.g. CoreCLR, NativeAOT). + + Vstupní soubor `{0}` nezačíná na `<replacements/>`. + {0} - file path + + + Atribut {0} v elementu {1} má hodnotu {2}, kterou nelze analyzovat jako logickou hodnotu; {3} – řádek {4}. + {0} - attribute name; {1} - element name; {2} - attribute value; {3} - file path; {4} - line number + + + Požadovaný atribut {0} chybí v elementu {1}; {3} – řádek {2}. + {0} - attribute name; {1} - element name; {2} - file path; {3} - line number + Závislosti Java {0} není vyhověno. The following are literal names and should not be translated: Java. @@ -1080,6 +1089,20 @@ Pokud chcete pro sestavení z příkazového řádku použít vlastní cestu JDK The following are literal names and should not be translated: Manifest, framework. {0} - Java type name from AndroidManifest.xml + + Typ {0} používá atribut [JniAddNativeMethodRegistrationAttribute], což mapování ořezávatelných typů nepodporuje. Tento problém můžete obejít tak, že nepoužijete mapování ořezávatelných typů, například přepnutím na implementaci mapování typů llvm-ir, a nahlásíte tento scénář na https://github.com/dotnet/android/issues, aby tým mohl vyhodnotit, jestli pro něj zavede podporu. + The following are literal names and should not be translated: JniAddNativeMethodRegistrationAttribute, llvm-ir. +{0} - Fully-qualified managed type name + + + Adresa URL nezabezpečeného úložiště Maven {0} není povolena. Pokud chcete tuto kontrolu přepsat, použijte adresu URL HTTPS nebo nastavte u položky metadata AllowInsecureHttp="true". + The following are literal names and should not be translated: HTTP, HTTPS, Maven, AllowInsecureHttp +{0} - The insecure HTTP URL + + + Generated Java callable wrapper code changed: '{0}' + {0} - The path to the generated Java callable wrapper file + Příkaz {0} selhal.\n{1} '{0}' is a failed command name (potentially with path) followed by all the arguments passed to it. {1} is the combined output on the standard error and standard output streams. diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.de.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.de.resx index a6dece58c59..7af4a693145 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.de.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.de.resx @@ -478,30 +478,6 @@ In this message, the term "binding" means a piece of generated code that makes i Die MSBuild-Eigenschaft „RunAOTCompilation“ wird nur unterstützt, wenn die Kürzung aktiviert ist. Bearbeiten Sie die Projektdatei in einem Text-Editor, um „PublishTrimmed“ für diese Buildkonfiguration auf „wahr“ festzulegen. The following are literal names and should not be translated: 'RunAOTCompilation', 'PublishTrimmed' - - Der „AndroidHttpClientHandlerType“-Eigenschaftswert „{0}“ muss von „{1}“ abgeleitet werden. -Bitte ändern Sie den Wert in einen Assembly-basierten Typnamen, der von „{1}“ erbt, oder entfernen Sie die Eigenschaft vollständig. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - A type from which the AndroidHttpClientHandlerType should derive . - - - Der AndroidHttpClientHandlerType-Eigenschaftswert "{0}" muss nicht von "System.Net.Htt.HttpClientHandler" abgeleitet werden. -Bitte ändern Sie den Wert in einen Assembly-basierten Typnamen, der von "System.Net.Http.HttpMessageHandler" erbt, oder entfernen Sie die Eigenschaft vollständig. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', 'System.Net.Htt.HttpClientHandler', 'System.Net.Http.HttpMessageHandler', -{0} - The value of the property. - - - Fehler beim Auflösen von „{0}“ aus „{1}“. Bitte überprüfen Sie Ihre Einstellung „AndroidHttpClientHandlerType“. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - The name of the assembly. - - - „{0}“ konnte nicht aufgelöst werden. Bitte überprüfen Sie Ihre Einstellung „AndroidHttpClientHandlerType“. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. - Ihr Projekt verweist auf „{0}“, das die Assembly „_Microsoft.Android.Resource.Designer“ verwendet, aber Sie haben dieses Feature nicht aktiviert. Bitte legen Sie die MSBuild-Eigenschaft „AndroidUseDesignerAssembly“ in Ihrer Projektdatei auf „WAHR“ fest. The following are literal names and should not be translated: '_Microsoft.Android.Resource.Desinger', 'AndroidUseDesignerAssembly', 'true' @@ -589,6 +565,10 @@ Bitte ändern Sie den Wert in einen Assembly-basierten Typnamen, der von "System Die native freigegebene Bibliothek konnte nicht verknüpft werden: {0}{1} The '{1}' placeholder is replaced with full output of the failed command, starting and ending with a newline. + + Failed to extract debug info from '{0}'{1} + '{0}' is the native shared library filename. '{1}' is the full output of the failed command, starting and ending with a newline. + Fehler beim Generieren des Java-Typs für die Klasse "{0}". Ursache: {1} {0} - The managed type name @@ -646,6 +626,12 @@ Bitte ändern Sie den Wert in einen Assembly-basierten Typnamen, der von "System The following are literal names and should not be translated: AndroidManifest.xml, //uses-sdk/@android:targetSdkVersion , API-{1} {0} - The target SDK version number {1} - The API version number + + + Architecture '{0}' has Java types which have no counterparts in template architecture '{1}': {2} + {0} - The target architecture name +{1} - The template architecture name +{2} - Comma-separated list of Java type names that have no counterparts "//manifest/application/uses-library" wurde nicht im Pfad "{0}" gefunden. @@ -697,6 +683,11 @@ In this mesage, the term "layout" means an Android UI layout. Das Ressourcenelement "{0}" verfügt nicht über das erforderliche Metadatenelement "{1}". {0} - The name of the Android layout resource file {1} - The name of the metadata item + + + Architecture '{0}' doesn't match all marshal methods in architecture '{1}'. Please see detailed MSBuild logs for more information. + {0} - The target architecture name +{1} - The template architecture name Die angegebene //activity-alias/@android:targetActivity wurde nicht gefunden: "{0}" @@ -920,6 +911,12 @@ Um einen benutzerdefinierten JDK-Pfad für einen Befehlszeilenbuild zu verwenden Möglicherweise wird dieses Projekt von zwei Prozessen gleichzeitig kompiliert. Im Pfad ist eine Sperrdatei vorhanden: {0} + + Failed to parse 'DescriptorIndex' metadata value '{0}' for assembly '{1}'. + +{0} - The DescriptorIndex metadata value that could not be parsed +{1} - The assembly ItemSpec + Dieser Code wurde durch ein Tool generiert. @@ -1015,6 +1012,18 @@ Um einen benutzerdefinierten JDK-Pfad für einen Befehlszeilenbuild zu verwenden {1} - The name of the .NET runtime (e.g. CoreCLR, NativeAOT). + + Die Eingabedatei `{0}` beginnt nicht mit `<replacements/>`. + {0} - file path + + + Das Attribut „{0}“ im Element „{1}“ hat den Wert „{2}“, der nicht als Boolescher Wert ausgewertet werden kann; {3} Zeile {4}. + {0} - attribute name; {1} - element name; {2} - attribute value; {3} - file path; {4} - line number + + + Das erforderliche Attribut „{0}“ fehlt im Element „{1}“; {2} Zeile {3}. + {0} - attribute name; {1} - element name; {2} - file path; {3} - line number + Java-Abhängigkeit „{0}“ wurde nicht erfüllt. The following are literal names and should not be translated: Java. @@ -1080,6 +1089,20 @@ Um einen benutzerdefinierten JDK-Pfad für einen Befehlszeilenbuild zu verwenden The following are literal names and should not be translated: Manifest, framework. {0} - Java type name from AndroidManifest.xml + + Der Typ „{0}“ verwendet [JniAddNativeMethodRegistrationAttribute], was von der trimmbaren Typzuordnung nicht unterstützt wird. Um dies zu umgehen, verwenden Sie nicht die trimmbare Typzuordnung (z. B. durch Wechseln zur Implementierung der LLVM-IR-Typzuordnung), und melden Sie dieses Szenario unter https://github.com/dotnet/android/issues, damit das Team auswerten kann, ob dies unterstützt wird. + The following are literal names and should not be translated: JniAddNativeMethodRegistrationAttribute, llvm-ir. +{0} - Fully-qualified managed type name + + + Die unsichere HTTP-Maven-Repository-URL „{0}“ ist nicht zulässig. Verwenden Sie eine HTTPS-URL oder legen Sie die Metadaten „AllowInsecureHttp=„WAHR““ für das Element fest, um diese Überprüfung zu umgehen. + The following are literal names and should not be translated: HTTP, HTTPS, Maven, AllowInsecureHttp +{0} - The insecure HTTP URL + + + Generated Java callable wrapper code changed: '{0}' + {0} - The path to the generated Java callable wrapper file + Befehl „{0}“ fehlgeschlagen.\n{1} '{0}' is a failed command name (potentially with path) followed by all the arguments passed to it. {1} is the combined output on the standard error and standard output streams. diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.es.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.es.resx index 8b0625c47bd..a52a4a976bf 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.es.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.es.resx @@ -478,30 +478,6 @@ In this message, the term "binding" means a piece of generated code that makes i La propiedad "RunAOTCompilation" de MSBuild solo se admite cuando el recorte está habilitado. Edite el archivo del proyecto en un editor de texto para establecer "PublishTrimmed" en "true" para esta configuración de compilación. The following are literal names and should not be translated: 'RunAOTCompilation', 'PublishTrimmed' - - El valor de la propiedad "AndroidHttpClientHandlerType" "{0}" debe derivar de "{1}". -Cambie el valor a un nombre de tipo de ensamblado que herede de "{1}" o quite la propiedad por completo. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - A type from which the AndroidHttpClientHandlerType should derive . - - - El valor "{0}" de la propiedad "AndroidHttpClientHandlerType" no debe derivar de "System.Net.Htt.HttpClientHandler". -Cambie el valor a un nombre de tipo calificado para ensamblado que herede de "System.Net.Http.HttpMessageHandler" o quite la propiedad por completo. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', 'System.Net.Htt.HttpClientHandler', 'System.Net.Http.HttpMessageHandler', -{0} - The value of the property. - - - No se pudo resolver "{0}" desde "{1}". Compruebe la configuración de "AndroidHttpClientHandlerType". - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - The name of the assembly. - - - No se pudo resolver "{0}". Compruebe la configuración de "AndroidHttpClientHandlerType". - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. - El proyecto hace referencia a "{0}", que usa el ensamblado "_Microsoft.Android.Resource.Designer", pero no tiene esta característica habilitada. Establezca la propiedad "AndroidUseDesignerAssembly" de MSBuild en "true" en el archivo del proyecto. The following are literal names and should not be translated: '_Microsoft.Android.Resource.Desinger', 'AndroidUseDesignerAssembly', 'true' @@ -589,6 +565,10 @@ Cambie el valor de AndroidManifest.xml para que coincida con el valor $(Supporte No se pudo vincular la biblioteca compartida nativa: {0}{1} The '{1}' placeholder is replaced with full output of the failed command, starting and ending with a newline. + + Failed to extract debug info from '{0}'{1} + '{0}' is the native shared library filename. '{1}' is the full output of the failed command, starting and ending with a newline. + No se pudo generar el tipo Java {0} para la clase debido a lo siguiente: {1} {0} - The managed type name @@ -646,6 +626,12 @@ Cambie el valor de AndroidManifest.xml para que coincida con el valor $(Supporte The following are literal names and should not be translated: AndroidManifest.xml, //uses-sdk/@android:targetSdkVersion , API-{1} {0} - The target SDK version number {1} - The API version number + + + Architecture '{0}' has Java types which have no counterparts in template architecture '{1}': {2} + {0} - The target architecture name +{1} - The template architecture name +{2} - Comma-separated list of Java type names that have no counterparts No se puede encontrar //manifest/application/uses-library en la ruta de acceso: {0} @@ -697,6 +683,11 @@ In this mesage, the term "layout" means an Android UI layout. El elemento del recurso "{0}" no tiene el elemento de metadatos "{1}" necesario. {0} - The name of the Android layout resource file {1} - The name of the metadata item + + + Architecture '{0}' doesn't match all marshal methods in architecture '{1}'. Please see detailed MSBuild logs for more information. + {0} - The target architecture name +{1} - The template architecture name No se encuentra el elemento //activity-alias/@android:targetActivity especificado: "{0}" @@ -920,6 +911,12 @@ Para usar una ruta de acceso de JDK personalizada para una compilación de líne Puede que haya dos procesos compilando este proyecto a la vez. El archivo de bloqueo existe en la ruta de acceso {0} + + Failed to parse 'DescriptorIndex' metadata value '{0}' for assembly '{1}'. + +{0} - The DescriptorIndex metadata value that could not be parsed +{1} - The assembly ItemSpec + Una herramienta generó este código. @@ -1015,6 +1012,18 @@ Para usar una ruta de acceso de JDK personalizada para una compilación de líne {1} - The name of the .NET runtime (e.g. CoreCLR, NativeAOT). + + El archivo de entrada `{0}` no empieza por `<replacements/>`. + {0} - file path + + + El atributo "{0}" del elemento "{1}" tiene un valor "{2}" que no se puede analizar como booleano; {3} línea {4}. + {0} - attribute name; {1} - element name; {2} - attribute value; {3} - file path; {4} - line number + + + Falta el atributo obligatorio "{0}" del elemento "{1}"; {2} línea {3}. + {0} - attribute name; {1} - element name; {2} - file path; {3} - line number + No se cumple la dependencia de Java "{0}". The following are literal names and should not be translated: Java. @@ -1080,6 +1089,20 @@ Para usar una ruta de acceso de JDK personalizada para una compilación de líne The following are literal names and should not be translated: Manifest, framework. {0} - Java type name from AndroidManifest.xml + + El tipo "{0}" usa [JniAddNativeMethodRegistrationAttribute], que no es compatible con la asignación de tipos recortables. Para solucionar este problema, no use la asignación de tipos recortables (por ejemplo, al cambiar a la implementación de la asignación de tipos 'llvm-ir') e informe de este caso en https://github.com/dotnet/android/issues para que el equipo pueda evaluar si debe admitirse. + The following are literal names and should not be translated: JniAddNativeMethodRegistrationAttribute, llvm-ir. +{0} - Fully-qualified managed type name + + + No se permite la dirección URL del repositorio de Maven HTTP "{0}" no segura. Use una URL HTTPS o configure el metadato AllowInsecureHttp="true" en el elemento para invalidar esta comprobación. + The following are literal names and should not be translated: HTTP, HTTPS, Maven, AllowInsecureHttp +{0} - The insecure HTTP URL + + + Generated Java callable wrapper code changed: '{0}' + {0} - The path to the generated Java callable wrapper file + Error del comando '{0}'.\n{1} '{0}' is a failed command name (potentially with path) followed by all the arguments passed to it. {1} is the combined output on the standard error and standard output streams. diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.fr.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.fr.resx index 9265d86c8fd..c07cf0d2834 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.fr.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.fr.resx @@ -478,30 +478,6 @@ In this message, the term "binding" means a piece of generated code that makes i La propriété MSBuild « RunAOTCompilation » n’est prise en charge que lorsque le découpage est activé. Modifiez le fichier projet dans un éditeur de texte pour définir « PublishTrimmed » sur « true » pour cette configuration de build. The following are literal names and should not be translated: 'RunAOTCompilation', 'PublishTrimmed' - - La valeur de la propriété « AndroidHttpClientHandlerType » « {0} » doit dériver de « {1} ». -Veuillez remplacer la valeur par un nom de type qualifié par l’assembly qui hérite de « {1} » ou supprimez complètement la propriété. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - A type from which the AndroidHttpClientHandlerType should derive . - - - La valeur de propriété « AndroidHttpClientHandlerType » « {0} » ne doit pas dériver de « System.Net.Htt.HttpClientHandler ». -Remplacez la valeur par un nom de type assembly-qualifed qui hérite de « System.Net.Http.HttpMessageHandler » ou supprimez complètement la propriété. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', 'System.Net.Htt.HttpClientHandler', 'System.Net.Http.HttpMessageHandler', -{0} - The value of the property. - - - Échec de la résolution de « {0} » à partir de « {1} ». Vérifiez votre paramètre `AndroidHttpClientHandlerType`. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - The name of the assembly. - - - Impossible de résoudre « {0} ». Veuillez vérifier votre paramètre `AndroidHttpClientHandlerType`. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. - Vos références de projet « {0} » qui utilise l’assembly `_Microsoft.Android.Resource.Designer`, mais cette fonctionnalité n’est pas activée. Veuillez définir la propriété MSBuild `AndroidUseDesignerAssembly` sur `true` dans votre Fichier projet. The following are literal names and should not be translated: '_Microsoft.Android.Resource.Desinger', 'AndroidUseDesignerAssembly', 'true' @@ -589,6 +565,10 @@ Modifiez la valeur dans AndroidManifest.xml pour qu’elle corresponde à la val Impossible de lier la bibliothèque partagée native : {0}{1} The '{1}' placeholder is replaced with full output of the failed command, starting and ending with a newline. + + Failed to extract debug info from '{0}'{1} + '{0}' is the native shared library filename. '{1}' is the full output of the failed command, starting and ending with a newline. + Échec de la génération du type Java pour la classe {0} en raison de {1} {0} - The managed type name @@ -646,6 +626,12 @@ Modifiez la valeur dans AndroidManifest.xml pour qu’elle corresponde à la val The following are literal names and should not be translated: AndroidManifest.xml, //uses-sdk/@android:targetSdkVersion , API-{1} {0} - The target SDK version number {1} - The API version number + + + Architecture '{0}' has Java types which have no counterparts in template architecture '{1}': {2} + {0} - The target architecture name +{1} - The template architecture name +{2} - Comma-separated list of Java type names that have no counterparts Impossible de localiser //manifest/application/uses-library dans le chemin : {0} @@ -697,6 +683,11 @@ In this mesage, the term "layout" means an Android UI layout. L'élément de ressource '{0}' n'a pas l'élément de métadonnées obligatoire '{1}'. {0} - The name of the Android layout resource file {1} - The name of the metadata item + + + Architecture '{0}' doesn't match all marshal methods in architecture '{1}'. Please see detailed MSBuild logs for more information. + {0} - The target architecture name +{1} - The template architecture name Le //activity-alias/@android:targetActivity spécifié est introuvable : '{0}' @@ -920,6 +911,12 @@ Pour utiliser un chemin JDK personnalisé pour une build de ligne de commande, d Deux processus génèrent peut-être ce projet en même temps. Il existe un fichier de verrouillage dans le chemin suivant : {0} + + Failed to parse 'DescriptorIndex' metadata value '{0}' for assembly '{1}'. + +{0} - The DescriptorIndex metadata value that could not be parsed +{1} - The assembly ItemSpec + Ce code a été généré par un outil. @@ -1015,6 +1012,18 @@ Pour utiliser un chemin JDK personnalisé pour une build de ligne de commande, d {1} - The name of the .NET runtime (e.g. CoreCLR, NativeAOT). + + Le fichier d’entrée `{0}` ne commence pas par `<replacements/>`. + {0} - file path + + + L’attribut « {0} » dans l’élément « {1} » a une valeur « {2} » qui ne peut pas être analysée comme une valeur booléenne; {3} ligne {4}. + {0} - attribute name; {1} - element name; {2} - attribute value; {3} - file path; {4} - line number + + + L'attribut requis « {0} » est absent de l'élément « {1} »; {2} ligne {3}. + {0} - attribute name; {1} - element name; {2} - file path; {3} - line number + La dépendance Java « {0} » n’est pas satisfaite. The following are literal names and should not be translated: Java. @@ -1080,6 +1089,20 @@ Pour utiliser un chemin JDK personnalisé pour une build de ligne de commande, d The following are literal names and should not be translated: Manifest, framework. {0} - Java type name from AndroidManifest.xml + + Le type « {0} » utilise [JniAddNativeMethodRegistrationAttribute] qui n’est pas pris en charge par le mappage de type ajustable. Pour contourner ce problème, ne ciblez pas le mappage de type ajustable (par exemple, en basculant vers l’implémentation du mappage de type « llvm-ir ») et signalez ce scénario sur https://github.com/dotnet/android/issues afin que l’équipe puisse évaluer s’il faut le prendre en charge. + The following are literal names and should not be translated: JniAddNativeMethodRegistrationAttribute, llvm-ir. +{0} - Fully-qualified managed type name + + + L’URL du dépôt Maven HTTP non sécurisé « {0} » n’est pas autorisée. Utilisez une URL HTTPS, ou définissez les métadonnées AllowInsecureHttp="true" sur l’élément pour ignorer cette vérification. + The following are literal names and should not be translated: HTTP, HTTPS, Maven, AllowInsecureHttp +{0} - The insecure HTTP URL + + + Generated Java callable wrapper code changed: '{0}' + {0} - The path to the generated Java callable wrapper file + La commande « {0} » a échoué.\n{1} '{0}' is a failed command name (potentially with path) followed by all the arguments passed to it. {1} is the combined output on the standard error and standard output streams. diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.it.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.it.resx index a5038617a04..a78382398db 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.it.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.it.resx @@ -478,30 +478,6 @@ In this message, the term "binding" means a piece of generated code that makes i La proprietà MSBuild 'RunAOTCompilation' è supportata solo quando è abilitato il troncamento. Modifica il file di progetto in un editor di testo per impostare 'PublishTrimmed' su 'true' per questa configurazione di compilazione. The following are literal names and should not be translated: 'RunAOTCompilation', 'PublishTrimmed' - - Il valore della proprietà 'AndroidHttpClientHandlerType' '{0}' deve derivare da '{1}'. -Modificare il valore in un nome di tipo con qualifica assembly che eredita da '{1}' o rimuovere completamente la proprietà. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - A type from which the AndroidHttpClientHandlerType should derive . - - - Il valore della proprietà 'AndroidHttpClientHandlerType' '{0}' non deve derivare da 'System.Net.Htt.HttpClientHandler'. -Modificare il valore in un nome di tipo con qualifica assembly che eredita da 'System.Net.Http.HttpMessageHandler' o rimuovere completamente la proprietà. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', 'System.Net.Htt.HttpClientHandler', 'System.Net.Http.HttpMessageHandler', -{0} - The value of the property. - - - Non è stato possibile risolvere '{0}' da '{1}'. Controllare l'impostazione 'AndroidHttpClientHandlerType'. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - The name of the assembly. - - - Non è stato possibile risolvere '{0}'. Controllare l'impostazione 'AndroidHttpClientHandlerType'. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. - Il progetto fa riferimento '{0}' che usa l'assembly '_Microsoft.Android.Resource.Designer', ma questa funzionalità non è abilitata. Impostare la proprietà 'AndroidUseDesignerAssembly' di MSBuild su 'true' nel file di progetto. The following are literal names and should not be translated: '_Microsoft.Android.Resource.Desinger', 'AndroidUseDesignerAssembly', 'true' @@ -589,6 +565,10 @@ Modificare il valore in AndroidManifest.xml in modo che corrisponda al valore $( Non è stato possibile collegare la libreria condivisa nativa: {0}{1} The '{1}' placeholder is replaced with full output of the failed command, starting and ending with a newline. + + Failed to extract debug info from '{0}'{1} + '{0}' is the native shared library filename. '{1}' is the full output of the failed command, starting and ending with a newline. + Non è stato possibile generare il tipo Java per la classe {0} a causa di {1} {0} - The managed type name @@ -646,6 +626,12 @@ Modificare il valore in AndroidManifest.xml in modo che corrisponda al valore $( The following are literal names and should not be translated: AndroidManifest.xml, //uses-sdk/@android:targetSdkVersion , API-{1} {0} - The target SDK version number {1} - The API version number + + + Architecture '{0}' has Java types which have no counterparts in template architecture '{1}': {2} + {0} - The target architecture name +{1} - The template architecture name +{2} - Comma-separated list of Java type names that have no counterparts Non è possibile trovare //manifest/application/uses-library nel percorso: {0} @@ -697,6 +683,11 @@ In this mesage, the term "layout" means an Android UI layout. L'elemento di risorsa '{0}' non contiene l'elemento di metadati richiesto '{1}'. {0} - The name of the Android layout resource file {1} - The name of the metadata item + + + Architecture '{0}' doesn't match all marshal methods in architecture '{1}'. Please see detailed MSBuild logs for more information. + {0} - The target architecture name +{1} - The template architecture name Non è possibile trovare l'attività specificata //activity-alias/@android:targetActivity: '{0}' @@ -920,6 +911,12 @@ Per usare un percorso JDK personalizzato per una compilazione della riga di coma Due processi possono compilare il progetto contemporaneamente. Il file di blocco si trova nel percorso: {0} + + Failed to parse 'DescriptorIndex' metadata value '{0}' for assembly '{1}'. + +{0} - The DescriptorIndex metadata value that could not be parsed +{1} - The assembly ItemSpec + Questo codice è stato generato da uno strumento. @@ -1015,6 +1012,18 @@ Per usare un percorso JDK personalizzato per una compilazione della riga di coma {1} - The name of the .NET runtime (e.g. CoreCLR, NativeAOT). + + Il file di input ''{0}'' non inizia con ''<replacements/>''. + {0} - file path + + + L'attributo ''{0}'' nell'elemento ''{1}'' contiene il valore ''{2}'' che non può essere analizzato come booleano; {3} riga {4}. + {0} - attribute name; {1} - element name; {2} - attribute value; {3} - file path; {4} - line number + + + Manca l'attributo obbligatorio '''{0}'' per l'elemento ''{1}'', {2}linea ''{3}''. + {0} - attribute name; {1} - element name; {2} - file path; {3} - line number + La dipendenza Java '{0}' non è soddisfatta. The following are literal names and should not be translated: Java. @@ -1080,6 +1089,20 @@ Per usare un percorso JDK personalizzato per una compilazione della riga di coma The following are literal names and should not be translated: Manifest, framework. {0} - Java type name from AndroidManifest.xml + + Il tipo "{0}" usa [JniAddNativeMethodRegistrationAttribute], che non è supportato dalla mappa dei tipi trimmable. Per aggirare il problema, non usare la mappa dei tipi trimmable come destinazione (ad esempio, passando all'implementazione della mappa dei tipi "llvm-ir") e segnala questo scenario su https://github.com/dotnet/android/issues, così il team potrà valutare se supportarlo. + The following are literal names and should not be translated: JniAddNativeMethodRegistrationAttribute, llvm-ir. +{0} - Fully-qualified managed type name + + + L'URL del repository Http Maven ''{0}'' non è consentito. Usare un URL HTTPS o impostare i metadati AllowInsecureHttp="true" sull'elemento per eseguire l'override di questo controllo. + The following are literal names and should not be translated: HTTP, HTTPS, Maven, AllowInsecureHttp +{0} - The insecure HTTP URL + + + Generated Java callable wrapper code changed: '{0}' + {0} - The path to the generated Java callable wrapper file + Comando '{0}' non riuscito.\n{1} '{0}' is a failed command name (potentially with path) followed by all the arguments passed to it. {1} is the combined output on the standard error and standard output streams. diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.ja.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.ja.resx index 4294d7aab31..7cbfcfb7e8a 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.ja.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.ja.resx @@ -478,30 +478,6 @@ In this message, the term "binding" means a piece of generated code that makes i 'RunAOTCompilation' MSBuild プロパティは、トリミングが有効な場合にのみサポートされます。テキスト エディターでプロジェクト ファイルを編集して、このビルド構成の 'PublishTrimmed' を 'true' に設定します。 The following are literal names and should not be translated: 'RunAOTCompilation', 'PublishTrimmed' - - 'AndroidHttpClientHandlerType' プロパティ値 '{0}' は '{1}' から派生する必要があります。 -値を '{1}' から継承するアセンブリ修飾型名に変更するか、プロパティを完全に削除してください。 - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - A type from which the AndroidHttpClientHandlerType should derive . - - - 'AndroidHttpClientHandlerType' プロパティ値 '{0}' は 'System.Net.Htt.HttpClientHandler' から派生することはできません。 -値を 'System.Net.Http.HttpMessageHandler' から継承する assembly-qualifed 型名に変更するか、プロパティを完全に削除してください。 - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', 'System.Net.Htt.HttpClientHandler', 'System.Net.Http.HttpMessageHandler', -{0} - The value of the property. - - - '{1}' から '{0}' を解決できませんでした。`AndroidHttpClientHandlerType` の設定を確認してください。 - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - The name of the assembly. - - - '{0}' を解決できませんでした。`AndroidHttpClientHandlerType` の設定を確認してください。 - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. - プロジェクトは `_Microsoft.Android.Resource.Designer` アセンブリを使用する '{0}' を参照していますが、この機能を有効にしていません。プロジェクト ファイルで `AndroidUseDesignerAssembly` MSBuild プロパティを `true` に設定してください。 The following are literal names and should not be translated: '_Microsoft.Android.Resource.Desinger', 'AndroidUseDesignerAssembly', 'true' @@ -590,6 +566,10 @@ $(SupportedOSPlatformVersion) 値に一致するように AndroidManifest.xml ネイティブの共有ライブラリをリンクできませんでした: {0}{1} The '{1}' placeholder is replaced with full output of the failed command, starting and ending with a newline. + + Failed to extract debug info from '{0}'{1} + '{0}' is the native shared library filename. '{1}' is the full output of the failed command, starting and ending with a newline. + {1} のため、クラス {0} の Java の型を生成できませんでした {0} - The managed type name @@ -647,6 +627,12 @@ $(SupportedOSPlatformVersion) 値に一致するように AndroidManifest.xml The following are literal names and should not be translated: AndroidManifest.xml, //uses-sdk/@android:targetSdkVersion , API-{1} {0} - The target SDK version number {1} - The API version number + + + Architecture '{0}' has Java types which have no counterparts in template architecture '{1}': {2} + {0} - The target architecture name +{1} - The template architecture name +{2} - Comma-separated list of Java type names that have no counterparts パスに //manifest/application/uses-library が見つかりませんでした: {0} @@ -698,6 +684,11 @@ In this mesage, the term "layout" means an Android UI layout. リソース項目 '{0}' に、必要なメタデータ項目 '{1}' がありません。 {0} - The name of the Android layout resource file {1} - The name of the metadata item + + + Architecture '{0}' doesn't match all marshal methods in architecture '{1}'. Please see detailed MSBuild logs for more information. + {0} - The target architecture name +{1} - The template architecture name 指定された //activity-alias/@android:targetActivity '{0}' が見つかりません @@ -921,6 +912,12 @@ In this message, the term "handheld app" means "app for handheld devices." 2 つのプロセスがこのプロジェクトを同時にビルドしている可能性があります。ロック ファイルはパス {0} に存在します + + Failed to parse 'DescriptorIndex' metadata value '{0}' for assembly '{1}'. + +{0} - The DescriptorIndex metadata value that could not be parsed +{1} - The assembly ItemSpec + このコードはツールによって生成されました。 @@ -1016,6 +1013,18 @@ In this message, the term "handheld app" means "app for handheld devices." {1} - The name of the .NET runtime (e.g. CoreCLR, NativeAOT). + + 入力ファイル `{0}` は `<replacements/>` で始まっていません。 + {0} - file path + + + 要素 '{1}' の属性 '{0}' には、ブール値として解析できない値 '{2}' があります。{3} 行 {4}。 + {0} - attribute name; {1} - element name; {2} - attribute value; {3} - file path; {4} - line number + + + 必須属性 '{0}' が要素 '{1}' にありません。{2} 行 {3}。 + {0} - attribute name; {1} - element name; {2} - file path; {3} - line number + Java 依存関係 '{0}' が満たされていません。 The following are literal names and should not be translated: Java. @@ -1081,6 +1090,20 @@ In this message, the term "handheld app" means "app for handheld devices." The following are literal names and should not be translated: Manifest, framework. {0} - Java type name from AndroidManifest.xml + + 型 '{0}' は [JniAddNativeMethodRegistrationAttribute] を使用していますが、これはトリミング可能な型マップではサポートされていません。これを回避するには、トリミング可能な型マップを対象にしないでください (たとえば、'llvm-ir' 型マップの実装に切り替えます)。また、このシナリオを https://github.com/dotnet/android/issues で報告してください。チームがサポートするかどうかを評価できます。 + The following are literal names and should not be translated: JniAddNativeMethodRegistrationAttribute, llvm-ir. +{0} - Fully-qualified managed type name + + + セキュリティで保護されていない HTTP Maven リポジトリ URL '{0}' は許可されていません。HTTPS URL を使用するか、項目に AllowInsecureHttp="true" メタデータを設定して、このチェックをオーバーライドします。 + The following are literal names and should not be translated: HTTP, HTTPS, Maven, AllowInsecureHttp +{0} - The insecure HTTP URL + + + Generated Java callable wrapper code changed: '{0}' + {0} - The path to the generated Java callable wrapper file + コマンド '{0}' が失敗しました。\n{1} '{0}' is a failed command name (potentially with path) followed by all the arguments passed to it. {1} is the combined output on the standard error and standard output streams. diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.ko.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.ko.resx index 487c11326de..704fb916b33 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.ko.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.ko.resx @@ -478,30 +478,6 @@ In this message, the term "binding" means a piece of generated code that makes i 'RunAOTCompilation' MSBuild 속성은 트리밍이 활성화된 경우에만 지원됩니다. 텍스트 편집기에서 프로젝트 파일을 편집하여 이 빌드 구성에 대해 'PublishTrimmed'를 'true'로 설정합니다. The following are literal names and should not be translated: 'RunAOTCompilation', 'PublishTrimmed' - - `AndroidHttpClientHandlerType` 속성 값 '{0}'은(는) '{1}'에서 파생되어야 합니다. -값을 '{1}'에서 상속하는 정규화된 어셈블리 형식 이름으로 변경하거나 속성을 완전히 제거하세요. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - A type from which the AndroidHttpClientHandlerType should derive . - - - 'AndroidHttpClientHandlerType' 속성 값 '{0}'은(는) 'System.Net.Htt.HttpClientHandler'에서 파생되어서는 안됩니다. -값을 'System.Net.Http.Http.HttpMessageHandler'에서 상속하는 어셈블리 형식 이름으로 변경하거나 속성을 완전히 제거하세요. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', 'System.Net.Htt.HttpClientHandler', 'System.Net.Http.HttpMessageHandler', -{0} - The value of the property. - - - '{1}'에서 '{0}'을(를) 확인하지 못했습니다. `AndroidHttpClientHandlerType` 설정을 확인하세요. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - The name of the assembly. - - - '{0}'을(를) 확인할 수 없습니다. `AndroidHttpClientHandlerType` 설정을 확인하세요. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. - 프로젝트에서 `_Microsoft.Android.Resource.Designer` 어셈블리를 사용하는 '{0}'을(를) 참조하지만 이 기능이 활성화되지 않았습니다. 프로젝트 파일에서 `AndroidUseDesignerAssembly` MSBuild 속성을 `true`로 설정하세요. The following are literal names and should not be translated: '_Microsoft.Android.Resource.Desinger', 'AndroidUseDesignerAssembly', 'true' @@ -589,6 +565,10 @@ AndroidManifest.xml의 값을 $(SupportedOSPlatformVersion) 값에 맞도록 변 네이티브 공유 라이브러리를 연결할 수 없음: {0}{1} The '{1}' placeholder is replaced with full output of the failed command, starting and ending with a newline. + + Failed to extract debug info from '{0}'{1} + '{0}' is the native shared library filename. '{1}' is the full output of the failed command, starting and ending with a newline. + 클래스의 Java 형식({0})을 생성하지 못했습니다(원인: {1}). {0} - The managed type name @@ -646,6 +626,12 @@ AndroidManifest.xml의 값을 $(SupportedOSPlatformVersion) 값에 맞도록 변 The following are literal names and should not be translated: AndroidManifest.xml, //uses-sdk/@android:targetSdkVersion , API-{1} {0} - The target SDK version number {1} - The API version number + + + Architecture '{0}' has Java types which have no counterparts in template architecture '{1}': {2} + {0} - The target architecture name +{1} - The template architecture name +{2} - Comma-separated list of Java type names that have no counterparts {0} 경로에서 //manifest/application/uses-library를 찾을 수 없습니다. @@ -697,6 +683,11 @@ In this mesage, the term "layout" means an Android UI layout. 리소스 항목 '{0}'에 필수 메타데이터 항목 '{1}'이(가) 없습니다. {0} - The name of the Android layout resource file {1} - The name of the metadata item + + + Architecture '{0}' doesn't match all marshal methods in architecture '{1}'. Please see detailed MSBuild logs for more information. + {0} - The target architecture name +{1} - The template architecture name 지정한 //activity-alias/@android:targetActivity를 찾을 수 없음: '{0}' @@ -920,6 +911,12 @@ In this message, the term "handheld app" means "app for handheld devices." 두 프로세스에서 이 프로젝트를 동시에 빌드할 수 있습니다. 잠금 파일은 {0} 경로에 있습니다. + + Failed to parse 'DescriptorIndex' metadata value '{0}' for assembly '{1}'. + +{0} - The DescriptorIndex metadata value that could not be parsed +{1} - The assembly ItemSpec + 이 코드는 도구를 사용하여 생성되었습니다. @@ -1015,6 +1012,18 @@ In this message, the term "handheld app" means "app for handheld devices." {1} - The name of the .NET runtime (e.g. CoreCLR, NativeAOT). + + 입력 파일 `{0}`이(가) `<replacements/>`로 시작하지 않습니다. + {0} - file path + + + 요소 '{1}'의 특성 '{0}'에는 부울로 구문 분석할 수 없는 값 '{2}'이 있습니다. {3} 줄 {4}. + {0} - attribute name; {1} - element name; {2} - attribute value; {3} - file path; {4} - line number + + + 필수 특성 '{0}'이(가) 요소 '{1}'에 없습니다. {2} 줄 {3}. + {0} - attribute name; {1} - element name; {2} - file path; {3} - line number + Java 종속성 '{0}'이(가) 충족되지 않습니다. The following are literal names and should not be translated: Java. @@ -1080,6 +1089,20 @@ In this message, the term "handheld app" means "app for handheld devices." The following are literal names and should not be translated: Manifest, framework. {0} - Java type name from AndroidManifest.xml + + '{0}' 형식은 트리밍 가능한 형식 맵에서 지원되지 않는 [JniAddNativeMethodRegistrationAttribute]을(를) 사용합니다. 이 문제를 해결하려면 트리밍 가능한 형식 맵을 대상으로 하지 마세요. 예를 들어 'llvm-ir' 형식 맵 구현으로 전환하면 됩니다. 또한 팀에서 지원 여부를 검토할 수 있도록 https://github.com/dotnet/android/issues에 이 시나리오를 보고해 주세요. + The following are literal names and should not be translated: JniAddNativeMethodRegistrationAttribute, llvm-ir. +{0} - Fully-qualified managed type name + + + 안전하지 않은 HTTP Maven 리포지토리 URL '{0}'은(는) 허용되지 않습니다. HTTPS URL을 사용하거나 항목에 AllowInsecureHttp="true" 메타데이터를 설정하여 이 검사를 재정의하세요. + The following are literal names and should not be translated: HTTP, HTTPS, Maven, AllowInsecureHttp +{0} - The insecure HTTP URL + + + Generated Java callable wrapper code changed: '{0}' + {0} - The path to the generated Java callable wrapper file + '{0}' 명령이 실패했습니다.\n{1} '{0}' is a failed command name (potentially with path) followed by all the arguments passed to it. {1} is the combined output on the standard error and standard output streams. diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.pl.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.pl.resx index 6a95292cab6..3c8546d883b 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.pl.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.pl.resx @@ -478,30 +478,6 @@ In this message, the term "binding" means a piece of generated code that makes i Właściwość „RunAOTCompilation” programu MSBuild jest obsługiwana tylko wtedy, gdy jest włączone przycinanie. Edytuj plik projektu w edytorze tekstu, aby ustawić właściwość „PublishTrimmed” na wartość „true” dla tej konfiguracji kompilacji. The following are literal names and should not be translated: 'RunAOTCompilation', 'PublishTrimmed' - - Wartość właściwości „AndroidHttpClientHandlerType” „{0}” musi pochodzić od „{1}”. -Zmień wartość na nazwę typu kwalifikowalnego zestawu, która dziedziczy po „{1}” lub całkowicie usuń właściwość. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - A type from which the AndroidHttpClientHandlerType should derive . - - - Wartość właściwości 'AndroidHttpClientHandlerType' „{0}” nie może pochodzić od właściwości 'System.Net.Htt.HttpClientHandler'. -Zmień wartość na nazwę typu zgodną z zestawem, która dziedziczy z elementu „System.Net.Http.HttpMessageHandler” lub całkowicie usuń tę właściwość. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', 'System.Net.Htt.HttpClientHandler', 'System.Net.Http.HttpMessageHandler', -{0} - The value of the property. - - - Nie można rozpoznać elementu „{0}” z „{1}”. Sprawdź ustawienie „AndroidHttpClientHandlerType”. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - The name of the assembly. - - - Nie można rozpoznać elementu „{0}”. Sprawdź ustawienie „AndroidHttpClientHandlerType”. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. - Projekt odwołuje się do elementu „{0}”, który używa zestawu „_Microsoft.Android.Resource.Designer”, ale ta funkcja nie jest włączona. Ustaw właściwość MSBuild „AndroidUseDesignerAssembly” na wartość „true” w pliku projektu. The following are literal names and should not be translated: '_Microsoft.Android.Resource.Desinger', 'AndroidUseDesignerAssembly', 'true' @@ -589,6 +565,10 @@ Zmień wartość w pliku AndroidManifest.xml, aby odpowiadała wartości $(Suppo Nie można połączyć natywnej biblioteki współużytkowanej: {0}{1} The '{1}' placeholder is replaced with full output of the failed command, starting and ending with a newline. + + Failed to extract debug info from '{0}'{1} + '{0}' is the native shared library filename. '{1}' is the full output of the failed command, starting and ending with a newline. + Nie można wygenerować typu języka Java dla klasy {0} z powodu wyjątku {1} {0} - The managed type name @@ -646,6 +626,12 @@ Zmień wartość w pliku AndroidManifest.xml, aby odpowiadała wartości $(Suppo The following are literal names and should not be translated: AndroidManifest.xml, //uses-sdk/@android:targetSdkVersion , API-{1} {0} - The target SDK version number {1} - The API version number + + + Architecture '{0}' has Java types which have no counterparts in template architecture '{1}': {2} + {0} - The target architecture name +{1} - The template architecture name +{2} - Comma-separated list of Java type names that have no counterparts Nie można znaleźć elementu //manifest/application/uses-library w ścieżce: {0} @@ -697,6 +683,11 @@ In this mesage, the term "layout" means an Android UI layout. Element zasobu „{0}” nie ma wymaganego elementu metadanych „{1}”. {0} - The name of the Android layout resource file {1} - The name of the metadata item + + + Architecture '{0}' doesn't match all marshal methods in architecture '{1}'. Please see detailed MSBuild logs for more information. + {0} - The target architecture name +{1} - The template architecture name Nie można znaleźć określonego elementu //activity-alias/@android:targetActivity: „{0}” @@ -920,6 +911,12 @@ Aby użyć niestandardowej ścieżki zestawu JDK dla kompilacji wiersza poleceni Ten projekt może być kompilowany jednocześnie przez dwa procesy. Plik blokady istnieje w ścieżce: {0} + + Failed to parse 'DescriptorIndex' metadata value '{0}' for assembly '{1}'. + +{0} - The DescriptorIndex metadata value that could not be parsed +{1} - The assembly ItemSpec + Ten kod został wygenerowany przez narzędzie. @@ -1015,6 +1012,18 @@ Aby użyć niestandardowej ścieżki zestawu JDK dla kompilacji wiersza poleceni {1} - The name of the .NET runtime (e.g. CoreCLR, NativeAOT). + + Plik wejściowy `{0}` nie rozpoczyna się od elementu `<replacements/>`. + {0} - file path + + + Atrybut „{0}” w elemencie „{1}” ma wartość „{2}”, która nie może być analizowana jako wartość logiczna; {3} wiersz {4}. + {0} - attribute name; {1} - element name; {2} - attribute value; {3} - file path; {4} - line number + + + Brak wymaganego atrybutu „{0}” w elemencie „{1}”; {2} wiersza {3}. + {0} - attribute name; {1} - element name; {2} - file path; {3} - line number + Zależność Java „{0}” nie jest spełniona. The following are literal names and should not be translated: Java. @@ -1080,6 +1089,20 @@ Aby użyć niestandardowej ścieżki zestawu JDK dla kompilacji wiersza poleceni The following are literal names and should not be translated: Manifest, framework. {0} - Java type name from AndroidManifest.xml + + Typ „{0}” używa [JniAddNativeMethodRegistrationAttribute], co nie jest obsługiwane przez mapę typów z możliwością przycinania. Aby obejść ten problem, nie korzystaj z mapy typów z możliwością przycinania (na przykład przez przejście na implementację mapy typów „llvm-ir”) i zgłoś ten scenariusz na https://github.com/dotnet/android/issues, aby zespół mógł ocenić, czy warto dodać dla niego obsługę. + The following are literal names and should not be translated: JniAddNativeMethodRegistrationAttribute, llvm-ir. +{0} - Fully-qualified managed type name + + + Niezabezpieczony adres URL repozytorium HTTP Maven „{0}” jest niedozwolony. Aby pominąć tę kontrolę, należy użyć adresu URL w protokole HTTPS lub ustawić w metadanych elementu wartość AllowInsecureHttp="true". + The following are literal names and should not be translated: HTTP, HTTPS, Maven, AllowInsecureHttp +{0} - The insecure HTTP URL + + + Generated Java callable wrapper code changed: '{0}' + {0} - The path to the generated Java callable wrapper file + Wykonanie polecenia „{0}” nie powiodło się.\n{1} '{0}' is a failed command name (potentially with path) followed by all the arguments passed to it. {1} is the combined output on the standard error and standard output streams. diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.pt-BR.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.pt-BR.resx index f8859846d3a..e5a72c5aa75 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.pt-BR.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.pt-BR.resx @@ -478,30 +478,6 @@ In this message, the term "binding" means a piece of generated code that makes i A propriedade MSBuild 'RunAOTCompilation' só tem suporte quando o corte está habilitado. Edite o arquivo de projeto em um editor de texto para definir 'PublishTrimmed' como 'true' para esta configuração de compilação. The following are literal names and should not be translated: 'RunAOTCompilation', 'PublishTrimmed' - - O valor da propriedade "AndroidHttpClientHandlerType" "{0}" deve derivar de "{1}". -Altere o valor para um nome de tipo qualificado por assembly que herda de "{1}" ou remova a propriedade completamente. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - A type from which the AndroidHttpClientHandlerType should derive . - - - O valor da propriedade "AndroidHttpClientHandlerType" "{0}" não deve derivar de "System.Net.Htt.HttpClientHandler". -Altere o valor para um nome de tipo qualificado por assembly que herda de "System.Net.Http.HttpMessageHandler" ou remova a propriedade completamente. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', 'System.Net.Htt.HttpClientHandler', 'System.Net.Http.HttpMessageHandler', -{0} - The value of the property. - - - Falha ao resolver "{0}" de "{1}". Verifique a configuração "AndroidHttpClientHandlerType". - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - The name of the assembly. - - - Não foi possível resolver "{0}". Verifique a configuração "AndroidHttpClientHandlerType". - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. - Seu projeto faz referência a "{0}", que usa o assembly "_Microsoft.Android.Resource.Designer", mas você não tem esse recurso habilitado. Defina a propriedade "AndroidUseDesignerAssembly" do MSBuild como "true" no seu arquivo de projeto. The following are literal names and should not be translated: '_Microsoft.Android.Resource.Desinger', 'AndroidUseDesignerAssembly', 'true' @@ -589,6 +565,10 @@ Altere o valor no AndroidManifest.xml para corresponder ao valor $(SupportedOSPl Não foi possível vincular a biblioteca compartilhada nativa: {0}{1} The '{1}' placeholder is replaced with full output of the failed command, starting and ending with a newline. + + Failed to extract debug info from '{0}'{1} + '{0}' is the native shared library filename. '{1}' is the full output of the failed command, starting and ending with a newline. + Falha ao gerar o tipo Java para a classe: {0} devido a {1} {0} - The managed type name @@ -646,6 +626,12 @@ Altere o valor no AndroidManifest.xml para corresponder ao valor $(SupportedOSPl The following are literal names and should not be translated: AndroidManifest.xml, //uses-sdk/@android:targetSdkVersion , API-{1} {0} - The target SDK version number {1} - The API version number + + + Architecture '{0}' has Java types which have no counterparts in template architecture '{1}': {2} + {0} - The target architecture name +{1} - The template architecture name +{2} - Comma-separated list of Java type names that have no counterparts Não é possível localizar //manifest/application/uses-library no caminho: {0} @@ -697,6 +683,11 @@ In this mesage, the term "layout" means an Android UI layout. O item de recurso '{0}' não tem o item de metadados necessário '{1}'. {0} - The name of the Android layout resource file {1} - The name of the metadata item + + + Architecture '{0}' doesn't match all marshal methods in architecture '{1}'. Please see detailed MSBuild logs for more information. + {0} - The target architecture name +{1} - The template architecture name Não é possível localizar o //activity-alias/@android:targetActivity especificado: '{0}' @@ -920,6 +911,12 @@ Para usar um caminho JDK personalizado para um build de linha de comando, defina Dois processos podem estar criando este projeto ao mesmo tempo. O arquivo de bloqueio existe no caminho: {0} + + Failed to parse 'DescriptorIndex' metadata value '{0}' for assembly '{1}'. + +{0} - The DescriptorIndex metadata value that could not be parsed +{1} - The assembly ItemSpec + Este código foi gerado por uma ferramenta. @@ -1015,6 +1012,18 @@ Para usar um caminho JDK personalizado para um build de linha de comando, defina {1} - The name of the .NET runtime (e.g. CoreCLR, NativeAOT). + + O arquivo de entrada `{0}` não começa com `<replacements/>`. + {0} - file path + + + Atributo '{0}' no elemento '{1}' tem o valor '{2}' que não pode ser analisado como booleano; {3} linha {4}. + {0} - attribute name; {1} - element name; {2} - attribute value; {3} - file path; {4} - line number + + + Atributo obrigatório '{0}' faltando do elemento '{1}'; {2} linha {3}. + {0} - attribute name; {1} - element name; {2} - file path; {3} - line number + A dependência de Java "{0}" não foi atendida. The following are literal names and should not be translated: Java. @@ -1080,6 +1089,20 @@ Para usar um caminho JDK personalizado para um build de linha de comando, defina The following are literal names and should not be translated: Manifest, framework. {0} - Java type name from AndroidManifest.xml + + O tipo '{0}' usa [JniAddNativeMethodRegistrationAttribute], que não é suportado pelo mapa de tipos trimmable. Para contornar isso, não direcione ao mapa de tipo trimmable (por exemplo, mudando para a implementação do mapa de tipo 'llvm-ir') e relate esse cenário em https://github.com/dotnet/android/issues para que a equipe possa avaliar se deve suportá-lo. + The following are literal names and should not be translated: JniAddNativeMethodRegistrationAttribute, llvm-ir. +{0} - Fully-qualified managed type name + + + A URL insegura do repositório Maven '{0}' não é permitida. Use uma URL HTTPS ou defina AllowInsecureHttp="true" nos metadados do item para ignorar essa verificação. + The following are literal names and should not be translated: HTTP, HTTPS, Maven, AllowInsecureHttp +{0} - The insecure HTTP URL + + + Generated Java callable wrapper code changed: '{0}' + {0} - The path to the generated Java callable wrapper file + O comando '{0}' falhou.\n{1} '{0}' is a failed command name (potentially with path) followed by all the arguments passed to it. {1} is the combined output on the standard error and standard output streams. diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx index 214d4b89df3..5d442e70aa0 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx @@ -478,30 +478,6 @@ In this message, the term "binding" means a piece of generated code that makes i The 'RunAOTCompilation' MSBuild property is only supported when trimming is enabled. Edit the project file in a text editor to set 'PublishTrimmed' to 'true' for this build configuration. The following are literal names and should not be translated: 'RunAOTCompilation', 'PublishTrimmed' - - The 'AndroidHttpClientHandlerType' property value '{0}' must derive from '{1}'. -Please change the value to an assembly-qualifed type name which inherits from '{1}' or remove the property completely. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - A type from which the AndroidHttpClientHandlerType should derive . - - - The 'AndroidHttpClientHandlerType' property value '{0}' must not derive from 'System.Net.Htt.HttpClientHandler'. -Please change the value to an assembly-qualifed type name which inherits from 'System.Net.Http.HttpMessageHandler' or remove the property completely. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', 'System.Net.Htt.HttpClientHandler', 'System.Net.Http.HttpMessageHandler', -{0} - The value of the property. - - - Failed to resolve '{0}' from '{1}'. Please check your `AndroidHttpClientHandlerType` setting. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - The name of the assembly. - - - Could not resolve '{0}'. Please check your `AndroidHttpClientHandlerType` setting. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. - Your project references '{0}' which uses the `_Microsoft.Android.Resource.Designer` assembly, but you do not have this feature enabled. Please set the `AndroidUseDesignerAssembly` MSBuild property to `true` in your project file. The following are literal names and should not be translated: '_Microsoft.Android.Resource.Desinger', 'AndroidUseDesignerAssembly', 'true' @@ -589,6 +565,10 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla Could not link native shared library: {0}{1} The '{1}' placeholder is replaced with full output of the failed command, starting and ending with a newline. + + Failed to extract debug info from '{0}'{1} + '{0}' is the native shared library filename. '{1}' is the full output of the failed command, starting and ending with a newline. + Failed to generate Java type for class: {0} due to {1} {0} - The managed type name @@ -646,6 +626,12 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla The following are literal names and should not be translated: AndroidManifest.xml, //uses-sdk/@android:targetSdkVersion , API-{1} {0} - The target SDK version number {1} - The API version number + + + Architecture '{0}' has Java types which have no counterparts in template architecture '{1}': {2} + {0} - The target architecture name +{1} - The template architecture name +{2} - Comma-separated list of Java type names that have no counterparts Unable to find //manifest/application/uses-library at path: {0} @@ -697,6 +683,11 @@ In this mesage, the term "layout" means an Android UI layout. Resource item '{0}' does not have the required metadata item '{1}'. {0} - The name of the Android layout resource file {1} - The name of the metadata item + + + Architecture '{0}' doesn't match all marshal methods in architecture '{1}'. Please see detailed MSBuild logs for more information. + {0} - The target architecture name +{1} - The template architecture name Unable to find specified //activity-alias/@android:targetActivity: '{0}' @@ -821,6 +812,22 @@ Remove the '{0}' reference from your project and add the '{1}' NuGet package ins Ignoring `{0}`. Manifest does not have the required 'package' attribute on the manifest element. {0} - The path the the file. + + + + Specified input file '{0}' does not exist. Ignoring. + {0} - The path to the file. + + + + Input file '{0}' does not start with '<replacements/>'. Skipping. + {0} - The path to the file. + + + + Input file '{0}' could not be read: {1}. Skipping. + {0} - The path to the file. +{1} - The exception message. @@ -920,6 +927,12 @@ To use a custom JDK path for a command line build, set the 'JavaSdkDirectory' MS Two processes may be building this project at once. Lock file exists at path: {0} + + Failed to parse 'DescriptorIndex' metadata value '{0}' for assembly '{1}'. + +{0} - The DescriptorIndex metadata value that could not be parsed +{1} - The assembly ItemSpec + This code was generated by a tool. @@ -1015,6 +1028,18 @@ To use a custom JDK path for a command line build, set the 'JavaSdkDirectory' MS {1} - The name of the .NET runtime (e.g. CoreCLR, NativeAOT). + + Input file `{0}` does not start with `<replacements/>`. + {0} - file path + + + Attribute '{0}' in element '{1}' has value '{2}' that cannot be parsed as boolean; {3} line {4}. + {0} - attribute name; {1} - element name; {2} - attribute value; {3} - file path; {4} - line number + + + Required attribute '{0}' missing from element '{1}'; {2} line {3}. + {0} - attribute name; {1} - element name; {2} - file path; {3} - line number + Java dependency '{0}' is not satisfied. The following are literal names and should not be translated: Java. @@ -1080,6 +1105,20 @@ To use a custom JDK path for a command line build, set the 'JavaSdkDirectory' MS The following are literal names and should not be translated: Manifest, framework. {0} - Java type name from AndroidManifest.xml + + Type '{0}' uses [JniAddNativeMethodRegistrationAttribute], which is not supported by the trimmable type map. To work around this, do not target the trimmable type map (for example, by switching to the 'llvm-ir' type map implementation), and please report this scenario at https://github.com/dotnet/android/issues so the team can evaluate whether to support it. + The following are literal names and should not be translated: JniAddNativeMethodRegistrationAttribute, llvm-ir. +{0} - Fully-qualified managed type name + + + Insecure HTTP Maven repository URL '{0}' is not allowed. Use an HTTPS URL, or set AllowInsecureHttp="true" metadata on the item to override this check. + The following are literal names and should not be translated: HTTP, HTTPS, Maven, AllowInsecureHttp +{0} - The insecure HTTP URL + + + Generated Java callable wrapper code changed: '{0}' + {0} - The path to the generated Java callable wrapper file + Command '{0}' failed.\n{1} '{0}' is a failed command name (potentially with path) followed by all the arguments passed to it. {1} is the combined output on the standard error and standard output streams. diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.ru.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.ru.resx index b4cf99bd276..417c48d82f6 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.ru.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.ru.resx @@ -478,30 +478,6 @@ In this message, the term "binding" means a piece of generated code that makes i Свойство MSBuild "RunAOTCompilation" поддерживается только при включенной обрезке. Измените файл проекта в текстовом редакторе и установите значение "true" для свойства "PublishTrimmed" для этой конфигурации сборки. The following are literal names and should not be translated: 'RunAOTCompilation', 'PublishTrimmed' - - Значение "{0}" свойства "AndroidHttpClientHandlerType" должно быть производным от "{1}". -Измените значение на соответствующее сборке имя типа, наследуемое от "{1}", или полностью удалите свойство. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - A type from which the AndroidHttpClientHandlerType should derive . - - - Значение свойства "AndroidHttpClientHandlerType" "{0}" не должно быть производным от "System.Net.Htt.HttpClientHandler". -Измените значение на имя типа с указанием сборки, наследуемое от System.Net.Http.HttpMessageHandler, или полностью удалите это свойство. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', 'System.Net.Htt.HttpClientHandler', 'System.Net.Http.HttpMessageHandler', -{0} - The value of the property. - - - Не удалось разрешить "{0}" из "{1}". Проверьте параметр "AndroidHttpClientHandlerType". - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - The name of the assembly. - - - Не удалось разрешить "{0}". Проверьте параметр "AndroidHttpClientHandlerType". - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. - Проект ссылается на "{0}", который использует сборку _Microsoft.Android.Resource.Designer, но эта функция не включена. Задайте для свойства MSBuild "AndroidUseDesignerAssembly" значение "true" в файле проекта. The following are literal names and should not be translated: '_Microsoft.Android.Resource.Desinger', 'AndroidUseDesignerAssembly', 'true' @@ -589,6 +565,10 @@ In this message, the term "binding" means a piece of generated code that makes i Не удалось связать платформенную общую библиотеку: {0}{1} The '{1}' placeholder is replaced with full output of the failed command, starting and ending with a newline. + + Failed to extract debug info from '{0}'{1} + '{0}' is the native shared library filename. '{1}' is the full output of the failed command, starting and ending with a newline. + Не удалось создать тип Java для класса {0}, так как возникло исключение {1} {0} - The managed type name @@ -646,6 +626,12 @@ In this message, the term "binding" means a piece of generated code that makes i The following are literal names and should not be translated: AndroidManifest.xml, //uses-sdk/@android:targetSdkVersion , API-{1} {0} - The target SDK version number {1} - The API version number + + + Architecture '{0}' has Java types which have no counterparts in template architecture '{1}': {2} + {0} - The target architecture name +{1} - The template architecture name +{2} - Comma-separated list of Java type names that have no counterparts Не удалось найти //manifest/application/uses-library по пути: {0} @@ -697,6 +683,11 @@ In this mesage, the term "layout" means an Android UI layout. Элемент ресурса "{0}" не включает необходимый элемент метаданных "{1}". {0} - The name of the Android layout resource file {1} - The name of the metadata item + + + Architecture '{0}' doesn't match all marshal methods in architecture '{1}'. Please see detailed MSBuild logs for more information. + {0} - The target architecture name +{1} - The template architecture name Не найдено указанное действие //activity-alias/@android:targetActivity: "{0}". @@ -920,6 +911,12 @@ In this message, the term "handheld app" means "app for handheld devices." Сборка этого проекта может одновременно осуществляться двумя процессами. Файл блокировки находится по следующему пути: {0} + + Failed to parse 'DescriptorIndex' metadata value '{0}' for assembly '{1}'. + +{0} - The DescriptorIndex metadata value that could not be parsed +{1} - The assembly ItemSpec + Этот код был создан программным средством. @@ -1015,6 +1012,18 @@ In this message, the term "handheld app" means "app for handheld devices." {1} - The name of the .NET runtime (e.g. CoreCLR, NativeAOT). + + Входной файл `{0}` не начинается с `<replacements/>`. + {0} - file path + + + Атрибут "{0}" в элементе "{1}" использует значение "{2}", которое невозможно проанализировать как логическое; {3} строка {4}. + {0} - attribute name; {1} - element name; {2} - attribute value; {3} - file path; {4} - line number + + + Необходимый атрибут "{0}" отсутствует в элементе "{1}"; {2} строка {3}. + {0} - attribute name; {1} - element name; {2} - file path; {3} - line number + Зависимость Java "{0}" не удовлетворена. The following are literal names and should not be translated: Java. @@ -1080,6 +1089,20 @@ In this message, the term "handheld app" means "app for handheld devices." The following are literal names and should not be translated: Manifest, framework. {0} - Java type name from AndroidManifest.xml + + В типе"{0}" используется атрибут [JniAddNativeMethodRegistrationAttribute], который не поддерживается картой типов с возможностью обрезки. Чтобы обойти эту проблему, не нацеливайтесь на карту типов с возможностью обрезки (например, переключившись на реализацию карты типа "llvm-ir"), и сообщите об этом случае на странице https://github.com/dotnet/android/issues, чтобы команда разработчиков могла решить, стоит ли добавлять поддержку этой функции. + The following are literal names and should not be translated: JniAddNativeMethodRegistrationAttribute, llvm-ir. +{0} - Fully-qualified managed type name + + + Небезопасный URL-адрес репозитория Maven HTTP "{0}" не допускается. Используйте URL-адрес HTTPS или задайте для элемента метаданные AllowInsecureHttp="true", чтобы переопределить эту проверку. + The following are literal names and should not be translated: HTTP, HTTPS, Maven, AllowInsecureHttp +{0} - The insecure HTTP URL + + + Generated Java callable wrapper code changed: '{0}' + {0} - The path to the generated Java callable wrapper file + Не удалось выполнить команду "{0}".\n{1} '{0}' is a failed command name (potentially with path) followed by all the arguments passed to it. {1} is the combined output on the standard error and standard output streams. diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.tr.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.tr.resx index 2c13039f196..8ea81d9cd3b 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.tr.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.tr.resx @@ -478,30 +478,6 @@ In this message, the term "binding" means a piece of generated code that makes i 'RunAOTCompilation' MSBuild özelliği yalnızca kırpma etkinleştirildiğinde desteklenir. Bu yapı yapılandırması için 'PublishTrimmed' öğesini 'true' olarak ayarlamak için proje dosyasını bir metin düzenleyicide düzenleyin. The following are literal names and should not be translated: 'RunAOTCompilation', 'PublishTrimmed' - - '{0}' 'AndroidHttpClientHandlerType' özellik değeri '{1}' türünden türetilmelidir. -Lütfen değeri '{1}' türünden devralan derlemeye uygun bir tür adına dönüştürün veya özelliği tamamen kaldırın. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - A type from which the AndroidHttpClientHandlerType should derive . - - - ‘AndroidHttpClientHandlerType’ özellik değeri ‘{0}’ ‘System.Net.Htt.HttpClientHandler’ öğesinden türetilmemeli. -Lütfen değeri ‘System.Net.Http.HttpMessageHandler’ türünden devralan derlemeye uygun bir tür adına dönüştürün veya özelliği tamamen kaldırın. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', 'System.Net.Htt.HttpClientHandler', 'System.Net.Http.HttpMessageHandler', -{0} - The value of the property. - - - '{0}' değeri '{1}' derlemesinden çözümlenemedi. Lütfen `AndroidHttpClientHandlerType` ayarınızı kontrol edin. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - The name of the assembly. - - - '{0}' değeri çözümlenemedi. Lütfen `AndroidHttpClientHandlerType` ayarınızı kontrol edin. - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. - Projeniz `_Microsoft.Android.Resource.Designer` derlemesini kullanan '{0}' özelliğine başvuruyor ancak bu özellik etkin değil. Lütfen proje dosyanızda `AndroidUseDesignerAssembly` MSBuild özelliğini `true` olarak ayarlayın. The following are literal names and should not be translated: '_Microsoft.Android.Resource.Desinger', 'AndroidUseDesignerAssembly', 'true' @@ -589,6 +565,10 @@ AndroidManifest.xml dosyasındaki değeri $(SupportedOSPlatformVersion) değeriy Yerel paylaşılan kitaplık bağlanamadı: {0}{1} The '{1}' placeholder is replaced with full output of the failed command, starting and ending with a newline. + + Failed to extract debug info from '{0}'{1} + '{0}' is the native shared library filename. '{1}' is the full output of the failed command, starting and ending with a newline. + {0} sınıfı için şu özel durum nedeniyle Java türü oluşturulamadı: {1} {0} - The managed type name @@ -646,6 +626,12 @@ AndroidManifest.xml dosyasındaki değeri $(SupportedOSPlatformVersion) değeriy The following are literal names and should not be translated: AndroidManifest.xml, //uses-sdk/@android:targetSdkVersion , API-{1} {0} - The target SDK version number {1} - The API version number + + + Architecture '{0}' has Java types which have no counterparts in template architecture '{1}': {2} + {0} - The target architecture name +{1} - The template architecture name +{2} - Comma-separated list of Java type names that have no counterparts Yolda //manifest/application/uses-library bulunamıyor: {0} @@ -697,6 +683,11 @@ In this mesage, the term "layout" means an Android UI layout. '{0}' kaynak öğesinde gerekli meta veri öğesi ('{1}') yok. {0} - The name of the Android layout resource file {1} - The name of the metadata item + + + Architecture '{0}' doesn't match all marshal methods in architecture '{1}'. Please see detailed MSBuild logs for more information. + {0} - The target architecture name +{1} - The template architecture name Belirtilen //activity-alias/@android:targetActivity: '{0}' bulunamadı @@ -920,6 +911,12 @@ Bir komut satırı derlemesi için özel bir SDK yolu kullanmak için 'JavaSdkDi Bu proje aynı anda iki işlemde derleniyor olabilir. Yolda kilit dosyası var: {0} + + Failed to parse 'DescriptorIndex' metadata value '{0}' for assembly '{1}'. + +{0} - The DescriptorIndex metadata value that could not be parsed +{1} - The assembly ItemSpec + Bu kod, bir araç tarafından oluşturuldu. @@ -1015,6 +1012,18 @@ Bir komut satırı derlemesi için özel bir SDK yolu kullanmak için 'JavaSdkDi {1} - The name of the .NET runtime (e.g. CoreCLR, NativeAOT). + + Giriş dosyası `{0}`, `<replacements/>` ile başlamıyor. + {0} - file path + + + ‘{1}’ öğesindeki ‘{0}’ özniteliğinin değeri ‘{2}’ olup, bu değer boole olarak yorumlanamaz; {3}, satır {4}. + {0} - attribute name; {1} - element name; {2} - attribute value; {3} - file path; {4} - line number + + + ‘{1}’ öğesinde ‘{0}’ özniteliği eksik; {2}, satır {3}. + {0} - attribute name; {1} - element name; {2} - file path; {3} - line number + '{0}' Java bağımlılığı karşılanmadı. The following are literal names and should not be translated: Java. @@ -1080,6 +1089,20 @@ Bir komut satırı derlemesi için özel bir SDK yolu kullanmak için 'JavaSdkDi The following are literal names and should not be translated: Manifest, framework. {0} - Java type name from AndroidManifest.xml + + '{0}' türü, kırpılabilir tür eşlemesi tarafından desteklenmeyen [JniAddNativeMethodRegistrationAttribute] kullanır. Bu sorunu geçici olarak çözmek için kırpılabilir tür eşlemesini hedeflemeyin (örneğin, 'llvm-ir' tür eşlemesi uygulamaya geçerek) ve ekibin bunun desteklenip desteklenmeyeceğini değerlendirebilmesi için lütfen https://github.com/dotnet/android/issues adresinde bu senaryoyu raporlayın. + The following are literal names and should not be translated: JniAddNativeMethodRegistrationAttribute, llvm-ir. +{0} - Fully-qualified managed type name + + + Güvenli olmayan HTTP Maven deposu URL'si ‘{0}’ kullanılamaz. Bu denetimi geçersiz kılmak için bir HTTPS URL'si kullanın veya öğede AllowInsecureHttp="true" meta verisini ayarlayın. + The following are literal names and should not be translated: HTTP, HTTPS, Maven, AllowInsecureHttp +{0} - The insecure HTTP URL + + + Generated Java callable wrapper code changed: '{0}' + {0} - The path to the generated Java callable wrapper file + '{0}' komutu başarısız oldu.\n{1} '{0}' is a failed command name (potentially with path) followed by all the arguments passed to it. {1} is the combined output on the standard error and standard output streams. diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.zh-Hans.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.zh-Hans.resx index ed0dc36ad40..cb457c8e92b 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.zh-Hans.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.zh-Hans.resx @@ -478,30 +478,6 @@ In this message, the term "binding" means a piece of generated code that makes i 仅当启用剪裁时,才支持 “RunAOTCompilation” MSBuild 属性。在文本编辑器中编辑项目文件,以将此生成配置的 “PublishTrimmed” 设置为 “true”。 The following are literal names and should not be translated: 'RunAOTCompilation', 'PublishTrimmed' - - "AndroidHttpClientHandlerType" 属性值 "{0}" 必须派生自 "{1}"。 -请将值更改为继承自 "{1}" 的程序集限定类型名称,或完全删除该属性。 - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - A type from which the AndroidHttpClientHandlerType should derive . - - - "AndroidHttpClientHandlerType" 属性值 "{0}" 不得派生自 "System.Net.Htt.HttpClientHandler"。 -请将该值更改为继承自 "System.Net.Http.HttpMessageHandler" 的符合程序集要求的类型名称,或完全删除该属性。 - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', 'System.Net.Htt.HttpClientHandler', 'System.Net.Http.HttpMessageHandler', -{0} - The value of the property. - - - 无法从 "{1}" 解析 "{0}"。请检查你的 `AndroidHttpClientHandlerType` 设置。 - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - The name of the assembly. - - - 无法解析 "{0}"。请检查你的 `AndroidHttpClientHandlerType` 设置。 - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. - 你的项目引用了 "{0}",它使用 `_Microsoft.Android.Resource.Designer` 程序集,但未启用此功能。请在项目文件中将 `AndroidUseDesignerAssembly` MSBuild 属性设置为 "true"。 The following are literal names and should not be translated: '_Microsoft.Android.Resource.Desinger', 'AndroidUseDesignerAssembly', 'true' @@ -589,6 +565,10 @@ In this message, the term "binding" means a piece of generated code that makes i 无法链接本机共享库: {0}{1} The '{1}' placeholder is replaced with full output of the failed command, starting and ending with a newline. + + Failed to extract debug info from '{0}'{1} + '{0}' is the native shared library filename. '{1}' is the full output of the failed command, starting and ending with a newline. + 由于 {1},未能为类 {0} 生成 Java 类型 {0} - The managed type name @@ -646,6 +626,12 @@ In this message, the term "binding" means a piece of generated code that makes i The following are literal names and should not be translated: AndroidManifest.xml, //uses-sdk/@android:targetSdkVersion , API-{1} {0} - The target SDK version number {1} - The API version number + + + Architecture '{0}' has Java types which have no counterparts in template architecture '{1}': {2} + {0} - The target architecture name +{1} - The template architecture name +{2} - Comma-separated list of Java type names that have no counterparts 在以下路径找不到 //manifest/application/uses-library: {0} @@ -697,6 +683,11 @@ In this mesage, the term "layout" means an Android UI layout. 资源项“{0}”没有所需的元数据项“{1}”。 {0} - The name of the Android layout resource file {1} - The name of the metadata item + + + Architecture '{0}' doesn't match all marshal methods in architecture '{1}'. Please see detailed MSBuild logs for more information. + {0} - The target architecture name +{1} - The template architecture name 找不到指定的 //activity-alias/@android:targetActivity:“{0}” @@ -920,6 +911,12 @@ In this message, the term "handheld app" means "app for handheld devices." 可同时生成此项目的两个进程。以下路径中存在锁定文件: {0} + + Failed to parse 'DescriptorIndex' metadata value '{0}' for assembly '{1}'. + +{0} - The DescriptorIndex metadata value that could not be parsed +{1} - The assembly ItemSpec + 此代码由工具生成。 @@ -1015,6 +1012,18 @@ In this message, the term "handheld app" means "app for handheld devices." {1} - The name of the .NET runtime (e.g. CoreCLR, NativeAOT). + + 输入文件 `{0}` 没有以 `<replacements/>` 开头。 + {0} - file path + + + 元素“{1}”中的属性“{0}”具有无法解析为布尔值的值“{2}”;{3} 行 {4}。 + {0} - attribute name; {1} - element name; {2} - attribute value; {3} - file path; {4} - line number + + + 元素“{1}”中缺少必需属性“{0}”,{2} 行 {3}。 + {0} - attribute name; {1} - element name; {2} - file path; {3} - line number + 不满足 Java 依赖项“{0}”。 The following are literal names and should not be translated: Java. @@ -1080,6 +1089,20 @@ In this message, the term "handheld app" means "app for handheld devices." The following are literal names and should not be translated: Manifest, framework. {0} - Java type name from AndroidManifest.xml + + 类型“{0}”使用 [JniAddNativeMethodRegistrationAttribute],此属性不受可修整类型映射的支持。若要解决此问题,请不要以可修整类型映射为目标(例如,切换到 "llvm-ir" 类型映射实现),并请在 https://github.com/dotnet/android/issues 报告此场景,以便团队评估是否支持它。 + The following are literal names and should not be translated: JniAddNativeMethodRegistrationAttribute, llvm-ir. +{0} - Fully-qualified managed type name + + + 不允许使用不安全的 HTTP Maven 存储库 URL“{0}”。请使用 HTTPS URL,或在项上设置 AllowInsecureHttp="true" 元数据以替代此检查。 + The following are literal names and should not be translated: HTTP, HTTPS, Maven, AllowInsecureHttp +{0} - The insecure HTTP URL + + + Generated Java callable wrapper code changed: '{0}' + {0} - The path to the generated Java callable wrapper file + 命令 '{0}' 失败。\n{1} '{0}' is a failed command name (potentially with path) followed by all the arguments passed to it. {1} is the combined output on the standard error and standard output streams. diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.zh-Hant.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.zh-Hant.resx index 8f3c63311eb..bd4e111dda1 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.zh-Hant.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.zh-Hant.resx @@ -478,30 +478,6 @@ In this message, the term "binding" means a piece of generated code that makes i 只有在啟用 [修剪] 時,才支援 'RunAOTCompilation' MSBuild 屬性。在文字編輯器中編輯專案檔案,以將此組建組態的 'PublishTrimmed' 設為 'true'。 The following are literal names and should not be translated: 'RunAOTCompilation', 'PublishTrimmed' - - 'AndroidHttpClientHandlerType' 屬性值 '{0}' 必須衍生自 '{1}'。 -請將值變更為繼承自 '{1}' 或完全移除屬性的 assembly-qualifed 類型名稱。 - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - A type from which the AndroidHttpClientHandlerType should derive . - - - 'AndroidHttpClientHandlerType' 屬性值 '{0}' 不能衍生自 'System.Net.Htt.HttpClientHandler'。 -請將值變更為繼承自 'System.Net.Http.HttpMessageHandler' 或完全移除屬性的 assembly-qualifed 類型名稱。 - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', 'System.Net.Htt.HttpClientHandler', 'System.Net.Http.HttpMessageHandler', -{0} - The value of the property. - - - 無法從 '{0}' 解析 '{1}'。請檢查您的 'AndroidHttpClientHandlerType' 設定。 - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. -{1} - The name of the assembly. - - - 無法解析 '{0}'。請檢查您的 'AndroidHttpClientHandlerType' 設定。 - The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', -{0} - The value of the property. - 您的專案參考 '{0}' 使用 '_Microsoft.Android.Resource.Designer' 組件,但您未啟用此功能。請將專案檔案中的 'AndroidUseDesignerAssembly' MSBuild 屬性設定為 'true'。 The following are literal names and should not be translated: '_Microsoft.Android.Resource.Desinger', 'AndroidUseDesignerAssembly', 'true' @@ -589,6 +565,10 @@ In this message, the term "binding" means a piece of generated code that makes i 無法連結原生共用程式庫: {0}{1} The '{1}' placeholder is replaced with full output of the failed command, starting and ending with a newline. + + Failed to extract debug info from '{0}'{1} + '{0}' is the native shared library filename. '{1}' is the full output of the failed command, starting and ending with a newline. + 因為 {1},所以無法為類別 {0} 產生 Java 類型 {0} - The managed type name @@ -646,6 +626,12 @@ In this message, the term "binding" means a piece of generated code that makes i The following are literal names and should not be translated: AndroidManifest.xml, //uses-sdk/@android:targetSdkVersion , API-{1} {0} - The target SDK version number {1} - The API version number + + + Architecture '{0}' has Java types which have no counterparts in template architecture '{1}': {2} + {0} - The target architecture name +{1} - The template architecture name +{2} - Comma-separated list of Java type names that have no counterparts 在路徑 {0} 中找不到 //manifest/application/uses-library @@ -697,6 +683,11 @@ In this mesage, the term "layout" means an Android UI layout. 資源項目 '{0}' 不具所需的中繼資料項目 '{1}'。 {0} - The name of the Android layout resource file {1} - The name of the metadata item + + + Architecture '{0}' doesn't match all marshal methods in architecture '{1}'. Please see detailed MSBuild logs for more information. + {0} - The target architecture name +{1} - The template architecture name 找不到指定的 //activity-alias/@android:targetActivity: '{0}' @@ -920,6 +911,12 @@ In this message, the term "handheld app" means "app for handheld devices." 可能有兩個處理序正在同時建置此專案。鎖定檔案存在於下列路徑: {0} + + Failed to parse 'DescriptorIndex' metadata value '{0}' for assembly '{1}'. + +{0} - The DescriptorIndex metadata value that could not be parsed +{1} - The assembly ItemSpec + 這段程式碼已由工具產生。 @@ -1015,6 +1012,18 @@ In this message, the term "handheld app" means "app for handheld devices." {1} - The name of the .NET runtime (e.g. CoreCLR, NativeAOT). + + 輸入檔案 `{0}` 不是以 `<replacements/>` 開頭。 + {0} - file path + + + 元素 t '{1}' 中的屬性 '{0}' 具有無法剖析為布林值的值 '{2}'; {3} 行 {4}。 + {0} - attribute name; {1} - element name; {2} - attribute value; {3} - file path; {4} - line number + + + 元素 '{1}' 缺少必要的屬性 '{0}'; {2} 行 {3}。 + {0} - attribute name; {1} - element name; {2} - file path; {3} - line number + 無法滿足 JAVA 相依性 '{0}'。 The following are literal names and should not be translated: Java. @@ -1080,6 +1089,20 @@ In this message, the term "handheld app" means "app for handheld devices." The following are literal names and should not be translated: Manifest, framework. {0} - Java type name from AndroidManifest.xml + + 類型 '{0}' 使用 [JniAddNativeMethodRegistrationAttribute],但可修剪的類型對應不支援此類型。若要處理此情況,請勿以可修剪的類型對應為目標 (例如,切換到 'llvm-ir' 類型對應實作),請在 https://github.com/dotnet/android/issues 報告此案例,讓小組可以評估是否提供支援。 + The following are literal names and should not be translated: JniAddNativeMethodRegistrationAttribute, llvm-ir. +{0} - Fully-qualified managed type name + + + 不允許不安全的 HTTP Maven 存放庫 URL '{0}'。使用 HTTPS URL,或在項目上設定 AllowInsecureHttp="true" 中繼資料以覆寫此檢查。 + The following are literal names and should not be translated: HTTP, HTTPS, Maven, AllowInsecureHttp +{0} - The insecure HTTP URL + + + Generated Java callable wrapper code changed: '{0}' + {0} - The path to the generated Java callable wrapper file + 指令 '{0}' 失敗。\n{1} '{0}' is a failed command name (potentially with path) followed by all the arguments passed to it. {1} is the combined output on the standard error and standard output streams. diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CheckClientHandlerType.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CheckClientHandlerType.cs deleted file mode 100644 index 0f145294382..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CheckClientHandlerType.cs +++ /dev/null @@ -1,98 +0,0 @@ -#nullable enable - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using System.IO; -using Microsoft.Android.Build.Tasks; -using Java.Interop.Tools.Cecil; -using Mono.Cecil; - -namespace Xamarin.Android.Tasks -{ - public class CheckClientHandlerType : AndroidTask - { - public override string TaskPrefix => "CCHT"; - - [Required] - public string ClientHandlerType { get; set; } = ""; - - [Required] - public ITaskItem[] ResolvedAssemblies { get; set; } = []; - - public override bool RunTask () - { - string[] types = ClientHandlerType.Split (','); - string type = types[0].Trim (); - string assembly = "Mono.Android"; - - if (types.Length > 1) { - assembly = types[1].Trim (); - } - // load the assembly. - ITaskItem? foundAssembly = null; - foreach (var asm in ResolvedAssemblies) { - string filename = Path.GetFileNameWithoutExtension (asm.ItemSpec); - if (string.CompareOrdinal (assembly, filename) == 0) { - foundAssembly = asm; - break; - } - } - if (foundAssembly == null) { - Log.LogCodedError ("XA1033", Xamarin.Android.Tasks.Properties.Resources.XA1033, assembly); - return !Log.HasLoggedErrors; - } - // find the type. - var readerParameters = new ReaderParameters { - ReadSymbols = false, - }; - using (var resolver = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: false, loadReaderParameters: readerParameters)) { - var cache = new TypeDefinitionCache (); - foreach (var asm in ResolvedAssemblies) { - var path = Path.GetFullPath (Path.GetDirectoryName (asm.ItemSpec)); - if (!resolver.SearchDirectories.Contains (path)) { - resolver.SearchDirectories.Add (path); - } - } - - var assemblyDefinition = resolver.GetAssembly (Path.GetFullPath (foundAssembly.ItemSpec)); - TypeDefinition? handlerType = null; - foreach (var model in assemblyDefinition.Modules) { - handlerType = assemblyDefinition.MainModule.GetType (type); - if (handlerType != null) - break; - } - if (handlerType == null) { - Log.LogCodedError ("XA1032", Xamarin.Android.Tasks.Properties.Resources.XA1032, type, assembly); - return false; - } - - if (Extends (cache, handlerType, "System.Net.Http.HttpClientHandler")) { - Log.LogCodedError ("XA1031", Xamarin.Android.Tasks.Properties.Resources.XA1031_HCH, type); - } - - if (!Extends (cache, handlerType, "System.Net.Http.HttpMessageHandler")) { - Log.LogCodedError ("XA1031", Xamarin.Android.Tasks.Properties.Resources.XA1031, type, "System.Net.Http.HttpMessageHandler"); - } - - return !Log.HasLoggedErrors; - } - } - - static bool Extends (TypeDefinitionCache cache, TypeDefinition type, string validBase) { - var bt = cache.Resolve (type); - while (bt != null) { - if (bt.FullName == validBase) - return true; - if (bt.BaseType != null) { - bt = cache.Resolve (bt.BaseType); - } else { - bt = null; - } - } - return false; - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CheckForInvalidResourceFileNames.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CheckForInvalidResourceFileNames.cs index af44eabd506..712c3b1cf0c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CheckForInvalidResourceFileNames.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CheckForInvalidResourceFileNames.cs @@ -1,16 +1,8 @@ #nullable enable using System; -using System.Diagnostics; using System.IO; -using System.Linq; -using System.Threading; -using System.Xml; -using System.Xml.Linq; -using Microsoft.Build.Utilities; using Microsoft.Build.Framework; using System.Text.RegularExpressions; -using System.Collections.Generic; -using Xamarin.Android.Tasks; using Microsoft.Android.Build.Tasks; namespace Xamarin.Android.Tasks { diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CompressAssemblies.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CompressAssemblies.cs index f2730a732d2..039cfc0626c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CompressAssemblies.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CompressAssemblies.cs @@ -35,7 +35,7 @@ public override bool RunTask () break; if (!uint.TryParse (descriptor_index_string, out var descriptor_index)) { - Log.LogError ($"Failed to parse 'DescriptorIndex' metadata value '{descriptor_index_string}' for assembly '{assembly.ItemSpec}'"); + Log.LogCodedError ("XA5303", Properties.Resources.XA5303, descriptor_index_string, assembly.ItemSpec); break; } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CreateDynamicFeatureManifest.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CreateDynamicFeatureManifest.cs index a3e3e766f73..c052e225101 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CreateDynamicFeatureManifest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CreateDynamicFeatureManifest.cs @@ -1,12 +1,6 @@ #nullable enable using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Xml; using System.Xml.Linq; -using Microsoft.Build.Utilities; using Microsoft.Build.Framework; using Xamarin.Android.Tools; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateAdditionalProviderSources.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateAdditionalProviderSources.cs index 4971e0f636a..9afb10b3404 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateAdditionalProviderSources.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateAdditionalProviderSources.cs @@ -33,9 +33,8 @@ public sealed class GenerateAdditionalProviderSources : AndroidTask public ITaskItem[]? Environments { get; set; } - // We need to pass these two to the environment builder, otherwise not used + // We need to pass this to the environment builder, otherwise not used // by this task. See also GenerateNativeApplicationSources.cs - public string? HttpClientHandlerType { get; set; } public bool EnableSGenConcurrent { get; set; } AndroidRuntime androidRuntime; @@ -86,7 +85,7 @@ void Generate (NativeCodeGenStateObject codeGenState) // For NativeAOT, generate JavaInteropRuntime.java and NativeAotEnvironmentVars.java if (androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) { - GenerateNativeAotBootstrapFiles (Log, OutputDirectory, TargetName, Environments, HttpClientHandlerType, EnableSGenConcurrent); + GenerateNativeAotBootstrapFiles (Log, OutputDirectory, TargetName, Environments, EnableSGenConcurrent); } // Create additional application java sources. @@ -139,7 +138,6 @@ internal static void GenerateNativeAotBootstrapFiles ( string outputDirectory, string targetName, ITaskItem []? environments, - string? httpClientHandlerType, bool enableSGenConcurrent) { GenerateJavaSource ( @@ -152,7 +150,7 @@ internal static void GenerateNativeAotBootstrapFiles ( // We care only about environment variables here var envBuilder = new EnvironmentBuilder (log); envBuilder.Read (environments); - GenerateNativeApplicationConfigSources.AddDefaultEnvironmentVariables (envBuilder, httpClientHandlerType, enableSGenConcurrent); + GenerateNativeApplicationConfigSources.AddDefaultEnvironmentVariables (envBuilder, enableSGenConcurrent); var envVarNames = new StringBuilder (); var envVarValues = new StringBuilder (); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs index 7da71df76e3..16cf42c3533 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs @@ -70,7 +70,7 @@ void Generate (string remappingXmlFilePath) using (var reader = XmlReader.Create (File.OpenRead (remappingXmlFilePath), readerSettings)) { if (reader.MoveToContent () != XmlNodeType.Element || reader.LocalName != "replacements") { - Log.LogError ($"Input file `{remappingXmlFilePath}` does not start with ``"); + Log.LogCodedError ("XA1045", Properties.Resources.XA1045, remappingXmlFilePath); } else { ReadXml (reader, typeReplacements, methodReplacements, remappingXmlFilePath); } @@ -131,7 +131,7 @@ void ReadXml (XmlReader reader, List typeReplacemen } if (!Boolean.TryParse (targetIsStatic, out bool isStatic)) { - Log.LogError ($"Attribute 'target-method-instance-to-static' in element '{reader.LocalName}' value '{targetIsStatic}' cannot be parsed as boolean; {remappingXmlFilePath} line {GetCurrentLineNumber ()}"); + Log.LogCodedError ("XA1046", Properties.Resources.XA1046, "target-method-instance-to-static", reader.LocalName, targetIsStatic, remappingXmlFilePath, GetCurrentLineNumber ()); continue; } @@ -152,7 +152,7 @@ bool GetRequiredAttribute (string attributeName, out string attributeValue) return true; } - Log.LogError ($"Attribute '{attributeName}' missing from element '{reader.LocalName}'; {remappingXmlFilePath} line {GetCurrentLineNumber ()}"); + Log.LogCodedError ("XA1047", Properties.Resources.XA1047, attributeName, reader.LocalName, remappingXmlFilePath, GetCurrentLineNumber ()); return false; } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotBootstrapSources.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotBootstrapSources.cs index f3bda0f349b..ba2505f71f3 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotBootstrapSources.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotBootstrapSources.cs @@ -27,14 +27,12 @@ public sealed class GenerateNativeAotBootstrapSources : AndroidTask public ITaskItem []? Environments { get; set; } - public string? HttpClientHandlerType { get; set; } - public bool EnableSGenConcurrent { get; set; } public override bool RunTask () { GenerateAdditionalProviderSources.GenerateNativeAotBootstrapFiles ( - Log, OutputDirectory, TargetName, Environments, HttpClientHandlerType, EnableSGenConcurrent); + Log, OutputDirectory, TargetName, Environments, EnableSGenConcurrent); return !Log.HasLoggedErrors; } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotEnvironmentAssemblerSources.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotEnvironmentAssemblerSources.cs index 55e11752638..a23748cc237 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotEnvironmentAssemblerSources.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeAotEnvironmentAssemblerSources.cs @@ -14,7 +14,6 @@ public class GenerateNativeAotEnvironmentAssemblerSources : AndroidTask [Required] public string RID { get; set; } = ""; public ITaskItem[]? Environments { get; set; } - public string? HttpClientHandlerType { get; set; } public override bool RunTask () { diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs index 92aab238757..52404559199 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs @@ -69,7 +69,6 @@ public class GenerateNativeApplicationConfigSources : AndroidTask public string? AndroidAotMode { get; set; } public bool AndroidAotEnableLazyLoad { get; set; } public bool EnableLLVM { get; set; } - public string? HttpClientHandlerType { get; set; } public string? TlsProvider { get; set; } public string? AndroidSequencePointsMode { get; set; } public bool EnableSGenConcurrent { get; set; } @@ -85,10 +84,9 @@ bool _Debug { AndroidRuntime androidRuntime; - internal static void AddDefaultEnvironmentVariables (EnvironmentBuilder envBuilder, string? httpClientHandlerType, bool enableSGenConcurrent) + internal static void AddDefaultEnvironmentVariables (EnvironmentBuilder envBuilder, bool enableSGenConcurrent) { envBuilder.AddDefaultMonoDebug (); - envBuilder.AddHttpClientHandlerType (httpClientHandlerType); envBuilder.AddMonoGcParams (enableSGenConcurrent); } @@ -121,7 +119,7 @@ public override bool RunTask () } if (androidRuntime != Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) { - AddDefaultEnvironmentVariables (envBuilder, HttpClientHandlerType, EnableSGenConcurrent); + AddDefaultEnvironmentVariables (envBuilder, EnableSGenConcurrent); } else { // NativeAOT sets all the environment variables from Java, we don't want to repeat that // process in the native code. This is just a precaution, because NativeAOT builds should @@ -179,8 +177,17 @@ public override bool RunTask () } }; + static bool ShouldSkipAssembly (ITaskItem assembly) + { + return assembly.GetMetadataOrDefault ("AndroidSkipAddToPackage", false); + } + if (SatelliteAssemblies != null) { foreach (ITaskItem assembly in SatelliteAssemblies) { + if (ShouldSkipAssembly (assembly)) { + continue; + } + updateNameWidth (assembly); updateAssemblyCount (assembly); } @@ -190,6 +197,10 @@ public override bool RunTask () int jnienv_initialize_method_token = -1; int jnienv_registerjninatives_method_token = -1; foreach (var assembly in ResolvedAssemblies) { + if (ShouldSkipAssembly (assembly)) { + continue; + } + updateNameWidth (assembly); updateAssemblyCount (assembly); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs index d3797bbddb5..ae5fbcfd791 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs @@ -37,12 +37,15 @@ public void LogRootingManifestReferencedTypeInfo (string javaTypeName, string ma log.LogMessage (MessageImportance.Low, $"Rooting manifest-referenced type '{javaTypeName}' ({managedTypeName}) as unconditional."); public void LogManifestReferencedTypeNotFoundWarning (string javaTypeName) => log.LogCodedWarning ("XA4250", Properties.Resources.XA4250, javaTypeName); + public void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeName) => + log.LogCodedError ("XA4251", Properties.Resources.XA4251, managedTypeName); } public override string TaskPrefix => "GTT"; [Required] public ITaskItem [] ResolvedAssemblies { get; set; } = []; + public ITaskItem [] ResolvedFrameworkAssemblies { get; set; } = []; [Required] public string OutputDirectory { get; set; } = ""; [Required] @@ -54,6 +57,8 @@ public void LogManifestReferencedTypeNotFoundWarning (string javaTypeName) => public string? ApplicationRegistrationOutputFile { get; set; } + public string? GeneratedAssembliesListFile { get; set; } + public string? ManifestTemplate { get; set; } public string? MergedAndroidManifestOutput { get; set; } @@ -68,6 +73,7 @@ public void LogManifestReferencedTypeNotFoundWarning (string javaTypeName) => public bool Debug { get; set; } public bool NeedsInternet { get; set; } public bool EmbedAssemblies { get; set; } + public string? PackageNamingPolicy { get; set; } /// /// Maximum array rank for which the generator emits per-rank __ArrayMapRank{N} @@ -75,6 +81,7 @@ public void LogManifestReferencedTypeNotFoundWarning (string javaTypeName) => /// $(_AndroidTrimmableTypeMapMaxArrayRank). /// public int MaxArrayRank { get; set; } + public string? ManifestPlaceholders { get; set; } public string? CheckedBuild { get; set; } public string? ApplicationJavaClass { get; set; } @@ -89,8 +96,15 @@ public void LogManifestReferencedTypeNotFoundWarning (string javaTypeName) => public override bool RunTask () { var systemRuntimeVersion = ParseTargetFrameworkVersion (TargetFrameworkVersion); - var assemblyPaths = ResolvedAssemblies.Select (i => i.ItemSpec).Distinct ().ToList (); - // TODO(#10792): populate with framework assembly names to skip JCW generation for pre-compiled framework types + var frameworkAssemblyPaths = new HashSet ( + ResolvedFrameworkAssemblies.Select (i => Path.GetFullPath (i.ItemSpec)), + StringComparer.OrdinalIgnoreCase); + var assemblyInputs = ResolvedAssemblies + .GroupBy (i => Path.GetFullPath (i.ItemSpec), StringComparer.OrdinalIgnoreCase) + .Select (g => ( + Path: g.Key, + IsFrameworkAssembly: frameworkAssemblyPaths.Contains (g.Key) || g.Any (IsFrameworkAssemblyItem))) + .ToList (); var frameworkAssemblyNames = new HashSet (StringComparer.OrdinalIgnoreCase); Directory.CreateDirectory (OutputDirectory); @@ -100,11 +114,15 @@ public override bool RunTask () var assemblies = new List<(string Name, PEReader Reader)> (); TrimmableTypeMapResult? result = null; try { - foreach (var path in assemblyPaths) { + foreach (var (path, isFrameworkAssembly) in assemblyInputs) { var peReader = new PEReader (File.OpenRead (path)); peReaders.Add (peReader); var mdReader = peReader.GetMetadataReader (); - assemblies.Add ((mdReader.GetString (mdReader.GetAssemblyDefinition ().Name), peReader)); + var assemblyName = mdReader.GetString (mdReader.GetAssemblyDefinition ().Name); + assemblies.Add ((assemblyName, peReader)); + if (isFrameworkAssembly) { + frameworkAssemblyNames.Add (assemblyName); + } } ManifestConfig? manifestConfig = null; @@ -137,11 +155,13 @@ public override bool RunTask () systemRuntimeVersion, frameworkAssemblyNames, useSharedTypemapUniverse: !Debug, - manifestConfig, - manifestTemplate, + manifestConfig: manifestConfig, + manifestTemplate: manifestTemplate, + packageNamingPolicy: PackageNamingPolicy, maxArrayRank: MaxArrayRank); - GeneratedAssemblies = WriteAssembliesToDisk (result.GeneratedAssemblies, assemblyPaths); + GeneratedAssemblies = WriteAssembliesToDisk (result.GeneratedAssemblies, assemblyInputs.Select (i => i.Path).ToList ()); + WriteGeneratedAssembliesListFile (GeneratedAssemblies); GeneratedJavaFiles = WriteJavaSourcesToDisk (result.GeneratedJavaSources); // Write manifest to disk if generated @@ -196,6 +216,27 @@ public override bool RunTask () return !Log.HasLoggedErrors; } + static bool IsFrameworkAssemblyItem (ITaskItem item) => + string.Equals (item.GetMetadata ("FrameworkAssembly"), bool.TrueString, StringComparison.OrdinalIgnoreCase) || + MonoAndroidHelper.IsFrameworkAssembly (item); + + void WriteGeneratedAssembliesListFile (IReadOnlyList assemblies) + { + if (GeneratedAssembliesListFile.IsNullOrEmpty ()) { + return; + } + + var directory = Path.GetDirectoryName (GeneratedAssembliesListFile); + if (!directory.IsNullOrEmpty ()) { + Directory.CreateDirectory (directory); + } + + var text = assemblies.Count == 0 + ? "" + : string.Join (Environment.NewLine, assemblies.Select (a => a.ItemSpec)) + Environment.NewLine; + Files.CopyIfStringChanged (text, GeneratedAssembliesListFile); + } + ITaskItem [] WriteAssembliesToDisk (IReadOnlyList assemblies, IReadOnlyList assemblyPaths) { // Build a map from assembly name -> source path for timestamp comparison diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs index 491ece9dd1b..b1ade49afd5 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs @@ -131,7 +131,6 @@ bool LinkForAbi (string abi) HashStyleBoth = true, LittleEndian = true, EntryPoint = "0x0", - CompressDebugSections = "zlib", }; if (!ExportsFile.IsNullOrEmpty ()) { diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs index 0521682daf6..96eced16660 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs @@ -146,7 +146,14 @@ public async override System.Threading.Tasks.Task RunTaskAsync () _ => null }; - if (repo is null && type.StartsWith ("http", StringComparison.OrdinalIgnoreCase)) { + if (repo is null && Uri.TryCreate (type, UriKind.Absolute, out var uri) && + (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)) { + if (uri.Scheme == Uri.UriSchemeHttp && + !string.Equals (item.GetMetadataOrDefault ("AllowInsecureHttp", "false"), "true", StringComparison.OrdinalIgnoreCase)) { + Log.LogCodedError ("XA4252", Properties.Resources.XA4252, type); + return null; + } + using var hasher = SHA256.Create (); var hash = hasher.ComputeHash (Encoding.UTF8.GetBytes (type)); var cache_name = Convert.ToBase64String (hash); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/MergeRemapXml.cs b/src/Xamarin.Android.Build.Tasks/Tasks/MergeRemapXml.cs index 29ba45a0905..67cb1ec4323 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/MergeRemapXml.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/MergeRemapXml.cs @@ -53,7 +53,7 @@ public override bool RunTask () void MergeInputFile (XmlWriter writer, string file) { if (!File.Exists (file)) { - Log.LogWarning ($"Specified input file `{file}` does not exist. Ignoring."); + Log.LogCodedWarning ("XA4316", Properties.Resources.XA4316, file); return; } var settings = new XmlReaderSettings { @@ -65,7 +65,7 @@ void MergeInputFile (XmlWriter writer, string file) return; } if (reader.LocalName != "replacements") { - Log.LogWarning ($"Input file `{file}` does not start with ``. Skipping."); + Log.LogCodedWarning ("XA4317", Properties.Resources.XA4317, file); return; } while (reader.Read ()) { @@ -76,7 +76,7 @@ void MergeInputFile (XmlWriter writer, string file) } } catch (Exception e) { - Log.LogWarning ($"Input file `{file}` could not be read: {e.Message} Skipping."); + Log.LogCodedWarning ("XA4318", Properties.Resources.XA4318, file, e.Message); Log.LogDebugMessage ($"Input file `{file}` could not be read: {e.ToString ()}"); } } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ProcessRuntimePackLibraryDirectories.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ProcessRuntimePackLibraryDirectories.cs index 142a0dccde6..c4d10e2f3f9 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ProcessRuntimePackLibraryDirectories.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ProcessRuntimePackLibraryDirectories.cs @@ -95,6 +95,7 @@ bool IsInSupportedRuntimePack (ITaskItem item) } return NuGetPackageId.StartsWith ("Microsoft.Android.Runtime.CoreCLR.", StringComparison.OrdinalIgnoreCase) || - NuGetPackageId.StartsWith ("Microsoft.Android.Runtime.Mono.", StringComparison.OrdinalIgnoreCase); + NuGetPackageId.StartsWith ("Microsoft.Android.Runtime.Mono.", StringComparison.OrdinalIgnoreCase) || + NuGetPackageId.StartsWith ("Microsoft.Android.Runtime.NativeAOT.", StringComparison.OrdinalIgnoreCase); } } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs index c20e0bc0973..7d07fecaa32 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs @@ -90,11 +90,17 @@ public override bool RunTask () AndroidApiLevel = GetMaxStableApiLevel ().ToString (); } + var androidSdk = MonoAndroidHelper.AndroidSdk; + if (androidSdk == null || AndroidSdkPath.IsNullOrEmpty ()) { + Log.LogCodedError ("XA5300", Properties.Resources.XA5300_Android_SDK); + return false; + } + string toolsZipAlignPath = Path.Combine (AndroidSdkPath, "tools", ZipAlign); bool findZipAlign = (ZipAlignPath.IsNullOrEmpty () || !Directory.Exists (ZipAlignPath)) && !File.Exists (toolsZipAlignPath); - var lintPaths = MonoAndroidHelper.AndroidSdk.GetCommandLineToolsPaths (CommandLineToolsVersion ?? "") - .SelectMany (p => new[]{ + var lintPaths = androidSdk.GetCommandLineToolsPaths (CommandLineToolsVersion ?? "") + .SelectMany (p => new [] { p, Path.Combine (p, "bin"), }); @@ -107,7 +113,7 @@ public override bool RunTask () } } - foreach (var dir in MonoAndroidHelper.AndroidSdk.GetBuildToolsPaths (AndroidSdkBuildToolsVersion ?? "")) { + foreach (var dir in androidSdk.GetBuildToolsPaths (AndroidSdkBuildToolsVersion ?? "")) { Log.LogDebugMessage ("Trying build-tools path: {0}", dir); if (dir == null || !Directory.Exists (dir)) continue; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/SetNdkPathForIlc.cs b/src/Xamarin.Android.Build.Tasks/Tasks/SetIlcToolchainPath.cs similarity index 57% rename from src/Xamarin.Android.Build.Tasks/Tasks/SetNdkPathForIlc.cs rename to src/Xamarin.Android.Build.Tasks/Tasks/SetIlcToolchainPath.cs index f295da3d0ec..c0474d09ce2 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/SetNdkPathForIlc.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/SetIlcToolchainPath.cs @@ -8,19 +8,20 @@ namespace Xamarin.Android.Tasks; /// -/// NativeAOT's compiler (ILC) expects to find tooling in $PATH +/// Adds the toolchain bin directory to $PATH so that NativeAOT's compiler (ILC) +/// can find tools like llvm-objcopy and llvm-ar. /// -public class SetNdkPathForIlc : AndroidTask +public class SetIlcToolchainPath : AndroidTask { public override string TaskPrefix => "SILC"; [Required] - public string NdkBinDirectory { get; set; } = ""; + public string ToolchainBinDirectory { get; set; } = ""; public override bool RunTask () { - var ndkbin = Path.GetFullPath (NdkBinDirectory); - var path = $"{ndkbin}{Path.PathSeparator}{Environment.GetEnvironmentVariable ("PATH")}"; + var binDir = Path.GetFullPath (ToolchainBinDirectory); + var path = $"{binDir}{Path.PathSeparator}{Environment.GetEnvironmentVariable ("PATH")}"; Log.LogDebugMessage ($"Setting $PATH to: {path}"); Environment.SetEnvironmentVariable ("PATH", path); return !Log.HasLoggedErrors; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ValidateJavaVersion.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ValidateJavaVersion.cs index a6bf13ce8e4..259eadc0dc5 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ValidateJavaVersion.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ValidateJavaVersion.cs @@ -110,6 +110,12 @@ protected virtual bool ValidateJava (string javaExe, Regex versionRegex) return version; } + if (JavaSdkPath.IsNullOrEmpty ()) { + Log.LogDebugMessage ("JavaSdkPath is not set, unable to locate Java tools."); + Log.LogCodedError ("XA5300", Properties.Resources.XA5300_Java_SDK); + return null; + } + // NOTE: this doesn't need to use GetRegisteredTaskObjectAssemblyLocal() // because the path to java/javac is the key and the value is a System.Version. var javaTool = Path.Combine (JavaSdkPath, "bin", javaExe); @@ -145,7 +151,7 @@ protected virtual bool ValidateJava (string javaExe, Regex versionRegex) bool GetVersionFromFile (string javaExe, out Version version) { var path = Path.Combine (Path.GetDirectoryName (javaExe), "..", "release"); - if (!File.Exists (path)) { + if (!File.Exists (path) && !JavaSdkPath.IsNullOrEmpty ()) { path = Path.Combine (JavaSdkPath, "release"); } if (!File.Exists (path)) { diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidDependenciesTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidDependenciesTests.cs index afff1ba0c0a..dd6e5d7c5c4 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidDependenciesTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidDependenciesTests.cs @@ -34,7 +34,7 @@ public void InstallAndroidDependenciesTest ([Values ("GoogleV2", "Xamarin")] str // Set to true when we are marking a new Android API level as stable, but it has not // been added to the Xamarin manifest yet. - var xamarin_manifest_needs_updating = true; + var xamarin_manifest_needs_updating = false; AssertCommercialBuild (); var oldSdkPath = Environment.GetEnvironmentVariable ("TEST_ANDROID_SDK_PATH"); @@ -167,14 +167,13 @@ public void GetDependencyNdkRequiredConditions (string property, bool ndkRequire Assert.Ignore ("CoreCLR doesn't support AOT, it doesn't ever require the NDK"); } - // NativeAOT doesn't support profiled AOT + // NativeAOT doesn't support profiled AOT or EnableLLVM (Mono concepts) if (runtime == AndroidRuntime.NativeAOT && property == "AndroidEnableProfiledAot") { Assert.Ignore ("NativeAOT doesn't support profiled AOT"); } - // NativeAOT always requires the NDK (PublishAot=true), so ndkRequired=false cases don't apply - if (runtime == AndroidRuntime.NativeAOT && !ndkRequired) { - Assert.Ignore ("NativeAOT always requires NDK via PublishAot=true"); + if (runtime == AndroidRuntime.NativeAOT && property == "EnableLLVM") { + Assert.Ignore ("EnableLLVM is not applicable to NativeAOT"); } var proj = new XamarinAndroidApplicationProject { @@ -200,12 +199,14 @@ public void GetDependencyNdkRequiredConditions (string property, bool ndkRequire } [Test] - public void NativeAotRequiresNdk () + public void NativeAotRequiresNdk_WhenWorkloadLinkerDisabled () { var proj = new XamarinAndroidApplicationProject { IsRelease = true, }; proj.SetRuntime (AndroidRuntime.NativeAOT); + proj.SetProperty ("_AndroidUseWorkloadNativeLinker", "false"); + proj.SetProperty ("_SkipNdkResolution", "false"); using (var builder = CreateApkBuilder ()) { builder.Verbosity = LoggerVerbosity.Detailed; builder.Target = "GetAndroidDependencies"; @@ -215,7 +216,7 @@ public void NativeAotRequiresNdk () .SkipWhile (x => !x.StartsWith ("Task \"CalculateProjectDependencies\"", StringComparison.Ordinal)) .SkipWhile (x => !x.StartsWith ("Output Item(s):", StringComparison.Ordinal)) .TakeWhile (x => !x.StartsWith ("Done executing task \"CalculateProjectDependencies\"", StringComparison.Ordinal)); - StringAssertEx.Contains ("ndk-bundle", taskOutput, "ndk-bundle should be a dependency for NativeAOT."); + StringAssertEx.Contains ("ndk-bundle", taskOutput, "ndk-bundle should be a dependency for NativeAOT without workload linker."); } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidGradleProjectTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidGradleProjectTests.cs index a1d13e8d1a0..58e0c2152ab 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidGradleProjectTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidGradleProjectTests.cs @@ -214,6 +214,7 @@ public void BuildIncremental ([Values] AndroidRuntime runtime) if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } + AssertCommercialBuild (); // Incremental build assertions require Fast Deployment var gradleProject = AndroidGradleProject.CreateDefault (GradleTestProjectDir); var gradleModule = gradleProject.Modules.First (); @@ -237,7 +238,7 @@ public void BuildIncremental ([Values] AndroidRuntime runtime) FileAssert.Exists (outputAar); var outputAarFirstWriteTime = File.GetLastWriteTime (outputAar); var packagedManifestContent = System.Text.Encoding.UTF8.GetString (ZipHelper.ReadFileFromZip (outputAar, "AndroidManifest.xml")); - StringAssert.Contains (@"uses-sdk android:minSdkVersion=""21""", packagedManifestContent); + StringAssert.Contains (@"uses-sdk android:minSdkVersion=""24""", packagedManifestContent); // Build again, _BuildAndroidGradleProjects should be skipped builder.BuildLogFile = "build2.log"; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs index f04aabcb3dd..fa01fb3d335 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs @@ -277,6 +277,7 @@ public void RepetiviteBuildUpdateSingleResource ([Values] AndroidRuntime runtime if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } + AssertCommercialBuild (); // Incremental build assertions require Fast Deployment var proj = new XamarinAndroidApplicationProject { IsRelease = isRelease, @@ -543,7 +544,9 @@ protected override void OnClick() Assert.IsFalse (StringAssertEx.ContainsText (b.LastBuildOutput, "AndroidResgen: Warning while updating Resource XML"), "Warning while processing resources should not have been raised."); Assert.IsTrue (b.Build (proj, doNotCleanupOnUpdate: true), "Build should have succeeded."); - Assert.IsTrue (b.Output.IsTargetSkipped ("_GenerateJavaStubs"), "Target _GenerateJavaStubs should have been skipped"); + if (TestEnvironment.CommercialBuildAvailable) { + Assert.IsTrue (b.Output.IsTargetSkipped ("_GenerateJavaStubs"), "Target _GenerateJavaStubs should have been skipped"); + } lib.Touch ("CustomTextView.cs"); @@ -1084,10 +1087,14 @@ public string GetFoo () { appBuilder.Output.AssertTargetIsNotSkipped ("_UpdateAndroidResgen"); foo.Timestamp = DateTimeOffset.UtcNow; Assert.IsTrue (libBuilder.Build (libProj, doNotCleanupOnUpdate: true, saveProject: false), "Library project should have built"); - libBuilder.Output.AssertTargetIsSkipped (target); + if (TestEnvironment.CommercialBuildAvailable) { + libBuilder.Output.AssertTargetIsSkipped (target); + } appBuilder.BuildLogFile = "build1.log"; Assert.IsTrue (appBuilder.Build (appProj, doNotCleanupOnUpdate: true, saveProject: false), "Application Build should have succeeded."); - appBuilder.Output.AssertTargetIsSkipped ("_UpdateAndroidResgen"); + if (TestEnvironment.CommercialBuildAvailable) { + appBuilder.Output.AssertTargetIsSkipped ("_UpdateAndroidResgen"); + } // Check Contents of the file in the apk are correct. string apk = Path.Combine (Root, appBuilder.ProjectDirectory, appProj.OutputPath, appProj.PackageName + "-Signed.apk"); byte[] rawContentBuildOne = ZipHelper.ReadFileFromZip (apk, @@ -1169,12 +1176,16 @@ public void BuildAppWithManagedResourceParser ([Values] AndroidRuntime runtime) appBuilder.BuildLogFile = "build.log"; Assert.IsTrue (appBuilder.Build (appProj, doNotCleanupOnUpdate: true), "Normal Application Build should have succeeded."); - Assert.IsTrue (appProj.CreateBuildOutput (appBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen", defaultIfNotUsed: true), - "Target '_ManagedUpdateAndroidResgen' should not have run."); + if (TestEnvironment.CommercialBuildAvailable) { + Assert.IsTrue (appProj.CreateBuildOutput (appBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen", defaultIfNotUsed: true), + "Target '_ManagedUpdateAndroidResgen' should not have run."); + } appBuilder.BuildLogFile = "designtimebuild.log"; Assert.IsTrue (appBuilder.DesignTimeBuild (appProj, doNotCleanupOnUpdate: true), "DesignTime Application Build should have succeeded."); - Assert.IsTrue (appProj.CreateBuildOutput (appBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen", defaultIfNotUsed: true), - "Target '_ManagedUpdateAndroidResgen' should not have run."); + if (TestEnvironment.CommercialBuildAvailable) { + Assert.IsTrue (appProj.CreateBuildOutput (appBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen", defaultIfNotUsed: true), + "Target '_ManagedUpdateAndroidResgen' should not have run."); + } Assert.IsTrue (appBuilder.Clean (appProj), "Clean should have succeeded"); Assert.IsTrue (File.Exists (designerFile), $"'{designerFile}' should not have been cleaned."); @@ -1451,12 +1462,14 @@ public void CustomViewAddResourceId ([Values] AndroidRuntime runtime) proj.Touch (@"Resources\layout\Main.axml"); Assert.IsTrue (b.Build (proj, doNotCleanupOnUpdate: true), "second build should have succeeded"); - Assert.IsTrue ( - b.Output.IsTargetPartiallyBuilt ("_CompileResources"), - "The target _CompileResources should have been partially built"); - Assert.IsTrue ( - b.Output.IsTargetSkipped ("_FixupCustomViewsForAapt2", defaultIfNotUsed: true), - "The target _FixupCustomViewsForAapt2 should have been skipped"); + if (TestEnvironment.CommercialBuildAvailable) { + Assert.IsTrue ( + b.Output.IsTargetPartiallyBuilt ("_CompileResources"), + "The target _CompileResources should have been partially built"); + Assert.IsTrue ( + b.Output.IsTargetSkipped ("_FixupCustomViewsForAapt2", defaultIfNotUsed: true), + "The target _FixupCustomViewsForAapt2 should have been skipped"); + } var r_java = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath, "android", "src", proj.PackageNameJavaIntermediatePath, "R.java"); FileAssert.Exists (r_java); @@ -1516,7 +1529,9 @@ BuildItem CreateItem (string include) => using (var b = CreateApkBuilder ()) { Assert.IsTrue (b.Build (proj), "first build should have succeeded."); Assert.IsTrue (b.Build (proj), "second build should have succeeded."); - b.Output.AssertTargetIsSkipped ("_CompileResources"); + if (TestEnvironment.CommercialBuildAvailable) { + b.Output.AssertTargetIsSkipped ("_CompileResources"); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AotTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AotTests.cs index c7da77626fd..83dc2d24605 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AotTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AotTests.cs @@ -239,12 +239,14 @@ public void BuildAotApplicationWithNdkAndBundleAndÜmläüts (string supportedAb } } Assert.IsTrue (b.Build (proj), "Second Build should have succeeded."); - Assert.IsTrue ( - b.Output.IsTargetSkipped ("_CompileJava"), - "the _CompileJava target should be skipped"); - Assert.IsTrue ( - b.Output.IsTargetSkipped ("_BuildApkEmbed"), - "the _BuildApkEmbed target should be skipped"); + if (TestEnvironment.CommercialBuildAvailable) { + Assert.IsTrue ( + b.Output.IsTargetSkipped ("_CompileJava"), + "the _CompileJava target should be skipped"); + Assert.IsTrue ( + b.Output.IsTargetSkipped ("_BuildApkEmbed"), + "the _BuildApkEmbed target should be skipped"); + } } } @@ -287,12 +289,14 @@ public void BuildAotApplicationAndÜmläüts (string supportedAbis, bool enableL } } Assert.IsTrue (b.Build (proj), "Second Build should have succeeded."); - Assert.IsTrue ( - b.Output.IsTargetSkipped ("_CompileJava"), - "the _CompileJava target should be skipped"); - Assert.IsTrue ( - b.Output.IsTargetSkipped ("_BuildApkEmbed"), - "the _BuildApkEmbed target should be skipped"); + if (TestEnvironment.CommercialBuildAvailable) { + Assert.IsTrue ( + b.Output.IsTargetSkipped ("_CompileJava"), + "the _CompileJava target should be skipped"); + Assert.IsTrue ( + b.Output.IsTargetSkipped ("_BuildApkEmbed"), + "the _BuildApkEmbed target should be skipped"); + } } } @@ -399,7 +403,7 @@ public void NoSymbolsArgShouldReduceAppSize ([Values (false, true)] bool skipDeb proj.SetRuntime (AndroidRuntime.MonoVM); var supportedAbi = "arm64-v8a"; - proj.SetAndroidSupportedAbis (supportedAbi); + proj.SetRuntimeIdentifiers (new[] { supportedAbi }); proj.SetProperty ("EnableLLVM", true.ToString ()); var xaAssemblySize = 0; @@ -440,7 +444,7 @@ public void AotAssembliesInIDE () }; // Mono-only test proj.SetRuntime (AndroidRuntime.MonoVM); - proj.SetAndroidSupportedAbis (supportedAbis); + proj.SetRuntimeIdentifiers (supportedAbis.Split (';')); using var b = CreateApkBuilder (); Assert.IsTrue (b.RunTarget (proj, target: "Build")); @@ -475,7 +479,7 @@ public void CheckWhetherLibcAndLibmAreReferencedInAOTLibraries () proj.SetProperty ("EnableLLVM", "True"); var abis = new [] { "arm64-v8a", "x86_64" }; - proj.SetAndroidSupportedAbis (abis); + proj.SetRuntimeIdentifiers (abis); var libPaths = new List (); libPaths.Add (Path.Combine ("android-arm64", "aot", "Mono.Android.dll.so")); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs index 5b1a3831c71..048d6814c0c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AssetPackTests.cs @@ -219,9 +219,11 @@ public void BuildApplicationWithAssetPack ([Values] bool isRelease, [Values] And Assert.IsFalse (zip.ContainsEntry ("assetpack1/resources.pb"), "aab should not contain assetpack1/resources.pb"); } Assert.IsTrue (appBuilder.Build (app, doNotCleanupOnUpdate: true, saveProject: false), $"{app.ProjectName} should succeed"); - appBuilder.Output.AssertTargetIsSkipped ("_CreateAssetPackManifests"); - appBuilder.Output.AssertTargetIsSkipped ("_BuildAssetPacks"); - appBuilder.Output.AssertTargetIsSkipped ("_GenerateAndroidAssetsDir"); + if (TestEnvironment.CommercialBuildAvailable) { + appBuilder.Output.AssertTargetIsSkipped ("_CreateAssetPackManifests"); + appBuilder.Output.AssertTargetIsSkipped ("_BuildAssetPacks"); + appBuilder.Output.AssertTargetIsSkipped ("_GenerateAndroidAssetsDir"); + } FileAssert.Exists (asset3File, $"file {asset3File} should exist."); asset3.TextContent = () => "Asset3 Updated"; asset3.Timestamp = DateTime.UtcNow.AddSeconds(1); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs index 50ff67da91b..6276cba7eb4 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs @@ -97,6 +97,7 @@ public void BindingLibraryIncremental (string classParser, AndroidRuntime runtim if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } + AssertCommercialBuild (); // Incremental build assertions require Fast Deployment var targets = new List { "_ExportJarToXml", @@ -883,8 +884,10 @@ public void BindingWithAndroidJavaSource ([Values] AndroidRuntime runtime) "generated", "src", "Com.Xamarin.Android.Test.Msbuildtest.IJavaSourceTestInterface.cs"); StringAssertEx.ContainsText (File.ReadAllLines (generatedIface), "string GreetWithQuestion (string name, global::Java.Util.Date date, string question);"); Assert.IsTrue (libBuilder.Build (lib), "Library build should have succeeded."); - Assert.IsTrue (libBuilder.Output.IsTargetSkipped ("_CompileBindingJava", defaultIfNotUsed: true), $"`_CompileBindingJava` should be skipped on second build!"); - Assert.IsTrue (libBuilder.Output.IsTargetSkipped ("_ClearGeneratedManagedBindings", defaultIfNotUsed: true), $"`_ClearGeneratedManagedBindings` should be skipped on second build!"); + if (TestEnvironment.CommercialBuildAvailable) { + Assert.IsTrue (libBuilder.Output.IsTargetSkipped ("_CompileBindingJava", defaultIfNotUsed: true), $"`_CompileBindingJava` should be skipped on second build!"); + Assert.IsTrue (libBuilder.Output.IsTargetSkipped ("_ClearGeneratedManagedBindings", defaultIfNotUsed: true), $"`_ClearGeneratedManagedBindings` should be skipped on second build!"); + } FileAssert.Exists (generatedCode, $"'{generatedCode}' should have not be deleted on second build."); Assert.IsTrue (libBuilder.DesignTimeBuild (lib, target: "UpdateGeneratedFiles"), "DTB should have succeeded."); FileAssert.Exists (generatedCode, $"'{generatedCode}' should have not be deleted on DTB build."); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs index d607be2c85e..0a715e7667f 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs @@ -38,7 +38,7 @@ public void DotNetBuild (string runtimeIdentifiers, bool isRelease, bool aot, bo "https://api.nuget.org/v3/index.json", }, PackageReferences = { - new Package { Id = "Xamarin.AndroidX.AppCompat", Version = "1.3.1.1" }, + new Package { Id = "Xamarin.AndroidX.AppCompat", Version = "1.7.1.3" }, // Using * here, so we explicitly get newer packages new Package { Id = "Microsoft.AspNetCore.Components.WebView", Version = "8.0.*" }, new Package { Id = "Microsoft.Extensions.FileProviders.Embedded", Version = "8.0.*" }, @@ -165,7 +165,7 @@ public void DotNetBuild (string runtimeIdentifiers, bool isRelease, bool aot, bo var manifest = XDocument.Load (manifestPath); XNamespace ns = "http://schemas.android.com/apk/res/android"; var uses_sdk = manifest.Root.Element ("uses-sdk"); - Assert.AreEqual ("21", uses_sdk.Attribute (ns + "minSdkVersion").Value); + Assert.AreEqual ("24", uses_sdk.Attribute (ns + "minSdkVersion").Value); Assert.AreEqual (XABuildConfig.AndroidDefaultTargetDotnetApiLevel.Major.ToString (), uses_sdk.Attribute (ns + "targetSdkVersion").Value); @@ -244,7 +244,7 @@ public void DotNetBuild (string runtimeIdentifiers, bool isRelease, bool aot, bo [TestCaseSource (nameof (MonoComponentMaskChecks))] public void CheckMonoComponentsMask (bool enableProfiler, bool useInterpreter, bool debugBuild, uint expectedMask) { - var proj = new XamarinFormsAndroidApplicationProject () { + var proj = new XamarinAndroidApplicationProject () { IsRelease = !debugBuild, }; @@ -254,7 +254,7 @@ public void CheckMonoComponentsMask (bool enableProfiler, bool useInterpreter, b proj.SetProperty (proj.ActiveConfigurationProperties, "UseInterpreter", useInterpreter.ToString ()); var abis = new [] { "armeabi-v7a", "x86" }; - proj.SetAndroidSupportedAbis (abis); + proj.SetRuntimeIdentifiers (abis); using (var b = CreateApkBuilder ()) { Assert.IsTrue (b.Build (proj), "Build should have succeeded."); @@ -370,7 +370,7 @@ public void CheckWhichRuntimeIsIncluded (string supportedAbi, bool debugSymbols, var proj = new XamarinAndroidApplicationProject (); // MonoVM-only test proj.SetRuntime (Android.Tasks.AndroidRuntime.MonoVM); - proj.SetAndroidSupportedAbis (supportedAbi); + proj.SetRuntimeIdentifiers (new[] { supportedAbi }); proj.SetProperty (proj.ActiveConfigurationProperties, "DebugSymbols", debugSymbols); if (optimize.HasValue) proj.SetProperty (proj.ActiveConfigurationProperties, "Optimize", optimize.Value); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs index 86366d2a73f..8a519e8e365 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs @@ -335,7 +335,7 @@ public void BuildReleaseArm64 ([Values] bool forms, [Values] AndroidRuntime runt proj.SetRuntime (runtime); proj.IsRelease = isRelease; proj.AotAssemblies = false; // Release defaults to Profiled AOT for .NET 6 - proj.SetAndroidSupportedAbis ("arm64-v8a"); + proj.SetRuntimeIdentifiers (new[] { "arm64-v8a" }); proj.SetProperty ("LinkerDumpDependencies", "True"); proj.SetProperty ("AndroidUseAssemblyStore", "False"); @@ -374,7 +374,6 @@ static IEnumerable Get_BuildHasNoWarningsData () foreach (AndroidRuntime runtime in Enum.GetValues (typeof (AndroidRuntime))) { AddTestData ( isRelease: false, - xamarinForms: false, multidex: false, packageFormat: "apk", runtime @@ -382,15 +381,6 @@ static IEnumerable Get_BuildHasNoWarningsData () AddTestData ( isRelease: false, - xamarinForms: true, - multidex: false, - packageFormat: "apk", - runtime - ); - - AddTestData ( - isRelease: false, - xamarinForms: true, multidex: true, packageFormat: "apk", runtime @@ -398,15 +388,6 @@ static IEnumerable Get_BuildHasNoWarningsData () AddTestData ( isRelease: true, - xamarinForms: false, - multidex: false, - packageFormat: "apk", - runtime - ); - - AddTestData ( - isRelease: true, - xamarinForms: true, multidex: false, packageFormat: "apk", runtime @@ -414,7 +395,6 @@ static IEnumerable Get_BuildHasNoWarningsData () AddTestData ( isRelease: false, - xamarinForms: false, multidex: false, packageFormat: "aab", runtime @@ -422,7 +402,6 @@ static IEnumerable Get_BuildHasNoWarningsData () AddTestData ( isRelease: true, - xamarinForms: false, multidex: false, packageFormat: "aab", runtime @@ -431,11 +410,10 @@ static IEnumerable Get_BuildHasNoWarningsData () return ret; - void AddTestData (bool isRelease, bool xamarinForms, bool multidex, string packageFormat, AndroidRuntime runtime) + void AddTestData (bool isRelease, bool multidex, string packageFormat, AndroidRuntime runtime) { ret.Add (new object[] { isRelease, - xamarinForms, multidex, packageFormat, runtime @@ -445,19 +423,17 @@ void AddTestData (bool isRelease, bool xamarinForms, bool multidex, string packa [Test] [TestCaseSource (nameof (Get_BuildHasNoWarningsData))] - public void BuildHasNoWarnings (bool isRelease, bool xamarinForms, bool multidex, string packageFormat, AndroidRuntime runtime) + public void BuildHasNoWarnings (bool isRelease, bool multidex, string packageFormat, AndroidRuntime runtime) { if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } - var proj = xamarinForms ? - new XamarinFormsAndroidApplicationProject () : - new XamarinAndroidApplicationProject (); + var proj = new XamarinAndroidApplicationProject (); proj.IsRelease = isRelease; proj.SetRuntime (runtime); // Enable full trimming - if (!xamarinForms && isRelease) { + if (isRelease) { proj.TrimModeRelease = TrimMode.Full; } if (multidex) { @@ -478,20 +454,11 @@ public void BuildHasNoWarnings (bool isRelease, bool xamarinForms, bool multidex Assert.IsTrue (b.Build (proj), "Build should have succeeded."); if (runtime == AndroidRuntime.NativeAOT) { - int numberOfExpectedWarnings; - bool validateWarnings; - if (xamarinForms && !multidex && packageFormat == "apk") { - // NativeAOT goes nuts here (Nov 2025) with 120 different ILC warnings, too many to verify them here in a way that makes sense - numberOfExpectedWarnings = 120; - validateWarnings = false; - } else { - // NativeAOT currently (Nov 2025) produces 6 `ILC : AOT analysis warning IL3050` warnings for various - // bits of code. Even though this test expects no warnings and the above likely make the app not work - // correctly at run time, it is still worth running this test under NativeAOT to test for the absence - // of other warnings. - numberOfExpectedWarnings = 6; - validateWarnings = true; - } + // NativeAOT currently (Nov 2025) produces 6 `ILC : AOT analysis warning IL3050` warnings for various + // bits of code. Even though this test expects no warnings and the above likely make the app not work + // correctly at run time, it is still worth running this test under NativeAOT to test for the absence + // of other warnings. + int numberOfExpectedWarnings = 6; Assert.IsTrue ( StringAssertEx.ContainsText ( @@ -501,12 +468,9 @@ public void BuildHasNoWarnings (bool isRelease, bool xamarinForms, bool multidex $"{b.BuildLogFile} should have exactly {numberOfExpectedWarnings} MSBuild warnings for NativeAOT." ); - if (validateWarnings) { - const string expectedWarningIL3050 = "ILC : AOT analysis warning IL3050:"; - var warnings = b.LastBuildOutput.SkipWhile (x => !x.StartsWith ("Build succeeded.", StringComparison.Ordinal)).Where (x => x.Contains (expectedWarningIL3050, StringComparison.Ordinal)); - Assert.IsTrue (warnings.Count () == numberOfExpectedWarnings, $"Expected {numberOfExpectedWarnings} 'IL3050' warnings, found {warnings.Count ()}"); - } - + const string expectedWarningIL3050 = "ILC : AOT analysis warning IL3050:"; + var warnings = b.LastBuildOutput.SkipWhile (x => !x.StartsWith ("Build succeeded.", StringComparison.Ordinal)).Where (x => x.Contains (expectedWarningIL3050, StringComparison.Ordinal)); + Assert.IsTrue (warnings.Count () == numberOfExpectedWarnings, $"Expected {numberOfExpectedWarnings} 'IL3050' warnings, found {warnings.Count ()}"); } else { b.AssertHasNoWarnings (); } @@ -850,7 +814,7 @@ public void BuildBasicApplicationAppCompat ([Values] bool publishAot, [Values] A IsRelease = isRelease, }; proj.SetRuntime (runtime); - proj.SetPublishAot (true, AndroidNdkPath); + proj.SetPublishAot (true); var packages = proj.PackageReferences; packages.Add (KnownPackages.AndroidXAppCompat); @@ -890,16 +854,14 @@ public void DuplicateRJavaOutput ([Values] AndroidRuntime runtime) [Test] [Category ("XamarinBuildDownload")] [NonParallelizable] // parallel NuGet restore causes failures - public void BuildXamarinFormsMapsApplication ([Values] bool multidex, [Values] AndroidRuntime runtime) + public void BuildXamarinFormsMapsApplication ([Values] bool multidex, [Values (AndroidRuntime.MonoVM, AndroidRuntime.CoreCLR)] AndroidRuntime runtime) { - bool isRelease = runtime == AndroidRuntime.NativeAOT; - if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + if (IgnoreUnsupportedConfiguration (runtime, release: false)) { return; } + AssertCommercialBuild (); // Incremental build assertions require Fast Deployment - var proj = new XamarinFormsMapsApplicationProject { - IsRelease = isRelease, - }; + var proj = new XamarinFormsMapsApplicationProject (); proj.SetRuntime (runtime); if (multidex) @@ -1197,17 +1159,19 @@ public CustomTextView(Context context, IAttributeSet attributes) : base(context, FileAssert.Exists (designtime_build_props, "designtime/build.props should exist after the second `Build`."); //NOTE: none of these targets should run, since we have not actually changed anything! - var targetsToBeSkipped = new [] { - //TODO: We would like for this assertion to work, but the item group changes between DTB and regular builds - // $(IntermediateOutputPath)designtime\Resource.designer.cs -> Resources\Resource.designer.cs - // And so the built assembly changes between DTB and regular build, triggering `_LinkAssembliesNoShrink` - //"_LinkAssembliesNoShrink", - "_UpdateAndroidResgen", - "_BuildLibraryImportsCache", - "_CompileJava", - }; - foreach (var targetName in targetsToBeSkipped) { - Assert.IsTrue (b.Output.IsTargetSkipped (targetName), $"`{targetName}` should be skipped!"); + if (TestEnvironment.CommercialBuildAvailable) { + var targetsToBeSkipped = new [] { + //TODO: We would like for this assertion to work, but the item group changes between DTB and regular builds + // $(IntermediateOutputPath)designtime\Resource.designer.cs -> Resources\Resource.designer.cs + // And so the built assembly changes between DTB and regular build, triggering `_LinkAssembliesNoShrink` + //"_LinkAssembliesNoShrink", + "_UpdateAndroidResgen", + "_BuildLibraryImportsCache", + "_CompileJava", + }; + foreach (var targetName in targetsToBeSkipped) { + Assert.IsTrue (b.Output.IsTargetSkipped (targetName), $"`{targetName}` should be skipped!"); + } } b.Target = "Clean"; @@ -1347,13 +1311,15 @@ public void CheckTimestamps ([Values] bool isRelease, [Values] AndroidRuntime ru //One last build with no changes Assert.IsTrue (b.Build (proj), "third build should have succeeded."); - // NativeAOT always runs the linking step - if (runtime != AndroidRuntime.NativeAOT) { - b.Output.AssertTargetIsSkipped (isRelease ? KnownTargets.LinkAssembliesShrink : KnownTargets.LinkAssembliesNoShrink); + if (TestEnvironment.CommercialBuildAvailable) { + // NativeAOT always runs the linking step + if (runtime != AndroidRuntime.NativeAOT) { + b.Output.AssertTargetIsSkipped (isRelease ? KnownTargets.LinkAssembliesShrink : KnownTargets.LinkAssembliesNoShrink); + } + b.Output.AssertTargetIsSkipped ("_UpdateAndroidResgen"); + b.Output.AssertTargetIsSkipped ("_BuildLibraryImportsCache"); + b.Output.AssertTargetIsSkipped ("_CompileJava"); } - b.Output.AssertTargetIsSkipped ("_UpdateAndroidResgen"); - b.Output.AssertTargetIsSkipped ("_BuildLibraryImportsCache"); - b.Output.AssertTargetIsSkipped ("_CompileJava"); } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Emulator.csproj b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Emulator.csproj index 1962bfdcd1a..c93eb78a648 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Emulator.csproj +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Emulator.csproj @@ -4,5 +4,8 @@ false + + + \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/EnvironmentContentTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/EnvironmentContentTests.cs index 1f5d51ef909..7616d8d0412 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/EnvironmentContentTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/EnvironmentContentTests.cs @@ -57,7 +57,7 @@ public void BuildApplicationWithMonoEnvironment ([Values ("", "Normal", "Offline string linkSkip = "FormsViewGroup"; app.SetProperty ("AndroidLinkSkip", linkSkip); app.SetProperty ("_AndroidSequencePointsMode", sequencePointsMode); - app.SetAndroidSupportedAbis (supportedAbis); + app.SetRuntimeIdentifiers (supportedAbis.Split (';')); using (var libb = CreateDllBuilder (Path.Combine ("temp", TestName, lib.ProjectName))) using (var appb = CreateApkBuilder (Path.Combine ("temp", TestName, app.ProjectName))) { Assert.IsTrue (libb.Build (lib), "Library build should have succeeded."); @@ -105,7 +105,7 @@ public void CheckMonoDebugIsAddedToEnvironment ([Values ("", "Normal", "Offline" // Mono-only test proj.SetRuntime (AndroidRuntime.MonoVM); proj.SetProperty ("_AndroidSequencePointsMode", sequencePointsMode); - proj.SetAndroidSupportedAbis (supportedAbis); + proj.SetRuntimeIdentifiers (supportedAbis.Split (';')); using (var b = CreateApkBuilder ()) { Assert.IsTrue (b.Build (proj), "Build should have succeeded."); @@ -140,7 +140,7 @@ public void CheckConcurrentGC () var supportedAbis = "armeabi-v7a;arm64-v8a"; // MonoVM-only test proj.SetRuntime (Android.Tasks.AndroidRuntime.MonoVM); - proj.SetAndroidSupportedAbis (supportedAbis); + proj.SetRuntimeIdentifiers (supportedAbis.Split (';')); using (var b = CreateApkBuilder ()) { proj.SetProperty ("AndroidEnableSGenConcurrent", "False"); @@ -160,82 +160,5 @@ public void CheckConcurrentGC () Assert.AreEqual (expectedUpdatedValue, envvars[gcVarName], $"'{gcVarName}' should have been '{expectedUpdatedValue}' when concurrent GC is enabled."); } } - - [Test] - public void CheckForInvalidHttpClientHandlerType ([Values] AndroidRuntime runtime) - { - const bool isRelease = true; - if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { - return; - } - var proj = new XamarinAndroidApplicationProject () { - IsRelease = isRelease, - }; - proj.SetRuntime (runtime); - using (var b = CreateApkBuilder ()) { - b.ThrowOnBuildFailure = false; - proj.SetProperty ("AndroidHttpClientHandlerType", "Android.App.Application"); - Assert.IsFalse (b.Build (proj), "Build should not have succeeded."); - Assert.IsTrue (StringAssertEx.ContainsText (b.LastBuildOutput, "XA1031"), "Output should contain XA1031"); - } - } - - [Test] - public void CheckHttpClientHandlerType ([Values] AndroidRuntime runtime) - { - bool isRelease = runtime == AndroidRuntime.NativeAOT; - if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { - return; - } - var proj = new XamarinAndroidApplicationProject () { - IsRelease = isRelease, - }; - var httpClientHandlerVarName = "XA_HTTP_CLIENT_HANDLER_TYPE"; - var expectedDefaultValue = "System.Net.Http.SocketsHttpHandler, System.Net.Http"; - var expectedUpdatedValue = "Xamarin.Android.Net.AndroidMessageHandler"; - - string supportedAbis = runtime switch { - AndroidRuntime.MonoVM => "armeabi-v7a;arm64-v8a", - AndroidRuntime.CoreCLR => "arm64-v8a;x86_64", - AndroidRuntime.NativeAOT => "arm64-v8a;x86_64", - _ => throw new NotSupportedException ($"Unsupported runtime '{runtime}'") - }; - proj.SetRuntime (runtime); - proj.SetAndroidSupportedAbis (supportedAbis); - proj.PackageReferences.Add (new Package() { Id = "System.Net.Http", Version = "*" }); - proj.MainActivity = proj.DefaultMainActivity.Replace ("//${AFTER_ONCREATE}", "var _ = new System.Net.Http.HttpClient ();"); - - using (var b = CreateApkBuilder ()) { - proj.SetProperty ("AndroidHttpClientHandlerType", expectedDefaultValue); - Assert.IsTrue (b.Build (proj), "Build should have succeeded."); - var intermediateOutputDir = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath); - - List? envFiles = null; - Dictionary envvars; - - if (runtime == AndroidRuntime.NativeAOT) { - envvars = EnvironmentHelper.ReadNativeAotEnvironmentVariables (intermediateOutputDir); - } else { - envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, supportedAbis, true, runtime); - envvars = EnvironmentHelper.ReadEnvironmentVariables (envFiles, runtime); - } - - Assert.IsTrue (envvars.ContainsKey (httpClientHandlerVarName), $"Environment should contain '{httpClientHandlerVarName}'."); - Assert.AreEqual (expectedDefaultValue, envvars[httpClientHandlerVarName]); - - proj.SetProperty ("AndroidHttpClientHandlerType", expectedUpdatedValue); - Assert.IsTrue (b.Build (proj), "Second build should have succeeded."); - - if (runtime == AndroidRuntime.NativeAOT) { - envvars = EnvironmentHelper.ReadNativeAotEnvironmentVariables (intermediateOutputDir); - } else { - envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, supportedAbis, true, runtime); - envvars = EnvironmentHelper.ReadEnvironmentVariables (envFiles, runtime); - } - - Assert.IsTrue (envvars.ContainsKey (httpClientHandlerVarName), $"Environment should contain '{httpClientHandlerVarName}'."); - Assert.AreEqual (expectedUpdatedValue, envvars[httpClientHandlerVarName]); - } - } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs index a6ba2b98710..c13c7e400ca 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs @@ -22,6 +22,7 @@ public void BasicApplicationRepetitiveBuild ([Values] AndroidRuntime runtime) if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } + AssertCommercialBuild (); // Incremental build assertions require Fast Deployment var proj = new XamarinAndroidApplicationProject { IsRelease = isRelease, }; @@ -68,6 +69,7 @@ public void BasicApplicationRepetitiveReleaseBuild ([Values] AndroidRuntime runt if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } + AssertCommercialBuild (); // Incremental build assertions require Fast Deployment if (runtime != AndroidRuntime.MonoVM) { // temporarily Assert.Ignore ("Runtimes other than MonoVM are currently broken here."); @@ -102,6 +104,81 @@ public class Foo { } } + [Test] + public void JniRemappingCountsSurviveIncrementalBuild () + { + const AndroidRuntime runtime = AndroidRuntime.CoreCLR; + const bool isRelease = true; + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + + var proj = new XamarinAndroidApplicationProject { + IsRelease = isRelease, + OtherBuildItems = { + new AndroidItem._AndroidRemapMembers ("Remap.xml") { + Encoding = Encoding.UTF8, + TextContent = () => """ + + + + +""", + }, + }, + }; + proj.SetRuntime (runtime); + + using (var b = CreateApkBuilder ()) { + Assert.IsTrue (b.Build (proj), "first build failed"); + AssertJniRemappingCounts (proj, b, expectedTypeCount: 1, expectedMethodCount: 1); + var remapSourceTimestamps = GetJniRemappingSourceTimestamps (proj, b); + + proj.MainActivity += Environment.NewLine + "// Force an incremental C# rebuild."; + proj.Touch ("MainActivity.cs"); + Assert.IsTrue (b.Build (proj, doNotCleanupOnUpdate: true, saveProject: false), "second build failed"); + AssertJniRemappingCounts (proj, b, expectedTypeCount: 1, expectedMethodCount: 1); + AssertJniRemappingSourceTimestamps (remapSourceTimestamps); + } + } + + void AssertJniRemappingCounts (XamarinAndroidApplicationProject proj, ProjectBuilder builder, uint expectedTypeCount, uint expectedMethodCount) + { + string objDirPath = Path.Combine (Root, builder.ProjectDirectory, proj.IntermediateOutputPath); + var envFiles = EnvironmentHelper.GatherEnvironmentFiles (objDirPath, "arm64-v8a;x86_64", required: true, runtime: AndroidRuntime.CoreCLR); + var appConfig = (EnvironmentHelper.ApplicationConfig_CoreCLR) EnvironmentHelper.ReadApplicationConfig (envFiles, AndroidRuntime.CoreCLR); + Assert.AreEqual (expectedTypeCount, appConfig.jni_remapping_replacement_type_count, "jni_remapping_replacement_type_count should be preserved."); + Assert.AreEqual (expectedMethodCount, appConfig.jni_remapping_replacement_method_index_entry_count, "jni_remapping_replacement_method_index_entry_count should be preserved."); + } + + Dictionary GetJniRemappingSourceTimestamps (XamarinAndroidApplicationProject proj, ProjectBuilder builder) + { + string objDirPath = Path.Combine (Root, builder.ProjectDirectory, proj.IntermediateOutputPath, "android"); + var timestamps = new Dictionary (StringComparer.Ordinal); + foreach (string abi in new [] { "arm64-v8a", "x86_64" }) { + string path = Path.Combine (objDirPath, $"jni_remap.{abi}.ll"); + FileAssert.Exists (path); + timestamps.Add (path, File.GetLastWriteTimeUtc (path)); + } + return timestamps; + } + + void AssertJniRemappingSourceTimestamps (Dictionary expectedTimestamps) + { + foreach (var expectedTimestamp in expectedTimestamps) { + Assert.AreEqual ( + expectedTimestamp.Value, + File.GetLastWriteTimeUtc (expectedTimestamp.Key), + $"{expectedTimestamp.Key} should not be touched when regenerated with unchanged contents." + ); + } + } + [Test] public void CheckNothingIsDeletedByIncrementalClean ([Values] bool enableMultiDex, [Values] AndroidRuntime runtime) { @@ -117,7 +194,7 @@ public void CheckNothingIsDeletedByIncrementalClean ([Values] bool enableMultiDe } var path = Path.Combine ("temp", TestName); - var proj = new XamarinFormsAndroidApplicationProject () { + var proj = new XamarinAndroidApplicationProject () { ProjectName = "App1", IsRelease = isRelease, }; @@ -352,6 +429,7 @@ public void JavacTaskDoesNotRunOnSecondBuild ([Values] AndroidRuntime runtime) if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } + AssertCommercialBuild (); // Incremental build assertions require Fast Deployment var app = new XamarinAndroidApplicationProject () { IsRelease = isRelease, @@ -487,7 +565,7 @@ public Class2 () if (runtime == AndroidRuntime.MonoVM) { // Using `SetRuntimeIdentifier` would change the intermediate path (by adding the RID component to it) and, thus, the way this test used to work. // Keep it as it was. - app.SetAndroidSupportedAbis (abi); + app.SetRuntimeIdentifiers (new[] { abi }); } else { app.SetRuntimeIdentifier (abi); } @@ -532,6 +610,7 @@ public void AppProjectTargetsDoNotBreak ([Values] AndroidRuntime runtime) if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } + AssertCommercialBuild (); // Incremental build assertions require Fast Deployment var targets = new List<(string target, bool ignoreOnNAOT)> { ("_GeneratePackageManagerJava", true), // TODO: NativeAOT doesn't skip this target on 3rd attempt, check if that's ok? @@ -670,6 +749,7 @@ public void ManifestMergerIncremental ([Values] AndroidRuntime runtime) if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } + AssertCommercialBuild (); // Incremental build assertions require Fast Deployment var proj = new XamarinAndroidApplicationProject { IsRelease = isRelease, @@ -746,16 +826,18 @@ public void ProduceReferenceAssembly ([Values] AndroidRuntime runtime) Assert.IsTrue (libBuilder.Build (lib, doNotCleanupOnUpdate: true, saveProject: false), "second library build should have succeeded."); Assert.IsTrue (appBuilder.Build (app, doNotCleanupOnUpdate: true, saveProject: false), "second app build should have succeeded."); - appBuilder.Output.AssertTargetIsSkipped ("CoreCompile"); - appBuilder.Output.AssertTargetIsSkipped ("_BuildLibraryImportsCache"); - appBuilder.Output.AssertTargetIsSkipped ("_ResolveLibraryProjectImports"); - appBuilder.Output.AssertTargetIsSkipped ("_GenerateJavaStubs"); + if (TestEnvironment.CommercialBuildAvailable) { + appBuilder.Output.AssertTargetIsSkipped ("CoreCompile"); + appBuilder.Output.AssertTargetIsSkipped ("_BuildLibraryImportsCache"); + appBuilder.Output.AssertTargetIsSkipped ("_ResolveLibraryProjectImports"); + appBuilder.Output.AssertTargetIsSkipped ("_GenerateJavaStubs"); - appBuilder.Output.AssertTargetIsPartiallyBuilt (KnownTargets.LinkAssembliesNoShrink); + appBuilder.Output.AssertTargetIsPartiallyBuilt (KnownTargets.LinkAssembliesNoShrink); - appBuilder.Output.AssertTargetIsNotSkipped ("_BuildApkEmbed"); - appBuilder.Output.AssertTargetIsNotSkipped ("_CopyPackage"); - appBuilder.Output.AssertTargetIsNotSkipped ("_Sign"); + appBuilder.Output.AssertTargetIsNotSkipped ("_BuildApkEmbed"); + appBuilder.Output.AssertTargetIsNotSkipped ("_CopyPackage"); + appBuilder.Output.AssertTargetIsNotSkipped ("_Sign"); + } } } @@ -848,6 +930,7 @@ public void LinkAssembliesNoShrink ([Values] AndroidRuntime runtime) if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } + AssertCommercialBuild (); // Incremental build assertions require Fast Deployment var proj = new XamarinFormsAndroidApplicationProject { IsRelease = isRelease, }; @@ -990,13 +1073,15 @@ public CustomTextView(Context context, IAttributeSet attributes) : base(context, appBuilder.BuildLogFile = "build2.log"; Assert.IsTrue (appBuilder.Build (app, doNotCleanupOnUpdate: true, saveProject: false), "second app build should have succeeded."); - var targetsShouldSkip = new [] { - "_BuildLibraryImportsCache", - "_ResolveLibraryProjectImports", - "_ConvertCustomView", - }; - foreach (var target in targetsShouldSkip) { - Assert.IsTrue (appBuilder.Output.IsTargetSkipped (target), $"`{target}` should be skipped!"); + if (TestEnvironment.CommercialBuildAvailable) { + var targetsShouldSkip = new [] { + "_BuildLibraryImportsCache", + "_ResolveLibraryProjectImports", + "_ConvertCustomView", + }; + foreach (var target in targetsShouldSkip) { + Assert.IsTrue (appBuilder.Output.IsTargetSkipped (target), $"`{target}` should be skipped!"); + } } var targetsShouldRun = new [] { @@ -1007,16 +1092,18 @@ public CustomTextView(Context context, IAttributeSet attributes) : base(context, "_CopyPackage", "_Sign", }; - foreach (var target in targetsShouldRun) { - Assert.IsFalse (appBuilder.Output.IsTargetSkipped (target), $"`{target}` should *not* be skipped!"); - } + if (TestEnvironment.CommercialBuildAvailable) { + foreach (var target in targetsShouldRun) { + Assert.IsFalse (appBuilder.Output.IsTargetSkipped (target), $"`{target}` should *not* be skipped!"); + } - var aapt2TargetsShouldBeSkipped = new [] { - "_FixupCustomViewsForAapt2", - "_CompileResources" - }; - foreach (var target in aapt2TargetsShouldBeSkipped) { - Assert.IsTrue (appBuilder.Output.IsTargetSkipped (target, defaultIfNotUsed: true), $"{target} should be skipped!"); + var aapt2TargetsShouldBeSkipped = new [] { + "_FixupCustomViewsForAapt2", + "_CompileResources" + }; + foreach (var target in aapt2TargetsShouldBeSkipped) { + Assert.IsTrue (appBuilder.Output.IsTargetSkipped (target, defaultIfNotUsed: true), $"{target} should be skipped!"); + } } } } @@ -1028,6 +1115,7 @@ public void ResolveLibraryProjectImports ([Values] AndroidRuntime runtime) if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } + AssertCommercialBuild (); // Incremental build assertions require Fast Deployment var proj = new XamarinFormsAndroidApplicationProject { IsRelease = isRelease, @@ -1233,6 +1321,7 @@ public void GenerateJavaStubsAndAssembly ([Values] bool isRelease, [Values] Andr if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } + AssertCommercialBuild (); // Incremental build assertions require Fast Deployment // TODO: NativeAOT build doesn't add android/environment.arm64-v8a.o to file writes if (runtime == AndroidRuntime.NativeAOT) { @@ -1257,7 +1346,7 @@ public void GenerateJavaStubsAndAssembly ([Values] bool isRelease, [Values] Andr if (runtime == AndroidRuntime.MonoVM) { // Using `SetRuntimeIdentifier` would change the intermediate path (by adding the RID component to it) and, thus, the way this test used to work. // Keep it as it was. - proj.SetAndroidSupportedAbis (abi); + proj.SetRuntimeIdentifiers (new[] { abi }); } else { proj.SetRuntimeIdentifier (abi); } @@ -1381,7 +1470,7 @@ public void BuildIncrementalAot (string supportedAbis, string androidAotMode, bo if (aotAssemblies) { targets.Add ("_AndroidAot"); } - proj.SetAndroidSupportedAbis (supportedAbis); + proj.SetRuntimeIdentifiers (supportedAbis.Split (';')); if (!string.IsNullOrEmpty (androidAotMode)) proj.SetProperty ("AndroidAotMode", androidAotMode); using (var b = CreateApkBuilder (path)) { @@ -1669,6 +1758,7 @@ public void AndroidResourceChange ([Values] AndroidRuntime runtime) if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } + AssertCommercialBuild (); // Incremental build assertions require Fast Deployment var proj = new XamarinAndroidApplicationProject { IsRelease = isRelease, }; @@ -1802,7 +1892,7 @@ public void ChangeSupportedAbis ([Values] AndroidRuntime runtime) _ => throw new NotSupportedException ($"Unsupported runtime '{runtime}'") }; - proj.SetAndroidSupportedAbis (supportedAbi); + proj.SetRuntimeIdentifiers (new[] { supportedAbi }); using (var b = CreateApkBuilder ()) { b.Build (proj); b.Build (proj, parameters: new [] { $"{KnownProperties.RuntimeIdentifier}=android-{alternativeRid}" }, doNotCleanupOnUpdate: true); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ManifestTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ManifestTest.cs index 4e3a93f3b2f..a129a988691 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ManifestTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ManifestTest.cs @@ -220,6 +220,7 @@ public void OverlayManifestIncrementalBuildTest ([Values] AndroidRuntime runtime if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } + AssertCommercialBuild (); // Incremental build assertions require Fast Deployment var proj = new XamarinAndroidApplicationProject () { IsRelease = isRelease, ManifestMerger = "manifestmerger.jar", @@ -536,7 +537,7 @@ public void DirectBootAwareAttribute ([Values] AndroidRuntime runtime) /* pattern */ "{abi}{minSDK:00}{versionCode:000}", /* props */ null, /* shouldBuild */ true, - /* expected */ "221012;321012", + /* expected */ "224012;324012", }, new object[] { /* seperateApk */ true, @@ -546,7 +547,7 @@ public void DirectBootAwareAttribute ([Values] AndroidRuntime runtime) /* pattern */ "{abi}{minSDK:00}{screen}{versionCode:000}", /* props */ "screen=24", /* shouldBuild */ true, - /* expected */ "22124012;32124012", + /* expected */ "22424012;32424012", }, new object[] { /* seperateApk */ true, @@ -556,7 +557,7 @@ public void DirectBootAwareAttribute ([Values] AndroidRuntime runtime) /* pattern */ "{abi}{minSDK:00}{screen}{foo:0}{versionCode:000}", /* props */ "screen=24;foo=$(Foo)", /* shouldBuild */ true, - /* expected */ "221241012;321241012", + /* expected */ "224241012;324241012", }, new object[] { /* seperateApk */ true, @@ -566,7 +567,7 @@ public void DirectBootAwareAttribute ([Values] AndroidRuntime runtime) /* pattern */ "{abi}{minSDK:00}{screen}{foo:00}{versionCode:000}", /* props */ "screen=24;foo=$(Foo)", /* shouldBuild */ false, - /* expected */ "2212401012;3212401012", + /* expected */ "2242401012;3242401012", }, }; @@ -577,8 +578,8 @@ public void VersionCodeTests (bool seperateApk, string abis, string versionCode, { var proj = new XamarinAndroidApplicationProject () { IsRelease = true, - MinSdkVersion = "21", - SupportedOSPlatformVersion = "21.0", + MinSdkVersion = "24", + SupportedOSPlatformVersion = "24.0", }; // MonoVM-only test, for now (changing anything in the test data changes the codes, each case must be @@ -588,7 +589,7 @@ public void VersionCodeTests (bool seperateApk, string abis, string versionCode, proj.SetProperty ("GenerateApplicationManifest", "false"); // Disable $(AndroidVersionCode) support proj.SetProperty (proj.ReleaseProperties, KnownProperties.AndroidCreatePackagePerAbi, seperateApk); if (!string.IsNullOrEmpty (abis)) - proj.SetAndroidSupportedAbis (abis); + proj.SetRuntimeIdentifiers (abis.Split (';')); if (!string.IsNullOrEmpty (versionCodePattern)) proj.SetProperty (proj.ReleaseProperties, "AndroidVersionCodePattern", versionCodePattern); else @@ -1238,7 +1239,7 @@ public void ExportedErrorMessage ([Values] AndroidRuntime runtime) /* removeUsesSdk */ false, }, new object[] { - /* minSdkVersion */ "21.0", + /* minSdkVersion */ "24.0", /* removeUsesSdk */ true, }, new object[] { @@ -1269,7 +1270,7 @@ static IEnumerable Get_SupportedOSTestSources_Data () ); AddTestData ( - minSdkVersion: "21.0", + minSdkVersion: "24.0", removeUsesSdkElement: true, runtime: runtime ); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs new file mode 100644 index 00000000000..1c176bec323 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs @@ -0,0 +1,65 @@ +using NUnit.Framework; +using Xamarin.Android.Tasks; +using Xamarin.ProjectTools; + +namespace Xamarin.Android.Build.Tests +{ + /// + /// Build tests specific to the NativeAOT runtime. + /// + [TestFixture] + [Category ("Node-2")] + public class NativeAotBuildTests : BaseTest + { + [Test] + public void BuildNativeAot_WithoutNdk () + { + var proj = new XamarinAndroidApplicationProject { + IsRelease = true, + }; + proj.SetRuntime (AndroidRuntime.NativeAOT); + + using var builder = CreateApkBuilder (); + Assert.IsTrue ( + builder.Build (proj), + "Build should succeed without NDK (workload linker is the default)." + ); + } + + [Test] + public void BuildNativeAot_WithNdkLinker () + { + var proj = new XamarinAndroidApplicationProject { + IsRelease = true, + }; + proj.SetRuntime (AndroidRuntime.NativeAOT); + proj.SetProperty ("_SkipNdkResolution", "false"); + + using var builder = CreateApkBuilder (); + Assert.IsTrue ( + builder.Build (proj, parameters: new [] { + "_AndroidUseWorkloadNativeLinker=false", + }), + "Build should succeed with NDK linker." + ); + } + + [Test] + public void BuildNativeAot_WithoutNdk_WorkloadLinkerDisabled_Fails () + { + var proj = new XamarinAndroidApplicationProject { + IsRelease = true, + }; + proj.SetRuntime (AndroidRuntime.NativeAOT); + + using var builder = CreateApkBuilder (); + builder.ThrowOnBuildFailure = false; + Assert.IsFalse ( + builder.Build (proj, parameters: new [] { + "_AndroidUseWorkloadNativeLinker=false", + }), + "Build should fail without NDK when workload linker is disabled." + ); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs index 563e987ee41..dd599912590 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs @@ -1,15 +1,15 @@ using System; +using System.Collections.Generic; using System.IO; -using NUnit.Framework; -using Xamarin.ProjectTools; using System.Linq; using System.Text; -using System.Collections.Generic; using System.Xml.Linq; -using Xamarin.Tools.Zip; +using Microsoft.Build.Framework; +using NUnit.Framework; using Xamarin.Android.Tasks; using Xamarin.Android.Tools; -using Microsoft.Build.Framework; +using Xamarin.ProjectTools; +using Xamarin.Tools.Zip; namespace Xamarin.Android.Build.Tests { @@ -252,7 +252,7 @@ public void CheckIncludedNativeLibraries ([Values] bool compressNativeLibraries, }; proj.SetRuntime (runtime); proj.PackageReferences.Add(KnownPackages.SQLitePCLRaw_Core); - proj.SetAndroidSupportedAbis ("x86_64"); + proj.SetRuntimeIdentifiers (new[] { "x86_64" }); proj.SetProperty (proj.ReleaseProperties, "AndroidStoreUncompressedFileExtensions", compressNativeLibraries ? "" : "so"); using (var b = CreateApkBuilder ()) { b.ThrowOnBuildFailure = false; @@ -468,6 +468,49 @@ public void CheckMetadataSkipItemsAreProcessedCorrectly ([Values] AndroidRuntime } } + [Test] + [NonParallelizable] + public void MonoAndroidExportIsNotPackagedWithTrimmableTypeMap () + { + const AndroidRuntime runtime = AndroidRuntime.CoreCLR; + const bool isRelease = false; + + var proj = new XamarinAndroidApplicationProject { + IsRelease = isRelease, + References = { + new BuildItem.Reference ("Mono.Android.Export"), + }, + }; + proj.SetRuntime (runtime); + proj.SetProperty ("_AndroidTypeMapImplementation", "trimmable"); + proj.Sources.Add (new BuildItem.Source ("ContainsExportedMethods.cs") { + TextContent = () => @"using System; +using Java.Interop; + +namespace UnnamedProject { + class ContainsExportedMethods : Java.Lang.Object { + [Export] + public void Exported () + { + Console.WriteLine (""# ExportedCallbackInvoked""); + } + } +}" + }); + + using (var b = CreateApkBuilder ()) { + Assert.IsTrue (b.Build (proj), "build failed"); + + var apk = Path.Combine (Root, b.ProjectDirectory, proj.OutputPath, $"{proj.PackageName}-Signed.apk"); + var helper = new ArchiveAssemblyHelper (apk, useAssemblyStores: true); + var contents = helper.ListArchiveContents (); + + Assert.IsFalse ( + contents.Any (e => Path.GetFileName (e).Equals ("Mono.Android.Export.dll", StringComparison.Ordinal)), + $"APK file `{apk}` should not contain Mono.Android.Export.dll when the trimmable type map is enabled."); + } + } + [Test] public void CheckSignApk ([Values] bool useApkSigner, [Values] bool perAbiApk, [Values] AndroidRuntime runtime) { @@ -513,7 +556,7 @@ public void CheckSignApk ([Values] bool useApkSigner, [Values] bool perAbiApk, [ proj.SetProperty (proj.ReleaseProperties, KnownProperties.AndroidCreatePackagePerAbi, perAbiApk); if (perAbiApk) { if (runtime == AndroidRuntime.MonoVM) { - proj.SetAndroidSupportedAbis ("armeabi-v7a", "x86", "arm64-v8a", "x86_64"); + proj.SetRuntimeIdentifiers (new[] { "armeabi-v7a", "x86", "arm64-v8a", "x86_64" }); } else { proj.SetRuntimeIdentifiers (AndroidTargetArch.Arm64, AndroidTargetArch.X86_64); } @@ -531,9 +574,7 @@ public void CheckSignApk ([Values] bool useApkSigner, [Values] bool perAbiApk, [ //Make sure the APKs are signed foreach (var apk in Directory.GetFiles (bin, "*-Signed.apk")) { - using (var zip = ZipHelper.OpenZip (apk)) { - Assert.IsTrue (zip.Any (e => e.FullName == "META-INF/MANIFEST.MF"), $"APK file `{apk}` is not signed! It is missing `META-INF/MANIFEST.MF`."); - } + AssertApkIsSigned (apk); } // Make sure the APKs have unique version codes @@ -568,9 +609,7 @@ public void CheckSignApk ([Values] bool useApkSigner, [Values] bool perAbiApk, [ //Make sure the APKs are signed foreach (var apk in Directory.GetFiles (bin, "*-Signed.apk")) { - using (var zip = ZipHelper.OpenZip (apk)) { - Assert.IsTrue (zip.Any (e => e.FullName == "META-INF/MANIFEST.MF"), $"APK file `{apk}` is not signed! It is missing `META-INF/MANIFEST.MF`."); - } + AssertApkIsSigned (apk); } } @@ -613,8 +652,10 @@ public void CheckAppBundle ([Values] bool isRelease, [Values] AndroidRuntime run // Build with no changes Assert.IsTrue (b.Build (proj), "second build should have succeeded."); - foreach (var target in new [] { "_Sign", "_BuildApkEmbed" }) { - Assert.IsTrue (b.Output.IsTargetSkipped (target), $"`{target}` should be skipped!"); + if (TestEnvironment.CommercialBuildAvailable) { + foreach (var target in new [] { "_Sign", "_BuildApkEmbed" }) { + Assert.IsTrue (b.Output.IsTargetSkipped (target), $"`{target}` should be skipped!"); + } } } } @@ -702,7 +743,7 @@ public void MissingSatelliteAssemblyInApp ([Values] bool publishAot, [Values] An } } }; - proj.SetPublishAot (publishAot, AndroidNdkPath); + proj.SetPublishAot (publishAot); using (var b = CreateApkBuilder ()) { b.Verbosity = LoggerVerbosity.Diagnostic; // Needed for --satellite switch to appear in the log @@ -1010,7 +1051,7 @@ public void ExtractNativeLibsTrue ([Values] AndroidRuntime runtime) var proj = new XamarinAndroidApplicationProject { IsRelease = isRelease, // This combination produces android:extractNativeLibs="false" by default - SupportedOSPlatformVersion = "23", + SupportedOSPlatformVersion = "24", ManifestMerger = "manifestmerger.jar", }; proj.SetRuntime (runtime); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/CheckClientHandlerTypeTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/CheckClientHandlerTypeTests.cs deleted file mode 100644 index 87616d10078..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/CheckClientHandlerTypeTests.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using NUnit.Framework; -using Xamarin.Android.Tasks; -using Xamarin.Android.Tools; -using Xamarin.ProjectTools; -using Microsoft.Android.Build.Tasks; - -namespace Xamarin.Android.Build.Tests -{ - public class CheckClientHandlerTypeTests : BaseTest - { - static IEnumerable Get_ErrorIsNotRaised_Data () - { - var ret = new List (); - - foreach (AndroidRuntime runtime in Enum.GetValues (typeof (AndroidRuntime))) { - AddTestData ("Xamarin.Android.Net.AndroidMessageHandler", runtime); - AddTestData ("System.Net.Http.SocketsHttpHandler, System.Net.Http", runtime); - } - - return ret; - - void AddTestData (string handler, AndroidRuntime runtime) - { - ret.Add (new object[] { - handler, - runtime, - }); - } - } - - [Test] - [TestCaseSource (nameof (Get_ErrorIsNotRaised_Data))] - public void ErrorIsNotRaised (string handler, AndroidRuntime runtime) - { - const bool isRelease = false; - if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { - return; - } - - string path = Path.Combine (Root, "temp", TestName); - Directory.CreateDirectory (path); - string intermediatePath; - bool shouldSkip = handler.Contains ("Xamarin.Android.Net.AndroidMessageHandler"); - bool targetSkipped; - var proj = new XamarinAndroidApplicationProject () { - IsRelease = isRelease, - }; - proj.SetRuntime (runtime); - proj.SetProperty ("AndroidHttpClientHandlerType", handler); - using (var b = CreateApkBuilder (path)) { - b.Verbosity = LoggerVerbosity.Detailed; - b.Build (proj); - intermediatePath = Path.Combine (path,proj.IntermediateOutputPath); - targetSkipped = b.Output.IsTargetSkipped ("_CheckAndroidHttpClientHandlerType", defaultIfNotUsed: shouldSkip); - } - - if (shouldSkip) - Assert.IsTrue (targetSkipped, "_CheckAndroidHttpClientHandlerType should not have run."); - else - Assert.IsFalse (targetSkipped, "_CheckAndroidHttpClientHandlerType should have run."); - - string asmPath = Path.GetFullPath (Path.Combine (intermediatePath, "android", "assets")); - var errors = new List (); - var warnings = new List (); - List assemblies = new List (); - string[] files = Directory.GetFiles (asmPath, "*.dll", SearchOption.AllDirectories); - foreach (var file in files) - assemblies.Add (new TaskItem (file)); - IBuildEngine4 engine = new MockBuildEngine (System.Console.Out, errors, warnings); - var task = new CheckClientHandlerType () { - BuildEngine = engine, - ClientHandlerType = handler, - ResolvedAssemblies = assemblies.ToArray (), - }; - Assert.True (task.Execute (), $"task should have succeeded. {string.Join (";", errors.Select (x => x.Message))}"); - } - - static IEnumerable Get_ErrorIsRaised_Data () - { - var ret = new List (); - - foreach (AndroidRuntime runtime in Enum.GetValues (typeof (AndroidRuntime))) { - AddTestData ("Xamarin.Android.Net.AndroidClientHandler", runtime); - } - - return ret; - - void AddTestData (string handler, AndroidRuntime runtime) - { - ret.Add (new object[] { - handler, - runtime, - }); - } - } - - [Test] - [TestCaseSource (nameof (Get_ErrorIsRaised_Data))] - public void ErrorIsRaised (string handler, AndroidRuntime runtime) - { - const bool isRelease = false; - if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { - return; - } - - var path = Path.Combine (Root, "temp", TestName); - Directory.CreateDirectory (path); - string intermediatePath; - var proj = new XamarinAndroidApplicationProject () { - IsRelease = isRelease, - }; - proj.SetRuntime (runtime); - proj.PackageReferences.Add (new Package() { Id = "System.Net.Http", Version = "*" }); - using (var b = CreateApkBuilder ()) { - b.ThrowOnBuildFailure = false; - b.Build (proj); - intermediatePath = Path.Combine (path, proj.IntermediateOutputPath); - } - string asmPath = Path.Combine (intermediatePath, "android", "assets"); - var errors = new List (); - var warnings = new List (); - List assemblies = new List (); - string[] files = Directory.GetFiles (asmPath, "*.dll", SearchOption.AllDirectories); - foreach (var file in files) - assemblies.Add (new TaskItem (file)); - IBuildEngine4 engine = new MockBuildEngine (System.Console.Out, errors, warnings); - var task = new CheckClientHandlerType () { - BuildEngine = engine, - ClientHandlerType = handler, - ResolvedAssemblies = assemblies.ToArray (), - }; - Assert.False (task.Execute (), $"task should have failed."); - Assert.AreEqual (1, errors.Count, $"One error should have been raised. {string.Join (" ", errors.Select (e => e.Message))}"); - Assert.AreEqual ("XA1031", errors [0].Code, "Error code should have been XA1031."); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/EnvironmentFilesParserTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/EnvironmentFilesParserTests.cs new file mode 100644 index 00000000000..b36a863fe44 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/EnvironmentFilesParserTests.cs @@ -0,0 +1,199 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using NUnit.Framework; +using Xamarin.Android.Tasks; + +namespace Xamarin.Android.Build.Tests +{ + [TestFixture] + [Parallelizable (ParallelScope.Self)] + public class EnvironmentFilesParserTests : BaseTest + { + string? tempDirectory; + + [SetUp] + public void Setup () + { + tempDirectory = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ()); + Directory.CreateDirectory (tempDirectory); + } + + [TearDown] + public void TearDown () + { + if (tempDirectory != null && Directory.Exists (tempDirectory)) + Directory.Delete (tempDirectory, recursive: true); + } + + ITaskItem CreateEnvFile (string content) + { + var path = Path.Combine (tempDirectory ?? Path.GetTempPath (), Path.GetRandomFileName () + ".env"); + File.WriteAllText (path, content); + return new TaskItem (path); + } + + [Test] + public void DetectsMonoLogLevel () + { + var envFile = CreateEnvFile ("MONO_LOG_LEVEL=debug"); + var parser = new EnvironmentFilesParser (); + var engine = new MockBuildEngine (TestContext.Out); + var log = new TaskLoggingHelper (engine, "Test"); + + parser.Parse (new [] { envFile }, SequencePointsMode.None, log); + + Assert.IsTrue (parser.HaveLogLevel, "HaveLogLevel should be true"); + } + + [Test] + public void DetectsMonoGCParams () + { + var envFile = CreateEnvFile ("MONO_GC_PARAMS=nursery-size=64m"); + var parser = new EnvironmentFilesParser (); + var engine = new MockBuildEngine (TestContext.Out); + var log = new TaskLoggingHelper (engine, "Test"); + + parser.Parse (new [] { envFile }, SequencePointsMode.None, log); + + Assert.IsTrue (parser.HaveMonoGCParams, "HaveMonoGCParams should be true"); + } + + [Test] + public void MonoGCParams_OldBridgeWarning () + { + var envFile = CreateEnvFile ("MONO_GC_PARAMS=bridge-implementation=old"); + var warnings = new List (); + var engine = new MockBuildEngine (TestContext.Out, warnings: warnings); + var log = new TaskLoggingHelper (engine, "Test"); + var parser = new EnvironmentFilesParser (); + + parser.Parse (new [] { envFile }, SequencePointsMode.None, log); + + Assert.IsTrue (parser.HaveMonoGCParams, "HaveMonoGCParams should be true"); + Assert.AreEqual (1, warnings.Count, "Expected one warning"); + Assert.AreEqual ("XA2000", warnings [0].Code); + } + + [Test] + public void DetectsMonoDebug () + { + var envFile = CreateEnvFile ("MONO_DEBUG=soft-breakpoints"); + var parser = new EnvironmentFilesParser (); + var engine = new MockBuildEngine (TestContext.Out); + var log = new TaskLoggingHelper (engine, "Test"); + + parser.Parse (new [] { envFile }, SequencePointsMode.None, log); + + Assert.IsTrue (parser.HaveMonoDebug, "HaveMonoDebug should be true"); + } + + [Test] + public void MonoDebug_AppendsSequencePoints () + { + var envFile = CreateEnvFile ("MONO_DEBUG=soft-breakpoints"); + var parser = new EnvironmentFilesParser (); + var engine = new MockBuildEngine (TestContext.Out); + var log = new TaskLoggingHelper (engine, "Test"); + + parser.Parse (new [] { envFile }, SequencePointsMode.Normal, log); + + Assert.IsTrue (parser.HaveMonoDebug, "HaveMonoDebug should be true"); + var monoDebugLine = parser.EnvironmentVariableLines.FirstOrDefault (l => l.StartsWith ("MONO_DEBUG=")); + Assert.IsNotNull (monoDebugLine, "Should have a MONO_DEBUG line"); + StringAssert.Contains ("gen-compact-seq-points", monoDebugLine); + } + + [Test] + public void MonoDebug_DoesNotDuplicateSequencePoints () + { + var envFile = CreateEnvFile ("MONO_DEBUG=soft-breakpoints,gen-compact-seq-points"); + var parser = new EnvironmentFilesParser (); + var engine = new MockBuildEngine (TestContext.Out); + var log = new TaskLoggingHelper (engine, "Test"); + + parser.Parse (new [] { envFile }, SequencePointsMode.Normal, log); + + Assert.IsTrue (parser.HaveMonoDebug, "HaveMonoDebug should be true"); + var monoDebugLine = parser.EnvironmentVariableLines.FirstOrDefault (l => l.StartsWith ("MONO_DEBUG=")); + Assert.IsNotNull (monoDebugLine, "Should have a MONO_DEBUG line"); + // Should not duplicate the gen-compact-seq-points entry + Assert.AreEqual ("MONO_DEBUG=soft-breakpoints,gen-compact-seq-points", monoDebugLine); + } + + [Test] + public void AssemblyPreload_SetsFlag_AndExcludesLine () + { + var envFile = CreateEnvFile ("mono.enable_assembly_preload=1"); + var parser = new EnvironmentFilesParser (); + var engine = new MockBuildEngine (TestContext.Out); + var log = new TaskLoggingHelper (engine, "Test"); + + parser.Parse (new [] { envFile }, SequencePointsMode.None, log); + + Assert.IsFalse (parser.EnvironmentVariableLines.Any (l => l.Contains ("mono.enable_assembly_preload")), + "mono.enable_assembly_preload should be excluded from EnvironmentVariableLines"); + } + + [Test] + public void BrokenExceptionTransitions_SetsFlag_AndExcludesLine () + { + var envFile = CreateEnvFile ("XA_BROKEN_EXCEPTION_TRANSITIONS=true"); + var parser = new EnvironmentFilesParser (); + var engine = new MockBuildEngine (TestContext.Out); + var log = new TaskLoggingHelper (engine, "Test"); + + parser.Parse (new [] { envFile }, SequencePointsMode.None, log); + + Assert.IsTrue (parser.BrokenExceptionTransitions, "BrokenExceptionTransitions should be true"); + Assert.IsFalse (parser.EnvironmentVariableLines.Any (l => l.Contains ("XA_BROKEN_EXCEPTION_TRANSITIONS")), + "XA_BROKEN_EXCEPTION_TRANSITIONS should be excluded from EnvironmentVariableLines"); + } + + [Test] + public void AreBrokenExceptionTransitionsEnabled_ReturnsTrue () + { + var envFile = CreateEnvFile ("XA_BROKEN_EXCEPTION_TRANSITIONS=true"); + var parser = new EnvironmentFilesParser (); + + Assert.IsTrue (parser.AreBrokenExceptionTransitionsEnabled (new [] { envFile })); + } + + [Test] + public void AreBrokenExceptionTransitionsEnabled_ReturnsFalse () + { + var envFile = CreateEnvFile ("MONO_LOG_LEVEL=debug"); + var parser = new EnvironmentFilesParser (); + + Assert.IsFalse (parser.AreBrokenExceptionTransitionsEnabled (new [] { envFile })); + } + + [Test] + public void Parse_NullEnvironments_DoesNotThrow () + { + var parser = new EnvironmentFilesParser (); + var engine = new MockBuildEngine (TestContext.Out); + var log = new TaskLoggingHelper (engine, "Test"); + + Assert.DoesNotThrow (() => parser.Parse (null, SequencePointsMode.None, log)); + } + + [Test] + public void Parse_MultipleFiles_AccumulatesFlags () + { + var envFile1 = CreateEnvFile ("MONO_LOG_LEVEL=debug"); + var envFile2 = CreateEnvFile ("MONO_GC_PARAMS=nursery-size=64m"); + var parser = new EnvironmentFilesParser (); + var engine = new MockBuildEngine (TestContext.Out); + var log = new TaskLoggingHelper (engine, "Test"); + + parser.Parse (new [] { envFile1, envFile2 }, SequencePointsMode.None, log); + + Assert.IsTrue (parser.HaveLogLevel, "HaveLogLevel should be true"); + Assert.IsTrue (parser.HaveMonoGCParams, "HaveMonoGCParams should be true"); + Assert.AreEqual (2, parser.EnvironmentVariableLines.Count, "Should have 2 environment variable lines"); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GeneratePackageManagerJavaTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GeneratePackageManagerJavaTests.cs index 9591b26a7e8..3d9e9cbfd1c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GeneratePackageManagerJavaTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GeneratePackageManagerJavaTests.cs @@ -1,15 +1,15 @@ #nullable disable -using NUnit.Framework; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using Xamarin.Android.Tasks; +using Microsoft.Android.Build.Tasks; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -using Microsoft.Android.Build.Tasks; +using NUnit.Framework; +using Xamarin.Android.Tasks; using Xamarin.ProjectTools; namespace Xamarin.Android.Build.Tests @@ -104,5 +104,40 @@ public void CheckPackageManagerAssemblyOrder (string[] resolvedUserAssemblies, s txt = File.ReadAllText (Path.Combine (path, "env", "environment.x86.ll")); StringAssert.Contains ("XXXX", txt, "environment.x86.ll should contain 'XXXX'"); } + + [Test] + public void GenerateNativeApplicationConfigSkipsAssembliesExcludedFromPackage () + { + var path = Path.Combine (Root, "temp", nameof (GenerateNativeApplicationConfigSkipsAssembliesExcludedFromPackage)); + Directory.CreateDirectory (path); + + File.WriteAllText (Path.Combine (path, "myenv.txt"), @"MYENV=ZZZZ"); + + var metadata = new Dictionary (StringComparer.OrdinalIgnoreCase) { + { "Abi", "arm64-v8a" }, + }; + var skipped = new Dictionary (metadata, StringComparer.OrdinalIgnoreCase) { + { "AndroidSkipAddToPackage", "true" }, + }; + + var configTask = new GenerateNativeApplicationConfigSources { + BuildEngine = new MockBuildEngine (TestContext.Out), + ResolvedAssemblies = [ + new TaskItem ("linked/HelloAndroid.dll", metadata), + new TaskItem ("linked/Mono.Android.Export.dll", skipped), + ], + EnvironmentOutputDirectory = Path.Combine (path, "env"), + SupportedAbis = ["arm64-v8a"], + AndroidPackageName = "com.microsoft.net6.helloandroid", + EnablePreloadAssembliesDefault = false, + Environments = [new TaskItem (Path.Combine (path, "myenv.txt"))], + }; + + Assert.IsTrue (configTask.Execute (), "GenerateNativeApplicationConfigSources task should have executed."); + + var txt = File.ReadAllText (Path.Combine (path, "env", "environment.arm64-v8a.ll")); + StringAssert.Contains ("ZZZZ", txt, "environment.arm64-v8a.ll should contain the custom environment value."); + StringAssert.DoesNotContain ("Mono.Android.Export.dll", txt, "environment.arm64-v8a.ll should not list assemblies excluded from packaging."); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.cs index f5954e7e77d..b93d314e922 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.cs @@ -110,6 +110,71 @@ public void Execute_SecondRun_OutputsAreUpToDate () "Typemap assembly should NOT be rewritten when content hasn't changed."); } + [Test] + public void Execute_WritesGeneratedAssembliesListFile () + { + var path = Path.Combine ("temp", TestName); + var outputDir = Path.Combine (Root, path, "typemap"); + var javaDir = Path.Combine (Root, path, "java"); + var listFile = Path.Combine (outputDir, "typemap-assemblies.txt"); + var staleAssembly = Path.Combine (outputDir, "_Stale.TypeMap.dll"); + + var monoAndroidItem = FindMonoAndroidDll (); + if (monoAndroidItem is null) { + Assert.Ignore ("Mono.Android.dll not found; skipping."); + return; + } + + Directory.CreateDirectory (outputDir); + File.WriteAllText (staleAssembly, "stale"); + + var task = CreateTask (new [] { monoAndroidItem }, outputDir, javaDir); + task.GeneratedAssembliesListFile = listFile; + + Assert.IsTrue (task.Execute (), "Task should succeed."); + + var generatedAssemblies = task.GeneratedAssemblies.Select (i => i.ItemSpec).ToArray (); + var listedAssemblies = File.ReadAllLines (listFile); + CollectionAssert.AreEqual (generatedAssemblies, listedAssemblies); + CollectionAssert.DoesNotContain (listedAssemblies, staleAssembly); + } + + [Test] + public void Execute_GeneratesFrameworkJcws () + { + var path = Path.Combine ("temp", TestName); + var outputDir = Path.Combine (Root, path, "typemap"); + var javaDir = Path.Combine (Root, path, "java"); + + var monoAndroidItem = FindMonoAndroidDll (); + if (monoAndroidItem is null) { + Assert.Ignore ("Mono.Android.dll not found; skipping."); + return; + } + + var task = CreateTask (new [] { monoAndroidItem }, outputDir, javaDir); + task.ResolvedFrameworkAssemblies = new [] { monoAndroidItem }; + + Assert.IsTrue (task.Execute (), "Task should succeed."); + + var generatedJavaFiles = task.GeneratedJavaFiles.Select (i => i.ItemSpec).ToArray (); + CollectionAssert.Contains ( + generatedJavaFiles, + Path.Combine (javaDir, "android/runtime/JavaProxyThrowable.java")); + CollectionAssert.Contains ( + generatedJavaFiles, + Path.Combine (javaDir, "xamarin/android/net/ServerCertificateCustomValidator_TrustManager.java")); + CollectionAssert.Contains ( + generatedJavaFiles, + Path.Combine (javaDir, "xamarin/android/net/ServerCertificateCustomValidator_TrustManager_FakeSSLSession.java")); + CollectionAssert.Contains ( + generatedJavaFiles, + Path.Combine (javaDir, "xamarin/android/net/ServerCertificateCustomValidator_AlwaysAcceptingHostnameVerifier.java")); + CollectionAssert.DoesNotContain ( + generatedJavaFiles, + Path.Combine (javaDir, "android/app/Activity.java")); + } + [TestCase ("v11.0")] [TestCase ("v10.0")] [TestCase ("11.0")] @@ -159,7 +224,7 @@ public void Execute_ManifestPlaceholdersAreResolvedForRooting () task.ApplicationRegistrationOutputFile = applicationRegistration; task.PackageName = "android.app"; task.AndroidApiLevel = "35"; - task.SupportedOSPlatformVersion = "21"; + task.SupportedOSPlatformVersion = "24"; task.RuntimeProviderJavaName = "mono.MonoRuntimeProvider"; task.ManifestPlaceholders = "applicationId=android.app"; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs index ca87f1367bc..1dc50e6de9c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs @@ -161,79 +161,6 @@ static AssemblyDefinition CreateFauxMonoAndroidAssembly () return assm; } - private void PreserveCustomHttpClientHandler ( - string handlerType, - string handlerAssembly, - string testProjectName, - string assemblyPath, - TrimMode trimMode, - AndroidRuntime runtime) - { - const bool isRelease = true; - if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { - return; - } - testProjectName += trimMode.ToString (); - - var class_library = new XamarinAndroidLibraryProject { - IsRelease = isRelease, - ProjectName = "MyClassLibrary", - Sources = { - new BuildItem.Source ("MyCustomHandler.cs") { - TextContent = () => """ - class MyCustomHandler : System.Net.Http.HttpMessageHandler - { - protected override Task SendAsync (HttpRequestMessage request, CancellationToken cancellationToken) => - throw new NotImplementedException (); - } - """ - }, - new BuildItem.Source ("Bar.cs") { - TextContent = () => "public class Bar { }", - } - } - }; - class_library.SetRuntime (runtime); - using (var libBuilder = CreateDllBuilder ($"{testProjectName}/{class_library.ProjectName}")) { - Assert.IsTrue (libBuilder.Build (class_library), $"Build for {class_library.ProjectName} should have succeeded."); - } - - var proj = new XamarinAndroidApplicationProject { - ProjectName = "MyApp", - IsRelease = isRelease, - TrimModeRelease = trimMode, - Sources = { - new BuildItem.Source ("Foo.cs") { - TextContent = () => "public class Foo : Bar { }", - } - } - }; - proj.SetRuntime (runtime); - proj.AddReference (class_library); - proj.AddReferences ("System.Net.Http"); - string handlerTypeFullName = string.IsNullOrEmpty(handlerAssembly) ? handlerType : handlerType + ", " + handlerAssembly; - proj.SetProperty (proj.ActiveConfigurationProperties, "AndroidHttpClientHandlerType", handlerTypeFullName); - proj.MainActivity = proj.DefaultMainActivity.Replace ("base.OnCreate (bundle);", "base.OnCreate (bundle);\nvar client = new System.Net.Http.HttpClient ();"); - using (var b = CreateApkBuilder ($"{testProjectName}/{proj.ProjectName}")) { - Assert.IsTrue (b.Build (proj), "Build should have succeeded."); - - using (var assembly = AssemblyDefinition.ReadAssembly (Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath, assemblyPath))) { - Assert.IsTrue (assembly.MainModule.GetType (handlerType) != null, $"'{handlerTypeFullName}' should have been preserved by the linker."); - } - } - } - - [Test] - public void PreserveCustomHttpClientHandlers ([Values (TrimMode.Partial, TrimMode.Full)] TrimMode trimMode, [Values] AndroidRuntime runtime) - { - PreserveCustomHttpClientHandler ("Xamarin.Android.Net.AndroidMessageHandler", "", - $"temp/PreserveAndroidMessageHandler{trimMode}{runtime}", "android-arm64/linked/Mono.Android.dll", trimMode, runtime); - PreserveCustomHttpClientHandler ("System.Net.Http.SocketsHttpHandler", "System.Net.Http", - $"temp/PreserveSocketsHttpHandler{trimMode}{runtime}", "android-arm64/linked/System.Net.Http.dll", trimMode, runtime); - PreserveCustomHttpClientHandler ("MyCustomHandler", "MyClassLibrary", - $"temp/MyCustomHandler{trimMode}{runtime}", "android-arm64/linked/MyClassLibrary.dll", trimMode, runtime); - } - [Test] public void WarnAboutAppDomains ([Values] bool isRelease, [Values] AndroidRuntime runtime) { @@ -259,7 +186,7 @@ public void WarnAboutAppDomains ([Values] bool isRelease, [Values] AndroidRuntim var app = new XamarinAndroidApplicationProject { IsRelease = isRelease }; app.SetRuntime (runtime); - app.SetAndroidSupportedAbis ("arm64-v8a"); + app.SetRuntimeIdentifiers (new[] { "arm64-v8a" }); app.AddReference (lib); using var libBuilder = CreateDllBuilder (Path.Combine (path, lib.ProjectName)); Assert.IsTrue (libBuilder.Build (lib), "library build should have succeeded."); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTests.cs index 55d81bb69c3..5f17e684cd6 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/MavenDownloadTests.cs @@ -72,6 +72,41 @@ public async Task UnknownRepository () Assert.AreEqual ("Unknown Maven repository: 'bad-repo'.", engine.Errors [0].Message); } + [Test] + public async Task InsecureHttpRepository_Blocked () + { + var engine = new MockBuildEngine (TestContext.Out, new List ()); + var task = new MavenDownload { + BuildEngine = engine, + AndroidMavenLibraries = [CreateMavenTaskItem ("com.google.android.material:material", "1.0.0", "http://repo.example.com/maven2/")], + }; + + await task.RunTaskAsync (); + + Assert.AreEqual (1, engine.Errors.Count); + Assert.AreEqual ("Insecure HTTP Maven repository URL 'http://repo.example.com/maven2/' is not allowed. Use an HTTPS URL, or set AllowInsecureHttp=\"true\" metadata on the item to override this check.", engine.Errors [0].Message); + } + + [Test] + public async Task InsecureHttpRepository_AllowedWithOptIn () + { + var engine = new MockBuildEngine (TestContext.Out, new List ()); + var item = CreateMavenTaskItem ("com.example:dummy", "1.0.0", "http://repo.example.com/maven2/"); + item.SetMetadata ("AllowInsecureHttp", "true"); + + var task = new MavenDownload { + BuildEngine = engine, + MavenCacheDirectory = Path.GetTempPath (), + AndroidMavenLibraries = [item], + }; + + await task.RunTaskAsync (); + + // Should bypass the XA4252 insecure HTTP check and attempt the download, which fails with XA4236 + Assert.AreEqual (1, engine.Errors.Count); + Assert.AreEqual ("XA4236", engine.Errors [0].Code, "Expected a download error (XA4236), not a security error (XA4252)"); + } + [Test] public async Task UnknownArtifact () { diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ResolveSdksTaskTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ResolveSdksTaskTests.cs index f9bf21dc4ac..23533f76703 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ResolveSdksTaskTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ResolveSdksTaskTests.cs @@ -219,5 +219,36 @@ public void ResolveSdkTiming () Directory.Delete (Path.Combine (Root, path), recursive: true); } + [Test] + public void ResolveAndroidToolingWithMissingResolvedSdkDoesNotThrow () + { + var path = Path.Combine (Path.GetTempPath (), "xa-tests", nameof (ResolveAndroidToolingWithMissingResolvedSdkDoesNotThrow), Guid.NewGuid ().ToString ("N")); + Directory.CreateDirectory (path); + var errors = new List (); + var originalAndroidSdk = MonoAndroidHelper.AndroidSdk; + try { + MonoAndroidHelper.AndroidSdk = null; + IBuildEngine engine = new MockBuildEngine (TestContext.Out, errors); + var androidTooling = new Xamarin.Android.Tasks.ResolveAndroidTooling { + BuildEngine = engine, + AndroidSdkPath = path, + AndroidSdkBuildToolsVersion = "36.0.0", + TargetPlatformVersion = "37.0", + AotAssemblies = false, + SequencePointsMode = "None", + AndroidApplication = true, + }; + + Assert.IsFalse (androidTooling.Execute (), "ResolveAndroidTooling should fail when SDK resolution has not succeeded."); + Assert.AreEqual ("37", androidTooling.AndroidApiLevel, "AndroidApiLevel should still be calculated for dependency resolution."); + Assert.IsTrue (errors.Any (e => e.Code == "XA5300"), "XA5300 should be logged when Android SDK tooling is unavailable."); + Assert.IsFalse (errors.Any (e => e.Code == "XARAT7001"), "ResolveAndroidTooling should not throw when AndroidSdk is unavailable."); + } finally { + MonoAndroidHelper.AndroidSdk = originalAndroidSdk; + if (Directory.Exists (path)) + Directory.Delete (path, recursive: true); + } + } + } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ValidateJavaVersionTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ValidateJavaVersionTests.cs index be3496c6b24..06e043b08b4 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ValidateJavaVersionTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ValidateJavaVersionTests.cs @@ -200,5 +200,40 @@ public void ReleaseFile_Invalid () Assert.IsTrue (validateJavaVersion.Execute (), "Execute should succeed!"); Assert.IsTrue (messages.Any (m => m.Message.Contains ("did not contain a valid JAVA_VERSION")), "valid JAVA_VERSION should *not* be found"); } + + [Test] + public void NullJavaSdkPathDoesNotThrow () + { + var validateJavaVersion = new Xamarin.Android.Tasks.ValidateJavaVersion { + BuildEngine = engine, + JavaSdkPath = null, + JavaToolExe = "java", + JavacToolExe = "javac", + LatestSupportedJavaVersion = "17.99.0", + MinimumSupportedJavaVersion = "11.0.0", + UseJavaExeVersion = true, + }; + + Assert.IsFalse (validateJavaVersion.Execute (), "Execute should fail when JavaSdkPath is null."); + Assert.IsFalse (errors.Any (e => e.Code == "XAVJV7001"), "ValidateJavaVersion should not throw when JavaSdkPath is null."); + Assert.IsTrue (messages.Any (m => m.Message.Contains ("JavaSdkPath is not set")), "Should log debug message about missing JavaSdkPath."); + } + + [Test] + public void NullJavaSdkPathWithReleaseFileDoesNotThrow () + { + var validateJavaVersion = new Xamarin.Android.Tasks.ValidateJavaVersion { + BuildEngine = engine, + JavaSdkPath = null, + JavaToolExe = "java", + JavacToolExe = "javac", + LatestSupportedJavaVersion = "17.99.0", + MinimumSupportedJavaVersion = "11.0.0", + UseJavaExeVersion = false, + }; + + Assert.IsFalse (validateJavaVersion.Execute (), "Execute should fail when JavaSdkPath is null."); + Assert.IsFalse (errors.Any (e => e.Code == "XAVJV7001"), "ValidateJavaVersion should not throw when JavaSdkPath is null (release file path)."); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs index 8e91d242d0a..93195ece435 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -36,6 +38,7 @@ public void Build_WithTrimmableTypeMap_IncrementalBuild ([Values] bool isRelease if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } + AssertCommercialBuild (); // Incremental build assertions require Fast Deployment var proj = new XamarinAndroidApplicationProject { IsRelease = isRelease, @@ -56,6 +59,32 @@ public void Build_WithTrimmableTypeMap_IncrementalBuild ([Values] bool isRelease "_GenerateJavaStubs should be skipped on incremental build."); } + [Test] + public void Build_WithTrimmableTypeMap_ArrayRankChangeRegeneratesTypeMap () + { + if (IgnoreUnsupportedConfiguration (AndroidRuntime.CoreCLR, release: true)) { + return; + } + + var proj = new XamarinAndroidApplicationProject { + IsRelease = true, + }; + proj.SetRuntime (AndroidRuntime.CoreCLR); + proj.SetProperty ("_AndroidTypeMapImplementation", "trimmable"); + proj.SetProperty ("_AndroidTrimmableTypeMapMaxArrayRank", "0"); + + using var builder = CreateApkBuilder (); + Assert.IsTrue (builder.Build (proj), "First build should have succeeded."); + builder.Output.AssertTargetIsNotSkipped ("_GenerateTrimmableTypeMap"); + + Assert.IsTrue (builder.Build (proj, doNotCleanupOnUpdate: true), "Second build should have succeeded."); + builder.Output.AssertTargetIsSkipped ("_GenerateTrimmableTypeMap"); + + proj.SetProperty ("_AndroidTrimmableTypeMapMaxArrayRank", "3"); + Assert.IsTrue (builder.Build (proj, doNotCleanupOnUpdate: true), "Array rank change build should have succeeded."); + builder.Output.AssertTargetIsNotSkipped ("_GenerateTrimmableTypeMap"); + } + [Test] public void Build_WithTrimmableTypeMap_DoesNotHitCopyIfChangedMismatch ([Values (AndroidRuntime.CoreCLR, AndroidRuntime.NativeAOT)] AndroidRuntime runtime) { @@ -118,11 +147,66 @@ public void Build_WithTrimmableTypeMap_AssemblyStoreMappingsStayInRange () } [Test] - public void TrimmableTypeMap_PreserveList_IsPackagedInSdk () + public void NativeAotTrimmableTypeMap_DoesNotExportFrameworkTypeMaps () + { + if (IgnoreUnsupportedConfiguration (AndroidRuntime.NativeAOT, release: true)) { + return; + } + + var proj = new XamarinAndroidApplicationProject { + IsRelease = true, + }; + proj.SetRuntime (AndroidRuntime.NativeAOT); + proj.SetProperty ("_AndroidTypeMapImplementation", "trimmable"); + + using var builder = CreateApkBuilder (); + Assert.IsTrue (builder.Build (proj), "Build should have succeeded."); + + var ridIntermediateDir = builder.Output.GetIntermediaryPath ("android-arm64"); + var rspFiles = Directory.GetFiles (ridIntermediateDir, "*.ilc.rsp", SearchOption.AllDirectories); + Assert.IsNotEmpty (rspFiles, $"{ridIntermediateDir} should contain an ILC response file."); + + var rspText = File.ReadAllText (rspFiles [0]); + StringAssert.Contains ("_Java.Interop.TypeMap.dll", rspText); + StringAssert.Contains ("_Mono.Android.TypeMap.dll", rspText); + StringAssert.DoesNotContain ("--generateunmanagedentrypoints:_Java.Interop.TypeMap", rspText); + StringAssert.DoesNotContain ("--generateunmanagedentrypoints:_Mono.Android.TypeMap", rspText); + StringAssert.Contains ($"--generateunmanagedentrypoints:_{proj.ProjectName}.TypeMap", rspText); + } + + [Test] + public void CoreClrTrimmableTypeMap_PackagesJavaProxyThrowable () { - var path = Path.Combine (TestEnvironment.DotNetPreviewAndroidSdkDirectory, "PreserveLists", "Trimmable.CoreCLR.xml"); + if (IgnoreUnsupportedConfiguration (AndroidRuntime.CoreCLR, release: true)) { + return; + } + + var proj = new XamarinAndroidApplicationProject { + IsRelease = true, + }; + proj.SetRuntime (AndroidRuntime.CoreCLR); + proj.SetProperty ("_AndroidTypeMapImplementation", "trimmable"); + + using var builder = CreateApkBuilder (); + Assert.IsTrue (builder.Build (proj), "Build should have succeeded."); - FileAssert.Exists (path, $"{path} should exist in the SDK pack."); + var dexFile = builder.Output.GetIntermediaryPath (Path.Combine ("android", "bin", "classes.dex")); + FileAssert.Exists (dexFile); + Assert.IsTrue ( + DexUtils.ContainsClassWithMethod ("Landroid/runtime/JavaProxyThrowable;", "", "(Ljava/lang/String;)V", dexFile, AndroidSdkPath), + $"`{dexFile}` should include `android.runtime.JavaProxyThrowable`."); + } + + [Test] + public void TrimmableTypeMap_PreserveLists_ArePackagedInSdk () + { + foreach (var file in new [] { + "Trimmable.CoreCLR.xml", + "System.Private.CoreLib.xml", + }) { + var path = Path.Combine (TestEnvironment.DotNetPreviewAndroidSdkDirectory, "PreserveLists", file); + FileAssert.Exists (path, $"{path} should exist in the SDK pack."); + } } [Test] @@ -146,5 +230,200 @@ public void TrimmableTypeMap_RuntimeArtifacts_ArePackagedInSdk () } } + + // T1: end-to-end build coverage for [Export] and [ExportField] under trimmable. + // The trimmable typemap path emits a per-assembly typemap DLL and JCW Java + // sources for user peer types. This test confirms that, for a project that + // uses both [Export] (instance method) and [ExportField] (static getter), + // the JCW Java file the build generates contains the expected `native` + // method declaration AND a static field declaration referencing the field + // initializer method. If either side regresses, the runtime would silently + // fail to wire up the user's exports. + [Test] + public void Build_WithExportAndExportField_GeneratesJcwAndTypeMap () + { + const AndroidRuntime runtime = AndroidRuntime.CoreCLR; + + var proj = new XamarinAndroidApplicationProject { + IsRelease = false, + References = { + new BuildItem.Reference ("Mono.Android.Export"), + }, + }; + proj.SetRuntime (runtime); + proj.SetProperty ("_AndroidTypeMapImplementation", "trimmable"); + proj.Sources.Add (new BuildItem.Source ("ExportShapes.cs") { + TextContent = () => @"using System; +using Java.Interop; + +namespace UnnamedProject { + class ExportShapes : Java.Lang.Object { + [Export] + public string EchoString (string x) => ""<"" + x + "">""; + + [ExportField (""FOO"")] + public static int InitialFoo () => 42; + } +}" + }); + + using var builder = CreateApkBuilder (); + Assert.IsTrue (builder.Build (proj), "Build should have succeeded."); + + var javaDir = Path.Combine (builder.Output.GetIntermediaryPath ("typemap"), "java"); + DirectoryAssert.Exists (javaDir, "Trimmable JCW Java output directory should exist."); + + var allJavaFiles = Directory.GetFiles (javaDir, "*.java", SearchOption.AllDirectories); + Assert.IsNotEmpty (allJavaFiles, "At least one JCW Java source file should be generated."); + + // The JCW Java file for ExportShapes lives under a crc64/ECDH directory + // matching the CRC64 hash of the type. Search by content (one of the method + // names that must appear in the generated source) rather than by filename + // to avoid coupling to the hash. + string? exportShapesJava = null; + string? exportShapesText = null; + foreach (var f in allJavaFiles) { + var text = File.ReadAllText (f); + if (text.Contains ("EchoString") && text.Contains ("InitialFoo")) { + exportShapesJava = f; + exportShapesText = text; + break; + } + } + Assert.IsNotNull (exportShapesJava, + $"Could not find a generated JCW Java file referencing both EchoString and InitialFoo under {javaDir}."); + Assert.IsNotNull (exportShapesText, + $"Could not find a generated JCW Java file referencing both EchoString and InitialFoo under {javaDir}."); + var javaText = exportShapesText; + + // [Export] EchoString — Java side must declare a `native` method matching + // the C# signature (String -> String). The trimmable emitter generates + // `public native` for instance [Export] methods. + StringAssert.Contains ("native", javaText, + "Generated JCW should contain a native method declaration for [Export]."); + StringAssert.Contains ("EchoString", javaText, + "Generated JCW should contain the [Export] method name."); + + // [ExportField] FOO — Java side must declare a static field initialized + // by calling the C# initializer method (`InitialFoo`). Without this, + // the [ExportField] is silently dropped and Java callers see no FOO. + StringAssert.Contains ("FOO", javaText, + "Generated JCW should contain the [ExportField] declaration."); + StringAssert.Contains ("InitialFoo", javaText, + "Generated JCW should reference the [ExportField] initializer method."); + + // A per-assembly typemap DLL should be present (named after the app + // assembly + .TypeMap suffix). We only check that *some* user typemap + // assembly was produced — the exact name varies based on app assembly. + var typemapDir = builder.Output.GetIntermediaryPath ("typemap"); + var typemapDlls = Directory.GetFiles (typemapDir, "*.TypeMap.dll"); + Assert.IsNotEmpty (typemapDlls, "Trimmable typemap should produce at least one *.TypeMap.dll."); + } + + // T6: trim-warning baseline for [Export] under trimmable. + // The trimmable [Export] code generator emits IL that reaches into + // Mono.Android via [IgnoresAccessChecksTo] and dispatches through + // member references built from System.Reflection.Metadata. If the + // emitter starts producing reflection-style patterns that the trim + // analyzer cannot track (e.g. missing [DynamicallyAccessedMembers] on + // helper signatures), IL2xxx / IL3xxx warnings will appear pointing + // at the generated `_.TypeMap.dll` or at the user's [Export] + // source. The baseline is: zero such warnings reference either of + // those locations. This is a targeted assertion (not a full no-IL-warnings + // guarantee), so unrelated framework warnings don't fail the test. + [Test] + public void Build_WithExport_ProducesNoTrimWarningsTargetingExportCodegen () + { + const AndroidRuntime runtime = AndroidRuntime.CoreCLR; + + var proj = new XamarinAndroidApplicationProject { + IsRelease = true, + References = { + new BuildItem.Reference ("Mono.Android.Export"), + }, + }; + proj.SetRuntime (runtime); + proj.SetProperty ("_AndroidTypeMapImplementation", "trimmable"); + proj.SetProperty ("TrimMode", "full"); + proj.SetProperty ("TrimmerSingleWarn", "false"); + proj.Sources.Add (new BuildItem.Source ("ExportShapes.cs") { + TextContent = () => @"using System; +using Java.Interop; + +namespace UnnamedProject { + class ExportShapes : Java.Lang.Object { + [Export] + public string EchoString (string x) => ""<"" + x + "">""; + + [ExportField (""FOO"")] + public static int InitialFoo () => 42; + } +}" + }); + + using var builder = CreateApkBuilder (); + Assert.IsTrue (builder.Build (proj), "Build should have succeeded."); + + // Match actual IL2xxx and IL3xxx warning lines (trim + AOT analysis), then + // keep only those whose message text references either the generated + // trimmable typemap assembly or the [Export] source file. + // The regex requires ": warning IL" to avoid matching CSC command lines + // that mention IL codes in /nowarn switches. + // Exclude IL2026 about ExportAttribute/ExportFieldAttribute constructors + // themselves — those are expected (the attributes carry [RequiresUnreferencedCode]). + var ilWarningRegex = new Regex (@":\s*warning\s+(IL[23]\d{3})\b", RegexOptions.Compiled); + var offending = new List (); + foreach (var line in builder.LastBuildOutput) { + if (!ilWarningRegex.IsMatch (line)) { + continue; + } + if (line.Contains ("ExportAttribute", StringComparison.Ordinal) + && line.Contains ("RequiresUnreferencedCode", StringComparison.Ordinal)) { + continue; + } + bool mentionsTypeMap = line.Contains (".TypeMap.dll", StringComparison.OrdinalIgnoreCase) + || line.Contains ("_Microsoft.Android.TypeMaps", StringComparison.OrdinalIgnoreCase); + bool mentionsExportSource = line.Contains ("ExportShapes.cs", StringComparison.OrdinalIgnoreCase); + if (mentionsTypeMap || mentionsExportSource) { + offending.Add (line.Trim ()); + } + } + + Assert.IsEmpty (offending, + "Trimmable [Export] codegen should not introduce IL2xxx / IL3xxx warnings against the generated typemap " + + "assembly or the user's [Export] source. Offending warning lines:\n " + + string.Join ("\n ", offending)); + } + + [Test] + public void Build_WithTrimmableTypeMap_AbstractTypeWithProtectedCtor_Succeeds () + { + if (IgnoreUnsupportedConfiguration (AndroidRuntime.NativeAOT, release: true)) { + return; + } + + var proj = new XamarinAndroidApplicationProject { + IsRelease = true, + }; + proj.SetRuntime (AndroidRuntime.NativeAOT); + proj.SetProperty ("_AndroidTypeMapImplementation", "trimmable"); + proj.Sources.Add (new BuildItem.Source ("AbstractProvider.cs") { + TextContent = () => @" +namespace UnnamedProject { + public abstract class AbstractProvider : Java.Lang.Object { + protected AbstractProvider (Android.Content.Context context) { } + public abstract string GetData (); + } + + public class ConcreteProvider : AbstractProvider { + public ConcreteProvider (Android.Content.Context context) : base (context) { } + public override string GetData () => ""hello""; + } +}" + }); + + using var builder = CreateApkBuilder (); + Assert.IsTrue (builder.Build (proj), "Build should have succeeded — abstract types with protected ctors should not cause XAGTT7009."); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs index fc5e4e3ed81..31e2be6d3db 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs @@ -476,6 +476,38 @@ protected string GetPathToAapt () return GetPathToLatestBuildTools (exe); } + /// + /// Verifies that an APK is signed using `apksigner verify`. + /// With min SDK 24+, APKs may use v2/v3 signing only (no v1 JAR signatures), + /// so checking for META-INF/MANIFEST.MF is not reliable. + /// + protected void AssertApkIsSigned (string apkPath) + { + var ext = IsWindows ? ".bat" : ""; + var apksignerExe = Path.Combine (GetPathToLatestBuildTools ("apksigner" + ext), "apksigner" + ext); + if (!File.Exists (apksignerExe)) { + // Fall back to META-INF check if apksigner is not available + using (var zip = ZipHelper.OpenZip (apkPath)) { + Assert.IsTrue (zip.Any (e => e.FullName == "META-INF/MANIFEST.MF"), $"APK file `{apkPath}` is not signed! It is missing `META-INF/MANIFEST.MF`."); + } + return; + } + + var psi = new ProcessStartInfo { + FileName = apksignerExe, + Arguments = $"verify \"{apkPath}\"", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + using var proc = Process.Start (psi); + string stdout = proc!.StandardOutput.ReadToEnd (); + string stderr = proc.StandardError.ReadToEnd (); + proc.WaitForExit (); + Assert.AreEqual (0, proc.ExitCode, $"APK file `{apkPath}` is not signed! apksigner verify failed:\n{stderr}\n{stdout}"); + } + protected string GetResourceDesignerPath (ProjectBuilder builder, XamarinAndroidProject project) { string path = Path.Combine (Root, builder.ProjectDirectory, project.IntermediateOutputPath); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/ProjectExtensions.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/ProjectExtensions.cs index a3cac4affef..41e701a39e2 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/ProjectExtensions.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/ProjectExtensions.cs @@ -23,7 +23,8 @@ public static void SetRuntime (this XamarinAndroidApplicationProject project, An DoSetRuntime (project, runtime); return; } - project.SetPublishAot (true, BaseTest.AndroidNdkPath); + project.SetPublishAot (true); + project.SetProperty ("_SkipNdkResolution", "true"); EnablePreviewFeaturesIfNeeded (project, runtime); } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/WearTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/WearTests.cs index 067449a6ced..e45a9aff2b7 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/WearTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/WearTests.cs @@ -72,7 +72,7 @@ public void WearProjectJavaBuildFailure ([Values] AndroidRuntime runtime) new Package { Id = "Xamarin.AndroidX.PercentLayout", Version = "1.0.0.14" }, new Package { Id = "Xamarin.AndroidX.Legacy.Support.Core.UI", Version = "1.0.0.14" }, }, - SupportedOSPlatformVersion = "23", + SupportedOSPlatformVersion = "24", }; proj.SetRuntime (runtime); var builder = CreateApkBuilder (); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/AndroidSdkResolver.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/AndroidSdkResolver.cs index 13e27f82d3e..d4106453efe 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/AndroidSdkResolver.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/AndroidSdkResolver.cs @@ -50,8 +50,6 @@ public static string GetAndroidNdkPath () public static string GetJavaSdkPath () { var javaSdkPath = Environment.GetEnvironmentVariable ("TEST_ANDROID_JDK_PATH"); - if (string.IsNullOrEmpty (javaSdkPath)) - javaSdkPath = JavaSdkPath ??= Environment.GetEnvironmentVariable ("JI_JAVA_HOME"); if (string.IsNullOrEmpty (javaSdkPath)) javaSdkPath = JavaSdkPath ??= Environment.GetEnvironmentVariable ("JAVA_HOME"); if (string.IsNullOrEmpty (javaSdkPath)) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidApplicationProject.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidApplicationProject.cs index 67d982fb0d9..f8ec5686a9a 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidApplicationProject.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidApplicationProject.cs @@ -79,7 +79,7 @@ public XamarinAndroidApplicationProject (string debugConfigurationName = "Debug" SetProperty (KnownProperties.ImplicitUsings, "enable"); SetProperty ("XamarinAndroidSupportSkipVerifyVersions", "True"); SetProperty ("_FastDeploymentDiagnosticLogging", "True"); - SupportedOSPlatformVersion = "21.0"; + SupportedOSPlatformVersion = "24.0"; AndroidManifest = default_android_manifest; LayoutMain = default_layout_main; @@ -112,7 +112,7 @@ public virtual string DefaultMainActivity { public string MinSdkVersion { get; set; } /// - /// Defaults to 21.0 + /// Defaults to 24.0 /// public string SupportedOSPlatformVersion { get { return GetProperty (KnownProperties.SupportedOSPlatformVersion); } @@ -198,13 +198,12 @@ private bool PublishAot { /// /// Sets properties required for $(PublishAot)=true /// - public void SetPublishAot (bool value, string androidNdkPath) + public void SetPublishAot (bool value) { // Only toggle IsRelease=true when value is true if (value) IsRelease = true; PublishAot = value; - SetProperty ("AndroidNdkDirectory", androidNdkPath); } public string AndroidManifest { get; set; } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidWearApplicationProject.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidWearApplicationProject.cs index 7b32bcc983e..e9907869a52 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidWearApplicationProject.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidWearApplicationProject.cs @@ -34,7 +34,7 @@ public XamarinAndroidWearApplicationProject (string debugConfigurationName = "De PackageReferences.Add (KnownPackages.XamarinAndroidXWear); // uses-sdk:minSdkVersion 21 cannot be smaller than version 23 declared in library androidx.wear.wear.aar as the library might be using APIs not available in 21 - SupportedOSPlatformVersion = "23"; + SupportedOSPlatformVersion = "24"; MainActivity = default_main_activity; StringsXml = default_strings_xml; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/SolutionBuilder.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/SolutionBuilder.cs index 8c3eea39321..9201694a794 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/SolutionBuilder.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/SolutionBuilder.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.Text; using System.IO; @@ -9,8 +10,8 @@ namespace Xamarin.ProjectTools public class SolutionBuilder : Builder { public IList Projects { get; } - public string SolutionPath { get; set; } - public string SolutionName { get; set; } + public string? SolutionPath { get; set; } + public string SolutionName { get; set; } = ""; public bool BuildSucceeded { get; set; } public SolutionBuilder (string solutionName) : base() @@ -21,6 +22,7 @@ public SolutionBuilder (string solutionName) : base() public void Save () { + ArgumentNullException.ThrowIfNull (SolutionPath); foreach (var p in Projects) { using (var pb = new ProjectBuilder (Path.Combine (SolutionPath, p.ProjectName))) { pb.Save (p); @@ -65,6 +67,7 @@ public void Save () public bool BuildProject(XamarinProject project, string target = "Build") { + ArgumentNullException.ThrowIfNull (SolutionPath); BuildSucceeded = BuildInternal(Path.Combine (SolutionPath, project.ProjectName, project.ProjectFilePath), target, restore: project.ShouldRestorePackageReferences); return BuildSucceeded; } @@ -93,7 +96,7 @@ public bool Clean(params string[] parameters) protected override void Dispose (bool disposing) { if (disposing) - if (BuildSucceeded) + if (BuildSucceeded && !string.IsNullOrEmpty (SolutionPath)) try { Directory.Delete (SolutionPath, recursive: true); } catch (Exception) { diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.MonoVM.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.MonoVM.apkdesc index 02e57a96a2d..42c7d46617e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.MonoVM.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.MonoVM.apkdesc @@ -5,46 +5,46 @@ "Size": 3036 }, "classes.dex": { - "Size": 22388 + "Size": 22384 }, "lib/arm64-v8a/lib__Microsoft.Android.Resource.Designer.dll.so": { - "Size": 18296 + "Size": 18232 }, "lib/arm64-v8a/lib_Java.Interop.dll.so": { - "Size": 88240 + "Size": 87720 }, "lib/arm64-v8a/lib_Mono.Android.dll.so": { - "Size": 117736 + "Size": 118392 }, "lib/arm64-v8a/lib_Mono.Android.Runtime.dll.so": { - "Size": 26752 + "Size": 26848 }, "lib/arm64-v8a/lib_System.Console.dll.so": { - "Size": 24424 + "Size": 24360 }, "lib/arm64-v8a/lib_System.Linq.dll.so": { - "Size": 25504 + "Size": 25432 }, "lib/arm64-v8a/lib_System.Private.CoreLib.dll.so": { - "Size": 638088 + "Size": 638568 }, "lib/arm64-v8a/lib_System.Runtime.dll.so": { - "Size": 20288 + "Size": 20224 }, "lib/arm64-v8a/lib_System.Runtime.InteropServices.dll.so": { - "Size": 19824 + "Size": 19752 }, "lib/arm64-v8a/lib_UnnamedProject.dll.so": { - "Size": 20032 + "Size": 19968 }, "lib/arm64-v8a/libarc.bin.so": { - "Size": 19240 + "Size": 19296 }, "lib/arm64-v8a/libmono-component-marshal-ilgen.so": { "Size": 36416 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 1387712 + "Size": 1385800 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3111840 @@ -56,22 +56,13 @@ "Size": 1280336 }, "lib/arm64-v8a/libSystem.Native.so": { - "Size": 100552 + "Size": 101872 }, "lib/arm64-v8a/libSystem.Security.Cryptography.Native.Android.so": { "Size": 162000 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 19792 - }, - "META-INF/BNDLTOOL.RSA": { - "Size": 1223 - }, - "META-INF/BNDLTOOL.SF": { - "Size": 3266 - }, - "META-INF/MANIFEST.MF": { - "Size": 3139 + "Size": 19832 }, "res/drawable-hdpi-v4/icon.png": { "Size": 2178 @@ -98,5 +89,5 @@ "Size": 1904 } }, - "PackageSize": 3258901 + "PackageSize": 3254606 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.MonoVM.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.MonoVM.apkdesc index 72de0e4e88c..043500537a1 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.MonoVM.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.MonoVM.apkdesc @@ -5,7 +5,7 @@ "Size": 6652 }, "classes.dex": { - "Size": 9159624 + "Size": 9122408 }, "kotlin/annotation/annotation.kotlin_builtins": { "Size": 928 @@ -29,217 +29,217 @@ "Size": 2396 }, "lib/arm64-v8a/lib__Microsoft.Android.Resource.Designer.dll.so": { - "Size": 19544 + "Size": 19480 }, "lib/arm64-v8a/lib_FormsViewGroup.dll.so": { - "Size": 25424 + "Size": 25360 }, "lib/arm64-v8a/lib_Java.Interop.dll.so": { - "Size": 96848 + "Size": 96344 }, "lib/arm64-v8a/lib_Mono.Android.dll.so": { - "Size": 541664 + "Size": 542848 }, "lib/arm64-v8a/lib_Mono.Android.Runtime.dll.so": { - "Size": 26752 + "Size": 26848 }, "lib/arm64-v8a/lib_mscorlib.dll.so": { - "Size": 21472 + "Size": 21408 }, "lib/arm64-v8a/lib_netstandard.dll.so": { - "Size": 23112 + "Size": 23048 }, "lib/arm64-v8a/lib_System.Collections.dll.so": { - "Size": 34016 + "Size": 33944 }, "lib/arm64-v8a/lib_System.Collections.NonGeneric.dll.so": { - "Size": 25680 + "Size": 25608 }, "lib/arm64-v8a/lib_System.Collections.Specialized.dll.so": { - "Size": 23872 + "Size": 23800 }, "lib/arm64-v8a/lib_System.ComponentModel.dll.so": { - "Size": 19624 + "Size": 19560 }, "lib/arm64-v8a/lib_System.ComponentModel.Primitives.dll.so": { - "Size": 21360 + "Size": 21296 }, "lib/arm64-v8a/lib_System.ComponentModel.TypeConverter.dll.so": { - "Size": 43672 + "Size": 43600 }, "lib/arm64-v8a/lib_System.Console.dll.so": { - "Size": 24456 + "Size": 24392 }, "lib/arm64-v8a/lib_System.Core.dll.so": { - "Size": 19496 + "Size": 19424 }, "lib/arm64-v8a/lib_System.Diagnostics.TraceSource.dll.so": { - "Size": 24648 + "Size": 24584 }, "lib/arm64-v8a/lib_System.dll.so": { - "Size": 19856 + "Size": 19784 }, "lib/arm64-v8a/lib_System.Drawing.dll.so": { - "Size": 19464 + "Size": 19400 }, "lib/arm64-v8a/lib_System.Drawing.Primitives.dll.so": { - "Size": 30088 + "Size": 30016 }, "lib/arm64-v8a/lib_System.Formats.Asn1.dll.so": { - "Size": 51040 + "Size": 50968 }, "lib/arm64-v8a/lib_System.IO.Compression.Brotli.dll.so": { - "Size": 29608 + "Size": 29536 }, "lib/arm64-v8a/lib_System.IO.Compression.dll.so": { - "Size": 34672 + "Size": 34600 }, "lib/arm64-v8a/lib_System.IO.IsolatedStorage.dll.so": { - "Size": 28304 + "Size": 28232 }, "lib/arm64-v8a/lib_System.Linq.dll.so": { - "Size": 48144 + "Size": 47912 }, "lib/arm64-v8a/lib_System.Linq.Expressions.dll.so": { - "Size": 186032 + "Size": 185968 }, "lib/arm64-v8a/lib_System.Net.Http.dll.so": { - "Size": 86752 + "Size": 86680 }, "lib/arm64-v8a/lib_System.Net.Primitives.dll.so": { - "Size": 42352 + "Size": 42280 }, "lib/arm64-v8a/lib_System.Net.Requests.dll.so": { - "Size": 21584 + "Size": 21520 }, "lib/arm64-v8a/lib_System.ObjectModel.dll.so": { - "Size": 27040 + "Size": 26968 }, "lib/arm64-v8a/lib_System.Private.CoreLib.dll.so": { - "Size": 1000576 + "Size": 999864 }, "lib/arm64-v8a/lib_System.Private.DataContractSerialization.dll.so": { - "Size": 217840 + "Size": 217808 }, "lib/arm64-v8a/lib_System.Private.Uri.dll.so": { - "Size": 62280 + "Size": 62216 }, "lib/arm64-v8a/lib_System.Private.Xml.dll.so": { - "Size": 237032 + "Size": 236968 }, "lib/arm64-v8a/lib_System.Private.Xml.Linq.dll.so": { - "Size": 35528 + "Size": 35464 }, "lib/arm64-v8a/lib_System.Runtime.dll.so": { - "Size": 20424 + "Size": 20352 }, "lib/arm64-v8a/lib_System.Runtime.InteropServices.dll.so": { - "Size": 19824 + "Size": 19752 }, "lib/arm64-v8a/lib_System.Runtime.Numerics.dll.so": { - "Size": 63352 + "Size": 63312 }, "lib/arm64-v8a/lib_System.Runtime.Serialization.dll.so": { - "Size": 19400 + "Size": 19328 }, "lib/arm64-v8a/lib_System.Runtime.Serialization.Formatters.dll.so": { - "Size": 20368 + "Size": 20296 }, "lib/arm64-v8a/lib_System.Runtime.Serialization.Primitives.dll.so": { - "Size": 21488 + "Size": 21424 }, "lib/arm64-v8a/lib_System.Security.Cryptography.dll.so": { - "Size": 82096 + "Size": 82024 }, "lib/arm64-v8a/lib_System.Text.RegularExpressions.dll.so": { - "Size": 194240 + "Size": 194176 }, "lib/arm64-v8a/lib_System.Xml.dll.so": { - "Size": 19288 + "Size": 19216 }, "lib/arm64-v8a/lib_System.Xml.Linq.dll.so": { - "Size": 19304 + "Size": 19240 }, "lib/arm64-v8a/lib_UnnamedProject.dll.so": { - "Size": 22104 + "Size": 22040 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.Activity.dll.so": { - "Size": 34960 + "Size": 34896 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.AppCompat.AppCompatResources.dll.so": { - "Size": 24512 + "Size": 24448 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.AppCompat.dll.so": { - "Size": 163240 + "Size": 163176 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.CardView.dll.so": { - "Size": 24568 + "Size": 24504 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.CoordinatorLayout.dll.so": { - "Size": 35912 + "Size": 35848 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.Core.dll.so": { - "Size": 151408 + "Size": 151344 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.CursorAdapter.dll.so": { - "Size": 27168 + "Size": 27104 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.DrawerLayout.dll.so": { - "Size": 33944 + "Size": 33880 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.Fragment.dll.so": { - "Size": 72528 + "Size": 72464 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.Legacy.Support.Core.UI.dll.so": { - "Size": 23896 + "Size": 23832 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.Lifecycle.Common.dll.so": { - "Size": 25072 + "Size": 25008 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.Lifecycle.LiveData.Core.dll.so": { - "Size": 24856 + "Size": 24792 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.Lifecycle.ViewModel.dll.so": { - "Size": 25208 + "Size": 25144 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.Loader.dll.so": { - "Size": 31592 + "Size": 31528 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.RecyclerView.dll.so": { - "Size": 112256 + "Size": 112192 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.SavedState.dll.so": { - "Size": 23144 + "Size": 23080 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.SwipeRefreshLayout.dll.so": { - "Size": 31952 + "Size": 31888 }, "lib/arm64-v8a/lib_Xamarin.AndroidX.ViewPager.dll.so": { - "Size": 38056 + "Size": 37992 }, "lib/arm64-v8a/lib_Xamarin.Forms.Core.dll.so": { - "Size": 581000 + "Size": 580936 }, "lib/arm64-v8a/lib_Xamarin.Forms.Platform.Android.dll.so": { - "Size": 390392 + "Size": 390328 }, "lib/arm64-v8a/lib_Xamarin.Forms.Platform.dll.so": { - "Size": 35992 + "Size": 35928 }, "lib/arm64-v8a/lib_Xamarin.Forms.Xaml.dll.so": { - "Size": 80632 + "Size": 80568 }, "lib/arm64-v8a/lib_Xamarin.Google.Android.Material.dll.so": { - "Size": 84768 + "Size": 84704 }, "lib/arm64-v8a/libarc.bin.so": { - "Size": 19240 + "Size": 19296 }, "lib/arm64-v8a/libmono-component-marshal-ilgen.so": { "Size": 36416 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 1387712 + "Size": 1385800 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3111840 @@ -251,13 +251,13 @@ "Size": 1280336 }, "lib/arm64-v8a/libSystem.Native.so": { - "Size": 100552 + "Size": 101872 }, "lib/arm64-v8a/libSystem.Security.Cryptography.Native.Android.so": { "Size": 162000 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 350576 + "Size": 350616 }, "META-INF/androidx.activity_activity.version": { "Size": 6 @@ -409,12 +409,6 @@ "META-INF/androidx.viewpager2_viewpager2.version": { "Size": 6 }, - "META-INF/BNDLTOOL.RSA": { - "Size": 1605 - }, - "META-INF/BNDLTOOL.SF": { - "Size": 98319 - }, "META-INF/com.android.tools/proguard/coroutines.pro": { "Size": 1345 }, @@ -439,9 +433,6 @@ "META-INF/kotlinx_coroutines_core.version": { "Size": 5 }, - "META-INF/MANIFEST.MF": { - "Size": 98192 - }, "META-INF/maven/com.google.guava/listenablefuture/pom.properties": { "Size": 96 }, @@ -679,9 +670,6 @@ "res/color-night-v8/material_timepicker_modebutton_tint.xml": { "Size": 340 }, - "res/color-v21/abc_btn_colored_borderless_text_material.xml": { - "Size": 464 - }, "res/color-v23/abc_btn_colored_borderless_text_material.xml": { "Size": 500 }, @@ -715,9 +703,6 @@ "res/color/abc_background_cache_hint_selector_material_light.xml": { "Size": 468 }, - "res/color/abc_btn_colored_text_material.xml": { - "Size": 604 - }, "res/color/abc_hint_foreground_material_dark.xml": { "Size": 564 }, @@ -745,24 +730,6 @@ "res/color/abc_secondary_text_material_light.xml": { "Size": 464 }, - "res/color/abc_tint_btn_checkable.xml": { - "Size": 728 - }, - "res/color/abc_tint_default.xml": { - "Size": 1224 - }, - "res/color/abc_tint_edittext.xml": { - "Size": 772 - }, - "res/color/abc_tint_seek_thumb.xml": { - "Size": 604 - }, - "res/color/abc_tint_spinner.xml": { - "Size": 772 - }, - "res/color/abc_tint_switch_track.xml": { - "Size": 768 - }, "res/color/checkbox_themeable_attribute_color.xml": { "Size": 464 }, @@ -1900,9 +1867,6 @@ "res/drawable/mtrl_ic_error.xml": { "Size": 644 }, - "res/drawable/mtrl_popupmenu_background_dark.xml": { - "Size": 740 - }, "res/drawable/mtrl_popupmenu_background.xml": { "Size": 740 }, @@ -1999,15 +1963,6 @@ "res/layout-v21/notification_template_icon_group.xml": { "Size": 988 }, - "res/layout-v22/abc_alert_dialog_button_bar_material.xml": { - "Size": 1584 - }, - "res/layout-v22/material_timepicker_dialog.xml": { - "Size": 3184 - }, - "res/layout-v22/mtrl_alert_dialog_actions.xml": { - "Size": 1764 - }, "res/layout-v26/abc_screen_toolbar.xml": { "Size": 1560 }, @@ -2045,7 +2000,7 @@ "Size": 1684 }, "res/layout/abc_alert_dialog_button_bar_material.xml": { - "Size": 1536 + "Size": 1584 }, "res/layout/abc_alert_dialog_material.xml": { "Size": 2648 @@ -2210,7 +2165,7 @@ "Size": 1208 }, "res/layout/material_timepicker_dialog.xml": { - "Size": 3132 + "Size": 3184 }, "res/layout/material_timepicker_textinput_display.xml": { "Size": 684 @@ -2219,7 +2174,7 @@ "Size": 1136 }, "res/layout/mtrl_alert_dialog_actions.xml": { - "Size": 1620 + "Size": 1764 }, "res/layout/mtrl_alert_dialog_title.xml": { "Size": 956 @@ -2477,8 +2432,8 @@ "Size": 268 }, "resources.arsc": { - "Size": 812848 + "Size": 794696 } }, - "PackageSize": 11140157 + "PackageSize": 11024231 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Utilities/ProjectExtensions.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Utilities/ProjectExtensions.cs index 9492df1738c..25037ba0547 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Utilities/ProjectExtensions.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Utilities/ProjectExtensions.cs @@ -9,25 +9,6 @@ namespace Xamarin.ProjectTools { public static class ProjectExtensions { - /// - /// Sets $(AndroidSupportedAbis) or $(RuntimeIdentifiers) depending if running under dotnet - /// - [Obsolete ("SetAndroidSupportedAbis is deprecated, please use SetRuntimeIdentifiers instead.")] - public static void SetAndroidSupportedAbis (this IShortFormProject project, params string [] abis) - { - project.SetRuntimeIdentifiers (abis); - } - - /// - /// Sets $(AndroidSupportedAbis) or $(RuntimeIdentifiers) depending if running under dotnet - /// - /// A semi-colon-delimited list of ABIs - [Obsolete ("SetAndroidSupportedAbis is deprecated, please use SetRuntimeIdentifiers instead.")] - public static void SetAndroidSupportedAbis (this IShortFormProject project, string abis) - { - project.SetRuntimeIdentifiers (abis.Split (';')); - } - public static void SetRuntimeIdentifier (this IShortFormProject project, string androidAbi) { project.SetProperty (KnownProperties.RuntimeIdentifier, AbiUtils.AbiToRuntimeIdentifier (androidAbi)); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/DSOWrapperGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/DSOWrapperGenerator.cs index ff584246f61..b0d450822fa 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/DSOWrapperGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/DSOWrapperGenerator.cs @@ -68,7 +68,8 @@ public static Config GetConfig (TaskLoggingHelper log, string androidBinUtilsDir string stubPath = Path.Combine (packLibDir.ItemSpec, StubFileName); if (!File.Exists (stubPath)) { - throw new InvalidOperationException ($"Internal error: archive DSO stub file '{stubPath}' does not exist in runtime pack at {packLibDir}"); + log.LogDebugMessage ($"Archive DSO stub file '{stubPath}' not found in runtime pack directory, skipping"); + continue; } AndroidTargetArch arch = MonoAndroidHelper.RidToArch (packRID); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentBuilder.cs b/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentBuilder.cs index 51121c052bf..6575db7c815 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentBuilder.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentBuilder.cs @@ -9,8 +9,6 @@ class EnvironmentBuilder { static readonly string[] defaultLogLevel = {"MONO_LOG_LEVEL", "info"}; static readonly string[] defaultMonoDebug = {"MONO_DEBUG", "gen-compact-seq-points"}; - static readonly string defaultHttpMessageHandler = "System.Net.Http.HttpClientHandler, System.Net.Http"; - readonly EnvironmentFilesParser environmentParser; readonly Dictionary environmentVariables; readonly Dictionary systemProperties; @@ -86,19 +84,6 @@ public void AddDefaultMonoDebug () AddEnvironmentVariable (defaultMonoDebug[0], defaultMonoDebug[1]); } - public void AddHttpClientHandlerType (string? handlerType) - { - if (environmentParser.HaveHttpMessageHandler) { - return; - } - - if (String.IsNullOrEmpty (handlerType)) { - handlerType = defaultHttpMessageHandler; - } - - AddEnvironmentVariable ("XA_HTTP_CLIENT_HANDLER_TYPE", handlerType!.Trim ()); - } - public void AddMonoGcParams (bool enableSgenConcurrent) { if (environmentParser.HaveMonoGCParams) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentFilesParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentFilesParser.cs index 0aecd7b1434..d459ee55ad1 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentFilesParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentFilesParser.cs @@ -12,7 +12,6 @@ namespace Xamarin.Android.Tasks class EnvironmentFilesParser { public bool BrokenExceptionTransitions { get; set; } - public bool HaveHttpMessageHandler { get; private set; } public bool HaveLogLevel { get; private set; } public bool HaveMonoDebug { get; private set; } public bool HaveMonoGCParams { get; private set; } @@ -51,8 +50,6 @@ public void Parse (ITaskItem[]? environments, SequencePointsMode sequencePointsM if (sequencePointsMode != SequencePointsMode.None && !lineToWrite.Contains ("gen-compact-seq-points")) lineToWrite = line + ",gen-compact-seq-points"; } - if (lineToWrite.StartsWith ("XA_HTTP_CLIENT_HANDLER_TYPE=", StringComparison.Ordinal)) - HaveHttpMessageHandler = true; if (lineToWrite.StartsWith ("mono.enable_assembly_preload=", StringComparison.Ordinal)) { int idx = lineToWrite.IndexOf ('='); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs index 3bb8c5a6ee4..6c273db0ccf 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs @@ -107,7 +107,7 @@ public bool GenerateCode (CallableWrapperType generator, TypeDefinition type, st var changed = Files.CopyIfStreamChanged (writer.BaseStream, path); if (changed) { - log.LogError ($"Generated Java callable wrapper code changed: '{path}' "); + log.LogCodedError ("XA4253", Properties.Resources.XA4253, path); } else { log.LogMessage ($"Java callable wrapper code already up to date: '{path}'"); } @@ -188,12 +188,9 @@ static void EnsureIdenticalCollections (TaskLoggingHelper logger, NativeCodeGenS } if (!typesSet.SetEquals (templateSet)) { - logger.LogError ($"Architecture '{state.TargetArch}' has Java types which have no counterparts in template architecture '{templateState.TargetArch}':"); - typesSet.ExceptWith (templateSet); - - foreach (var type in typesSet) - logger.LogError ($" {type}"); + var typesList = string.Join (", ", typesSet.OrderBy (t => t, StringComparer.Ordinal)); + logger.LogCodedError ("XA4217", Properties.Resources.XA4217, state.TargetArch, templateState.TargetArch, typesList); } } @@ -268,7 +265,7 @@ static void EnsureClassifiersMatch (TaskLoggingHelper logger, NativeCodeGenState return; } - logger.LogError ($"Architecture '{state.TargetArch}' doesn't match all marshal methods in architecture '{templateState.TargetArch}'. Please see detailed MSBuild logs for more information."); + logger.LogCodedError ("XA4227", Properties.Resources.XA4227, state.TargetArch, templateState.TargetArch); } static bool CheckWhetherMethodsMatch (TaskLoggingHelper logger, MarshalMethodEntry templateMethod, AndroidTargetArch templateArch, MarshalMethodEntry method, AndroidTargetArch arch) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs index 69dee1b5659..0b7aded5a27 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs @@ -160,7 +160,7 @@ public Br (LlvmIrVariable cond, LlvmIrFunctionLabelItem ifTrue, LlvmIrFunctionLa : base (OpName) { if (cond.Type != typeof(bool)) { - throw new ArgumentException ($"Internal error: condition must refer to a variable of type 'bool', was 'cond.Type' instead", nameof (cond)); + throw new ArgumentException ($"Internal error: condition must refer to a variable of type 'bool', was '{cond.Type}' instead", nameof (cond)); } this.cond = cond; @@ -218,7 +218,7 @@ public Call (LlvmIrFunction function, LlvmIrVariable? result = null, ICollection if (function.Signature.ReturnType != typeof(void)) { if (result == null) { - throw new ArgumentNullException ($"Internal error: function '{function.Signature.Name}' returns '{function.Signature.ReturnType} and thus requires a result variable", nameof (result)); + throw new ArgumentNullException (nameof (result), $"Internal error: function '{function.Signature.Name}' returns '{function.Signature.ReturnType}' and thus requires a result variable"); } } else if (result != null) { throw new ArgumentException ($"Internal error: function '{function.Signature.Name}' returns no value and yet a result variable was provided", nameof (result)); @@ -227,7 +227,7 @@ public Call (LlvmIrFunction function, LlvmIrVariable? result = null, ICollection int argCount = function.Signature.Parameters.Count; if (argCount != 0) { if (arguments == null) { - throw new ArgumentNullException ($"Internal error: function '{function.Signature.Name}' requires {argCount} arguments", nameof (arguments)); + throw new ArgumentNullException (nameof (arguments), $"Internal error: function '{function.Signature.Name}' requires {argCount} arguments"); } if (function.UsesVarArgs) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs index 1572df2d2dc..3218cf34a32 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs @@ -39,6 +39,7 @@ internal class ManifestDocument static readonly XNamespace androidNs = AndroidXmlNamespace; static readonly XNamespace androidToolsNs = AndroidXmlToolsNamespace; static readonly XName versionCodeAttributeName = androidNs + "versionCode"; + static readonly Regex versionCodeRegex = new Regex ("\\{(?([A-Za-z]+)):?[D0-9]*[\\}]", RegexOptions.Compiled); XDocument doc; @@ -1113,7 +1114,6 @@ public bool ValidateVersionCode (out string error, out string errorCode) public void CalculateVersionCode (string currentAbi, string versionCodePattern, string versionCodeProperties) { - var regex = new Regex ("\\{(?([A-Za-z]+)):?[D0-9]*[\\}]"); var kvp = new Dictionary (); foreach (var item in versionCodeProperties?.Split (new char [] { ';', ':' }) ?? []) { var keyValue = item.Split (new char [] { '=' }); @@ -1135,15 +1135,15 @@ public void CalculateVersionCode (string currentAbi, string versionCodePattern, int.TryParse (GetMinimumSdk (), out var minSdk)) { kvp.Add ("minSDK", minSdk); } - var versionCode = String.Empty; - foreach (Match match in regex.Matches (versionCodePattern)) { + var versionCode = new StringBuilder (); + foreach (Match match in versionCodeRegex.Matches (versionCodePattern)) { var key = match.Groups ["key"].Value; var format = match.Value.Replace (key, "0"); if (!kvp.ContainsKey (key)) continue; - versionCode += string.Format (format, kvp [key]); + versionCode.AppendFormat (format, kvp [key]); } - VersionCode = versionCode.TrimStart ('0'); + VersionCode = versionCode.ToString ().TrimStart ('0'); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index 488efd54bd3..08455232c0d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -884,10 +884,7 @@ public static void LogTextStreamContents (TaskLoggingHelper log, string message, public static int GetMinimumApiLevel (AndroidTargetArch arch, AndroidRuntime runtime) { - int minValue = 0; - - Dictionary apiLevels = runtime == AndroidRuntime.MonoVM ? XABuildConfig.ArchToApiLevel : XABuildConfig.ArchToApiLevelNonMono; - if (!apiLevels.TryGetValue (arch, out minValue)) { + if (!XABuildConfig.ArchToApiLevel.TryGetValue (arch, out int minValue)) { throw new InvalidOperationException ($"Unable to determine minimum API level for architecture {arch}"); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeLinker.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeLinker.cs index 05f7568a6ae..1ea2b57c29b 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeLinker.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NativeLinker.cs @@ -402,9 +402,7 @@ bool ExtractDebugSymbols (ITaskItem outputSharedLibrary) void LogFailure () { var sb = MonoAndroidHelper.MergeStdoutAndStderrMessages (stdoutLines, stderrLines); - // TODO: consider making it a warning - // TODO: make it a coded message - log.LogError ("Failed to extract debug info", Path.GetFileName (sourceLib), sb.ToString ()); + log.LogCodedError ("XA3008", Properties.Resources.XA3008, Path.GetFileName (sourceLib), sb.ToString ()); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGeneratorCLR.cs index d4589d3b0a8..284868e2bf8 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGeneratorCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingReleaseNativeAssemblyGeneratorCLR.cs @@ -198,10 +198,6 @@ sealed class ConstructionState StructureInfo typeMapModuleEntryStructureInfo; JavaNameHashComparer javaNameHashComparer; -#pragma warning disable CS0414 // Field is assigned but its value is never used - might be used for debugging or future functionality - ulong moduleCounter = 0; -#pragma warning restore CS0414 - public TypeMappingReleaseNativeAssemblyGeneratorCLR (TaskLoggingHelper log, NativeTypeMappingData mappingData) : base (log) { @@ -242,7 +238,7 @@ protected override void Construct (LlvmIrModule module) }; module.Add (managed_to_java_map); - // Java hashes are output bafore Java type map **and** managed modules, because they will also sort the Java map for us. + // Java hashes are output before Java type map **and** managed modules, because they will also sort the Java map for us. // This is not strictly necessary, as we could do the sorting in the java map BeforeWriteCallback, but this way we save // time sorting only once. var java_to_managed_hashes = new LlvmIrGlobalVariable (typeof(List), "java_to_managed_hashes") { @@ -256,14 +252,6 @@ protected override void Construct (LlvmIrModule module) java_to_managed_hashes.WriteOptions &= ~LlvmIrVariableWriteOptions.ArrayWriteIndexComments; module.Add (java_to_managed_hashes); - // foreach (ModuleMapData mmd in cs.AllModulesData) { - // var mmdVar = new LlvmIrGlobalVariable (mmd.Entries, mmd.SymbolLabel, LlvmIrVariableOptions.LocalConstant) { - // BeforeWriteCallback = SortEntriesAndUpdateJavaIndexes, - // BeforeWriteCallbackCallerState = cs, - // }; - // module.Add (mmdVar); - // } - var modulesMapData = new LlvmIrGlobalVariable (cs.AllModulesMaps, "modules_map_data", LlvmIrVariableOptions.GlobalConstant) { BeforeWriteCallback = SortEntriesAndUpdateJavaIndexes, BeforeWriteCallbackCallerState = cs, diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj index bbf90d51ebe..abf151ec204 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -249,7 +249,7 @@ False @@ -260,6 +260,9 @@ False + + False + diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets index 34f67926f65..1ca5eb6b5c2 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets @@ -1,6 +1,5 @@ - @@ -150,10 +149,8 @@ <_XACommonPropsReplacement Include="@NDK_PKG_REVISION@=26.3.11579264" /> <_XACommonPropsReplacement Include="@NDK_ARM64_V8A_API@=$(AndroidNdkApiLevel_ArmV8a)" /> - <_XACommonPropsReplacement Include="@NDK_ARM64_V8A_API_NON_MONO@=$(AndroidNdkApiLevelNonMono_Arm64)" /> <_XACommonPropsReplacement Include="@NDK_ARMEABI_V7_API@=$(AndroidNdkApiLevel_ArmV7a)" /> <_XACommonPropsReplacement Include="@NDK_X86_64_API@=$(AndroidNdkApiLevel_X86_64)" /> - <_XACommonPropsReplacement Include="@NDK_X86_64_API_NON_MONO@=$(AndroidNdkApiLevelNonMono_X64)" /> <_XACommonPropsReplacement Include="@NDK_X86_API@=$(AndroidNdkApiLevel_X86)" /> <_XACommonPropsReplacement Include="@PACKAGE_VERSION_BUILD@=$(XAVersionCommitCount)" /> <_XACommonPropsReplacement Include="@PACKAGE_VERSION@=$(ProductVersion)" /> @@ -174,7 +171,6 @@ AndroidApiInfo="@(AndroidApiInfo)" TargetApiLevel="$(AndroidDefaultTargetDotnetApiLevel)" MinimumApiLevel="$(AndroidMinimumDotNetApiLevel)" - MinimumNonMonoApiLevel="$(AndroidMinimumNonMonoApiLevel)" OutputFile="Microsoft.Android.Sdk\targets\Microsoft.Android.Sdk.SupportedPlatforms.targets" /> diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in index 02589ee9384..881196597c8 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in @@ -21,6 +21,7 @@ {abi}{versionCode:D5} UpdateGeneratedFiles True + <_AndroidPackageNamingPolicySetByUser Condition=" '$(AndroidPackageNamingPolicy)' != '' ">true LowercaseCrc64 False @BUNDLETOOL_VERSION@ @@ -34,27 +35,21 @@ true - @NDK_ARM64_V8A_API_NON_MONO@ - @NDK_X86_64_API_NON_MONO@ + @NDK_ARM64_V8A_API@ + @NDK_X86_64_API@ - - - @NDK_ARMEABI_V7_API@ - - - @NDK_ARM64_V8A_API@ - - - @NDK_ARM64_V8A_API_NON_MONO@ - - - @NDK_X86_API@ - - - @NDK_X86_64_API@ - - - @NDK_X86_64_API_NON_MONO@ - - + + + @NDK_ARMEABI_V7_API@ + + + @NDK_ARM64_V8A_API@ + + + @NDK_X86_API@ + + + @NDK_X86_64_API@ + + diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 5674bb72ff2..42b4601125c 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -38,7 +38,6 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. - @@ -479,11 +478,8 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. - + Condition=" '$(AndroidHttpClientHandlerType)' != '' "> + @@ -994,6 +990,7 @@ because xbuild doesn't support framework reference assemblies. <_PropertyCacheItems Include="ProjectFullPath=$(MSBuildProjectFullPath)" /> <_PropertyCacheItems Include="AndroidUseDesignerAssembly=$(AndroidUseDesignerAssembly)" /> <_PropertyCacheItems Include="_AndroidTypeMapImplementation=$(_AndroidTypeMapImplementation)" /> + <_PropertyCacheItems Include="_AndroidTrimmableTypeMapMaxArrayRank=$(_AndroidTrimmableTypeMapMaxArrayRank)" /> <_PropertyCacheItems Include="_AndroidUseMarshalMethods=$(_AndroidUseMarshalMethods)" /> <_PropertyCacheItems Include="_AndroidJcwCodegenTarget=$(_AndroidJcwCodegenTarget)" /> @@ -1646,21 +1643,22 @@ because xbuild doesn't support framework reference assemblies. <_GenerateAndroidRemapNativeCodeDependsOn> _ConvertAndroidMamMappingFileToXml; _CollectAndroidRemapMembers; - _PrepareAndroidRemapNativeAssemblySources + _PrepareAndroidRemapNativeAssemblySources; + _GetGeneratePackageManagerJavaInputs + Inputs="@(_GeneratePackageManagerJavaInputs);$(_XARemapMembersFilePath)" + Outputs="$(_AndroidStampDirectory)_GenerateAndroidRemapNativeCode.stamp"> - + <_GeneratePackageManagerJavaInputs Include="@(_GenerateJavaStubsInputs)" /> + <_GeneratePackageManagerJavaInputs Include="$(_XARemapMembersFilePath)" Condition=" '@(_AndroidRemapMembers->Count())' != '0' " /> @@ -1750,7 +1749,6 @@ because xbuild doesn't support framework reference assemblies. AndroidAotMode="$(AndroidAotMode)" AndroidAotEnableLazyLoad="$(AndroidAotEnableLazyLoad)" EnableLLVM="$(EnableLLVM)" - HttpClientHandlerType="$(AndroidHttpClientHandlerType)" TlsProvider="$(AndroidTlsProvider)" Debug="$(AndroidIncludeDebugSymbols)" AndroidSequencePointsMode="$(_SequencePointsMode)" @@ -2860,7 +2858,7 @@ because xbuild doesn't support framework reference assemblies. <_ProjectAndroidManifest>$(ProjectDir)$(AndroidManifest) <_NdkRequired Condition="'$(EnableLLVM)' == 'True'">true - <_NdkRequired Condition="'$(PublishAot)' == 'true'">true + <_NdkRequired Condition="'$(PublishAot)' == 'true' and '$(_AndroidUseWorkloadNativeLinker)' != 'true'">true <_NdkRequired Condition="'$(_NdkRequired)' == ''">false diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Tooling.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Tooling.targets index 9a4f86bc5a2..f14948c9a14 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Tooling.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Tooling.targets @@ -72,6 +72,13 @@ used by all .NET Android projects. + + + <_AndroidNdkDirectory /> + diff --git a/src/Xamarin.Android.Tools.Aidl/AidlCompiler.cs b/src/Xamarin.Android.Tools.Aidl/AidlCompiler.cs index 4f95b8c8205..0fc361e5f74 100644 --- a/src/Xamarin.Android.Tools.Aidl/AidlCompiler.cs +++ b/src/Xamarin.Android.Tools.Aidl/AidlCompiler.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using System.Collections.Generic; using System.IO; @@ -21,10 +22,10 @@ public Result () } Dictionary units = new Dictionary (); - string output_path; - string output_ns; + string? output_path; + string? output_ns; - public event Action FileWritten; + public event Action? FileWritten; public Result Run (ConverterOptions opts, Func resolveAssembly) { @@ -72,14 +73,14 @@ public Result Run (ConverterOptions opts, Func resolv csharp = csharp.Replace (pair.Value.Package.ToString (), output_ns); using (var fw = File.CreateText (file)) fw.Write (csharp); - FileWritten (file, csharp); + FileWritten?.Invoke (file, csharp); } return result; } // This overload is primarily for unit tests. - public Result Run (string input, out string output, AssemblyDefinition[] references = null, ParcelableHandling parcelableHandling = ParcelableHandling.Ignore) + public Result Run (string input, out string? output, AssemblyDefinition[]? references = null, ParcelableHandling parcelableHandling = ParcelableHandling.Ignore) { var result = new Result (); var database = new BindingDatabase (references ?? Array.Empty ()); diff --git a/src/aapt2/aapt2.targets b/src/aapt2/aapt2.targets index abc40372832..6112fc2520d 100644 --- a/src/aapt2/aapt2.targets +++ b/src/aapt2/aapt2.targets @@ -1,32 +1,64 @@ - 4.0.0-6051327 <_Destination>$(MicrosoftAndroidSdkOutDir) obj\$(Configuration)\ + <_AndroidRepositoryUrl>https://dl.google.com/android/repository/ + <_BuildTools Include="build-tools_r$(XABuildToolsVersion)_macosx.zip"> osx Darwin + $(XABuildToolsHashMacOS) aapt2 <_BuildTools Include="build-tools_r$(XABuildToolsVersion)_linux.zip"> linux Linux + $(XABuildToolsHashLinux) aapt2 <_BuildTools Include="build-tools_r$(XABuildToolsVersion)_windows.zip"> windows + $(XABuildToolsHashWindows) aapt2.exe - + + + + + + + + + + + + netstandard2.0 + false + + + + bin\$(Configuration) + + + + + + + + + diff --git a/src/androidsdk/androidsdk.targets b/src/androidsdk/androidsdk.targets new file mode 100644 index 00000000000..3a662d9989a --- /dev/null +++ b/src/androidsdk/androidsdk.targets @@ -0,0 +1,38 @@ + + + + <_SdkManagerDir>$(AndroidSdkFullPath)\cmdline-tools\$(CommandLineToolsFolder)\bin + <_SdkManagerPath Condition=" '$(HostOS)' == 'Windows' ">$(_SdkManagerDir)\sdkmanager.bat + <_SdkManagerPath Condition=" '$(HostOS)' != 'Windows' ">$(_SdkManagerDir)\sdkmanager + <_LicensesAcceptedFile>$(AndroidSdkFullPath)\licenses\.licenses-accepted + + + + + + + + + + + + + + + diff --git a/src/binutils/binutils.csproj b/src/binutils/binutils.csproj new file mode 100644 index 00000000000..7d326dddade --- /dev/null +++ b/src/binutils/binutils.csproj @@ -0,0 +1,17 @@ + + + netstandard2.0 + false + + + + bin\$(Configuration) + + + + + + + + + diff --git a/src/binutils/binutils.targets b/src/binutils/binutils.targets new file mode 100644 index 00000000000..f0300dc276a --- /dev/null +++ b/src/binutils/binutils.targets @@ -0,0 +1,96 @@ + + + <_BinutilsArchiveUrl>https://github.com/dotnet/android-native-tools/releases/download/$(XABinutilsVersion)/xamarin-android-toolchain-$(XABinutilsVersion).7z + <_BinutilsArchive>$(AndroidToolchainCacheDirectory)\xamarin-android-toolchain-$(XABinutilsVersion).7z + <_BinutilsExtractDir>$(AndroidToolchainCacheDirectory)\binutils-$(XABinutilsVersion) + <_BinutilsHostOS>$(HostOS.ToLowerInvariant()) + <_HostBinutilsInstallDir>$(MicrosoftAndroidSdkOutDir)$(HostOS)\binutils + <_WindowsBinutilsInstallDir>$(MicrosoftAndroidSdkOutDir)binutils + <_7zaPath Condition=" '$(HostOS)' == 'Windows' ">$(Pkg7-Zip_CommandLine)\tools\x64\7za.exe + <_7zaPath Condition=" '$(_7zaPath)' == '' ">7za + + + + + + + + + + + + + + + + + + + + + <_HostBinutilsBinFiles Include="$(_BinutilsExtractDir)\$(_BinutilsHostOS)\bin\*" /> + <_HostBinutilsLibFiles Include="$(_BinutilsExtractDir)\$(_BinutilsHostOS)\lib\*" /> + <_WindowsBinutilsBinFiles Include="$(_BinutilsExtractDir)\windows\bin\*" Exclude="$(_BinutilsExtractDir)\windows\bin\*.pdb" /> + <_WindowsBinutilsPdbFiles Include="$(_BinutilsExtractDir)\windows\bin\*.pdb" /> + <_WindowsBinutilsExeForPdb Include="@(_WindowsBinutilsPdbFiles->'$(_BinutilsExtractDir)\windows\bin\%(Filename).exe')" Condition="Exists('%(RootDir)%(Directory)%(Filename).exe')" /> + + + + + + + + + + + + + + + + + + + + diff --git a/src/bundletool/bundletool.csproj b/src/bundletool/bundletool.csproj index 99596487642..299d283bbcf 100644 --- a/src/bundletool/bundletool.csproj +++ b/src/bundletool/bundletool.csproj @@ -7,6 +7,10 @@ bin\$(Configuration) + + + + diff --git a/src/bundletool/bundletool.targets b/src/bundletool/bundletool.targets index 449ab44f493..1306697cf8e 100644 --- a/src/bundletool/bundletool.targets +++ b/src/bundletool/bundletool.targets @@ -1,5 +1,4 @@ - <_Destination>$(MicrosoftAndroidSdkOutDir) obj\$(Configuration)\ @@ -11,9 +10,18 @@ - + + + + diff --git a/src/java-runtime/java-runtime.csproj b/src/java-runtime/java-runtime.csproj index d09fb57b735..a47628385c9 100644 --- a/src/java-runtime/java-runtime.csproj +++ b/src/java-runtime/java-runtime.csproj @@ -18,6 +18,7 @@ + diff --git a/src/manifestmerger/build.gradle b/src/manifestmerger/build.gradle index e67d34e904f..61a060fa37d 100644 --- a/src/manifestmerger/build.gradle +++ b/src/manifestmerger/build.gradle @@ -19,10 +19,10 @@ repositories { dependencies { // https://mvnrepository.com/artifact/com.android.tools.build/manifest-merger - implementation 'com.android.tools.build:manifest-merger:32.2.0' + implementation 'com.android.tools.build:manifest-merger:32.2.1' // ILogger is needed at compile time for the LenientMerger subclass, but // manifest-merger declares common with runtime scope only. - implementation 'com.android.tools:common:32.2.0' + implementation 'com.android.tools:common:32.2.1' } sourceSets { diff --git a/src/manifestmerger/manifestmerger.csproj b/src/manifestmerger/manifestmerger.csproj index 7aafd3b11a8..0af62c782e3 100644 --- a/src/manifestmerger/manifestmerger.csproj +++ b/src/manifestmerger/manifestmerger.csproj @@ -5,6 +5,10 @@ false bin\$(Configuration) + + + + diff --git a/src/native/CMakePresets.json.in b/src/native/CMakePresets.json.in index fb5c77267de..964103d5903 100644 --- a/src/native/CMakePresets.json.in +++ b/src/native/CMakePresets.json.in @@ -108,17 +108,6 @@ } }, - { - "name": "nonmono-common-arm64-v8a", - "hidden": true, - "cacheVariables": { - "ANDROID_ABI": "arm64-v8a", - "ANDROID_NATIVE_API_LEVEL": "@NDK_ARM64_V8A_NONMONO_API_NET@", - "ANDROID_PLATFORM": "android-@NDK_ARM64_V8A_NONMONO_API_NET@", - "ANDROID_RID": "android-arm64" - } - }, - { "name": "common-x86", "hidden": true, @@ -141,17 +130,6 @@ } }, - { - "name": "nonmono-common-x86_64", - "hidden": true, - "cacheVariables": { - "ANDROID_ABI": "x86_64", - "ANDROID_NATIVE_API_LEVEL": "@NDK_X86_64_NONMONO_API_NET@", - "ANDROID_PLATFORM": "android-@NDK_X86_64_NONMONO_API_NET@", - "ANDROID_RID": "android-x64" - } - }, - { "name": "asan-common", "hidden": true, @@ -219,12 +197,12 @@ { "name": "nativeaot-default-debug-arm64-v8a", - "inherits": ["nativeaot-default-common", "common-debug", "nonmono-common-arm64-v8a"] + "inherits": ["nativeaot-default-common", "common-debug", "common-arm64-v8a"] }, { "name": "coreclr-default-debug-arm64-v8a", - "inherits": ["default-common", "common-debug", "nonmono-common-arm64-v8a"] + "inherits": ["default-common", "common-debug", "common-arm64-v8a"] }, { @@ -234,12 +212,12 @@ { "name": "nativeaot-default-release-arm64-v8a", - "inherits": ["nativeaot-default-common", "common-release", "nonmono-common-arm64-v8a"] + "inherits": ["nativeaot-default-common", "common-release", "common-arm64-v8a"] }, { "name": "coreclr-default-release-arm64-v8a", - "inherits": ["default-common", "common-release", "nonmono-common-arm64-v8a"] + "inherits": ["default-common", "common-release", "common-arm64-v8a"] }, { @@ -327,12 +305,12 @@ { "name": "nativeaot-default-debug-x86_64", - "inherits": ["nativeaot-default-common", "common-debug", "nonmono-common-x86_64"] + "inherits": ["nativeaot-default-common", "common-debug", "common-x86_64"] }, { "name": "coreclr-default-debug-x86_64", - "inherits": ["default-common", "common-debug", "nonmono-common-x86_64"] + "inherits": ["default-common", "common-debug", "common-x86_64"] }, { @@ -342,12 +320,12 @@ { "name": "nativeaot-default-release-x86_64", - "inherits": ["nativeaot-default-common", "common-release", "nonmono-common-x86_64"] + "inherits": ["nativeaot-default-common", "common-release", "common-x86_64"] }, { "name": "coreclr-default-release-x86_64", - "inherits": ["default-common", "common-release", "nonmono-common-x86_64"] + "inherits": ["default-common", "common-release", "common-x86_64"] }, { diff --git a/src/native/native.targets b/src/native/native.targets index 1f2e4d3cbe6..f6f8ddfc28f 100644 --- a/src/native/native.targets +++ b/src/native/native.targets @@ -85,6 +85,7 @@ <_ArchiveDSOInput Include="common\archive-dso-stub\CMakeLists.txt" /> <_ArchiveDSOOutput Include="@(AndroidSupportedTargetJitAbi->'$(FlavorIntermediateOutputPath)\%(AndroidRID)-archive-dso-stub\CMakeCache.txt')" /> + <_ArchiveDSOOutput Include="@(AndroidSupportedTargetJitAbi->'$(OutputPath)\%(AndroidRID)\libarchive-dso-stub.so')" /> <_ArchiveOutputDirToCreate Include="$(FlavorIntermediateOutputPath)\%(AndroidSupportedTargetJitAbi.AndroidRID)-archive-dso-stub" /> @@ -366,7 +367,7 @@ RuntimePackName="$(_RuntimePackName)" /> - + <_RuntimePackFiles Include="$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\%(AndroidSupportedTargetJitAbi.AndroidRID)\*.o" AndroidRID="%(AndroidSupportedTargetJitAbi.AndroidRID)" AndroidRuntime="$(CMakeRuntimeFlavor)" @@ -377,13 +378,6 @@ RuntimePackName="$(_RuntimePackName)" /> - - <_RuntimePackFiles Include="$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\%(AndroidSupportedTargetJitAbi.AndroidRID)\*.a" - AndroidRID="%(AndroidSupportedTargetJitAbi.AndroidRID)" - AndroidRuntime="$(CMakeRuntimeFlavor)" - RuntimePackName="$(_RuntimePackName)" /> - - FindClass ("mono/android/IGCUserPeer"); + if (lrefIGCUserPeer == nullptr) [[unlikely]] { + env->ExceptionDescribe (); + env->ExceptionClear (); + abort_unless (false, "Failed to load mono/android/IGCUserPeer class"); + } + + jclass lrefGCUserPeerable = env->FindClass ("net/dot/jni/GCUserPeerable"); + if (lrefGCUserPeerable == nullptr) [[unlikely]] { + env->ExceptionDescribe (); + env->ExceptionClear (); + abort_unless (false, "Failed to load net/dot/jni/GCUserPeerable class"); + } + initArgs->logCategories = log_categories; + initArgs->grefGcThreshold = static_cast(AndroidSystem::get_gref_gc_threshold ()); + initArgs->grefIGCUserPeer = env->NewGlobalRef (lrefIGCUserPeer); + initArgs->grefGCUserPeerable = env->NewGlobalRef (lrefGCUserPeerable); + + env->DeleteLocalRef (lrefIGCUserPeer); + env->DeleteLocalRef (lrefGCUserPeerable); } diff --git a/src/openjdk/openjdk.csproj b/src/openjdk/openjdk.csproj new file mode 100644 index 00000000000..ee545d45a03 --- /dev/null +++ b/src/openjdk/openjdk.csproj @@ -0,0 +1,13 @@ + + + netstandard2.0 + false + + + + bin\$(Configuration) + + + + + diff --git a/src/openjdk/openjdk.targets b/src/openjdk/openjdk.targets new file mode 100644 index 00000000000..dc35ed2d59f --- /dev/null +++ b/src/openjdk/openjdk.targets @@ -0,0 +1,105 @@ + + + + + <_OpenJDKPlatform>windows-x64 + <_OpenJDKExtension>.zip + + + <_OpenJDKPlatform>linux-x64 + <_OpenJDKExtension>.tar.gz + + + <_OpenJDKPlatform>macos-x64 + <_OpenJDKExtension>.tar.gz + + + <_OpenJDKPlatform>macos-aarch64 + <_OpenJDKExtension>.tar.gz + + + + <_OpenJDKFileName>microsoft-jdk-$(MicrosoftOpenJDKVersion)-$(_OpenJDKPlatform)$(_OpenJDKExtension) + <_OpenJDKHashFileName>$(_OpenJDKFileName).sha256sum.txt + <_OpenJDKArchiveUrl>https://aka.ms/download-jdk/$(_OpenJDKFileName) + <_OpenJDKHashUrl>$(_OpenJDKArchiveUrl).sha256sum.txt + <_OpenJDKArchive>$(AndroidToolchainCacheDirectory)\$(_OpenJDKFileName) + <_OpenJDKHashFile>$(AndroidToolchainCacheDirectory)\$(_OpenJDKHashFileName) + <_OpenJDKInstallDir>$(AndroidToolchainDirectory)\$(MicrosoftOpenJDKFolder) + <_OpenJDKVersionFile>$(_OpenJDKInstallDir)\xa_jdk_version.txt + + <_OpenJDKStripComponents Condition=" '$(HostOS)' != 'Darwin' ">1 + <_OpenJDKStripComponents Condition=" '$(HostOS)' == 'Darwin' ">4 + + + + + + + + <_OpenJDKExpectedHash>$([System.IO.File]::ReadAllText('$(_OpenJDKHashFile)').Substring(0, 64).ToUpperInvariant()) + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/profiled-aot/dotnet.aotprofile.txt b/src/profiled-aot/dotnet.aotprofile.txt index 94c18039f44..a4ae63547a9 100644 --- a/src/profiled-aot/dotnet.aotprofile.txt +++ b/src/profiled-aot/dotnet.aotprofile.txt @@ -10,7 +10,6 @@ Methods: bool Android.OS.Handler:Post (Java.Lang.IRunnable) bool Android.OS.Handler:Post (System.Action) bool Android.Runtime.AndroidEnvironment:Extends (System.Type,string) - bool Android.Runtime.AndroidEnvironment:IsAcceptableHttpMessageHandlerType (System.Type) bool Android.Runtime.AndroidObjectReferenceManager:get_LogGlobalReferenceMessages () bool Android.Runtime.AndroidValueManager:ShouldReplaceMapping (System.WeakReference`1,Java.Interop.JniObjectReference,Java.Interop.IJavaPeerable,Java.Interop.IJavaPeerable&) bool Android.Runtime.InputStreamInvoker:get_CanRead () diff --git a/src/proguard-android/proguard-android.csproj b/src/proguard-android/proguard-android.csproj index 80ee062aaf8..0f4530816e1 100644 --- a/src/proguard-android/proguard-android.csproj +++ b/src/proguard-android/proguard-android.csproj @@ -5,6 +5,11 @@ false bin\$(Configuration) + + + + + \ No newline at end of file diff --git a/src/r8/r8.csproj b/src/r8/r8.csproj index f355168160b..c658a9323ed 100644 --- a/src/r8/r8.csproj +++ b/src/r8/r8.csproj @@ -12,6 +12,7 @@ + diff --git a/tests/MSBuildDeviceIntegration/Resources/LinkDescTest/MainActivityReplacement.cs b/tests/MSBuildDeviceIntegration/Resources/LinkDescTest/MainActivityReplacement.cs index fec93a94af5..6136877cbc5 100644 --- a/tests/MSBuildDeviceIntegration/Resources/LinkDescTest/MainActivityReplacement.cs +++ b/tests/MSBuildDeviceIntegration/Resources/LinkDescTest/MainActivityReplacement.cs @@ -96,18 +96,6 @@ protected override void OnCreate(Bundle bundle) Android.Util.Log.Info(TAG, $"[FAIL] Unable to access 'IsPreserved' field of 'LinkerClass'.\n{ex}"); } - // [Test] TryCreateInstanceOfNonXmlPreservedClass - try - { - var asm = typeof(Library1.SomeClass).Assembly; - var o = Activator.CreateInstance(asm.GetType("Library1.NonPreserved")); - Android.Util.Log.Info(TAG, $"[LINKALLFAIL] Able to create instance of '{o.GetType().Name}' which should have been linked away."); - } - catch (Exception ex) - { - Android.Util.Log.Info(TAG, $"[LINKALLPASS] Unable to create instance of 'NonPreserved' as expected.\n{ex}"); - } - // [Test] Post Android.Util.Log.Info(TAG, HttpClientTest.Post ()); diff --git a/tests/MSBuildDeviceIntegration/Tests/AotProfileTests.cs b/tests/MSBuildDeviceIntegration/Tests/AotProfileTests.cs index 641b2e4bfae..344b43caf31 100644 --- a/tests/MSBuildDeviceIntegration/Tests/AotProfileTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/AotProfileTests.cs @@ -33,7 +33,7 @@ public void BuildBasicApplicationAndAotProfileIt () }; // MonoVM-only test proj.SetRuntime (Android.Tasks.AndroidRuntime.MonoVM); - proj.SetAndroidSupportedAbis (DeviceAbi); + proj.SetRuntimeIdentifiers (new[] { DeviceAbi }); // TODO: only needed in .NET 6+ // See https://github.com/dotnet/runtime/issues/56989 diff --git a/tests/MSBuildDeviceIntegration/Tests/BundleToolTests.cs b/tests/MSBuildDeviceIntegration/Tests/BundleToolTests.cs index c970517884d..3a4e0bfa47a 100644 --- a/tests/MSBuildDeviceIntegration/Tests/BundleToolTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/BundleToolTests.cs @@ -118,7 +118,7 @@ public void OneTimeSetUp () //NOTE: this is here to enable adb shell run-as app.AndroidManifest = app.AndroidManifest.Replace ("= TimeSpan.Zero) { Thread.Sleep (10); timeout = timeout.Subtract (TimeSpan.FromMilliseconds (10)); diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index 7f88cfe0caf..cb0ab1c00ba 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -618,13 +618,13 @@ public void GlobalLayoutEvent_ShouldRegisterAndFire_OnActivityLaunch ([Values] b var proj = new XamarinAndroidApplicationProject (packageName: PackageUtils.MakePackageName (runtime)) { IsRelease = isRelease, - SupportedOSPlatformVersion = "23", + SupportedOSPlatformVersion = "24", }; proj.SetRuntime (runtime); if (isRelease || !TestEnvironment.CommercialBuildAvailable) { if (runtime == AndroidRuntime.MonoVM) { - proj.SetAndroidSupportedAbis ("armeabi-v7a", "arm64-v8a", "x86", "x86_64"); + proj.SetRuntimeIdentifiers (new[] { "armeabi-v7a", "arm64-v8a", "x86", "x86_64" }); } else { proj.SetRuntimeIdentifiers (new [] {"arm64-v8a", "x86_64"}); } @@ -661,7 +661,7 @@ public void SubscribeToAppDomainUnhandledException ([Values] AndroidRuntime runt }; proj.SetRuntime (runtime); if (runtime == AndroidRuntime.MonoVM) { - proj.SetAndroidSupportedAbis ("armeabi-v7a", "arm64-v8a", "x86", "x86_64"); + proj.SetRuntimeIdentifiers (new[] { "armeabi-v7a", "arm64-v8a", "x86", "x86_64" }); } else { proj.SetRuntimeIdentifiers (new [] {"arm64-v8a", "x86_64"}); } @@ -746,7 +746,7 @@ public void UnhandledExceptionFromButtonClick ([Values (AndroidRuntime.MonoVM, A { proj = new XamarinAndroidApplicationProject (); proj.SetRuntime (runtime); - proj.SetAndroidSupportedAbis (DeviceAbi); + proj.SetRuntimeIdentifiers (new[] { DeviceAbi }); proj.MainActivity = proj.DefaultMainActivity.Replace ("//${AFTER_ONCREATE}", """ Android.Runtime.AndroidEnvironment.UnhandledExceptionRaiser += (sender, e) => { @@ -775,6 +775,77 @@ public void UnhandledExceptionFromButtonClick ([Values (AndroidRuntime.MonoVM, A $"Output did not contain {expectedRaiser}!"); } + [Test] + public void NativeCrashProducesManagedStackTrace ([Values (AndroidRuntime.CoreCLR)] AndroidRuntime runtime) + { + // This test verifies that when a native crash (SIGSEGV) occurs, the .NET runtime + // logs a managed stack trace in logcat BEFORE Android's signal handler takes over. + // This is enabled by the System.Runtime.CrashReportBeforeSignalChaining config option. + // See: https://github.com/dotnet/android/pull/11291 + // See: https://github.com/dotnet/runtime/pull/123735 + // See: https://github.com/dotnet/runtime/pull/123824 + const bool isRelease = true; + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + + proj = new XamarinAndroidApplicationProject (packageName: PackageUtils.MakePackageName (runtime)) { + IsRelease = isRelease, + }; + proj.SetRuntime (runtime); + proj.SetProperty ("AllowUnsafeBlocks", "true"); + proj.SetRuntimeIdentifiers (new[] { DeviceAbi }); + + proj.MainActivity = proj.DefaultMainActivity + .Replace ("//${USINGS}", "using System.Runtime.InteropServices;") + .Replace ("//${AFTER_ONCREATE}", """ + // Force a native crash (SIGSEGV) via P/Invoke to libc memset with a null pointer. + // The .NET runtime's crash reporting should log the managed stack trace before + // Android's signal handler aborts the process. + CrashHelper.ForceNativeSegfault (); + """) + .Replace ("//${AFTER_MAINACTIVITY}", """ + static class CrashHelper + { + [DllImport ("libc", EntryPoint = "memset")] + static extern unsafe void MemSet (void* ptr, int value, nuint count); + + public static unsafe void ForceNativeSegfault () + { + MemSet (null, 0, (nuint)1); + } + } + """); + + builder = CreateApkBuilder (); + Assert.IsTrue (builder.Install (proj), "Install should have succeeded."); + ClearAdbLogcat (); + AdbStartActivity ($"{proj.PackageName}/{proj.JavaPackageName}.MainActivity"); + + // CoreCLR logs the managed stack trace via the DOTNET logcat tag when crash reporting + // runs before signal chaining. The output includes: + // E DOTNET : Got a SIGSEGV while executing native code. ... + // E DOTNET : at .CrashHelper.ForceNativeSegfault() + string logcatFile = Path.Combine (Root, builder.ProjectDirectory, "crash-logcat.log"); + bool foundSigsegv = false; + bool foundManagedFrame = false; + Assert.IsTrue ( + MonitorAdbLogcat ( + (line) => { + if (line.Contains ("DOTNET") && line.Contains ("SIGSEGV")) { + foundSigsegv = true; + } + if (line.Contains ("DOTNET") && line.Contains ("ForceNativeSegfault")) { + foundManagedFrame = true; + } + return foundSigsegv && foundManagedFrame; + }, + logcatFilePath: logcatFile, + timeout: 30), + "Crash reporting should have logged 'SIGSEGV' and a managed stack trace containing 'ForceNativeSegfault' via the DOTNET logcat tag. " + + "Verify the System.Runtime.CrashReportBeforeSignalChaining config option is set and the runtime supports it."); + } + [Test] [Category ("UsesDevice")] [TestCaseSource (nameof (Get_SmokeTestBuildAndRunWithSpecialCharacters_Data))] @@ -804,7 +875,7 @@ public void SmokeTestBuildAndRunWithSpecialCharacters (string testName, AndroidR IsRelease = isRelease, }; proj.SetRuntime (runtime); - proj.SetAndroidSupportedAbis (DeviceAbi); + proj.SetRuntimeIdentifiers (new[] { DeviceAbi }); proj.SetDefaultTargetDevice (); using (var builder = CreateApkBuilder (Path.Combine (rootPath, proj.ProjectName))){ Assert.IsTrue (builder.Install (proj), "Install should have succeeded."); @@ -864,9 +935,6 @@ [Values] AndroidRuntime runtime new BuildItem.Source ("SomeClass.cs") { TextContent = () => "namespace Library1 { public class SomeClass { } }" }, - new BuildItem.Source ("NonPreserved.cs") { - TextContent = () => "namespace Library1 { public class NonPreserved { } }" - }, new BuildItem.Source ("LinkerClass.cs") { TextContent = () => @" namespace Library1 { @@ -1016,7 +1084,7 @@ public void JsonDeserializationCreatesJavaHandle ([Values] bool isRelease, [Valu proj.SetProperty ("NoWarn", "SYSLIB0011"); if (isRelease || !TestEnvironment.CommercialBuildAvailable) { - proj.SetAndroidSupportedAbis (DeviceAbi); + proj.SetRuntimeIdentifiers (new[] { DeviceAbi }); } proj.References.Add (new BuildItem.Reference ("System.Runtime.Serialization")); @@ -1116,7 +1184,7 @@ public void RunWithInterpreterEnabled ([Values] bool isRelease, [Values] Android }; proj.SetRuntime (runtime); var abis = new string[] { "armeabi-v7a", "arm64-v8a", "x86", "x86_64" }; - proj.SetAndroidSupportedAbis (abis); + proj.SetRuntimeIdentifiers (abis); proj.SetProperty (proj.CommonProperties, "UseInterpreter", "True"); builder = CreateApkBuilder (); builder.BuildLogFile = "install.log"; @@ -1152,7 +1220,7 @@ public void RunWithLLVMEnabled () }; // Mono-only test proj.SetRuntime (AndroidRuntime.MonoVM); - proj.SetAndroidSupportedAbis ("armeabi-v7a", "arm64-v8a", "x86", "x86_64"); + proj.SetRuntimeIdentifiers (new[] { "armeabi-v7a", "arm64-v8a", "x86", "x86_64" }); proj.SetProperty ("EnableLLVM", true.ToString ()); builder = CreateApkBuilder (); @@ -1211,7 +1279,7 @@ public void SingleProject_ApplicationId ([Values] bool testOnly, [Values] Androi if (testOnly) proj.AndroidManifest = proj.AndroidManifest.Replace (" throw new NotSupportedException ($"Unsupported runtime {runtime}") }; - proj.SetAndroidSupportedAbis (abis); + proj.SetRuntimeIdentifiers (abis); var libBuilder = CreateDllBuilder (Path.Combine (rootPath, lib.ProjectName)); Assert.IsTrue (libBuilder.Build (lib), "Library should have built succeeded."); builder = CreateApkBuilder (Path.Combine (rootPath, proj.ProjectName)); @@ -1397,7 +1465,7 @@ public void CheckXamarinFormsAppDeploysAndAButtonWorks ([Values] AndroidRuntime IsRelease = isRelease, }; proj.SetRuntime (runtime); - proj.SetAndroidSupportedAbis (DeviceAbi); + proj.SetRuntimeIdentifiers (new[] { DeviceAbi }); var builder = CreateApkBuilder (packageName: packageName); Assert.IsTrue (builder.Build (proj), "Build should have succeeded."); @@ -1664,7 +1732,7 @@ public void DotNetInstallAndRunPreviousSdk ( proj.SetRuntime (runtime); // Requires 32-bit ABIs - proj.SetAndroidSupportedAbis (["armeabi-v7a", "arm64-v8a", "x86", "x86_64"]); + proj.SetRuntimeIdentifiers (new[] { "armeabi-v7a", "arm64-v8a", "x86", "x86_64" }); var builder = CreateApkBuilder (); Assert.IsTrue (builder.Build (proj), "`dotnet build` should succeed"); @@ -1874,10 +1942,9 @@ public void SupportDesugaringStaticInterfaceMethods ([Values] AndroidRuntime run }; proj.SetRuntime (runtime); - // Note: To properly test, Desugaring must be *enabled*, which requires that - // `$(SupportedOSPlatformVersion)` be *less than* 23. 21 is currently the default, - // but set this explicitly anyway just so that this implicit requirement is explicit. - proj.SupportedOSPlatformVersion = "21"; + // Note: To properly test, static interface default methods (Java 8+) must be compiled correctly. + // With $(SupportedOSPlatformVersion) >= 24, D8 handles them natively without desugaring. + proj.SupportedOSPlatformVersion = "24"; proj.MainActivity = proj.DefaultMainActivity.Replace ("//${AFTER_ONCREATE}", @" Console.WriteLine ($""# jonp static interface default method invocation; IStaticMethodsInterface.Value={Example.IStaticMethodsInterface.Value}""); @@ -2109,7 +2176,7 @@ public void MicrosoftIntune ([Values] bool isRelease, [Values] AndroidRuntime ru .Replace ("Icon = \"@drawable/icon\")]", "Icon = \"@drawable/icon\", Theme = \"@style/Theme.AppCompat.Light.DarkActionBar\")]") .Replace ("public class MainActivity : Activity", "public class MainActivity : AndroidX.AppCompat.App.AppCompatActivity"); var abis = new string [] { "armeabi-v7a", "arm64-v8a", "x86", "x86_64" }; - proj.SetAndroidSupportedAbis (abis); + proj.SetRuntimeIdentifiers (abis); builder = CreateApkBuilder (); builder.BuildLogFile = "install.log"; Assert.IsTrue (builder.Install (proj), "Install should have succeeded."); @@ -2372,10 +2439,13 @@ public void DotNetNewAndroidTest (string mode, AndroidRuntime runtime) Assert.IsTrue (dotnet.Build (target: "Install", parameters: buildParameters.ToArray ()), "`dotnet build -t:Install` should succeed"); // Run based on mode - var runParameters = buildParameters.Select (p => $"/p:{p}").ToArray (); + var runParameters = buildParameters.Select (p => $"/p:{p}").ToList (); + if (mode == "test") + runParameters.Add ("--report-trx"); + using var process = mode == "run" - ? dotnet.StartRun (waitForExit: true, parameters: runParameters) - : dotnet.StartTest (parameters: runParameters); + ? dotnet.StartRun (waitForExit: true, parameters: runParameters.ToArray ()) + : dotnet.StartTest (parameters: runParameters.ToArray ()); var locker = new Lock (); var output = new StringBuilder (); @@ -2428,6 +2498,17 @@ public void DotNetNewAndroidTest (string mode, AndroidRuntime runtime) StringAssert.Contains ("succeeded: 1", outputText, $"Output should report 1 passed test. See {logPath} for details."); StringAssert.Contains ("failed: 1", outputText, $"Output should report 1 failed test. See {logPath} for details."); StringAssert.Contains ("skipped: 1", outputText, $"Output should report 1 skipped test. See {logPath} for details."); + + // Verify that a TRX file was produced by --report-trx + var trxFiles = Directory.GetFiles (projectDirectory, "*.trx", SearchOption.AllDirectories); + Assert.IsTrue (trxFiles.Length > 0, $"Expected at least one .trx file in {projectDirectory}. See {logPath} for details."); + + TestContext.AddTestAttachment (trxFiles [0]); + + var trxDoc = XDocument.Load (trxFiles [0]); + var trxNs = trxDoc.Root?.Name.Namespace ?? XNamespace.None; + var resultSummary = trxDoc.Root?.Element (trxNs + "ResultSummary"); + Assert.IsNotNull (resultSummary, $"TRX file should contain a ResultSummary element. File: {trxFiles [0]}"); } } diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallTests.cs index d755a222782..72e37f62ec8 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallTests.cs @@ -38,7 +38,7 @@ public void ReInstallIfUserUninstalled ([Values (false, true)] bool isRelease) IsRelease = isRelease, }; if (isRelease) { - proj.SetAndroidSupportedAbis (DeviceAbi); + proj.SetRuntimeIdentifiers (new[] { DeviceAbi }); } using (var builder = CreateApkBuilder ()) { Assert.IsTrue (builder.Build (proj)); @@ -68,7 +68,7 @@ public void InstallAndUnInstall ([Values (false, true)] bool isRelease) if (isRelease) { // Set debuggable=true to allow run-as command usage with a release build proj.AndroidManifest = proj.AndroidManifest.Replace ("/..." while the new scanner + // produces "scrc64/..." (System.IO.Hashing CRC64) by default. Normalize both + // to a single sentinel so the type maps can be compared structurally. + bool hasCrc64Prefix = javaName.StartsWith ("crc64", StringComparison.Ordinal) + || javaName.StartsWith ("scrc64", StringComparison.Ordinal); + if (hasCrc64Prefix) { int slash = javaName.IndexOf ('/'); if (slash > 0) { return "crc64.../" + javaName.Substring (slash + 1); @@ -83,6 +88,30 @@ static string NormalizeCrc64 (string javaName) return javaName; } + static string NormalizeCrc64InJniValue (string value) + { + // Marshal-method signatures/connectors can embed generated user-peer JNI names. + // Normalize the hash portion there too so legacy/new scanner comparison stays + // structural instead of depending on the CRC64 implementation. + return System.Text.RegularExpressions.Regex.Replace (value, @"s?crc64[0-9a-f]{16}", "crc64..."); + } + + static List NormalizeMethodGroups (List groups) + { + return groups + .Select (g => new TypeMethodGroup ( + g.ManagedName, + g.Methods + .Select (m => new MethodEntry ( + NormalizeCrc64 (m.JniName), + NormalizeCrc64InJniValue (m.JniSignature), + m.Connector is null ? null : NormalizeCrc64InJniValue (m.Connector) + )) + .ToList () + )) + .ToList (); + } + void AssertTypeMapMatch (List legacy, List newEntries) { var legacyMap = legacy.GroupBy (e => e.JavaName).ToDictionary (g => g.Key, g => g.ToList ()); diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerComparisonTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerComparisonTests.cs index fcfe51cb1df..2db61b9ca8d 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerComparisonTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerComparisonTests.cs @@ -132,9 +132,9 @@ public void ExactMarshalMethods_UserTypesFixture () var (_, newMethods) = ScannerRunner.RunNew (paths); var legacyNormalized = legacyMethods -.ToDictionary (kvp => NormalizeCrc64 (kvp.Key), kvp => kvp.Value); +.ToDictionary (kvp => NormalizeCrc64 (kvp.Key), kvp => NormalizeMethodGroups (kvp.Value)); var newNormalized = newMethods -.ToDictionary (kvp => NormalizeCrc64 (kvp.Key), kvp => kvp.Value); +.ToDictionary (kvp => NormalizeCrc64 (kvp.Key), kvp => NormalizeMethodGroups (kvp.Value)); var result = MarshalMethodDiffHelper.CompareUserTypeMarshalMethods (legacyNormalized, newNormalized); AssertNoDiffs ("MISSING from new scanner", result.Missing); diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerExportShapesTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerExportShapesTests.cs new file mode 100644 index 00000000000..ed3f727ad71 --- /dev/null +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerExportShapesTests.cs @@ -0,0 +1,263 @@ +using System.IO; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using Xunit; + +namespace Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests; + +/// +/// Integration coverage for the trimmable scanner's [Export] handling on +/// shapes that the legacy JCW emitter (CecilImporter.GetJniSignature) cannot +/// encode: enum-typed parameters / returns, ICharSequence, and non-generic +/// IList / IDictionary / ICollection. ScannerComparisonTests.RunLegacy falls +/// back to direct [Register] extraction for these types (yields no entries), +/// so legacy↔new comparison is intentionally skipped — these tests assert +/// the new scanner produces the right JNI signatures end-to-end. +/// +public class ScannerExportShapesTests +{ + static string UserTypesFixturePath { + get { + var testDir = Path.GetDirectoryName (typeof (ScannerExportShapesTests).Assembly.Location) + ?? throw new System.InvalidOperationException ("Could not determine test assembly directory."); + var path = Path.Combine (testDir, "UserTypesFixture.dll"); + Assert.True (File.Exists (path), $"UserTypesFixture.dll not found at '{path}'."); + return path; + } + } + + static MarshalMethodInfo[] GetMarshalMethods (string javaName) + { + var fixturePath = UserTypesFixturePath; + var dir = AssertNotNull (Path.GetDirectoryName (fixturePath)); + + var paths = new System.Collections.Generic.List { fixturePath }; + var monoAndroid = Path.Combine (dir, "Mono.Android.dll"); + var javaInterop = Path.Combine (dir, "Java.Interop.dll"); + if (File.Exists (monoAndroid)) + paths.Add (monoAndroid); + if (File.Exists (javaInterop)) + paths.Add (javaInterop); + + using var scanner = new JavaPeerScanner (); + var peReaders = new System.Collections.Generic.List (); + try { + var assemblies = new System.Collections.Generic.List<(string Name, PEReader Reader)> (); + foreach (var p in paths) { + var pe = new PEReader (File.OpenRead (p)); + peReaders.Add (pe); + var md = pe.GetMetadataReader (); + assemblies.Add ((md.GetString (md.GetAssemblyDefinition ().Name), pe)); + } + + var peers = scanner.Scan (assemblies); + var peer = peers.FirstOrDefault (p => p.ManagedTypeName.EndsWith (javaName)); + return AssertNotNull (peer).MarshalMethods.ToArray (); + } finally { + foreach (var pe in peReaders) + pe.Dispose (); + } + } + + static T AssertNotNull (T? value) where T : class + { + Assert.NotNull (value); + return value ?? throw new System.InvalidOperationException ("Expected non-null value."); + } + + static void AssertHasExport (MarshalMethodInfo[] methods, string jniName, string jniSignature) + { + var match = methods.FirstOrDefault (m => m.JniName == jniName && m.JniSignature == jniSignature); + Assert.True (match != null, + $"Expected [Export] marshal method '{jniName}{jniSignature}' not found. " + + $"Discovered: {string.Join (", ", methods.Select (m => m.JniName + m.JniSignature))}"); + match = AssertNotNull (match); + // [Export] methods carry no Connector — legacy uses __export__ at runtime, + // trimmable wires registration via UCO fnptr. [ExportField] methods do + // surface the "__export__" connector by design (matches legacy + // CecilImporter behaviour), so accept that case too. + Assert.True (match.Connector is null || match.Connector == "__export__", + $"Unexpected connector '{match.Connector}' on {jniName}{jniSignature}."); + } + + [Fact] + public void ExportField_RegistersGetterAsMarshalMethod () + { + var methods = GetMarshalMethods ("ExportFieldShapes"); + + // [ExportField] uses the managed method name as the JNI method name + // (legacy Mono.Android.Export does the same thing). The signatures + // below match the underlying CLR method shape. + // User-peer return type uses a CRC64-based package name; assert by prefix + // so the test isn't tied to the exact CRC64 hash of the assembly. + var getInstance = System.Array.Find (methods, m => m.JniName == "GetInstance"); + getInstance = AssertNotNull (getInstance); + Assert.EndsWith ("/ExportFieldShapes;", getInstance.JniSignature); + Assert.StartsWith ("()L", getInstance.JniSignature); + Assert.DoesNotContain ("Ljava/lang/Object;", getInstance.JniSignature); + + AssertHasExport (methods, "GetValue", "()Ljava/lang/String;"); + AssertHasExport (methods, "GetCount", "()I"); + } + + // === Phase A: dispatch & declaration shapes === + + [Fact] + public void Export_WithThrowsClause_SurfacesDeclaredExceptions () + { + var methods = GetMarshalMethods ("ExportThrowsShapes"); + + var ioCall = System.Array.Find (methods, m => m.JniName == "ioCall"); + ioCall = AssertNotNull (ioCall); + var ioThrownNames = AssertNotNull (ioCall.ThrownNames); + Assert.Contains ("java/io/IOException", ioThrownNames); + + var multiThrow = System.Array.Find (methods, m => m.JniName == "multiThrow"); + multiThrow = AssertNotNull (multiThrow); + var multiThrownNames = AssertNotNull (multiThrow.ThrownNames); + Assert.Contains ("java/io/IOException", multiThrownNames); + Assert.Contains ("java/lang/IllegalStateException", multiThrownNames); + } + + [Fact] + public void MixedRegisterAndExport_BothPathsSurface () + { + var methods = GetMarshalMethods ("MixedRegisterAndExport"); + + // [Register]-driven Activity override carries a connector + var onCreate = System.Array.Find (methods, m => m.JniName == "onCreate"); + onCreate = AssertNotNull (onCreate); + Assert.False (onCreate.Connector is null or "__export__", + $"OnCreate override should have a real Get*Handler connector, got '{onCreate.Connector}'."); + + // [Export]-driven new methods carry no connector (or "__export__") + AssertHasExport (methods, "doWork", "()V"); + AssertHasExport (methods, "compute", "(I)I"); + } + + [Fact] + public void VirtualExport_TopMostDeclarationRegisters () + { + var baseMethods = GetMarshalMethods ("VirtualExportBase"); + AssertHasExport (baseMethods, "ping", "()I"); + + var derivedMethods = GetMarshalMethods ("VirtualExportDerived"); + // Derived class doesn't re-declare [Export]; only the base [Export] applies, + // so the derived peer should NOT add a duplicate marshal-method entry of its + // own. (Legacy CecilImporter walks up the inheritance chain and registers + // the [Export] on the topmost declaring type.) + var derivedPing = System.Array.FindAll (derivedMethods, m => m.JniName == "ping"); + Assert.True (derivedPing.Length <= 1, + $"Derived peer should not duplicate base's [Export] entry, found {derivedPing.Length}."); + } + + [Fact] + public void Export_CustomJniName_NotIdentityMappedFromMethodName () + { + var methods = GetMarshalMethods ("ExportRenameShapes"); + + // JNI name comes from [Export("javaSideName")], not from "CSharpSideName". + Assert.Contains (methods, m => m.JniName == "javaSideName" && m.JniSignature == "()V"); + Assert.DoesNotContain (methods, m => m.JniName == "CSharpSideName"); + } + + // === Phase B: edge marshalling === + + [Fact] + public void Export_JavaLangObjectExplicitly_KeepsObjectDescriptor () + { + var methods = GetMarshalMethods ("ExportObjectShapes"); + AssertHasExport (methods, "any", "(Ljava/lang/Object;)Ljava/lang/Object;"); + } + + [Fact] + public void Export_ArrayOfUserPeerType_RecursesUserPeerResolver () + { + var methods = GetMarshalMethods ("ExportUserPeerArrayShapes"); + var echoArr = System.Array.Find (methods, m => m.JniName == "echoArr"); + echoArr = AssertNotNull (echoArr); + // Both parameter and return are arrays of the user-peer UserPeerForArray. + // CRC64 hash is environment-dependent; assert by suffix. + Assert.Matches (@"^\(\[Ls?crc64[0-9a-f]{16}/UserPeerForArray;\)\[Ls?crc64[0-9a-f]{16}/UserPeerForArray;$", echoArr.JniSignature); + } + + [Fact] + public void Export_ProtectedAndPrivateVisibility_BothSurface () + { + var methods = GetMarshalMethods ("ExportVisibilityShapes"); + AssertHasExport (methods, "doProtected", "()V"); + AssertHasExport (methods, "doPrivate", "()V"); + } + + [Fact] + public void ExportField_ReturningPrimitive () + { + var methods = GetMarshalMethods ("ExportFieldPrimitiveShapes"); + // [ExportField] uses the managed method name as the JNI name (not the field name). + var getMaxValue = System.Array.Find (methods, m => m.JniName == "GetMaxValue"); + getMaxValue = AssertNotNull (getMaxValue); + Assert.Equal ("()I", getMaxValue.JniSignature); + Assert.Equal ("__export__", getMaxValue.Connector); + } + + [Fact] + public void Export_OverloadsWithSameJavaName_RegisterDistinctly () + { + var methods = GetMarshalMethods ("ExportOverloadShapes"); + var calls = System.Array.FindAll (methods, m => m.JniName == "call"); + Assert.Equal (2, calls.Length); + Assert.Contains (calls, m => m.JniSignature == "(I)V"); + Assert.Contains (calls, m => m.JniSignature == "(Ljava/lang/String;)V"); + } + + [Fact] + public void Export_OnRegisterOverride_RegisterPathWins () + { + var methods = GetMarshalMethods ("ExportOverridingRegisterShape"); + + // The Activity.OnCreate override carries [Register]-driven dispatch + // (real Get*Handler connector). Putting [Export] on top of an override + // of a [Register]'d base means BOTH entries are registered: the + // [Register]-driven override (so Activity.onCreate dispatch still works) + // AND the [Export]-driven new method (so Java callers can call the + // renamed method). Matches legacy CecilImporter behaviour. + var onCreate = System.Array.Find (methods, m => m.JniName == "onCreate"); + onCreate = AssertNotNull (onCreate); + Assert.False (onCreate.Connector is null or "__export__", + $"OnCreate override should keep its [Register]-driven Get*Handler connector, got '{onCreate.Connector}'."); + + var onCreateExport = System.Array.Find (methods, m => m.JniName == "onCreateExport"); + onCreateExport = AssertNotNull (onCreateExport); + Assert.True (onCreateExport.Connector is null or "__export__", + $"[Export]-driven entry should have no real connector, got '{onCreateExport.Connector}'."); + } + + // === Phase D: [Export] on a [Register]'d-interface implementation === + + [Fact] + public void Export_OnInterfaceImpl_RegistersOnImplementor () + { + // IOnClickListener.OnClick is declared with [Register("onClick", "(Landroid/view/View;)V", "...")] + // on the interface itself. The implementor adds [Export("onClick")] to its + // OnClick override. The trimmable scanner must surface a UCO marshal-method + // entry on the implementor type so the [UnmanagedCallersOnly] wrapper gets + // generated alongside the interface-invoker path. Without this entry, the + // trimmable build silently relies on the legacy Invoker for dispatch, which + // defeats the user's explicit [Export] opt-in. + var methods = GetMarshalMethods ("ExportInterfaceImplShape"); + AssertHasExport (methods, "onClick", "(Landroid/view/View;)V"); + } + + [Fact] + public void Export_OnInterfaceImpl_WithRenamedJniName_RegistersExportName () + { + // User implements IOnClickListener.OnClick but with [Export("onClickRenamed")]. + // The [Export] attribute opts the method into JCW/UCO dispatch under a + // different JNI name from the interface contract. Assert that the renamed + // entry is present — the scanner must NOT collapse it into the interface's + // "onClick" name nor drop it because of the contract mismatch. + var methods = GetMarshalMethods ("ExportInterfaceRenameShape"); + Assert.Contains (methods, m => m.JniName == "onClickRenamed" && m.JniSignature == "(Landroid/view/View;)V"); + } +} diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerRunner.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerRunner.cs index d01d02746de..fcd8a0a477e 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerRunner.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerRunner.cs @@ -178,7 +178,19 @@ static List ExtractMethodRegistrations (CecilTypeDefinition typeDef return ExtractDirectRegisterAttributes (typeDef); } - var wrapper = CecilImporter.CreateType (typeDef, cache); + Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers.CallableWrapperType wrapper; + try { + wrapper = CecilImporter.CreateType (typeDef, cache); + } catch (ArgumentNullException) { + // Legacy JCW emitter (CecilImporter.GetJniSignature) cannot encode + // certain [Export] parameter / return types (enum, ICharSequence, + // non-generic collections). The trimmable scanner handles these, + // but legacy comparison can't be performed — yield direct + // [Register] attributes so the type is still represented in the + // legacy snapshot. This is the documented JCW emitter blocker + // (covered by ScannerExportShapesTests for the new scanner). + return ExtractDirectRegisterAttributes (typeDef); + } var methods = new List (); foreach (var m in wrapper.Methods) { diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/UserTypesFixture/UserTypes.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/UserTypesFixture/UserTypes.cs index 75586236a8e..9a5415426ec 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/UserTypesFixture/UserTypes.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/UserTypesFixture/UserTypes.cs @@ -155,4 +155,239 @@ public void DoWork () { } } + + // [Export] shapes that the legacy JCW emitter (CecilImporter.GetJniSignature) + // cannot encode but that the trimmable scanner is expected to handle. These + // types are excluded from legacy↔new comparison in ScannerComparisonTests + // and validated by ScannerExportShapesTests via the new scanner only. + public enum ExportSampleEnum { Zero, One, Two } + public enum ExportSampleByteEnum : byte { Red, Green, Blue } + public enum ExportSampleLongEnum : long { Zero = 0L, Big = long.MaxValue } + + public class ExportEnumShapes : Java.Lang.Object + { + [Export ("echoEnum")] + public ExportSampleEnum EchoEnum (ExportSampleEnum value) => value; + + [Export ("echoByteEnum")] + public ExportSampleByteEnum EchoByteEnum (ExportSampleByteEnum value) => value; + + [Export ("echoLongEnum")] + public ExportSampleLongEnum EchoLongEnum (ExportSampleLongEnum value) => value; + } + + public class ExportCharSequenceShapes : Java.Lang.Object + { + [Export ("echoCharSequence")] + public Java.Lang.ICharSequence? EchoCharSequence (Java.Lang.ICharSequence? value) => value; + } + + public class ExportCollectionShapes : Java.Lang.Object + { + [Export ("echoList")] + public System.Collections.IList? EchoList (System.Collections.IList? value) => value; + + [Export ("echoMap")] + public System.Collections.IDictionary? EchoMap (System.Collections.IDictionary? value) => value; + + [Export ("echoCollection")] + public System.Collections.ICollection? EchoCollection (System.Collections.ICollection? value) => value; + } + + // [ExportField] generates a Java field whose value is produced by a getter + // method. The scanner must surface the method-level registration so the UCO + // can dispatch to the getter. + public class ExportFieldShapes : Java.Lang.Object + { + protected ExportFieldShapes (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } + + [ExportField ("STATIC_INSTANCE")] + public static ExportFieldShapes? GetInstance () => null; + + [ExportField ("VALUE")] + public string GetValue () => ""; + + [ExportField ("COUNT")] + public int GetCount () => 0; + } + + // [ExportParameter] overrides a Stream / XmlReader's Java type without + // relying on auto-resolution. Each kind must map to its specific JNI + // descriptor (java/io/InputStream, OutputStream, org/xmlpull/v1/XmlPullParser, + // android/content/res/XmlResourceParser). + public class ExportParameterShapes : Java.Lang.Object + { + [Export ("openStream")] + public int OpenStream ([ExportParameter (ExportParameterKind.InputStream)] System.IO.Stream? stream) + => stream is null ? 0 : 1; + + [return: ExportParameter (ExportParameterKind.OutputStream)] + [Export ("wrapStream")] + public System.IO.Stream? WrapStream ([ExportParameter (ExportParameterKind.OutputStream)] System.IO.Stream? stream) + => stream; + + [return: ExportParameter (ExportParameterKind.XmlPullParser)] + [Export ("readXml")] + public System.Xml.XmlReader? ReadXml ([ExportParameter (ExportParameterKind.XmlPullParser)] System.Xml.XmlReader? reader) + => reader; + + [return: ExportParameter (ExportParameterKind.XmlResourceParser)] + [Export ("readResourceXml")] + public System.Xml.XmlReader? ReadResourceXml ([ExportParameter (ExportParameterKind.XmlResourceParser)] System.Xml.XmlReader? reader) + => reader; + } + + // === Phase A: dispatch & declaration shapes === + + // A.1: static [Export] method — different dispatch path (no `this`). + public class StaticExportShapes : Java.Lang.Object + { + [Export ("compute")] + public static int Compute (int x) => x; + + [Export ("hello")] + public static string Hello () => "hi"; + } + + // A.2: [Export(Throws = ...)] — declared exceptions in JNI signature. + public class ExportThrowsShapes : Java.Lang.Object + { + [Export ("ioCall", Throws = new [] { typeof (Java.IO.IOException) })] + public void IoCall () { } + + [Export ("multiThrow", Throws = new [] { typeof (Java.IO.IOException), typeof (Java.Lang.IllegalStateException) })] + public int MultiThrow () => 0; + } + + // A.3: Mixed [Register] overrides + new [Export] methods on the same type. + [Register ("my/app/MixedRegisterAndExport")] + public class MixedRegisterAndExport : Activity + { + protected override void OnCreate (Android.OS.Bundle? savedInstanceState) + { + base.OnCreate (savedInstanceState); + } + + [Export ("doWork")] + public void DoWork () { } + + [Export ("compute")] + public int Compute (int x) => x; + } + + // A.4: [Export] on a virtual method, derived class re-declaring without [Export]. + public class VirtualExportBase : Java.Lang.Object + { + [Export ("ping")] + public virtual int Ping () => 0; + } + + public class VirtualExportDerived : VirtualExportBase + { + public override int Ping () => 1; + } + + // A.5: [Export] with explicit JNI name differing from C# method name. + public class ExportRenameShapes : Java.Lang.Object + { + [Export ("javaSideName")] + public void CSharpSideName () { } + } + + // === Phase B: edge marshalling === + + // B.1: [Export] returning Java.Lang.Object explicitly (intentional unwrapped path). + public class ExportObjectShapes : Java.Lang.Object + { + [Export ("any")] + public Java.Lang.Object? Any (Java.Lang.Object? v) => v; + } + + // B.2: array of user-peer type — exercise [] recursion through the user-peer + // JNI resolver fix from a prior commit. + public class UserPeerForArray : Java.Lang.Object + { + protected UserPeerForArray (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } + } + + public class ExportUserPeerArrayShapes : Java.Lang.Object + { + [Export ("echoArr")] + public UserPeerForArray []? EchoArr (UserPeerForArray []? a) => a; + } + + // B.3: protected/private [Export] methods — visibility shouldn't gate registration. + public class ExportVisibilityShapes : Java.Lang.Object + { + [Export ("doProtected")] + protected void DoProtected () { } + + [Export ("doPrivate")] + void DoPrivate () { } + } + + // B.4: [ExportField] returning a primitive — focused single-shape assertion. + public class ExportFieldPrimitiveShapes : Java.Lang.Object + { + protected ExportFieldPrimitiveShapes (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } + + [ExportField ("MAX_VALUE")] + public static int GetMaxValue () => 42; + } + + // B.5: [Export] overloads with same Java name, different signatures — no dedup. + public class ExportOverloadShapes : Java.Lang.Object + { + [Export ("call")] + public void Call (int x) { } + + [Export ("call")] + public void Call (string s) { } + } + + // === Phase C: robustness === + // C.1 (property) is gated by [AttributeUsage(Method|Constructor)] — skip. + + // C.2: generic method with [Export] — scanner shouldn't crash on T. + public class ExportGenericShapes : Java.Lang.Object + { + [Export ("g")] + public T Identity (T x) => x; + } + + // C.3: override of a [Register]'d base method also marked [Export]. + // Legacy: [Register]-driven dispatch wins (with connector); [Export] is a no-op. + public class ExportOverridingRegisterShape : Activity + { + [Export ("onCreateExport")] + protected override void OnCreate (Android.OS.Bundle? savedInstanceState) + { + base.OnCreate (savedInstanceState); + } + } + + // === Phase D: [Export] on a method that implements a [Register]'d interface === + + // D.1: [Export] on an interface implementation. The interface method is + // [Register]'d on the *interface declaration* (and its Invoker handles + // dispatch in legacy bindings), but the user puts [Export] on their + // implementation. The trimmable scanner must produce a UCO marshal-method + // entry on the implementor (not silently rely on the Invoker), matching + // the JNI name/signature declared by the interface. + public class ExportInterfaceImplShape : Java.Lang.Object, Android.Views.View.IOnClickListener + { + [Export ("onClick")] + public void OnClick (Android.Views.View? v) { } + } + + // D.2: [Export] with a non-matching JNI name on a method that *also* + // implements a [Register]'d interface method. This asserts that the + // scanner registers the [Export]-named entry (the user explicitly opted + // in) without silently dropping it because the interface contract uses a + // different name. + public class ExportInterfaceRenameShape : Java.Lang.Object, Android.Views.View.IOnClickListener + { + [Export ("onClickRenamed")] + public void OnClick (Android.Views.View? v) { } + } } diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs index 44b5bff4f85..d824733c2f2 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs @@ -36,6 +36,16 @@ private protected static string TestFixtureAssemblyPath { private protected static List ScanFixtures () => _cachedScanResult.Value.peers; + private protected static List ScanFixtures (string packageNamingPolicy) + { + using var scanner = new JavaPeerScanner (packageNamingPolicy); + using var peReader = new PEReader (File.OpenRead (TestFixtureAssemblyPath)); + var mdReader = peReader.GetMetadataReader (); + var assemblyName = mdReader.GetString (mdReader.GetAssemblyDefinition ().Name); + var assemblies = new [] { (assemblyName, peReader) }; + return scanner.Scan (assemblies); + } + private protected static AssemblyManifestInfo ScanAssemblyManifestInfo () => _cachedScanResult.Value.manifestInfo; private protected static JavaPeerInfo FindFixtureByJavaName (string javaName) @@ -54,6 +64,14 @@ private protected static JavaPeerInfo FindFixtureByManagedName (string managedNa return peer; } + private protected static JavaPeerInfo FindFixtureByManagedName (string managedName, string packageNamingPolicy) + { + var peers = ScanFixtures (packageNamingPolicy); + var peer = peers.FirstOrDefault (p => p.ManagedTypeName == managedName); + Assert.NotNull (peer); + return peer; + } + static (string ns, string shortName) ParseManagedTypeName (string managedName) { var ns = managedName.Contains ('.') ? managedName.Substring (0, managedName.LastIndexOf ('.')) : ""; @@ -91,7 +109,7 @@ private protected static JavaPeerInfo MakeAcwPeer (string jniName, string manage return MakePeerWithActivation (jniName, managedName, asmName) with { DoNotGenerateAcw = false, JavaConstructors = new List { - new JavaConstructorInfo { ConstructorIndex = 0, JniSignature = "()V" }, + new JavaConstructorInfo { ConstructorIndex = 0, JniSignature = "()V", HasMatchingManagedCtor = true }, }, MarshalMethods = new List { new MarshalMethodInfo { diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/JcwJavaSourceGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/JcwJavaSourceGeneratorTests.cs index 30d81218883..81fc6457f35 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/JcwJavaSourceGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/JcwJavaSourceGeneratorTests.cs @@ -375,6 +375,19 @@ public void Generate_ExportWithThrows_HasThrowsClause () } + public class StaticExportMethods + { + + [Fact] + public void Generate_StaticExport_HasStaticKeyword () + { + var java = GenerateFixture ("my/app/StaticExportExample"); + AssertContainsLine ("public static java.lang.String computeLabel (int p0)", java); + AssertContainsLine ("public static native java.lang.String", java); + } + + } + public class MethodReturnTypesAndParams { diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs index 7de6aec0679..2905ee9eed5 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs @@ -21,6 +21,9 @@ static MemoryStream GenerateRootAssembly (IReadOnlyList perAssemblyNames return stream; } + static MethodDefinitionHandle FindMethodDefinition (MetadataReader reader, string methodName) => + reader.MethodDefinitions.First (h => reader.GetString (reader.GetMethodDefinition (h).Name) == methodName); + [Fact] public void Generate_ProducesValidPEAssembly () { @@ -29,6 +32,23 @@ public void Generate_ProducesValidPEAssembly () Assert.True (pe.HasMetadata); } + [Theory] + [InlineData (false)] + [InlineData (true)] + public void Generate_InitializeUsesComputedMaxStack (bool useSharedTypemapUniverse) + { + using var stream = GenerateRootAssembly ( + new [] { "_App.TypeMap", "_Mono.Android.TypeMap" }, + useSharedTypemapUniverse); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var initialize = reader.GetMethodDefinition (FindMethodDefinition (reader, "Initialize")); + var body = pe.GetMethodBody (initialize.RelativeVirtualAddress); + + Assert.InRange (body.MaxStack, 5, 16); + } + [Theory] [InlineData (null, "_Microsoft.Android.TypeMaps")] [InlineData ("MyRoot", "MyRoot")] diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs index c9f070dc146..6139ea8ee6a 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs @@ -34,6 +34,8 @@ public void LogRootingManifestReferencedTypeInfo (string javaTypeName, string ma logMessages.Add ($"Rooting manifest-referenced type '{javaTypeName}' ({managedTypeName}) as unconditional."); public void LogManifestReferencedTypeNotFoundWarning (string javaTypeName) => warnings?.Add ($"Manifest-referenced type '{javaTypeName}' was not found in any scanned assembly. It may be a framework type."); + public void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeName) => + logMessages.Add ($"XA4251: Type '{managedTypeName}' uses [JniAddNativeMethodRegistrationAttribute], which is not supported by the trimmable type map."); } [Fact] @@ -72,6 +74,17 @@ public void Execute_WithTestFixtures_ProducesOutputs () Assert.Contains (result.GeneratedAssemblies, a => a.Name == "_TestFixtures.TypeMap"); } + [Fact] + public void Execute_WithJniAddNativeMethodRegistrationAttribute_ReportsXA4251 () + { + // TestFixtures.HandWrittenNativeRegistrationPeer carries [JniAddNativeMethodRegistrationAttribute]. + // The trimmable typemap does not support that attribute by design — the orchestrator must + // emit XA4251 so MSBuild fails the build via HasLoggedErrors. + using var peReader = CreateTestFixturePEReader (); + CreateGenerator ().Execute (new List<(string, PEReader)> { ("TestFixtures", peReader) }, new Version (11, 0), new HashSet ()); + Assert.Contains (logMessages, m => m.Contains ("XA4251") && m.Contains ("HandWrittenNativeRegistrationPeer")); + } + [Fact] public void Execute_CollectsDeferredRegistrationTypes_ForAllApplicationAndInstrumentationSubtypes () { @@ -165,6 +178,21 @@ public void Execute_JavaSourcesHaveCorrectStructure () Assert.Contains ("class ", source.Content); } + [Fact] + public void Execute_FrameworkAssembly_GeneratesFrameworkJcwTypes () + { + using var peReader = CreateTestFixturePEReader (); + var result = CreateGenerator ().Execute ( + new List<(string, PEReader)> { ("Mono.Android", peReader) }, + new Version (11, 0), + new HashSet (StringComparer.OrdinalIgnoreCase) { "Mono.Android" }); + + Assert.Contains (result.GeneratedJavaSources, s => s.RelativePath == "xamarin/android/net/ServerCertificateCustomValidator_TrustManager.java"); + Assert.Contains (result.GeneratedJavaSources, s => s.RelativePath == "xamarin/android/net/ServerCertificateCustomValidator_TrustManager_FakeSSLSession.java"); + Assert.Contains (result.GeneratedJavaSources, s => s.RelativePath == "xamarin/android/net/ServerCertificateCustomValidator_AlwaysAcceptingHostnameVerifier.java"); + Assert.Contains (result.GeneratedJavaSources, s => s.RelativePath == "xamarin/android/net/ServerCertificateCustomValidator_NonRequiredFrameworkAcw.java"); + } + [Fact] public void Execute_ManifestPlaceholdersAreResolvedBeforeRooting () { @@ -186,7 +214,7 @@ public void Execute_ManifestPlaceholdersAreResolvedBeforeRooting () new ManifestConfig ( PackageName: "my.app", AndroidApiLevel: "35", - SupportedOSPlatformVersion: "21", + SupportedOSPlatformVersion: "24", RuntimeProviderJavaName: "mono.MonoRuntimeProvider", ManifestPlaceholders: "applicationId=my.app"), manifestTemplate); @@ -400,37 +428,42 @@ public void RootManifestReferencedTypes_EmptyManifest_NoChanges () } [Fact] - public void MergeCrossAssemblyAliases_CrossAssemblyDuplicate_FirstAssemblyOwns () + public void MergeCrossAssemblyAliases_RegisterTakesPrecedenceOverJniTypeSignature () { - var firstPeer = new JavaPeerInfo { - JavaName = "com/example/Duplicate", CompatJniName = "com/example/Duplicate", - ManagedTypeName = "First.Duplicate", ManagedTypeNamespace = "First", ManagedTypeShortName = "Duplicate", - AssemblyName = "A.Binding", + // Java.Interop has JavaObject with [JniTypeSignature("java/lang/Object")] + var javaInteropPeer = new JavaPeerInfo { + JavaName = "java/lang/Object", CompatJniName = "java/lang/Object", + ManagedTypeName = "Java.Interop.JavaObject", ManagedTypeNamespace = "Java.Interop", ManagedTypeShortName = "JavaObject", + AssemblyName = "Java.Interop", IsFromJniTypeSignature = true, DoNotGenerateAcw = true, }; - var secondPeer = new JavaPeerInfo { - JavaName = "com/example/Duplicate", CompatJniName = "com/example/Duplicate", - ManagedTypeName = "Second.Duplicate", ManagedTypeNamespace = "Second", ManagedTypeShortName = "Duplicate", - AssemblyName = "B.Binding", + // Mono.Android has Java.Lang.Object with [Register("java/lang/Object")] + var monoAndroidPeer = new JavaPeerInfo { + JavaName = "java/lang/Object", CompatJniName = "java/lang/Object", + ManagedTypeName = "Java.Lang.Object", ManagedTypeNamespace = "Java.Lang", ManagedTypeShortName = "Object", + AssemblyName = "Mono.Android", IsFromJniTypeSignature = false, DoNotGenerateAcw = true, }; - var uniquePeer = new JavaPeerInfo { - JavaName = "com/example/Unique", CompatJniName = "com/example/Unique", - ManagedTypeName = "Second.Unique", ManagedTypeNamespace = "Second", ManagedTypeShortName = "Unique", - AssemblyName = "B.Binding", + // Another unique peer in Java.Interop that shouldn't be moved + var otherPeer = new JavaPeerInfo { + JavaName = "java/interop/SomeHelper", CompatJniName = "java/interop/SomeHelper", + ManagedTypeName = "Java.Interop.SomeHelper", ManagedTypeNamespace = "Java.Interop", ManagedTypeShortName = "SomeHelper", + AssemblyName = "Java.Interop", IsFromJniTypeSignature = true, }; - var allPeers = new List { firstPeer, secondPeer, uniquePeer }; + var allPeers = new List { javaInteropPeer, monoAndroidPeer, otherPeer }; var result = TrimmableTypeMapGenerator.MergeCrossAssemblyAliases (allPeers); - var firstGroup = result.Single (g => g.AssemblyName == "A.Binding"); - Assert.Equal (2, firstGroup.Peers.Count); - Assert.Contains (firstGroup.Peers, p => p.ManagedTypeName == "First.Duplicate"); - Assert.Contains (firstGroup.Peers, p => p.ManagedTypeName == "Second.Duplicate"); + // Both java/lang/Object peers should be in the Mono.Android group ([Register] wins) + var monoAndroidGroup = result.Single (g => g.AssemblyName == "Mono.Android"); + Assert.Equal (2, monoAndroidGroup.Peers.Count); + Assert.Contains (monoAndroidGroup.Peers, p => p.ManagedTypeName == "Java.Lang.Object"); + Assert.Contains (monoAndroidGroup.Peers, p => p.ManagedTypeName == "Java.Interop.JavaObject"); - var secondGroup = result.Single (g => g.AssemblyName == "B.Binding"); - Assert.Single (secondGroup.Peers); - Assert.Equal ("Second.Unique", secondGroup.Peers [0].ManagedTypeName); + // Java.Interop should only have the unique peer + var javaInteropGroup = result.Single (g => g.AssemblyName == "Java.Interop"); + Assert.Single (javaInteropGroup.Peers); + Assert.Equal ("Java.Interop.SomeHelper", javaInteropGroup.Peers [0].ManagedTypeName); } [Fact] @@ -476,6 +509,189 @@ public void MergeCrossAssemblyAliases_SameAssemblyAliases_NotMoved () Assert.Equal (2, result [0].Peers.Count); } + [Fact] + public void MergeCrossAssemblyAliases_SameManagedName_DifferentAssemblies_MergedCorrectly () + { + // Reproduces the java/lang/Throwable crash: two assemblies define Java.Lang.Throwable + // with the same JNI name, plus Java.Interop.JavaException also maps to the same JNI name. + // All three should be merged into the [Register]-owning assembly's group. + var javaInteropThrowable = new JavaPeerInfo { + JavaName = "java/lang/Throwable", CompatJniName = "java/lang/Throwable", + ManagedTypeName = "Java.Lang.Throwable", ManagedTypeNamespace = "Java.Lang", ManagedTypeShortName = "Throwable", + AssemblyName = "Java.Interop", IsFromJniTypeSignature = true, DoNotGenerateAcw = true, + }; + + var monoAndroidThrowable = new JavaPeerInfo { + JavaName = "java/lang/Throwable", CompatJniName = "java/lang/Throwable", + ManagedTypeName = "Java.Lang.Throwable", ManagedTypeNamespace = "Java.Lang", ManagedTypeShortName = "Throwable", + AssemblyName = "Mono.Android", IsFromJniTypeSignature = false, DoNotGenerateAcw = true, + }; + + var javaException = new JavaPeerInfo { + JavaName = "java/lang/Throwable", CompatJniName = "java/lang/Throwable", + ManagedTypeName = "Java.Interop.JavaException", ManagedTypeNamespace = "Java.Interop", ManagedTypeShortName = "JavaException", + AssemblyName = "Java.Interop", IsFromJniTypeSignature = true, DoNotGenerateAcw = true, + }; + + var allPeers = new List { javaInteropThrowable, monoAndroidThrowable, javaException }; + var result = TrimmableTypeMapGenerator.MergeCrossAssemblyAliases (allPeers); + + // All java/lang/Throwable peers should be in the Mono.Android group ([Register] wins) + var monoAndroidGroup = result.Single (g => g.AssemblyName == "Mono.Android"); + Assert.Equal (3, monoAndroidGroup.Peers.Count); + Assert.Contains (monoAndroidGroup.Peers, p => p.ManagedTypeName == "Java.Lang.Throwable" && p.AssemblyName == "Mono.Android"); + Assert.Contains (monoAndroidGroup.Peers, p => p.ManagedTypeName == "Java.Lang.Throwable" && p.AssemblyName == "Java.Interop"); + Assert.Contains (monoAndroidGroup.Peers, p => p.ManagedTypeName == "Java.Interop.JavaException"); + + // Java.Interop group should be empty (all peers moved to Mono.Android) + Assert.DoesNotContain (result, g => g.AssemblyName == "Java.Interop"); + } + + [Fact] + public void MergeCrossAssemblyAliases_SameManagedName_ProducesCorrectAliasGroup () + { + // End-to-end: after merging, ModelBuilder must produce a 3-way alias group + // for java/lang/Throwable with indexed entries and a single base entry, + // ensuring the runtime dictionary only sees java/lang/Throwable once. + var javaInteropThrowable = new JavaPeerInfo { + JavaName = "java/lang/Throwable", CompatJniName = "java/lang/Throwable", + ManagedTypeName = "Java.Lang.Throwable", ManagedTypeNamespace = "Java.Lang", ManagedTypeShortName = "Throwable", + AssemblyName = "Java.Interop", IsFromJniTypeSignature = true, DoNotGenerateAcw = true, + }; + + var monoAndroidThrowable = new JavaPeerInfo { + JavaName = "java/lang/Throwable", CompatJniName = "java/lang/Throwable", + ManagedTypeName = "Java.Lang.Throwable", ManagedTypeNamespace = "Java.Lang", ManagedTypeShortName = "Throwable", + AssemblyName = "Mono.Android", IsFromJniTypeSignature = false, DoNotGenerateAcw = true, + }; + + var javaException = new JavaPeerInfo { + JavaName = "java/lang/Throwable", CompatJniName = "java/lang/Throwable", + ManagedTypeName = "Java.Interop.JavaException", ManagedTypeNamespace = "Java.Interop", ManagedTypeShortName = "JavaException", + AssemblyName = "Java.Interop", IsFromJniTypeSignature = true, DoNotGenerateAcw = true, + }; + + var allPeers = new List { javaInteropThrowable, monoAndroidThrowable, javaException }; + var merged = TrimmableTypeMapGenerator.MergeCrossAssemblyAliases (allPeers); + + // All peers should be in the Mono.Android group + Assert.Single (merged); + var group = merged [0]; + Assert.Equal ("Mono.Android", group.AssemblyName); + Assert.Equal (3, group.Peers.Count); + + // Build the model — should produce a 3-way alias group + string typeMapAssemblyName = $"_{group.AssemblyName}.TypeMap"; + var model = ModelBuilder.Build (group.Peers, typeMapAssemblyName + ".dll", typeMapAssemblyName); + + // 3 indexed entries + 1 base entry = 4 + Assert.Equal (4, model.Entries.Count); + Assert.Equal ("java/lang/Throwable[0]", model.Entries [0].JniName); + Assert.Equal ("java/lang/Throwable[1]", model.Entries [1].JniName); + Assert.Equal ("java/lang/Throwable[2]", model.Entries [2].JniName); + Assert.Equal ("java/lang/Throwable", model.Entries [3].JniName); + + // Exactly 1 alias holder + Assert.Single (model.AliasHolders); + Assert.Equal (3, model.AliasHolders [0].AliasKeys.Count); + + // The base "java/lang/Throwable" entry points to the alias holder, not a type directly + var baseEntry = model.Entries [3]; + Assert.Contains ("_Aliases", baseEntry.ProxyTypeReference); + + // 3 associations (one per peer → alias holder) + Assert.Equal (3, model.Associations.Count); + + // The bare "java/lang/Throwable" key appears exactly once — no duplicates + Assert.Single (model.Entries, e => e.JniName == "java/lang/Throwable"); + } + + [Fact] + public void RootManifestReferencedTypes_ResolvesRelativeNames () + { + var peers = new List { + new JavaPeerInfo { + JavaName = "com/example/MyActivity", CompatJniName = "com.example.MyActivity", + ManagedTypeName = "MyApp.MyActivity", ManagedTypeNamespace = "MyApp", ManagedTypeShortName = "MyActivity", + AssemblyName = "MyApp", IsUnconditional = false, + }, + new JavaPeerInfo { + JavaName = "com/example/MyService", CompatJniName = "com.example.MyService", + ManagedTypeName = "MyApp.MyService", ManagedTypeNamespace = "MyApp", ManagedTypeShortName = "MyService", + AssemblyName = "MyApp", IsUnconditional = false, + }, + }; + + var doc = System.Xml.Linq.XDocument.Parse (""" + + + + + + + + """); + + var generator = CreateGenerator (); + generator.RootManifestReferencedTypes (peers, doc); + + Assert.True (peers [0].IsUnconditional, "Dot-relative name '.MyActivity' should resolve to com.example.MyActivity."); + Assert.True (peers [1].IsUnconditional, "Simple name 'MyService' should resolve to com.example.MyService."); + } + + [Fact] + public void RootManifestReferencedTypes_MatchesCompatNames () + { + var peers = new List { + new JavaPeerInfo { + JavaName = "crc64123456789abc/MyActivity", CompatJniName = "my/app/MyActivity", + ManagedTypeName = "My.App.MyActivity", ManagedTypeNamespace = "My.App", ManagedTypeShortName = "MyActivity", + AssemblyName = "MyApp", IsUnconditional = false, + }, + }; + + var doc = System.Xml.Linq.XDocument.Parse (""" + + + + + + + """); + + var generator = CreateGenerator (); + generator.RootManifestReferencedTypes (peers, doc); + + Assert.True (peers [0].IsUnconditional, "Relative manifest name should match CompatJniName when JavaName uses a CRC64 package."); + } + + [Fact] + public void RootManifestReferencedTypes_MatchesNestedTypes () + { + var peers = new List { + new JavaPeerInfo { + JavaName = "com/example/Outer$Inner", CompatJniName = "com.example.Outer$Inner", + ManagedTypeName = "MyApp.Outer.Inner", ManagedTypeNamespace = "MyApp", ManagedTypeShortName = "Inner", + AssemblyName = "MyApp", IsUnconditional = false, + }, + }; + + var doc = System.Xml.Linq.XDocument.Parse (""" + + + + + + + """); + + var generator = CreateGenerator (); + generator.RootManifestReferencedTypes (peers, doc); + + Assert.True (peers [0].IsUnconditional, "Nested type 'Outer$Inner' should be matched using '$' separator."); + } + + static PEReader CreateTestFixturePEReader () { var dir = Path.GetDirectoryName (typeof (FixtureTestBase).Assembly.Location) diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs index ec37cbff9af..1e25ee19728 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; using System.Reflection; @@ -44,6 +45,24 @@ static List FindCtorMemberRefs (MetadataReader reader, st static MemberReferenceHandle FindCtorMemberRef (MetadataReader reader, string parentNamespace, string parentName, params string [] parameterTypes) => FindCtorMemberRefs (reader, parentNamespace, parentName, parameterTypes).First (); + static TypeRefData TypeRef (string managedTypeName) => new () { + ManagedTypeName = managedTypeName, + AssemblyName = GetAssemblyNameForManagedType (managedTypeName), + }; + + static string GetAssemblyNameForManagedType (string managedTypeName) + { + if (managedTypeName.StartsWith ("System.", StringComparison.Ordinal)) { + return "System.Runtime"; + } + if (managedTypeName.StartsWith ("Android.", StringComparison.Ordinal) || + managedTypeName.StartsWith ("Java.", StringComparison.Ordinal) || + managedTypeName.StartsWith ("Javax.", StringComparison.Ordinal)) { + return "Mono.Android"; + } + return "TestAsm"; + } + [Fact] public void Generate_ProducesValidPEAssembly () { @@ -288,7 +307,40 @@ public void Generate_LeafCtor_DoesNotUseCreateManagedPeer () } [Fact] - public void Generate_InheritedCtor_ReferencesGuardAndActivationCtor () + public void Generate_InheritedCtor_CreateInstanceDoesNotActivate () + { + var peers = ScanFixtures (); + var simpleActivity = peers.First (p => p.JavaName == "my/app/SimpleActivity"); + Assert.NotNull (simpleActivity.ActivationCtor); + Assert.NotEqual (simpleActivity.ManagedTypeName, simpleActivity.ActivationCtor.DeclaringTypeName); + + using var stream = GenerateAssembly (new [] { simpleActivity }, "InheritedCtorCreateInstanceTest"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + AssertCreateInstanceReturnsNull (pe, reader, "MyApp_SimpleActivity_Proxy"); + } + + [Fact] + public void Generate_InheritedJavaInteropCtor_CreateInstanceDoesNotActivate () + { + var peer = MakeAcwPeer ("test/JiInheritedTarget", "Test.JiInheritedTarget", "TestAsm") with { + ActivationCtor = new ActivationCtorInfo { + DeclaringTypeName = "Test.JiInheritedBase", + DeclaringAssemblyName = "TestAsm", + Style = ActivationCtorStyle.JavaInterop, + }, + }; + + using var stream = GenerateAssembly (new [] { peer }, "InheritedJiCtorCreateInstanceTest"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + AssertCreateInstanceReturnsNull (pe, reader, "Test_JiInheritedTarget_Proxy"); + } + + [Fact] + public void Generate_InheritedCtor_NctorUcoCallsDefaultConstructor () { var peers = ScanFixtures (); var simpleActivity = peers.First (p => p.JavaName == "my/app/SimpleActivity"); @@ -302,18 +354,29 @@ public void Generate_InheritedCtor_ReferencesGuardAndActivationCtor () Assert.Contains ("ShouldSkipActivation", memberNames); Assert.Contains ("GetUninitializedObject", memberNames); + Assert.Contains ("SetActivationPeerReference", memberNames); + Assert.Contains ("MarkActivationPeerReplaceable", memberNames); Assert.DoesNotContain ("Invoke", memberNames); Assert.DoesNotContain ("ActivateInstance", memberNames); Assert.DoesNotContain ("ActivatePeerFromJavaConstructor", memberNames); - Assert.NotEmpty (FindCtorMemberRefs (reader, "Android.App", "Activity", - "System.IntPtr", "Android.Runtime.JniHandleOwnership")); + // The new no-arg nctor codegen calls the target type's parameterless .ctor() + // directly, not the legacy (IntPtr, JniHandleOwnership) activation ctor on the base. + Assert.NotEmpty (FindCtorMemberRefs (reader, "MyApp", "SimpleActivity")); + var nctorIl = GetNctorUcoIL (pe, reader); + var activityActivationCtorTokens = FindCtorMemberRefs (reader, "Android.App", "Activity", + "System.IntPtr", "Android.Runtime.JniHandleOwnership") + .Select (h => MetadataTokens.GetToken (h)) + .ToList (); + Assert.DoesNotContain (activityActivationCtorTokens, + t => ILContainsCallToken (nctorIl, t) || ILContainsNewobjToken (nctorIl, t)); + var nctorMethodHandle = FindNctorUcoMethod (reader); Assert.False (nctorMethodHandle.IsNil, "SimpleActivity should have a nctor_*_uco method"); } [Fact] - public void Generate_InheritedJavaInteropCtor_ReferencesActivationCtor () + public void Generate_InheritedJavaInteropCtor_NctorUcoCallsDefaultConstructor () { var peer = MakeAcwPeer ("test/JiInheritedTarget", "Test.JiInheritedTarget", "TestAsm") with { ActivationCtor = new ActivationCtorInfo { @@ -332,10 +395,22 @@ public void Generate_InheritedJavaInteropCtor_ReferencesActivationCtor () var memberNames = GetMemberRefNames (reader); Assert.Contains ("GetUninitializedObject", memberNames); + Assert.Contains ("SetActivationPeerReference", memberNames); + Assert.Contains ("MarkActivationPeerReplaceable", memberNames); Assert.DoesNotContain ("Invoke", memberNames); - Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "JiInheritedBase", - "Java.Interop.JniObjectReference&", "Java.Interop.JniObjectReferenceOptions")); + // The new no-arg nctor codegen calls the target type's parameterless .ctor() + // directly, not the JI-style (JniObjectReference&, JniObjectReferenceOptions) + // activation ctor on the inherited base. + Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "JiInheritedTarget")); + var nctorIl = GetNctorUcoIL (pe, reader); + var inheritedJiActivationCtorTokens = FindCtorMemberRefs (reader, "Test", "JiInheritedBase", + "Java.Interop.JniObjectReference&", "Java.Interop.JniObjectReferenceOptions") + .Select (h => MetadataTokens.GetToken (h)) + .ToList (); + Assert.DoesNotContain (inheritedJiActivationCtorTokens, + t => ILContainsCallToken (nctorIl, t) || ILContainsNewobjToken (nctorIl, t)); + var nctorMethodHandle = FindNctorUcoMethod (reader); Assert.False (nctorMethodHandle.IsNil, "The ACW peer should have a nctor_*_uco method"); } @@ -455,7 +530,7 @@ public void EmitBody_ILCallbackCallsAddMemberRef_SignatureNotCorrupted () // This AddMemberRef call clears and repopulates _sigBlob pe.AddMemberRef (objectRef, ".ctor", s => s.MethodSignature (isInstanceMethod: true).Parameters (0, rt => rt.Void (), p => { })); - encoder.OpCode (ILOpCode.Ret); + encoder.Return (); }); // If the sig blob was corrupted, the PE metadata will have a wrong signature. @@ -539,6 +614,41 @@ sig.ParameterTypes [0].Contains ("JniObjectReference")) { Assert.True (foundByRefCtor, "Expected to find a JI-style invoker .ctor with byref JniObjectReference parameter"); } + [Fact] + public void Generate_UcoConstructor_InvokerUsesXamarinAndroidActivationCtor () + { + var peer = MakeAcwPeer ("test/AbstractCtorTarget", "Test.AbstractCtorTarget", "TestAsm") with { + InvokerTypeName = "Test.AbstractCtorInvoker", + }; + + using var stream = GenerateAssembly (new [] { peer }, "InvokerCtorUcoTest"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "AbstractCtorInvoker", + "System.IntPtr", "Android.Runtime.JniHandleOwnership")); + Assert.Empty (FindCtorMemberRefs (reader, "Test", "AbstractCtorTarget", + "System.IntPtr", "Android.Runtime.JniHandleOwnership")); + } + + [Fact] + public void Generate_UcoConstructor_InvokerUsesJavaInteropActivationCtor () + { + var peer = MakeAcwPeer ("test/AbstractJiCtorTarget", "Test.AbstractJiCtorTarget", "TestAsm") with { + InvokerTypeName = "Test.AbstractJiCtorInvoker", + InvokerActivationCtorStyle = ActivationCtorStyle.JavaInterop, + }; + + using var stream = GenerateAssembly (new [] { peer }, "JiInvokerCtorUcoTest"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "AbstractJiCtorInvoker", + "Java.Interop.JniObjectReference&", "Java.Interop.JniObjectReferenceOptions")); + Assert.Empty (FindCtorMemberRefs (reader, "Test", "AbstractJiCtorTarget", + "Java.Interop.JniObjectReference&", "Java.Interop.JniObjectReferenceOptions")); + } + [Fact] public void Generate_JiStyleCtor_EmitsDeleteRefCall () { @@ -632,6 +742,22 @@ public void Generate_AcwProxy_HasRegisterNativesAndUcoMethods () Assert.DoesNotContain ("RegisterNatives", privateImplMethodNames); } + [Fact] + public void Generate_AcwProxy_RegisterNativesUsesComputedMaxStack () + { + var peers = ScanFixtures (); + var acwPeer = peers.First (p => p.JavaName == "my/app/MainActivity"); + + using var stream = GenerateAssembly (new [] { acwPeer }, "RegisterNativesMaxStack"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var registerNatives = reader.GetMethodDefinition (FindMethodDefinition (reader, "RegisterNatives")); + var body = pe.GetMethodBody (registerNatives.RelativeVirtualAddress); + + Assert.InRange (body.MaxStack, 5, 16); + } + [Fact] public void Generate_AcwProxy_HasUnmanagedCallersOnlyAttribute () { @@ -656,7 +782,7 @@ public void Generate_AcwProxy_HasUnmanagedCallersOnlyAttribute () [Theory] [InlineData (1, 0x05)] // Boolean → byte (unsigned) for JNI ABI [InlineData (2, 0x04)] // Byte → sbyte - [InlineData (3, 0x03)] // Char → char + [InlineData (3, 0x07)] // Char → uint16 (blittable JNI jchar) [InlineData (4, 0x06)] // Short → int16 [InlineData (5, 0x08)] // Int → int32 [InlineData (6, 0x0A)] // Long → int64 @@ -723,7 +849,6 @@ public void EncodeClrTypeForCallback_Void_Throws () [Theory] [InlineData (2)] // Byte - [InlineData (3)] // Char [InlineData (4)] // Short [InlineData (5)] // Int [InlineData (6)] // Long @@ -792,9 +917,9 @@ public void Generate_UcoMethod_BooleanParam_WrapperUsesByte_CallbackUsesSByte () } [Fact] - public void Generate_UcoMethod_HasCatchRegionWithoutFinally () + public void Generate_ExportUcoMethod_HasCatchAndFinallyRegions () { - var peer = FindFixtureByJavaName ("my/app/TouchHandler"); + var peer = FindFixtureByJavaName ("my/app/ExportExample"); using var stream = GenerateAssembly (new [] { peer }, "UcoLegacyWrapperShape"); using var pe = new PEReader (stream); var reader = pe.GetMetadataReader (); @@ -803,13 +928,13 @@ public void Generate_UcoMethod_HasCatchRegionWithoutFinally () .First (h => { var method = reader.GetMethodDefinition (h); var name = reader.GetString (method.Name); - return name.Contains ("onTouch") && name.Contains ("_uco_"); + return name.Contains ("myExportedMethod") && name.Contains ("_uco_"); }); var ucoMethod = reader.GetMethodDefinition (ucoMethodHandle); var body = pe.GetMethodBody (ucoMethod.RelativeVirtualAddress); Assert.NotNull (body); Assert.Contains (body.ExceptionRegions, r => r.Kind == ExceptionRegionKind.Catch); - Assert.DoesNotContain (body.ExceptionRegions, r => r.Kind == ExceptionRegionKind.Finally); + Assert.Contains (body.ExceptionRegions, r => r.Kind == ExceptionRegionKind.Finally); } [Fact] @@ -943,6 +1068,166 @@ public void Generate_AcwProxy_HasPrivateImplementationDetails () Assert.Contains ("", typeDefNames); } + [Fact] + public void Generate_ExportProxy_CallsManagedMethodDirectly () + { + var peers = ScanFixtures (); + var exportPeer = peers.First (p => p.JavaName == "my/app/ExportExample"); + + using var stream = GenerateAssembly (new [] { exportPeer }, "ExportDispatch"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var memberNames = GetMemberRefNames (reader); + Assert.Contains ("MyExportedMethod", memberNames); + Assert.DoesNotContain ("n_MyExportedMethod", memberNames); + } + + [Fact] + public void Generate_StaticExportProxy_CallsManagedMethodDirectly () + { + var peers = ScanFixtures (); + var exportPeer = peers.First (p => p.JavaName == "my/app/StaticExportExample"); + + using var stream = GenerateAssembly (new [] { exportPeer }, "StaticExportDispatch"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var memberNames = GetMemberRefNames (reader); + Assert.Contains ("ComputeLabel", memberNames); + Assert.DoesNotContain ("n_ComputeLabel", memberNames); + } + + [Fact] + public void Generate_ExportProxy_UsesStaticMarshallingHelpers () + { + var peers = ScanFixtures (); + var exportPeer = peers.First (p => p.JavaName == "my/app/ExportWithJavaBoundParams"); + + using var stream = GenerateAssembly (new [] { exportPeer }, "ExportMarshalling"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var memberNames = GetMemberRefNames (reader); + Assert.Contains ("GetObject", memberNames); + Assert.Contains ("NewString", memberNames); + Assert.Contains ("HandleClick", memberNames); + Assert.Contains ("ProcessView", memberNames); + Assert.Contains ("GetViewName", memberNames); + } + + [Fact] + public void Generate_ExportFieldProxy_UsesToLocalJniHandleForObjectReturn () + { + var peers = ScanFixtures (); + var exportFieldPeer = peers.First (p => p.JavaName == "my/app/ExportFieldExample"); + + using var stream = GenerateAssembly (new [] { exportFieldPeer }, "ExportFieldDispatch"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var memberNames = GetMemberRefNames (reader); + Assert.Contains ("ToLocalJniHandle", memberNames); + Assert.Contains ("GetInstance", memberNames); + } + + [Fact] + public void Generate_ExportProxy_SupportsArrayAndLegacyMarshallerHelpers () + { + var peers = ScanFixtures (); + var exportPeer = peers.First (p => p.JavaName == "my/app/ExportMarshallingShapes"); + + using var stream = GenerateAssembly (new [] { exportPeer }, "ExportLegacyMarshalling"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var memberNames = GetMemberRefNames (reader); + Assert.Contains ("FromJniHandle", memberNames); + Assert.Contains ("CopyArray", memberNames); + Assert.Contains ("NewArray", memberNames); + Assert.Contains ("WrapStream", memberNames); + Assert.Contains ("ReadXml", memberNames); + Assert.Contains ("ReadResourceXml", memberNames); + + var typeNames = GetTypeRefNames (reader); + Assert.Contains ("XmlResourceParserReader", typeNames); + Assert.Contains ("XmlReaderResourceParser", typeNames); + } + + [Fact] + public void Generate_ExportProxy_UsesExactCrossAssemblyTypeReferences () + { + var peer = MakePeerWithActivation ("my/app/CrossAssemblyExport", "MyApp.CrossAssemblyExport", "App") with { + DoNotGenerateAcw = false, + MarshalMethods = new List { + new () { + JniName = "convert", + NativeCallbackName = "n_convert", + JniSignature = "(Lthird/party/Widget;)Lthird/party/Result;", + ManagedMethodName = "Convert", + ManagedParameterTypes = new [] { + new TypeRefData { ManagedTypeName = "ThirdParty.Widget", AssemblyName = "ThirdParty.Library" }, + }, + ManagedReturnType = new TypeRefData { ManagedTypeName = "ThirdParty.Result", AssemblyName = "ThirdParty.Library" }, + IsExport = true, + }, + }, + }; + + using var stream = GenerateAssembly (new [] { peer }, "CrossAssemblyExport"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var thirdPartyAsmRef = reader.AssemblyReferences + .First (h => reader.GetString (reader.GetAssemblyReference (h).Name) == "ThirdParty.Library"); + + var typeRefs = reader.TypeReferences + .Select (h => (Handle: h, Ref: reader.GetTypeReference (h))) + .ToList (); + + var widgetRef = typeRefs.First (t => reader.GetString (t.Ref.Name) == "Widget"); + var resultRef = typeRefs.First (t => reader.GetString (t.Ref.Name) == "Result"); + + Assert.Equal (thirdPartyAsmRef, widgetRef.Ref.ResolutionScope); + Assert.Equal (thirdPartyAsmRef, resultRef.Ref.ResolutionScope); + } + + [Theory] + [InlineData ("System.Int32&", "System.Void", "(I)V", "by-ref or pointer")] + [InlineData ("System.Int32*", "System.Void", "(I)V", "by-ref or pointer")] + [InlineData ("System.Int32", "System.Collections.Generic.List", "(I)Ljava/lang/Object;", "generic")] + [InlineData ("!!0", "System.Void", "(Ljava/lang/Object;)V", "generic")] + [InlineData ("!0[]", "System.Void", "([Ljava/lang/Object;)V", "generic")] + public void Generate_ExportProxy_UnsupportedManagedShapesThrow (string parameterType, string returnType, string jniSignature, string expectedMessage) + { + var peer = MakePeerWithActivation ("my/app/UnsupportedExport", "MyApp.UnsupportedExport", "App") with { + DoNotGenerateAcw = false, + MarshalMethods = new List { + new () { + JniName = "badExport", + NativeCallbackName = "n_badExport", + JniSignature = jniSignature, + ManagedMethodName = "BadExport", + ManagedParameterTypes = new [] { + new TypeRefData { ManagedTypeName = parameterType, AssemblyName = "System.Runtime" }, + }, + ManagedReturnType = new TypeRefData { + ManagedTypeName = returnType, + AssemblyName = returnType.StartsWith ("System.Collections.Generic.", StringComparison.Ordinal) + ? "System.Collections" + : "System.Runtime", + }, + IsExport = true, + }, + }, + }; + + var ex = Assert.Throws (() => { + using var stream = GenerateAssembly (new [] { peer }, "UnsupportedExport"); + }); + Assert.Contains (expectedMessage, ex.Message); + } + [Fact] public void Generate_MultipleAcwProxies_DeduplicatesUtf8Strings () { @@ -1144,6 +1429,105 @@ public void Generate_UcoConstructor_HasMarshalMethodMetadataAndExceptionRegions Assert.Contains (regions, r => r.Kind == ExceptionRegionKind.Finally); } + [Fact] + public void Generate_UcoConstructor_ParameterizedPrimitiveCtorCallsManagedConstructor () + { + string jniSignature = "(ZBCSIJFD)V"; + var managedTypes = new [] { + "System.Boolean", + "System.SByte", + "System.Char", + "System.Int16", + "System.Int32", + "System.Int64", + "System.Single", + "System.Double", + }; + var managedTypeRefs = managedTypes.Select (TypeRef).ToArray (); + var peer = MakeAcwPeer ("test/PrimitiveCtorArgs", "Test.PrimitiveCtorArgs", "TestAsm") with { + JavaConstructors = new List { + new JavaConstructorInfo { + ConstructorIndex = 0, + JniSignature = jniSignature, + ManagedParameterTypes = managedTypeRefs, + HasMatchingManagedCtor = true, + }, + }, + MarshalMethods = new List { + new MarshalMethodInfo { + JniName = "", + NativeCallbackName = "n_ctor", + JniSignature = jniSignature, + ManagedMethodName = ".ctor", + IsConstructor = true, + ManagedParameterTypes = managedTypeRefs, + }, + }, + }; + + using var stream = GenerateAssembly (new [] { peer }, "ParameterizedPrimitiveCtorTest"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "PrimitiveCtorArgs", managedTypes)); + + var nctorMethod = reader.GetMethodDefinition (FindNctorUcoMethod (reader)); + var nctorSignature = nctorMethod.DecodeSignature (SignatureTypeProvider.Instance, null); + Assert.Equal ( + new [] { "System.IntPtr", "System.IntPtr", "System.Byte", "System.SByte", "System.UInt16", "System.Int16", "System.Int32", "System.Int64", "System.Single", "System.Double" }, + nctorSignature.ParameterTypes); + } + + [Fact] + public void Generate_UcoConstructor_ParameterizedObjectCtorUsesExplicitMarshalHelpers () + { + string jniSignature = "(Ljava/lang/String;[I[Ljava/lang/String;Landroid/content/Context;)V"; + var managedTypes = new [] { + "System.String", + "System.Int32[]", + "System.String[]", + "Android.Content.Context", + }; + var managedTypeRefs = managedTypes.Select (TypeRef).ToArray (); + var peer = MakeAcwPeer ("test/ObjectCtorArgs", "Test.ObjectCtorArgs", "TestAsm") with { + JavaConstructors = new List { + new JavaConstructorInfo { + ConstructorIndex = 0, + JniSignature = jniSignature, + ManagedParameterTypes = managedTypeRefs, + HasMatchingManagedCtor = true, + }, + }, + MarshalMethods = new List { + new MarshalMethodInfo { + JniName = "", + NativeCallbackName = "n_ctor", + JniSignature = jniSignature, + ManagedMethodName = ".ctor", + IsConstructor = true, + ManagedParameterTypes = managedTypeRefs, + }, + }, + }; + + using var stream = GenerateAssembly (new [] { peer }, "ParameterizedObjectCtorTest"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "ObjectCtorArgs", managedTypes)); + + var memberNames = GetMemberRefNames (reader); + Assert.Contains ("GetString", memberNames); + Assert.Contains ("GetArray", memberNames); + Assert.Contains ("GetObject", memberNames); + var ilBytes = GetNctorUcoIL (pe, reader); + var fromJniHandleTokens = AllMemberRefHandles (reader) + .Where (h => reader.GetString (reader.GetMemberReference (h).Name) == "FromJniHandle") + .Select (h => MetadataTokens.GetToken (h)) + .ToList (); + Assert.DoesNotContain (fromJniHandleTokens, t => ILContainsCallToken (ilBytes, t)); + } + [Fact] public void Generate_UcoConstructor_JiStyle_HasExceptionRegions () { @@ -1258,6 +1642,32 @@ public void Generate_UcoConstructor_InheritedCtor_HasExceptionRegions () Assert.Contains (regions, r => r.Kind == ExceptionRegionKind.Finally); } + [Fact] + public void Generate_UcoConstructor_UsesComputedMaxStack () + { + var peer = MakeAcwPeer ("test/UcoCtorMaxStack", "Test.UcoCtorMaxStack", "TestAsm"); + using var stream = GenerateAssembly (new [] { peer }, "UcoCtorMaxStackTest"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var nctorMethod = reader.GetMethodDefinition (FindNctorUcoMethod (reader)); + var body = pe.GetMethodBody (nctorMethod.RelativeVirtualAddress); + + Assert.InRange (body.MaxStack, 5, 16); + } + + [Fact] + public void TrackedInstructionEncoder_UnconditionalBranchIsUnsupported () + { + var code = new BlobBuilder (); + var controlFlow = new ControlFlowBuilder (); + var encoder = new PEAssemblyBuilder.TrackedInstructionEncoder (new InstructionEncoder (code, controlFlow)); + var label = encoder.DefineLabel (); + + Assert.Throws (() => encoder.Branch (ILOpCode.Br, label)); + Assert.Throws (() => encoder.Branch (ILOpCode.Br_s, label)); + } + [Fact] public void Generate_ProxyTypes_HaveSelfAppliedAttribute () { @@ -1287,4 +1697,409 @@ public void Generate_ProxyTypes_HaveSelfAppliedAttribute () } Assert.True (hasSelfApplied, "Proxy type should have a self-applied attribute (ctor is MethodDefinition)"); } + + [Fact] + public void Generate_UcoConstructor_Parameterless_InvokesUserVisibleCtorViaActivationPeerReference () + { + // Regression test for ContainsExportedMethods (JnienvTest.ActivatedDirectObjectSubclassesShouldBeRegistered): + // for the parameterless `()V` UCO constructor wrapper, the emitter must mirror + // TypeManager.Activate (Mono.Android/Java.Interop/TypeManager.cs): + // + // 1. RuntimeHelpers.GetUninitializedObject(typeof(T)) + // 2. JavaPeerProxy.SetActivationPeerReference(obj, self) + // 3. obj..ctor() // user-visible parameterless ctor + // + // The legacy implementation called the inherited activation ctor `(IntPtr, + // JniHandleOwnership)` instead, so user-visible ctor bodies (e.g. `Constructed = true`) + // never ran when the peer was created from the Java side. + var peer = MakeAcwPeer ("test/UcoCtorPeer", "Test.UcoCtorPeer", "TestAsm"); + using var stream = GenerateAssembly (new [] { peer }, "UcoCtorParameterlessTest"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + // SetActivationPeerReference member ref must exist. + var memberNames = GetMemberRefNames (reader); + Assert.Contains ("SetActivationPeerReference", memberNames); + Assert.Contains ("GetUninitializedObject", memberNames); + + var nctorMethodHandle = FindNctorUcoMethod (reader); + Assert.False (nctorMethodHandle.IsNil, "Expected a nctor_*_uco method in the generated assembly"); + + var nctorMethod = reader.GetMethodDefinition (nctorMethodHandle); + var body = pe.GetMethodBody (nctorMethod.RelativeVirtualAddress); + Assert.NotNull (body); + var ilBytes = body.GetILBytes (); + Assert.NotNull (ilBytes); + + var memberRefHandles = Enumerable.Range (1, reader.GetTableRowCount (TableIndex.MemberRef)) + .Select (i => MetadataTokens.MemberReferenceHandle (i)) + .ToList (); + + // 1. The body must call SetActivationPeerReference (the new behavior). + var setPeerHandle = memberRefHandles.First (h => reader.GetString (reader.GetMemberReference (h).Name) == "SetActivationPeerReference"); + Assert.True (ILContainsCallToken (ilBytes, MetadataTokens.GetToken (setPeerHandle)), + "nctor_*_uco IL should call JavaPeerProxy.SetActivationPeerReference for parameterless ctor"); + + // 2. The body must call GetUninitializedObject (no `newobj` of the activation ctor). + var getUninitHandle = memberRefHandles.First (h => reader.GetString (reader.GetMemberReference (h).Name) == "GetUninitializedObject"); + Assert.True (ILContainsCallToken (ilBytes, MetadataTokens.GetToken (getUninitHandle)), + "nctor_*_uco IL should call RuntimeHelpers.GetUninitializedObject for parameterless ctor"); + + // 3. The body must call the user-visible parameterless ctor on the target type — and + // NOT the (IntPtr, JniHandleOwnership) activation ctor. We disambiguate by signature. + var targetCtorRefs = memberRefHandles + .Where (h => { + var mref = reader.GetMemberReference (h); + if (reader.GetString (mref.Name) != ".ctor") + return false; + if (mref.Parent.Kind != HandleKind.TypeReference) + return false; + var typeRef = reader.GetTypeReference ((TypeReferenceHandle) mref.Parent); + return reader.GetString (typeRef.Name) == "UcoCtorPeer"; + }) + .ToList (); + Assert.NotEmpty (targetCtorRefs); + + var ctorSigDecoder = new MethodSignatureDecoder (); + MemberReferenceHandle? userCtorHandle = null; + MemberReferenceHandle? activationCtorHandle = null; + foreach (var h in targetCtorRefs) { + var mref = reader.GetMemberReference (h); + int paramCount = mref.DecodeMethodSignature (ctorSigDecoder, genericContext: null).RequiredParameterCount; + if (paramCount == 0) userCtorHandle = h; + else if (paramCount == 2) activationCtorHandle = h; + } + + Assert.NotNull (userCtorHandle); + var resolvedUserCtorHandle = userCtorHandle.Value; + Assert.True (ILContainsCallToken (ilBytes, MetadataTokens.GetToken (resolvedUserCtorHandle)), + "nctor_*_uco IL should call the user-visible parameterless ctor on the target type"); + if (activationCtorHandle.HasValue) { + Assert.False (ILContainsCallToken (ilBytes, MetadataTokens.GetToken (activationCtorHandle.Value)), + "nctor_*_uco IL should NOT call the (IntPtr, JniHandleOwnership) activation ctor for parameterless `()V`"); + } + } + + [Fact] + public void Generate_UcoConstructor_ObjectRefParam_MarshalsViaJavaLangObjectGetObject () + { + // (Ljava/lang/Throwable;)V — verifies ref-arg marshalling delegates to + // Java.Lang.Object.GetObject (jniHandle, DoNotTransfer, paramType) + // and that the user-visible (Throwable) ctor is invoked. + var paramType = new TypeRefData { ManagedTypeName = "Java.Lang.Throwable", AssemblyName = "Mono.Android" }; + var peer = MakeAcwPeer ("test/UcoCtorObjArg", "Test.UcoCtorObjArg", "TestAsm") with { + JavaConstructors = new List { + new JavaConstructorInfo { + ConstructorIndex = 0, + JniSignature = "(Ljava/lang/Throwable;)V", + HasMatchingManagedCtor = true, + ManagedParameterTypes = new [] { paramType }, + }, + }, + }; + using var stream = GenerateAssembly (new [] { peer }, "UcoCtorObjArg"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + Assert.Contains ("GetObject", GetMemberRefNames (reader)); + + var ilBytes = GetNctorUcoIL (pe, reader); + var memberRefHandles = AllMemberRefHandles (reader); + + var getObjectHandles = memberRefHandles + .Where (h => reader.GetString (reader.GetMemberReference (h).Name) == "GetObject") + .ToList (); + Assert.Contains (getObjectHandles, h => ILContainsCallToken (ilBytes, MetadataTokens.GetToken (h))); + Assert.True (getObjectHandles.Count > 0, + "nctor_*_uco IL should call Java.Lang.Object.GetObject for an object-ref ctor arg"); + + var userCtor = FindUserCtorRefByFirstParam (reader, "UcoCtorObjArg", paramCount: 1, firstParamTypeName: "Java.Lang.Throwable"); + Assert.NotNull (userCtor); + Assert.True (ILContainsCallToken (ilBytes, MetadataTokens.GetToken (userCtor!.Value)), + "nctor_*_uco IL should call the user-visible (Throwable) ctor"); + } + + [Fact] + public void Generate_UcoConstructor_PrimitiveIntParam_LoadsArgDirectly () + { + // (I)V — verifies primitive int args are loaded directly without GetObject. + var paramType = new TypeRefData { ManagedTypeName = "System.Int32", AssemblyName = "System.Runtime" }; + var peer = MakeAcwPeer ("test/UcoCtorIntArg", "Test.UcoCtorIntArg", "TestAsm") with { + JavaConstructors = new List { + new JavaConstructorInfo { + ConstructorIndex = 0, + JniSignature = "(I)V", + HasMatchingManagedCtor = true, + ManagedParameterTypes = new [] { paramType }, + }, + }, + }; + using var stream = GenerateAssembly (new [] { peer }, "UcoCtorIntArg"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var ilBytes = GetNctorUcoIL (pe, reader); + + // The IL must NOT call GetObject — primitive int is loaded directly via Ldarg. + var memberRefHandles = AllMemberRefHandles (reader); + var getObjectHandle = memberRefHandles.FirstOrDefault (h => reader.GetString (reader.GetMemberReference (h).Name) == "GetObject"); + if (!getObjectHandle.IsNil) { + Assert.False (ILContainsCallToken (ilBytes, MetadataTokens.GetToken (getObjectHandle)), + "nctor_*_uco IL should NOT call GetObject for a primitive int ctor arg"); + } + + var userCtor = FindUserCtorRef (reader, "UcoCtorIntArg", new [] { "System.Int32" }); + Assert.NotNull (userCtor); + Assert.True (ILContainsCallToken (ilBytes, MetadataTokens.GetToken (userCtor!.Value)), + "nctor_*_uco IL should call the user-visible (int) ctor"); + } + + [Fact] + public void Generate_UcoConstructor_BooleanParam_EmitsByteToBoolConversion () + { + // (Z)V — verifies byte→bool conversion (Ldc.I4.0 + Cgt.Un) is emitted for + // System.Boolean params, matching ExportMethodDispatchEmitter's primitive marshalling. + var paramType = new TypeRefData { ManagedTypeName = "System.Boolean", AssemblyName = "System.Runtime" }; + var peer = MakeAcwPeer ("test/UcoCtorBoolArg", "Test.UcoCtorBoolArg", "TestAsm") with { + JavaConstructors = new List { + new JavaConstructorInfo { + ConstructorIndex = 0, + JniSignature = "(Z)V", + HasMatchingManagedCtor = true, + ManagedParameterTypes = new [] { paramType }, + }, + }, + }; + using var stream = GenerateAssembly (new [] { peer }, "UcoCtorBoolArg"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var ilBytes = GetNctorUcoIL (pe, reader); + + // Look for the bool conversion sequence: Ldc_I4_0 (0x16) ; Cgt_Un (0xFE 0x03) + bool foundBoolConv = false; + for (int i = 0; i < ilBytes.Length - 2; i++) { + if (ilBytes [i] == 0x16 && ilBytes [i + 1] == 0xFE && ilBytes [i + 2] == 0x03) { + foundBoolConv = true; + break; + } + } + Assert.True (foundBoolConv, "nctor_*_uco IL should emit Ldc.I4.0 + Cgt.Un to convert byte→bool for Boolean ctor arg"); + + var userCtor = FindUserCtorRef (reader, "UcoCtorBoolArg", new [] { "System.Boolean" }); + Assert.NotNull (userCtor); + Assert.True (ILContainsCallToken (ilBytes, MetadataTokens.GetToken (userCtor!.Value)), + "nctor_*_uco IL should call the user-visible (bool) ctor"); + } + + [Fact] + public void Generate_UcoConstructor_StringParam_MarshalsViaJniEnvGetString () + { + // (Ljava/lang/String;)V — verifies String args marshal via JNIEnv.GetString. + var paramType = new TypeRefData { ManagedTypeName = "System.String", AssemblyName = "System.Runtime" }; + var peer = MakeAcwPeer ("test/UcoCtorStrArg", "Test.UcoCtorStrArg", "TestAsm") with { + JavaConstructors = new List { + new JavaConstructorInfo { + ConstructorIndex = 0, + JniSignature = "(Ljava/lang/String;)V", + HasMatchingManagedCtor = true, + ManagedParameterTypes = new [] { paramType }, + }, + }, + }; + using var stream = GenerateAssembly (new [] { peer }, "UcoCtorStrArg"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var ilBytes = GetNctorUcoIL (pe, reader); + var memberRefHandles = AllMemberRefHandles (reader); + + // JNIEnv.GetString member ref must be present and called. + var getStringHandles = memberRefHandles.Where (h => { + var mref = reader.GetMemberReference (h); + if (reader.GetString (mref.Name) != "GetString") + return false; + if (mref.Parent.Kind != HandleKind.TypeReference) + return false; + var typeRef = reader.GetTypeReference ((TypeReferenceHandle) mref.Parent); + return reader.GetString (typeRef.Name) == "JNIEnv"; + }).ToList (); + Assert.NotEmpty (getStringHandles); + Assert.Contains (getStringHandles, h => ILContainsCallToken (ilBytes, MetadataTokens.GetToken (h))); + + var userCtor = FindUserCtorRef (reader, "UcoCtorStrArg", new [] { "System.String" }); + Assert.NotNull (userCtor); + Assert.True (ILContainsCallToken (ilBytes, MetadataTokens.GetToken (userCtor!.Value)), + "nctor_*_uco IL should call the user-visible (string) ctor"); + } + + [Fact] + public void Generate_UcoConstructor_MixedSignature_MarshalsBothPrimitiveAndObjectArgs () + { + // (ILjava/lang/Throwable;)V — verifies int passes through and Throwable goes via GetObject. + var intParam = new TypeRefData { ManagedTypeName = "System.Int32", AssemblyName = "System.Runtime" }; + var throwableParam = new TypeRefData { ManagedTypeName = "Java.Lang.Throwable", AssemblyName = "Mono.Android" }; + var peer = MakeAcwPeer ("test/UcoCtorMixed", "Test.UcoCtorMixed", "TestAsm") with { + JavaConstructors = new List { + new JavaConstructorInfo { + ConstructorIndex = 0, + JniSignature = "(ILjava/lang/Throwable;)V", + HasMatchingManagedCtor = true, + ManagedParameterTypes = new [] { intParam, throwableParam }, + }, + }, + }; + using var stream = GenerateAssembly (new [] { peer }, "UcoCtorMixed"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + var memberNames = GetMemberRefNames (reader); + Assert.Contains ("GetObject", memberNames); + + var ilBytes = GetNctorUcoIL (pe, reader); + var memberRefHandles = AllMemberRefHandles (reader); + var getObjectHandles = memberRefHandles + .Where (h => reader.GetString (reader.GetMemberReference (h).Name) == "GetObject") + .ToList (); + Assert.Contains (getObjectHandles, h => ILContainsCallToken (ilBytes, MetadataTokens.GetToken (h))); + Assert.True (getObjectHandles.Count > 0, + "nctor_*_uco IL should call GetObject for the Throwable arg in the mixed signature"); + + // User ctor: (Int32, Java.Lang.Throwable). Need a signature-discriminated lookup + // because the activation ctor (IntPtr, JniHandleOwnership) also has 2 params. + var userCtor = FindUserCtorRefByFirstParam (reader, "UcoCtorMixed", paramCount: 2, firstParamTypeName: "System.Int32"); + Assert.NotNull (userCtor); + Assert.True (ILContainsCallToken (ilBytes, MetadataTokens.GetToken (userCtor!.Value)), + "nctor_*_uco IL should call the user-visible (int, Throwable) ctor"); + } + + static byte[] GetNctorUcoIL (PEReader pe, MetadataReader reader) + { + var nctorMethodHandle = FindNctorUcoMethod (reader); + Assert.False (nctorMethodHandle.IsNil, "Expected a nctor_*_uco method in the generated assembly"); + var nctorMethod = reader.GetMethodDefinition (nctorMethodHandle); + var body = pe.GetMethodBody (nctorMethod.RelativeVirtualAddress); + Assert.NotNull (body); + var ilBytes = body.GetILBytes (); + Assert.NotNull (ilBytes); + return ilBytes!; + } + + static void AssertCreateInstanceReturnsNull (PEReader pe, MetadataReader reader, string proxyTypeName) + { + var proxyTypeHandle = reader.TypeDefinitions.First (h => { + var type = reader.GetTypeDefinition (h); + return reader.GetString (type.Namespace) == "_TypeMap.Proxies" && + reader.GetString (type.Name) == proxyTypeName; + }); + var proxyType = reader.GetTypeDefinition (proxyTypeHandle); + var createInstanceHandle = proxyType.GetMethods ().First (h => + reader.GetString (reader.GetMethodDefinition (h).Name) == "CreateInstance"); + var createInstance = reader.GetMethodDefinition (createInstanceHandle); + var body = pe.GetMethodBody (createInstance.RelativeVirtualAddress); + Assert.NotNull (body); + var ilBytes = body.GetILBytes (); + Assert.NotNull (ilBytes); + Assert.Equal (new [] { (byte) ILOpCode.Ldnull, (byte) ILOpCode.Ret }, ilBytes!); + } + + static List AllMemberRefHandles (MetadataReader reader) => + Enumerable.Range (1, reader.GetTableRowCount (TableIndex.MemberRef)) + .Select (i => MetadataTokens.MemberReferenceHandle (i)) + .ToList (); + + static MemberReferenceHandle? FindUserCtorRef (MetadataReader reader, string typeShortName, IReadOnlyList paramTypeNames) + { + var decoder = new TypeNameSignatureDecoder (reader); + foreach (var h in AllMemberRefHandles (reader)) { + var mref = reader.GetMemberReference (h); + if (reader.GetString (mref.Name) != ".ctor") + continue; + if (mref.Parent.Kind != HandleKind.TypeReference) + continue; + var typeRef = reader.GetTypeReference ((TypeReferenceHandle) mref.Parent); + if (reader.GetString (typeRef.Name) != typeShortName) + continue; + var sig = mref.DecodeMethodSignature (decoder, genericContext: null); + if (sig.RequiredParameterCount != paramTypeNames.Count) + continue; + bool match = true; + for (int i = 0; i < paramTypeNames.Count; i++) { + if (sig.ParameterTypes [i] != paramTypeNames [i]) { + match = false; + break; + } + } + if (match) + return h; + } + return null; + } + + static MemberReferenceHandle? FindUserCtorRefByFirstParam (MetadataReader reader, string typeShortName, int paramCount, string firstParamTypeName) + { + var decoder = new TypeNameSignatureDecoder (reader); + foreach (var h in AllMemberRefHandles (reader)) { + var mref = reader.GetMemberReference (h); + if (reader.GetString (mref.Name) != ".ctor") + continue; + if (mref.Parent.Kind != HandleKind.TypeReference) + continue; + var typeRef = reader.GetTypeReference ((TypeReferenceHandle) mref.Parent); + if (reader.GetString (typeRef.Name) != typeShortName) + continue; + var sig = mref.DecodeMethodSignature (decoder, genericContext: null); + if (sig.RequiredParameterCount != paramCount) + continue; + if (sig.ParameterTypes [0] == firstParamTypeName) + return h; + } + return null; + } + + // SignatureTypeProvider returning a stringified type name for primitives and typerefs. + sealed class TypeNameSignatureDecoder : ISignatureTypeProvider + { + readonly MetadataReader _reader; + public TypeNameSignatureDecoder (MetadataReader reader) => _reader = reader; + public string GetPrimitiveType (PrimitiveTypeCode typeCode) => "System." + typeCode; + public string GetTypeFromReference (MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) + { + var tr = reader.GetTypeReference (handle); + var name = reader.GetString (tr.Name); + var ns = tr.Namespace.IsNil ? "" : reader.GetString (tr.Namespace); + return ns.Length == 0 ? name : ns + "." + name; + } + public string GetTypeFromDefinition (MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) => ""; + public string GetTypeFromSpecification (MetadataReader reader, object? genericContext, TypeSpecificationHandle handle, byte rawTypeKind) => ""; + public string GetSZArrayType (string elementType) => elementType + "[]"; + public string GetArrayType (string elementType, ArrayShape shape) => elementType + "[*]"; + public string GetByReferenceType (string elementType) => elementType + "&"; + public string GetFunctionPointerType (MethodSignature signature) => ""; + public string GetGenericInstantiation (string genericType, ImmutableArray typeArguments) => genericType; + public string GetGenericMethodParameter (object? genericContext, int index) => ""; + public string GetGenericTypeParameter (object? genericContext, int index) => ""; + public string GetModifiedType (string modifier, string unmodifiedType, bool isRequired) => unmodifiedType; + public string GetPinnedType (string elementType) => elementType; + public string GetPointerType (string elementType) => elementType + "*"; + } + + // Minimal SignatureTypeProvider used only to count required parameters of a member ref. + sealed class MethodSignatureDecoder : ISignatureTypeProvider + { + public int GetArrayType (int elementType, ArrayShape shape) => 0; + public int GetByReferenceType (int elementType) => 0; + public int GetFunctionPointerType (MethodSignature signature) => 0; + public int GetGenericInstantiation (int genericType, ImmutableArray typeArguments) => 0; + public int GetGenericMethodParameter (object? genericContext, int index) => 0; + public int GetGenericTypeParameter (object? genericContext, int index) => 0; + public int GetModifiedType (int modifier, int unmodifiedType, bool isRequired) => 0; + public int GetPinnedType (int elementType) => 0; + public int GetPointerType (int elementType) => 0; + public int GetPrimitiveType (PrimitiveTypeCode typeCode) => 0; + public int GetSZArrayType (int elementType) => 0; + public int GetTypeFromDefinition (MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) => 0; + public int GetTypeFromReference (MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) => 0; + public int GetTypeFromSpecification (MetadataReader reader, object? genericContext, TypeSpecificationHandle handle, byte rawTypeKind) => 0; + } } diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs index 29eb367c39b..c01c5701d00 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs @@ -135,6 +135,54 @@ public void Build_AliasWithMixedActivation_PrimaryNoActivation_AliasHasActivatio // Both peers get associations to alias holder Assert.Equal (2, model.Associations.Count); } + + [Fact] + public void Build_AllMcwAliasGroup_BaseEntryIsConditional () + { + // When all peers in an alias group are MCW bindings (trimmable), + // the base alias-holder entry should be conditional (3-arg). + var peers = new List { + MakeMcwPeer ("test/AllMcw", "Test.First", "A") with { DoNotGenerateAcw = true }, + MakeMcwPeer ("test/AllMcw", "Test.Second", "A") with { DoNotGenerateAcw = true }, + }; + + var model = BuildModel (peers); + var baseEntry = model.Entries.Single (e => e.JniName == "test/AllMcw"); + Assert.False (baseEntry.IsUnconditional, "All-MCW alias group base entry should be conditional"); + Assert.NotNull (baseEntry.TargetTypeReference); + } + + [Fact] + public void Build_MixedAcwMcwAliasGroup_BaseEntryIsUnconditional () + { + // When at least one peer in an alias group is an ACW (unconditional), + // the base alias-holder entry should be unconditional (2-arg). + var peers = new List { + MakeMcwPeer ("test/Mixed", "Test.Mcw", "A") with { DoNotGenerateAcw = true }, + MakeAcwPeer ("test/Mixed", "Test.Acw", "A"), + }; + + var model = BuildModel (peers); + var baseEntry = model.Entries.Single (e => e.JniName == "test/Mixed"); + Assert.True (baseEntry.IsUnconditional, "Mixed alias group with ACW should have unconditional base entry"); + Assert.Null (baseEntry.TargetTypeReference); + } + + [Fact] + public void Build_EssentialTypeAliasGroup_BaseEntryIsUnconditional () + { + // Essential runtime types (java/lang/Object etc.) should always be unconditional, + // even when all peers are MCW bindings. + var peers = new List { + MakeMcwPeer ("java/lang/Object", "Java.Lang.Object", "Mono.Android"), + MakeMcwPeer ("java/lang/Object", "Java.Lang.Another", "Mono.Android"), + }; + + var model = BuildModel (peers); + var baseEntry = model.Entries.Single (e => e.JniName == "java/lang/Object"); + Assert.True (baseEntry.IsUnconditional, "Essential type alias group should have unconditional base entry"); + Assert.Null (baseEntry.TargetTypeReference); + } } public class ConditionalAttributes @@ -172,14 +220,12 @@ public void Build_UserAcwType_IsUnconditional () public void Build_McwBinding_IsTrimmable () { // MCW binding types (DoNotGenerateAcw=true) are trimmable unless essential. - // When ForceUnconditionalEntries is enabled (workaround for dotnet/runtime#127004), - // all entries become unconditional. var peer = MakeMcwPeer ("android/app/Activity", "Android.App.Activity", "Mono.Android") with { DoNotGenerateAcw = true }; var model = BuildModel (new [] { peer }); Assert.Single (model.Entries); - Assert.True (model.Entries [0].IsUnconditional); - Assert.Null (model.Entries [0].TargetTypeReference); + Assert.False (model.Entries [0].IsUnconditional); + Assert.Equal ("Android.App.Activity, Mono.Android", model.Entries [0].TargetTypeReference); } [Fact] @@ -194,6 +240,23 @@ public void Build_UnconditionalScannedType_IsUnconditional () Assert.True (model.Entries [0].IsUnconditional); } + + [Fact] + public void Build_FrameworkAcwType_IsConditional () + { + var frameworkAcwPeer = MakeAcwPeer ("mono/android/view/View_ClickEventDispatcher", "Android.Views.View_ClickEventDispatcher", "Mono.Android") with { + IsFrameworkAssembly = true, + }; + var appAcwPeer = MakeAcwPeer ("my/app/MyActivity", "MyApp.MyActivity", "MyApp"); + + Assert.False ( + BuildModel ([frameworkAcwPeer]).Entries.Single ().IsUnconditional, + "Framework ACWs should not unconditionally root their proxy types."); + Assert.True ( + BuildModel ([appAcwPeer]).Entries.Single ().IsUnconditional, + "Application ACWs must remain unconditional because Java can instantiate them."); + } + } public class Aliases @@ -248,8 +311,8 @@ public void Build_PeerWithActivation_CreatesNamedProxy (string jniName, string m [Fact] public void Build_SinglePeer_HasAssociation () { - // When ForceUnconditionalEntries is enabled, single peers emit associations - // so the runtime proxy type map is populated. + // Single peers with generated proxies emit associations so the runtime proxy + // type map is populated. var peer = MakePeerWithActivation ("my/app/MainActivity", "MyApp.MainActivity", "App"); var model = BuildModel (new [] { peer }, "MyTypeMap"); @@ -278,7 +341,7 @@ public void Build_PeerWithInvoker_CreatesProxy () public void Build_Crc64RenamedPeer_StoresFinalJavaNameOnProxy (string managedName) { var peer = FindFixtureByManagedName (managedName); - Assert.StartsWith ("crc64", peer.JavaName); + Assert.StartsWith ("scrc64", peer.JavaName); Assert.NotEqual (peer.CompatJniName, peer.JavaName); var model = BuildModel (new [] { peer }, "MyTypeMap"); @@ -338,8 +401,8 @@ public void Fixture_McwBinding_IsTrimmable (string javaName) var peer = FindFixtureByJavaName (javaName); Assert.True (peer.DoNotGenerateAcw); var model = BuildModel (new [] { peer }); - // ForceUnconditionalEntries workaround makes all entries unconditional - Assert.True (model.Entries [0].IsUnconditional); + Assert.False (model.Entries [0].IsUnconditional); + Assert.NotNull (model.Entries [0].TargetTypeReference); } } @@ -776,7 +839,6 @@ public class PeBlobValidation [Fact] public void FullPipeline_Mixed2ArgAnd3Arg_BothSurviveRoundTrip () { - // With ForceUnconditionalEntries, both are emitted as 2-arg unconditional var objectPeer = FindFixtureByJavaName ("java/lang/Object"); var activityPeer = FindFixtureByJavaName ("android/app/Activity"); @@ -793,7 +855,7 @@ public void FullPipeline_Mixed2ArgAnd3Arg_BothSurviveRoundTrip () var activityEntry = attrs.FirstOrDefault (a => a.jniName == "android/app/Activity"); Assert.NotNull (activityEntry.jniName); - Assert.Null (activityEntry.targetRef); // unconditional due to ForceUnconditionalEntries + Assert.Equal ("Android.App.Activity, TestFixtures", activityEntry.targetRef); }); } @@ -818,22 +880,20 @@ public void FullPipeline_UnconditionalType_Emits2ArgAttribute (string javaName, } [Fact] - public void FullPipeline_McwBinding_Emits2ArgAttribute_WithWorkaround () + public void FullPipeline_McwBinding_Emits3ArgAttribute () { - // With ForceUnconditionalEntries workaround for dotnet/runtime#127004, - // MCW bindings are emitted as 2-arg unconditional. var peer = FindFixtureByJavaName ("android/app/Activity"); - var model = BuildModel (new [] { peer }, "Blob2ArgWorkaround"); + var model = BuildModel (new [] { peer }, "Blob3ArgConditional"); Assert.Single (model.Entries); - Assert.True (model.Entries [0].IsUnconditional); + Assert.False (model.Entries [0].IsUnconditional); - EmitAndVerify (model, "Blob2ArgWorkaround", (pe, reader) => { + EmitAndVerify (model, "Blob3ArgConditional", (pe, reader) => { var (jniName, proxyRef, targetRef) = ReadFirstTypeMapAttributeBlob (reader); Assert.Equal ("android/app/Activity", jniName); Assert.NotNull (proxyRef); Assert.Contains ("Android_App_Activity_Proxy", proxyRef!); - Assert.Null (targetRef); // unconditional due to ForceUnconditionalEntries + Assert.Equal ("Android.App.Activity, TestFixtures", targetRef); }); } } @@ -965,6 +1025,28 @@ public void Build_EmitArrayEntries_OpenGenericPeer_Skipped () Assert.DoesNotContain (model.Entries, e => e.AnchorRank is not null); } + [Fact] + public void Build_EmitArrayEntries_FrameworkPeer_Skipped () + { + var frameworkPeer = MakeMcwPeer ("android/widget/Button", "Android.Widget.Button", "Mono.Android") + with { IsFrameworkAssembly = true, GenerateArrayEntries = false }; + var model = BuildModelWithArrays (new [] { frameworkPeer }); + + Assert.DoesNotContain (model.Entries, e => e.AnchorRank is not null); + } + + [Fact] + public void Build_EmitArrayEntries_ReferencedFrameworkPeer_Emitted () + { + var frameworkPeer = MakeMcwPeer ("android/widget/Button", "Android.Widget.Button", "Mono.Android") + with { IsFrameworkAssembly = true, GenerateArrayEntries = true }; + var model = BuildModelWithArrays (new [] { frameworkPeer }); + + var arrayEntries = model.Entries.Where (e => e.AnchorRank is not null).ToList (); + Assert.Equal (3, arrayEntries.Count); + Assert.All (arrayEntries, e => Assert.Equal ("android/widget/Button", e.JniName)); + } + [Fact] public void Build_EmitArrayEntries_AliasGroup_Skipped () { @@ -1250,8 +1332,15 @@ public void Build_AcwWithConstructors_CreatesUcoConstructors () { var peer = MakeAcwPeer ("my/app/Baz", "MyApp.Baz", "App") with { JavaConstructors = new List { - new JavaConstructorInfo { ConstructorIndex = 0, JniSignature = "()V" }, - new JavaConstructorInfo { ConstructorIndex = 1, JniSignature = "(Landroid/content/Context;)V" }, + new JavaConstructorInfo { ConstructorIndex = 0, JniSignature = "()V", HasMatchingManagedCtor = true }, + new JavaConstructorInfo { + ConstructorIndex = 1, + JniSignature = "(Landroid/content/Context;)V", + HasMatchingManagedCtor = true, + ManagedParameterTypes = [ + new TypeRefData { ManagedTypeName = "Android.Content.Context", AssemblyName = "Mono.Android" }, + ], + }, }, }; var model = BuildModel (new [] { peer }); @@ -1267,6 +1356,39 @@ public void Build_PeerWithoutActivationCtor_NoUcoConstructors () var model = BuildModel (new [] { peer }); Assert.Empty (model.ProxyTypes); } + + [Fact] + public void Build_ExportConstructorWithoutMatchingManagedCtor_Throws () + { + var peer = MakeAcwPeer ("my/app/MissingCtor", "MyApp.MissingCtor", "App") with { + JavaConstructors = new List { + new JavaConstructorInfo { ConstructorIndex = 0, JniSignature = "()V", HasMatchingManagedCtor = false, SuperArgumentsString = "" }, + }, + }; + var ex = Assert.Throws (() => BuildModel (new [] { peer })); + Assert.Contains ("no matching user-visible managed constructor", ex.Message); + Assert.Contains ("MyApp.MissingCtor", ex.Message); + } + + [Fact] + public void Build_AbstractTypeWithProtectedCtor_NoUcoConstructors () + { + var peer = MakeAcwPeer ("my/app/AbstractAdapter", "MyApp.AbstractAdapter", "App") with { + IsAbstract = true, + JavaConstructors = new List { + new JavaConstructorInfo { + ConstructorIndex = 0, + JniSignature = "(Landroid/content/Context;)V", + HasMatchingManagedCtor = false, + SuperArgumentsString = "p0", + }, + }, + }; + var model = BuildModel (new [] { peer }); + var proxy = model.ProxyTypes.FirstOrDefault (p => p.TypeName.Contains ("AbstractAdapter")); + Assert.NotNull (proxy); + Assert.Empty (proxy.UcoConstructors); + } } public class NativeRegistrations @@ -1287,7 +1409,7 @@ public void Build_NativeRegistrations_MatchUcoMethods () }, }, JavaConstructors = new List { - new JavaConstructorInfo { ConstructorIndex = 0, JniSignature = "()V" }, + new JavaConstructorInfo { ConstructorIndex = 0, JniSignature = "()V", HasMatchingManagedCtor = true }, }, }; var model = BuildModel (new [] { peer }); @@ -1343,6 +1465,58 @@ public void Fixture_TouchHandler_AllUcoMethods () Assert.True (proxy.UcoMethods.Count >= 2, "TouchHandler should have multiple UCO methods"); } + [Fact] + public void Fixture_ExportExample_UsesExportMethodDispatch () + { + var peer = FindFixtureByJavaName ("my/app/ExportExample"); + var model = BuildModel (new [] { peer }, "TypeMap"); + var proxy = model.ProxyTypes.FirstOrDefault (); + Assert.NotNull (proxy); + var exportUco = Assert.Single (proxy.UcoMethods); + var exportDispatch = exportUco.ExportMethodDispatch; + Assert.True (exportUco.UsesExportMethodDispatch); + Assert.NotNull (exportDispatch); + Assert.Equal ("MyExportedMethod", exportDispatch.ManagedMethodName); + } + + [Fact] + public void Fixture_StaticExportExample_UsesStaticExportMethodDispatch () + { + var peer = FindFixtureByJavaName ("my/app/StaticExportExample"); + var model = BuildModel (new [] { peer }, "TypeMap"); + var proxy = model.ProxyTypes.FirstOrDefault (); + Assert.NotNull (proxy); + var exportUco = Assert.Single (proxy.UcoMethods); + var exportDispatch = exportUco.ExportMethodDispatch; + Assert.True (exportUco.UsesExportMethodDispatch); + Assert.NotNull (exportDispatch); + Assert.True (exportDispatch.IsStatic); + Assert.Equal ("ComputeLabel", exportDispatch.ManagedMethodName); + } + + [Fact] + public void Fixture_ExportMarshallingShapes_PropagatesExactManagedTypeMetadata () + { + var peer = FindFixtureByJavaName ("my/app/ExportMarshallingShapes"); + var model = BuildModel (new [] { peer }, "TypeMap"); + var proxy = model.ProxyTypes.FirstOrDefault (); + Assert.NotNull (proxy); + + var xmlUco = proxy.UcoMethods.First (u => u.ExportMethodDispatch?.ManagedMethodName == "ReadXml"); + var xmlDispatch = xmlUco.ExportMethodDispatch; + Assert.NotNull (xmlDispatch); + Assert.Equal ("System.Xml.XmlReader", xmlDispatch.ParameterTypes [0].ManagedTypeName); + Assert.Equal ("System.Xml.ReaderWriter", xmlDispatch.ParameterTypes [0].AssemblyName); + Assert.Equal (ExportParameterKindInfo.XmlPullParser, xmlDispatch.ParameterKinds [0]); + Assert.Equal (ExportParameterKindInfo.XmlPullParser, xmlDispatch.ReturnKind); + + var resourceXmlUco = proxy.UcoMethods.First (u => u.ExportMethodDispatch?.ManagedMethodName == "ReadResourceXml"); + var resourceXmlDispatch = resourceXmlUco.ExportMethodDispatch; + Assert.NotNull (resourceXmlDispatch); + Assert.Equal (ExportParameterKindInfo.XmlResourceParser, resourceXmlDispatch.ParameterKinds [0]); + Assert.Equal (ExportParameterKindInfo.XmlResourceParser, resourceXmlDispatch.ReturnKind); + } + [Fact] public void Fixture_CustomView_HasTwoConstructorWrappers () { diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/ConstructorDetectionTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/ConstructorDetectionTests.cs index 8d591ae9669..3e051ecffc7 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/ConstructorDetectionTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/ConstructorDetectionTests.cs @@ -111,6 +111,20 @@ public void CustomDialog_SameArityTypeMismatch_UsesParameterlessFallback () Assert.Contains ("(Ljava/lang/String;)V", ctorSigs); } + [Fact] + public void HasMatchingManagedCtor_False_WhenSameArityCtorHasDifferentParameterType () + { + var peer = FindFixtureByJavaName ("my/app/CustomDialog"); + + var contextCtor = Assert.Single (peer.JavaConstructors, c => c.JniSignature == "(Landroid/content/Context;)V"); + Assert.False (contextCtor.HasMatchingManagedCtor, + "CustomDialog has only a string ctor; the inherited Context Java ctor must not match by arity alone."); + + var stringCtor = Assert.Single (peer.JavaConstructors, c => c.JniSignature == "(Ljava/lang/String;)V"); + Assert.True (stringCtor.HasMatchingManagedCtor, + "The parameterless-fallback string Java ctor should match CustomDialog(string)."); + } + [Fact] public void ActivityWithMultiParamCtor_FallbackComputesFullSignature () { @@ -140,4 +154,65 @@ public void UnsignedPrimitiveCtor_MapsCorrectly () var ctorSigs = peer.JavaConstructors.Select (c => c.JniSignature).ToList (); Assert.Contains ("(SIJ)V", ctorSigs); } + + // --- Regression: HasMatchingManagedCtor semantics --- + // These guard the safety net introduced for Java.Lang.Thread+RunnableImplementor: + // when a Java ctor (e.g. ()V seeded from a [Register]'d base) has no matching + // user-visible managed ctor, HasMatchingManagedCtor must be false so the UCO + // model builder can fail instead of emitting an invalid constructor wrapper. + // If this flips silently to true, the generator emits a member ref to a + // non-existent managed ctor — manifesting at runtime as + // `MissingMethodException: Default constructor not found for type ...`. + + [Fact] + public void HasMatchingManagedCtor_True_WhenExplicitParameterlessExists () + { + // MainActivity defines `public MainActivity () { }` — the scanner must + // flag the inherited ()V ctor as having a matching user-visible managed + // ctor so codegen invokes the user ctor rather than falling back. + var peer = FindFixtureByJavaName ("my/app/MainActivity"); + var voidCtor = Assert.Single (peer.JavaConstructors, c => c.JniSignature == "()V"); + Assert.True (voidCtor.HasMatchingManagedCtor, + "MainActivity has an explicit public () ctor; the scanner must record HasMatchingManagedCtor = true."); + } + + [Fact] + public void HasMatchingManagedCtor_False_WhenOnlyActivationCtorExists () + { + // UserActivity only declares the activation ctor (IntPtr, JniHandleOwnership). + // There is NO user-visible managed `() : base()`. The Java side gets a ()V + // ctor seeded from Activity. RunnableImplementor in the SDK has the same + // shape (only parameterized managed ctors + a JCW-codegen-emitted ()V). + // HasMatchingManagedCtor MUST be false here, or the generator will emit a + // metadata reference to a non-existent ..ctor() and the runtime explodes + // with MissingMethodException once Java tries to activate the peer. + var peer = FindFixtureByJavaName ("my/app/UserActivity"); + var voidCtor = Assert.Single (peer.JavaConstructors, c => c.JniSignature == "()V"); + Assert.False (voidCtor.HasMatchingManagedCtor, + "UserActivity has only an activation ctor; HasMatchingManagedCtor must be false so model building fails."); + } + + [Fact] + public void HasMatchingManagedCtor_False_WhenOnlyProtectedDefaultCtorExists () + { + var peer = FindFixtureByJavaName ("my/app/ProtectedDefaultCtorActivity"); + var voidCtor = Assert.Single (peer.JavaConstructors, c => c.JniSignature == "()V"); + Assert.False (voidCtor.HasMatchingManagedCtor, + "The scanner must only match public managed constructors for Java-visible constructor wrappers."); + } + + [Fact] + public void HasMatchingManagedCtor_False_WhenOnlyParameterizedManagedCtorExists () + { + // ActivityWithCustomCtor has only an activation ctor + a (string) ctor. + // The Java ()V ctor is seeded from Activity. There is no managed ()V. + // HasMatchingManagedCtor MUST be false on the ()V Java ctor. + // (The (Ljava/lang/String;)V Java ctor uses parameterless-fallback codegen, + // which is a different code path documented by SuperArgumentsString = "".) + var peer = FindFixtureByJavaName ("my/app/ActivityWithCustomCtor"); + var voidCtor = Assert.Single (peer.JavaConstructors, c => c.JniSignature == "()V"); + Assert.False (voidCtor.HasMatchingManagedCtor, + "Only parameterized managed ctors exist; the inherited ()V seed must not claim a managed match."); + } + } diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.Behavior.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.Behavior.cs index 659de452634..d6b10c033ce 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.Behavior.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.Behavior.cs @@ -15,6 +15,7 @@ public partial class JavaPeerScannerTests [InlineData ("my/app/TouchHandler", "OnFocusChange", "onFocusChange", "(Landroid/view/View;Z)V")] [InlineData ("my/app/TouchHandler", "OnScroll", "onScroll", "(IFJD)V")] [InlineData ("my/app/TouchHandler", "SetItems", "setItems", "([Ljava/lang/String;)V")] + [InlineData ("my/app/StaticExportExample", "ComputeLabel", "computeLabel", "(I)Ljava/lang/String;")] public void Scan_MarshalMethod_HasCorrectSignature (string javaName, string managedName, string jniName, string jniSig) { var method = FindFixtureByJavaName (javaName) @@ -58,15 +59,105 @@ public void Scan_MarshalMethod_ConstructorsAndSpecialCases () [InlineData ("processView", "(Landroid/view/View;)V")] [InlineData ("handleClick", "(Landroid/view/View;I)Z")] [InlineData ("getViewName", "(Landroid/view/View;)Ljava/lang/String;")] + [InlineData ("computeLabel", "(I)Ljava/lang/String;")] public void Scan_ExportMethod_ResolvesJavaBoundParameterTypes (string jniName, string expectedSig) { - var method = FindFixtureByJavaName ("my/app/ExportWithJavaBoundParams") + var peer = jniName == "computeLabel" + ? FindFixtureByJavaName ("my/app/StaticExportExample") + : FindFixtureByJavaName ("my/app/ExportWithJavaBoundParams"); + var method = peer .MarshalMethods.FirstOrDefault (m => m.JniName == jniName); Assert.NotNull (method); Assert.Equal (expectedSig, method.JniSignature); Assert.Null (method.Connector); } + [Fact] + public void Scan_ExportMethod_CapturesStaticDispatchShape () + { + var method = FindFixtureByJavaName ("my/app/StaticExportExample") + .MarshalMethods.Single (m => m.JniName == "computeLabel"); + Assert.True (method.IsStatic); + Assert.Equal ("ComputeLabel", method.ManagedMethodName); + } + + [Theory] + [InlineData ("roundTripNames", "([Ljava/lang/String;)[Ljava/lang/String;")] + [InlineData ("openStream", "(Ljava/io/InputStream;)I")] + [InlineData ("wrapStream", "(Ljava/io/OutputStream;)Ljava/io/OutputStream;")] + [InlineData ("readXml", "(Lorg/xmlpull/v1/XmlPullParser;)Lorg/xmlpull/v1/XmlPullParser;")] + [InlineData ("readResourceXml", "(Landroid/content/res/XmlResourceParser;)Landroid/content/res/XmlResourceParser;")] + public void Scan_ExportMethod_SupportsLegacyMarshallerShapes (string jniName, string expectedSig) + { + var method = FindFixtureByJavaName ("my/app/ExportMarshallingShapes") + .MarshalMethods.FirstOrDefault (m => m.JniName == jniName); + Assert.NotNull (method); + Assert.Equal (expectedSig, method.JniSignature); + } + + [Theory] + [InlineData ("echoEnum", "(I)I")] + [InlineData ("echoByteEnum", "(B)B")] + [InlineData ("echoLongEnum", "(J)J")] + public void Scan_ExportMethod_EnumParametersUseUnderlyingPrimitiveJniDescriptor (string jniName, string expectedSig) + { + var method = FindFixtureByJavaName ("my/app/ExportEnumShapes") + .MarshalMethods.FirstOrDefault (m => m.JniName == jniName); + Assert.NotNull (method); + Assert.Equal (expectedSig, method.JniSignature); + } + + [Fact] + public void Scan_ExportMethod_EnumParametersFlagTypeRefAsEnum () + { + var method = FindFixtureByJavaName ("my/app/ExportEnumShapes") + .MarshalMethods.First (m => m.JniName == "echoEnum"); + Assert.True (method.ManagedParameterTypes [0].IsEnum, "enum parameter should be tagged IsEnum=true"); + Assert.True (method.ManagedReturnType.IsEnum, "enum return type should be tagged IsEnum=true"); + } + + [Theory] + [InlineData ("echoCharSequence", "(Ljava/lang/CharSequence;)Ljava/lang/CharSequence;")] + public void Scan_ExportMethod_CharSequenceMapsToCanonicalJavaType (string jniName, string expectedSig) + { + var method = FindFixtureByJavaName ("my/app/ExportCharSequenceShapes") + .MarshalMethods.FirstOrDefault (m => m.JniName == jniName); + Assert.NotNull (method); + Assert.Equal (expectedSig, method.JniSignature); + } + + [Theory] + [InlineData ("echoList", "(Ljava/util/List;)Ljava/util/List;")] + [InlineData ("echoMap", "(Ljava/util/Map;)Ljava/util/Map;")] + [InlineData ("echoCollection", "(Ljava/util/Collection;)Ljava/util/Collection;")] + public void Scan_ExportMethod_NonGenericCollectionsMapToCanonicalJavaTypes (string jniName, string expectedSig) + { + var method = FindFixtureByJavaName ("my/app/ExportCollectionShapes") + .MarshalMethods.FirstOrDefault (m => m.JniName == jniName); + Assert.NotNull (method); + Assert.Equal (expectedSig, method.JniSignature); + } + + [Fact] + public void Scan_ExportMethod_CapturesPreciseManagedTypeMetadata () + { + var arrayMethod = FindFixtureByJavaName ("my/app/ExportMarshallingShapes") + .MarshalMethods.First (m => m.JniName == "roundTripNames"); + Assert.Equal ("System.String[]", arrayMethod.ManagedParameterTypes [0].ManagedTypeName); + Assert.Equal ("System.Runtime", arrayMethod.ManagedParameterTypes [0].AssemblyName); + + var xmlMethod = FindFixtureByJavaName ("my/app/ExportMarshallingShapes") + .MarshalMethods.First (m => m.JniName == "readXml"); + Assert.Equal (ExportParameterKindInfo.XmlPullParser, xmlMethod.ManagedParameterExportKinds [0]); + Assert.Equal (ExportParameterKindInfo.XmlPullParser, xmlMethod.ManagedReturnExportKind); + Assert.Equal ("System.Xml.ReaderWriter", xmlMethod.ManagedReturnType.AssemblyName); + + var resourceXmlMethod = FindFixtureByJavaName ("my/app/ExportMarshallingShapes") + .MarshalMethods.First (m => m.JniName == "readResourceXml"); + Assert.Equal (ExportParameterKindInfo.XmlResourceParser, resourceXmlMethod.ManagedParameterExportKinds [0]); + Assert.Equal (ExportParameterKindInfo.XmlResourceParser, resourceXmlMethod.ManagedReturnExportKind); + } + [Theory] [InlineData ("android/app/Activity", "Android.App.Activity")] [InlineData ("my/app/SimpleActivity", "Android.App.Activity")] @@ -115,7 +206,7 @@ public void Scan_CompatJniName (string javaName, string expectedCompat) public void Scan_CompatJniName_UnregisteredType_UsesRawNamespace () { var unregistered = FindFixtureByManagedName ("MyApp.UnregisteredHelper"); - Assert.StartsWith ("crc64", unregistered.JavaName); + Assert.StartsWith ("scrc64", unregistered.JavaName); Assert.Equal ("myapp/UnregisteredHelper", unregistered.CompatJniName); } diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.EdgeCases.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.EdgeCases.cs index 52786546f22..c581f13894a 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.EdgeCases.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.EdgeCases.cs @@ -22,7 +22,7 @@ public void Scan_ComponentOnlyBase_BothBaseAndDerivedDiscovered () Assert.Equal ("android/app/Activity", baseType.BaseJavaName); var derived = FindFixtureByManagedName ("MyApp.DerivedFromComponentBase"); - Assert.StartsWith ("crc64", derived.JavaName); + Assert.StartsWith ("scrc64", derived.JavaName); } [Theory] @@ -64,7 +64,7 @@ public void Scan_EmptyNamespace_Handled () [InlineData ("MyApp.UnregisteredExporter")] public void Scan_UnregisteredType_DiscoveredWithCrc64Name (string managedName) { - Assert.StartsWith ("crc64", FindFixtureByManagedName (managedName).JavaName); + Assert.StartsWith ("scrc64", FindFixtureByManagedName (managedName).JavaName); } [Fact] diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs index 4864ff291cd..6d32900bdb7 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; using Xunit; namespace Microsoft.Android.Sdk.TrimmableTypeMap.Tests; @@ -42,6 +45,57 @@ public void Scan_IsUnconditional (string javaName, bool expected) Assert.Equal (expected, FindFixtureByJavaName (javaName).IsUnconditional); } + [Fact] + public void Scan_MarksFrameworkAssemblyPeers () + { + using var peReader = new PEReader (File.OpenRead (TestFixtureAssemblyPath)); + var reader = peReader.GetMetadataReader (); + var assemblyName = reader.GetString (reader.GetAssemblyDefinition ().Name); + using var scanner = new JavaPeerScanner (frameworkAssemblyNames: new HashSet (StringComparer.OrdinalIgnoreCase) { assemblyName }); + + var peers = scanner.Scan (new List<(string, PEReader)> { (assemblyName, peReader) }); + + Assert.NotEmpty (peers); + Assert.All (peers, p => Assert.True (p.IsFrameworkAssembly, $"{p.ManagedTypeName} should be marked as a framework peer.")); + Assert.All (peers, p => Assert.False (p.GenerateArrayEntries, $"{p.ManagedTypeName} should not emit array entries unless referenced from a non-framework assembly.")); + } + + [Fact] + public void Scan_JniAddNativeMethodRegistrationAttribute_LogsError () + { + // The trimmable typemap refuses to support [JniAddNativeMethodRegistrationAttribute] + // by design (XA4251). The scanner reports each offending type via the logger. + var errors = new List (); + var logger = new RecordingLogger (errors); + + using var scanner = new JavaPeerScanner (logger: logger); + using var peReader = new PEReader (File.OpenRead (TestFixtureAssemblyPath)); + var reader = peReader.GetMetadataReader (); + var assemblyName = reader.GetString (reader.GetAssemblyDefinition ().Name); + _ = scanner.Scan (new List<(string, PEReader)> { (assemblyName, peReader) }); + + Assert.Contains (errors, e => e.Contains ("HandWrittenNativeRegistrationPeer")); + Assert.Contains (errors, e => e.Contains ("NonPeerNativeRegistration")); + Assert.DoesNotContain (errors, e => e.Contains ("OtherNamespaceNativeRegistration")); + Assert.DoesNotContain (errors, e => e.Contains ("MyHelper")); + } + + sealed class RecordingLogger (List errors) : ITrimmableTypeMapLogger + { + public void LogNoJavaPeerTypesFound () { } + public void LogJavaPeerScanInfo (int assemblyCount, int peerCount) { } + public void LogGeneratingJcwFilesInfo (int jcwPeerCount, int totalPeerCount) { } + public void LogDeferredRegistrationTypesInfo (int typeCount) { } + public void LogGeneratedTypeMapAssemblyInfo (string assemblyName, int typeCount) { } + public void LogGeneratedRootTypeMapInfo (int assemblyReferenceCount) { } + public void LogGeneratedTypeMapAssembliesInfo (int assemblyCount) { } + public void LogGeneratedJcwFilesInfo (int sourceCount) { } + public void LogRootingManifestReferencedTypeInfo (string javaTypeName, string managedTypeName) { } + public void LogManifestReferencedTypeNotFoundWarning (string javaTypeName) { } + public void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeName) => + errors.Add ($"XA4251: {managedTypeName}"); + } + [Fact] public void Scan_TypeMetadata_IsCorrect () { @@ -99,12 +153,12 @@ public void Scan_RegisterAttribute_DotFormat_NormalizedToSlashes () } [Theory] - [InlineData ("MyApp.PlainActivitySubclass", "crc64eb3df85c64aa1af6/PlainActivitySubclass")] - [InlineData ("MyApp.UnregisteredClickListener", "crc64eb3df85c64aa1af6/UnregisteredClickListener")] - [InlineData ("MyApp.UnregisteredExporter", "crc64eb3df85c64aa1af6/UnregisteredExporter")] - public void Scan_UnregisteredType_UsesCrc64PackageName (string managedName, string expectedJavaName) + [InlineData ("MyApp.PlainActivitySubclass", "scrc64f93df85c64aa1af6/PlainActivitySubclass")] + [InlineData ("MyApp.UnregisteredClickListener", "scrc64f93df85c64aa1af6/UnregisteredClickListener")] + [InlineData ("MyApp.UnregisteredExporter", "scrc64f93df85c64aa1af6/UnregisteredExporter")] + public void Scan_UnregisteredType_UsesHashedPackageName (string managedName, string expectedHashedJavaName) { - Assert.Equal (expectedJavaName, FindFixtureByManagedName (managedName).JavaName); + Assert.Equal (expectedHashedJavaName, FindFixtureByManagedName (managedName).JavaName); } [Fact] @@ -156,4 +210,40 @@ public void Scan_JniTypeSignature_ArrayRank_IsExcluded () // Non-keyword array (e.g., JavaObjectArray with "java/lang/Object", ArrayRank=1) Assert.DoesNotContain (peers, p => p.ManagedTypeName == "Java.Interop.TestTypes.NonKeywordArrayType"); } + + [Fact] + public void Scan_UnregisteredType_Crc64Default_DiffersFromLegacyLowercaseCrc64Policy () + { + const string managedName = "MyApp.PlainActivitySubclass"; + var withCrc64 = FindFixtureByManagedName (managedName).JavaName; + var withLowercaseCrc64 = FindFixtureByManagedName (managedName, "LowercaseCrc64").JavaName; + + Assert.Equal ("scrc64f93df85c64aa1af6/PlainActivitySubclass", withCrc64); + Assert.Equal ("crc64ec59e927bc71f4d8/PlainActivitySubclass", withLowercaseCrc64); + Assert.NotEqual (withCrc64, withLowercaseCrc64); + } + + [Theory] + [InlineData ("Lowercase")] + [InlineData ("crc46")] + [InlineData ("XxHash64")] + [InlineData ("not-a-policy")] + public void Constructor_UnsupportedPackageNamingPolicy_Throws (string policy) + { + var ex = Assert.Throws (() => new JavaPeerScanner (policy)); + Assert.Contains (policy, ex.Message); + } + + [Theory] + [InlineData (null)] + [InlineData ("")] + [InlineData ("Crc64")] + [InlineData ("crc64")] + [InlineData ("LowercaseCrc64")] + [InlineData ("lowercasecrc64")] + public void Constructor_SupportedPackageNamingPolicy_DoesNotThrow (string? policy) + { + using var scanner = new JavaPeerScanner (policy); + } + } diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/ScannerHashingHelperTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/ScannerHashingHelperTests.cs new file mode 100644 index 00000000000..c3031aac384 --- /dev/null +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/ScannerHashingHelperTests.cs @@ -0,0 +1,32 @@ +using Xunit; + +namespace Microsoft.Android.Sdk.TrimmableTypeMap.Tests; + +public class ScannerHashingHelperTests +{ + [Theory] + [InlineData ("MyApp", "TestFixtures", "ec59e927bc71f4d8")] + [InlineData ("System.Collections.Generic", "My.Assembly", "9ff866e93b19f500")] + [InlineData ("Hello", "World", "f6bdbfa73a558c54")] + public void ToLegacyCrc64_KnownInputs_HaveStableOutput (string ns, string assemblyName, string expected) + { + Assert.Equal (expected, ScannerHashingHelper.ToLegacyCrc64 (ns, assemblyName)); + } + + [Theory] + [InlineData ("MyApp", "TestFixtures", "f93df85c64aa1af6")] + [InlineData ("System.Collections.Generic", "My.Assembly", "663b37c9b3a5014d")] + [InlineData ("Hello", "World", "442a517f331e7a2c")] + public void ToCrc64_KnownInputs_HaveStableOutput (string ns, string assemblyName, string expected) + { + Assert.Equal (expected, ScannerHashingHelper.ToCrc64 (ns, assemblyName)); + } + + [Fact] + public void ToCrc64_DifferentLengthsOfZeroBytes_HaveDifferentOutput () + { + Assert.NotEqual ( + ScannerHashingHelper.ToCrc64 ("\0", ""), + ScannerHashingHelper.ToCrc64 ("\0\0\0\0", "")); + } +} diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/StubAttributes.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/StubAttributes.cs index ba579e4e9d1..050741c3e20 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/StubAttributes.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/StubAttributes.cs @@ -188,6 +188,23 @@ public ExportAttribute () { } public ExportAttribute (string name) => Name = name; } + public enum ExportParameterKind + { + Unspecified = 0, + InputStream = 1, + OutputStream = 2, + XmlPullParser = 3, + XmlResourceParser = 4, + } + + [AttributeUsage (AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false)] + public sealed class ExportParameterAttribute : Attribute + { + public ExportParameterKind Kind { get; } + + public ExportParameterAttribute (ExportParameterKind kind) => Kind = kind; + } + [AttributeUsage (AttributeTargets.Method, AllowMultiple = false)] public sealed class ExportFieldAttribute : Attribute { @@ -219,8 +236,21 @@ public sealed class JniTypeSignatureAttribute : Attribute } } +namespace Java.Interop +{ + [AttributeUsage (AttributeTargets.Method, AllowMultiple = false)] + public sealed class JniAddNativeMethodRegistrationAttribute : Attribute + { + } +} + namespace MyApp { + [AttributeUsage (AttributeTargets.Method, AllowMultiple = false)] + public sealed class JniAddNativeMethodRegistrationAttribute : Attribute + { + } + [AttributeUsage (AttributeTargets.Class)] public sealed class CustomJniNameAttribute : Attribute, Java.Interop.IJniNameProviderAttribute { diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs index 7e8111cfd24..ca0b8f4adf8 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs @@ -1,4 +1,6 @@ using System; +using System.IO; +using System.Xml; using Android.App; using Android.Content; using Android.Runtime; @@ -26,6 +28,12 @@ public class Exception : Throwable { protected Exception (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } } + + // Mirrors Mono.Android's Java.Lang.ICharSequence: an interface without a + // [Register] attribute. The trimmable typemap scanner / emitter must + // special-case it to map onto java/lang/CharSequence and dispatch via + // Android.Runtime.CharSequence.ToLocalJniHandle. + public interface ICharSequence { } } namespace Android.App @@ -175,6 +183,55 @@ protected TextView (IntPtr handle, JniHandleOwnership transfer) : base (handle, } } +namespace Javax.Net.Ssl +{ + [Register ("javax/net/ssl/HostnameVerifier", DoNotGenerateAcw = true)] + public interface IHostnameVerifier + { + } + + [Register ("javax/net/ssl/TrustManager", DoNotGenerateAcw = true)] + public interface ITrustManager + { + } + + [Register ("javax/net/ssl/X509TrustManager", DoNotGenerateAcw = true)] + public interface IX509TrustManager : ITrustManager + { + } + + [Register ("javax/net/ssl/SSLSession", DoNotGenerateAcw = true)] + public interface ISSLSession + { + } +} + +namespace Xamarin.Android.Net +{ + internal sealed class ServerCertificateCustomValidator + { + private sealed class TrustManager : Java.Lang.Object, Javax.Net.Ssl.IX509TrustManager + { + public TrustManager () { } + + private sealed class FakeSSLSession : Java.Lang.Object, Javax.Net.Ssl.ISSLSession + { + public FakeSSLSession () { } + } + } + + private sealed class AlwaysAcceptingHostnameVerifier : Java.Lang.Object, Javax.Net.Ssl.IHostnameVerifier + { + public AlwaysAcceptingHostnameVerifier () { } + } + + private sealed class NonRequiredFrameworkAcw : Java.Lang.Object + { + public NonRequiredFrameworkAcw () { } + } + } +} + namespace MyApp { [Activity (MainLauncher = true, Label = "My App", Name = "my.app.MainActivity")] @@ -193,6 +250,36 @@ public class MyHelper : Java.Lang.Object public virtual void DoSomething () { } } + // Fixture for the trimmable typemap's [JniAddNativeMethodRegistrationAttribute] detection. + // The trimmable typemap deliberately does not support this attribute (XA4251). + [Register ("my/app/HandWrittenNativeRegistrationPeer", DoNotGenerateAcw = true)] + public class HandWrittenNativeRegistrationPeer : Java.Lang.Object + { + [Java.Interop.JniAddNativeMethodRegistration] + static void RegisterNativeMembers () + { + } + } + + // Non-peer type carrying the attribute (no [Register], no Java peer base). + // The scanner must still emit XA4251 even though this type wouldn't otherwise + // have been added to the typemap. + public class NonPeerNativeRegistration + { + [Java.Interop.JniAddNativeMethodRegistration] + static void RegisterNativeMembers () + { + } + } + + public class OtherNamespaceNativeRegistration + { + [MyApp.JniAddNativeMethodRegistration] + static void RegisterNativeMembers () + { + } + } + [Service (Name = "my.app.MyService")] public class MyService : Android.App.Service { @@ -312,6 +399,13 @@ public class ExportExample : Java.Lang.Object public void MyExportedMethod () { } } + [Register ("my/app/StaticExportExample")] + public class StaticExportExample : Java.Lang.Object + { + [Java.Interop.Export ("computeLabel")] + public static string ComputeLabel (int value) => value.ToString (); + } + /// /// Has [Export] methods with non-primitive Java-bound parameter types. /// The JCW should resolve parameter types via [Register] instead of falling back to Object. @@ -329,6 +423,87 @@ public void ProcessView (Android.Views.View view) { } public string GetViewName (Android.Views.View view) { return ""; } } + [Register ("my/app/ExportMarshallingShapes")] + public class ExportMarshallingShapes : Java.Lang.Object + { + [Java.Interop.Export ("roundTripNames")] + public string[]? RoundTripNames (string[]? names) => names; + + [Java.Interop.Export ("openStream")] + public int OpenStream ([Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.InputStream)] Stream? stream) + => stream is null ? 0 : 1; + + [return: Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.OutputStream)] + [Java.Interop.Export ("wrapStream")] + public Stream? WrapStream ([Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.OutputStream)] Stream? stream) + => stream; + + [return: Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.XmlPullParser)] + [Java.Interop.Export ("readXml")] + public XmlReader? ReadXml ([Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.XmlPullParser)] XmlReader? reader) + => reader; + + [return: Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.XmlResourceParser)] + [Java.Interop.Export ("readResourceXml")] + public XmlReader? ReadResourceXml ([Java.Interop.ExportParameter (Java.Interop.ExportParameterKind.XmlResourceParser)] XmlReader? reader) + => reader; + } + + public enum SampleEnum { A, B, C } + + public enum SampleByteEnum : byte { Red, Green, Blue } + + public enum SampleLongEnum : long { Zero = 0L, Big = long.MaxValue } + + /// + /// Has [Export] methods that take and return enum-typed values. Enums must + /// marshal via their underlying primitive JNI ABI (matching legacy + /// Mono.Android.Export behaviour) — not as object peers. + /// + [Register ("my/app/ExportEnumShapes")] + public class ExportEnumShapes : Java.Lang.Object + { + [Java.Interop.Export ("echoEnum")] + public SampleEnum EchoEnum (SampleEnum value) => value; + + [Java.Interop.Export ("echoByteEnum")] + public SampleByteEnum EchoByteEnum (SampleByteEnum value) => value; + + [Java.Interop.Export ("echoLongEnum")] + public SampleLongEnum EchoLongEnum (SampleLongEnum value) => value; + } + + /// + /// Has [Export] methods that take and return ICharSequence values. Must + /// dispatch through Android.Runtime.CharSequence.ToLocalJniHandle (mirrors + /// legacy Mono.Android.Export behaviour) — not the generic IJavaObject + /// path used for other peers. + /// + [Register ("my/app/ExportCharSequenceShapes")] + public class ExportCharSequenceShapes : Java.Lang.Object + { + [Java.Interop.Export ("echoCharSequence")] + public Java.Lang.ICharSequence? EchoCharSequence (Java.Lang.ICharSequence? value) => value; + } + + /// + /// Has [Export] methods that take and return non-generic collection types + /// (IList, IDictionary, ICollection). Each must dispatch through the + /// matching JavaList/JavaDictionary/JavaCollection.ToLocalJniHandle helper. + /// + [Register ("my/app/ExportCollectionShapes")] + public class ExportCollectionShapes : Java.Lang.Object + { + [Java.Interop.Export ("echoList")] + public System.Collections.IList? EchoList (System.Collections.IList? value) => value; + + [Java.Interop.Export ("echoMap")] + public System.Collections.IDictionary? EchoMap (System.Collections.IDictionary? value) => value; + + [Java.Interop.Export ("echoCollection")] + public System.Collections.ICollection? EchoCollection (System.Collections.ICollection? value) => value; + } + /// /// Has [Export] methods with different access modifiers. /// The JCW should respect the C# visibility for [Export] methods. @@ -546,6 +721,12 @@ protected UserActivity (IntPtr handle, JniHandleOwnership transfer) : base (hand protected override void OnCreate (object? savedInstanceState) => base.OnCreate (savedInstanceState); } + [Register ("my/app/ProtectedDefaultCtorActivity")] + public class ProtectedDefaultCtorActivity : Android.App.Activity + { + protected ProtectedDefaultCtorActivity () { } + } + /// /// Overrides multiple registered base methods without [Register]. /// diff --git a/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj b/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj index 11426b511b1..5696ee844b8 100644 --- a/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj +++ b/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj @@ -28,6 +28,19 @@ + + + + + + + diff --git a/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.targets b/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.targets index b1fa7783883..838470a434b 100644 --- a/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.targets +++ b/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.targets @@ -1,12 +1,16 @@  + + <_JavaInteropTestsJarFile>Jars\Mono.Android-Test-classes.jar + <_JavaInteropTestsJarFile Condition=" '$(_AndroidTypeMapImplementation)' == 'trimmable' ">Jars\Mono.Android-Test-classes-trimmable.jar + - + - Jars\Mono.Android-Test-classes.jar + $(_JavaInteropTestsJarFile) @@ -34,8 +38,8 @@ --> - \ No newline at end of file + diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/AndroidEnvironmentTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/AndroidEnvironmentTest.cs deleted file mode 100644 index 76ff2e50b69..00000000000 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/AndroidEnvironmentTest.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Reflection; -using Android.Runtime; -using NUnit.Framework; - -namespace Android.RuntimeTests { - [TestFixture] - public class AndroidEnvironmentTest { - const string EnvironmentVariable = "XA_HTTP_CLIENT_HANDLER_TYPE"; - - static object envLock = new object (); - - private string? _originalValue = null; - - [TestFixtureSetUp] - public void SetUp () - { - _originalValue = Environment.GetEnvironmentVariable (EnvironmentVariable); - } - - [TestFixtureTearDown] - public void TearDown () - { - Environment.SetEnvironmentVariable (EnvironmentVariable, _originalValue); - ClearHttpMessageHandlerTypeCache (); - } - - [Test] - [TestCase (null)] - [TestCase ("Xamarin.Android.Net.AndroidHttpResponseMessage")] // does not extend HttpMessageHandler - // instantiating AndroidClientHandler or HttpClientHandler (or any other type extending HttpClientHandler) - // would cause infinite recursion in the .NET build and so it is replaced with AndroidMessageHandler - [TestCase ("System.Net.Http.HttpClientHandler, System.Net.Http")] - [TestCase ("Xamarin.Android.Net.AndroidClientHandler")] - public void GetHttpMessageHandler_FallbackToAndroidMessageHandler (string? typeName) - { - var handler = GetHttpMessageHandler (typeName); - - Assert.IsNotNull (handler, "GetHttpMessageHandler returned null"); - Assert.IsNotNull ("Xamarin.Android.Net.AndroidMessageHandler", handler.GetType ().FullName); - } - - [Test] - [TestCase ("System.Net.Http.HttpClientHandler")] // the type name doesn't contain the name of the assembly so the type won't be found - [TestCase ("Some.Nonexistent.Type")] - public void GetHttpMessageHandler_FallbackForInaccessibleTypes (string typeName) - { - var handler = GetHttpMessageHandler (typeName); - - Assert.IsNotNull (handler, "GetHttpMessageHandler returned null"); - Assert.IsNotNull ("Xamarin.Android.Net.AndroidMessageHandler", handler.GetType ().FullName); - } - - [Test] - [TestCase ("Xamarin.Android.Net.AndroidMessageHandler")] - [TestCase ("System.Net.Http.SocketsHttpHandler, System.Net.Http")] - public void GetHttpMessageHandler_OverridesDefaultValue (string typeName) - { - var handler = GetHttpMessageHandler (typeName); - - Assert.IsNotNull (handler, "GetHttpMessageHandler returned null"); - - // type's FullName doesn't contain the assembly name - var indexOfComma = typeName.IndexOf(','); - var expectedTypeName = indexOfComma > 0 ? typeName.Substring(0, indexOfComma) : typeName; - Assert.AreEqual (expectedTypeName, handler.GetType ().FullName); - } - - private static object? GetHttpMessageHandler (string? typeName) - { - var method = typeof (AndroidEnvironment).GetMethod ("GetHttpMessageHandler", BindingFlags.Static | BindingFlags.NonPublic); - lock (envLock) { - ClearHttpMessageHandlerTypeCache (); - Environment.SetEnvironmentVariable (EnvironmentVariable, typeName); - return method.Invoke (null, null); - } - } - - private static void ClearHttpMessageHandlerTypeCache () - { - var cacheField = typeof (AndroidEnvironment).GetField ("httpMessageHandlerType", BindingFlags.Static | BindingFlags.NonPublic)!; - cacheField.SetValue (null, null); - } - } -} diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/ConstructorActivationTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/ConstructorActivationTests.cs new file mode 100644 index 00000000000..988af02552a --- /dev/null +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/ConstructorActivationTests.cs @@ -0,0 +1,982 @@ +using System; + +using Android.App; +using Android.Content; +using Android.Runtime; +using Android.Util; +using Android.Views; + +using Java.Interop; + +using NUnit.Framework; + +namespace Java.InteropTests +{ + [TestFixture] + public class ConstructorActivationTests + { + [Test] + public void JavaSideDefaultConstructorRunsOnceAndRegistersPeer () + { + ConstructorActivationDefault.Reset (); + + using (var instance = CreateFromJava ("()V")) { + Assert.IsNotNull (instance); + Assert.AreEqual (1, ConstructorActivationDefault.ConstructorInvocations); + Assert.AreEqual (1, instance.ConstructorOrdinal); + AssertRegisteredSame (instance); + } + } + + [Test] + public void ManagedConstructionDoesNotReenterThroughJavaConstructorActivation () + { + ConstructorActivationDefault.Reset (); + + using (var instance = new ConstructorActivationDefault ()) { + Assert.IsNotNull (instance); + Assert.AreEqual (1, ConstructorActivationDefault.ConstructorInvocations); + Assert.AreEqual (1, instance.ConstructorOrdinal); + AssertRegisteredSame (instance); + } + } + + [Test] + public void CreateInstanceDoesNotActivateManagedConstructorInsideNewObjectScope () + { + ConstructorActivationDefault.Reset (); + + IntPtr handle = JNIEnv.CreateInstance (typeof (ConstructorActivationDefault), "()V"); + try { + var peer = JniEnvironment.Runtime.ValueManager.PeekPeer (new JniObjectReference (handle)); + + Assert.AreEqual (0, ConstructorActivationDefault.ConstructorInvocations); + Assert.IsNull (peer); + } finally { + JNIEnv.DeleteLocalRef (handle); + } + } + + [Test] + [Category ("InheritedActivationCreateInstance")] + public void CreateInstanceWithInheritedActivationCtorDoesNotRegisterPeerInsideNewObjectScope () + { + ConstructorActivationMarshalObject.Reset (); + + IntPtr handle = JNIEnv.CreateInstance (typeof (ConstructorActivationMarshalObject), "(Z)V", new JValue (true)); + try { + var peer = JniEnvironment.Runtime.ValueManager.PeekPeer (new JniObjectReference (handle)); + + Assert.AreEqual (0, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.IsNull (peer); + } finally { + JNIEnv.DeleteLocalRef (handle); + } + } + + [Test] + public void JavaSideConstructorReinvokesExistingActivatablePeer () + { + ConstructorActivationDefault.Reset (); + + using (var instance = new ConstructorActivationDefault ()) { + var peer = (IJavaPeerable) instance; + peer.SetJniManagedPeerState (instance.JniManagedPeerState | JniManagedPeerStates.Activatable | JniManagedPeerStates.Replaceable); + + JNIEnv.FinishCreateInstance (instance.Handle, "()V"); + + Assert.AreEqual (2, ConstructorActivationDefault.ConstructorInvocations); + Assert.AreEqual (2, instance.ConstructorOrdinal); + AssertRegisteredSame (instance); + } + } + + [Test] + public void JavaSideThrowingConstructorPropagatesException () + { + ConstructorActivationThrowing.Reset (); + + var exception = Assert.Catch (() => CreateFromJavaExpectingConstructorException ("()V")); + Assert.IsNotNull (exception); + Assert.AreEqual (1, ConstructorActivationThrowing.ConstructorInvocations); + Assert.IsTrue (exception.ToString ().Contains (ConstructorActivationThrowing.ExceptionMessage)); + } + + [Test] + public void JavaSideContextConstructorForwardsArgument () + { + ConstructorActivationContextView.Reset (); + + using (var instance = CreateFromJava ( + "(Landroid/content/Context;)V", + new JValue (Application.Context))) { + Assert.AreEqual (1, ConstructorActivationContextView.ConstructorInvocations); + Assert.IsNotNull (instance.ContextValue); + AssertSameJavaObject (Application.Context, instance.ContextValue); + } + } + + [Test] + public void JavaSideContextAttributeSetConstructorForwardsArguments () + { + ConstructorActivationAttributeSetView.Reset (); + + using (var instance = CreateFromJava ( + "(Landroid/content/Context;Landroid/util/AttributeSet;)V", + new JValue (Application.Context), + JValue.Zero)) { + Assert.AreEqual (1, ConstructorActivationAttributeSetView.ConstructorInvocations); + Assert.IsNotNull (instance.ContextValue); + AssertSameJavaObject (Application.Context, instance.ContextValue); + Assert.IsNull (instance.AttributeSetValue); + } + } + + [Test] + public void JavaSideContextAttributeSetStyleConstructorForwardsArguments () + { + ConstructorActivationStyledView.Reset (); + + using (var instance = CreateFromJava ( + "(Landroid/content/Context;Landroid/util/AttributeSet;I)V", + new JValue (Application.Context), + JValue.Zero, + new JValue (42))) { + Assert.AreEqual (1, ConstructorActivationStyledView.ConstructorInvocations); + Assert.IsNotNull (instance.ContextValue); + AssertSameJavaObject (Application.Context, instance.ContextValue); + Assert.IsNull (instance.AttributeSetValue); + Assert.AreEqual (42, instance.DefStyleAttrValue); + } + } + + [Test] + public void JavaSideBooleanConstructorForwardsTrue () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ("(Z)V", new JValue (true))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (true, instance.BooleanValue); + } + } + + [Test] + public void JavaSideBooleanConstructorForwardsFalse () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ("(Z)V", new JValue (false))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (false, instance.BooleanValue); + } + } + + [Test] + public void JavaSideByteConstructorForwardsSignedValue () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ("(B)V", new JValue ((sbyte) -12))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual ((sbyte) -12, instance.ByteValue); + } + } + + [Test] + public void JavaSideCharConstructorForwardsValue () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ("(C)V", new JValue ('Q'))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual ('Q', instance.CharValue); + } + } + + [Test] + public void JavaSideShortConstructorForwardsValue () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJava ("(S)V", new JValue ((short) -1234))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual ((short) -1234, instance.ShortValue); + } + } + + [Test] + public void JavaSideIntConstructorForwardsValue () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ("(I)V", new JValue (0x1234567))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (0x1234567, instance.IntValue); + } + } + + [Test] + public void JavaSideLongConstructorForwardsValue () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ("(J)V", new JValue (0x123456789L))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (0x123456789L, instance.LongValue); + } + } + + [Test] + public void JavaSideFloatConstructorForwardsValue () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ("(F)V", new JValue (12.5f))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (12.5f, instance.FloatValue); + } + } + + [Test] + public void JavaSideDoubleConstructorForwardsValue () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ("(D)V", new JValue (-42.25))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (-42.25, instance.DoubleValue); + } + } + + [Test] + public void JavaSidePrimitiveMixedConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ( + "(IZJ)V", + new JValue (12345), + new JValue (true), + new JValue (9876543210L))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (12345, instance.MultiIntValue); + Assert.AreEqual (true, instance.MultiBooleanValue); + Assert.AreEqual (9876543210L, instance.MultiLongValue); + } + } + + [Test] + public void JavaSideStringConstructorForwardsValue () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var value = new Java.Lang.String ("hello constructor")) + using (var instance = CreateFromJava ("(Ljava/lang/String;)V", new JValue (value))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual ("hello constructor", instance.StringValue); + } + } + + [Test] + public void JavaSideStringConstructorForwardsNull () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJava ("(Ljava/lang/String;)V", JValue.Zero)) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.IsNull (instance.StringValue); + } + } + + [Test] + public void JavaSideTwoStringConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var first = new Java.Lang.String ("first")) + using (var second = new Java.Lang.String ("second")) + using (var instance = CreateFromJava ( + "(Ljava/lang/String;Ljava/lang/String;)V", + new JValue (first), + new JValue (second))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual ("first", instance.FirstStringValue); + Assert.AreEqual ("second", instance.SecondStringValue); + } + } + + [Test] + public void JavaSideTwoStringConstructorForwardsNullSecondValue () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var first = new Java.Lang.String ("first")) + using (var instance = CreateFromJava ( + "(Ljava/lang/String;Ljava/lang/String;)V", + new JValue (first), + JValue.Zero)) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual ("first", instance.FirstStringValue); + Assert.IsNull (instance.SecondStringValue); + } + } + + [Test] + public void JavaSideStringIntConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var text = new Java.Lang.String ("string-int")) + using (var instance = CreateFromJava ( + "(Ljava/lang/String;I)V", + new JValue (text), + new JValue (17))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual ("string-int", instance.StringIntStringValue); + Assert.AreEqual (17, instance.StringIntValue); + } + } + + [Test] + public void JavaSideIntArrayConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJavaWithLocalArray ( + "([I)V", + JNIEnv.NewArray (new [] { 1, 2, 3, 5 }))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (new [] { 1, 2, 3, 5 }, instance.IntArrayValue); + } + } + + [Test] + public void JavaSideIntArrayConstructorForwardsEmptyArray () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJavaWithLocalArray ( + "([I)V", + JNIEnv.NewArray (Array.Empty ()))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.IsNotNull (instance.IntArrayValue); + Assert.AreEqual (0, instance.IntArrayValue.Length); + } + } + + [Test] + public void JavaSideIntArrayConstructorForwardsNull () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJava ("([I)V", JValue.Zero)) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.IsNull (instance.IntArrayValue); + } + } + + [Test] + public void JavaSideStringIntArrayConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var label = new Java.Lang.String ("string-array")) + { + IntPtr array = JNIEnv.NewArray (new [] { 8, 13, 21 }); + try { + using (var instance = CreateFromJava ( + "(Ljava/lang/String;[I)V", + new JValue (label), + new JValue (array))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual ("string-array", instance.StringArrayLabel); + Assert.AreEqual (new [] { 8, 13, 21 }, instance.StringArrayValues); + } + } finally { + JNIEnv.DeleteLocalRef (array); + } + } + } + + [Test] + public void JavaSideBooleanArrayConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJavaWithLocalArray ( + "([Z)V", + JNIEnv.NewArray (new [] { true, false, true }))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (new [] { true, false, true }, instance.BooleanArrayValue); + } + } + + [Test] + public void JavaSideByteArrayConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJavaWithLocalArray ( + "([B)V", + JNIEnv.NewArray (new byte [] { 1, 127, 255 }))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (new byte [] { 1, 127, 255 }, instance.ByteArrayValue); + } + } + + [Test] + public void JavaSideStringArrayConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJavaWithLocalArray ( + "([Ljava/lang/String;)V", + JNIEnv.NewArray (new [] { "red", "green", "blue" }))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (new [] { "red", "green", "blue" }, instance.StringArrayValue); + } + } + + [Test] + public void JavaSideStringArrayConstructorForwardsNullElement () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJavaWithLocalArray ( + "([Ljava/lang/String;)V", + JNIEnv.NewArray (new [] { "red", null, "blue" }))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (new [] { "red", null, "blue" }, instance.StringArrayValue); + } + } + + [Test] + public void JavaSideStringArrayConstructorForwardsNull () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJava ("([Ljava/lang/String;)V", JValue.Zero)) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.IsNull (instance.StringArrayValue); + } + } + + [Test] + public void JavaSideObjectArrayConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var first = new Java.Lang.String ("object-array-value")) + using (var instance = CreateFromJavaWithLocalArray ( + "([Ljava/lang/Object;)V", + JNIEnv.NewArray (new Java.Lang.Object [] { first, Application.Context }))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.IsNotNull (instance.ObjectArrayValue); + Assert.AreEqual (2, instance.ObjectArrayValue.Length); + Assert.AreEqual ("object-array-value", instance.ObjectArrayValue [0].ToString ()); + AssertSameJavaObject (Application.Context, instance.ObjectArrayValue [1]); + } + } + + [Test] + public void JavaSideObjectArrayConstructorForwardsNull () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJava ("([Ljava/lang/Object;)V", JValue.Zero)) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.IsNull (instance.ObjectArrayValue); + } + } + + [Test] + public void JavaSideNestedIntArrayConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJavaWithLocalArray ( + "([[I)V", + JNIEnv.NewArray (new [] { + new [] { 1, 2 }, + new [] { 3, 4, 5 }, + }))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.IsNotNull (instance.NestedIntArrayValue); + Assert.AreEqual (new [] { 1, 2 }, instance.NestedIntArrayValue [0]); + Assert.AreEqual (new [] { 3, 4, 5 }, instance.NestedIntArrayValue [1]); + } + } + + static void AssumeTrimmableConstructorParameterMarshalling () + { + if (!Microsoft.Android.Runtime.RuntimeFeature.TrimmableTypeMap) { + Assert.Ignore ("Legacy TypeManager.n_Activate does not marshal string, short, or array constructor parameters; this case validates trimmable constructor UCO parameter marshalling."); + } + } + + static T CreateFromJava (string constructorSignature, params JValue [] arguments) + where T : Java.Lang.Object + { + var instance = JNIEnv.StartCreateInstance (typeof (T), constructorSignature, arguments); + JNIEnv.FinishCreateInstance (instance, constructorSignature, arguments); + var result = Java.Lang.Object.GetObject (instance, JniHandleOwnership.TransferLocalRef); + Assert.IsNotNull (result); + return result; + } + + static T CreateFromJavaWithLocalArray (string constructorSignature, IntPtr array) + where T : Java.Lang.Object + { + try { + return CreateFromJava (constructorSignature, new JValue (array)); + } finally { + JNIEnv.DeleteLocalRef (array); + } + } + + static void CreateFromJavaExpectingConstructorException (string constructorSignature, params JValue [] arguments) + where T : Java.Lang.Object + { + IntPtr instance = IntPtr.Zero; + try { + instance = JNIEnv.StartCreateInstance (typeof (T), constructorSignature, arguments); + JNIEnv.FinishCreateInstance (instance, constructorSignature, arguments); + } finally { + if (instance != IntPtr.Zero) { + JNIEnv.DeleteLocalRef (instance); + } + } + } + + static void AssertRegisteredSame (T instance) + where T : Java.Lang.Object + { + var registered = Java.Lang.Object.GetObject (instance.Handle, JniHandleOwnership.DoNotTransfer); + try { + Assert.AreSame (instance, registered); + if (Microsoft.Android.Runtime.RuntimeFeature.TrimmableTypeMap) { + Assert.AreEqual (Java.Lang.JavaSystem.IdentityHashCode (instance), instance.JniIdentityHashCode); + } + } finally { + if (registered != null && !object.ReferenceEquals (instance, registered)) + registered.Dispose (); + } + } + + static void AssertSameJavaObject (Java.Lang.Object expected, Java.Lang.Object actual) + { + Assert.IsNotNull (expected); + Assert.IsNotNull (actual); + Assert.IsTrue ( + JniEnvironment.Types.IsSameObject (expected.PeerReference, actual.PeerReference), + $"Expected Java object identity to match. Expected handle: {expected.Handle}, actual handle: {actual.Handle}."); + } + } + + [Register ("net/dot/android/test/ConstructorActivationThrowing")] + public class ConstructorActivationThrowing : Java.Lang.Object + { + public const string ExceptionMessage = "constructor activation throw"; + + public static int ConstructorInvocations; + + public ConstructorActivationThrowing () + { + ConstructorInvocations++; + throw new InvalidOperationException (ExceptionMessage); + } + + public static void Reset () + { + ConstructorInvocations = 0; + } + } + + [Register ("net/dot/android/test/ConstructorActivationBase", DoNotGenerateAcw = true)] + public class ConstructorActivationBase : Java.Lang.Object + { + public ConstructorActivationBase () + { + } + + public ConstructorActivationBase (bool value) + { + } + + public ConstructorActivationBase (sbyte value) + { + } + + public ConstructorActivationBase (char value) + { + } + + public ConstructorActivationBase (short value) + { + } + + public ConstructorActivationBase (int value) + { + } + + public ConstructorActivationBase (long value) + { + } + + public ConstructorActivationBase (float value) + { + } + + public ConstructorActivationBase (double value) + { + } + + public ConstructorActivationBase (int number, bool flag, long longValue) + { + } + + public ConstructorActivationBase (string value) + { + } + + public ConstructorActivationBase (string first, string second) + { + } + + public ConstructorActivationBase (string text, int number) + { + } + + public ConstructorActivationBase (int[] value) + { + } + + public ConstructorActivationBase (string label, int[] value) + { + } + + public ConstructorActivationBase (bool[] value) + { + } + + public ConstructorActivationBase (byte[] value) + { + } + + public ConstructorActivationBase (string[] value) + { + } + + public ConstructorActivationBase (Java.Lang.Object[] value) + { + } + + public ConstructorActivationBase (int[][] value) + { + } + + public ConstructorActivationBase (IntPtr handle, JniHandleOwnership transfer) + : base (handle, transfer) + { + } + } + + [Register ("net/dot/android/test/ConstructorActivationDefault")] + public class ConstructorActivationDefault : Java.Lang.Object + { + public static int ConstructorInvocations; + + public int ConstructorOrdinal; + + public ConstructorActivationDefault () + { + ConstructorOrdinal = ++ConstructorInvocations; + } + + public static void Reset () + { + ConstructorInvocations = 0; + } + } + + [Register ("net/dot/android/test/ConstructorActivationMarshalObject")] + public class ConstructorActivationMarshalObject : ConstructorActivationBase + { + public static int ConstructorInvocations; + + public bool BooleanValue; + public sbyte ByteValue; + public char CharValue; + public short ShortValue; + public int IntValue; + public long LongValue; + public float FloatValue; + public double DoubleValue; + public int MultiIntValue; + public bool MultiBooleanValue; + public long MultiLongValue; + public string StringValue; + public string FirstStringValue; + public string SecondStringValue; + public string StringIntStringValue; + public int StringIntValue; + public int[] IntArrayValue; + public string StringArrayLabel; + public int[] StringArrayValues; + public bool[] BooleanArrayValue; + public byte[] ByteArrayValue; + public string[] StringArrayValue; + public Java.Lang.Object[] ObjectArrayValue; + public int[][] NestedIntArrayValue; + + [Register (".ctor", "(Z)V", "")] + public ConstructorActivationMarshalObject (bool value) + : base (value) + { + ConstructorInvocations++; + BooleanValue = value; + } + + [Register (".ctor", "(B)V", "")] + public ConstructorActivationMarshalObject (sbyte value) + : base (value) + { + ConstructorInvocations++; + ByteValue = value; + } + + [Register (".ctor", "(C)V", "")] + public ConstructorActivationMarshalObject (char value) + : base (value) + { + ConstructorInvocations++; + CharValue = value; + } + + [Register (".ctor", "(S)V", "")] + public ConstructorActivationMarshalObject (short value) + : base (value) + { + ConstructorInvocations++; + ShortValue = value; + } + + [Register (".ctor", "(I)V", "")] + public ConstructorActivationMarshalObject (int value) + : base (value) + { + ConstructorInvocations++; + IntValue = value; + } + + [Register (".ctor", "(J)V", "")] + public ConstructorActivationMarshalObject (long value) + : base (value) + { + ConstructorInvocations++; + LongValue = value; + } + + [Register (".ctor", "(F)V", "")] + public ConstructorActivationMarshalObject (float value) + : base (value) + { + ConstructorInvocations++; + FloatValue = value; + } + + [Register (".ctor", "(D)V", "")] + public ConstructorActivationMarshalObject (double value) + : base (value) + { + ConstructorInvocations++; + DoubleValue = value; + } + + [Register (".ctor", "(IZJ)V", "")] + public ConstructorActivationMarshalObject (int number, bool flag, long longValue) + : base (number, flag, longValue) + { + ConstructorInvocations++; + MultiIntValue = number; + MultiBooleanValue = flag; + MultiLongValue = longValue; + } + + [Register (".ctor", "(Ljava/lang/String;)V", "")] + public ConstructorActivationMarshalObject (string value) + : base (value) + { + ConstructorInvocations++; + StringValue = value; + } + + [Register (".ctor", "(Ljava/lang/String;Ljava/lang/String;)V", "")] + public ConstructorActivationMarshalObject (string first, string second) + : base (first, second) + { + ConstructorInvocations++; + FirstStringValue = first; + SecondStringValue = second; + } + + [Register (".ctor", "(Ljava/lang/String;I)V", "")] + public ConstructorActivationMarshalObject (string text, int number) + : base (text, number) + { + ConstructorInvocations++; + StringIntStringValue = text; + StringIntValue = number; + } + + [Register (".ctor", "([I)V", "")] + public ConstructorActivationMarshalObject (int[] value) + : base (value) + { + ConstructorInvocations++; + IntArrayValue = value; + } + + [Register (".ctor", "(Ljava/lang/String;[I)V", "")] + public ConstructorActivationMarshalObject (string label, int[] value) + : base (label, value) + { + ConstructorInvocations++; + StringArrayLabel = label; + StringArrayValues = value; + } + + [Register (".ctor", "([Z)V", "")] + public ConstructorActivationMarshalObject (bool[] value) + : base (value) + { + ConstructorInvocations++; + BooleanArrayValue = value; + } + + [Register (".ctor", "([B)V", "")] + public ConstructorActivationMarshalObject (byte[] value) + : base (value) + { + ConstructorInvocations++; + ByteArrayValue = value; + } + + [Register (".ctor", "([Ljava/lang/String;)V", "")] + public ConstructorActivationMarshalObject (string[] value) + : base (value) + { + ConstructorInvocations++; + StringArrayValue = value; + } + + [Register (".ctor", "([Ljava/lang/Object;)V", "")] + public ConstructorActivationMarshalObject (Java.Lang.Object[] value) + : base (value) + { + ConstructorInvocations++; + ObjectArrayValue = value; + } + + [Register (".ctor", "([[I)V", "")] + public ConstructorActivationMarshalObject (int[][] value) + : base (value) + { + ConstructorInvocations++; + NestedIntArrayValue = value; + } + + public static void Reset () + { + ConstructorInvocations = 0; + } + } + + [Register ("net/dot/android/test/ConstructorActivationContextView")] + public class ConstructorActivationContextView : View + { + public static int ConstructorInvocations; + + public Context ContextValue; + + [Register (".ctor", "(Landroid/content/Context;)V", "")] + public ConstructorActivationContextView (Context context) + : base (context) + { + ConstructorInvocations++; + ContextValue = context; + } + + public static void Reset () + { + ConstructorInvocations = 0; + } + } + + [Register ("net/dot/android/test/ConstructorActivationAttributeSetView")] + public class ConstructorActivationAttributeSetView : View + { + public static int ConstructorInvocations; + + public Context ContextValue; + public IAttributeSet AttributeSetValue; + + [Register (".ctor", "(Landroid/content/Context;Landroid/util/AttributeSet;)V", "")] + public ConstructorActivationAttributeSetView (Context context, IAttributeSet attrs) + : base (context, attrs) + { + ConstructorInvocations++; + ContextValue = context; + AttributeSetValue = attrs; + } + + public static void Reset () + { + ConstructorInvocations = 0; + } + } + + [Register ("net/dot/android/test/ConstructorActivationStyledView")] + public class ConstructorActivationStyledView : View + { + public static int ConstructorInvocations; + + public Context ContextValue; + public IAttributeSet AttributeSetValue; + public int DefStyleAttrValue; + + [Register (".ctor", "(Landroid/content/Context;Landroid/util/AttributeSet;I)V", "")] + public ConstructorActivationStyledView (Context context, IAttributeSet attrs, int defStyleAttr) + : base (context, attrs, defStyleAttr) + { + ConstructorInvocations++; + ContextValue = context; + AttributeSetValue = attrs; + DefStyleAttrValue = defStyleAttr; + } + + public static void Reset () + { + ConstructorInvocations = 0; + } + } +} diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/ExportTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/ExportTests.cs new file mode 100644 index 00000000000..be1ee4b06f5 --- /dev/null +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/ExportTests.cs @@ -0,0 +1,359 @@ +using System; + +using Android.Runtime; + +using Java.Interop; + +using Microsoft.Android.Runtime; + +using NUnit.Framework; + +namespace Java.InteropTests +{ + // Device-level coverage for [Export] / [ExportField] marshalling. + // + // These tests drive the Java side of an [Export]-bearing peer via JNIEnv, + // then assert what C# observed (and vice versa). They run under both the + // legacy llvm-ir typemap (which is the contract) and the trimmable typemap + // (which must match it). + // + // Naming: each test is named Export___ so the + // runner output is greppable. + [TestFixture] + public class ExportTests + { + // --------------------------------------------------------------- + // Group A — parameter / return marshalling + // --------------------------------------------------------------- + + [Test, Category ("Export")] + public void Export_Method_Primitive_RoundTrip () + { + using var e = new ExportPrimitiveInt (); + var m = JNIEnv.GetMethodID (e.Class.Handle, "EchoInt", "(I)I"); + Assert.AreNotEqual (IntPtr.Zero, m, "JNI method id for EchoInt not found"); + int r = JNIEnv.CallIntMethod (e.Handle, m, new JValue (21)); + Assert.AreEqual (43, r, "EchoInt(21) should be 43 (= 21*2 + 1)"); + } + + [Test, Category ("Export")] + public void Export_Method_Bool_RoundTrip () + { + using var e = new ExportPrimitiveBool (); + var m = JNIEnv.GetMethodID (e.Class.Handle, "EchoBool", "(Z)Z"); + Assert.AreNotEqual (IntPtr.Zero, m, "JNI method id for EchoBool not found"); + Assert.IsFalse (JNIEnv.CallBooleanMethod (e.Handle, m, new JValue (true)), "EchoBool(true) should return false"); + Assert.IsTrue (JNIEnv.CallBooleanMethod (e.Handle, m, new JValue (false)), "EchoBool(false) should return true"); + } + + [Test, Category ("Export")] + public void Export_Method_String_RoundTrip () + { + using var e = new ExportString (); + var m = JNIEnv.GetMethodID (e.Class.Handle, "EchoString", "(Ljava/lang/String;)Ljava/lang/String;"); + Assert.AreNotEqual (IntPtr.Zero, m, "JNI method id for EchoString not found"); + IntPtr argHandle = JNIEnv.NewString ("world"); + try { + IntPtr resultHandle = JNIEnv.CallObjectMethod (e.Handle, m, new JValue (argHandle)); + try { + string result = JNIEnv.GetString (resultHandle, JniHandleOwnership.DoNotTransfer); + Assert.AreEqual ("", result); + } finally { + JNIEnv.DeleteLocalRef (resultHandle); + } + } finally { + JNIEnv.DeleteLocalRef (argHandle); + } + } + + [Test, Category ("Export")] + public void Export_Method_PeerArg_RoundTrip () + { + using var e = new ExportPeerArg (); + using var arg = new Java.Lang.Integer (42); + var m = JNIEnv.GetMethodID (e.Class.Handle, "GetClassName", "(Ljava/lang/Object;)Ljava/lang/String;"); + Assert.AreNotEqual (IntPtr.Zero, m, "JNI method id for GetClassName not found"); + IntPtr resultHandle = JNIEnv.CallObjectMethod (e.Handle, m, new JValue (arg.Handle)); + try { + string result = JNIEnv.GetString (resultHandle, JniHandleOwnership.DoNotTransfer); + Assert.AreEqual ("java.lang.Integer", result); + } finally { + JNIEnv.DeleteLocalRef (resultHandle); + } + } + + [Test, Category ("Export")] + public void Export_Method_PeerArg_NullArg_HandledGracefully () + { + using var e = new ExportPeerArg (); + var m = JNIEnv.GetMethodID (e.Class.Handle, "GetClassName", "(Ljava/lang/Object;)Ljava/lang/String;"); + IntPtr resultHandle = JNIEnv.CallObjectMethod (e.Handle, m, new JValue (IntPtr.Zero)); + try { + string result = JNIEnv.GetString (resultHandle, JniHandleOwnership.DoNotTransfer); + Assert.AreEqual ("", result); + } finally { + JNIEnv.DeleteLocalRef (resultHandle); + } + } + + [Test, Category ("Export")] + public void Export_Method_IntArray_RoundTrip_AndCopyBack () + { + using var e = new ExportIntArray (); + var m = JNIEnv.GetMethodID (e.Class.Handle, "DoubleArray", "([I)[I"); + Assert.AreNotEqual (IntPtr.Zero, m, "JNI method id for DoubleArray not found"); + + var input = new int [] { 1, 2, 3 }; + IntPtr argHandle = JNIEnv.NewArray (input); + try { + IntPtr resultHandle = JNIEnv.CallObjectMethod (e.Handle, m, new JValue (argHandle)); + try { + var output = (int []) JNIEnv.GetArray (resultHandle, JniHandleOwnership.DoNotTransfer, typeof (int)); + Assert.AreEqual (new [] { 2, 4, 6 }, output, "return array should have doubled values"); + + // Copy-back: the input handle should also reflect the doubled values + var roundTrippedInput = (int []) JNIEnv.GetArray (argHandle, JniHandleOwnership.DoNotTransfer, typeof (int)); + Assert.AreEqual (new [] { 2, 4, 6 }, roundTrippedInput, "input array mutations should propagate back to JNI handle"); + } finally { + JNIEnv.DeleteLocalRef (resultHandle); + } + } finally { + JNIEnv.DeleteLocalRef (argHandle); + } + } + + // NOTE: A5/A6/A7 (enum, ICharSequence return, IList return) are + // deferred. The legacy Java callable wrapper emitter + // (CecilImporter.GetJniSignature) returns null for managed enum, + // non-bound IList, and certain ICharSequence shapes — the build + // fails before the runtime path can be exercised. Those tests + // belong with the codegen fix that teaches the JCW emitter to + // widen these types. + + [Test, Category ("Export")] + public void Export_Method_PeerArray_RoundTrip () + { + using var e = new ExportPeerArray (); + using var a = new Java.Lang.Integer (1); + using var b = new Java.Lang.Integer (2); + using var c = new Java.Lang.Integer (3); + + var m = JNIEnv.GetMethodID (e.Class.Handle, "Tail", "([Ljava/lang/Object;)[Ljava/lang/Object;"); + Assert.AreNotEqual (IntPtr.Zero, m, "JNI method id for Tail not found"); + + IntPtr argHandle = JNIEnv.NewObjectArray (a, b, c); + try { + IntPtr resultHandle = JNIEnv.CallObjectMethod (e.Handle, m, new JValue (argHandle)); + try { + var result = (Java.Lang.Object []) JNIEnv.GetArray (resultHandle, JniHandleOwnership.DoNotTransfer, typeof (Java.Lang.Object)); + Assert.AreEqual (2, result.Length); + Assert.AreEqual ("2", result [0].ToString ()); + Assert.AreEqual ("3", result [1].ToString ()); + } finally { + JNIEnv.DeleteLocalRef (resultHandle); + } + } finally { + JNIEnv.DeleteLocalRef (argHandle); + } + } + + // --------------------------------------------------------------- + // Group B — exception routing + // --------------------------------------------------------------- + // The trimmable [Export] UCO wraps the dispatch in BeginMarshalMethod / + // OnUserUnhandledException / EndMarshalMethod so unhandled managed + // exceptions are stored as a pending exception on the JniTransition + // (matching the JavaInterop contract used by UCO ctors) instead of + // aborting the process. When the JNI call returns to managed code on + // the same thread, RaisePendingException re-raises the original + // exception — which can be either the underlying managed exception + // or a Java.Lang.Throwable depending on the runtime path. The + // invariant we assert here is "process did not abort and an exception + // surfaces with a recognizable message". See + // ExportMethodDispatchEmitter.EmitWrappedExportMethodDispatch. + + [Test, Category ("Export")] + public void Export_Method_Throws_PrimitiveReturn_SurfacesAsManagedException () + { + AssumeTrimmableExportExceptionRouting (); + + using var e = new ExportThrowing (); + var m = JNIEnv.GetMethodID (e.Class.Handle, "Throwing", "()I"); + Assert.AreNotEqual (IntPtr.Zero, m, "JNI method id for Throwing not found"); + + // The managed body throws InvalidOperationException("boom"). The wrapper + // must catch it and route it through OnUserUnhandledException so the + // process survives; the exception then re-surfaces on the calling + // thread when the JNI call returns to managed code. + var ex = Assert.Catch (() => JNIEnv.CallIntMethod (e.Handle, m)); + Assert.That (ex, Is.Not.Null, "expected an exception, got null"); + Assert.That (ex.Message, Contains.Substring ("boom"), "exception message should preserve 'boom'"); + } + + [Test, Category ("Export")] + public void Export_Method_Throws_ObjectReturn_SurfacesAsManagedException () + { + AssumeTrimmableExportExceptionRouting (); + + using var e = new ExportThrowing (); + var m = JNIEnv.GetMethodID (e.Class.Handle, "ThrowingString", "()Ljava/lang/String;"); + Assert.AreNotEqual (IntPtr.Zero, m, "JNI method id for ThrowingString not found"); + var ex = Assert.Catch (() => JNIEnv.CallObjectMethod (e.Handle, m)); + Assert.That (ex, Is.Not.Null, "expected an exception, got null"); + } + + // Nested / re-entrant exception routing. The [Export] UCO wrapper sets a + // pending exception on the JniTransition and relies on RaisePendingException + // to re-raise it on the managed caller's return. If the per-thread JniTransition + // state isn't restored cleanly across nested invocations, a *second* exception + // would either swallow the first or surface stale state. + + [Test, Category ("Export")] + public void Export_Method_Throws_FollowedBySecondCall_DoesNotLeakPendingException () + { + AssumeTrimmableExportExceptionRouting (); + + using var e = new ExportThrowing (); + var throwing = JNIEnv.GetMethodID (e.Class.Handle, "Throwing", "()I"); + Assert.AreNotEqual (IntPtr.Zero, throwing, "JNI method id for Throwing not found"); + + // First call: must throw and the exception must surface to managed. + Assert.Catch (() => JNIEnv.CallIntMethod (e.Handle, throwing)); + + // Second call on a *different* peer goes through a separate [Export] + // method that does NOT throw. If the previous pending exception had + // leaked across the JniTransition boundary, this call would either + // throw the stale exception or return a corrupted value. + using var p = new ExportPrimitiveInt (); + var echo = JNIEnv.GetMethodID (p.Class.Handle, "EchoInt", "(I)I"); + Assert.AreNotEqual (IntPtr.Zero, echo, "JNI method id for EchoInt not found"); + int result = JNIEnv.CallIntMethod (p.Handle, echo, new JValue (10)); + Assert.AreEqual (21, result, "After a throwing [Export], a subsequent non-throwing [Export] must return its real value."); + } + + [Test, Category ("Export")] + public void Export_Method_NestedJniCall_PreservesExceptionFromInnerExport () + { + AssumeTrimmableExportExceptionRouting (); + + // Outer [Export] method (ReentrantOuter) invokes Java reflection to call + // an inner [Export] method on the same peer (ReentrantInner) that throws. + // The inner throw is caught by the *inner* wrapper, set as a pending + // exception, then re-raised across the JNI boundary back into the outer + // wrapper's catch — which in turn marks the pending exception on its own + // JniTransition. The managed caller (this test) must observe the original + // "reentrant-boom" message. + using var e = new ExportReentrant (); + var outer = JNIEnv.GetMethodID (e.Class.Handle, "ReentrantOuter", "()I"); + Assert.AreNotEqual (IntPtr.Zero, outer, "JNI method id for ReentrantOuter not found"); + var ex = Assert.Catch (() => JNIEnv.CallIntMethod (e.Handle, outer)); + Assert.That (ex, Is.Not.Null, "expected an exception from the nested call, got null"); + Assert.That (ex.Message, Contains.Substring ("reentrant-boom"), + "the original inner-export exception message must propagate through both [Export] wrappers"); + } + + static void AssumeTrimmableExportExceptionRouting () + { + if (!RuntimeFeature.TrimmableTypeMap) { + Assert.Ignore ("[Export] exception routing coverage is only relevant for the trimmable typemap path."); + } + } + + // --------------------------------------------------------------- + // Group D — [ExportField] runtime visibility from Java + // --------------------------------------------------------------- + // NOTE: device-level [ExportField] tests are deferred. The JCW + // generator (legacy and trimmable) currently emits a static field + // initializer that calls the [ExportField] method as a non-static + // member (`public static int FOO = InitialFoo();`), which fails + // javac when the C# method is `static`, and is unreachable at + // runtime when the C# method is an instance member because there + // is no peer instance during class init. Add runtime [ExportField] + // coverage once the JCW emitter handles both shapes correctly. + } + + // --------------------------------------------------------------- + // Test fixtures (peer types) used by the tests above. + // + // Each fixture is a small Java.Lang.Object subclass with [Export] members + // designed to exercise one corner of the marshalling matrix. + // --------------------------------------------------------------- + + class ExportPrimitiveInt : Java.Lang.Object + { + [Export] + public int EchoInt (int x) => x * 2 + 1; + } + + class ExportPrimitiveBool : Java.Lang.Object + { + [Export] + public bool EchoBool (bool x) => !x; + } + + class ExportString : Java.Lang.Object + { + [Export] + public string EchoString (string x) => "<" + x + ">"; + } + + class ExportPeerArg : Java.Lang.Object + { + [Export] + public string GetClassName (Java.Lang.Object o) => o?.Class?.Name ?? ""; + } + + class ExportIntArray : Java.Lang.Object + { + [Export] + public int [] DoubleArray (int [] xs) + { + for (int i = 0; i < xs.Length; i++) { + xs [i] *= 2; + } + return xs; + } + } + + class ExportPeerArray : Java.Lang.Object + { + [Export] + public Java.Lang.Object [] Tail (Java.Lang.Object [] xs) + { + if (xs.Length <= 1) { + return Array.Empty (); + } + var result = new Java.Lang.Object [xs.Length - 1]; + Array.Copy (xs, 1, result, 0, result.Length); + return result; + } + } + + class ExportThrowing : Java.Lang.Object + { + [Export] + public int Throwing () => throw new InvalidOperationException ("boom"); + + [Export] + public string ThrowingString () => throw new InvalidOperationException ("boom-string"); + } + + // Re-entrancy fixture: the outer [Export] invokes the inner [Export] on `this` + // via JNI reflection. The inner method throws; the test asserts the exception + // crosses both wrapper layers without losing its original message. + class ExportReentrant : Java.Lang.Object + { + [Export] + public int ReentrantInner () => throw new InvalidOperationException ("reentrant-boom"); + + [Export] + public int ReentrantOuter () + { + var m = JNIEnv.GetMethodID (Class.Handle, "ReentrantInner", "()I"); + if (m == IntPtr.Zero) { + throw new InvalidOperationException ("could not resolve ReentrantInner on the peer"); + } + return JNIEnv.CallIntMethod (Handle, m); + } + } +} diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs index 0eacc4eddad..f85703c6a3a 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs @@ -256,7 +256,6 @@ public void SetField_PermitNullValues () } [Test, Category ("Export")] - [Category ("CoreCLRIgnore")] //TODO: https://github.com/dotnet/android/issues/10069 public void CreateTypeWithExportedMethods () { using (var e = new ContainsExportedMethods ()) { @@ -269,7 +268,6 @@ public void CreateTypeWithExportedMethods () } [Test, Category ("Export")] - [Category ("CoreCLRIgnore")] //TODO: https://github.com/dotnet/android/issues/10069 public void ActivatedDirectObjectSubclassesShouldBeRegistered () { if (Build.VERSION.SdkInt <= BuildVersionCodes.GingerbreadMr1) @@ -301,14 +299,11 @@ public void ActivatedDirectObjectSubclassesShouldBeRegistered () } } - // TODO: https://github.com/dotnet/android/issues/11170 — throwable subclass not registered under trimmable typemap [Test] public void ActivatedDirectThrowableSubclassesShouldBeRegistered () { if (Build.VERSION.SdkInt <= BuildVersionCodes.GingerbreadMr1) Assert.Ignore ("Skipping test due to Bug #34141"); - - Console.Error.WriteLine ($"# jonp: BEGIN ActivatedDirectThrowableSubclassesShouldBeRegistered!!!"); using (var ThrowableActivatedFromJava_class = Java.Lang.Class.FromType (typeof (ThrowableActivatedFromJava))) { var ThrowableActivatedFromJava_init = JNIEnv.GetMethodID (ThrowableActivatedFromJava_class.Handle, "", "()V"); @@ -324,7 +319,65 @@ public void ActivatedDirectThrowableSubclassesShouldBeRegistered () Assert.IsTrue (v.Constructed); v.Dispose (); } - Console.Error.WriteLine ($"# jonp: END ActivatedDirectThrowableSubclassesShouldBeRegistered!!!"); + } + + // Locks in the legacy llvm-ir typemap behavior for parameterized ctor activation. + // Java instantiation forwards JNI args to the user-visible managed ctor; trimmable + // typemap codegen must match this contract for non-()V signatures. + // + // NOTE: Legacy mono.android.TypeManager.Activate routes args through + // JNIEnv.GetObjectArray, which only supports IJavaObject-derived element types. + // Tests deliberately use Java.Lang.Throwable args (not System.String) to stay + // inside the supported legacy contract. + [Test] + public void ActivatedDirectThrowableSubclasses_ThrowableCtor_ShouldForwardArgs () + { + using (var klass = Java.Lang.Class.FromType (typeof (ThrowableCauseActivatedFromJava))) + using (var cause = new Java.Lang.Throwable ("a-cause")) { + var ctor = JNIEnv.GetMethodID (klass.Handle, "", "(Ljava/lang/Throwable;)V"); + + var o = JNIEnv.StartCreateInstance (klass.Handle, ctor, new JValue (cause.Handle)); + JNIEnv.FinishCreateInstance (o, klass.Handle, ctor, new JValue (cause.Handle)); + + GC.Collect (); + GC.WaitForPendingFinalizers (); + + var v = Java.Lang.Object.GetObject (o, JniHandleOwnership.TransferLocalRef); + Assert.IsNotNull (v); + Assert.IsTrue (v.Constructed, "user-visible ctor body did not run"); + Assert.IsNotNull (v.ReceivedCause, "throwable arg not forwarded"); + Assert.AreEqual ("a-cause", v.ReceivedCause!.Message); + v.Dispose (); + } + } + + [Test] + public void ActivatedDirectThrowableSubclasses_MultipleCtors_ShouldDispatchToCorrectCtor () + { + using (var klass = Java.Lang.Class.FromType (typeof (MultiCtorActivatedFromJava))) { + // Default ctor + { + var ctor = JNIEnv.GetMethodID (klass.Handle, "", "()V"); + var o = JNIEnv.StartCreateInstance (klass.Handle, ctor); + JNIEnv.FinishCreateInstance (o, klass.Handle, ctor); + var v = Java.Lang.Object.GetObject (o, JniHandleOwnership.TransferLocalRef); + Assert.IsNotNull (v); + Assert.AreEqual (0, v.CtorIndex, "()V dispatched to wrong ctor"); + v.Dispose (); + } + // (Throwable) ctor + using (var cause = new Java.Lang.Throwable ("only-cause")) { + var ctor = JNIEnv.GetMethodID (klass.Handle, "", "(Ljava/lang/Throwable;)V"); + var o = JNIEnv.StartCreateInstance (klass.Handle, ctor, new JValue (cause.Handle)); + JNIEnv.FinishCreateInstance (o, klass.Handle, ctor, new JValue (cause.Handle)); + var v = Java.Lang.Object.GetObject (o, JniHandleOwnership.TransferLocalRef); + Assert.IsNotNull (v); + Assert.AreEqual (1, v.CtorIndex, "(Throwable) dispatched to wrong ctor"); + Assert.IsNotNull (v.ReceivedCause); + Assert.AreEqual ("only-cause", v.ReceivedCause!.Message); + v.Dispose (); + } + } } [Test] @@ -535,6 +588,42 @@ public ThrowableActivatedFromJava () } } + // Throwable subclass with (Throwable) ctor — exercises single IJavaObject-derived + // ref-arg ctor activation. (System.String args are NOT supported by the legacy + // TypeManager.Activate path because JNIEnv.GetObjectArray routes Object[] elements + // through the IJavaObject converter.) + class ThrowableCauseActivatedFromJava : Java.Lang.Throwable { + + public bool Constructed; + public Java.Lang.Throwable? ReceivedCause; + + public ThrowableCauseActivatedFromJava (Java.Lang.Throwable cause) + : base (cause) + { + Constructed = true; + ReceivedCause = cause; + } + } + + // Throwable subclass with multiple registered ctors — exercises ctor dispatch. + class MultiCtorActivatedFromJava : Java.Lang.Throwable { + + public int CtorIndex = -1; + public Java.Lang.Throwable? ReceivedCause; + + public MultiCtorActivatedFromJava () + { + CtorIndex = 0; + } + + public MultiCtorActivatedFromJava (Java.Lang.Throwable cause) + : base (cause) + { + CtorIndex = 1; + ReceivedCause = cause; + } + } + class GenericHolder : Java.Lang.Object { public T Value {get; set;} diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapRuntimeCoverageTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapRuntimeCoverageTests.cs new file mode 100644 index 00000000000..47c6c510876 --- /dev/null +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapRuntimeCoverageTests.cs @@ -0,0 +1,254 @@ +using System; + +using Android.Text; +using Android.Runtime; +using Android.Views; + +using Java.Interop; + +using Microsoft.Android.Runtime; + +using NUnit.Framework; + +namespace Java.InteropTests +{ + [TestFixture] + [Category ("TrimmableTypeMapRuntimeCoverage")] + public class TrimmableTypeMapRuntimeCoverageTests + { + [Test] + public void JavaToManagedTextWatcherCallback_MarshalsStringAndPrimitiveParameters () + { + AssumeTrimmableTypeMapEnabled (); + TrimmableRuntimeTextWatcher.Reset (); + + using var watcher = new TrimmableRuntimeTextWatcher (); + using var text = new Java.Lang.String ("managed"); + + var method = JNIEnv.GetMethodID (watcher.Class.Handle, "onTextChanged", "(Ljava/lang/CharSequence;III)V"); + JNIEnv.CallVoidMethod ( + watcher.Handle, + method, + new JValue (text.Handle), + new JValue (2), + new JValue (3), + new JValue (4)); + + Assert.AreEqual (1, TrimmableRuntimeTextWatcher.OnTextChangedInvocations); + Assert.AreEqual ("managed", watcher.TextValue); + Assert.AreEqual (2, watcher.StartValue); + Assert.AreEqual (3, watcher.BeforeValue); + Assert.AreEqual (4, watcher.CountValue); + } + + [Test] + public void JavaToManagedClickCallback_MarshalsObjectParameter () + { + AssumeTrimmableTypeMapEnabled (); + TrimmableRuntimeClickListener.Reset (); + + using var listener = new TrimmableRuntimeClickListener (); + using var view = new View (Android.App.Application.Context); + + var method = JNIEnv.GetMethodID (listener.Class.Handle, "onClick", "(Landroid/view/View;)V"); + JNIEnv.CallVoidMethod (listener.Handle, method, new JValue (view.Handle)); + + Assert.AreEqual (1, TrimmableRuntimeClickListener.OnClickInvocations); + Assert.AreEqual (view.Handle, listener.ViewHandle); + } + + [Test] + public void JavaToManagedInvocationHandlerCallback_MarshalsObjectArrayParameter () + { + AssumeTrimmableTypeMapEnabled (); + TrimmableRuntimeInvocationHandler.Reset (); + + using var handler = new TrimmableRuntimeInvocationHandler (); + using var first = new Java.Lang.String ("first"); + using var second = new Java.Lang.String ("second"); + var args = JNIEnv.NewArray (new Java.Lang.Object [] { first, second }); + + try { + var method = JNIEnv.GetMethodID (handler.Class.Handle, "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;"); + var result = JNIEnv.CallObjectMethod (handler.Handle, method, JValue.Zero, JValue.Zero, new JValue (args)); + JNIEnv.DeleteLocalRef (result); + + Assert.AreEqual (1, TrimmableRuntimeInvocationHandler.InvokeInvocations); + Assert.AreEqual (2, handler.ArgumentCount); + Assert.AreEqual ("first", handler.FirstArgument); + Assert.AreEqual ("second", handler.SecondArgument); + } finally { + JNIEnv.DeleteLocalRef (args); + } + } + + [Test] + public void JavaActivatedPeer_DisposeCanAccessThisAndInvokeVirtualMember () + { + AssumeTrimmableTypeMapEnabled (); + TrimmableRuntimeDisposePeer.Reset (); + + using (var peer = CreateFromJava ()) { + Assert.AreEqual (1, TrimmableRuntimeDisposePeer.ConstructorInvocations); + peer.Dispose (); + } + + Assert.AreEqual (1, TrimmableRuntimeDisposePeer.DisposeInvocations); + Assert.AreEqual (1, TrimmableRuntimeDisposePeer.VirtualInvocationsDuringDispose); + Assert.AreNotEqual (0, TrimmableRuntimeDisposePeer.DisposeIdentityHashCode); + } + + [Test] + public void ClosedGenericJavaList_CanWrapJavaCreatedArrayListHandle () + { + AssumeTrimmableTypeMapEnabled (); + + var arrayListClass = JniEnvironment.Types.FindClass ("java/util/ArrayList"); + try { + var constructor = JNIEnv.GetMethodID (arrayListClass.Handle, "", "()V"); + var handle = JNIEnv.NewObject (arrayListClass.Handle, constructor); + using (var list = Java.Lang.Object.GetObject> (handle, JniHandleOwnership.TransferLocalRef)) { + Assert.IsNotNull (list); + Assert.AreEqual (typeof (JavaList), list.GetType ()); + + list.Add ("alpha"); + list.Add ("beta"); + + Assert.AreEqual (2, list.Count); + Assert.AreEqual ("alpha", list [0]); + Assert.AreEqual ("beta", list [1]); + } + } finally { + JniObjectReference.Dispose (ref arrayListClass); + } + } + + static T CreateFromJava () + where T : Java.Lang.Object + { + var instance = JNIEnv.StartCreateInstance (typeof (T), "()V"); + JNIEnv.FinishCreateInstance (instance, "()V"); + var result = Java.Lang.Object.GetObject (instance, JniHandleOwnership.TransferLocalRef); + Assert.IsNotNull (result); + return result; + } + + static void AssumeTrimmableTypeMapEnabled () + { + if (!RuntimeFeature.TrimmableTypeMap) { + Assert.Ignore ("TrimmableTypeMap feature switch is off; test only relevant for the trimmable typemap path."); + } + } + } + + class TrimmableRuntimeTextWatcher : Java.Lang.Object, ITextWatcher + { + public static int OnTextChangedInvocations; + + public string TextValue; + public int StartValue; + public int BeforeValue; + public int CountValue; + + public void AfterTextChanged (IEditable s) + { + } + + public void BeforeTextChanged (Java.Lang.ICharSequence s, int start, int count, int after) + { + } + + public void OnTextChanged (Java.Lang.ICharSequence s, int start, int before, int count) + { + OnTextChangedInvocations++; + TextValue = s?.ToString (); + StartValue = start; + BeforeValue = before; + CountValue = count; + } + + public static void Reset () + { + OnTextChangedInvocations = 0; + } + } + + class TrimmableRuntimeClickListener : Java.Lang.Object, View.IOnClickListener + { + public static int OnClickInvocations; + + public IntPtr ViewHandle; + + public void OnClick (View v) + { + OnClickInvocations++; + ViewHandle = v.Handle; + } + + public static void Reset () + { + OnClickInvocations = 0; + } + } + + class TrimmableRuntimeInvocationHandler : Java.Lang.Object, Java.Lang.Reflect.IInvocationHandler + { + public static int InvokeInvocations; + + public int ArgumentCount; + public string FirstArgument; + public string SecondArgument; + + public Java.Lang.Object Invoke (Java.Lang.Object proxy, Java.Lang.Reflect.Method method, Java.Lang.Object [] args) + { + InvokeInvocations++; + ArgumentCount = args.Length; + FirstArgument = args [0].ToString (); + SecondArgument = args [1].ToString (); + return null; + } + + public static void Reset () + { + InvokeInvocations = 0; + } + } + + [Register ("net/dot/android/test/TrimmableRuntimeDisposePeer")] + class TrimmableRuntimeDisposePeer : Java.Lang.Object + { + public static int ConstructorInvocations; + public static int DisposeInvocations; + public static int VirtualInvocationsDuringDispose; + public static int DisposeIdentityHashCode; + + public TrimmableRuntimeDisposePeer () + { + ConstructorInvocations++; + } + + protected virtual int GetDisposeIdentityHashCodeCore () + { + VirtualInvocationsDuringDispose++; + return Java.Lang.JavaSystem.IdentityHashCode (this); + } + + protected override void Dispose (bool disposing) + { + if (disposing && Handle != IntPtr.Zero) { + DisposeInvocations++; + DisposeIdentityHashCode = GetDisposeIdentityHashCodeCore (); + } + + base.Dispose (disposing); + } + + public static void Reset () + { + ConstructorInvocations = 0; + DisposeInvocations = 0; + VirtualInvocationsDuringDispose = 0; + DisposeIdentityHashCode = 0; + } + } +} diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs index b4d921acd17..764fc416379 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs @@ -19,7 +19,6 @@ namespace Java.LangTests [TestFixture] public class ObjectTest { - // TODO: https://github.com/dotnet/android/issues/11170 — trimmable typemap doesn't resolve most-derived managed type [Test] public void GetObject_ReturnsMostDerivedType () { diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj b/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj index f1575d1e45d..c97da9b260c 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj @@ -29,18 +29,18 @@ NetworkInterfaces excluded: https://github.com/dotnet/runtime/issues/75155 --> - $(ExcludeCategories):CoreCLRIgnore:NTLM + $(ExcludeCategories):CoreCLRIgnore:NTLM - $(ExcludeCategories):NativeAOTIgnore:SSL:NTLM:AndroidClientHandler:Export:NativeTypeMap + $(ExcludeCategories):NativeAOTIgnore:SSL:NTLM:Export:NativeTypeMap $(ExcludeCategories):LLVMIgnore $(ExcludeCategories):InetAccess:NetworkInterfaces - false + false CoreCLRTrimmable - $(ExcludeCategories):NativeTypeMap:Export + $(ExcludeCategories):NativeTypeMap @@ -71,11 +71,13 @@ + + - - + + <_AndroidRemapMembers Include="Remaps.xml" /> @@ -92,7 +94,6 @@ - @@ -102,10 +103,13 @@ + + + @@ -132,10 +136,11 @@ - + - + + diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/TrimmerRoots.xml b/tests/Mono.Android-Tests/Mono.Android-Tests/TrimmerRoots.xml index 8197e4f5994..4c4442f2cce 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/TrimmerRoots.xml +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/TrimmerRoots.xml @@ -3,7 +3,6 @@ - diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidClientHandlerTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidHandlerTestBase.cs similarity index 77% rename from tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidClientHandlerTests.cs rename to tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidHandlerTestBase.cs index 5fd9f35b3b8..35e23cf5f7e 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidClientHandlerTests.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidHandlerTestBase.cs @@ -1,5 +1,5 @@ // -// HttpClientHandlerTest.cs +// HttpClientHandlerTestBase.cs // // Authors: // Marek Safar @@ -27,25 +27,24 @@ // using System; -using System.Reflection; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net; using System.Net.Http; +using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; -using NUnit.Framework; - using Android.OS; -using Xamarin.Android.Net; + +using NUnit.Framework; namespace Xamarin.Android.NetTests { - [Category("InetAccess")] + [Category ("InetAccess")] [Category ("SSL")] // TODO: https://github.com/dotnet/android/issues/10069 public abstract class HttpClientHandlerTestBase { @@ -151,10 +150,8 @@ protected bool IsConnectionFailure (Exception ex) return false; } - } - [Category ("AndroidClientHandler")] public abstract class AndroidHandlerTestBase : HttpClientHandlerTestBase { static IEnumerable Exceptions (Exception e) @@ -173,28 +170,28 @@ static bool IsSecureChannelFailure (Exception e) [UnconditionalSuppressMessage ("Trimming", "IL2075", Justification = "Tests private fields are preserved by other means")] static Type GetInnerHandlerType (HttpClient httpClient) { - BindingFlags bflasgs = BindingFlags.Instance | BindingFlags.NonPublic; - FieldInfo handlerField = typeof (HttpMessageInvoker).GetField("_handler", bflasgs); + BindingFlags bflags = BindingFlags.Instance | BindingFlags.NonPublic; + FieldInfo handlerField = typeof (HttpMessageInvoker).GetField ("_handler", bflags); Assert.IsNotNull (handlerField); object handler = handlerField.GetValue (httpClient); - FieldInfo innerHandlerField = handler.GetType ().GetField ("_delegatingHandler", bflasgs); + FieldInfo innerHandlerField = handler.GetType ().GetField ("_delegatingHandler", bflags); Assert.IsNotNull (handlerField); object innerHandler = innerHandlerField.GetValue (handler); return innerHandler.GetType (); } [Test] - public void Cancel_Client_Works() + public void Cancel_Client_Works () { var cts = new CancellationTokenSource (); cts.Cancel (); //Cancel immediately - using (var c = new HttpClient (CreateHandler())) { + using (var c = new HttpClient (CreateHandler ())) { var tr = ConnectIgnoreFailure (() => c.GetAsync ("http://10.255.255.1", cts.Token), out bool connectionFailed); if (connectionFailed) return; try { - RunIgnoringNetworkIssues (() => tr.Wait(), out connectionFailed); + RunIgnoringNetworkIssues (() => tr.Wait (), out connectionFailed); if (connectionFailed) return; @@ -208,10 +205,10 @@ public void Cancel_Client_Works() } [Test] - public void Token_Timeout_Works() + public void Token_Timeout_Works () { var cts = new CancellationTokenSource (2000); //Cancel after 2000ms through token - using (var c = new HttpClient (CreateHandler())){ + using (var c = new HttpClient (CreateHandler ())) { var tr = ConnectIgnoreFailure (() => c.GetAsync ("http://10.255.255.1", cts.Token), out bool connectionFailed); if (connectionFailed) return; @@ -224,14 +221,14 @@ public void Token_Timeout_Works() Assert.Fail ("SHOULD NOT HAPPEN: Request is expected to cancel"); } catch (AggregateException ex) { - Assert.IsTrue (ex.InnerExceptions.Any(ie => ie is System.OperationCanceledException), "Request did not throw cancellation exception; threw: {0}", ex); + Assert.IsTrue (ex.InnerExceptions.Any (ie => ie is System.OperationCanceledException), "Request did not throw cancellation exception; threw: {0}", ex); Assert.IsTrue (cts.IsCancellationRequested, "The request was canceled before cancellation was requested"); } } } [Test] - public void Property_Timeout_Works() + public void Property_Timeout_Works () { using (var c = new HttpClient (CreateHandler ())) { @@ -255,7 +252,7 @@ public void Property_Timeout_Works() } [Test] - public void Redirect_Without_Protocol_Works() + public void Redirect_Without_Protocol_Works () { var requestURI = new Uri ("https://httpbin.org/redirect-to?url=https://github.com/dotnet/android"); var redirectedURI = new Uri ("https://github.com/dotnet/android"); @@ -267,7 +264,7 @@ public void Redirect_Without_Protocol_Works() RunIgnoringNetworkIssues (() => tr.Wait (), out connectionFailed); if (connectionFailed) return; - + EnsureSuccessStatusCode (tr.Result); Assert.AreEqual (redirectedURI, tr.Result.RequestMessage.RequestUri, "Invalid redirected URI"); } @@ -280,8 +277,8 @@ public void Redirect_POST_With_Content_Works () var redirectedURI = new Uri ("https://github.com/dotnet/android"); using (var c = new HttpClient (CreateHandler ())) { var request = new HttpRequestMessage (HttpMethod.Post, requestURI); - request.Content = new StringContent("{}", Encoding.UTF8, "application/json"); - var t = ConnectIgnoreFailure (() => c.SendAsync(request), out bool connectionFailed); + request.Content = new StringContent ("{}", Encoding.UTF8, "application/json"); + var t = ConnectIgnoreFailure (() => c.SendAsync (request), out bool connectionFailed); if (connectionFailed) return; @@ -322,68 +319,4 @@ public bool ShouldIgnoreSuccessStatusCode (HttpStatusCode code) return false; } } - - [TestFixture] - [Category ("AndroidClientHandler")] - public class AndroidClientHandlerTests : AndroidHandlerTestBase - { - protected override HttpMessageHandler CreateHandler () - { - return new AndroidClientHandler (); - } - - [Test] - public void Properties_Defaults () - { - var h = new AndroidClientHandler (); - - Assert.IsTrue (h.AllowAutoRedirect, "#1"); - Assert.AreEqual (DecompressionMethods.None, h.AutomaticDecompression, "#2"); - Assert.AreEqual (0, h.CookieContainer.Count, "#3"); - Assert.AreEqual (4096, h.CookieContainer.MaxCookieSize, "#3b"); - Assert.AreEqual (null, h.Credentials, "#4"); - Assert.AreEqual (50, h.MaxAutomaticRedirections, "#5"); - Assert.IsFalse (h.PreAuthenticate, "#7"); - Assert.IsNull (h.Proxy, "#8"); - Assert.IsTrue (h.SupportsAutomaticDecompression, "#9"); - Assert.IsTrue (h.SupportsProxy, "#10"); - Assert.IsTrue (h.SupportsRedirectConfiguration, "#11"); - Assert.IsTrue (h.UseCookies, "#12"); - Assert.IsFalse (h.UseDefaultCredentials, "#13"); - Assert.IsTrue (h.UseProxy, "#14"); - Assert.AreEqual (ClientCertificateOption.Manual, h.ClientCertificateOptions, "#15"); - Assert.IsNull (h.ServerCertificateCustomValidationCallback, "#16"); - } - - [Test] - public void Properties_Invalid () - { - var h = new AndroidClientHandler (); - - try { - h.MaxAutomaticRedirections = 0; - Assert.Fail ("#1"); - } catch (ArgumentOutOfRangeException) { - } - - try { - h.MaxRequestContentBufferSize = -1; - Assert.Fail ("#2"); - } catch (ArgumentOutOfRangeException) { - } - } - - [Test] - public void Properties_AfterClientCreation () - { - var h = new AndroidClientHandler (); - - h.AllowAutoRedirect = true; - - // We may modify properties after creating the HttpClient. - using (var c = new HttpClient (h, true)) { - h.AllowAutoRedirect = false; - } - } - } } diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerIntegrationTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerIntegrationTests.cs new file mode 100644 index 00000000000..d9931409247 --- /dev/null +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerIntegrationTests.cs @@ -0,0 +1,325 @@ +// +// AndroidMessageHandlerIntegrationTests.cs +// +// Authors: +// Marek Safar +// +// Copyright (C) 2011 Xamarin Inc (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +using System; +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using System.Net; +using System.Linq; +using System.IO; + +namespace Xamarin.Android.NetTests { + [Category ("InetAccess")] + public sealed class AndroidMessageHandlerIntegrationTests + { + class CustomStream : Stream + { + public override void Flush () + { + throw new NotImplementedException (); + } + + int pos; + + public override int Read (byte[] buffer, int offset, int count) + { + ++pos; + if (pos > 4) + return 0; + + return 11; + } + + public override long Seek (long offset, SeekOrigin origin) + { + throw new NotImplementedException (); + } + + public override void SetLength (long value) + { + throw new NotImplementedException (); + } + + public override void Write (byte[] buffer, int offset, int count) + { + throw new NotImplementedException (); + } + + public override bool CanRead { + get { + return true; + } + } + + public override bool CanSeek { + get { + return false; + } + } + + public override bool CanWrite { + get { + throw new NotImplementedException (); + } + } + + public override long Length { + get { + throw new NotImplementedException (); + } + } + + public override long Position { + get { + throw new NotImplementedException (); + } + set { + throw new NotImplementedException (); + } + } + } + + const int WaitTimeout = 10000; + + string port, TestHost, LocalServer; + + [SetUp] + public void SetupFixture () + { + if (Environment.OSVersion.Platform == PlatformID.Win32NT) { + port = "810"; + } else { + port = "8810"; + } + + TestHost = "localhost:" + port; + LocalServer = string.Format ("http://{0}/", TestHost); + } + + [Test] + public void Ctor_Default () + { + using (var handler = new Xamarin.Android.Net.AndroidMessageHandler ()) { + var client = new HttpClient (handler); + Assert.IsNull (client.BaseAddress, "#1"); + Assert.IsNotNull (client.DefaultRequestHeaders, "#2"); // TODO: full check + Assert.AreEqual (int.MaxValue, client.MaxResponseContentBufferSize, "#3"); + Assert.AreEqual (TimeSpan.FromSeconds (100), client.Timeout, "#4"); + } + } + + + [Test] + public void CancelRequestViaProxy () + { + using (var handler = new Xamarin.Android.Net.AndroidMessageHandler ()) { + handler.Proxy = new WebProxy ("192.168.10.25:8888/"); // proxy that doesn't exist + handler.UseProxy = true; + handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + + var httpClient = new HttpClient (handler) { + BaseAddress = new Uri ("https://google.com"), + Timeout = TimeSpan.FromMilliseconds (1) + }; + + var restRequest = new HttpRequestMessage { + Method = HttpMethod.Post, + RequestUri = new Uri ("foo", UriKind.Relative), + Content = new StringContent ("", null, "application/json") + }; + + var task = httpClient.PostAsync (restRequest.RequestUri, restRequest.Content); + bool completed = false; + try { + completed = task.Wait (WaitTimeout); + } catch (AggregateException e) { + Console.WriteLine ("CancelRequestViaProxy exception: {0}", e); + Assert.IsTrue (e.InnerException is TaskCanceledException, $"Expected TaskCanceledException but got: {e.InnerException?.GetType ().FullName}: {e.InnerException?.Message}"); + return; // Test passed - got expected exception + } + + // If we reach here, the task completed or timed out without throwing + if (!completed) { + Assert.Inconclusive ($"Test timed out waiting for task. Task status: {task.Status}. This can happen due to timing issues on slow machines."); + } + + // Task completed without throwing - this is unexpected + if (task.IsFaulted) { + Assert.Fail ($"Task faulted with unexpected exception: {task.Exception?.InnerException?.GetType ().FullName}: {task.Exception?.InnerException?.Message}"); + } else if (task.IsCanceled) { + // This is actually fine - the task was canceled as expected + return; + } else { + Assert.Fail ($"Expected request to be canceled due to 1ms timeout with non-existent proxy, but task completed successfully with Status: {task.Status}"); + } + } + } + + [Test] + public void Properties () + { + using (var handler = new Xamarin.Android.Net.AndroidMessageHandler ()) { + var client = new HttpClient (handler); + client.BaseAddress = null; + client.MaxResponseContentBufferSize = int.MaxValue; + client.Timeout = Timeout.InfiniteTimeSpan; + + Assert.IsNull (client.BaseAddress, "#1"); + Assert.AreEqual (int.MaxValue, client.MaxResponseContentBufferSize, "#2"); + Assert.AreEqual (Timeout.InfiniteTimeSpan, client.Timeout, "#3"); + } + } + + [Test] + public void Properties_Invalid () + { + using (var handler = new Xamarin.Android.Net.AndroidMessageHandler ()) { + var client = new HttpClient (handler); + try { + client.MaxResponseContentBufferSize = 0; + Assert.Fail ("#1"); + } catch (ArgumentOutOfRangeException) { + } + + try { + client.Timeout = TimeSpan.MinValue; + Assert.Fail ("#2"); + } catch (ArgumentOutOfRangeException) { + } + } + } + + [Test] + void UrlEscaping_Bug43411 () + { + UrlEscaping_TestUrl ($"http://{TestHost}/?example=value%20_value", "#1"); + UrlEscaping_TestUrl ($"http://{TestHost}/?query=anna%20%26%20lotte¶m2=true", "#2"); + } + + void UrlEscaping_TestUrl (string url, string messagePrefix) + { + bool? failed = null; + + var listener = CreateListener (l => { + failed = true; + }); + + using (listener) { + try { + var client = new HttpClient (); + var request = new HttpRequestMessage (HttpMethod.Get, url); + + client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Wait (); + Assert.AreEqual (url, request.RequestUri.ToString (), $"{messagePrefix}-1"); + Assert.IsNull (failed, $"{messagePrefix}-2"); + } finally { + listener.Abort (); + listener.Close (); + } + } + } + + [Test] + public void Send_Invalid () + { + var client = new HttpClient (new Xamarin.Android.Net.AndroidMessageHandler ()); + try { + client.SendAsync (null).Wait (WaitTimeout); + Assert.Fail ("#1"); + } catch (ArgumentNullException) { + } + + try { + var request = new HttpRequestMessage (); + client.SendAsync (request).Wait (WaitTimeout); + Assert.Fail ("#2"); + } catch (InvalidOperationException) { + } + } + + [Test] + public void GetString_Many () + { + var client = new HttpClient (new Xamarin.Android.Net.AndroidMessageHandler ()); + var t1 = client.GetStringAsync ("https://google.com"); + var t2 = client.GetStringAsync ("https://google.com"); + Assert.IsTrue (Task.WaitAll (new [] { t1, t2 }, WaitTimeout)); + } + + [Test] + public void DisallowAutoRedirect () + { + var listener = CreateListener (l => { + using (var response = l.Response) + { + response.Redirect("http://xamarin.com/"); + } + }); + + using (listener) { + try { + var chandler = new Xamarin.Android.Net.AndroidMessageHandler (); + chandler.AllowAutoRedirect = false; + var client = new HttpClient (chandler); + + try { + client.GetStringAsync (LocalServer).Wait (WaitTimeout); + Assert.Fail ("#1: HttpRequestException wasn't thrown."); + } catch (AggregateException e) { + Assert.IsTrue (e.InnerException is HttpRequestException, "#2: " + e.ToString ()); + } + } finally { + listener.Abort (); + listener.Close (); + } + } + } + + HttpListener CreateListener (Action contextAssert) + { + var l = new HttpListener (); + l.Prefixes.Add (string.Format ("http://+:{0}/", port)); + l.Start (); + l.BeginGetContext (ar => { + var ctx = l.EndGetContext (ar); + try { + if (contextAssert != null) + contextAssert (ctx); + } finally { + ctx.Response.Close (); + } + }, null); + + return l; + } + } +} diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/HttpClientIntegrationTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/HttpClientIntegrationTests.cs deleted file mode 100644 index a0e7de1a32c..00000000000 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/HttpClientIntegrationTests.cs +++ /dev/null @@ -1,1156 +0,0 @@ -// -// HttpClientIntegrationTests.cs -// -// Authors: -// Marek Safar -// -// Copyright (C) 2011 Xamarin Inc (http://www.xamarin.com) -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// -using System; -using System.Collections; -using System.Collections.Generic; -using NUnit.Framework; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; -using System.Net; -using System.Linq; -using System.IO; - -namespace Xamarin.Android.NetTests { - [Category ("InetAccess")] - public abstract class HttpClientIntegrationTestBase - { - // AndroidHandlerSettingsAdapter is a class specific for this test class. - // It unifies the APIs of AndroidClientHandler and AndroidMessageHandler. - protected abstract AndroidHandlerSettingsAdapter CreateHandler (); - - class CustomStream : Stream - { - public override void Flush () - { - throw new NotImplementedException (); - } - - int pos; - - public override int Read (byte[] buffer, int offset, int count) - { - ++pos; - if (pos > 4) - return 0; - - return 11; - } - - public override long Seek (long offset, SeekOrigin origin) - { - throw new NotImplementedException (); - } - - public override void SetLength (long value) - { - throw new NotImplementedException (); - } - - public override void Write (byte[] buffer, int offset, int count) - { - throw new NotImplementedException (); - } - - public override bool CanRead { - get { - return true; - } - } - - public override bool CanSeek { - get { - return false; - } - } - - public override bool CanWrite { - get { - throw new NotImplementedException (); - } - } - - public override long Length { - get { - throw new NotImplementedException (); - } - } - - public override long Position { - get { - throw new NotImplementedException (); - } - set { - throw new NotImplementedException (); - } - } - } - - const int WaitTimeout = 10000; - - string port, TestHost, LocalServer; - - [SetUp] - public void SetupFixture () - { - if (Environment.OSVersion.Platform == PlatformID.Win32NT) { - port = "810"; - } else { - port = "8810"; - } - - TestHost = "localhost:" + port; - LocalServer = string.Format ("http://{0}/", TestHost); - } - - [Test] - public void Ctor_Default () - { - using (var handler = CreateHandler ()) { - var client = new HttpClient (handler); - Assert.IsNull (client.BaseAddress, "#1"); - Assert.IsNotNull (client.DefaultRequestHeaders, "#2"); // TODO: full check - Assert.AreEqual (int.MaxValue, client.MaxResponseContentBufferSize, "#3"); - Assert.AreEqual (TimeSpan.FromSeconds (100), client.Timeout, "#4"); - } - } - - - [Test] - public void CancelRequestViaProxy () - { - using (var handler = CreateHandler ()) { - handler.Proxy = new WebProxy ("192.168.10.25:8888/"); // proxy that doesn't exist - handler.UseProxy = true; - handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; - - var httpClient = new HttpClient (handler) { - BaseAddress = new Uri ("https://google.com"), - Timeout = TimeSpan.FromMilliseconds (1) - }; - - var restRequest = new HttpRequestMessage { - Method = HttpMethod.Post, - RequestUri = new Uri ("foo", UriKind.Relative), - Content = new StringContent ("", null, "application/json") - }; - - var task = httpClient.PostAsync (restRequest.RequestUri, restRequest.Content); - bool completed = false; - try { - completed = task.Wait (WaitTimeout); - } catch (AggregateException e) { - Console.WriteLine ("CancelRequestViaProxy exception: {0}", e); - Assert.IsTrue (e.InnerException is TaskCanceledException, $"Expected TaskCanceledException but got: {e.InnerException?.GetType ().FullName}: {e.InnerException?.Message}"); - return; // Test passed - got expected exception - } - - // If we reach here, the task completed or timed out without throwing - if (!completed) { - Assert.Inconclusive ($"Test timed out waiting for task. Task status: {task.Status}. This can happen due to timing issues on slow machines."); - } - - // Task completed without throwing - this is unexpected - if (task.IsFaulted) { - Assert.Fail ($"Task faulted with unexpected exception: {task.Exception?.InnerException?.GetType ().FullName}: {task.Exception?.InnerException?.Message}"); - } else if (task.IsCanceled) { - // This is actually fine - the task was canceled as expected - return; - } else { - Assert.Fail ($"Expected request to be canceled due to 1ms timeout with non-existent proxy, but task completed successfully with Status: {task.Status}"); - } - } - } - - [Test] - public void Properties () - { - using (var handler = CreateHandler ()) { - var client = new HttpClient (handler); - client.BaseAddress = null; - client.MaxResponseContentBufferSize = int.MaxValue; - client.Timeout = Timeout.InfiniteTimeSpan; - - Assert.IsNull (client.BaseAddress, "#1"); - Assert.AreEqual (int.MaxValue, client.MaxResponseContentBufferSize, "#2"); - Assert.AreEqual (Timeout.InfiniteTimeSpan, client.Timeout, "#3"); - } - } - - [Test] - public void Properties_Invalid () - { - using (var handler = CreateHandler ()) { - var client = new HttpClient (handler); - try { - client.MaxResponseContentBufferSize = 0; - Assert.Fail ("#1"); - } catch (ArgumentOutOfRangeException) { - } - - try { - client.Timeout = TimeSpan.MinValue; - Assert.Fail ("#2"); - } catch (ArgumentOutOfRangeException) { - } - } - } - -#if TODO - [Test] - public void Send_Complete_Default () - { - bool? failed = null; - var listener = CreateListener (l => { - try { - var request = l.Request; - - Assert.IsNull (request.AcceptTypes, "#1"); - Assert.AreEqual (0, request.ContentLength64, "#2"); - Assert.IsNull (request.ContentType, "#3"); - Assert.AreEqual (0, request.Cookies.Count, "#4"); - Assert.IsFalse (request.HasEntityBody, "#5"); - Assert.AreEqual (TestHost, request.Headers["Host"], "#6b"); - Assert.AreEqual ("GET", request.HttpMethod, "#7"); - Assert.IsFalse (request.IsAuthenticated, "#8"); -#if false - Assert.IsTrue (request.IsLocal, "#9"); -#endif // Buggy HttpListenerRequest (https://bugzilla.xamarin.com/show_bug.cgi?id=38322) - Assert.IsFalse (request.IsSecureConnection, "#10"); - Assert.IsFalse (request.IsWebSocketRequest, "#11"); - Assert.IsTrue (request.KeepAlive, "#12"); - Assert.AreEqual (HttpVersion.Version11, request.ProtocolVersion, "#13"); - Assert.IsNull (request.ServiceName, "#14"); - Assert.IsNull (request.UrlReferrer, "#15"); - Assert.IsNotNull (request.UserAgent, "#16"); // We're not using .NET client here, but rather the Java one which sets the UserAgent header - Assert.IsNull (request.UserLanguages, "#17"); - failed = false; - } catch (Exception e) { - Console.WriteLine ("# jonp: Send_Complete_Default"); - Console.WriteLine (e); - failed = true; - } - }); - - using (listener) { - try { - using (var handler = CreateHandler ()) { - var client = new HttpClient (handler); - var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); - var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; - - Assert.AreEqual ("", response.Content.ReadAsStringAsync ().Result, "#100"); - Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#101"); - Assert.AreEqual (false, failed, "#102"); - } - } finally { - listener.Close (); - } - } - } - - [Test] - public void Send_Complete_Version_1_0 () - { - bool? failed = null; - - var listener = CreateListener (l => { - try { - var request = l.Request; - - Assert.IsNull (request.AcceptTypes, "#1"); - Assert.AreEqual (0, request.ContentLength64, "#2"); - Assert.IsNull (request.ContentType, "#3"); - Assert.AreEqual (0, request.Cookies.Count, "#4"); - Assert.IsFalse (request.HasEntityBody, "#5"); - Assert.AreEqual (1, request.Headers.Count, "#6"); - Assert.AreEqual (TestHost, request.Headers["Host"], "#6a"); - Assert.AreEqual ("GET", request.HttpMethod, "#7"); - Assert.IsFalse (request.IsAuthenticated, "#8"); -#if false - Assert.IsTrue (request.IsLocal, "#9"); -#endif // Buggy HttpListenerRequest (https://bugzilla.xamarin.com/show_bug.cgi?id=38322) - Assert.IsFalse (request.IsSecureConnection, "#10"); - Assert.IsFalse (request.IsWebSocketRequest, "#11"); - Assert.IsFalse (request.KeepAlive, "#12"); -#if false // Java HTTP client doesn't support 1.0, always uses 1.1 - Assert.AreEqual (HttpVersion.Version10, request.ProtocolVersion, "#13"); -#endif - Assert.IsNull (request.ServiceName, "#14"); - Assert.IsNull (request.UrlReferrer, "#15"); - Assert.IsNotNull (request.UserAgent, "#16"); // We're not using .NET client here, but rather the Java one which sets the UserAgent header - Assert.IsNull (request.UserLanguages, "#17"); - failed = false; - } catch (Exception e) { - Console.WriteLine ("# jonp: Send_Complete_Version_1_0"); - Console.WriteLine (e); - failed = true; - } - }); - - using (listener) { - try { - using (var handler = CreateHandler ()) { - var client = new HttpClient (handler); - var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); - //request.Version = HttpVersion.Version10; - var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; - - Assert.AreEqual ("", response.Content.ReadAsStringAsync ().Result, "#100"); - Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#101"); - Assert.AreEqual (false, failed, "#102"); - } - } finally { - listener.Close (); - } - } - } - - // This is failing because the `try/catch` block (lines 308-336) aren't executed, - // and thus `failed` (line 304) is `null` on line 361, resulting in a NRE. - [Test] - public void Send_Complete_ClientHandlerSettings () - { - bool? failed = null; - - var listener = CreateListener (l => { - var request = l.Request; - - try { - Assert.IsNull (request.AcceptTypes, "#1"); - Assert.AreEqual (0, request.ContentLength64, "#2"); - Assert.IsNull (request.ContentType, "#3"); - Assert.AreEqual (1, request.Cookies.Count, "#4"); - Assert.AreEqual (new Cookie ("mycookie", "vv"), request.Cookies[0], "#4a"); - Assert.IsFalse (request.HasEntityBody, "#5"); - Assert.AreEqual (4, request.Headers.Count, "#6"); - Assert.AreEqual (TestHost, request.Headers["Host"], "#6a"); - Assert.AreEqual ("gzip", request.Headers["Accept-Encoding"], "#6b"); - Assert.AreEqual ("mycookie=vv", request.Headers["Cookie"], "#6c"); - Assert.AreEqual ("GET", request.HttpMethod, "#7"); - Assert.IsFalse (request.IsAuthenticated, "#8"); -#if false - Assert.IsTrue (request.IsLocal, "#9"); -#endif // Buggy HttpListenerRequest (https://bugzilla.xamarin.com/show_bug.cgi?id=38322) - Assert.IsFalse (request.IsSecureConnection, "#10"); - Assert.IsFalse (request.IsWebSocketRequest, "#11"); - Assert.IsTrue (request.KeepAlive, "#12"); -#if false // Java HTTP client doesn't support 1.0, always uses 1.1 - Assert.AreEqual (HttpVersion.Version10, request.ProtocolVersion, "#13"); -#endif - Assert.IsNull (request.ServiceName, "#14"); - Assert.IsNull (request.UrlReferrer, "#15"); -#if false - Assert.IsNull (request.UserAgent, "#16"); // We're not using .NET client here, but rather the Java one which sets the UserAgent header -#endif - Assert.IsNull (request.UserLanguages, "#17"); - failed = false; - } catch (Exception x) { - Console.WriteLine ("# jonp: Send_Complete_ClientHandlerSettings: ERROR"); - Console.WriteLine (x.ToString ()); - failed = true; - } - }); - - using (listener) { - try { - using (var chandler = CreateHandler ()) { - chandler.AllowAutoRedirect = true; - chandler.AutomaticDecompression = DecompressionMethods.GZip; - chandler.MaxAutomaticRedirections = 33; - chandler.MaxRequestContentBufferSize = 5555; - chandler.PreAuthenticate = true; - chandler.CookieContainer.Add (new Uri (LocalServer), new Cookie ("mycookie", "vv")); - chandler.UseCookies = true; - chandler.UseDefaultCredentials = true; - chandler.Proxy = new WebProxy ("ee"); - chandler.UseProxy = true; - - var client = new HttpClient (chandler); - var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); - request.Version = HttpVersion.Version10; - request.Headers.Add ("Keep-Alive", "false"); - var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; - - Assert.AreEqual ("", response.Content.ReadAsStringAsync ().Result, "#100"); - Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#101"); - Console.WriteLine ("# jonp: Send_Complete_ClientHandlerSettings: failed? {0}", failed.HasValue); - Assert.AreEqual (false, failed, "#102"); - } - } finally { - listener.Abort (); - listener.Close (); - } - } - } - - [Test] - public void Send_Complete_CustomHeaders () - { - bool? failed = null; - - var listener = CreateListener (l => { - var request = l.Request; - try { - Assert.AreEqual ("vv", request.Headers["aa"], "#1"); - - var response = l.Response; - response.Headers.Add ("rsp", "rrr"); - response.Headers.Add ("upgrade", "vvvvaa"); - response.Headers.Add ("Date", "aa"); - response.Headers.Add ("cache-control", "audio"); - - response.StatusDescription = "test description"; - response.ProtocolVersion = HttpVersion.Version10; - response.SendChunked = true; - response.RedirectLocation = "w3.org"; - - failed = false; - } catch { - failed = true; - } - }); - - using (listener) { - try { - using (var handler = CreateHandler ()) { - var client = new HttpClient (handler); - var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); - Assert.IsTrue (request.Headers.TryAddWithoutValidation ("aa", "vv"), "#0"); - var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; - - Assert.AreEqual ("", response.Content.ReadAsStringAsync ().Result, "#100"); - Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#101"); - - IEnumerable values; - Assert.IsTrue (response.Headers.TryGetValues ("rsp", out values), "#102"); - Assert.AreEqual ("rrr", values.First (), "#102a"); - - Assert.IsTrue (response.Headers.TryGetValues ("Transfer-Encoding", out values), "#103"); - Assert.AreEqual ("chunked", values.First (), "#103a"); - Assert.AreEqual (true, response.Headers.TransferEncodingChunked, "#103b"); - - Assert.IsTrue (response.Headers.TryGetValues ("Date", out values), "#104"); - Assert.AreEqual (1, values.Count (), "#104b"); - // .NET overwrites Date, Mono does not - // Assert.IsNotNull (response.Headers.Date, "#104c"); - - Assert.AreEqual (new ProductHeaderValue ("vvvvaa"), response.Headers.Upgrade.First (), "#105"); - - Assert.AreEqual ("audio", response.Headers.CacheControl.Extensions.First ().Name, "#106"); - - Assert.AreEqual ("w3.org", response.Headers.Location.OriginalString, "#107"); - - Assert.AreEqual ("test description", response.ReasonPhrase, "#110"); - Assert.AreEqual (HttpVersion.Version11, response.Version, "#111"); - - Assert.AreEqual (false, failed, "#112"); - } - } finally { - listener.Close (); - } - } - } - - [Test] - public void Send_Complete_CustomHeaders_SpecialSeparators () - { - bool? failed = null; - - var listener = CreateListener (l => { - var request = l.Request; - - try { - Assert.AreEqual ("MLK Android Phone 1.1.9", request.UserAgent, "#1"); - failed = false; - } catch (Exception ex) { - failed = true; - Console.WriteLine (ex); - } - }); - - using (listener) { - try { - using (var handler = CreateHandler ()) { - var client = new HttpClient (handler); - - client.DefaultRequestHeaders.Add ("User-Agent", "MLK Android Phone 1.1.9"); - - var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); - - var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; - - Assert.AreEqual ("", response.Content.ReadAsStringAsync ().Result, "#100"); - Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#101"); - Assert.AreEqual (false, failed, "#102"); - } - } finally { - listener.Abort (); - listener.Close (); - } - } - } - - [Test] - public void Send_Complete_CustomHeaders_Host () - { - bool? failed = null; - var listener = CreateListener (l => { - var request = l.Request; - - try { - Assert.AreEqual ("customhost", request.Headers["Host"], "#1"); - failed = false; - } catch (Exception ex) { - failed = true; - Console.WriteLine (ex); - } - }); - - using (listener) { - try { - using (var handler = CreateHandler ()) { - var client = new HttpClient (handler); - - client.DefaultRequestHeaders.Add ("Host", "customhost"); - - var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); - - var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; - - Assert.AreEqual ("", response.Content.ReadAsStringAsync ().Result, "#100"); - Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#101"); - Assert.AreEqual (false, failed, "#102"); - } - } finally { - listener.Abort (); - listener.Close (); - } - } - } - - [Test] - public void Send_Transfer_Encoding_Chunked () - { - bool? failed = null; - - var listener = CreateListener (l => { - var request = l.Request; - - try { - Assert.AreEqual (5, request.Headers.Count, "#1"); - failed = false; - } catch (Exception ex) { - failed = true; - Console.WriteLine (ex); - } - }); - - using (listener) { - try { - using (var handler = CreateHandler ()) { - var client = new HttpClient (handler); - client.DefaultRequestHeaders.TransferEncodingChunked = true; - - client.GetAsync (LocalServer).Wait (); - - Assert.AreEqual (false, failed, "#102"); - } - } finally { - listener.Abort (); - listener.Close (); - } - } - } -#endif // TODO - [Test] - void UrlEscaping_Bug43411 () - { - UrlEscaping_TestUrl ($"http://{TestHost}/?example=value%20_value", "#1"); - UrlEscaping_TestUrl ($"http://{TestHost}/?query=anna%20%26%20lotte¶m2=true", "#2"); - } - - void UrlEscaping_TestUrl (string url, string messagePrefix) - { - bool? failed = null; - - var listener = CreateListener (l => { - failed = true; - }); - - using (listener) { - try { - var client = new HttpClient (); - var request = new HttpRequestMessage (HttpMethod.Get, url); - - client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Wait (); - Assert.AreEqual (url, request.RequestUri.ToString (), $"{messagePrefix}-1"); - Assert.IsNull (failed, $"{messagePrefix}-2"); - } finally { - listener.Abort (); - listener.Close (); - } - } - } -#if TODO - [Test] - public void Send_Complete_Content () - { - var listener = CreateListener (l => { - var request = l.Request; - l.Response.OutputStream.WriteByte (55); - l.Response.OutputStream.WriteByte (75); - }); - - using (listener) { - try { - using (var handler = CreateHandler ()) { - var client = new HttpClient (handler); - var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); - Assert.IsTrue (request.Headers.TryAddWithoutValidation ("aa", "vv"), "#0"); - var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; - - Assert.AreEqual ("7K", response.Content.ReadAsStringAsync ().Result, "#100"); - Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#101"); - - IEnumerable values; - Assert.IsTrue (response.Headers.TryGetValues ("Transfer-Encoding", out values), "#102"); - Assert.AreEqual ("chunked", values.First (), "#102a"); - Assert.AreEqual (true, response.Headers.TransferEncodingChunked, "#102b"); - } - } finally { - listener.Close (); - } - } - } - - [Test] - public void Send_Complete_Content_MaxResponseContentBufferSize () - { - var listener = CreateListener (l => { - var request = l.Request; - var b = new byte[4000]; - l.Response.OutputStream.Write (b, 0, b.Length); - }); - - using (listener) { - try { - using (var handler = CreateHandler ()) { - var client = new HttpClient (handler); - client.MaxResponseContentBufferSize = 1000; - var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); - var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; - - Assert.AreEqual (4000, response.Content.ReadAsStringAsync ().Result.Length, "#100"); - Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#101"); - } - } finally { - listener.Close (); - } - } - } - - [Test] - public void Send_Complete_Content_MaxResponseContentBufferSize_Error () - { - var listener = CreateListener (l => { - var request = l.Request; - var b = new byte[4000]; - l.Response.OutputStream.Write (b, 0, b.Length); - }); - - using (listener) { - try { - using (var handler = CreateHandler ()) { - var client = new HttpClient (handler); - client.MaxResponseContentBufferSize = 1000; - var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); - - try { - client.SendAsync (request, HttpCompletionOption.ResponseContentRead).Wait (WaitTimeout); - Assert.Fail ("#2"); - } catch (AggregateException e) { - Assert.IsTrue (e.InnerException is HttpRequestException, "#3; threw: {0}", e); - } - } - } finally { - listener.Close (); - } - } - } -#endif - public void Send_Complete_NoContent (HttpMethod method) - { - bool? failed = null; - var listener = CreateListener (l => { - try { - var request = l.Request; - - Assert.AreEqual (6, request.Headers.Count, $"#1-{method}"); - Assert.AreEqual ("0", request.Headers ["Content-Length"], $"#1b-{method}"); - Assert.AreEqual (method.Method, request.HttpMethod, $"#2-{method}"); - Console.WriteLine ($"Asserts are fine - {method}"); - failed = false; - } catch (Exception ex) { - failed = true; - Console.WriteLine (ex); - } - }); - - using (listener) { - try { - using (var handler = CreateHandler ()) { - var client = new HttpClient (handler); - var request = new HttpRequestMessage (method, LocalServer); - var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; - - Assert.AreEqual ("", response.Content.ReadAsStringAsync ().Result, $"#100-{method}"); - Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, $"#101-{method}"); - Assert.AreEqual (false, failed, $"#102-{method}"); - } - } finally { - listener.Close (); - } - } - } -#if TODO - [Test] - public void Send_Complete_NoContent_POST () - { - Send_Complete_NoContent (HttpMethod.Post); - } - - [Test] - public void Send_Complete_NoContent_PUT () - { - Send_Complete_NoContent (HttpMethod.Put); - } - - - [Test] - public void Send_Complete_NoContent_DELETE () - { - Send_Complete_NoContent (HttpMethod.Delete); - } - - [Test] - public void Send_Complete_Error () - { - var listener = CreateListener (l => { - var response = l.Response; - response.StatusCode = 500; - }); - - using (listener) { - try { - using (var handler = CreateHandler ()) { - var client = new HttpClient (handler); - var request = new HttpRequestMessage (HttpMethod.Get, LocalServer); - var response = client.SendAsync (request, HttpCompletionOption.ResponseHeadersRead).Result; - - Assert.AreEqual ("", response.Content.ReadAsStringAsync ().Result, "#100"); - Assert.AreEqual (HttpStatusCode.InternalServerError, response.StatusCode, "#101"); - } - } finally { - listener.Close (); - } - } - } - - [Test] - public void Send_Content_Get () - { - var listener = CreateListener (l => { - var request = l.Request; - l.Response.OutputStream.WriteByte (72); - }); - - using (listener) { - try { - var client = new HttpClient (); - var r = new HttpRequestMessage (HttpMethod.Get, LocalServer); - var response = client.SendAsync (r).Result; - - Assert.AreEqual ("H", response.Content.ReadAsStringAsync ().Result); - } finally { - listener.Close (); - } - } - } - - [Test] - public void Send_Content_BomEncoding () - { - var listener = CreateListener (l => { - var request = l.Request; - - var str = l.Response.OutputStream; - str.WriteByte (0xEF); - str.WriteByte (0xBB); - str.WriteByte (0xBF); - str.WriteByte (71); - }); - - using (listener) { - try { - var client = new HttpClient (); - var r = new HttpRequestMessage (HttpMethod.Get, LocalServer); - var response = client.SendAsync (r).Result; - - Assert.AreEqual ("G", response.Content.ReadAsStringAsync ().Result); - } finally { - listener.Close (); - } - } - } - - [Test] - public void Send_Content_Put () - { - bool passed = false; - var listener = CreateListener (l => { - var request = l.Request; - passed = 7 == request.ContentLength64; - passed &= request.ContentType == "text/plain; charset=utf-8"; - passed &= request.InputStream.ReadByte () == 'm'; - }); - - using (listener) { - try { - var client = new HttpClient (); - var r = new HttpRequestMessage (HttpMethod.Put, LocalServer); - r.Content = new StringContent ("my text"); - var response = client.SendAsync (r).Result; - - Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#1"); - Assert.IsTrue (passed, "#2"); - } finally { - listener.Abort (); - listener.Close (); - } - } - } - - [Test] - public void Send_Content_Put_CustomStream () - { - bool passed = false; - var listener = CreateListener (l => { - var request = l.Request; - passed = 44 == request.ContentLength64; - passed &= request.ContentType == null; - }); - - using (listener) { - try { - var client = new HttpClient (); - var r = new HttpRequestMessage (HttpMethod.Put, LocalServer); - r.Content = new StreamContent (new CustomStream ()); - var response = client.SendAsync (r).Result; - - Assert.AreEqual (HttpStatusCode.OK, response.StatusCode, "#1"); - Assert.IsTrue (passed, "#2"); - } finally { - listener.Abort (); - - listener.Close (); - } - } - } -#endif // TODO - - [Test] - public void Send_Invalid () - { - var client = new HttpClient (CreateHandler ()); - try { - client.SendAsync (null).Wait (WaitTimeout); - Assert.Fail ("#1"); - } catch (ArgumentNullException) { - } - - try { - var request = new HttpRequestMessage (); - client.SendAsync (request).Wait (WaitTimeout); - Assert.Fail ("#2"); - } catch (InvalidOperationException) { - } - } - - [Test] - public void GetString_Many () - { - var client = new HttpClient (CreateHandler ()); - var t1 = client.GetStringAsync ("https://google.com"); - var t2 = client.GetStringAsync ("https://google.com"); - Assert.IsTrue (Task.WaitAll (new [] { t1, t2 }, WaitTimeout)); - } - -#if TODO - // Currently fails because GetByteArrayAsync().Wait(timeout) doesn't throw - [Test] - public void GetByteArray_ServerError () - { - var listener = CreateListener (l => { - var response = l.Response; - response.StatusCode = 500; - l.Response.OutputStream.WriteByte (72); - }); - - using (listener) { - try { - var client = new HttpClient (CreateHandler ()); - try { - client.GetByteArrayAsync (LocalServer).Wait (WaitTimeout); - Assert.Fail ("#1"); - } catch (AggregateException e) { - Console.WriteLine ("# jonp: GetByteArray_ServerError"); - Console.WriteLine (e); - Assert.IsTrue (e.InnerException is HttpRequestException, "#2; threw: {0}", e); - } - } finally { - listener.Close (); - } - } - } -#endif // TODO - - [Test] - public void DisallowAutoRedirect () - { - var listener = CreateListener (l => { - using (var response = l.Response) - { - response.Redirect("http://xamarin.com/"); - } - }); - - using (listener) { - try { - var chandler = CreateHandler (); - chandler.AllowAutoRedirect = false; - var client = new HttpClient (chandler); - - try { - client.GetStringAsync (LocalServer).Wait (WaitTimeout); - Assert.Fail ("#1: HttpRequestException wasn't thrown."); - } catch (AggregateException e) { - Assert.IsTrue (e.InnerException is HttpRequestException, "#2: " + e.ToString ()); - } - } finally { - listener.Abort (); - listener.Close (); - } - } - } -#if TODO - [Test] - public void RequestUriAfterRedirect () - { - var listener = CreateListener (l => { - var request = l.Request; - var response = l.Response; - - response.StatusCode = (int)HttpStatusCode.Moved; - response.RedirectLocation = "http://xamarin.com/"; - }); - - using (listener) { - try { - var chandler = CreateHandler (); - chandler.AllowAutoRedirect = true; - var client = new HttpClient (chandler); - - var r = client.GetAsync (LocalServer); - Assert.IsTrue (r.Wait (WaitTimeout), "#1"); - var resp = r.Result; - Assert.AreEqual ("http://xamarin.com/", resp.RequestMessage.RequestUri.AbsoluteUri, "#2"); - } finally { - listener.Abort (); - listener.Close (); - } - } - } -#endif -#if false - // It doesn't appear to be possible to satisfy this test, because e.g. - // HttpClientHandler.set_AllowAutoRedirect only throws when - // HttpClientHandler.sentRequest is true, and sentRequest is only set - // if HttpClientHandler.SendAsync() is invoked, and *we can't call it*. - // Perhaps a mono implementation bug? - [Test] - /* - * Properties may only be modified before sending the first request. - */ - public void ModifyHandlerAfterFirstRequest () - { - var chandler = CreateHandler (); - chandler.AllowAutoRedirect = true; - var client = new HttpClient (chandler, true); - - var listener = CreateListener (l => { - var response = l.Response; - response.StatusCode = 200; - response.OutputStream.WriteByte (55); - }); - - try { - client.GetStringAsync (LocalServer).Wait (WaitTimeout); - try { - chandler.AllowAutoRedirect = false; - Assert.Fail ("#1"); - } catch (InvalidOperationException) { - ; - } - } finally { - listener.Abort (); - listener.Close (); - } - } -#endif - - HttpListener CreateListener (Action contextAssert) - { - var l = new HttpListener (); - l.Prefixes.Add (string.Format ("http://+:{0}/", port)); - l.Start (); - l.BeginGetContext (ar => { - var ctx = l.EndGetContext (ar); - try { - if (contextAssert != null) - contextAssert (ctx); - } finally { - ctx.Response.Close (); - } - }, null); - - return l; - } - - // AndroidClientHandler and AndroidMessageHandler have the same properties and methods - // but they aren't declared in any of their shared base classes or interfaces so there - // is this adapter that allows us to unify their APIs for test purposes. - protected abstract class AndroidHandlerSettingsAdapter : IDisposable - { - protected abstract HttpMessageHandler Unwrap(); - public abstract void Dispose (); - - public abstract bool UseProxy { set; } - public abstract IWebProxy? Proxy { set; } - public abstract bool AllowAutoRedirect { set; } - public abstract DecompressionMethods AutomaticDecompression { set; } - public abstract int MaxAutomaticRedirections { set; } - public abstract int MaxRequestContentBufferSize { set; } - public abstract bool PreAuthenticate { set; } - public abstract CookieContainer CookieContainer { get; } - public abstract bool UseCookies { set; } - public abstract bool UseDefaultCredentials { set; } - - public static implicit operator HttpMessageHandler (AndroidHandlerSettingsAdapter adapter) - => adapter.Unwrap(); - } - } - - [TestFixture] - [Category ("AndroidClientHandler")] - public class AndroidClientHandlerIntegrationTests : HttpClientIntegrationTestBase - { - protected override AndroidHandlerSettingsAdapter CreateHandler () - { - return new AndroidClientHandlerAdapter (new Xamarin.Android.Net.AndroidClientHandler ()); - } - - private class AndroidClientHandlerAdapter : AndroidHandlerSettingsAdapter - { - private Xamarin.Android.Net.AndroidClientHandler _handler; - - public AndroidClientHandlerAdapter (Xamarin.Android.Net.AndroidClientHandler handler) - { - _handler = handler; - } - - protected override HttpMessageHandler Unwrap() - => _handler; - - public override void Dispose () - { - _handler.Dispose(); - } - - public override bool UseProxy { set => _handler.UseProxy = value; } - public override IWebProxy? Proxy { set => _handler.Proxy = value; } - public override bool AllowAutoRedirect { set => _handler.AllowAutoRedirect = value; } - public override DecompressionMethods AutomaticDecompression { set => _handler.AutomaticDecompression = value; } - public override int MaxAutomaticRedirections { set => _handler.MaxAutomaticRedirections = value; } - public override int MaxRequestContentBufferSize { set => _handler.MaxRequestContentBufferSize = value; } - public override bool PreAuthenticate { set => _handler.PreAuthenticate = value; } - public override CookieContainer CookieContainer => _handler.CookieContainer; - public override bool UseCookies { set => _handler.UseCookies = value; } - public override bool UseDefaultCredentials { set => _handler.UseDefaultCredentials = value; } - } - } - - [TestFixture] - public class AndroidMessageHandlerIntegrationTests : HttpClientIntegrationTestBase - { - protected override AndroidHandlerSettingsAdapter CreateHandler () - { - return new AndroidMessageHandlerAdapter (new Xamarin.Android.Net.AndroidMessageHandler ()); - } - - private class AndroidMessageHandlerAdapter : AndroidHandlerSettingsAdapter - { - private Xamarin.Android.Net.AndroidMessageHandler _handler; - - public AndroidMessageHandlerAdapter (Xamarin.Android.Net.AndroidMessageHandler handler) - { - _handler = handler; - } - - protected override HttpMessageHandler Unwrap() - => _handler; - - public override void Dispose () - { - _handler.Dispose(); - } - - public override bool UseProxy { set => _handler.UseProxy = value; } - public override IWebProxy? Proxy { set => _handler.Proxy = value; } - public override bool AllowAutoRedirect { set => _handler.AllowAutoRedirect = value; } - public override DecompressionMethods AutomaticDecompression { set => _handler.AutomaticDecompression = value; } - public override int MaxAutomaticRedirections { set => _handler.MaxAutomaticRedirections = value; } - public override int MaxRequestContentBufferSize { set { /* no-op */ } } - public override bool PreAuthenticate { set => _handler.PreAuthenticate = value; } - public override CookieContainer CookieContainer => _handler.CookieContainer; - public override bool UseCookies { set => _handler.UseCookies = value; } - public override bool UseDefaultCredentials { set => _handler.Credentials = value ? CredentialCache.DefaultCredentials : null; } - } - } -} diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/TrustManagerMarshallingTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/TrustManagerMarshallingTests.cs new file mode 100644 index 00000000000..53d558fe61b --- /dev/null +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/TrustManagerMarshallingTests.cs @@ -0,0 +1,61 @@ +using System.Linq; +using Android.Runtime; +using Java.Security; +using Javax.Net.Ssl; +using Microsoft.Android.Runtime; +using NUnit.Framework; + +namespace Xamarin.Android.NetTests +{ + [TestFixture] + public class TrustManagerMarshallingTests + { + [Test] + public void TrustManagerFactory_GetTrustManagers_ReturnsIX509TrustManager () + { + var tmf = TrustManagerFactory.GetInstance (TrustManagerFactory.DefaultAlgorithm); + tmf.Init ((KeyStore?) null); + + var trustManagers = tmf.GetTrustManagers (); + Assert.IsNotNull (trustManagers, "GetTrustManagers returned null"); + Assert.IsTrue (trustManagers.Length > 0, "GetTrustManagers returned empty array"); + + bool foundX509 = false; + foreach (var tm in trustManagers) { + if (tm is IX509TrustManager) { + foundX509 = true; + } + } + + Assert.IsTrue (foundX509, + $"No ITrustManager element was marshalled as IX509TrustManager. " + + $"Types found: {string.Join (", ", trustManagers.Select (t => t.GetType ().FullName))}"); + } + + [Test] + public void JavaInterfaceLookup_BaseInterfaceReturnType_UsesDerivedInterfaceProxy () + { + AssumeTrimmableTypeMapEnabled (); + + // Mirrors API 21-23 TrustManagerImpl: the Java signature returns the + // base interface, but the concrete object advertises a derived interface. + using var provider = global::Net.Dot.Android.Test.InterfaceMarshalling.ExtendedValueProviderAsValueProvider; + Assert.IsNotNull (provider, "Expected Java fixture to return a ValueProvider instance."); + + if (provider is not global::Net.Dot.Android.Test.IExtendedValueProvider extendedProvider) { + Assert.Fail ($"Expected ValueProvider to be marshalled as IExtendedValueProvider. Type found: {provider.GetType ().FullName}"); + return; + } + + Assert.AreEqual (42, provider.Value); + Assert.AreEqual (84, extendedProvider.OtherValue); + } + + static void AssumeTrimmableTypeMapEnabled () + { + if (!RuntimeFeature.TrimmableTypeMap) { + Assert.Ignore ("TrimmableTypeMap feature switch is off; test only relevant for the trimmable typemap path."); + } + } + } +} diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs index ccff36c26c4..dd519c2c610 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs @@ -28,24 +28,18 @@ protected NUnitInstrumentation(IntPtr handle, JniHandleOwnership transfer) if (Microsoft.Android.Runtime.RuntimeFeature.TrimmableTypeMap) { // TODO: https://github.com/dotnet/android/issues/11170 // Tests from the external Java.Interop-Tests assembly that fail under the - // trimmable typemap. These cannot use [Category("TrimmableIgnore")] because - // we don't control that assembly — they must be excluded by name here. + // trimmable typemap. We don't control that assembly, so they must be + // excluded by name here. ExcludedTestNames = new [] { - // net.dot.jni.test.CallVirtualFromConstructorDerived Java class not in APK + // [JniAddNativeMethodRegistrationAttribute] is not supported by design under + // the trimmable typemap. This Java.Interop-Tests fixture uses that attribute + // to register native callbacks on a hand-written Java peer (an obsolete code + // path whose primary consumer, jnimarshalmethod-gen, was removed in + // dotnet/java-interop#1405). The trimmable typemap generator emits XA4251 + // when it encounters the attribute and instructs users to either avoid it or + // switch off the trimmable typemap. See https://github.com/dotnet/android/issues/11170. "Java.InteropTests.InvokeVirtualFromConstructorTests", - // JNI method remapping not supported in trimmable typemap - "Java.InteropTests.JniPeerMembersTests.ReplaceInstanceMethodName", - "Java.InteropTests.JniPeerMembersTests.ReplaceInstanceMethodWithStaticMethod", - "Java.InteropTests.JniPeerMembersTests.ReplacementTypeUsedForMethodLookup", - "Java.InteropTests.JniPeerMembersTests.ReplaceStaticMethodName", - - // Throwable subclass registration - "Java.InteropTests.JnienvTest.ActivatedDirectThrowableSubclassesShouldBeRegistered", - - // Instance identity after JNI round-trip - "Java.LangTests.ObjectTest.JnienvCreateInstance_RegistersMultipleInstances", - // Global ref leak when inflating custom views "Xamarin.Android.RuntimeTests.CustomWidgetTests.InflateCustomView_ShouldNotLeakGlobalRefs", }; diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/ConstructorActivationBase.java b/tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/ConstructorActivationBase.java new file mode 100644 index 00000000000..20283efcfc9 --- /dev/null +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/ConstructorActivationBase.java @@ -0,0 +1,24 @@ +package net.dot.android.test; + +public class ConstructorActivationBase { + public ConstructorActivationBase() {} + public ConstructorActivationBase(boolean value) {} + public ConstructorActivationBase(byte value) {} + public ConstructorActivationBase(char value) {} + public ConstructorActivationBase(short value) {} + public ConstructorActivationBase(int value) {} + public ConstructorActivationBase(long value) {} + public ConstructorActivationBase(float value) {} + public ConstructorActivationBase(double value) {} + public ConstructorActivationBase(int number, boolean flag, long longValue) {} + public ConstructorActivationBase(String value) {} + public ConstructorActivationBase(String first, String second) {} + public ConstructorActivationBase(String text, int number) {} + public ConstructorActivationBase(int[] value) {} + public ConstructorActivationBase(String label, int[] value) {} + public ConstructorActivationBase(boolean[] value) {} + public ConstructorActivationBase(byte[] value) {} + public ConstructorActivationBase(String[] value) {} + public ConstructorActivationBase(Object[] value) {} + public ConstructorActivationBase(int[][] value) {} +} diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/ExtendedValueProvider.java b/tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/ExtendedValueProvider.java new file mode 100644 index 00000000000..98a4579ec12 --- /dev/null +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/ExtendedValueProvider.java @@ -0,0 +1,5 @@ +package net.dot.android.test; + +public interface ExtendedValueProvider extends ValueProvider { + int getOtherValue(); +} diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/InterfaceMarshalling.java b/tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/InterfaceMarshalling.java new file mode 100644 index 00000000000..74d615918fb --- /dev/null +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/InterfaceMarshalling.java @@ -0,0 +1,17 @@ +package net.dot.android.test; + +public class InterfaceMarshalling { + public static ValueProvider getExtendedValueProviderAsValueProvider() { + return new ExtendedValueProvider() { + @Override + public int getValue() { + return 42; + } + + @Override + public int getOtherValue() { + return 84; + } + }; + } +} diff --git a/tests/api-compatibility/README.md b/tests/api-compatibility/README.md index e005d9b78cd..96e409d9e55 100644 --- a/tests/api-compatibility/README.md +++ b/tests/api-compatibility/README.md @@ -18,11 +18,10 @@ current version of an assembly. We could also called it the V2 assembly. ## Update Contract Assembly -To update the contract assembly, run the `UpdateMonoAndroidContract` target -and provide the `$(ContractAssembly)` MSBuild property. `$(ContractAssembly)` -should be the path to the new contract assembly to use: - - dotnet msbuild Xamarin.Android.sln -t:UpdateMonoAndroidContract "-p:ContractAssembly=C:/code/xamarin-android-backport/bin/Debug/lib/packs/Microsoft.Android.Ref.34/34.99.0/ref/net9.0/Mono.Android.dll" +The `UpdateMonoAndroidContract` MSBuild target has been removed. To update the +contract assembly, copy the reference assembly from the `ref` directory to +`tests/api-compatibility/reference/Mono.Android.dll` and compress it into +`Mono.Android.dll.zip`. Note: using the assembly in the `ref` directory means it has already had IL stripped and is just API. @@ -109,19 +108,10 @@ binding. In order to be able to do that we need to commit the `Mono.Android.dll assembly to our repository. We don't want to commit the "naked" assembly because it is quite large (around 30MB), -so we should first strip IL code from the assembly by using `cil-strip.exe`, for -example on macOS: - - -``` -$ mono bin/Debug/lib/xamarin.android/xbuild/Xamarin/Android/cil-strip.exe Mono.Android.dll Mono.Android-out.dll -Mono CIL Stripper - -26747392 Mono.Android.dll -19300864 Mono.Android-out.dll -``` +so we should use a reference assembly that already has IL stripped (from the `ref` +directory of the build output). -Rename `Mono.Android-out.dll` to `Mono.Android.dll`, then compress into a `.zip` file: +Compress into a `.zip` file: ``` zip Mono.Android.dll.zip Mono.Android.dll diff --git a/tests/api-compatibility/acceptable-breakages-vReference-net11.0.txt b/tests/api-compatibility/acceptable-breakages-vReference-net11.0.txt index 2b2518ec5e3..0feba3a1457 100644 --- a/tests/api-compatibility/acceptable-breakages-vReference-net11.0.txt +++ b/tests/api-compatibility/acceptable-breakages-vReference-net11.0.txt @@ -46,4 +46,5 @@ CannotRemoveAttribute : Attribute 'Android.Runtime.RequiresPermissionAttribute' MembersMustExist : Member 'public void Android.Telecom.CallControl.Answer(Android.Telecom.CallType, Java.Util.Concurrent.IExecutor, Android.OS.IOutcomeReceiver)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Android.Telecom.CallControl.RequestVideoState(Android.Telecom.CallType, Java.Util.Concurrent.IExecutor, Android.OS.IOutcomeReceiver)' does not exist in the implementation but it does exist in the contract. TypesMustExist : Type 'Android.Text.IInputType' does not exist in the implementation but it does exist in the contract. +TypesMustExist : Type 'Xamarin.Android.Net.AndroidClientHandler' does not exist in the implementation but it does exist in the contract. CannotRemoveBaseTypeOrInterface : Type 'Android.Views.InputMethods.EditorInfo' does not implement interface 'Android.Text.IInputType' in the implementation but it does in the contract. diff --git a/tests/api-compatibility/api-compatibility.targets b/tests/api-compatibility/api-compatibility.targets index 81637ca8c3e..56e63c75167 100644 --- a/tests/api-compatibility/api-compatibility.targets +++ b/tests/api-compatibility/api-compatibility.targets @@ -3,32 +3,4 @@ - - - - <_GenAPI>"$(XAPackagesDir)\microsoft.dotnet.genapi\$(MicrosoftDotNetApiCompatPackageVersion)\tools\net472\Microsoft.DotNet.GenAPI.exe" - <_ContractRefDll>$(MSBuildThisFileDirectory)\reference\Mono.Android.dll - <_ContractRefSrc>$(MSBuildThisFileDirectory)\reference\Mono.Android.dll.cs - - - - - <_ZipEntry Include="$(_ContractRefDll)" /> - <_ZipEntry Include="$(_ContractRefSrc)" /> - - -