Skip to content

[release/10.0.1xx] Support for exempting native libraries from JNI preload#10879

Open
jonathanpeppers wants to merge 4 commits intorelease/10.0.1xxfrom
dev/peppers/jnipreload-net10
Open

[release/10.0.1xx] Support for exempting native libraries from JNI preload#10879
jonathanpeppers wants to merge 4 commits intorelease/10.0.1xxfrom
dev/peppers/jnipreload-net10

Conversation

@jonathanpeppers
Copy link
Member

@jonathanpeppers jonathanpeppers commented Mar 3, 2026

Backport of: #10787
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 to true.

It is also possible to exempt individual libraries from preload by adding their name to the AndroidNativeLibraryNoJniPreload MSBuild item group, for instance:

<ItemGroup>
  <AndroidNativeLibraryNoJniPreload Include="libMyLibrary.so" />
</ItemGroup>

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 to `true`.

It is also possible to exempt individual libraries from preload by
adding their name to the `AndroidNativeLibraryNoJniPreload` MSBuild
item group, for instance:

    <ItemGroup>
      <AndroidNativeLibraryNoJniPreload Include="libMyLibrary.so" />
    </ItemGroup>
Copilot AI review requested due to automatic review settings March 3, 2026 15:47
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-out mechanism for the JNI native-library preload behavior introduced for .NET Android startup, allowing projects to exempt specific native libraries (or all of them) from being preloaded while still preserving required framework defaults.

Changes:

  • Introduces $(AndroidIgnoreAllJniPreload) and @(AndroidNativeLibraryNoJniPreload) to control JNI preload behavior from MSBuild.
  • Extends native application config generation to honor “always preload” and “never preload” library lists.
  • Adds a small native “test JNI library” plus new build/test infrastructure and NUnit tests validating preload behavior.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/native/native.targets Builds a new test JNI library as part of native runtime build orchestration.
src/native/common/test-jni-library/stub.cc Adds a minimal JNI_OnLoad implementation for preload testing.
src/native/common/test-jni-library/CMakeLists.txt CMake rules to build and place the test JNI library in test output.
src/native/CMakePresets.json.in Adds XA_TEST_OUTPUT_DIR to CMake cache variables.
src/native/CMakeLists.txt Requires XA_TEST_OUTPUT_DIR and wires the test JNI library subdirectory.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets Adds MSBuild property + item groups and passes new inputs to config generation.
src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs Adds helpers for minimum API lookup and normalizing native library names.
src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs Implements “ignore/always preload” decision logic for CoreCLR config generation.
src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs Implements the same “ignore/always preload” decision logic for MonoVM config generation.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/AndroidItem.cs Adds a ProjectTools 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 Adds parsing/validation helpers for JNI preload index data from generated native sources.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest3.cs New NUnit coverage for JNI preload inclusion/exclusion scenarios.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs Wires MSBuild inputs into the native config generator.
build-tools/xaprepare/xaprepare/xaprepare.targets Adds placeholder replacement for @TestOutputDirectory@.
build-tools/xaprepare/xaprepare/Steps/Step_GenerateFiles.cs Plumbs XA_TEST_OUTPUT_DIR placeholder into generated CMake presets.
build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in Adds KnownProperties.TestOutputDirectory default wiring.
build-tools/xaprepare/xaprepare/Application/KnownProperties.cs Adds TestOutputDirectory to known xaprepare properties.
Documentation/docs-mobile/building-apps/build-properties.md Documents AndroidIgnoreAllJniPreload.
Documentation/docs-mobile/building-apps/build-items.md Documents AndroidNativeLibraryNoJniPreload.

