From 0cb855adf9945615d60234dd6870e183ef910346 Mon Sep 17 00:00:00 2001
From: Tom Edwards <109803929+TomEdwardsEnscape@users.noreply.github.com>
Date: Thu, 22 Feb 2024 21:26:28 +0100
Subject: [PATCH] Fix "original.dll" file permission errors during build
(#13840)
* Avoid constantly recompiling Avalonia Xaml files by implementing incremental build checks
- Should be a (or even the) fix for file permission errors during builds
CompileAvaloniaXaml no longer overwrites the Compile output, but creates its own output files
- This supports incremental build checks and is safer in general
Removed unused executable features from Avalonia.Build.Tasks
- This is instead of refactoring for the new ITaskItem properties
Updated Desktop SLNF
* Fixed tests
* Touch each copied output file
---
Avalonia.Desktop.slnf | 5 +-
packages/Avalonia/AvaloniaBuildTasks.targets | 79 ++++++++----
.../Avalonia.Build.Tasks.csproj | 1 -
.../CompileAvaloniaXamlTask.cs | 116 ++++++------------
src/Avalonia.Build.Tasks/Program.cs | 86 -------------
.../Avalonia.Build.Tasks.UnitTest.csproj | 1 +
.../CompileAvaloniaXamlTaskTest.cs | 17 ++-
7 files changed, 104 insertions(+), 201 deletions(-)
delete mode 100644 src/Avalonia.Build.Tasks/Program.cs
diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf
index c63d55f2b4..794cdf5d3b 100644
--- a/Avalonia.Desktop.slnf
+++ b/Avalonia.Desktop.slnf
@@ -51,6 +51,7 @@
"src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj",
"tests\\Avalonia.Base.UnitTests\\Avalonia.Base.UnitTests.csproj",
"tests\\Avalonia.Benchmarks\\Avalonia.Benchmarks.csproj",
+ "tests\\Avalonia.Build.Tasks.UnitTest\\Avalonia.Build.Tasks.UnitTest.csproj",
"tests\\Avalonia.Controls.DataGrid.UnitTests\\Avalonia.Controls.DataGrid.UnitTests.csproj",
"tests\\Avalonia.Controls.ItemsRepeater.UnitTests\\Avalonia.Controls.ItemsRepeater.UnitTests.csproj",
"tests\\Avalonia.Controls.UnitTests\\Avalonia.Controls.UnitTests.csproj",
@@ -58,6 +59,7 @@
"tests\\Avalonia.DesignerSupport.Tests\\Avalonia.DesignerSupport.Tests.csproj",
"tests\\Avalonia.Direct2D1.RenderTests\\Avalonia.Direct2D1.RenderTests.csproj",
"tests\\Avalonia.Direct2D1.UnitTests\\Avalonia.Direct2D1.UnitTests.csproj",
+ "tests\\Avalonia.Generators.Tests\\Avalonia.Generators.Tests.csproj",
"tests\\Avalonia.IntegrationTests.Appium\\Avalonia.IntegrationTests.Appium.csproj",
"tests\\Avalonia.LeakTests\\Avalonia.LeakTests.csproj",
"tests\\Avalonia.Markup.UnitTests\\Avalonia.Markup.UnitTests.csproj",
@@ -65,7 +67,8 @@
"tests\\Avalonia.ReactiveUI.UnitTests\\Avalonia.ReactiveUI.UnitTests.csproj",
"tests\\Avalonia.Skia.RenderTests\\Avalonia.Skia.RenderTests.csproj",
"tests\\Avalonia.Skia.UnitTests\\Avalonia.Skia.UnitTests.csproj",
- "tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj"
+ "tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj",
+ "tests\\TestFiles\\BuildTasks\\PInvoke\\PInvoke.csproj"
]
}
}
\ No newline at end of file
diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets
index ef5658f1ca..eef6d51cae 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.targets
+++ b/packages/Avalonia/AvaloniaBuildTasks.targets
@@ -58,7 +58,7 @@
$(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences;_GenerateAvaloniaResourcesDependencyCache;_GenerateNoWarnForExec
- $(CompileAvaloniaXamlDependsOn);_GenerateNoWarnForExec
+ $(CompileAvaloniaXamlDependsOn);PrepareToCompileAvaloniaXaml;_GenerateNoWarnForExec
@@ -114,38 +114,44 @@
Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:NoWarn=$(_NoWarnForExec) /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:BuildProjectReferences=false"/>
-
+
$(IntermediateOutputPath)/Avalonia/references
- $(IntermediateOutputPath)/Avalonia/original.dll
false
false
false
<_AvaloniaHasCompiledXaml>true
-
-
+
+
+
+ <_DebugSymbolsIntermediatePath Update="*" AvaloniaCompileOutput="%(RelativeDir)Avalonia\%(Filename)%(Extension)"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+ AnalyzerConfigFiles="@(EditorConfigFiles)"/>
+
+
+
+
+
+
+ <_AvaloniaXamlCompiledAssembly Include="@(IntermediateAssembly->Metadata('AvaloniaCompileOutput'))"/>
+
+
+
+ <_AvaloniaXamlCompiledRefAssembly Include="@(IntermediateRefAssembly->Metadata('AvaloniaCompileOutput'))"/>
+
+
+ <_AvaloniaXamlCompiledSymbols Include="@(_DebugSymbolsIntermediatePath->Metadata('AvaloniaCompileOutput'))"/>
+ <_DebugSymbolsIntermediatePath Remove="@(_DebugSymbolsIntermediatePath)"/>
+ <_DebugSymbolsIntermediatePath Include="@(_AvaloniaXamlCompiledSymbols)"/>
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
index 3d83ca6a50..49b9e7cddc 100644
--- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
+++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
@@ -1,7 +1,6 @@
netstandard2.0
- exe
false
tools
$(DefineConstants);BUILDTASK;XAMLX_CECIL_INTERNAL;XAMLX_INTERNAL
diff --git a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
index 5287827652..8cd444d30b 100644
--- a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
+++ b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
@@ -8,110 +7,70 @@ namespace Avalonia.Build.Tasks
{
public class CompileAvaloniaXamlTask: ITask
{
+ public const string AvaloniaCompileOutputMetadataName = "AvaloniaCompileOutput";
+
public bool Execute()
{
Enum.TryParse(ReportImportance, true, out MessageImportance outputImportance);
- var writtenFilePaths = new List();
-
- OutputPath ??= AssemblyFile;
- RefOutputPath ??= RefAssemblyFile;
- var outputPdb = GetPdbPath(OutputPath);
- var input = AssemblyFile;
- var refInput = RefOutputPath;
- var inputPdb = GetPdbPath(input);
- // Make a copy and delete the original file to prevent MSBuild from thinking that everything is OK
- if (OriginalCopyPath != null)
- {
- var originalCopyPathRef = Path.ChangeExtension(OriginalCopyPath, ".ref.dll");
- File.Copy(AssemblyFile, OriginalCopyPath, true);
- writtenFilePaths.Add(OriginalCopyPath);
- input = OriginalCopyPath;
- File.Delete(AssemblyFile);
+ var outputPath = AssemblyFile.GetMetadata(AvaloniaCompileOutputMetadataName);
+ var refOutputPath = RefAssemblyFile?.GetMetadata(AvaloniaCompileOutputMetadataName);
- if (File.Exists(inputPdb))
- {
- var copyPdb = GetPdbPath(OriginalCopyPath);
- File.Copy(inputPdb, copyPdb, true);
- writtenFilePaths.Add(copyPdb);
- File.Delete(inputPdb);
- inputPdb = copyPdb;
- }
-
- if (!string.IsNullOrWhiteSpace(RefAssemblyFile) && File.Exists(RefAssemblyFile))
- {
- // We also copy ref assembly just for case if needed later for testing.
- // But do not remove the original one, as MSBuild actually complains about it with multi-thread compiling.
- File.Copy(RefAssemblyFile, originalCopyPathRef, true);
- writtenFilePaths.Add(originalCopyPathRef);
- refInput = originalCopyPathRef;
- }
+ Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
+ if (!string.IsNullOrEmpty(refOutputPath))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(refOutputPath));
}
- var msg = $"CompileAvaloniaXamlTask -> AssemblyFile:{AssemblyFile}, ProjectDirectory:{ProjectDirectory}, OutputPath:{OutputPath}";
+ var msg = $"CompileAvaloniaXamlTask -> AssemblyFile:{AssemblyFile}, ProjectDirectory:{ProjectDirectory}, OutputPath:{outputPath}";
BuildEngine.LogMessage(msg, outputImportance < MessageImportance.Low ? MessageImportance.High : outputImportance);
var res = XamlCompilerTaskExecutor.Compile(BuildEngine,
- input, OutputPath,
- refInput, RefOutputPath,
- File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
+ AssemblyFile.ItemSpec, outputPath,
+ RefAssemblyFile?.ItemSpec, refOutputPath,
+ References?.Select(i => i.ItemSpec).ToArray() ?? Array.Empty(),
ProjectDirectory, VerifyIl, DefaultCompileBindings, outputImportance,
new XamlCompilerDiagnosticsFilter(AnalyzerConfigFiles),
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null,
SkipXamlCompilation, DebuggerLaunch, VerboseExceptions);
- if (!res.Success)
- {
- WrittenFilePaths = writtenFilePaths.ToArray();
- return false;
- }
- if (!res.WrittenFile)
+ if (res.Success && !res.WrittenFile)
{
- File.Copy(input, OutputPath, true);
- if (File.Exists(inputPdb))
- File.Copy(inputPdb, outputPdb, true);
- }
- else if (!string.IsNullOrWhiteSpace(RefOutputPath) && File.Exists(RefOutputPath))
- writtenFilePaths.Add(RefOutputPath);
+ // To simplify incremental build checks, copy the input files to the expected output locations even if the Xaml compiler didn't do anything.
+ CopyAndTouch(AssemblyFile.ItemSpec, outputPath);
+ CopyAndTouch(Path.ChangeExtension(AssemblyFile.ItemSpec, ".pdb"), Path.ChangeExtension(outputPath, ".pdb"));
- writtenFilePaths.Add(OutputPath);
- if (File.Exists(outputPdb))
- writtenFilePaths.Add(outputPdb);
+ if (!string.IsNullOrEmpty(refOutputPath))
+ {
+ CopyAndTouch(RefAssemblyFile.ItemSpec, refOutputPath);
+ }
+ }
- WrittenFilePaths = writtenFilePaths.ToArray();
- return true;
+ return res.Success;
}
- string GetPdbPath(string p)
+ private static void CopyAndTouch(string source, string destination)
{
- var d = Path.GetDirectoryName(p);
- var f = Path.GetFileNameWithoutExtension(p);
- var rv = f + ".pdb";
- if (d != null)
- rv = Path.Combine(d, rv);
- return rv;
+ File.Copy(source, destination, overwrite: true);
+ File.SetLastWriteTimeUtc(destination, DateTime.UtcNow);
}
-
- [Required]
- public string AssemblyFile { get; set; }
- [Required]
- public string ReferencesFilePath { get; set; }
- [Required]
- public string OriginalCopyPath { get; set; }
+
[Required]
public string ProjectDirectory { get; set; }
-
- public string RefAssemblyFile { get; set; }
- public string RefOutputPath { get; set; }
-
- public string OutputPath { get; set; }
+
+ [Required]
+ public ITaskItem AssemblyFile { get; set; }
+
+ public ITaskItem? RefAssemblyFile { get; set; }
+
+ public ITaskItem[]? References { get; set; }
public bool VerifyIl { get; set; }
public bool DefaultCompileBindings { get; set; }
-
+
public bool SkipXamlCompilation { get; set; }
-
+
public string AssemblyOriginatorKeyFile { get; set; }
public bool SignAssembly { get; set; }
public bool DelaySign { get; set; }
@@ -124,10 +83,7 @@ namespace Avalonia.Build.Tasks
public bool DebuggerLaunch { get; set; }
public bool VerboseExceptions { get; set; }
-
+
public ITaskItem[] AnalyzerConfigFiles { get; set; }
-
- [Output]
- public string[] WrittenFilePaths { get; private set; } = Array.Empty();
}
}
diff --git a/src/Avalonia.Build.Tasks/Program.cs b/src/Avalonia.Build.Tasks/Program.cs
deleted file mode 100644
index 8ddea2d142..0000000000
--- a/src/Avalonia.Build.Tasks/Program.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using System;
-using System.Collections;
-using System.IO;
-using System.Linq;
-using Microsoft.Build.Framework;
-
-namespace Avalonia.Build.Tasks
-{
- public class Program
- {
- private const string OriginalDll = "original.dll";
- private const string References = "references";
- private const string OutDll = "out.dll";
-
- static int Main(string[] args)
- {
- if (args.Length < 3)
- {
- if (args.Length == 1)
- {
- args = new[] {OriginalDll, References, OutDll}
- .Select(x => Path.Combine(args[0], x)).ToArray();
- }
- else
- {
- const string referencesOutputPath = "path/to/Avalonia/samples/Sandbox/obj/Debug/net60/Avalonia";
- Console.WriteLine(@$"Usage:
- 1) dotnet ./Avalonia.Build.Tasks.dll
- , where likes {referencesOutputPath}
- 2) dotnet ./Avalonia.Build.Tasks.dll
- , where:
- - likes {referencesOutputPath}/{OriginalDll}
- - likes {referencesOutputPath}/{References}
- - likes {referencesOutputPath}/{OutDll}
- - Likes {referencesOutputPath}/original.ref.dll");
-
- return 1;
- }
- }
-
- return new CompileAvaloniaXamlTask()
- {
- AssemblyFile = args[0],
- ReferencesFilePath = args[1],
- OutputPath = args[2],
- RefAssemblyFile = args.Length > 3 ? args[3] : null,
- BuildEngine = new ConsoleBuildEngine(),
- ProjectDirectory = Directory.GetCurrentDirectory(),
- VerifyIl = true
- }.Execute() ?
- 0 :
- 2;
- }
-
- class ConsoleBuildEngine : IBuildEngine
- {
- public void LogErrorEvent(BuildErrorEventArgs e)
- {
- Console.WriteLine($"ERROR: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
- }
-
- public void LogWarningEvent(BuildWarningEventArgs e)
- {
- Console.WriteLine($"WARNING: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
- }
-
- public void LogMessageEvent(BuildMessageEventArgs e)
- {
- Console.WriteLine($"MESSAGE: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
- }
-
- public void LogCustomEvent(CustomBuildEventArgs e)
- {
- Console.WriteLine($"CUSTOM: {e.Message}");
- }
-
- public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties,
- IDictionary targetOutputs) => throw new NotSupportedException();
-
- public bool ContinueOnError { get; }
- public int LineNumberOfTaskNode { get; }
- public int ColumnNumberOfTaskNode { get; }
- public string ProjectFileOfTaskNode { get; }
- }
- }
-}
diff --git a/tests/Avalonia.Build.Tasks.UnitTest/Avalonia.Build.Tasks.UnitTest.csproj b/tests/Avalonia.Build.Tasks.UnitTest/Avalonia.Build.Tasks.UnitTest.csproj
index 0a289ff28b..7182b0d0f7 100644
--- a/tests/Avalonia.Build.Tasks.UnitTest/Avalonia.Build.Tasks.UnitTest.csproj
+++ b/tests/Avalonia.Build.Tasks.UnitTest/Avalonia.Build.Tasks.UnitTest.csproj
@@ -25,6 +25,7 @@
+
diff --git a/tests/Avalonia.Build.Tasks.UnitTest/CompileAvaloniaXamlTaskTest.cs b/tests/Avalonia.Build.Tasks.UnitTest/CompileAvaloniaXamlTaskTest.cs
index fee678c388..115457926b 100644
--- a/tests/Avalonia.Build.Tasks.UnitTest/CompileAvaloniaXamlTaskTest.cs
+++ b/tests/Avalonia.Build.Tasks.UnitTest/CompileAvaloniaXamlTaskTest.cs
@@ -1,6 +1,8 @@
using System;
using System.IO;
+using System.Linq;
using System.Reflection;
+using Microsoft.Build.Utilities;
using Xunit;
namespace Avalonia.Build.Tasks.UnitTest;
@@ -13,19 +15,16 @@ public class CompileAvaloniaXamlTaskTest
{
using var engine = UnitTestBuildEngine.Start();
var basePath = Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath), "Assets");
- var originalAssemblyPath = Path.Combine(basePath,
- "PInvoke.dll");
- var referencesPath = Path.Combine(basePath,
- "PInvoke.dll.refs");
- var compiledAssemblyPath = "PInvoke.dll";
+ var assembly = new TaskItem(Path.Combine(basePath, "PInvoke.dll"));
+ assembly.SetMetadata(CompileAvaloniaXamlTask.AvaloniaCompileOutputMetadataName, Path.Combine(basePath, "Avalonia", Path.GetFileName(assembly.ItemSpec)));
+ var references = File.ReadAllLines(Path.Combine(basePath, "PInvoke.dll.refs")).Select(p => new TaskItem(p)).ToArray();
- Assert.True(File.Exists(originalAssemblyPath), $"The original {originalAssemblyPath} don't exists.");
+ Assert.True(File.Exists(assembly.ItemSpec), $"The original {assembly.ItemSpec} don't exist.");
new CompileAvaloniaXamlTask()
{
- AssemblyFile = originalAssemblyPath,
- ReferencesFilePath = referencesPath,
- OutputPath = compiledAssemblyPath,
+ AssemblyFile = assembly,
+ References = references,
RefAssemblyFile = null,
BuildEngine = engine,
ProjectDirectory = Directory.GetCurrentDirectory(),