diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs index 49402865722..4b3952d8141 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs @@ -22,6 +22,7 @@ sealed class Config public string? AssemblerPath; public string? AssemblerOptions; public string? InputSource; + public string? OutputFile; } [Required] @@ -43,6 +44,14 @@ public override System.Threading.Tasks.Task RunTaskAsync () void RunAssembler (Config config) { + if (config.OutputFile is not null && config.InputSource is not null && File.Exists (config.OutputFile)) { + string sourceFile = Path.Combine (WorkingDirectory, Path.GetFileName (config.InputSource)); + if (File.Exists (sourceFile) && File.GetLastWriteTimeUtc (config.OutputFile) >= File.GetLastWriteTimeUtc (sourceFile)) { + LogDebugMessage ($"[LLVM llc] Skipping '{Path.GetFileName (config.InputSource)}' because '{Path.GetFileName (config.OutputFile)}' is up to date"); + return; + } + } + var stdout_completed = new ManualResetEvent (false); var stderr_completed = new ManualResetEvent (false); var psi = new ProcessStartInfo () { @@ -118,10 +127,13 @@ IEnumerable GetAssemblerConfigs () string executableDir = Path.GetDirectoryName (llcPath); string executableName = MonoAndroidHelper.GetExecutablePath (executableDir, Path.GetFileName (llcPath)); + string outputFilePath = Path.Combine (WorkingDirectory, sourceFile.Replace (".ll", ".o")); + yield return new Config { InputSource = item.ItemSpec, AssemblerPath = Path.Combine (executableDir, executableName), AssemblerOptions = $"{assemblerOptions} -o={outputFile} {QuoteFileName (sourceFile)}", + OutputFile = outputFilePath, }; } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs index 8e3600f20dd..607a832afb9 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs @@ -1289,6 +1289,38 @@ public void GenerateJavaStubsAndAssembly ([Values] bool isRelease, [Values] Andr } } + [Test] + public void CompileNativeAssemblySourcesSkipsUnchangedFiles ([Values (AndroidRuntime.CoreCLR)] AndroidRuntime runtime) + { + if (IgnoreUnsupportedConfiguration (runtime, release: false)) { + return; + } + + var proj = new XamarinAndroidApplicationProject (); + proj.SetRuntime (runtime); + + string abi = "arm64-v8a"; + proj.SetRuntimeIdentifier (abi); + + using (var b = CreateApkBuilder ()) { + b.Verbosity = LoggerVerbosity.Detailed; + Assert.IsTrue (b.Build (proj), "first build should have succeeded."); + + // Modify MainActivity to trigger recompilation of typemap sources + proj.MainActivity = proj.DefaultMainActivity + Environment.NewLine + "// test comment"; + proj.Touch ("MainActivity.cs"); + Assert.IsTrue (b.Build (proj), "second build should have succeeded."); + + Assert.IsFalse (b.Output.IsTargetSkipped ("_CompileNativeAssemblySources"), "`_CompileNativeAssemblySources` should *not* be skipped!"); + + // At least one .ll file should have been skipped as up to date (e.g., environment.arm64-v8a.ll) + Assert.IsTrue ( + StringAssertEx.ContainsRegex (@"\[LLVM llc\] Skipping.*up to date", b.LastBuildOutput), + "Expected at least one .ll file to be skipped as up to date" + ); + } + } + readonly string [] ExpectedAssemblyFiles = new [] { Path.Combine ("android", "environment.@ABI@.o"), Path.Combine ("android", "environment.@ABI@.ll"),