master
targets 235 lines 13.5 KB
Raw
1 <?xml version="1.0" encoding="utf-8"?>
2 <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 <!-- ================================================================== -->
4 <!-- Container Image Build Targets -->
5 <!-- ================================================================== -->
6
7 <!-- Default properties -->
8 <PropertyGroup>
9 <!-- Default to bare 'wslc' so cmd.exe resolves it via PATH (the WSL MSI puts wslc.exe there).
10 Override with WslcCliPath property if needed. -->
11 <WslcCliPath Condition="'$(WslcCliPath)' == ''">wslc</WslcCliPath>
12 <WslcImageBuildPull Condition="'$(WslcImageBuildPull)' == ''">false</WslcImageBuildPull>
13 <WslcImageBuildNoCache Condition="'$(WslcImageBuildNoCache)' == ''">false</WslcImageBuildNoCache>
14 <!-- Unified intermediate directory: $(IntDir) for C++, $(IntermediateOutputPath) for .NET -->
15 <_WslcIntDir Condition="'$(IntDir)' != ''">$(IntDir)</_WslcIntDir>
16 <_WslcIntDir Condition="'$(_WslcIntDir)' == '' AND '$(IntermediateOutputPath)' != ''">$(IntermediateOutputPath)</_WslcIntDir>
17 <_WslcIntDir Condition="'$(_WslcIntDir)' == ''">obj\</_WslcIntDir>
18 </PropertyGroup>
19
20 <!-- Default values for optional WslcImage metadata. Required metadata
21 (Dockerfile, Context) is intentionally not defaulted here so
22 _WslcValidateItems reports it as missing. -->
23 <ItemDefinitionGroup>
24 <WslcImage>
25 <Image></Image>
26 <TarLocation></TarLocation>
27 <BuildArgs></BuildArgs>
28 <Labels></Labels>
29 </WslcImage>
30 </ItemDefinitionGroup>
31
32 <!-- Check that wslc CLI is installed (invoked via DependsOnTargets from WslcBuildAllImages) -->
33 <Target Name="WslcCheckDependencies">
34
35 <Exec Command="&quot;$(WslcCliPath)&quot; --version"
36 IgnoreExitCode="true"
37 ConsoleToMSBuild="true"
38 StandardOutputImportance="low"
39 StandardErrorImportance="low">
40 <Output TaskParameter="ExitCode" PropertyName="_WslcExitCode" />
41 </Exec>
42
43 <Error Condition="'$(_WslcExitCode)' != '0'"
44 Code="WSLC0001"
45 Text="The wslc CLI check failed: '$(WslcCliPath) --version' returned exit code $(_WslcExitCode). Install WSL by running 'wsl --install --no-distribution', or set the WslcCliPath property to a specific wslc.exe path." />
46 </Target>
47
48 <!-- Validate required metadata on WslcImage items (invoked via DependsOnTargets from WslcBuildAllImages) -->
49 <Target Name="_WslcValidateItems">
50
51 <Error Condition="'%(WslcImage.Dockerfile)' == ''"
52 Code="WSLC0002"
53 Text="WslcImage '%(WslcImage.Identity)' is missing required Dockerfile metadata." />
54
55 <Error Condition="'%(WslcImage.Context)' == ''"
56 Code="WSLC0003"
57 Text="WslcImage '%(WslcImage.Identity)' is missing required Context metadata." />
58
59 <!-- Identity is used as default tar filename and in .tlog filenames; reject filename-invalid chars. -->
60 <Error Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(WslcImage.Identity)', '[/\\:*?&quot;&lt;&gt;|]'))"
61 Code="WSLC0004"
62 Text="WslcImage with Include='%(WslcImage.Identity)' contains a character invalid for filesystem names (one of / \ : * ? &quot; &lt; &gt; |). The Identity is used as the default tar filename and in .tlog filenames. Use a simple identity in Include (e.g. Include=&quot;my-image&quot;) and put the registry/org/tag in the Image metadata." />
63 </Target>
64
65 <!-- Dispatch each WslcImage via MSBuild task instead of batching directly because:
66 1. Sources may contain semicolons (multi-directory) which conflict with Properties syntax
67 2. Wildcard expansion in Inputs requires an intermediate ItemGroup (_WslcCollectSources)
68 that must run per-image in a separate evaluation context
69
70 Build + save run together per image, and StopOnFirstFailure halts the
71 batch on the first failure — so later images aren't attempted before
72 earlier ones are finished. Within an image, save uses .tmp+Move so a
73 half-written tar is never visible (a build-success/save-failure does
74 still leave the image in the daemon without a tar on disk). -->
75 <Target Name="WslcBuildAllImages"
76 AfterTargets="Build"
77 DependsOnTargets="WslcCheckDependencies;_WslcValidateItems"
78 Condition="'@(WslcImage)' != '' AND '$(DesignTimeBuild)' != 'true' AND '$(IsCrossTargetingBuild)' != 'true'">
79
80 <MSBuild Projects="$(MSBuildProjectFullPath)"
81 Targets="_WslcBuildAndSaveSingleImage"
82 StopOnFirstFailure="true"
83 Properties="Configuration=$(Configuration);Platform=$(Platform);_WslcName=%(WslcImage.Identity);_WslcImage=%(WslcImage.Image);_WslcDockerfile=%(WslcImage.Dockerfile);_WslcContext=%(WslcImage.Context);_WslcSourceDir=$([MSBuild]::Escape('%(WslcImage.Sources)'));_WslcIntDir=$(_WslcIntDir);_WslcTarLocation=%(WslcImage.TarLocation);_WslcOutDir=$(OutDir);_WslcBuildArgs=$([MSBuild]::Escape('%(WslcImage.BuildArgs)'));_WslcLabels=$([MSBuild]::Escape('%(WslcImage.Labels)'));_WslcPull=$(WslcImageBuildPull);_WslcNoCache=$(WslcImageBuildNoCache)" />
84 </Target>
85
86 <!--
87 Collect source files and the effective build options for incremental checks.
88 Wildcards are expanded here in ItemGroup Include (not in Inputs attribute or MSBuild task Properties,
89 where they get escaped). Sources can be semicolon separated directory paths;
90 each is expanded with **\* to collect all files recursively.
91 If Sources is omitted, fall back to tracking the full Context directory so changes to
92 .dockerignore and other Docker build inputs still invalidate the marker.
93 -->
94 <Target Name="_WslcCollectSources">
95 <ItemGroup>
96 <_WslcSourceDirs Include="$([MSBuild]::Unescape('$(_WslcSourceDir)').Split(';'))" Condition="'$(_WslcSourceDir)' != ''" />
97 <_WslcSourceFiles Include="$(_WslcDockerfile)" />
98 <_WslcSourceFiles Include="%(_WslcSourceDirs.Identity)\**\*" Condition="'%(_WslcSourceDirs.Identity)' != ''" />
99 <_WslcSourceFiles Include="$(_WslcContext)\**\*" Condition="'$(_WslcSourceDir)' == '' AND '$(_WslcContext)' != ''" />
100 <_WslcBuildArgItems Include="$([MSBuild]::Unescape('$(_WslcBuildArgs)').Split(';'))" Condition="'$(_WslcBuildArgs)' != ''" />
101 <_WslcLabelItems Include="$([MSBuild]::Unescape('$(_WslcLabels)').Split(';'))" Condition="'$(_WslcLabels)' != ''" />
102 </ItemGroup>
103 </Target>
104
105 <Target Name="_WslcWriteBuildSignature"
106 DependsOnTargets="_WslcResolveImageRef;_WslcCollectSources">
107 <PropertyGroup>
108 <_WslcBuildSignaturePath>$(_WslcIntDir)wslc.$(_WslcName).options</_WslcBuildSignaturePath>
109 </PropertyGroup>
110
111 <MakeDir Directories="$(_WslcIntDir)" />
112
113 <ItemGroup>
114 <_WslcBuildSignature Include="Image=$(_WslcFullRef)" />
115 <_WslcBuildSignature Include="Dockerfile=$(_WslcDockerfile)" />
116 <_WslcBuildSignature Include="Context=$(_WslcContext)" />
117 <_WslcBuildSignature Include="Pull=$(_WslcPull)" />
118 <_WslcBuildSignature Include="NoCache=$(_WslcNoCache)" />
119 <_WslcBuildSignature Include="BuildArg=%(_WslcBuildArgItems.Identity)" />
120 <_WslcBuildSignature Include="Label=%(_WslcLabelItems.Identity)" />
121 </ItemGroup>
122
123 <WriteLinesToFile File="$(_WslcBuildSignaturePath)"
124 Lines="@(_WslcBuildSignature)"
125 Overwrite="true"
126 WriteOnlyWhenDifferent="true" />
127
128 <ItemGroup>
129 <_WslcSourceFiles Include="$(_WslcBuildSignaturePath)" />
130 </ItemGroup>
131 </Target>
132
133 <!-- Resolve _WslcFullRef. Falls back to Identity when Image unset; appends
134 ':latest' when no tag is present (':' after the last '/'). -->
135 <Target Name="_WslcResolveImageRef">
136 <PropertyGroup>
137 <_WslcRawRef Condition="'$(_WslcImage)' != ''">$(_WslcImage)</_WslcRawRef>
138 <_WslcRawRef Condition="'$(_WslcRawRef)' == ''">$(_WslcName)</_WslcRawRef>
139 <_WslcLastSlash>$([System.String]::Copy('$(_WslcRawRef)').LastIndexOf('/'))</_WslcLastSlash>
140 <_WslcLastColon>$([System.String]::Copy('$(_WslcRawRef)').LastIndexOf(':'))</_WslcLastColon>
141 <_WslcFullRef Condition="$(_WslcLastColon) &gt; $(_WslcLastSlash)">$(_WslcRawRef)</_WslcFullRef>
142 <_WslcFullRef Condition="'$(_WslcFullRef)' == ''">$(_WslcRawRef):latest</_WslcFullRef>
143 </PropertyGroup>
144 </Target>
145
146 <!-- Resolve tar path into a property so _WslcBuildAndSaveSingleImage can use it in Inputs/Outputs. -->
147 <Target Name="_WslcResolveSavePath" DependsOnTargets="_WslcResolveImageRef">
148 <PropertyGroup>
149 <!-- Resolve tar path: user-provided TarLocation, else $(OutDir)<name>.tar -->
150 <_WslcTarPath Condition="'$(_WslcTarLocation)' != ''">$(_WslcTarLocation)</_WslcTarPath>
151 <_WslcTarPath Condition="'$(_WslcTarPath)' == ''">$(_WslcOutDir)$(_WslcName).tar</_WslcTarPath>
152
153 <_WslcTarDir>$([System.IO.Path]::GetDirectoryName('$(_WslcTarPath)'))</_WslcTarDir>
154 </PropertyGroup>
155 </Target>
156
157 <!-- Build + save one image atomically (per-image, so a mid-list failure
158 leaves a coherent state). Save writes <tar>.tmp and Moves on success. -->
159 <Target Name="_WslcBuildAndSaveSingleImage"
160 DependsOnTargets="_WslcWriteBuildSignature;_WslcResolveSavePath"
161 Inputs="@(_WslcSourceFiles)"
162 Outputs="$(_WslcTarPath)">
163
164 <MakeDir Directories="$(_WslcTarDir)" Condition="'$(_WslcTarDir)' != ''" />
165
166 <PropertyGroup>
167 <_WslcBuildOptions>-t &quot;$(_WslcFullRef)&quot;</_WslcBuildOptions>
168 <_WslcBuildOptions Condition="'$(_WslcNoCache)' == 'true'">$(_WslcBuildOptions) --no-cache</_WslcBuildOptions>
169 <_WslcBuildOptions Condition="'$(_WslcPull)' == 'true'">$(_WslcBuildOptions) --pull</_WslcBuildOptions>
170 <_WslcBuildOptions Condition="'@(_WslcBuildArgItems)' != ''">$(_WslcBuildOptions) @(_WslcBuildArgItems->'--build-arg &quot;%(Identity)&quot;', ' ')</_WslcBuildOptions>
171 <_WslcBuildOptions Condition="'@(_WslcLabelItems)' != ''">$(_WslcBuildOptions) @(_WslcLabelItems->'--label &quot;%(Identity)&quot;', ' ')</_WslcBuildOptions>
172 <_WslcBuildOptions>$(_WslcBuildOptions) -f &quot;$(_WslcDockerfile)&quot;</_WslcBuildOptions>
173 </PropertyGroup>
174
175 <Message Importance="high"
176 Text="WSLC: Building image '$(_WslcFullRef)'..." />
177
178 <Exec Command="&quot;$(WslcCliPath)&quot; image build $(_WslcBuildOptions) &quot;$(_WslcContext)&quot;"
179 ConsoleToMSBuild="true" />
180
181 <Message Importance="high"
182 Text="WSLC: Saving image '$(_WslcFullRef)' to '$(_WslcTarPath)'..." />
183
184 <Exec Command="&quot;$(WslcCliPath)&quot; image save -o &quot;$(_WslcTarPath).tmp&quot; &quot;$(_WslcFullRef)&quot;"
185 ConsoleToMSBuild="true" />
186
187 <Move SourceFiles="$(_WslcTarPath).tmp" DestinationFiles="$(_WslcTarPath)" />
188
189 <!-- Opt-in: WslcPruneAfterBuild=true. Prune failure is a Warning by default;
190 set WslcTreatPruneFailureAsError=true to fail the build instead. -->
191 <Message Importance="high"
192 Text="WSLC: Pruning dangling images..."
193 Condition="'$(WslcPruneAfterBuild)' == 'true'" />
194
195 <Exec Command="&quot;$(WslcCliPath)&quot; image prune"
196 ConsoleToMSBuild="true"
197 IgnoreExitCode="true"
198 Condition="'$(WslcPruneAfterBuild)' == 'true'">
199 <Output TaskParameter="ExitCode" PropertyName="_WslcPruneExitCode" />
200 </Exec>
201
202 <Error Condition="'$(WslcPruneAfterBuild)' == 'true' AND '$(WslcTreatPruneFailureAsError)' == 'true' AND '$(_WslcPruneExitCode)' != '0'"
203 Code="WSLC0007"
204 Text="wslc image prune failed (exit code $(_WslcPruneExitCode)). The image and tar were saved successfully; only the post-build prune failed." />
205
206 <Warning Condition="'$(WslcPruneAfterBuild)' == 'true' AND '$(WslcTreatPruneFailureAsError)' != 'true' AND '$(_WslcPruneExitCode)' != '0'"
207 Code="WSLC0007"
208 Text="wslc image prune failed (exit code $(_WslcPruneExitCode)). The image and tar were saved successfully; only the post-build prune was skipped. Set WslcTreatPruneFailureAsError=true to fail the build instead." />
209 </Target>
210
211 <!-- Clean container artifacts. ContinueOnError so a transient file lock
212 (AV scanner, downstream consumer) doesn't block the rest of clean.
213 .tmp variants are removed too — a failed save can leave one behind. -->
214 <Target Name="WslcClean"
215 AfterTargets="Clean"
216 Condition="'@(WslcImage)' != ''">
217
218 <Delete Files="%(WslcImage.TarLocation)"
219 Condition="'%(WslcImage.TarLocation)' != ''"
220 ContinueOnError="WarnAndContinue" />
221
222 <Delete Files="%(WslcImage.TarLocation).tmp"
223 Condition="'%(WslcImage.TarLocation)' != ''"
224 ContinueOnError="WarnAndContinue" />
225
226 <Delete Files="$(OutDir)%(WslcImage.Identity).tar"
227 Condition="'%(WslcImage.TarLocation)' == ''"
228 ContinueOnError="WarnAndContinue" />
229
230 <Delete Files="$(OutDir)%(WslcImage.Identity).tar.tmp"
231 Condition="'%(WslcImage.TarLocation)' == ''"
232 ContinueOnError="WarnAndContinue" />
233 </Target>
234
235 </Project>