Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android.
<!-- Default property values for NativeAOT -->
<PropertyGroup>
<_AndroidRuntimePackRuntime>NativeAOT</_AndroidRuntimePackRuntime>
<_AndroidUseWorkloadNativeLinker Condition=" '$(_AndroidUseWorkloadNativeLinker)' == '' ">true</_AndroidUseWorkloadNativeLinker>
<_AndroidJcwCodegenTarget Condition=" '$(_AndroidJcwCodegenTarget)' == '' ">JavaInterop1</_AndroidJcwCodegenTarget>
<_AndroidTypeMapImplementation Condition=" '$(_AndroidTypeMapImplementation)' == '' ">managed</_AndroidTypeMapImplementation>
<!-- .NET SDK gives: error NETSDK1191: A runtime identifier for the property 'PublishAot' couldn't be inferred. Specify a rid explicitly. -->
Expand Down Expand Up @@ -299,7 +300,6 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android.
Outputs="$(NativeOutputPath)$(NativeBinaryPrefix)$(TargetName).so">
<PropertyGroup>
<_AndroidNativeAotSharedLibrary>$(NativeOutputPath)$(NativeBinaryPrefix)$(TargetName).so</_AndroidNativeAotSharedLibrary>
<_AndroidUseWorkloadNativeLinker Condition=" '$(_AndroidUseWorkloadNativeLinker)' == '' ">false</_AndroidUseWorkloadNativeLinker>
</PropertyGroup>
Comment thread
sbomer marked this conversation as resolved.

<!-- Resolve the runtime pack native directory for workload-provided linker dependencies.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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";
Expand All @@ -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.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,53 @@ namespace Xamarin.Android.Build.Tests
public class NativeAotBuildTests : BaseTest
{
[Test]
public void BuildNativeAot_WithoutNdk_Fails ()
public void BuildNativeAot_WithoutNdk ()
{
var proj = new XamarinAndroidApplicationProject {
IsRelease = true,
};
proj.SetRuntime (AndroidRuntime.NativeAOT);

using var builder = CreateApkBuilder ();
builder.ThrowOnBuildFailure = false;
Assert.IsFalse (
builder.Build (proj, parameters: new [] { "_SkipNdkResolution=true" }),
"Build should have failed without NDK."
Assert.IsTrue (
builder.Build (proj),
"Build should succeed without NDK (workload linker is the default)."
);
}

[Test]
public void BuildNativeAot_WithWorkloadLinker_WithoutNdk ()
public void BuildNativeAot_WithNdkLinker ()
{
var proj = new XamarinAndroidApplicationProject {
IsRelease = true,
};
proj.SetRuntime (AndroidRuntime.NativeAOT);
proj.SetProperty ("_SkipNdkResolution", "false");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 💡 TestingSetRuntime(NativeAOT) now sets _SkipNdkResolution=true, but this test immediately overrides it to false. This is correct and necessary — the test exercises the NDK linker path, which requires real NDK resolution. Just noting that this test will fail on CI agents without an NDK installed, which appears to be fine for this repo's CI setup.

Rule: Deterministic test data


using var builder = CreateApkBuilder ();
Assert.IsTrue (
builder.Build (proj, parameters: new [] {
"_SkipNdkResolution=true",
"_AndroidUseWorkloadNativeLinker=true",
"_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 succeed with workload linker and no NDK."
"Build should fail without NDK when workload linker is disabled."
Comment on lines +47 to +61
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 💡 Testing — This test only asserts that the build fails, but doesn't verify why it fails. If a future regression causes the build to fail for an unrelated reason (e.g. a missing runtime pack), this test would still pass, masking the real issue.

Consider asserting on a specific error message or error code in the build output, e.g.:

Assert.IsFalse (
    builder.Build (proj, parameters: new [] {
        "_AndroidUseWorkloadNativeLinker=false",
    }),
    "Build should fail without NDK when workload linker is disabled."
);
StringAssertEx.Contains ("expected-error-string", builder.LastBuildOutput, "Expected NDK-related error.");

Rule: Test assertions must be specific

);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public static void SetRuntime (this XamarinAndroidApplicationProject project, An
return;
}
project.SetPublishAot (true);
project.SetProperty ("_SkipNdkResolution", "true");
EnablePreviewFeaturesIfNeeded (project, runtime);
Comment thread
sbomer marked this conversation as resolved.
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2860,7 +2860,7 @@ because xbuild doesn't support framework reference assemblies.
<PropertyGroup>
<_ProjectAndroidManifest>$(ProjectDir)$(AndroidManifest)</_ProjectAndroidManifest>
<_NdkRequired Condition="'$(EnableLLVM)' == 'True'">true</_NdkRequired>
<_NdkRequired Condition="'$(PublishAot)' == 'true'">true</_NdkRequired>
<_NdkRequired Condition="'$(PublishAot)' == 'true' and '$(_AndroidUseWorkloadNativeLinker)' != 'true'">true</_NdkRequired>
<_NdkRequired Condition="'$(_NdkRequired)' == ''">false</_NdkRequired>
</PropertyGroup>
<Error Text="AndroidManifest file does not exist" Condition="'$(_ProjectAndroidManifest)'!='' And !Exists ('$(_ProjectAndroidManifest)')"/>
Expand Down