Comment on lines +208 to +211
int ExpectedEntryCount = ExpectedJniPreloadIndexStride * numberOfLibs;
foreach (EnvironmentHelper.JniPreloads preloads in allPreloads) {
Assert.IsTrue (preloads.IndexStride == (uint)ExpectedJniPreloadIndexStride, $"JNI preloads index stride should be {ExpectedJniPreloadIndexStride}, was {preloads.IndexStride} instead. Source file: {preloads.SourceFile}");
Assert.IsTrue (preloads.Entries.Count == ExpectedEntryCount, $"JNI preloads index entry count should be {ExpectedEntryCount}, was {preloads.Entries.Count} instead. Source file: {preloads.SourceFile}");
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Local variable ExpectedEntryCount uses PascalCase, but locals in this test suite use camelCase (e.g., shouldMarshalMethodsBeEnabled in BuildTest2). Rename to expectedEntryCount for consistency.

Suggested change
int ExpectedEntryCount = ExpectedJniPreloadIndexStride * numberOfLibs;
foreach (EnvironmentHelper.JniPreloads preloads in allPreloads) {
Assert.IsTrue (preloads.IndexStride == (uint)ExpectedJniPreloadIndexStride, $"JNI preloads index stride should be {ExpectedJniPreloadIndexStride}, was {preloads.IndexStride} instead. Source file: {preloads.SourceFile}");
Assert.IsTrue (preloads.Entries.Count == ExpectedEntryCount, $"JNI preloads index entry count should be {ExpectedEntryCount}, was {preloads.Entries.Count} instead. Source file: {preloads.SourceFile}");
int expectedEntryCount = ExpectedJniPreloadIndexStride * numberOfLibs;
foreach (EnvironmentHelper.JniPreloads preloads in allPreloads) {
Assert.IsTrue (preloads.IndexStride == (uint)ExpectedJniPreloadIndexStride, $"JNI preloads index stride should be {ExpectedJniPreloadIndexStride}, was {preloads.IndexStride} instead. Source file: {preloads.SourceFile}");
Assert.IsTrue (preloads.Entries.Count == expectedEntryCount, $"JNI preloads index entry count should be {expectedEntryCount}, was {preloads.Entries.Count} instead. Source file: {preloads.SourceFile}");

Copilot uses AI. Check for mistakes.
public PackageNamingPolicy PackageNamingPolicy { get; set; }
public List<ITaskItem> NativeLibraries { get; set; } = [];
public ICollection<ITaskItem>? NativeLibrariesNoJniPreload { get; set; }
public ICollection<ITaskItem>? NativeLibrarysAlwaysJniPreload { get; set; }
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Property name NativeLibrarysAlwaysJniPreload is misspelled ("Librarys"). This propagates to callers and makes the API harder to read/search. Rename to NativeLibrariesAlwaysJniPreload (and update all assignments/usages accordingly).

Suggested change
public ICollection<ITaskItem>? NativeLibrarysAlwaysJniPreload { get; set; }
public ICollection<ITaskItem>? NativeLibrariesAlwaysJniPreload { get; set; }

Copilot uses AI. Check for mistakes.
public PackageNamingPolicy PackageNamingPolicy { get; set; }
public List<ITaskItem> NativeLibraries { get; set; } = [];
public ICollection<ITaskItem>? NativeLibrariesNoJniPreload { get; set; }
public ICollection<ITaskItem>? NativeLibrarysAlwaysJniPreload { get; set; }
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Property name NativeLibrarysAlwaysJniPreload is misspelled ("Librarys"). Rename to NativeLibrariesAlwaysJniPreload to keep naming consistent and avoid spreading the typo to other components.

Suggested change
public ICollection<ITaskItem>? NativeLibrarysAlwaysJniPreload { get; set; }
public ICollection<ITaskItem>? NativeLibrariesAlwaysJniPreload { get; set; }

Copilot uses AI. Check for mistakes.
BundledAssemblyNameWidth = assemblyNameWidth,
NativeLibraries = uniqueNativeLibraries,
NativeLibrariesNoJniPreload = NativeLibrariesNoJniPreload,
NativeLibrarysAlwaysJniPreload = NativeLibrariesAlwaysJniPreload,
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Object initializer uses NativeLibrarysAlwaysJniPreload (typo: "Librarys"). Rename the corresponding generator property to NativeLibrariesAlwaysJniPreload and update these assignments.

Suggested change
NativeLibrarysAlwaysJniPreload = NativeLibrariesAlwaysJniPreload,
NativeLibrariesAlwaysJniPreload = NativeLibrariesAlwaysJniPreload,

Copilot uses AI. Check for mistakes.
var nameMutations = new List<string> ();
var dsoNamesBlob = new LlvmIrStringBlob ();
int nameMutationsCount = -1;
ICollection<string> ignorePreload = MakeJniPreloadIgnoreCollection (Log, NativeLibrarysAlwaysJniPreload, NativeLibrariesNoJniPreload);
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DsoCacheJniPreloadIgnore is no longer consulted after switching to MakeJniPreloadIgnoreCollection/ShouldIgnoreForJniPreload (it’s now dead code). Either remove it, or seed the ignore set with it so the hardcoded safety list remains effective even if MSBuild doesn’t pass NativeLibrariesNoJniPreload.

Suggested change
ICollection<string> ignorePreload = MakeJniPreloadIgnoreCollection (Log, NativeLibrarysAlwaysJniPreload, NativeLibrariesNoJniPreload);
ICollection<string> ignorePreload = MakeJniPreloadIgnoreCollection (Log, NativeLibrarysAlwaysJniPreload, NativeLibrariesNoJniPreload);
if (DsoCacheJniPreloadIgnore != null) {
foreach (string name in DsoCacheJniPreloadIgnore) {
ignorePreload.Add (name);
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +710 to +716
ulong GetSymbolValueAsUInt64 (NativeAssemblyParser.AssemblerSymbol symbol)
{
NativeAssemblyParser.AssemblerSymbolItem item = symbol.Contents[0];
string[] field = GetField (envFile.Path, parser.SourceFilePath, item.Contents, item.LineNumber);
Assert.IsTrue (expectedUInt64Types.Contains (field [0]), $"Unexpected 64-bit integer field type for symbol {symbol.Name} in '{envFile.Path}:{item.LineNumber}': {field [0]}");
return ConvertFieldToUInt64 (DsoJniPreloadsIdxStrideSymbolName, envFile.Path, parser.SourceFilePath, item.LineNumber, field[1]);
}
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In GetSymbolValueAsUInt64, the field name passed to ConvertFieldToUInt64 is DsoJniPreloadsIdxStrideSymbolName, but this helper is used for the count symbol too. This makes assertion messages misleading; pass the appropriate symbol name (e.g., symbol.Name or DsoJniPreloadsIdxCountSymbolName).

Copilot uses AI. Check for mistakes.
from being preloaded at application startup. By default, all such libraries
will be loaded by the runtime early during application startup in order to
assure their proper initialization. However, in some cases it might not be the
desired behavior and this property allows to effectively disable this behavior.
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grammar: “this property allows to effectively disable this behavior” reads awkwardly. Consider rephrasing to “allows effectively disabling this behavior” (or similar) for clarity.

Suggested change
desired behavior and this property allows to effectively disable this behavior.
desired behavior and this property allows you to effectively disable it.

Copilot uses AI. Check for mistakes.
jonathanpeppers and others added 2 commits March 3, 2026 12:51
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI added a commit that referenced this pull request Mar 3, 2026
- Fix grammar in build-items.md: "on individual basis" → "on an individual basis"
- Fix grammar in build-properties.md: "allows to effectively disable this behavior" → "allows you to effectively disable it"
- Fix typo NativeLibrarysAlwaysJniPreload → NativeLibrariesAlwaysJniPreload

Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com>
@jonathanpeppers
Copy link
Member Author

This has test failures like:

Expected the '.byte' field type in file 'C:\a_work\1\a\TestRelease\03-03_23.16.24\temp\NativeLibraryJniPreloadDefaultsWorkCoreCLR\obj\Release\android\environment.arm64-v8a.s:53': .word. File generated from 'C:\a_work\1\a\TestRelease\03-03_23.16.24\temp\NativeLibraryJniPreloadDefaultsWorkCoreCLR\obj\Release\android\environment.arm64-v8a.ll'
String lengths are both 5. Strings differ at index 1.
Expected: ".byte"
But was:  ".word"
------------^

jonathanpeppers added a commit that referenced this pull request Mar 4, 2026
Context: #10879 (review)

* Fix typos and documentation wording from PR #10879 review
* Fix grammar in build-items.md: "on individual basis" → "on an individual basis"
* Fix grammar in build-properties.md: "allows to effectively disable this behavior" → "allows you to effectively disable it"
* Fix typo NativeLibrarysAlwaysJniPreload → NativeLibrariesAlwaysJniPreload

Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants