Skip to content

Commit 223cfab

Browse files
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
1 parent a74a284 commit 223cfab

File tree

4 files changed

+84
-189
lines changed

4 files changed

+84
-189
lines changed

packages/Avalonia/AvaloniaBuildTasks.targets

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858

5959
<PropertyGroup>
6060
<BuildAvaloniaResourcesDependsOn>$(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences;_GenerateAvaloniaResourcesDependencyCache;_GenerateNoWarnForExec</BuildAvaloniaResourcesDependsOn>
61-
<CompileAvaloniaXamlDependsOn>$(CompileAvaloniaXamlDependsOn);_GenerateNoWarnForExec</CompileAvaloniaXamlDependsOn>
61+
<CompileAvaloniaXamlDependsOn>$(CompileAvaloniaXamlDependsOn);PrepareToCompileAvaloniaXaml;_GenerateNoWarnForExec</CompileAvaloniaXamlDependsOn>
6262
</PropertyGroup>
6363

6464
<Target Name="_GenerateAvaloniaResourcesDependencyCache" BeforeTargets="GenerateAvaloniaResources">
@@ -114,38 +114,44 @@
114114
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"/>
115115
</Target>
116116

117-
<Target
118-
Name="CompileAvaloniaXaml"
119-
AfterTargets="AfterCompile"
120-
DependsOnTargets="$(CompileAvaloniaXamlDependsOn)"
121-
Condition="
122-
(('@(AvaloniaResource->Count())' &gt; 0)
123-
or ('@(AvaloniaXaml->Count())' &gt; 0))
124-
and Exists('@(IntermediateAssembly)')
125-
And $(DesignTimeBuild) != true
126-
And $(EnableAvaloniaXamlCompilation) != false"
127-
>
117+
<Target Name="PrepareToCompileAvaloniaXaml">
128118
<PropertyGroup>
129119
<AvaloniaXamlReferencesTemporaryFilePath Condition="'$(AvaloniaXamlReferencesTemporaryFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/references</AvaloniaXamlReferencesTemporaryFilePath>
130-
<AvaloniaXamlOriginalCopyFilePath Condition="'$(AvaloniaXamlOriginalCopyFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/original.dll</AvaloniaXamlOriginalCopyFilePath>
131120
<AvaloniaXamlIlVerifyIl Condition="'$(AvaloniaXamlIlVerifyIl)' == ''">false</AvaloniaXamlIlVerifyIl>
132121
<AvaloniaXamlIlDebuggerLaunch Condition="'$(AvaloniaXamlIlDebuggerLaunch)' == ''">false</AvaloniaXamlIlDebuggerLaunch>
133122
<AvaloniaXamlVerboseExceptions Condition="'$(AvaloniaXamlVerboseExceptions)' == ''">false</AvaloniaXamlVerboseExceptions>
134123
<_AvaloniaHasCompiledXaml>true</_AvaloniaHasCompiledXaml>
135124
</PropertyGroup>
136-
<WriteLinesToFile
137-
Condition="'$(_AvaloniaForceInternalMSBuild)' != 'true'"
138-
File="$(AvaloniaXamlReferencesTemporaryFilePath)"
139-
Lines="@(ReferencePathWithRefAssemblies)"
140-
Overwrite="true" />
141-
<ItemGroup Condition="'$(_AvaloniaForceInternalMSBuild)' != 'true'">
125+
126+
<ItemGroup>
127+
<IntermediateAssembly Update="*" AvaloniaCompileOutput="%(RelativeDir)Avalonia\%(Filename)%(Extension)"/>
128+
<_DebugSymbolsIntermediatePath Update="*" AvaloniaCompileOutput="%(RelativeDir)Avalonia\%(Filename)%(Extension)"/>
129+
<IntermediateRefAssembly Update="*" AvaloniaCompileOutput="%(RelativeDir)Avalonia\%(Filename)%(Extension)"/>
130+
<CompileAvaloniaXamlInputs Include="@(IntermediateAssembly);@(_DebugSymbolsIntermediatePath);@(IntermediateRefAssembly)"/>
131+
<CompileAvaloniaXamlOutputs Include="@(CompileAvaloniaXamlInputs->'%(AvaloniaCompileOutput)')"/>
132+
<CompileAvaloniaXamlInputs Include="@(AvaloniaResource);@(AvaloniaXaml)"/>
133+
134+
<FileWrites Include="@(CompileAvaloniaXamlOutputs)"/>
142135
<FileWrites Include="$(AvaloniaXamlReferencesTemporaryFilePath)" />
143136
</ItemGroup>
137+
</Target>
138+
139+
<Target
140+
Name="CompileAvaloniaXaml"
141+
AfterTargets="AfterCompile"
142+
DependsOnTargets="$(CompileAvaloniaXamlDependsOn)"
143+
Inputs="@(CompileAvaloniaXamlInputs)"
144+
Outputs="@(CompileAvaloniaXamlOutputs)"
145+
Condition="'@(AvaloniaResource)@(AvaloniaXaml)' != '' AND $(DesignTimeBuild) != true AND $(EnableAvaloniaXamlCompilation) != false">
146+
147+
<ReadLinesFromFile File="$(AvaloniaXamlReferencesTemporaryFilePath)" Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true' AND '@(ReferencePathWithRefAssemblies)' == ''">
148+
<Output TaskParameter="Lines" ItemName="ReferencePathWithRefAssemblies"/>
149+
</ReadLinesFromFile>
150+
144151
<CompileAvaloniaXamlTask
145152
Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'"
146153
AssemblyFile="@(IntermediateAssembly)"
147-
ReferencesFilePath="$(AvaloniaXamlReferencesTemporaryFilePath)"
148-
OriginalCopyPath="$(AvaloniaXamlOriginalCopyFilePath)"
154+
References="@(ReferencePathWithRefAssemblies)"
149155
RefAssemblyFile="@(IntermediateRefAssembly)"
150156
ProjectDirectory="$(MSBuildProjectDirectory)"
151157
VerifyIl="$(AvaloniaXamlIlVerifyIl)"
@@ -157,14 +163,39 @@
157163
DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
158164
DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)"
159165
VerboseExceptions="$(AvaloniaXamlVerboseExceptions)"
160-
AnalyzerConfigFiles="@(EditorConfigFiles)">
161-
<Output TaskParameter="WrittenFilePaths" ItemName="FileWrites" />
162-
</CompileAvaloniaXamlTask>
166+
AnalyzerConfigFiles="@(EditorConfigFiles)"/>
167+
168+
<WriteLinesToFile File="$(AvaloniaXamlReferencesTemporaryFilePath)" Lines="@(ReferencePathWithRefAssemblies)" Overwrite="true"
169+
Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'" />
170+
163171
<Exec
164172
Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"
165173
Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileAvaloniaXaml /p:NoWarn=$(_NoWarnForExec) /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:BuildProjectReferences=false"/>
166174
</Target>
175+
176+
<Target Name="InjectAvaloniaXamlOutput" DependsOnTargets="PrepareToCompileAvaloniaXaml" AfterTargets="CompileAvaloniaXaml" BeforeTargets="CopyFilesToOutputDirectory;BuiltProjectOutputGroup"
177+
Condition="'@(AvaloniaResource)@(AvaloniaXaml)' != '' AND $(EnableAvaloniaXamlCompilation) != false">
178+
<ItemGroup>
179+
<_AvaloniaXamlCompiledAssembly Include="@(IntermediateAssembly->Metadata('AvaloniaCompileOutput'))"/>
180+
<IntermediateAssembly Remove="@(IntermediateAssembly)"/>
181+
<IntermediateAssembly Include="@(_AvaloniaXamlCompiledAssembly)"/>
182+
183+
<_AvaloniaXamlCompiledRefAssembly Include="@(IntermediateRefAssembly->Metadata('AvaloniaCompileOutput'))"/>
184+
<IntermediateRefAssembly Remove="@(IntermediateRefAssembly)"/>
185+
<IntermediateRefAssembly Include="@(_AvaloniaXamlCompiledRefAssembly)"/>
167186

