Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- ================================================================== -->
<!-- Container Image Build Targets -->
<!-- ================================================================== -->

<!-- Default properties -->
<PropertyGroup>
<!-- Default to bare 'wslc' so cmd.exe resolves it via PATH (the WSL MSI puts wslc.exe there).
Override with WslcCliPath property if needed. -->
<WslcCliPath Condition="'$(WslcCliPath)' == ''">wslc</WslcCliPath>
<!-- Unified intermediate directory: $(IntDir) for C++, $(IntermediateOutputPath) for .NET -->
<_WslcIntDir Condition="'$(IntDir)' != ''">$(IntDir)</_WslcIntDir>
<_WslcIntDir Condition="'$(_WslcIntDir)' == '' AND '$(IntermediateOutputPath)' != ''">$(IntermediateOutputPath)</_WslcIntDir>
<_WslcIntDir Condition="'$(_WslcIntDir)' == ''">obj\</_WslcIntDir>
</PropertyGroup>

<!-- Default metadata for WslcImage items. Image is the full ref;
':latest' is appended automatically when no tag is given. -->
<ItemDefinitionGroup>
<WslcImage>
<Image></Image>
<TarLocation></TarLocation>
</WslcImage>
Comment thread
shuaiyuanxx marked this conversation as resolved.
</ItemDefinitionGroup>

<!-- Check that wslc CLI is installed (invoked via DependsOnTargets from WslcBuildAllImages) -->
<Target Name="WslcCheckDependencies">

<Exec Command="&quot;$(WslcCliPath)&quot; --version"
Comment thread
shuaiyuanxx marked this conversation as resolved.
IgnoreExitCode="true"
ConsoleToMSBuild="true"
StandardOutputImportance="low"
StandardErrorImportance="low">
<Output TaskParameter="ExitCode" PropertyName="_WslcExitCode" />
</Exec>

<Error Condition="'$(_WslcExitCode)' != '0'"
Code="WSLC0001"
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." />
</Target>

<!-- Validate required metadata on WslcImage items (invoked via DependsOnTargets from WslcBuildAllImages) -->
<Target Name="_WslcValidateItems">

<Error Condition="'%(WslcImage.Dockerfile)' == ''"
Code="WSLC0002"
Text="WslcImage '%(WslcImage.Identity)' is missing required Dockerfile metadata." />

<Error Condition="'%(WslcImage.Context)' == ''"
Code="WSLC0003"
Text="WslcImage '%(WslcImage.Identity)' is missing required Context metadata." />

<!-- Identity is used as default tar filename and in .tlog filenames; reject filename-invalid chars. -->
<Error Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(WslcImage.Identity)', '[/\\:*?&quot;&lt;&gt;|]'))"
Comment thread
shuaiyuanxx marked this conversation as resolved.
Code="WSLC0004"
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." />
</Target>

<!-- Dispatch each WslcImage via MSBuild task instead of batching directly because:
1. Sources may contain semicolons (multi-directory) which conflict with Properties syntax
2. Wildcard expansion in Inputs requires an intermediate ItemGroup (_WslcCollectSources)
that must run per-image in a separate evaluation context

Build + save run together per image, and StopOnFirstFailure halts the
batch on the first failure — so later images aren't attempted before
earlier ones are finished. Within an image, save uses .tmp+Move so a
half-written tar is never visible (a build-success/save-failure does
still leave the image in the daemon without a tar on disk). -->
<Target Name="WslcBuildAllImages"
AfterTargets="Build"
DependsOnTargets="WslcCheckDependencies;_WslcValidateItems"
Condition="'@(WslcImage)' != '' AND '$(DesignTimeBuild)' != 'true' AND '$(IsCrossTargetingBuild)' != 'true'">

<MSBuild Projects="$(MSBuildProjectFullPath)"
Targets="_WslcBuildAndSaveSingleImage"
StopOnFirstFailure="true"
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)" />
</Target>
Comment thread
shuaiyuanxx marked this conversation as resolved.
Comment thread
shuaiyuanxx marked this conversation as resolved.

