Support for exempting native libraries from JNI preload#10787
Support for exempting native libraries from JNI preload#10787
Conversation
| <_AndroidStripNativeLibraries Condition=" '$(AndroidStripNativeLibraries)' != '' And '$(AndroidStripNativeLibraries)' == 'true' ">true</_AndroidStripNativeLibraries> | ||
| <_AndroidStripNativeLibraries Condition=" '$(_AndroidStripNativeLibraries)' != 'true' ">false</_AndroidStripNativeLibraries> | ||
|
|
||
| <AndroidIgnoreAllJniPreload Condition=" '$(AndroidIgnoreAllJniPreload)' == '' ">false</AndroidIgnoreAllJniPreload> |
There was a problem hiding this comment.
How common do you think people would have to do this? Should it be a public, documented property?
Or is it an edge-case, and $(_AndroidIgnoreAllJniPreload) would be fine?
There was a problem hiding this comment.
I don't know, to be honest. The original scenario it addresses appears to be quite unusual, but with the dependency hell that the AndroidX ecosystem is, who knows? I would make it a documented and supported property, just to be safe and not let people without easy choice.
d399967 to
afa6525
Compare
f60c1c8 to
fe5dc19
Compare
Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
MonoVM tests are weirdly broken (appconfig reads incorrectly, need to investigate)
6df6659 to
2d3b823
Compare
There was a problem hiding this comment.
Pull request overview
Adds an opt-out mechanism for JNI native library preloading, including per-library exemptions and a global “ignore all” switch, with supporting build/test infrastructure and documentation updates.
Changes:
- Introduces
$(AndroidIgnoreAllJniPreload)and@(AndroidNativeLibraryNoJniPreload)integration into native application config generation. - Adds a small “test JNI library” build path to enable preload-related tests.
- Expands test utilities and adds new tests validating JNI preload inclusion/exclusion behavior.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/native/native.targets | Adds targets to configure/build a test JNI library as part of runtime build. |
| src/native/common/test-jni-library/stub.cc | Adds minimal JNI_OnLoad stub for preload tests. |
| src/native/common/test-jni-library/CMakeLists.txt | Builds the test JNI shared library and outputs to test directory. |
| src/native/CMakePresets.json.in | Adds XA_TEST_OUTPUT_DIR to presets cache variables. |
| src/native/CMakeLists.txt | Wires BUILD_TEST_JNI_LIBRARY flow and requires XA_TEST_OUTPUT_DIR. |
| src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets | Defines new property and item groups; passes new item lists into generator task. |
| src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs | Adds helper to normalize native library names. |
| src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs | Implements ignore/always-preload resolution for JNI preload list generation (CoreCLR path). |
| src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs | Applies same JNI preload ignore logic (MonoVM path). |
| src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs | Adds task parameters for preload ignore/always-preload lists. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/AndroidItem.cs | Adds test project item wrapper for AndroidNativeLibraryNoJniPreload. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/AndroidBuildActions.cs | Adds build action constant for AndroidNativeLibraryNoJniPreload. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs | Extends parsing helpers to read JNI preload index data from generated native sources. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest3.cs | Adds tests validating JNI preload inclusion/exemption and deduplication. |
| build-tools/xaprepare/xaprepare/xaprepare.targets | Adds placeholder replacement for TestOutputDirectory. |
| build-tools/xaprepare/xaprepare/Steps/Step_GenerateFiles.cs | Plumbs XA_TEST_OUTPUT_DIR into generated CMake presets. |
| build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in | Adds default for TestOutputDirectory. |
| build-tools/xaprepare/xaprepare/Application/KnownProperties.cs | Adds TestOutputDirectory known property. |
| Documentation/docs-mobile/building-apps/build-properties.md | Documents AndroidIgnoreAllJniPreload. |
| Documentation/docs-mobile/building-apps/build-items.md | Documents AndroidNativeLibraryNoJniPreload. |
| <Target Name="_ConfigureAndBuildTestJniLibrary" | ||
| Condition="'$(CMakeRuntimeFlavor)' != 'NativeAOT' " | ||
| DependsOnTargets="_PrepareCommonProperties;_ConfigureTestJniLibraryInputsAndOutputs" | ||
| Inputs="@_TestJniLibraryInput)" | ||
| Outputs="@(_TestJniLibraryOutput)"> | ||
| <ItemGroup> | ||
| <_ConfigureTestJniLibraryCommands Include="@(_TestJniLibraryAbi)"> | ||
| <Command>$(CmakePath)</Command> | ||
| <Arguments>-DOUTPUT_PATH="$(OutputPath.TrimEnd('\'))" -DRUNTIME_FLAVOR="$(CMakeRuntimeFlavor)" --preset default-release-%(_TestJniLibraryAbi.Identity) -DBUILD_TEST_JNI_LIBRARY=ON "$(MSBuildThisFileDirectory)"</Arguments> | ||
| <WorkingDirectory>$(FlavorIntermediateOutputPath)%(_TestJniLibraryAbi.AndroidRID)-test-jni-library</WorkingDirectory> | ||
| </_ConfigureTestJniLibraryCommands> | ||
| </ItemGroup> |
There was a problem hiding this comment.
Inputs="@_TestJniLibraryInput)" has an extra ) and is missing MSBuild item list syntax, which will break incremental build tracking for this target. Also, WorkingDirectory paths at lines 141 and 150 appear to be missing a directory separator after $(FlavorIntermediateOutputPath), producing an invalid path (note MakeDir uses $(FlavorIntermediateOutputPath)\... earlier). Fix the Inputs attribute to use the correct @(...) form, and make WorkingDirectory use the same path joining convention as the created directory.
| <Exec | ||
| Command="$(NinjaPath) -v" | ||
| WorkingDirectory="$(FlavorIntermediateOutputPath)%(_TestJniLibraryAbi.AndroidRID)-test-jni-library" /> |
There was a problem hiding this comment.
Inputs="@_TestJniLibraryInput)" has an extra ) and is missing MSBuild item list syntax, which will break incremental build tracking for this target. Also, WorkingDirectory paths at lines 141 and 150 appear to be missing a directory separator after $(FlavorIntermediateOutputPath), producing an invalid path (note MakeDir uses $(FlavorIntermediateOutputPath)\... earlier). Fix the Inputs attribute to use the correct @(...) form, and make WorkingDirectory use the same path joining convention as the created directory.
| } | ||
|
|
||
| if (!String.Equals (Path.GetExtension (ret), ".so", StringComparison.OrdinalIgnoreCase)) { | ||
| return "${ret}.so"; |
There was a problem hiding this comment.
This returns the literal string "${ret}.so" instead of appending the .so extension to ret. Use C# string interpolation ($"{ret}.so") or concatenation (ret + ".so") so callers get the correct normalized filename.
| return "${ret}.so"; | |
| return $"{ret}.so"; |
| NativeLibraries="@(AndroidNativeLibrary);@(EmbeddedNativeLibrary);@(FrameworkNativeLibrary)" | ||
| NativeLibraries="@(_AllNativeLibraries)" | ||
| NativeLibrariesNoJniPreload="@(_AndroidNativeLibraryNeverJniPreload);@(AndroidNativeLibraryNoJniPreload)" | ||
| NativeLibrarysAlwaysJniPreload="@(_AndroidNativeLibraryAlwaysJniPreload)" |
There was a problem hiding this comment.
The parameter name NativeLibrarysAlwaysJniPreload has a spelling/grammar error (“Librarys” vs “Libraries”). Since this name flows through MSBuild task parameters and generator state, consider renaming consistently everywhere (targets, task properties, state objects) to NativeLibrariesAlwaysJniPreload to avoid permanently cementing a typo into the public-ish build/task surface.
| NativeLibrarysAlwaysJniPreload="@(_AndroidNativeLibraryAlwaysJniPreload)" | |
| NativeLibrariesAlwaysJniPreload="@(_AndroidNativeLibraryAlwaysJniPreload)" |
| Some framework libraries which must be loaded at application startup will not | ||
| be affected if included in this item group. | ||
|
|
||
| See also [`$(AndroidIgnoreAllJniPreload)`](build-items.md#androidignorealljnipreload) |
There was a problem hiding this comment.
This link points back to build-items.md, but $(AndroidIgnoreAllJniPreload) is documented in build-properties.md (added in this PR). Update the link target to the correct document/anchor so readers can actually navigate to the property description.
| See also [`$(AndroidIgnoreAllJniPreload)`](build-items.md#androidignorealljnipreload) | |
| See also [`$(AndroidIgnoreAllJniPreload)`](build-properties.md#androidignorealljnipreload) |
|
|
||
| NativeAssemblyParser.AssemblerSymbol dsoJniPreloadsIdxCount = GetNonEmptyRequiredSymbol (parser, envFile, DsoJniPreloadsIdxCountSymbolName); | ||
| ulong preloadsCount = GetSymbolValueAsUInt64 (dsoJniPreloadsIdxCount); | ||
| Assert.IsTrue (preloadsCount > 0, $"Symbol {dsoJniPreloadsIdxStride.Name} must have value larger than 0."); |
There was a problem hiding this comment.
The assertion message references dsoJniPreloadsIdxStride.Name, but the check is validating the count symbol. This will produce misleading failure output when the count is 0. Use dsoJniPreloadsIdxCount.Name (or the DsoJniPreloadsIdxCountSymbolName constant) in the message.
| Assert.IsTrue (preloadsCount > 0, $"Symbol {dsoJniPreloadsIdxStride.Name} must have value larger than 0."); | |
| Assert.IsTrue (preloadsCount > 0, $"Symbol {dsoJniPreloadsIdxCount.Name} must have value larger than 0."); |
| static string ReadStringBlob (EnvironmentFile envFile, NativeAssemblyParser.AssemblerSymbol contentsSymbol, NativeAssemblyParser parser) | ||
| { | ||
| NativeAssemblyParser.AssemblerSymbolItem contentsItem = contentsSymbol.Contents[0]; | ||
| string[] field = GetField (envFile.Path, parser.SourceFilePath, contentsItem.Contents, contentsItem.LineNumber);; |
There was a problem hiding this comment.
There’s a double semicolon (;;) at the end of the line. Removing the extra ; will avoid noise and keep the file consistent with typical style.
| string[] field = GetField (envFile.Path, parser.SourceFilePath, contentsItem.Contents, contentsItem.LineNumber);; | |
| string[] field = GetField (envFile.Path, parser.SourceFilePath, contentsItem.Contents, contentsItem.LineNumber); |
Fixes: #10617
Context: cba39dc
cba39dc introduced support for preloading of JNI native libraries at
application startup. However, it appears that in some scenarios this
behavior isn't desired.
This PR introduces a mechanism which allows exempting some or all (with
exception of the BCL libraries) libraries from the preload mechanism.
In order to not preload any JNI libraries it's now possible to set the
$(AndroidIgnoreAllJniPreload)MSBuild property totrue.It is also possible to exempt individual libraries from preload by
adding their name to the
AndroidNativeLibraryNoJniPreloadMSBuilditem group, for instance: