Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,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 = () => """
<replacements>
<replace-type from="android/app/Activity" to="example/RemapActivity" />
<replace-method
source-type="example/RemapActivity"
source-method-name="onCreate"
target-type="example/RemapActivity"
target-method-name="onMyCreate"
target-method-instance-to-static="false" />
</replacements>
""",
},
},
};
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);
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 — Consider asserting that _GenerateAndroidRemapNativeCode was actually skipped during the incremental build (e.g., b.Output.AssertTargetIsSkipped ("_GenerateAndroidRemapNativeCode")). This would directly verify the MSBuild target-skip behavior, making the test more diagnostic if it ever fails — you'd know whether the target ran unnecessarily vs. ran but produced wrong output.

Rule: Test assertions must be specific

}
}

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<string, DateTime> GetJniRemappingSourceTimestamps (XamarinAndroidApplicationProject proj, ProjectBuilder builder)
{
string objDirPath = Path.Combine (Root, builder.ProjectDirectory, proj.IntermediateOutputPath, "android");
var timestamps = new Dictionary<string, DateTime> (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<string, DateTime> 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)
{
Expand Down
10 changes: 6 additions & 4 deletions src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -1646,21 +1646,22 @@ because xbuild doesn't support framework reference assemblies.
<_GenerateAndroidRemapNativeCodeDependsOn>
_ConvertAndroidMamMappingFileToXml;
_CollectAndroidRemapMembers;
_PrepareAndroidRemapNativeAssemblySources
_PrepareAndroidRemapNativeAssemblySources;
_GetGeneratePackageManagerJavaInputs
</_GenerateAndroidRemapNativeCodeDependsOn>
</PropertyGroup>

<Target Name="_GenerateAndroidRemapNativeCode"
DependsOnTargets="$(_GenerateAndroidRemapNativeCodeDependsOn)"
Condition=" '@(_AndroidRemapMembers->Count())' != '0' "
Inputs="$(_AndroidBuildPropertiesCache);@(_AndroidMSBuildAllProjects);$(_XARemapMembersFilePath)"
Outputs="@(_AndroidRemapAssemblySource)">
Comment thread
simonrozsival marked this conversation as resolved.
Inputs="@(_GeneratePackageManagerJavaInputs);$(_XARemapMembersFilePath)"
Outputs="$(_AndroidStampDirectory)_GenerateAndroidRemapNativeCode.stamp">
<GenerateJniRemappingNativeCode
OutputDirectory="$(_NativeAssemblySourceDir)"
RemappingXmlFilePath="$(_XARemapMembersFilePath)"
SupportedAbis="@(_BuildTargetAbis)"
/>
<Touch Files="@(_AndroidRemapAssemblySource)" />
<Touch Files="$(_AndroidStampDirectory)_GenerateAndroidRemapNativeCode.stamp" AlwaysCreate="True" />
</Target>

<Target Name="_RunAotForAllRIDs"
Expand Down Expand Up @@ -1696,6 +1697,7 @@ because xbuild doesn't support framework reference assemblies.
<Target Name="_GetGeneratePackageManagerJavaInputs">
<ItemGroup>
<_GeneratePackageManagerJavaInputs Include="@(_GenerateJavaStubsInputs)" />
<_GeneratePackageManagerJavaInputs Include="$(_XARemapMembersFilePath)" Condition=" '@(_AndroidRemapMembers->Count())' != '0' " />
</ItemGroup>
</Target>

Expand Down