<!--
Collect source files for incremental check.
Wildcards are expanded here in ItemGroup Include (not in Inputs attribute or MSBuild task Properties,
where they get escaped). Sources can be semicolon separated directory paths;
each is expanded with **\* to collect all files recursively.
If Sources is omitted, fall back to tracking the full Context directory so changes to
.dockerignore and other Docker build inputs still invalidate the marker.
-->
<Target Name="_WslcCollectSources">
<ItemGroup>
<_WslcSourceDirs Include="$([MSBuild]::Unescape('$(_WslcSourceDir)').Split(';'))" Condition="'$(_WslcSourceDir)' != ''" />
<_WslcSourceFiles Include="$(_WslcDockerfile)" />
<_WslcSourceFiles Include="%(_WslcSourceDirs.Identity)\**\*" Condition="'%(_WslcSourceDirs.Identity)' != ''" />
Comment thread
shuaiyuanxx marked this conversation as resolved.
<_WslcSourceFiles Include="$(_WslcContext)\**\*" Condition="'$(_WslcSourceDir)' == '' AND '$(_WslcContext)' != ''" />
</ItemGroup>
</Target>

<!-- Resolve _WslcFullRef. Falls back to Identity when Image unset; appends
':latest' when no tag is present (':' after the last '/'). -->
<Target Name="_WslcResolveImageRef">
<PropertyGroup>
<_WslcRawRef Condition="'$(_WslcImage)' != ''">$(_WslcImage)</_WslcRawRef>
<_WslcRawRef Condition="'$(_WslcRawRef)' == ''">$(_WslcName)</_WslcRawRef>
<_WslcLastSlash>$([System.String]::Copy('$(_WslcRawRef)').LastIndexOf('/'))</_WslcLastSlash>
<_WslcLastColon>$([System.String]::Copy('$(_WslcRawRef)').LastIndexOf(':'))</_WslcLastColon>
<_WslcFullRef Condition="$(_WslcLastColon) &gt; $(_WslcLastSlash)">$(_WslcRawRef)</_WslcFullRef>
<_WslcFullRef Condition="'$(_WslcFullRef)' == ''">$(_WslcRawRef):latest</_WslcFullRef>
</PropertyGroup>
</Target>

<!-- Resolve tar path into a property so _WslcBuildAndSaveSingleImage can use it in Inputs/Outputs. -->
<Target Name="_WslcResolveSavePath" DependsOnTargets="_WslcResolveImageRef">
<PropertyGroup>
<!-- Resolve tar path: user-provided TarLocation, else $(OutDir)<name>.tar -->
<_WslcTarPath Condition="'$(_WslcTarLocation)' != ''">$(_WslcTarLocation)</_WslcTarPath>
<_WslcTarPath Condition="'$(_WslcTarPath)' == ''">$(_WslcOutDir)$(_WslcName).tar</_WslcTarPath>

<_WslcTarDir>$([System.IO.Path]::GetDirectoryName('$(_WslcTarPath)'))</_WslcTarDir>
</PropertyGroup>
</Target>

<!-- Build + save one image atomically (per-image, so a mid-list failure
leaves a coherent state). Save writes <tar>.tmp and Moves on success. -->
<Target Name="_WslcBuildAndSaveSingleImage"
DependsOnTargets="_WslcCollectSources;_WslcResolveImageRef;_WslcResolveSavePath"
Inputs="@(_WslcSourceFiles)"
Outputs="$(_WslcTarPath)">

<MakeDir Directories="$(_WslcIntDir)" />
<MakeDir Directories="$(_WslcTarDir)" Condition="'$(_WslcTarDir)' != ''" />

<Message Importance="high"
Text="WSLC: Building image '$(_WslcFullRef)'..." />

<Exec Command="&quot;$(WslcCliPath)&quot; image build -t &quot;$(_WslcFullRef)&quot; -f &quot;$(_WslcDockerfile)&quot; &quot;$(_WslcContext)&quot;"
ConsoleToMSBuild="true" />

<Message Importance="high"
Text="WSLC: Saving image '$(_WslcFullRef)' to '$(_WslcTarPath)'..." />

<Exec Command="&quot;$(WslcCliPath)&quot; image save -o &quot;$(_WslcTarPath).tmp&quot; &quot;$(_WslcFullRef)&quot;"
ConsoleToMSBuild="true" />
Comment thread
shuaiyuanxx marked this conversation as resolved.

<Move SourceFiles="$(_WslcTarPath).tmp" DestinationFiles="$(_WslcTarPath)" />

Comment thread
shuaiyuanxx marked this conversation as resolved.
<!-- Opt-in: WslcPruneAfterBuild=true. Prune failure is a Warning by default;
set WslcTreatPruneFailureAsError=true to fail the build instead. -->
<Message Importance="high"
Text="WSLC: Pruning dangling images..."
Condition="'$(WslcPruneAfterBuild)' == 'true'" />