187+
<_AvaloniaXamlCompiledSymbols Include="@(_DebugSymbolsIntermediatePath->Metadata('AvaloniaCompileOutput'))"/>
188+
<_DebugSymbolsIntermediatePath Remove="@(_DebugSymbolsIntermediatePath)"/>
189+
<_DebugSymbolsIntermediatePath Include="@(_AvaloniaXamlCompiledSymbols)"/>
190+
</ItemGroup>
191+
</Target>
192+
193+
<Target Name="Avalonia_CollectUpToDateCheckOutputDesignTime" Condition="'@(AvaloniaResource)@(AvaloniaXaml)' != '' AND $(EnableAvaloniaXamlCompilation) != false"
194+
BeforeTargets="CollectUpToDateCheckOutputDesignTime" DependsOnTargets="PrepareToCompileAvaloniaXaml">
195+
<ItemGroup>
196+
<UpToDateCheckOutput Include="@(CompileAvaloniaXamlOutputs)"/>
197+
</ItemGroup>
198+
</Target>
168199

169200
<ItemGroup>
170201
<UpToDateCheckInput Include="@(AvaloniaResource)" />

src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<TargetFrameworks>netstandard2.0</TargetFrameworks>
4-
<OutputType>exe</OutputType>
54
<GenerateDocumentationFile>false</GenerateDocumentationFile>
65
<BuildOutputTargetFolder>tools</BuildOutputTargetFolder>
76
<DefineConstants>$(DefineConstants);BUILDTASK;XAMLX_CECIL_INTERNAL;XAMLX_INTERNAL</DefineConstants>

