From 209c040d261558d6d58ebf046e39491b22fb28f1 Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Thu, 4 Aug 2022 10:02:13 -0700 Subject: [PATCH] [Android] Introduce NetTraceToMibcConverter task & streamline testing targets (#72394) NetTraceToMibcConverter - Used in profiled AOT scenarios where a .nettrace file is given as input and is converted to a .mibc file that can be fed into the AOT compiler. This previously was in the AotCompiler task, but for clarity purposes is now separated out. Streamline Android testing targets - The testing targets function the same, but are now structured similarly to iOS and Wasm. - Introduced new testing properties to support profiled AOT: NetTraceFilePath - The path to a .nettrace file that will be converted into a .mibc file and fed into the aot compiler RuntimeComponents - The list of native components to include in the test app build (diagnostics_tracing) DiagnosticsPorts - The ip address:port where the runtime will listen when running diagnostic tooling DiagnosticStartupMode - The mode the runtime will use at startup for diagnostic scenarios. Suspend will halt the app very early and wait, while nosuspend will wait for a connection, but not halt the runtime Co-authored-by: Mitchell Hwang <16830051+mdh1418@users.noreply.github.com> --- Directory.Build.props | 3 +- eng/Subsets.props | 7 +- eng/testing/tests.android.targets | 74 ++++++ eng/testing/tests.mobile.targets | 124 +--------- .../android/build/AndroidApp.InTree.props | 13 ++ .../android/build/AndroidApp.InTree.targets | 21 ++ .../msbuild/android/build/AndroidApp.props | 18 ++ .../msbuild/android/build/AndroidApp.targets | 168 +++++++++++++ src/mono/msbuild/apple/build/AppleApp.targets | 13 -- .../Sdk/MonoTargetsTasks.props.in | 2 + .../AndroidAppBuilder/AndroidAppBuilder.cs | 6 + src/tasks/AndroidAppBuilder/ApkBuilder.cs | 6 + .../AndroidAppBuilder/Templates/monodroid.c | 9 +- src/tasks/AotCompilerTask/MonoAOTCompiler.cs | 221 ------------------ .../AotCompilerTask/MonoAOTCompiler.csproj | 3 + src/tasks/Common/CompilerCache.cs | 21 ++ src/tasks/Common/FileCache.cs | 93 ++++++++ src/tasks/Common/ProxyFile.cs | 56 +++++ .../MonoTargetsTasks/MonoTargetsTasks.csproj | 2 + .../NetTraceToMibcConverter.cs | 117 ++++++++++ 20 files changed, 626 insertions(+), 351 deletions(-) create mode 100644 eng/testing/tests.android.targets create mode 100644 src/mono/msbuild/android/build/AndroidApp.InTree.props create mode 100644 src/mono/msbuild/android/build/AndroidApp.InTree.targets create mode 100644 src/mono/msbuild/android/build/AndroidApp.props create mode 100644 src/mono/msbuild/android/build/AndroidApp.targets create mode 100644 src/tasks/Common/CompilerCache.cs create mode 100644 src/tasks/Common/FileCache.cs create mode 100644 src/tasks/Common/ProxyFile.cs create mode 100644 src/tasks/MonoTargetsTasks/NetTraceToMibcConverterTask/NetTraceToMibcConverter.cs diff --git a/Directory.Build.props b/Directory.Build.props index c2b06f523658d..60a661e66ca44 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -115,7 +115,8 @@ $([MSBuild]::NormalizePath('$(MonoAOTCompilerDir)', 'MonoAOTCompiler.dll')) $([MSBuild]::NormalizePath('$(MonoTargetsTasksDir)', 'MonoTargetsTasks.dll')) $([MSBuild]::NormalizePath('$(TestExclusionListTasksDir)', 'TestExclusionListTasks.dll')) - $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'coreclr', '$(TargetOS).$(TargetArchitecture).$(Configuration)')) + $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'coreclr', '$(TargetOS).$(TargetArchitecture).$(Configuration)')) + $(CoreCLRToolPath) diff --git a/eng/Subsets.props b/eng/Subsets.props index 39ecf822395bc..45b2a49471e5c 100644 --- a/eng/Subsets.props +++ b/eng/Subsets.props @@ -61,7 +61,7 @@ mono.llvm+ $(DefaultMonoSubsets)mono.wasmruntime+ $(DefaultMonoSubsets)mono.aotcross+ - $(DefaultMonoSubsets)mono.runtime+mono.corelib+mono.packages+ + $(DefaultMonoSubsets)mono.runtime+mono.corelib+mono.packages+mono.tools+ $(DefaultMonoSubsets)host+ + @@ -358,6 +359,10 @@ + + + + diff --git a/eng/testing/tests.android.targets b/eng/testing/tests.android.targets new file mode 100644 index 0000000000000..a8b279bf85317 --- /dev/null +++ b/eng/testing/tests.android.targets @@ -0,0 +1,74 @@ + + + $(BundleTestAppTargets);BundleTestAndroidApp + + + + + + + PrepareForAndroidBuildApp;$(AndroidBuildAppDependsOn);_CopyTestArchive + + AndroidBuildApp + + + + + + + + + + + + AndroidTestRunner.dll + + $(PublishDir) + $(BundleDir) + + + + + 1 + + + 1883302047 + + + 1 + + + + + + <_InternalForceInterpret>true + <_IsNative>true + + + <_PublishAssemblies Include="$(PublishDir)\**\*.dll" Exclude="$(PublishDir)\**\*.resources.dll" /> + <_SatelliteAssemblies Include="$(PublishDir)\**\*.resources.dll" /> + + + <_InternalForceInterpret Condition="'$(UseMonoJustInterp)' == 'true' and '%(FileName)%(Extension)' != 'System.Private.CoreLib.dll'">true + <_IsNative>false + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eng/testing/tests.mobile.targets b/eng/testing/tests.mobile.targets index 1c109d50b7ebb..d4ef535566364 100644 --- a/eng/testing/tests.mobile.targets +++ b/eng/testing/tests.mobile.targets @@ -8,7 +8,6 @@ $([MSBuild]::NormalizePath('$(BundleDir)', '$(RunScriptOutputName)')) true - BundleTestAndroidApp Publish @@ -16,6 +15,11 @@ true + + $([MSBuild]::NormalizeDirectory('$(CoreCLRToolPath)', 'dotnet-pgo')) + $([MSBuild]::NormalizePath('$(DotnetPgoToolDir)', 'dotnet-pgo')) + + true @@ -39,6 +43,10 @@ $(AdditionalXHarnessArguments) --expected-exit-code $(ExpectedExitCode) + + $(DiagnosticPorts),$(DiagnosticStartupMode) + + $(AdditionalXHarnessArguments) --arg=-m=$(XUnitMethodName) @@ -55,9 +63,8 @@ - - - + $(IntermediateOutputPath)mobile - - - - - $(PublishDir)$(AssemblyName).runtimeconfig.json - $(PublishDir)runtimeconfig.bin - - - - - - - - - - - - - - - - - - - - arm64-v8a - armeabi-v7a - x86_64 - x86 - - AndroidTestRunner.dll - - - - <_AndroidEnv Condition="'$(XUnitSingleThreadedMode)' == 'true'" Include="XUNIT_SINGLE_THREADED"> - 1 - - <_AndroidEnv Condition="'$(XUnitUseRandomizedTestOrderer)' == 'true'" Include="XUNIT_RANDOM_ORDER_SEED"> - 1883302047 - - <_AndroidEnv Condition="'$(XUnitSingleThreadedMode)' == 'true'" Include="XUNIT_VERBOSE"> - 1 - - - - - - @(MonoAOTCompilerDefaultAotArguments, ';') - @(MonoAOTCompilerDefaultProcessArguments, ';') - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/mono/msbuild/android/build/AndroidApp.InTree.props b/src/mono/msbuild/android/build/AndroidApp.InTree.props new file mode 100644 index 0000000000000..26fb16475833a --- /dev/null +++ b/src/mono/msbuild/android/build/AndroidApp.InTree.props @@ -0,0 +1,13 @@ + + + + + + $(NetCoreAppCurrent) + false + false + true + link + false + + diff --git a/src/mono/msbuild/android/build/AndroidApp.InTree.targets b/src/mono/msbuild/android/build/AndroidApp.InTree.targets new file mode 100644 index 0000000000000..2b374f617ae53 --- /dev/null +++ b/src/mono/msbuild/android/build/AndroidApp.InTree.targets @@ -0,0 +1,21 @@ + + + + + + + + + + + + <_LocalMicrosoftNetCoreAppRuntimePackDir>$(MicrosoftNetCoreAppRuntimePackDir) + + + + + + + diff --git a/src/mono/msbuild/android/build/AndroidApp.props b/src/mono/msbuild/android/build/AndroidApp.props new file mode 100644 index 0000000000000..3e8f62b3e0a4b --- /dev/null +++ b/src/mono/msbuild/android/build/AndroidApp.props @@ -0,0 +1,18 @@ + + + $(TargetOS.ToLowerInvariant())-$(TargetArchitecture.ToLowerInvariant()) + true + true + + Publish + + _InitializeCommonProperties; + _BeforeAndroidBuildApp; + _AndroidResolveReferences; + _AndroidPrepareProfiledAot; + _AndroidAotCompileApp; + _AndroidGenerateAppBundle; + _AfterAndroidBuildApp + + + \ No newline at end of file diff --git a/src/mono/msbuild/android/build/AndroidApp.targets b/src/mono/msbuild/android/build/AndroidApp.targets new file mode 100644 index 0000000000000..6fd26641cd1e1 --- /dev/null +++ b/src/mono/msbuild/android/build/AndroidApp.targets @@ -0,0 +1,168 @@ + + + + + + + + + + + + <_MobileIntermediateOutputPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'mobile')) + + + + + + <_AndroidRuntimeConfigFilePath Condition="'$(_AndroidRuntimeConfigFilePath)' == ''">$([MSBuild]::NormalizePath($(AndroidAppDir), '$(AssemblyName).runtimeconfig.json')) + <_ParsedRuntimeConfigFilePath Condition="'$(_ParsedRuntimeConfigFilePath)' == ''">$([MSBuild]::NormalizePath($(AndroidAppDir), 'runtimeconfig.bin')) + + + + + + + + <_AndroidAssembliesInternal Remove="@(_AndroidAssembliesInternal)" /> + <_AndroidAssembliesInternal Include="@(AndroidAssembliesToBundle)"> + <_InternalForceInterpret>%(AndroidAssembliesToBundle._InternalForceInterpret) + <_IsNative>%(AndroidAssembliesToBundle._IsNative) + + + + + + + <_AOTMode Condition="'$(UseMonoJustInterp)' != 'true'">Normal + <_AOTMode Condition="'$(UseMonoJustInterp)' == 'true'">JustInterp + <_AOTMode Condition="'$(ForceFullAOT)' == 'true'">Full + + + + + + + + + + + + + + + + + + + + + @(MonoAOTCompilerDefaultAotArguments, ';') + @(MonoAOTCompilerDefaultProcessArguments, ';') + + + + <_AotInputAssemblies Include="@(_AndroidAssembliesInternal)" + Condition="'%(_AndroidAssembliesInternal._InternalForceInterpret)' != 'true'"> + $(AotArguments) + $(ProcessArguments) + + + <_AOT_InternalForceInterpretAssemblies Include="@(_AndroidAssembliesInternal->WithMetadataValue('_InternalForceInterpret', 'true'))" /> + <_AndroidAssembliesInternal Remove="@(_AndroidAssembliesInternal)" /> + + + + + + + + <_ToolPath>$([System.IO.Path]::GetDirectoryName('$(DotnetPgoToolPath)')) + + + + + + + + + + + + + + + + + + + <_AndroidAssembliesInternal Include="@(_AOT_InternalForceInterpretAssemblies)" /> + + + + + + + + + + + + + + + + + + + + + + <_RuntimeConfigReservedProperties Include="RUNTIME_IDENTIFIER"/> + <_RuntimeConfigReservedProperties Include="APP_CONTEXT_BASE_DIRECTORY"/> + + + + + + \ No newline at end of file diff --git a/src/mono/msbuild/apple/build/AppleApp.targets b/src/mono/msbuild/apple/build/AppleApp.targets index a431fe5ce618d..470a84edecb0b 100644 --- a/src/mono/msbuild/apple/build/AppleApp.targets +++ b/src/mono/msbuild/apple/build/AppleApp.targets @@ -24,15 +24,7 @@ <_AppleRuntimeConfigFilePath Condition="'$(_AppleRuntimeConfigFilePath)' == ''">$([MSBuild]::NormalizePath($(AppleAppDir), '$(AssemblyName).runtimeconfig.json')) <_ParsedRuntimeConfigFilePath Condition="'$(_ParsedRuntimeConfigFilePath)' == ''">$([MSBuild]::NormalizePath($(AppleAppDir), 'runtimeconfig.bin')) - @@ -83,11 +75,6 @@ <_AotExcludeAssemblies Include="*System.Runtime.WindowsRuntime.dll" /> - - - <_InternalForceInterpret>%(_AppleAssembliesInternal._InternalForceInterpret) - <_IsNative>%(_AppleAssembliesInternal._IsNative) - <_AotInputAssemblies Include="@(_AppleAssembliesInternal)" Condition="'%(_AppleAssembliesInternal._InternalForceInterpret)' != 'true'"> diff --git a/src/mono/nuget/Microsoft.NET.Runtime.MonoTargets.Sdk/Sdk/MonoTargetsTasks.props.in b/src/mono/nuget/Microsoft.NET.Runtime.MonoTargets.Sdk/Sdk/MonoTargetsTasks.props.in index 4ef325f421be7..ad5db32cbf581 100644 --- a/src/mono/nuget/Microsoft.NET.Runtime.MonoTargets.Sdk/Sdk/MonoTargetsTasks.props.in +++ b/src/mono/nuget/Microsoft.NET.Runtime.MonoTargets.Sdk/Sdk/MonoTargetsTasks.props.in @@ -7,6 +7,8 @@ + + diff --git a/src/tasks/AndroidAppBuilder/AndroidAppBuilder.cs b/src/tasks/AndroidAppBuilder/AndroidAppBuilder.cs index 66eef2cd542c8..0f409c1745f7c 100644 --- a/src/tasks/AndroidAppBuilder/AndroidAppBuilder.cs +++ b/src/tasks/AndroidAppBuilder/AndroidAppBuilder.cs @@ -38,6 +38,11 @@ public class AndroidAppBuilderTask : Task /// public bool ForceAOT { get; set; } + /// + /// Indicates if we want to AOT all assemblies or not + /// + public bool ForceFullAOT { get; set; } + /// /// Static linked runtime /// @@ -111,6 +116,7 @@ public override bool Execute() apkBuilder.KeyStorePath = KeyStorePath; apkBuilder.ForceInterpreter = ForceInterpreter; apkBuilder.ForceAOT = ForceAOT; + apkBuilder.ForceFullAOT = ForceFullAOT; apkBuilder.EnvironmentVariables = EnvironmentVariables; apkBuilder.StaticLinkedRuntime = StaticLinkedRuntime; apkBuilder.RuntimeComponents = RuntimeComponents; diff --git a/src/tasks/AndroidAppBuilder/ApkBuilder.cs b/src/tasks/AndroidAppBuilder/ApkBuilder.cs index e8eeac673998c..4cfc8d0d1cac9 100644 --- a/src/tasks/AndroidAppBuilder/ApkBuilder.cs +++ b/src/tasks/AndroidAppBuilder/ApkBuilder.cs @@ -28,6 +28,7 @@ public class ApkBuilder public string? KeyStorePath { get; set; } public bool ForceInterpreter { get; set; } public bool ForceAOT { get; set; } + public bool ForceFullAOT { get; set; } public ITaskItem[] EnvironmentVariables { get; set; } = Array.Empty(); public bool InvariantGlobalization { get; set; } public bool EnableRuntimeLogging { get; set; } @@ -342,6 +343,11 @@ public ApkBuilder(TaskLoggingHelper logger) } } + if (ForceFullAOT) + { + defines.AppendLine("add_definitions(-DFULL_AOT=1)"); + } + if (!string.IsNullOrEmpty(DiagnosticPorts)) { defines.AppendLine("add_definitions(-DDIAGNOSTIC_PORTS=\"" + DiagnosticPorts + "\")"); diff --git a/src/tasks/AndroidAppBuilder/Templates/monodroid.c b/src/tasks/AndroidAppBuilder/Templates/monodroid.c index 302324efd82df..1d8d3f1bd529c 100644 --- a/src/tasks/AndroidAppBuilder/Templates/monodroid.c +++ b/src/tasks/AndroidAppBuilder/Templates/monodroid.c @@ -273,9 +273,14 @@ mono_droid_runtime_init (const char* executable, int managed_argc, char* managed LOG_INFO("AOT Enabled"); #if STATIC_AOT register_aot_modules(); -#endif +#endif // STATIC_AOT + +#if FULL_AOT mono_jit_set_aot_mode(MONO_AOT_MODE_FULL); -#endif +#else + mono_jit_set_aot_mode(MONO_AOT_MODE_NORMAL); +#endif // FULL_AOT +#endif // FORCE_INTERPRETER MonoDomain *domain = mono_jit_init_version ("dotnet.android", "mobile"); assert (domain); diff --git a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs index ea18150ac44f9..291af85bde42a 100644 --- a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs +++ b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs @@ -111,21 +111,6 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task /// public bool UseDwarfDebug { get; set; } - /// - /// Path to Dotnet PGO binary (dotnet-pgo) - /// - public string? PgoBinaryPath { get; set; } - - /// - /// NetTrace file to use when invoking dotnet-pgo for - /// - public string? NetTracePath { get; set; } - - /// - /// Directory containing all assemblies referenced in a .nettrace collected from a separate device needed by dotnet-pgo. Necessary for mobile platforms. - /// - public ITaskItem[] ReferenceAssemblyPathsForPGO { get; set; } = Array.Empty(); - /// /// File to use for profile-guided optimization, *only* the methods described in the file will be AOT compiled. /// @@ -287,31 +272,6 @@ private bool ProcessAndValidateArguments() if (!Directory.Exists(IntermediateOutputPath)) Directory.CreateDirectory(IntermediateOutputPath); - if (!string.IsNullOrEmpty(NetTracePath)) - { - if (!File.Exists(NetTracePath)) - { - Log.LogError($"{nameof(NetTracePath)}='{NetTracePath}' doesn't exist"); - return false; - } - if (!File.Exists(PgoBinaryPath)) - { - Log.LogError($"NetTracePath was provided, but {nameof(PgoBinaryPath)}='{PgoBinaryPath}' doesn't exist"); - return false; - } - if (ReferenceAssemblyPathsForPGO.Length == 0) - { - Log.LogError($"NetTracePath was provided, but {nameof(ReferenceAssemblyPathsForPGO)} is empty"); - return false; - } - foreach (var refAsmItem in ReferenceAssemblyPathsForPGO) - { - string? fullPath = refAsmItem.GetMetadata("FullPath"); - if (!File.Exists(fullPath)) - throw new LogAsErrorException($"ReferenceAssembly '{fullPath}' doesn't exist"); - } - } - if (AotProfilePath != null) { foreach (var path in AotProfilePath) @@ -438,48 +398,6 @@ public override bool Execute() } } - private bool ProcessNettrace(string netTraceFile) - { - var outputMibcPath = Path.Combine(OutputDir, Path.ChangeExtension(Path.GetFileName(netTraceFile), ".mibc")); - - if (_cache!.Enabled) - { - string hash = Utils.ComputeHash(netTraceFile); - if (!_cache!.UpdateAndCheckHasFileChanged($"-mibc-source-file-{Path.GetFileName(netTraceFile)}", hash)) - { - Log.LogMessage(MessageImportance.Low, $"Skipping generating {outputMibcPath} from {netTraceFile} because source file hasn't changed"); - return true; - } - else - { - Log.LogMessage(MessageImportance.Low, $"Generating {outputMibcPath} from {netTraceFile} because the source file's hash has changed."); - } - } - - StringBuilder pgoArgsStr = new StringBuilder(string.Empty); - pgoArgsStr.Append($"create-mibc"); - pgoArgsStr.Append($" --trace {netTraceFile} "); - foreach (var refAsmItem in ReferenceAssemblyPathsForPGO) - { - string? fullPath = refAsmItem.GetMetadata("FullPath"); - pgoArgsStr.Append($" --reference \"{fullPath}\" "); - } - pgoArgsStr.Append($" --output {outputMibcPath} "); - (int exitCode, string output) = Utils.TryRunProcess(Log, - PgoBinaryPath!, - pgoArgsStr.ToString()); - - if (exitCode != 0) - { - Log.LogError($"dotnet-pgo({PgoBinaryPath}) failed for {netTraceFile}:{output}"); - return false; - } - - MibcProfilePath = MibcProfilePath.Append(outputMibcPath).ToArray(); - Log.LogMessage(MessageImportance.Low, $"Generated {outputMibcPath} from {PgoBinaryPath}"); - return true; - } - private bool ExecuteInternal() { if (!ProcessAndValidateArguments()) @@ -498,9 +416,6 @@ private bool ExecuteInternal() _cache = new FileCache(CacheFilePath, Log); - if (!string.IsNullOrEmpty(NetTracePath) && !ProcessNettrace(NetTracePath)) - return false; - List argsList = new(); foreach (var assemblyItem in _assembliesToCompile) argsList.Add(GetPrecompileArgumentsFor(assemblyItem, monoPaths)); @@ -1150,132 +1065,6 @@ public PrecompileArguments(string ResponseFilePath, IDictionary } } -internal sealed class FileCache -{ - private CompilerCache? _newCache; - private CompilerCache? _oldCache; - - public bool Enabled { get; } - public TaskLoggingHelper Log { get; } - - public FileCache(string? cacheFilePath, TaskLoggingHelper log) - { - Log = log; - if (string.IsNullOrEmpty(cacheFilePath)) - { - Log.LogMessage(MessageImportance.Low, $"Disabling cache, because CacheFilePath is not set"); - return; - } - - Enabled = true; - if (File.Exists(cacheFilePath)) - { - _oldCache = (CompilerCache?)JsonSerializer.Deserialize(File.ReadAllText(cacheFilePath), - typeof(CompilerCache), - new JsonSerializerOptions()); - } - - _oldCache ??= new(); - _newCache = new(_oldCache.FileHashes); - } - - public bool UpdateAndCheckHasFileChanged(string filePath, string newHash) - { - if (!Enabled) - throw new InvalidOperationException("Cache is not enabled. Make sure the cache file path is set"); - - _newCache!.FileHashes[filePath] = newHash; - return !_oldCache!.FileHashes.TryGetValue(filePath, out string? oldHash) || oldHash != newHash; - } - - public bool ShouldCopy(ProxyFile proxyFile, [NotNullWhen(true)] out string? cause) - { - if (!Enabled) - throw new InvalidOperationException("Cache is not enabled. Make sure the cache file path is set"); - - cause = null; - - string newHash = Utils.ComputeHash(proxyFile.TempFile); - _newCache!.FileHashes[proxyFile.TargetFile] = newHash; - - if (!File.Exists(proxyFile.TargetFile)) - { - cause = $"the output file didn't exist"; - return true; - } - - string? oldHash; - if (!_oldCache!.FileHashes.TryGetValue(proxyFile.TargetFile, out oldHash)) - oldHash = Utils.ComputeHash(proxyFile.TargetFile); - - if (oldHash != newHash) - { - cause = $"hash for the file changed"; - return true; - } - - return false; - } - - public bool Save(string? cacheFilePath) - { - if (!Enabled || string.IsNullOrEmpty(cacheFilePath)) - return false; - - var json = JsonSerializer.Serialize (_newCache, new JsonSerializerOptions { WriteIndented = true }); - File.WriteAllText(cacheFilePath!, json); - return true; - } - - public ProxyFile NewFile(string targetFile) => new ProxyFile(targetFile, this); -} - -internal sealed class ProxyFile -{ - public string TargetFile { get; } - public string TempFile { get; } - private FileCache _cache; - - public ProxyFile(string targetFile, FileCache cache) - { - _cache = cache; - this.TargetFile = targetFile; - this.TempFile = _cache.Enabled ? targetFile + ".tmp" : targetFile; - } - - public bool CopyOutputFileIfChanged() - { - if (!_cache.Enabled) - return true; - - if (!File.Exists(TempFile)) - throw new LogAsErrorException($"Could not find the temporary file {TempFile} for target file {TargetFile}. Look for any errors/warnings generated earlier in the build."); - - try - { - if (!_cache.ShouldCopy(this, out string? cause)) - { - _cache.Log.LogMessage(MessageImportance.Low, $"Skipping copying over {TargetFile} as the contents are unchanged"); - return false; - } - - if (File.Exists(TargetFile)) - File.Delete(TargetFile); - - File.Copy(TempFile, TargetFile); - - _cache.Log.LogMessage(MessageImportance.Low, $"Copying {TempFile} to {TargetFile} because {cause}"); - return true; - } - finally - { - _cache.Log.LogMessage(MessageImportance.Low, $"Deleting temp file {TempFile}"); - File.Delete(TempFile); - } - } - -} - public enum MonoAotMode { Normal, @@ -1306,13 +1095,3 @@ public enum MonoAotModulesTableLanguage C, ObjC } - -internal sealed class CompilerCache -{ - public CompilerCache() => FileHashes = new(); - public CompilerCache(IDictionary oldHashes) - => FileHashes = new(oldHashes); - - [JsonPropertyName("file_hashes")] - public ConcurrentDictionary FileHashes { get; set; } -} diff --git a/src/tasks/AotCompilerTask/MonoAOTCompiler.csproj b/src/tasks/AotCompilerTask/MonoAOTCompiler.csproj index 87a6349afea5e..28e5cc265c80a 100644 --- a/src/tasks/AotCompilerTask/MonoAOTCompiler.csproj +++ b/src/tasks/AotCompilerTask/MonoAOTCompiler.csproj @@ -18,6 +18,9 @@ + + + diff --git a/src/tasks/Common/CompilerCache.cs b/src/tasks/Common/CompilerCache.cs new file mode 100644 index 0000000000000..1d28125511996 --- /dev/null +++ b/src/tasks/Common/CompilerCache.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Build.Utilities; + +#nullable enable + +internal sealed class CompilerCache +{ + public CompilerCache() => FileHashes = new(); + public CompilerCache(IDictionary oldHashes) + => FileHashes = new(oldHashes); + + [JsonPropertyName("file_hashes")] + public ConcurrentDictionary FileHashes { get; set; } +} diff --git a/src/tasks/Common/FileCache.cs b/src/tasks/Common/FileCache.cs new file mode 100644 index 0000000000000..5f090693f7cee --- /dev/null +++ b/src/tasks/Common/FileCache.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Text.Json; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +#nullable enable + +internal sealed class FileCache +{ + private CompilerCache? _newCache; + private CompilerCache? _oldCache; + + public bool Enabled { get; } + public TaskLoggingHelper Log { get; } + + public FileCache(string? cacheFilePath, TaskLoggingHelper log) + { + Log = log; + if (string.IsNullOrEmpty(cacheFilePath)) + { + Log.LogMessage(MessageImportance.Low, $"Disabling cache, because CacheFilePath is not set"); + return; + } + + //Enabled = true; + if (File.Exists(cacheFilePath)) + { + _oldCache = (CompilerCache?)JsonSerializer.Deserialize(File.ReadAllText(cacheFilePath), + typeof(CompilerCache), + new JsonSerializerOptions()); + } + + _oldCache ??= new(); + _newCache = new(_oldCache.FileHashes); + } + + public bool UpdateAndCheckHasFileChanged(string filePath, string newHash) + { + if (!Enabled) + throw new InvalidOperationException("Cache is not enabled. Make sure the cache file path is set"); + + _newCache!.FileHashes[filePath] = newHash; + return !_oldCache!.FileHashes.TryGetValue(filePath, out string? oldHash) || oldHash != newHash; + } + + public bool ShouldCopy(ProxyFile proxyFile, [NotNullWhen(true)] out string? cause) + { + if (!Enabled) + throw new InvalidOperationException("Cache is not enabled. Make sure the cache file path is set"); + + cause = null; + + string newHash = Utils.ComputeHash(proxyFile.TempFile); + _newCache!.FileHashes[proxyFile.TargetFile] = newHash; + + if (!File.Exists(proxyFile.TargetFile)) + { + cause = $"the output file didn't exist"; + return true; + } + + string? oldHash; + if (!_oldCache!.FileHashes.TryGetValue(proxyFile.TargetFile, out oldHash)) + oldHash = Utils.ComputeHash(proxyFile.TargetFile); + + if (oldHash != newHash) + { + cause = $"hash for the file changed"; + return true; + } + + return false; + } + + public bool Save(string? cacheFilePath) + { + if (!Enabled || string.IsNullOrEmpty(cacheFilePath)) + return false; + + var json = JsonSerializer.Serialize (_newCache, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(cacheFilePath!, json); + return true; + } + + public ProxyFile NewFile(string targetFile) => new ProxyFile(targetFile, this); +} diff --git a/src/tasks/Common/ProxyFile.cs b/src/tasks/Common/ProxyFile.cs new file mode 100644 index 0000000000000..ec38b3eb3c6d1 --- /dev/null +++ b/src/tasks/Common/ProxyFile.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.IO; +using System.Text.Json; +using Microsoft.Build.Framework; + +#nullable enable + +internal sealed class ProxyFile +{ + public string TargetFile { get; } + public string TempFile { get; } + private FileCache _cache; + + public ProxyFile(string targetFile, FileCache cache) + { + _cache = cache; + this.TargetFile = targetFile; + this.TempFile = _cache.Enabled ? targetFile + ".tmp" : targetFile; + } + + public bool CopyOutputFileIfChanged() + { + if (!_cache.Enabled) + return true; + + if (!File.Exists(TempFile)) + throw new LogAsErrorException($"Could not find the temporary file {TempFile} for target file {TargetFile}. Look for any errors/warnings generated earlier in the build."); + + try + { + if (!_cache.ShouldCopy(this, out string? cause)) + { + _cache.Log.LogMessage(MessageImportance.Low, $"Skipping copying over {TargetFile} as the contents are unchanged"); + return false; + } + + if (File.Exists(TargetFile)) + File.Delete(TargetFile); + + File.Copy(TempFile, TargetFile); + + _cache.Log.LogMessage(MessageImportance.Low, $"Copying {TempFile} to {TargetFile} because {cause}"); + return true; + } + finally + { + _cache.Log.LogMessage(MessageImportance.Low, $"Deleting temp file {TempFile}"); + File.Delete(TempFile); + } + } +} diff --git a/src/tasks/MonoTargetsTasks/MonoTargetsTasks.csproj b/src/tasks/MonoTargetsTasks/MonoTargetsTasks.csproj index d470c822c08e9..a37d59c75ec5a 100644 --- a/src/tasks/MonoTargetsTasks/MonoTargetsTasks.csproj +++ b/src/tasks/MonoTargetsTasks/MonoTargetsTasks.csproj @@ -29,6 +29,8 @@ + + diff --git a/src/tasks/MonoTargetsTasks/NetTraceToMibcConverterTask/NetTraceToMibcConverter.cs b/src/tasks/MonoTargetsTasks/NetTraceToMibcConverterTask/NetTraceToMibcConverter.cs new file mode 100644 index 0000000000000..205b115a4685e --- /dev/null +++ b/src/tasks/MonoTargetsTasks/NetTraceToMibcConverterTask/NetTraceToMibcConverter.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using System.Text.Json.Serialization; + +#nullable enable + +public class NetTraceToMibcConverter : ToolTask +{ + /// + /// List of all assemblies referenced in a .nettrace file. Important when you run traces against an executable on a different machine / device + /// + [Required] + public ITaskItem[] Assemblies { get; set; } = Array.Empty(); + + /// + /// Path to .nettrace file which should be converted to .mibc + /// + [Required] + public string NetTraceFilePath { get; set; } = ""; + + /// + /// Directory where the mibc file will be placed + /// + [NotNull] + [Required] + public string? OutputDir { get; set; } + + /// + /// The path to the mibc file generated from the converter. + /// + [Output] + public string MibcFilePath { get; set; } = ""; + + public override string ToolExe { get; set; } = ""; + + protected override string ToolName { get; } = "NetTraceToMibcConverter"; + + protected override string GenerateFullPathToTool() + { + return ToolPath; + } + + protected override bool ValidateParameters() + { + if (string.IsNullOrEmpty(ToolPath)) + { + Log.LogError($"{nameof(ToolPath)}='{ToolPath}' must be specified."); + return false; + } + + if (string.IsNullOrEmpty(ToolExe)) + { + ToolExe = (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) ? "dotnet-pgo.exe" : "dotnet-pgo"; + } + + string mibcConverterBinaryPath = Path.Combine(ToolPath, ToolExe); + + if (!File.Exists(mibcConverterBinaryPath)) + { + Log.LogError($"{nameof(mibcConverterBinaryPath)}='{mibcConverterBinaryPath}' doesn't exist."); + return false; + } + + if (Assemblies.Length == 0) + { + Log.LogError($"'{nameof(Assemblies)}' is required."); + return false; + } + + if (!File.Exists(NetTraceFilePath)) + { + Log.LogError($"{nameof(NetTraceFilePath)}='{NetTraceFilePath}' doesn't exist"); + return false; + } + + foreach (var asmItem in Assemblies) + { + string? fullPath = asmItem.GetMetadata("FullPath"); + if (!File.Exists(fullPath)) + throw new LogAsErrorException($"Could not find {fullPath} to AOT"); + } + + return !Log.HasLoggedErrors; + } + + protected override string GenerateCommandLineCommands() + { + MibcFilePath = Path.Combine(OutputDir, Path.ChangeExtension(Path.GetFileName(NetTraceFilePath), ".mibc")); + + StringBuilder mibcConverterArgsStr = new StringBuilder("create-mibc"); + mibcConverterArgsStr.Append($" --trace \"{NetTraceFilePath}\" "); + + foreach (var refAsmItem in Assemblies) + { + string? fullPath = refAsmItem.GetMetadata("FullPath"); + mibcConverterArgsStr.Append($" --reference \"{fullPath}\" "); + } + + mibcConverterArgsStr.Append($" --output \"{MibcFilePath}\""); + + return mibcConverterArgsStr.ToString(); + } +}