From d89b6390f16a4d85e712588a592e1a370c87270a Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Thu, 15 Feb 2024 17:29:18 +0100 Subject: [PATCH] Update AOTCompile task to handle cycles in up-to-date check (#20103) #### Background While cyclic assembly references are unusual they are supported by the runtime and encountered in practice. In our project we use a version of Xamarin.Forms retargeted for a modern .NET (as a stopgap solution before an update to full MAUI stack). Xamarin.Forms historically come with such an assembly cycle: `Xamarin.Forms.Core.dll` -> `Xamarin.Forms.Platform.dll` -> `Xamarin.Forms.Platform.iOS.dll` -> `Xamarin.Forms.Core.dll`. This is produced by first compiling `Xamarin.Forms.Core.dll` against a dummy `Xamarin.Forms.Platform.dll` (`netstandard2.0` assembly). Then the Platform assemblies are built, and finally forwarder versions of `Xamarin.Forms.Platform.dll` are created for each platform. This is all packaged into NuGets in such a way that each TFM gets the same `Xamarin.Forms.Core.dll` but a different set of `Xamarin.Forms.Platform.dll` and `Xamarin.Forms.Platform..dll` assemblies. #### Problem With current workloads each incremental rebuild fails with: ``` /usr/local/share/dotnet/packs/Microsoft.iOS.Sdk/16.4.7125/targets/Xamarin.Shared.Sdk.targets(1047,3): error : Encountered an assembly reference cycle related to the assembly obj/Debug/net7.0-ios/iossimulator-arm64/linked/Xamarin.Forms.Core.dll ``` #### Solution The PR updates the algorithm in `AOTCompile` to detect cycles using [Tarjan's algorithm](https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm) for finding [strongly connected components](https://en.wikipedia.org/wiki/Strongly_connected_component) in a graph. When a cycle is detect any assembly in the cycle is considered up-to-date only if all the assemblies in the cycle are up-to-date. With this change the project does incremental rebuilds correctly. I verified in the build output that the assembly cycle is considered up-to-date, and that any changes to the application assemblies still return `false` from the up-to-date check and get rebuilt. --------- Co-authored-by: Filip Navara --- .../MSBStrings.resx.lcl | 9 - .../MSBStrings.resx.lcl | 9 - .../MSBStrings.resx.lcl | 9 - .../MSBStrings.resx.lcl | 9 - .../MSBStrings.resx.lcl | 9 - .../MSBStrings.resx.lcl | 9 - .../MSBStrings.resx.lcl | 9 - .../MSBStrings.resx.lcl | 9 - .../MSBStrings.resx.lcl | 9 - .../MSBStrings.resx.lcl | 9 - .../MSBStrings.resx.lcl | 9 - .../MSBStrings.resx.lcl | 9 - .../MSBStrings.resx.lcl | 9 - .../MSBStrings.resx | 7 - .../TranslatedAssemblies/MSBStrings.cs.resx | 6 - .../TranslatedAssemblies/MSBStrings.de.resx | 6 - .../TranslatedAssemblies/MSBStrings.es.resx | 6 - .../TranslatedAssemblies/MSBStrings.fr.resx | 6 - .../TranslatedAssemblies/MSBStrings.it.resx | 6 - .../TranslatedAssemblies/MSBStrings.ja.resx | 6 - .../TranslatedAssemblies/MSBStrings.ko.resx | 6 - .../TranslatedAssemblies/MSBStrings.pl.resx | 6 - .../MSBStrings.pt-BR.resx | 6 - .../TranslatedAssemblies/MSBStrings.ru.resx | 6 - .../TranslatedAssemblies/MSBStrings.tr.resx | 6 - .../MSBStrings.zh-Hans.resx | 6 - .../MSBStrings.zh-Hant.resx | 6 - .../Xamarin.MacDev.Tasks/Tasks/AOTCompile.cs | 159 +++++++++++++----- 28 files changed, 120 insertions(+), 241 deletions(-) diff --git a/Localize/loc/cs/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/Localize/loc/cs/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 789e2d93ee..9cb9e94944 100644 --- a/Localize/loc/cs/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/Localize/loc/cs/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -1939,15 +1939,6 @@ - - - - - - - - - diff --git a/Localize/loc/de/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/Localize/loc/de/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 9829f5d3b8..09579a0b2f 100644 --- a/Localize/loc/de/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/Localize/loc/de/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -1939,15 +1939,6 @@ - - - - - - - - - diff --git a/Localize/loc/es/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/Localize/loc/es/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 6527f731c7..22279c51a7 100644 --- a/Localize/loc/es/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/Localize/loc/es/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -1939,15 +1939,6 @@ - - - - - - - - - diff --git a/Localize/loc/fr/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/Localize/loc/fr/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 714905b3a3..4cb678419c 100644 --- a/Localize/loc/fr/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/Localize/loc/fr/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -1939,15 +1939,6 @@ - - - - - - - - - diff --git a/Localize/loc/it/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/Localize/loc/it/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 067dc29cf5..d302558e60 100644 --- a/Localize/loc/it/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/Localize/loc/it/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -1939,15 +1939,6 @@ - - - - - - - - - diff --git a/Localize/loc/ja/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/Localize/loc/ja/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 814bea6c74..5752e54fa9 100644 --- a/Localize/loc/ja/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/Localize/loc/ja/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -1939,15 +1939,6 @@ - - - - - - - - - diff --git a/Localize/loc/ko/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/Localize/loc/ko/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index f131bba51c..320f335093 100644 --- a/Localize/loc/ko/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/Localize/loc/ko/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -1939,15 +1939,6 @@ - - - - - - - - - diff --git a/Localize/loc/pl/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/Localize/loc/pl/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index fe84d1153b..1e0c60c861 100644 --- a/Localize/loc/pl/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/Localize/loc/pl/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -1939,15 +1939,6 @@ - - - - - - - - - diff --git a/Localize/loc/pt-BR/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/Localize/loc/pt-BR/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 178c912ec0..8dafd57e95 100644 --- a/Localize/loc/pt-BR/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/Localize/loc/pt-BR/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -1939,15 +1939,6 @@ - - - - - - - - - diff --git a/Localize/loc/ru/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/Localize/loc/ru/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index af0edc8c34..6659ccc366 100644 --- a/Localize/loc/ru/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/Localize/loc/ru/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -1939,15 +1939,6 @@ - - - - - - - - - diff --git a/Localize/loc/tr/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/Localize/loc/tr/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index aabb1c761e..3e6f7a6118 100644 --- a/Localize/loc/tr/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/Localize/loc/tr/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -1939,15 +1939,6 @@ - - - - - - - - - diff --git a/Localize/loc/zh-Hans/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/Localize/loc/zh-Hans/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 4be1aa4c9b..ffa6974ee5 100644 --- a/Localize/loc/zh-Hans/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/Localize/loc/zh-Hans/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -1939,15 +1939,6 @@ - - - - - - - - - diff --git a/Localize/loc/zh-Hant/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl b/Localize/loc/zh-Hant/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl index 583b2df942..4c4c1ec2e9 100644 --- a/Localize/loc/zh-Hant/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl +++ b/Localize/loc/zh-Hant/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx.lcl @@ -1939,15 +1939,6 @@ - - - - - - - - - diff --git a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx index 9566a70b79..1d812b56f3 100644 --- a/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx +++ b/msbuild/Xamarin.Localization.MSBuild/MSBStrings.resx @@ -1533,11 +1533,4 @@ {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.Localization.MSBuild/TranslatedAssemblies/MSBStrings.cs.resx b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.cs.resx index affd73e344..11fd620915 100644 --- a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.cs.resx +++ b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.cs.resx @@ -1237,10 +1237,4 @@ {1}: the exit code of a process - - Byl zjištěn cyklus odkazu na sestavení související se sestavením {0}. - - {0}: the path to an assembly - - \ No newline at end of file diff --git a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.de.resx b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.de.resx index ae3aad1aec..c8400aaec3 100644 --- a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.de.resx +++ b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.de.resx @@ -1237,10 +1237,4 @@ {1}: the exit code of a process - - Es wurde ein Assemblyverweiszyklus gefunden, der mit der Assembly {0} verknüpft ist. - - {0}: the path to an assembly - - \ No newline at end of file diff --git a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.es.resx b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.es.resx index 397371e033..ca878c0c2e 100644 --- a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.es.resx +++ b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.es.resx @@ -1237,10 +1237,4 @@ {1}: the exit code of a process - - Se encontró un ciclo de referencia de ensamblado relacionado con el ensamblado {0}. - - {0}: the path to an assembly - - \ No newline at end of file diff --git a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.fr.resx b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.fr.resx index 993178772d..f1b3e8ce26 100644 --- a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.fr.resx +++ b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.fr.resx @@ -1237,10 +1237,4 @@ {1}: the exit code of a process - - A rencontré un cycle de référence d'assemblage lié à l'assemblage {0}. - - {0}: the path to an assembly - - \ No newline at end of file diff --git a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.it.resx b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.it.resx index d0e635a341..794335d7f1 100644 --- a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.it.resx +++ b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.it.resx @@ -1237,10 +1237,4 @@ {1}: the exit code of a process - - È stato rilevato un ciclo di riferimento dell'assembly correlato all'assembly {0}. - - {0}: the path to an assembly - - \ No newline at end of file diff --git a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ja.resx b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ja.resx index f2674a9007..9adf703347 100644 --- a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ja.resx +++ b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ja.resx @@ -1237,10 +1237,4 @@ {1}: the exit code of a process - - アセンブリ {0} に関連するアセンブリ参照サイクルが発生しました。 - - {0}: the path to an assembly - - \ No newline at end of file diff --git a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ko.resx b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ko.resx index 15b0d2f058..139cca242c 100644 --- a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ko.resx +++ b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ko.resx @@ -1237,10 +1237,4 @@ {1}: the exit code of a process - - {0} 어셈블리와 관련된 어셈블리 참조 주기를 발견했습니다. - - {0}: the path to an assembly - - \ No newline at end of file diff --git a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pl.resx b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pl.resx index ec30c15ebb..dc63254b86 100644 --- a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pl.resx +++ b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pl.resx @@ -1237,10 +1237,4 @@ {1}: the exit code of a process - - Napotkano cykl odwołania do zestawu powiązany z zestawem {0}. - - {0}: the path to an assembly - - \ No newline at end of file diff --git a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pt-BR.resx b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pt-BR.resx index 44657360b8..64eeba8e48 100644 --- a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pt-BR.resx +++ b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.pt-BR.resx @@ -1237,10 +1237,4 @@ {1}: the exit code of a process - - Foi encontrado um ciclo de referência de assembly relacionado ao assembly {0}. - - {0}: the path to an assembly - - \ No newline at end of file diff --git a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ru.resx b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ru.resx index 7c29bce0ef..1b36d7cd39 100644 --- a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ru.resx +++ b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.ru.resx @@ -1237,10 +1237,4 @@ {1}: the exit code of a process - - Обнаружен цикл ссылки на сборку, связанный со сборкой {0}. - - {0}: the path to an assembly - - \ No newline at end of file diff --git a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.tr.resx b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.tr.resx index c9299cd92d..c87868a15c 100644 --- a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.tr.resx +++ b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.tr.resx @@ -1237,10 +1237,4 @@ {1}: the exit code of a process - - {0} bütünleştirilmiş koduyla ilgili bir bütünleştirilmiş kod başvuru döngüsüyle karşılaşıldı. - - {0}: the path to an assembly - - \ No newline at end of file diff --git a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hans.resx b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hans.resx index 6f0becaae3..fa115333a7 100644 --- a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hans.resx +++ b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hans.resx @@ -1237,10 +1237,4 @@ {1}: the exit code of a process - - 遇到与程序集 {0} 相关的程序集引用周期。 - - {0}: the path to an assembly - - \ No newline at end of file diff --git a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hant.resx b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hant.resx index ce40dbaecd..2f103a78a7 100644 --- a/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hant.resx +++ b/msbuild/Xamarin.Localization.MSBuild/TranslatedAssemblies/MSBStrings.zh-Hant.resx @@ -1237,10 +1237,4 @@ {1}: the exit code of a process - - 遇到與組件 {0} 相關的組件參考循環。 - - {0}: the path to an assembly - - \ No newline at end of file diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/AOTCompile.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/AOTCompile.cs index 509c458b9f..b8c3bec8cb 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/AOTCompile.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/AOTCompile.cs @@ -47,7 +47,12 @@ namespace Xamarin.MacDev.Tasks { class AssemblyInfo { public ITaskItem TaskItem; - public bool? IsUpToDate; + public bool IsUpToDate; + + // Tarjan's SCC algoritm + public bool OnStack; + public int Index; + public int LowLink; public AssemblyInfo (ITaskItem item) { @@ -62,26 +67,114 @@ namespace Xamarin.MacDev.Tasks { return Path.GetFileNameWithoutExtension (item.ItemSpec); } - bool IsUpToDate (ITaskItem assembly) + bool ComputeUpToDate (IEnumerable items) { - 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; + // Implements Tarjan's algorithm for finding strongly connected components. + // https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm + // + // We recursively compute up-to-date state for each assembly and its references. + // When we encounter a strongly connected component (cycle) we ensure that either + // all the files in the SCC are marked as up-to-date or none. + + int index = 0; + var stack = new Stack (); + bool success = true; + + foreach (ITaskItem assembly in items) { + var key = GetAssemblyName (assembly); + if (!assemblyInfos.ContainsKey (key)) { + var info = new AssemblyInfo (assembly); + assemblyInfos [key] = info; + success &= ComputeUpToDate (info, stack, ref index); } - return info.IsUpToDate.Value; } - info = new AssemblyInfo (assembly); - assemblyInfos [key] = info; + return success; + } + bool ComputeUpToDate (AssemblyInfo info, Stack stack, ref int index) + { + bool success = true; + + info.Index = index; + info.LowLink = index; + index++; + stack.Push (info); + info.OnStack = true; + + info.IsUpToDate = ComputeUpToDate (info.TaskItem); + + // Walk all referenced assemblies + var assemblyPath = info.TaskItem.ItemSpec; + using var ad = AssemblyDefinition.ReadAssembly (assemblyPath, 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); + success = false; + continue; + } + + var referencedItem = referencedItems [0]; + var key = GetAssemblyName (referencedItem); + if (!assemblyInfos.TryGetValue (key, out var referenceInfo)) { + // Referenced assembly has not yet been visited; recurse on it + referenceInfo = new AssemblyInfo (referencedItem); + assemblyInfos [key] = referenceInfo; + success &= ComputeUpToDate (referenceInfo, stack, ref index); + if (info.IsUpToDate && !referenceInfo.IsUpToDate) { + Log.LogMessage (MessageImportance.Low, $"The assembly {assemblyPath} is not up-to-date with regards to the reference {referenceInfo.TaskItem.ItemSpec}."); + info.IsUpToDate = false; + } + info.LowLink = Math.Min (info.LowLink, referenceInfo.LowLink); + } else if (referenceInfo.OnStack) { + // Referenced assembly is in stack and hence in the current SCC + info.LowLink = Math.Min (info.LowLink, referenceInfo.Index); + } + } + + // If this is a root node of SCC, pop the stack + if (info.Index == info.LowLink) { + bool sccIsUpToDate = true; + + // Walk the SCC on the stack and determine whether the whole + // component is up-to-date or not. + foreach (var itemOnStack in stack) { + if (itemOnStack == info) { + break; + } + sccIsUpToDate &= itemOnStack.IsUpToDate; + } + + // Remove the SCC from the stack and update IsUpToDate for each item. + AssemblyInfo popped; + do { + popped = stack.Pop (); + popped.OnStack = false; + + if (!sccIsUpToDate) { + // If any assembly in the SCC is not up-to-date then the whole SCC is not + // up to date. + popped.IsUpToDate = false; + Log.LogMessage (MessageImportance.Low, $"The assembly {popped.TaskItem.ItemSpec} in a cycle is not up-to-date."); + } else { + Log.LogMessage (MessageImportance.Low, $"The AOT-compiled code for {popped.TaskItem.ItemSpec} is up-to-date."); + } + } while (popped != info); + } + + return success; + } + + bool ComputeUpToDate (ITaskItem assembly) + { + var assemblyPath = assembly.ItemSpec; var finfo = new FileInfo (assemblyPath); if (!finfo.Exists) { Log.LogError (MSBStrings.E0158 /* The file {0} does not exist. */, assemblyPath); - info.IsUpToDate = false; return false; } @@ -89,13 +182,11 @@ namespace Xamarin.MacDev.Tasks { 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; } @@ -105,37 +196,24 @@ namespace Xamarin.MacDev.Tasks { 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 (ITaskItem assembly) + { + var assemblyPath = assembly.ItemSpec; + var key = GetAssemblyName (assembly); + if (assemblyInfos.TryGetValue (key, out var info)) { + return info.IsUpToDate; + } + + return false; + } + bool IsUpToDate (FileInfo input, FileInfo output) { if (!output.Exists) @@ -171,6 +249,9 @@ namespace Xamarin.MacDev.Tasks { } // Figure out which assemblies need to be aot'ed, and which are up-to-date. + if (!ComputeUpToDate (Assemblies)) { + return false; + } 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.");