src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs

Lines changed: 29 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -8,103 +8,57 @@ namespace Avalonia.Build.Tasks
88
{
99
public class CompileAvaloniaXamlTask: ITask
1010
{
11+
public const string AvaloniaCompileOutputMetadataName = "AvaloniaCompileOutput";
12+
1113
public bool Execute()
1214
{
1315
Enum.TryParse(ReportImportance, true, out MessageImportance outputImportance);
14-
var writtenFilePaths = new List<string>();
15-
16-
OutputPath ??= AssemblyFile;
17-
RefOutputPath ??= RefAssemblyFile;
18-
var outputPdb = GetPdbPath(OutputPath);
19-
var input = AssemblyFile;
20-
var refInput = RefOutputPath;
21-
var inputPdb = GetPdbPath(input);
22-
// Make a copy and delete the original file to prevent MSBuild from thinking that everything is OK
23-
if (OriginalCopyPath != null)
24-
{
25-
var originalCopyPathRef = Path.ChangeExtension(OriginalCopyPath, ".ref.dll");
16+
17+
var outputPath = AssemblyFile.GetMetadata(AvaloniaCompileOutputMetadataName);
18+
var refOutputPath = RefAssemblyFile?.GetMetadata(AvaloniaCompileOutputMetadataName);
2619

27-
File.Copy(AssemblyFile, OriginalCopyPath, true);
28-
writtenFilePaths.Add(OriginalCopyPath);
29-
input = OriginalCopyPath;
30-
File.Delete(AssemblyFile);
31-
32-
if (File.Exists(inputPdb))
33-
{
34-
var copyPdb = GetPdbPath(OriginalCopyPath);
35-
File.Copy(inputPdb, copyPdb, true);
36-
writtenFilePaths.Add(copyPdb);
37-
File.Delete(inputPdb);
38-
inputPdb = copyPdb;
39-
}
40-
41-
if (!string.IsNullOrWhiteSpace(RefAssemblyFile) && File.Exists(RefAssemblyFile))
42-
{
43-
// We also copy ref assembly just for case if needed later for testing.
44-
// But do not remove the original one, as MSBuild actually complains about it with multi-thread compiling.
45-
File.Copy(RefAssemblyFile, originalCopyPathRef, true);
46-
writtenFilePaths.Add(originalCopyPathRef);
47-
refInput = originalCopyPathRef;
48-
}
20+
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
21+
if (!string.IsNullOrEmpty(refOutputPath))
22+
{
23+
Directory.CreateDirectory(Path.GetDirectoryName(refOutputPath));
4924
}
5025

51-
var msg = $"CompileAvaloniaXamlTask -> AssemblyFile:{AssemblyFile}, ProjectDirectory:{ProjectDirectory}, OutputPath:{OutputPath}";
26+
var msg = $"CompileAvaloniaXamlTask -> AssemblyFile:{AssemblyFile}, ProjectDirectory:{ProjectDirectory}, OutputPath:{outputPath}";
5227
BuildEngine.LogMessage(msg, outputImportance < MessageImportance.Low ? MessageImportance.High : outputImportance);
5328

5429
var res = XamlCompilerTaskExecutor.Compile(BuildEngine,
55-
input, OutputPath,
56-
refInput, RefOutputPath,
57-
File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
30+
AssemblyFile.ItemSpec, outputPath,
31+
RefAssemblyFile?.ItemSpec, refOutputPath,
32+
References?.Select(i => i.ItemSpec).ToArray() ?? Array.Empty<string>(),
5833
ProjectDirectory, VerifyIl, DefaultCompileBindings, outputImportance,
5934
new XamlCompilerDiagnosticsFilter(AnalyzerConfigFiles),
6035
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null,
6136
SkipXamlCompilation, DebuggerLaunch, VerboseExceptions);
62-
if (!res.Success)
63-
{
64-
WrittenFilePaths = writtenFilePaths.ToArray();
65-
return false;
66-
}
6737

68-
if (!res.WrittenFile)
38+
if (res.Success && !res.WrittenFile)
6939
{
70-
File.Copy(input, OutputPath, true);
71-
if (File.Exists(inputPdb))
72-
File.Copy(inputPdb, outputPdb, true);
73-
}
74-
else if (!string.IsNullOrWhiteSpace(RefOutputPath) && File.Exists(RefOutputPath))
75-
writtenFilePaths.Add(RefOutputPath);
76-
77-
writtenFilePaths.Add(OutputPath);
78-
if (File.Exists(outputPdb))
79-
writtenFilePaths.Add(outputPdb);
40+
// To simplify incremental build checks, copy the input files to the expected output locations even if the Xaml compiler didn't do anything.
41+
File.Copy(AssemblyFile.ItemSpec, outputPath, overwrite: true);
42+
File.Copy(Path.ChangeExtension(AssemblyFile.ItemSpec, ".pdb"), Path.ChangeExtension(outputPath, ".pdb"), overwrite: true);
8043

81-
WrittenFilePaths = writtenFilePaths.ToArray();
82-
return true;
83-
}
44+
if (!string.IsNullOrEmpty(refOutputPath))
45+
{
46+
File.Copy(RefAssemblyFile.ItemSpec, refOutputPath, overwrite: true);
47+
}
48+
}
8449

85-
string GetPdbPath(string p)
86-
{
87-
var d = Path.GetDirectoryName(p);
88-
var f = Path.GetFileNameWithoutExtension(p);
89-
var rv = f + ".pdb";
90-
if (d != null)
91-
rv = Path.Combine(d, rv);
92-
return rv;
50+
return res.Success;
9351
}
9452

95-
[Required]
96-
public string AssemblyFile { get; set; }
97-
[Required]
98-
public string ReferencesFilePath { get; set; }
99-
[Required]
100-
public string OriginalCopyPath { get; set; }
10153
[Required]
10254
public string ProjectDirectory { get; set; }
10355

104-
public string RefAssemblyFile { get; set; }
105-
public string RefOutputPath { get; set; }
106-
107-
public string OutputPath { get; set; }
56+
[Required]
57+
public ITaskItem AssemblyFile { get; set; }
58+
59+
public ITaskItem? RefAssemblyFile { get; set; }
60+
61+
public ITaskItem[]? References { get; set; }
10862

10963
public bool VerifyIl { get; set; }
11064

@@ -126,8 +80,5 @@ string GetPdbPath(string p)
12680
public bool VerboseExceptions { get; set; }
12781

12882
public ITaskItem[] AnalyzerConfigFiles { get; set; }
129-
130-
[Output]
131-
public string[] WrittenFilePaths { get; private set; } = Array.Empty<string>();
13283
}
13384
}

src/Avalonia.Build.Tasks/Program.cs

Lines changed: 0 additions & 86 deletions
This file was deleted.

0 commit comments

Comments
 (0)