<Exec Command="&quot;$(WslcCliPath)&quot; image prune"
ConsoleToMSBuild="true"
IgnoreExitCode="true"
Condition="'$(WslcPruneAfterBuild)' == 'true'">
<Output TaskParameter="ExitCode" PropertyName="_WslcPruneExitCode" />
</Exec>

<Error Condition="'$(WslcPruneAfterBuild)' == 'true' AND '$(WslcTreatPruneFailureAsError)' == 'true' AND '$(_WslcPruneExitCode)' != '0'"
Code="WSLC0007"
Text="wslc image prune failed (exit code $(_WslcPruneExitCode)). The image and tar were saved successfully; only the post-build prune failed." />

<Warning Condition="'$(WslcPruneAfterBuild)' == 'true' AND '$(WslcTreatPruneFailureAsError)' != 'true' AND '$(_WslcPruneExitCode)' != '0'"
Code="WSLC0007"
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." />
</Target>

<!-- Clean container artifacts. ContinueOnError so a transient file lock
(AV scanner, downstream consumer) doesn't block the rest of clean.
.tmp variants are removed too — a failed save can leave one behind. -->
<Target Name="WslcClean"
AfterTargets="Clean"
Condition="'@(WslcImage)' != ''">

<Delete Files="%(WslcImage.TarLocation)"
Condition="'%(WslcImage.TarLocation)' != ''"
ContinueOnError="WarnAndContinue" />

<Delete Files="%(WslcImage.TarLocation).tmp"
Condition="'%(WslcImage.TarLocation)' != ''"
ContinueOnError="WarnAndContinue" />

<Delete Files="$(OutDir)%(WslcImage.Identity).tar"
Condition="'%(WslcImage.TarLocation)' == ''"
ContinueOnError="WarnAndContinue" />
Comment thread
shuaiyuanxx marked this conversation as resolved.

<Delete Files="$(OutDir)%(WslcImage.Identity).tar.tmp"
Condition="'%(WslcImage.TarLocation)' == ''"
ContinueOnError="WarnAndContinue" />
</Target>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,64 @@
<Target Name="WslcValidatePlatform" BeforeTargets="PrepareForBuild" Condition="'$(_wslcIsInvalidPlatform)' == 'true'">
<Error Text="wslcsdk.dll could not be copied because platform '$(WslcPlatform)' is not supported. Only x64 and arm64 platforms are supported. You can override the detected platform by setting the property WslcPlatform." />
</Target>
</Project>

<Import Project="$(MSBuildThisFileDirectory)..\Microsoft.WSL.Containers.common.targets" />

<!-- ================================================================== -->
<!-- C++ specific: Write .tlog files for VS Fast Up-to-Date Check -->
<!-- ================================================================== -->

<Target Name="_WslcWriteAllTlogs"
AfterTargets="WslcBuildAllImages"
Condition="'@(WslcImage)' != '' AND '$(TLogLocation)' != '' AND '$(DesignTimeBuild)' != 'true'">

<MSBuild Projects="$(MSBuildProjectFullPath)"
Targets="_WslcWriteSingleTlog"
Properties="Configuration=$(Configuration);Platform=$(Platform);_WslcName=%(WslcImage.Identity);_WslcDockerfile=%(WslcImage.Dockerfile);_WslcContext=%(WslcImage.Context);_WslcSourceDir=$([MSBuild]::Escape('%(WslcImage.Sources)'));_WslcIntDir=$(_WslcIntDir);_WslcTarLocation=%(WslcImage.TarLocation);_WslcOutDir=$(OutDir);_WslcTlogDir=$([MSBuild]::EnsureTrailingSlash('$(TLogLocation)'))" />
</Target>
Comment thread
shuaiyuanxx marked this conversation as resolved.
Comment thread
shuaiyuanxx marked this conversation as resolved.

<!-- tlog format requires one path per line; pass ItemGroups to WriteLinesToFile
so each entry is its own line (a single ';'-joined string would be one line). -->
<Target Name="_WslcWriteSingleTlog"
DependsOnTargets="_WslcCollectSources;_WslcResolveSavePath">
<PropertyGroup>
<!-- Tool path is just a FUTDC group marker; don't GetFullPath
(would mis-resolve bare 'wslc' to <projdir>\wslc). -->
<_WslcTlogToolPath>$(WslcCliPath)</_WslcTlogToolPath>
<_WslcTarFullPath>$([System.IO.Path]::GetFullPath('$(_WslcTarPath)'))</_WslcTarFullPath>
</PropertyGroup>

