From 64077229cb330ec6a9d66eba05aa00bc043ee66d Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 7 Aug 2023 20:19:08 +0200 Subject: [PATCH] [net8.0] [msbuild] Re-aot referencing assemblies. Fixes #17708. (#18518) If an assembly changes, then we must AOT compile that assembly again (which we already did), in addition to any assembly that references the modified assembly (which we didn't do). So rework the AOTCompile target: remove the Inputs and Outputs (because the dependency tracking is too complicated for MSBuild to resolve), and instead move the logic to detect if an assembly must be AOT-compiled again into the AOTCompile task. This is a port of the [original fix for main][1] to .NET 8, where we have to take the dedup assembly into account too: if any assembly has changed, then we must re-aot the dedup assembly as well. Fixes https://github.com/xamarin/xamarin-macios/issues/17708. [1]: https://github.com/xamarin/xamarin-macios/pull/18509 --------- Co-authored-by: Alex Soto --- dotnet/targets/Xamarin.Shared.Sdk.targets | 3 +- .../MSBStrings.resx | 29 ++++ .../Tasks/AOTCompileTaskBase.cs | 145 ++++++++++++++++-- .../AppDelegate.cs | 18 +++ .../Library/Library.cs | 15 ++ .../Library/MacCatalyst/Library.csproj | 7 + .../Library/MacCatalyst/Makefile | 1 + .../Library/Makefile | 2 + .../Library/iOS/Library.csproj | 7 + .../Library/iOS/Makefile | 1 + .../Library/macOS/Library.csproj | 7 + .../Library/macOS/Makefile | 1 + .../Library/shared.csproj | 12 ++ .../Library/shared.mk | 2 + .../Library/tvOS/Library.csproj | 7 + .../Library/tvOS/Makefile | 1 + .../MacCatalyst/Makefile | 1 + .../RebuildTestAppWithLibraryReference.csproj | 7 + .../Makefile | 2 + .../iOS/Makefile | 1 + .../RebuildTestAppWithLibraryReference.csproj | 7 + .../macOS/Makefile | 1 + .../RebuildTestAppWithLibraryReference.csproj | 7 + .../shared.csproj | 19 +++ .../shared.mk | 2 + .../tvOS/Makefile | 1 + .../RebuildTestAppWithLibraryReference.csproj | 7 + tests/dotnet/UnitTests/RebuildTest.cs | 39 +++++ tests/dotnet/UnitTests/TestBaseClass.cs | 9 +- 29 files changed, 341 insertions(+), 20 deletions(-) create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/AppDelegate.cs create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/Library/Library.cs create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/Library/MacCatalyst/Library.csproj create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/Library/MacCatalyst/Makefile create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/Library/Makefile create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/Library/iOS/Library.csproj create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/Library/iOS/Makefile create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/Library/macOS/Library.csproj create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/Library/macOS/Makefile create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/Library/shared.csproj create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/Library/shared.mk create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/Library/tvOS/Library.csproj create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/Library/tvOS/Makefile create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/MacCatalyst/Makefile create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/MacCatalyst/RebuildTestAppWithLibraryReference.csproj create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/Makefile create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/iOS/Makefile create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/iOS/RebuildTestAppWithLibraryReference.csproj create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/macOS/Makefile create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/macOS/RebuildTestAppWithLibraryReference.csproj create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/shared.csproj create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/shared.mk create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/tvOS/Makefile create mode 100644 tests/dotnet/RebuildTestAppWithLibraryReference/tvOS/RebuildTestAppWithLibraryReference.csproj create mode 100644 tests/dotnet/UnitTests/RebuildTest.cs diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index c790c216f2d..296e35c0e75 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -1156,8 +1156,7 @@ + > + + The assembly {0} does not provide an 'ObjectFile' metadata. + + Don't translate: 'ObjectFile'. + + + + + The assembly {0} was passed multiple times as an input assembly (referenced from {1}). + + {0}: the name of an assembly + {1}: the path to an assembly + + + + + Failed to AOT compile {0}, the AOT compiler exited with code {1}. + + {0}: the path to an assembly + {1}: the exit code of a process + + + + + Encountered an assembly reference cycle related to the assembly {0}. + + {0}: the path to an assembly + + diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/AOTCompileTaskBase.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/AOTCompileTaskBase.cs index 93bfd289eeb..e3f62192fa5 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/AOTCompileTaskBase.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/AOTCompileTaskBase.cs @@ -7,6 +7,8 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; +using Mono.Cecil; + using Xamarin.Localization.MSBuild; using Xamarin.Utils; @@ -42,11 +44,111 @@ public abstract class AOTCompileTaskBase : XamarinTask { public ITaskItem []? FileWrites { get; set; } #endregion + class AssemblyInfo { + public ITaskItem TaskItem; + public bool? IsUpToDate; + + public AssemblyInfo (ITaskItem item) + { + TaskItem = item; + } + } + + Dictionary assemblyInfos = new Dictionary (StringComparer.OrdinalIgnoreCase); + + string GetAssemblyName (ITaskItem item) + { + return Path.GetFileNameWithoutExtension (item.ItemSpec); + } + + bool IsUpToDate (ITaskItem assembly) + { + var assemblyPath = assembly.ItemSpec; + var key = GetAssemblyName (assembly); + if (assemblyInfos.TryGetValue (key, out var info)) { + if (!info.IsUpToDate.HasValue) { + Log.LogError (MSBStrings.E7119 /* Encountered an assembly reference cycle related to the assembly {0}. */, assemblyPath); + info.IsUpToDate = false; + return false; + } + return info.IsUpToDate.Value; + } + + info = new AssemblyInfo (assembly); + assemblyInfos [key] = info; + + var finfo = new FileInfo (assemblyPath); + if (!finfo.Exists) { + Log.LogError (MSBStrings.E0158 /* The file {0} does not exist. */, assemblyPath); + info.IsUpToDate = false; + return false; + } + + // ObjectFile is required + var objectFile = assembly.GetMetadata ("ObjectFile"); + if (string.IsNullOrEmpty (objectFile)) { + Log.LogError (MSBStrings.E7116 /* The assembly {0} does not provide an 'ObjectFile' metadata. */, assembly.ItemSpec); + info.IsUpToDate = false; + return false; + } + var objectFileInfo = new FileInfo (objectFile); + if (!IsUpToDate (finfo, objectFileInfo)) { + Log.LogMessage (MessageImportance.Low, "The assembly {0} is not up-to-date with regards to the object file {1}", assemblyPath, objectFile); + info.IsUpToDate = false; + return false; + } + + // LLVMFile is optional + var llvmFile = assembly.GetMetadata ("LLVMFile"); + if (!string.IsNullOrEmpty (llvmFile)) { + var llvmFileInfo = new FileInfo (llvmFile); + if (!IsUpToDate (finfo, llvmFileInfo)) { + Log.LogMessage (MessageImportance.Low, "The assembly {0} is not up-to-date with regards to the llvm file {1}", assemblyPath, llvmFile); + info.IsUpToDate = false; + return false; + } + } + + // We know now the assembly itself is up-to-date, but what about every referenced assembly? + // This assembly must be AOT-compiled again if any referenced assembly has changed as well. + using var ad = AssemblyDefinition.ReadAssembly (assembly.ItemSpec, new ReaderParameters { ReadingMode = ReadingMode.Deferred }); + foreach (var ar in ad.MainModule.AssemblyReferences) { + var referencedItems = Assemblies.Where (v => string.Equals (GetAssemblyName (v), ar.Name, StringComparison.OrdinalIgnoreCase)).ToArray (); + if (referencedItems.Length == 0) { + Log.LogMessage (MessageImportance.Low, $"Ignoring unresolved assembly {ar.Name} (referenced from {assemblyPath})."); + continue; + } else if (referencedItems.Length > 1) { + Log.LogError (MSBStrings.E7117 /* The assembly {0} was passed multiple times as an input assembly (referenced from {1}). */, ar.Name, assemblyPath); + info.IsUpToDate = false; + return false; + } + var referencedItem = referencedItems [0]; + if (!IsUpToDate (referencedItem)) { + info.IsUpToDate = false; + Log.LogMessage (MessageImportance.Low, "The assembly {0} is not up-to-date with regards to the reference {1}", assemblyPath, ar.Name); + return false; + } + } + + Log.LogMessage (MessageImportance.Low, $"The AOT-compiled code for {assemblyPath} is up-to-date."); + info.IsUpToDate = true; + return true; + } + + bool IsUpToDate (FileInfo input, FileInfo output) + { + if (!output.Exists) + return false; + return input.LastWriteTimeUtc <= output.LastWriteTimeUtc; + } + public override bool Execute () { var inputs = new List (Assemblies.Length); for (var i = 0; i < Assemblies.Length; i++) { - inputs.Add (Path.GetFullPath (Assemblies [i].ItemSpec)); + var input = Path.GetFullPath (Assemblies [i].ItemSpec); + inputs.Add (input); + Assemblies [i].SetMetadata ("Input", input); } // All the assemblies to AOT must be in the same directory @@ -64,25 +166,40 @@ public override bool Execute () InputDirectory = assemblyDirectories [0]; } + // Figure out which assemblies need to be aot'ed, and which are up-to-date. + var assembliesToAOT = Assemblies.Where (asm => !IsUpToDate (asm)).ToList (); + if (assembliesToAOT.Count == 0) { + Log.LogMessage (MessageImportance.Low, $"All the AOT-compiled code is up-to-date."); + return !Log.HasLoggedErrors; + } + + // If any assembly changed, then we must re-generate the dedup assembly. + var dedupAssembly = Assemblies.SingleOrDefault (asm => { + Boolean.TryParse (asm.GetMetadata ("IsDedupAssembly"), out var isDedupAssembly); + return isDedupAssembly; + }); + if (dedupAssembly is not null && !assembliesToAOT.Contains (dedupAssembly)) + assembliesToAOT.Add (dedupAssembly); + Directory.CreateDirectory (OutputDirectory); var aotAssemblyFiles = new List (); - var processes = new Task [Assemblies.Length]; + var processes = new Task [assembliesToAOT.Count]; var environment = new Dictionary { { "MONO_PATH", Path.GetFullPath (InputDirectory) }, }; var globalAotArguments = AotArguments?.Select (v => v.ItemSpec).ToList (); - for (var i = 0; i < Assemblies.Length; i++) { - var asm = Assemblies [i]; - var input = inputs [i]; - var arch = Assemblies [i].GetMetadata ("Arch"); - var aotArguments = Assemblies [i].GetMetadata ("Arguments"); - var processArguments = Assemblies [i].GetMetadata ("ProcessArguments"); - var aotData = Assemblies [i].GetMetadata ("AOTData"); - var aotAssembly = Assemblies [i].GetMetadata ("AOTAssembly"); - Boolean.TryParse (Assemblies [i].GetMetadata ("IsDedupAssembly"), out var isDedupAssembly); + for (var i = 0; i < assembliesToAOT.Count; i++) { + var asm = assembliesToAOT [i]; + var input = asm.GetMetadata ("Input"); + var arch = asm.GetMetadata ("Arch"); + var aotArguments = asm.GetMetadata ("Arguments"); + var processArguments = asm.GetMetadata ("ProcessArguments"); + var aotData = asm.GetMetadata ("AOTData"); + var aotAssembly = asm.GetMetadata ("AOTAssembly"); + var isDedupAssembly = object.ReferenceEquals (asm, dedupAssembly); var aotAssemblyItem = new TaskItem (aotAssembly); aotAssemblyItem.SetMetadata ("Arguments", "-Xlinker -rpath -Xlinker @executable_path/ -Qunused-arguments -x assembler -D DEBUG"); @@ -110,7 +227,7 @@ public override bool Execute () processes [i] = ExecuteAsync (AOTCompilerPath, arguments, environment: environment, sdkDevPath: SdkDevPath, showErrorIfFailure: false /* we show our own error below */) .ContinueWith ((v) => { if (v.Result.ExitCode != 0) - Log.LogError ("Failed to AOT compile {0}, the AOT compiler exited with code {1}", Path.GetFileName (input), v.Result.ExitCode); + Log.LogError (MSBStrings.E7118 /* Failed to AOT compile {0}, the AOT compiler exited with code {1} */, Path.GetFileName (input), v.Result.ExitCode); return System.Threading.Tasks.Task.FromResult (v.Result); }).Unwrap (); @@ -122,8 +239,8 @@ public override bool Execute () // For Windows support it's necessary to have the files we're going to create as an Output parameter, so that the files are // created on the windows side too, which makes the Inputs/Outputs logic work properly when working from Windows. - var objectFiles = Assemblies.Select (v => v.GetMetadata ("ObjectFile")).Where (v => !string.IsNullOrEmpty (v)); - var llvmFiles = Assemblies.Select (v => v.GetMetadata ("LLVMFile")).Where (v => !string.IsNullOrEmpty (v)); + var objectFiles = assembliesToAOT.Select (v => v.GetMetadata ("ObjectFile")).Where (v => !string.IsNullOrEmpty (v)); + var llvmFiles = assembliesToAOT.Select (v => v.GetMetadata ("LLVMFile")).Where (v => !string.IsNullOrEmpty (v)); FileWrites = objectFiles.Union (llvmFiles).Select (v => new TaskItem (v)).ToArray (); return !Log.HasLoggedErrors; diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/AppDelegate.cs b/tests/dotnet/RebuildTestAppWithLibraryReference/AppDelegate.cs new file mode 100644 index 00000000000..9c40ddb7276 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/AppDelegate.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.InteropServices; + +using Foundation; + +namespace MySimpleApp { + public class Program { + static int Main (string [] args) + { + GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly + + Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD")); + Console.WriteLine (Library.Class.SomeFunction ()); + + return args.Length; + } + } +} diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/Library/Library.cs b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/Library.cs new file mode 100644 index 00000000000..7f457d7e7ad --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/Library.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.InteropServices; + +namespace Library { + public class Class { + public static string SomeFunction () + { +#if FIRSTBUILD + return "FIRSTBUILD"; +#elif SECONDBUILD + return "SECONDBUILD"; +#endif + } + } +} diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/Library/MacCatalyst/Library.csproj b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/MacCatalyst/Library.csproj new file mode 100644 index 00000000000..6390277d5f5 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/MacCatalyst/Library.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion) + + + diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/Library/MacCatalyst/Makefile b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/MacCatalyst/Makefile new file mode 100644 index 00000000000..110d078f457 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/MacCatalyst/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/Library/Makefile b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/Makefile new file mode 100644 index 00000000000..a97c2cbb3d5 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/Makefile @@ -0,0 +1,2 @@ +TOP=../../../.. +include $(TOP)/tests/common/shared-dotnet-test.mk diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/Library/iOS/Library.csproj b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/iOS/Library.csproj new file mode 100644 index 00000000000..6390277d5f5 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/iOS/Library.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion) + + + diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/Library/iOS/Makefile b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/iOS/Makefile new file mode 100644 index 00000000000..110d078f457 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/iOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/Library/macOS/Library.csproj b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/macOS/Library.csproj new file mode 100644 index 00000000000..6390277d5f5 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/macOS/Library.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion) + + + diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/Library/macOS/Makefile b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/macOS/Makefile new file mode 100644 index 00000000000..110d078f457 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/macOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/Library/shared.csproj b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/shared.csproj new file mode 100644 index 00000000000..b1a43cfef9e --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/shared.csproj @@ -0,0 +1,12 @@ + + + + Library + $(DefineConstants);FIRSTBUILD + $(DefineConstants);SECONDBUILD + + + + + + diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/Library/shared.mk b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/shared.mk new file mode 100644 index 00000000000..3b1ea8d6cf3 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/shared.mk @@ -0,0 +1,2 @@ +TOP=../../../../.. +include $(TOP)/tests/common/shared-dotnet.mk diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/Library/tvOS/Library.csproj b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/tvOS/Library.csproj new file mode 100644 index 00000000000..6390277d5f5 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/tvOS/Library.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion) + + + diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/Library/tvOS/Makefile b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/tvOS/Makefile new file mode 100644 index 00000000000..110d078f457 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/Library/tvOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/MacCatalyst/Makefile b/tests/dotnet/RebuildTestAppWithLibraryReference/MacCatalyst/Makefile new file mode 100644 index 00000000000..110d078f457 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/MacCatalyst/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/MacCatalyst/RebuildTestAppWithLibraryReference.csproj b/tests/dotnet/RebuildTestAppWithLibraryReference/MacCatalyst/RebuildTestAppWithLibraryReference.csproj new file mode 100644 index 00000000000..6b0e2c77318 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/MacCatalyst/RebuildTestAppWithLibraryReference.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-maccatalyst + + + diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/Makefile b/tests/dotnet/RebuildTestAppWithLibraryReference/Makefile new file mode 100644 index 00000000000..6affa45ff12 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/Makefile @@ -0,0 +1,2 @@ +TOP=../../.. +include $(TOP)/tests/common/shared-dotnet-test.mk diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/iOS/Makefile b/tests/dotnet/RebuildTestAppWithLibraryReference/iOS/Makefile new file mode 100644 index 00000000000..110d078f457 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/iOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/iOS/RebuildTestAppWithLibraryReference.csproj b/tests/dotnet/RebuildTestAppWithLibraryReference/iOS/RebuildTestAppWithLibraryReference.csproj new file mode 100644 index 00000000000..86d408734aa --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/iOS/RebuildTestAppWithLibraryReference.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-ios + + + diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/macOS/Makefile b/tests/dotnet/RebuildTestAppWithLibraryReference/macOS/Makefile new file mode 100644 index 00000000000..110d078f457 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/macOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/macOS/RebuildTestAppWithLibraryReference.csproj b/tests/dotnet/RebuildTestAppWithLibraryReference/macOS/RebuildTestAppWithLibraryReference.csproj new file mode 100644 index 00000000000..a77287b9ba0 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/macOS/RebuildTestAppWithLibraryReference.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-macos + + + diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/shared.csproj b/tests/dotnet/RebuildTestAppWithLibraryReference/shared.csproj new file mode 100644 index 00000000000..532458ef45a --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/shared.csproj @@ -0,0 +1,19 @@ + + + + Exe + + RebuildTestAppWithLibraryReference + com.xamarin.rebuildtestappwithlibraryreference + + true + true + + + + + + + + + diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/shared.mk b/tests/dotnet/RebuildTestAppWithLibraryReference/shared.mk new file mode 100644 index 00000000000..f555cad4e80 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/shared.mk @@ -0,0 +1,2 @@ +TOP=../../../.. +include $(TOP)/tests/common/shared-dotnet.mk diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/tvOS/Makefile b/tests/dotnet/RebuildTestAppWithLibraryReference/tvOS/Makefile new file mode 100644 index 00000000000..110d078f457 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/tvOS/Makefile @@ -0,0 +1 @@ +include ../shared.mk diff --git a/tests/dotnet/RebuildTestAppWithLibraryReference/tvOS/RebuildTestAppWithLibraryReference.csproj b/tests/dotnet/RebuildTestAppWithLibraryReference/tvOS/RebuildTestAppWithLibraryReference.csproj new file mode 100644 index 00000000000..bd487ddcd88 --- /dev/null +++ b/tests/dotnet/RebuildTestAppWithLibraryReference/tvOS/RebuildTestAppWithLibraryReference.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-tvos + + + diff --git a/tests/dotnet/UnitTests/RebuildTest.cs b/tests/dotnet/UnitTests/RebuildTest.cs new file mode 100644 index 00000000000..3fa72a7f2f6 --- /dev/null +++ b/tests/dotnet/UnitTests/RebuildTest.cs @@ -0,0 +1,39 @@ +namespace Xamarin.Tests { + [TestFixture] + public class RebuildTest : TestBaseClass { + [Test] + [TestCase (ApplePlatform.MacCatalyst, "maccatalyst-arm64")] + public void ReAotTest (ApplePlatform platform, string runtimeIdentifiers) + { + var project = "RebuildTestAppWithLibraryReference"; + Configuration.IgnoreIfIgnoredPlatform (platform); + Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); + + var project_path = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out var appPath); + Clean (project_path); + + var properties = GetDefaultProperties (runtimeIdentifiers); + properties ["BuildCounter"] = "First"; + + DotNet.AssertBuild (project_path, properties); + + var appExecutable = Path.Combine (appPath, "Contents", "MacOS", Path.GetFileNameWithoutExtension (project_path)); + + Assert.That (appExecutable, Does.Exist, "There is an executable"); + if (CanExecute (platform, runtimeIdentifiers)) { + var output = ExecuteWithMagicWordAndAssert (appExecutable); + Assert.That (output, Does.Contain ("FIRSTBUILD"), "First build"); + } + + // Build again, changing something + properties ["BuildCounter"] = "Second"; + DotNet.AssertBuild (project_path, properties); + Assert.That (appExecutable, Does.Exist, "There is an executable"); + if (CanExecute (platform, runtimeIdentifiers)) { + var output = ExecuteWithMagicWordAndAssert (appExecutable); + Assert.That (output, Does.Contain ("SECONDBUILD"), "Second build"); + } + } + } +} + diff --git a/tests/dotnet/UnitTests/TestBaseClass.cs b/tests/dotnet/UnitTests/TestBaseClass.cs index d63a4464746..ced32db8264 100644 --- a/tests/dotnet/UnitTests/TestBaseClass.cs +++ b/tests/dotnet/UnitTests/TestBaseClass.cs @@ -313,19 +313,20 @@ protected string GenerateProject (ApplePlatform platform, string name, string ru return csproj; } - protected void ExecuteWithMagicWordAndAssert (ApplePlatform platform, string runtimeIdentifiers, string executable) + protected string ExecuteWithMagicWordAndAssert (ApplePlatform platform, string runtimeIdentifiers, string executable) { if (!CanExecute (platform, runtimeIdentifiers)) - return; + return string.Empty; - ExecuteWithMagicWordAndAssert (executable); + return ExecuteWithMagicWordAndAssert (executable); } - protected void ExecuteWithMagicWordAndAssert (string executable) + protected string ExecuteWithMagicWordAndAssert (string executable) { var rv = Execute (executable, out var output, out string magicWord); Assert.That (output.ToString (), Does.Contain (magicWord), "Contains magic word"); Assert.AreEqual (0, rv.ExitCode, "ExitCode"); + return output.ToString (); } protected Execution Execute (string executable, out StringBuilder output, out string magicWord)