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();
+ }
+}