<ItemGroup>
<_WslcTlogReadLines Include="^$(_WslcTlogToolPath)" />
<_WslcTlogReadLines Include="@(_WslcSourceFiles->'%(FullPath)')" />
<_WslcTlogWriteLines Include="^$(_WslcTlogToolPath)" />
<_WslcTlogWriteLines Include="$(_WslcTarFullPath)" />
</ItemGroup>

<WriteLinesToFile
File="$(_WslcTlogDir)wslc.$(_WslcName).read.1.tlog"
Lines="@(_WslcTlogReadLines)"
Overwrite="true"
Encoding="Unicode" />

<WriteLinesToFile
File="$(_WslcTlogDir)wslc.$(_WslcName).write.1.tlog"
Lines="@(_WslcTlogWriteLines)"
Overwrite="true"
Comment thread
shuaiyuanxx marked this conversation as resolved.
Encoding="Unicode" />
</Target>

<!-- Clean tlog files -->
<Target Name="_WslcCleanTlogs"
AfterTargets="WslcClean"
Condition="'@(WslcImage)' != '' AND '$(TLogLocation)' != ''">
<PropertyGroup>
<_WslcTlogDir>$([MSBuild]::EnsureTrailingSlash('$(TLogLocation)'))</_WslcTlogDir>
</PropertyGroup>
<Delete Files="$(_WslcTlogDir)wslc.%(WslcImage.Identity).read.1.tlog"
ContinueOnError="WarnAndContinue" />
<Delete Files="$(_WslcTlogDir)wslc.%(WslcImage.Identity).write.1.tlog"
ContinueOnError="WarnAndContinue" />
</Target>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,29 @@
<Target Name="WslcValidatePlatform" BeforeTargets="PrepareForBuild" Condition="'$(_wslcInvalidPlatform)' != ''">
<Error Text="wslcsdk.dll could not be copied because the $(_wslcInvalidPlatformProperty) '$(_wslcInvalidPlatform)' is not supported. Only x64 and arm64 platforms are supported." />
</Target>
</Project>

<Import Project="$(MSBuildThisFileDirectory)..\Microsoft.WSL.Containers.common.targets" />

<!-- ================================================================== -->
<!-- C# specific: CPS Up-to-Date Check for container source files -->
<!-- ================================================================== -->

<!-- Register the saved tar and source files for CPS up-to-date check.
The tar is the sole build output (build is atomic with save — see
_WslcBuildAndSaveSingleImage in common.targets). -->
<Target Name="_WslcRegisterUpToDateCheckInputs"
BeforeTargets="CollectUpToDateCheckBuiltDesignTime"
Condition="'$(UsingMicrosoftNETSdk)' == 'true' AND '@(WslcImage)' != ''">
<ItemGroup>
<UpToDateCheckBuilt Include="%(WslcImage.TarLocation)"
Condition="'%(WslcImage.TarLocation)' != ''" />
<UpToDateCheckBuilt Include="$(OutDir)%(WslcImage.Identity).tar"
Condition="'%(WslcImage.TarLocation)' == ''" />
<UpToDateCheckInput Include="%(WslcImage.Dockerfile)" Condition="'%(WslcImage.Dockerfile)' != ''" />
<_WslcUpToDateDirs Include="@(WslcImage->'%(Sources)')" Condition="'%(WslcImage.Sources)' != ''" />
Comment thread
shuaiyuanxx marked this conversation as resolved.
<UpToDateCheckInput Include="%(_WslcUpToDateDirs.Identity)\**\*" Condition="'%(_WslcUpToDateDirs.Identity)' != ''" />
Comment thread
shuaiyuanxx marked this conversation as resolved.
<UpToDateCheckInput Include="%(WslcImage.Context)\**\*" Condition="'%(WslcImage.Sources)' == '' AND '%(WslcImage.Context)' != ''" />
Comment thread
shuaiyuanxx marked this conversation as resolved.
</ItemGroup>
Comment thread
shuaiyuanxx marked this conversation as resolved.
</Target>

</Project>
Loading
Loading