Skip to content

Support for exempting native libraries from JNI preload#10787

Open
grendello wants to merge 22 commits intomainfrom
dev/grendel/jni-preload-exemption
Open

Support for exempting native libraries from JNI preload#10787
grendello wants to merge 22 commits intomainfrom
dev/grendel/jni-preload-exemption

Conversation

@grendello
Copy link
Contributor

@grendello grendello commented Feb 9, 2026

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>

<_AndroidStripNativeLibraries Condition=" '$(AndroidStripNativeLibraries)' != '' And '$(AndroidStripNativeLibraries)' == 'true' ">true</_AndroidStripNativeLibraries>
<_AndroidStripNativeLibraries Condition=" '$(_AndroidStripNativeLibraries)' != 'true' ">false</_AndroidStripNativeLibraries>

<AndroidIgnoreAllJniPreload Condition=" '$(AndroidIgnoreAllJniPreload)' == '' ">false</AndroidIgnoreAllJniPreload>
Copy link
Member

Choose a reason for hiding this comment

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

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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.

@grendello grendello force-pushed the dev/grendel/jni-preload-exemption branch 4 times, most recently from d399967 to afa6525 Compare February 13, 2026 13:00
@grendello grendello force-pushed the dev/grendel/jni-preload-exemption branch 2 times, most recently from f60c1c8 to fe5dc19 Compare February 18, 2026 15:17
@grendello grendello force-pushed the dev/grendel/jni-preload-exemption branch from 6df6659 to 2d3b823 Compare February 18, 2026 15:52
@grendello grendello marked this pull request as ready for review February 18, 2026 15:52
Copilot AI review requested due to automatic review settings February 18, 2026 15:52
Copy link

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 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.

Comment on lines +132 to +143
<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>
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +148 to +150
<Exec
Command="$(NinjaPath) -v"
WorkingDirectory="$(FlavorIntermediateOutputPath)%(_TestJniLibraryAbi.AndroidRID)-test-jni-library" />
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
}

if (!String.Equals (Path.GetExtension (ret), ".so", StringComparison.OrdinalIgnoreCase)) {
return "${ret}.so";
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
return "${ret}.so";
return $"{ret}.so";

Copilot uses AI. Check for mistakes.
NativeLibraries="@(AndroidNativeLibrary);@(EmbeddedNativeLibrary);@(FrameworkNativeLibrary)"
NativeLibraries="@(_AllNativeLibraries)"
NativeLibrariesNoJniPreload="@(_AndroidNativeLibraryNeverJniPreload);@(AndroidNativeLibraryNoJniPreload)"
NativeLibrarysAlwaysJniPreload="@(_AndroidNativeLibraryAlwaysJniPreload)"
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
NativeLibrarysAlwaysJniPreload="@(_AndroidNativeLibraryAlwaysJniPreload)"
NativeLibrariesAlwaysJniPreload="@(_AndroidNativeLibraryAlwaysJniPreload)"

Copilot uses AI. Check for mistakes.
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)
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
See also [`$(AndroidIgnoreAllJniPreload)`](build-items.md#androidignorealljnipreload)
See also [`$(AndroidIgnoreAllJniPreload)`](build-properties.md#androidignorealljnipreload)

Copilot uses AI. Check for mistakes.

NativeAssemblyParser.AssemblerSymbol dsoJniPreloadsIdxCount = GetNonEmptyRequiredSymbol (parser, envFile, DsoJniPreloadsIdxCountSymbolName);
ulong preloadsCount = GetSymbolValueAsUInt64 (dsoJniPreloadsIdxCount);
Assert.IsTrue (preloadsCount > 0, $"Symbol {dsoJniPreloadsIdxStride.Name} must have value larger than 0.");
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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.");

Copilot uses AI. Check for mistakes.
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);;
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
string[] field = GetField (envFile.Path, parser.SourceFilePath, contentsItem.Contents, contentsItem.LineNumber);;
string[] field = GetField (envFile.Path, parser.SourceFilePath, contentsItem.Contents, contentsItem.LineNumber);

Copilot uses AI. Check for mistakes.
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.

.NET 10 breaks library loading semantics

2 participants

Comments