From acc6bcf72117290fbec59c8d2ffb0ef9c1c57f9f Mon Sep 17 00:00:00 2001 From: Shawn Yuan Date: Wed, 6 May 2026 10:44:46 +0800 Subject: [PATCH 01/18] Add MSBuild and CMake build integration for WSLC container images --- .../Microsoft.WSL.Containers.common.targets | 132 ++++++++++++++++++ .../native/Microsoft.WSL.Containers.targets | 59 ++++++-- .../net/Microsoft.WSL.Containers.targets | 50 +++---- .../Microsoft.WSL.ContainersConfig.cmake | 99 ++++++++++++- 4 files changed, 303 insertions(+), 37 deletions(-) create mode 100644 nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets diff --git a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets new file mode 100644 index 000000000..4364dd517 --- /dev/null +++ b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + $(ProgramW6432)\WSL\wslc.exe + + <_WslcIntDir Condition="'$(IntDir)' != ''">$(IntDir) + <_WslcIntDir Condition="'$(_WslcIntDir)' == '' AND '$(IntermediateOutputPath)' != ''">$(IntermediateOutputPath) + <_WslcIntDir Condition="'$(_WslcIntDir)' == ''">obj\ + + + + + + + latest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_WslcSourceDirs Include="$([MSBuild]::Unescape('$(_WslcSourceDir)').Split(';'))" Condition="'$(_WslcSourceDir)' != ''" /> + <_WslcSourceFiles Include="$(_WslcDockerfile)" /> + <_WslcSourceFiles Include="%(_WslcSourceDirs.Identity)\**\*" Condition="'%(_WslcSourceDirs.Identity)' != ''" /> + <_WslcSourceFiles Include="$(_WslcContext)\**\*" Condition="'$(_WslcSourceDir)' == '' AND '$(_WslcContext)' != ''" /> + + + + + + + + + + + <_WslcImageRef Condition="'$(_WslcImage)' != ''">$(_WslcImage) + <_WslcImageRef Condition="'$(_WslcImageRef)' == ''">$(_WslcName) + + + + + + + + + + + + + + + + + + diff --git a/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets b/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets index abd559faa..300127889 100644 --- a/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets +++ b/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets @@ -1,10 +1,8 @@ - - $(Platform) - <_wslcIsInvalidPlatform Condition="'$(WslcPlatform)' != 'x64' and '$(WslcPlatform)' != 'arm64'">true + + $(Platform) - @@ -18,17 +16,56 @@ %(AdditionalDependencies) - $(MSBuildThisFileDirectory)..\..\runtimes\win-$(WslcPlatform); + $(MSBuildThisFileDirectory)..\..\runtimes\win-$(Platform); %(AdditionalLibraryDirectories) - - - + + + + + + + + + + + + + + + <_WslcTlogToolPath>$([System.IO.Path]::GetFullPath('$(WslcCliPath)')) + <_WslcMarkerFullPath>$([System.IO.Path]::GetFullPath('$(_WslcIntDir)wslc_$(_WslcName).built')) + + + - - + - \ No newline at end of file + + + + + <_WslcTlogDir>$([MSBuild]::EnsureTrailingSlash('$(TLogLocation)')) + + + + + + diff --git a/nuget/Microsoft.WSL.Containers/build/net/Microsoft.WSL.Containers.targets b/nuget/Microsoft.WSL.Containers/build/net/Microsoft.WSL.Containers.targets index ecc7f902f..b234c00a0 100644 --- a/nuget/Microsoft.WSL.Containers/build/net/Microsoft.WSL.Containers.targets +++ b/nuget/Microsoft.WSL.Containers/build/net/Microsoft.WSL.Containers.targets @@ -1,31 +1,31 @@ - - - - - <_wslcPlatform Condition="$(RuntimeIdentifier.EndsWith('-x64'))">x64 - <_wslcPlatform Condition="$(RuntimeIdentifier.EndsWith('-arm64'))">arm64 - <_wslcInvalidPlatformProperty Condition="'$(_wslcPlatform)' == ''">RuntimeIdentifier - <_wslcInvalidPlatform Condition="'$(_wslcPlatform)' == ''">$(RuntimeIdentifier) - - - - - <_wslcPlatform Condition="'$(PlatformTarget)' == 'x64'">x64 - <_wslcPlatform Condition="'$(PlatformTarget)' == 'arm64'">arm64 - <_wslcInvalidPlatformProperty Condition="'$(_wslcPlatform)' == ''">PlatformTarget - <_wslcInvalidPlatform Condition="'$(_wslcPlatform)' == ''">$(PlatformTarget) - + + x64 + arm64 + - - - - - + + $(Platform) + - - + + + + + + + + + + + + <_WslcUpToDateDirs Include="@(WslcImage->'%(Sources)')" Condition="'%(WslcImage.Sources)' != ''" /> + + + + \ No newline at end of file diff --git a/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake b/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake index 4829bed47..7c3198ce7 100644 --- a/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake +++ b/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake @@ -47,7 +47,7 @@ add_library(Microsoft.WSL.Containers::SDK SHARED IMPORTED GLOBAL) set_target_properties(Microsoft.WSL.Containers::SDK PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${_wslcsdk_include_dir}" IMPORTED_IMPLIB "${_wslcsdk_lib_dir}/wslcsdk.lib" - IMPORTED_LOCATION "${_wslcsdk_lib_dir}/native/wslcsdk.dll" + IMPORTED_LOCATION "${_wslcsdk_lib_dir}/wslcsdk.dll" ) # Clean up temporary variables @@ -55,3 +55,100 @@ unset(_wslcsdk_arch) unset(_wslcsdk_root) unset(_wslcsdk_include_dir) unset(_wslcsdk_lib_dir) + +# ============================================================================ +# Container Image Build Targets +# ============================================================================ +# +# Provides the wslc_add_image() function for declaring container image +# build targets with incremental rebuild support. +# +# Usage: +# find_package(Microsoft.WSL.Containers REQUIRED) +# +# wslc_add_image(my-server +# IMAGE ghcr.io/myorg/my-server +# DOCKERFILE container/Dockerfile +# CONTEXT container/ +# SOURCES container/src/*.cpp container/src/*.h +# TAG latest +# ) +# +# add_dependencies(my_app my-server) +# +# The first positional argument is the CMake target name. +# IMAGE is the container image reference (required). + +function(wslc_add_image _target_name) + cmake_parse_arguments( + PARSE_ARGV 1 ARG + "" # options (none) + "IMAGE;TAG;DOCKERFILE;CONTEXT" # one-value keywords + "SOURCES" # multi-value keywords + ) + + # Validate required arguments + if(NOT ARG_IMAGE) + message(FATAL_ERROR "wslc_add_image: IMAGE is required") + endif() + # IMAGE must not contain a tag — use the TAG parameter instead. + # Allow ':' in the registry host portion (for example, localhost:5000/myimg) + # and only reject ':' when it appears after the last '/'. + string(FIND "${ARG_IMAGE}" "/" _last_slash_pos REVERSE) + string(FIND "${ARG_IMAGE}" ":" _last_colon_pos REVERSE) + if(_last_colon_pos GREATER _last_slash_pos) + message(FATAL_ERROR "wslc_add_image: IMAGE '${ARG_IMAGE}' contains a tag. Specify the tag separately with the TAG parameter.") + endif() + if(NOT ARG_DOCKERFILE) + message(FATAL_ERROR "wslc_add_image: DOCKERFILE is required") + endif() + if(NOT ARG_CONTEXT) + message(FATAL_ERROR "wslc_add_image: CONTEXT is required") + endif() + + # Defaults + if(NOT ARG_TAG) + set(ARG_TAG "latest") + endif() + + # Find wslc CLI + if(NOT WSLC_CLI_PATH) + find_program(WSLC_CLI_PATH wslc PATHS "$ENV{ProgramW6432}/WSL" "$ENV{ProgramFiles}/WSL") + if(NOT WSLC_CLI_PATH) + message(FATAL_ERROR "wslc CLI not found. Install WSL by running: wsl --install --no-distribution") + endif() + endif() + + # Validate target name + string(REGEX MATCH "[^a-zA-Z0-9_.+-]" _bad_char "${_target_name}") + if(_bad_char) + message(FATAL_ERROR "wslc_add_image: '${_target_name}' contains unsupported character '${_bad_char}'. Supported characters are letters, digits, '_', '.', '+', and '-'.") + endif() + + # Normalize paths to be independent of the build directory + get_filename_component(_dockerfile_path "${ARG_DOCKERFILE}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + get_filename_component(_context_path "${ARG_CONTEXT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + + set(_image_ref "${ARG_IMAGE}:${ARG_TAG}") + set(_stamp "${CMAKE_CURRENT_BINARY_DIR}/wslc_${_target_name}.built") + + # Resolve source globs to file lists; default to CONTEXT contents if SOURCES omitted + if(ARG_SOURCES) + file(GLOB_RECURSE _resolved_sources CONFIGURE_DEPENDS ${ARG_SOURCES}) + else() + file(GLOB_RECURSE _resolved_sources CONFIGURE_DEPENDS "${_context_path}/*") + endif() + + add_custom_command( + OUTPUT "${_stamp}" + COMMAND "${WSLC_CLI_PATH}" image build -t "${_image_ref}" -f "${_dockerfile_path}" "${_context_path}" + COMMAND ${CMAKE_COMMAND} -E touch "${_stamp}" + DEPENDS ${_resolved_sources} "${_dockerfile_path}" + COMMENT "WSLC: Building image '${_image_ref}'..." + VERBATIM + ) + + add_custom_target(${_target_name} ALL + DEPENDS "${_stamp}" + ) +endfunction() From 629bd2d2cddec99b2724b6d538072b3f25994634 Mon Sep 17 00:00:00 2001 From: Shawn Yuan Date: Wed, 6 May 2026 11:25:08 +0800 Subject: [PATCH 02/18] resolve comments --- .../build/Microsoft.WSL.Containers.common.targets | 10 +++++++--- .../build/native/Microsoft.WSL.Containers.targets | 2 +- .../cmake/Microsoft.WSL.ContainersConfig.cmake | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets index 4364dd517..33d740045 100644 --- a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets +++ b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets @@ -1,11 +1,15 @@ - + + + + + - + @@ -61,7 +65,7 @@ XML entities: " = " < = < > = > --> + Text="WslcImage item '%(WslcImage.Identity)' must be a valid file name." /> diff --git a/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets b/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets index a23e786e5..1a294fb17 100644 --- a/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets +++ b/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets @@ -1,8 +1,10 @@ - - $(Platform) + + $(Platform) + <_wslcIsInvalidPlatform Condition="'$(WslcPlatform)' != 'x64' and '$(WslcPlatform)' != 'arm64'">true + @@ -16,12 +18,20 @@ %(AdditionalDependencies) - $(MSBuildThisFileDirectory)..\..\runtimes\win-$(WSLCSDK_Platform); + $(MSBuildThisFileDirectory)..\..\runtimes\win-$(WslcPlatform); %(AdditionalLibraryDirectories) + + + + + + + + diff --git a/nuget/Microsoft.WSL.Containers/build/net/Microsoft.WSL.Containers.targets b/nuget/Microsoft.WSL.Containers/build/net/Microsoft.WSL.Containers.targets index b234c00a0..981beed43 100644 --- a/nuget/Microsoft.WSL.Containers/build/net/Microsoft.WSL.Containers.targets +++ b/nuget/Microsoft.WSL.Containers/build/net/Microsoft.WSL.Containers.targets @@ -1,13 +1,33 @@ - - x64 - arm64 - + + + + + <_wslcPlatform Condition="$(RuntimeIdentifier.EndsWith('-x64'))">x64 + <_wslcPlatform Condition="$(RuntimeIdentifier.EndsWith('-arm64'))">arm64 + <_wslcInvalidPlatformProperty Condition="'$(_wslcPlatform)' == ''">RuntimeIdentifier + <_wslcInvalidPlatform Condition="'$(_wslcPlatform)' == ''">$(RuntimeIdentifier) + + + + + <_wslcPlatform Condition="'$(PlatformTarget)' == 'x64'">x64 + <_wslcPlatform Condition="'$(PlatformTarget)' == 'arm64'">arm64 + <_wslcInvalidPlatformProperty Condition="'$(_wslcPlatform)' == ''">PlatformTarget + <_wslcInvalidPlatform Condition="'$(_wslcPlatform)' == ''">$(PlatformTarget) + - - $(Platform) - + + + + + + + + + @@ -28,4 +48,4 @@ - \ No newline at end of file + From ee02d291091224255fd74b45d4765322f6a80296 Mon Sep 17 00:00:00 2001 From: Shawn Yuan Date: Thu, 7 May 2026 11:07:01 +0800 Subject: [PATCH 04/18] save image to tar file --- .../Microsoft.WSL.Containers.common.targets | 31 +++++++++++++++++ .../Microsoft.WSL.ContainersConfig.cmake | 34 ++++++++++++++----- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets index 0ecf878dc..eab4d9c1c 100644 --- a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets +++ b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets @@ -19,6 +19,7 @@ latest + @@ -68,6 +69,10 @@ + + + + + + + <_WslcImageRef Condition="'$(_WslcImage)' != ''">$(_WslcImage) + <_WslcImageRef Condition="'$(_WslcImageRef)' == ''">$(_WslcName) + <_WslcFullRef>$(_WslcImageRef):$(_WslcTag) + + + <_WslcTarPath Condition="'$(_WslcTarLocation)' != ''">$(_WslcTarLocation) + <_WslcTarPath Condition="'$(_WslcTarPath)' == ''">$(_WslcOutDir)$(_WslcName).tar + + <_WslcTarDir>$([System.IO.Path]::GetDirectoryName('$(_WslcTarPath)')) + + + + + + + + + .tar). +# The image is exported via 'wslc image save' on every build, even +# when sources have not changed. function(wslc_add_image _target_name) cmake_parse_arguments( PARSE_ARGV 1 ARG - "" # options (none) - "IMAGE;TAG;DOCKERFILE;CONTEXT" # one-value keywords - "SOURCES" # multi-value keywords + "" # options (none) + "IMAGE;TAG;DOCKERFILE;CONTEXT;TAR_LOCATION" # one-value keywords + "SOURCES" # multi-value keywords ) # Validate required arguments @@ -110,6 +115,13 @@ function(wslc_add_image _target_name) if(NOT ARG_TAG) set(ARG_TAG "latest") endif() + if(NOT ARG_TAR_LOCATION) + set(ARG_TAR_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/${_target_name}.tar") + endif() + # Normalize TAR_LOCATION to an absolute path. A bare filename or relative + # path would leave _tar_dir empty below and break `make_directory ""`. + get_filename_component(ARG_TAR_LOCATION "${ARG_TAR_LOCATION}" ABSOLUTE + BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") # Find wslc CLI if(NOT WSLC_CLI_PATH) @@ -139,6 +151,8 @@ function(wslc_add_image _target_name) file(GLOB_RECURSE _resolved_sources CONFIGURE_DEPENDS "${_context_path}/*") endif() + get_filename_component(_tar_dir "${ARG_TAR_LOCATION}" DIRECTORY) + add_custom_command( OUTPUT "${_stamp}" COMMAND "${WSLC_CLI_PATH}" image build -t "${_image_ref}" -f "${_dockerfile_path}" "${_context_path}" @@ -149,6 +163,10 @@ function(wslc_add_image _target_name) ) add_custom_target(${_target_name} ALL + COMMAND ${CMAKE_COMMAND} -E make_directory "${_tar_dir}" + COMMAND "${WSLC_CLI_PATH}" image save -o "${ARG_TAR_LOCATION}" "${_image_ref}" DEPENDS "${_stamp}" + COMMENT "WSLC: Saving image '${_image_ref}' to '${ARG_TAR_LOCATION}'..." + VERBATIM ) endfunction() From c2befa09fa7b272a3282a177f76be86fe90cd05a Mon Sep 17 00:00:00 2001 From: Shawn Yuan Date: Thu, 7 May 2026 15:08:28 +0800 Subject: [PATCH 05/18] Clean tar file and prune after build --- .../Microsoft.WSL.Containers.common.targets | 18 ++++++++- .../Microsoft.WSL.ContainersConfig.cmake | 37 ++++++++++++------- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets index eab4d9c1c..e66acd5b9 100644 --- a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets +++ b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets @@ -142,14 +142,30 @@ + + + + + - + + + + + diff --git a/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake b/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake index 1673ad8a9..1bcb9ab4c 100644 --- a/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake +++ b/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake @@ -81,8 +81,8 @@ unset(_wslcsdk_lib_dir) # IMAGE is the container image reference (required). # TAR_LOCATION is the output path for the saved image tarball # (optional; defaults to ${CMAKE_CURRENT_BINARY_DIR}/.tar). -# The image is exported via 'wslc image save' on every build, even -# when sources have not changed. +# The build, save, and prune steps run together when SOURCES change; +# `cmake --build . --target clean` (and VS Clean) remove the tar. function(wslc_add_image _target_name) cmake_parse_arguments( @@ -120,8 +120,14 @@ function(wslc_add_image _target_name) endif() # Normalize TAR_LOCATION to an absolute path. A bare filename or relative # path would leave _tar_dir empty below and break `make_directory ""`. - get_filename_component(ARG_TAR_LOCATION "${ARG_TAR_LOCATION}" ABSOLUTE - BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") + # Skip the normalization when the path contains a generator expression + # (e.g. $) — those resolve to absolute paths at + # build time and would otherwise get BASE_DIR prepended at configure + # time, producing a doubled path like build/$<...>/foo.tar. + if(NOT ARG_TAR_LOCATION MATCHES "\\$<") + get_filename_component(ARG_TAR_LOCATION "${ARG_TAR_LOCATION}" ABSOLUTE + BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") + endif() # Find wslc CLI if(NOT WSLC_CLI_PATH) @@ -153,20 +159,25 @@ function(wslc_add_image _target_name) get_filename_component(_tar_dir "${ARG_TAR_LOCATION}" DIRECTORY) + # build + save + prune live in one add_custom_command so the tar shows + # up as a known OUTPUT — this is what makes Visual Studio's Clean (and + # `cmake --build . --target clean`) actually delete the tar on rebuild. + # The chain is incremental: when ${_resolved_sources} hasn't changed, + # nothing reruns. (MSBuild side is unconditional save-every-build by + # design; CMake gets to be smarter because OUTPUT semantics demand it.) add_custom_command( - OUTPUT "${_stamp}" + OUTPUT "${_stamp}" "${ARG_TAR_LOCATION}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${_tar_dir}" COMMAND "${WSLC_CLI_PATH}" image build -t "${_image_ref}" -f "${_dockerfile_path}" "${_context_path}" + COMMAND "${WSLC_CLI_PATH}" image save -o "${ARG_TAR_LOCATION}" "${_image_ref}" + # Prune dangling images left behind by previous builds. The image we + # just tagged stays; only untagged predecessors get removed. + COMMAND "${WSLC_CLI_PATH}" image prune COMMAND ${CMAKE_COMMAND} -E touch "${_stamp}" DEPENDS ${_resolved_sources} "${_dockerfile_path}" - COMMENT "WSLC: Building image '${_image_ref}'..." + COMMENT "WSLC: Building image '${_image_ref}', saving to '${ARG_TAR_LOCATION}', and pruning dangling images..." VERBATIM ) - add_custom_target(${_target_name} ALL - COMMAND ${CMAKE_COMMAND} -E make_directory "${_tar_dir}" - COMMAND "${WSLC_CLI_PATH}" image save -o "${ARG_TAR_LOCATION}" "${_image_ref}" - DEPENDS "${_stamp}" - COMMENT "WSLC: Saving image '${_image_ref}' to '${ARG_TAR_LOCATION}'..." - VERBATIM - ) + add_custom_target(${_target_name} ALL DEPENDS "${_stamp}" "${ARG_TAR_LOCATION}") endfunction() From cb240f48c9b6fc04953baa197eb3ebdebdcb3182 Mon Sep 17 00:00:00 2001 From: Shawn Yuan Date: Thu, 7 May 2026 17:51:07 +0800 Subject: [PATCH 06/18] resolve comments --- .../Microsoft.WSL.Containers.common.targets | 13 +++++----- .../native/Microsoft.WSL.Containers.targets | 2 +- .../Microsoft.WSL.ContainersConfig.cmake | 24 +++++++++---------- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets index e66acd5b9..7559c0666 100644 --- a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets +++ b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets @@ -143,18 +143,17 @@ - + + Text="WSLC: Pruning dangling images..." + Condition="'$(WslcPruneAfterBuild)' == 'true'" /> + ConsoleToMSBuild="true" + Condition="'$(WslcPruneAfterBuild)' == 'true'" /> - + diff --git a/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets b/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets index 1a294fb17..026fd76ea 100644 --- a/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets +++ b/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets @@ -40,7 +40,7 @@ + Condition="'@(WslcImage)' != '' AND '$(TLogLocation)' != '' AND '$(DesignTimeBuild)' != 'true'"> .tar). -# The build, save, and prune steps run together when SOURCES change; -# `cmake --build . --target clean` (and VS Clean) remove the tar. +# Pass PRUNE_AFTER_BUILD to also run 'wslc image prune' after save. function(wslc_add_image _target_name) cmake_parse_arguments( PARSE_ARGV 1 ARG - "" # options (none) + "PRUNE_AFTER_BUILD" # options (boolean flags) "IMAGE;TAG;DOCKERFILE;CONTEXT;TAR_LOCATION" # one-value keywords "SOURCES" # multi-value keywords ) @@ -159,23 +158,22 @@ function(wslc_add_image _target_name) get_filename_component(_tar_dir "${ARG_TAR_LOCATION}" DIRECTORY) - # build + save + prune live in one add_custom_command so the tar shows - # up as a known OUTPUT — this is what makes Visual Studio's Clean (and - # `cmake --build . --target clean`) actually delete the tar on rebuild. - # The chain is incremental: when ${_resolved_sources} hasn't changed, - # nothing reruns. (MSBuild side is unconditional save-every-build by - # design; CMake gets to be smarter because OUTPUT semantics demand it.) + set(_prune_command "") + set(_prune_comment "") + if(ARG_PRUNE_AFTER_BUILD) + set(_prune_command COMMAND "${WSLC_CLI_PATH}" image prune) + set(_prune_comment ", and pruning dangling images") + endif() + add_custom_command( OUTPUT "${_stamp}" "${ARG_TAR_LOCATION}" COMMAND ${CMAKE_COMMAND} -E make_directory "${_tar_dir}" COMMAND "${WSLC_CLI_PATH}" image build -t "${_image_ref}" -f "${_dockerfile_path}" "${_context_path}" COMMAND "${WSLC_CLI_PATH}" image save -o "${ARG_TAR_LOCATION}" "${_image_ref}" - # Prune dangling images left behind by previous builds. The image we - # just tagged stays; only untagged predecessors get removed. - COMMAND "${WSLC_CLI_PATH}" image prune + ${_prune_command} COMMAND ${CMAKE_COMMAND} -E touch "${_stamp}" DEPENDS ${_resolved_sources} "${_dockerfile_path}" - COMMENT "WSLC: Building image '${_image_ref}', saving to '${ARG_TAR_LOCATION}', and pruning dangling images..." + COMMENT "WSLC: Building image '${_image_ref}', saving to '${ARG_TAR_LOCATION}'${_prune_comment}..." VERBATIM ) From 6a71869bb945a6db8b0423b25d7e06611e728c37 Mon Sep 17 00:00:00 2001 From: Shawn Yuan Date: Fri, 8 May 2026 09:24:45 +0800 Subject: [PATCH 07/18] resolve comments --- .../build/Microsoft.WSL.Containers.common.targets | 4 ++++ .../build/native/Microsoft.WSL.Containers.targets | 15 ++++++++++++--- .../cmake/Microsoft.WSL.ContainersConfig.cmake | 3 +++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets index 7559c0666..58fba1e66 100644 --- a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets +++ b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets @@ -26,6 +26,10 @@ + + - + <_WslcTlogToolPath>$([System.IO.Path]::GetFullPath('$(WslcCliPath)')) <_WslcMarkerFullPath>$([System.IO.Path]::GetFullPath('$(_WslcIntDir)wslc_$(_WslcName).built')) + + <_WslcTlogReadLines Include="^$(_WslcTlogToolPath)" /> + <_WslcTlogReadLines Include="@(_WslcSourceFiles->'%(FullPath)')" /> + <_WslcTlogWriteLines Include="^$(_WslcTlogToolPath)" /> + <_WslcTlogWriteLines Include="$(_WslcMarkerFullPath)" /> + + diff --git a/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake b/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake index 5e7af7ba4..140f3c4c3 100644 --- a/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake +++ b/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake @@ -169,6 +169,9 @@ function(wslc_add_image _target_name) OUTPUT "${_stamp}" "${ARG_TAR_LOCATION}" COMMAND ${CMAKE_COMMAND} -E make_directory "${_tar_dir}" COMMAND "${WSLC_CLI_PATH}" image build -t "${_image_ref}" -f "${_dockerfile_path}" "${_context_path}" + # Remove any tar from a previous (possibly failed) save so a partial file + # doesn't sit on disk pretending to be a valid image. + COMMAND ${CMAKE_COMMAND} -E rm -f "${ARG_TAR_LOCATION}" COMMAND "${WSLC_CLI_PATH}" image save -o "${ARG_TAR_LOCATION}" "${_image_ref}" ${_prune_command} COMMAND ${CMAKE_COMMAND} -E touch "${_stamp}" From 20c9bea8f18eeba37a8f59e6f5a4b45fa4e96aaa Mon Sep 17 00:00:00 2001 From: Shawn Yuan Date: Fri, 8 May 2026 09:42:10 +0800 Subject: [PATCH 08/18] change to incremental save for tar file --- .../Microsoft.WSL.Containers.common.targets | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets index 58fba1e66..0e4e6fb8c 100644 --- a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets +++ b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets @@ -122,10 +122,9 @@ Text="WSLC: Image '$(_WslcImageRef):$(_WslcTag)' built successfully." /> - - - + + <_WslcImageRef Condition="'$(_WslcImage)' != ''">$(_WslcImage) @@ -138,12 +137,24 @@ <_WslcTarDir>$([System.IO.Path]::GetDirectoryName('$(_WslcTarPath)')) + + + + + + From 087fd695e413b6dec5e7c9230b18360b76d7dcf4 Mon Sep 17 00:00:00 2001 From: Shawn Yuan Date: Fri, 8 May 2026 10:46:24 +0800 Subject: [PATCH 09/18] resolve comments --- .../Microsoft.WSL.Containers.common.targets | 74 ++++++++++--------- .../Microsoft.WSL.ContainersConfig.cmake | 62 ++++++++-------- 2 files changed, 73 insertions(+), 63 deletions(-) diff --git a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets index 0e4e6fb8c..f529d892d 100644 --- a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets +++ b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets @@ -6,19 +6,21 @@ - - $(ProgramW6432)\WSL\wslc.exe + + wslc <_WslcIntDir Condition="'$(IntDir)' != ''">$(IntDir) <_WslcIntDir Condition="'$(_WslcIntDir)' == '' AND '$(IntermediateOutputPath)' != ''">$(IntermediateOutputPath) <_WslcIntDir Condition="'$(_WslcIntDir)' == ''">obj\ - + - latest @@ -26,10 +28,6 @@ - - + + + + + + <_WslcRawRef Condition="'$(_WslcImage)' != ''">$(_WslcImage) + <_WslcRawRef Condition="'$(_WslcRawRef)' == ''">$(_WslcName) + <_WslcLastSlash>$([System.String]::Copy('$(_WslcRawRef)').LastIndexOf('/')) + <_WslcLastColon>$([System.String]::Copy('$(_WslcRawRef)').LastIndexOf(':')) + <_WslcFullRef Condition="$(_WslcLastColon) > $(_WslcLastSlash)">$(_WslcRawRef) + <_WslcFullRef Condition="'$(_WslcFullRef)' == ''">$(_WslcRawRef):latest + + + - - - <_WslcImageRef Condition="'$(_WslcImage)' != ''">$(_WslcImage) - <_WslcImageRef Condition="'$(_WslcImageRef)' == ''">$(_WslcName) - - + Text="WSLC: Building image '$(_WslcFullRef)'..." /> - + Text="WSLC: Image '$(_WslcFullRef)' built successfully." /> - - + + - - <_WslcImageRef Condition="'$(_WslcImage)' != ''">$(_WslcImage) - <_WslcImageRef Condition="'$(_WslcImageRef)' == ''">$(_WslcName) - <_WslcFullRef>$(_WslcImageRef):$(_WslcTag) - <_WslcTarPath Condition="'$(_WslcTarLocation)' != ''">$(_WslcTarLocation) <_WslcTarPath Condition="'$(_WslcTarPath)' == ''">$(_WslcOutDir)$(_WslcName).tar @@ -140,8 +145,11 @@ - - - + + .tar). # Pass PRUNE_AFTER_BUILD to also run 'wslc image prune' after save. @@ -86,23 +86,20 @@ unset(_wslcsdk_lib_dir) function(wslc_add_image _target_name) cmake_parse_arguments( PARSE_ARGV 1 ARG - "PRUNE_AFTER_BUILD" # options (boolean flags) - "IMAGE;TAG;DOCKERFILE;CONTEXT;TAR_LOCATION" # one-value keywords - "SOURCES" # multi-value keywords + "PRUNE_AFTER_BUILD" # options (boolean flags) + "IMAGE;TAG;DOCKERFILE;CONTEXT;TAR_LOCATION" # one-value keywords (TAG kept only to error) + "SOURCES" # multi-value keywords ) + # TAG was merged into IMAGE — error explicitly to guide migration. + if(DEFINED ARG_TAG) + message(FATAL_ERROR "wslc_add_image: TAG is no longer supported; include the tag in IMAGE (e.g. IMAGE my-server:v1).") + endif() + # Validate required arguments if(NOT ARG_IMAGE) message(FATAL_ERROR "wslc_add_image: IMAGE is required") endif() - # IMAGE must not contain a tag — use the TAG parameter instead. - # Allow ':' in the registry host portion (for example, localhost:5000/myimg) - # and only reject ':' when it appears after the last '/'. - string(FIND "${ARG_IMAGE}" "/" _last_slash_pos REVERSE) - string(FIND "${ARG_IMAGE}" ":" _last_colon_pos REVERSE) - if(_last_colon_pos GREATER _last_slash_pos) - message(FATAL_ERROR "wslc_add_image: IMAGE '${ARG_IMAGE}' contains a tag. Specify the tag separately with the TAG parameter.") - endif() if(NOT ARG_DOCKERFILE) message(FATAL_ERROR "wslc_add_image: DOCKERFILE is required") endif() @@ -110,10 +107,17 @@ function(wslc_add_image _target_name) message(FATAL_ERROR "wslc_add_image: CONTEXT is required") endif() - # Defaults - if(NOT ARG_TAG) - set(ARG_TAG "latest") + # Resolve image ref: append :latest if IMAGE has no tag (no ':' after the + # last '/' — this keeps registry-port refs like localhost:5000/repo intact). + string(FIND "${ARG_IMAGE}" "/" _last_slash_pos REVERSE) + string(FIND "${ARG_IMAGE}" ":" _last_colon_pos REVERSE) + if(_last_colon_pos GREATER _last_slash_pos) + set(_image_ref "${ARG_IMAGE}") + else() + set(_image_ref "${ARG_IMAGE}:latest") endif() + + # Defaults if(NOT ARG_TAR_LOCATION) set(ARG_TAR_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/${_target_name}.tar") endif() @@ -128,11 +132,11 @@ function(wslc_add_image _target_name) BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") endif() - # Find wslc CLI + # Find wslc CLI on PATH (the WSL MSI puts it there). if(NOT WSLC_CLI_PATH) - find_program(WSLC_CLI_PATH wslc PATHS "$ENV{ProgramW6432}/WSL" "$ENV{ProgramFiles}/WSL") + find_program(WSLC_CLI_PATH wslc) if(NOT WSLC_CLI_PATH) - message(FATAL_ERROR "wslc CLI not found. Install WSL by running: wsl --install --no-distribution") + message(FATAL_ERROR "wslc CLI not found on PATH. Install WSL by running: wsl --install --no-distribution") endif() endif() @@ -146,9 +150,6 @@ function(wslc_add_image _target_name) get_filename_component(_dockerfile_path "${ARG_DOCKERFILE}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") get_filename_component(_context_path "${ARG_CONTEXT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") - set(_image_ref "${ARG_IMAGE}:${ARG_TAG}") - set(_stamp "${CMAKE_CURRENT_BINARY_DIR}/wslc_${_target_name}.built") - # Resolve source globs to file lists; default to CONTEXT contents if SOURCES omitted if(ARG_SOURCES) file(GLOB_RECURSE _resolved_sources CONFIGURE_DEPENDS ${ARG_SOURCES}) @@ -165,20 +166,21 @@ function(wslc_add_image _target_name) set(_prune_comment ", and pruning dangling images") endif() + # Save to a .tmp and atomically rename on success — wslc image save uses + # CREATE_ALWAYS, which truncates the destination on entry, so a failed + # save would otherwise leave a partial tar with newer mtime than sources + # (and break incremental). The rename only happens if save succeeded. add_custom_command( - OUTPUT "${_stamp}" "${ARG_TAR_LOCATION}" + OUTPUT "${ARG_TAR_LOCATION}" COMMAND ${CMAKE_COMMAND} -E make_directory "${_tar_dir}" COMMAND "${WSLC_CLI_PATH}" image build -t "${_image_ref}" -f "${_dockerfile_path}" "${_context_path}" - # Remove any tar from a previous (possibly failed) save so a partial file - # doesn't sit on disk pretending to be a valid image. - COMMAND ${CMAKE_COMMAND} -E rm -f "${ARG_TAR_LOCATION}" - COMMAND "${WSLC_CLI_PATH}" image save -o "${ARG_TAR_LOCATION}" "${_image_ref}" + COMMAND "${WSLC_CLI_PATH}" image save -o "${ARG_TAR_LOCATION}.tmp" "${_image_ref}" + COMMAND ${CMAKE_COMMAND} -E rename "${ARG_TAR_LOCATION}.tmp" "${ARG_TAR_LOCATION}" ${_prune_command} - COMMAND ${CMAKE_COMMAND} -E touch "${_stamp}" DEPENDS ${_resolved_sources} "${_dockerfile_path}" COMMENT "WSLC: Building image '${_image_ref}', saving to '${ARG_TAR_LOCATION}'${_prune_comment}..." VERBATIM ) - add_custom_target(${_target_name} ALL DEPENDS "${_stamp}" "${ARG_TAR_LOCATION}") + add_custom_target(${_target_name} DEPENDS "${ARG_TAR_LOCATION}") endfunction() From 65e9a013f2d5d9be418eac36261435b6df56fe91 Mon Sep 17 00:00:00 2001 From: Shawn Yuan Date: Fri, 8 May 2026 10:55:13 +0800 Subject: [PATCH 10/18] resolve comments --- .../build/Microsoft.WSL.Containers.common.targets | 10 ++-------- .../cmake/Microsoft.WSL.ContainersConfig.cmake | 15 +++++---------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets index f529d892d..45c94b5af 100644 --- a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets +++ b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets @@ -15,9 +15,8 @@ <_WslcIntDir Condition="'$(_WslcIntDir)' == ''">obj\ - + @@ -57,11 +56,6 @@ - - - - + <_WslcTlogToolPath>$([System.IO.Path]::GetFullPath('$(WslcCliPath)')) <_WslcMarkerFullPath>$([System.IO.Path]::GetFullPath('$(_WslcIntDir)wslc_$(_WslcName).built')) + <_WslcTarFullPath>$([System.IO.Path]::GetFullPath('$(_WslcTarPath)')) @@ -61,6 +67,7 @@ <_WslcTlogReadLines Include="@(_WslcSourceFiles->'%(FullPath)')" /> <_WslcTlogWriteLines Include="^$(_WslcTlogToolPath)" /> <_WslcTlogWriteLines Include="$(_WslcMarkerFullPath)" /> + <_WslcTlogWriteLines Include="$(_WslcTarFullPath)" /> - + + + <_WslcUpToDateDirs Include="@(WslcImage->'%(Sources)')" Condition="'%(WslcImage.Sources)' != ''" /> diff --git a/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake b/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake index c689b943b..2539bd00f 100644 --- a/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake +++ b/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake @@ -91,6 +91,11 @@ function(wslc_add_image _target_name) "SOURCES" # multi-value keywords ) + # Reject typos / unknown keywords so they can't silently slip through. + if(ARG_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "wslc_add_image: unknown argument(s): ${ARG_UNPARSED_ARGUMENTS}") + endif() + # Validate required arguments if(NOT ARG_IMAGE) message(FATAL_ERROR "wslc_add_image: IMAGE is required") From 3180a640abe5fe73e3b8f3239cd12aeb534d98f4 Mon Sep 17 00:00:00 2001 From: Shawn Yuan <128874481+shuaiyuanxx@users.noreply.github.com> Date: Fri, 8 May 2026 12:19:00 +0800 Subject: [PATCH 13/18] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../build/Microsoft.WSL.Containers.common.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets index 45c94b5af..e2468f565 100644 --- a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets +++ b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets @@ -65,7 +65,7 @@ + Condition="'@(WslcImage)' != '' AND '$(DesignTimeBuild)' != 'true' AND '$(IsCrossTargetingBuild)' != 'true'"> Date: Sat, 9 May 2026 08:46:26 +0800 Subject: [PATCH 14/18] resolve comments --- .../Microsoft.WSL.Containers.common.targets | 79 ++++++++----------- .../native/Microsoft.WSL.Containers.targets | 14 ++-- .../net/Microsoft.WSL.Containers.targets | 7 +- 3 files changed, 44 insertions(+), 56 deletions(-) diff --git a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets index e2468f565..d4cd5aa6b 100644 --- a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets +++ b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets @@ -52,28 +52,32 @@ Text="WslcImage '%(WslcImage.Identity)' is missing required Context metadata." /> + Text="WslcImage with Include='%(WslcImage.Identity)' contains a character invalid for use as a build marker filename (one of / \ : * ? " < > |). Use a simple identity in Include (e.g. Include="my-image") and put the registry/org/tag in the Image metadata." /> + that must run per-image in a separate evaluation context + + Each image is built and saved atomically in one inner target so a failure + partway through the list leaves a coherent on-disk state (no half-state + where image N is in the daemon but its tar is missing). StopOnFirstFailure + halts the batch as soon as any image fails. --> - - + 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)" /> - - - - - - - - - - - - - - + @@ -138,20 +122,26 @@ - - .tmp and Moves on success so a failed save can't + leave a partial tar that would mislead later incremental checks. --> + + + + + + @@ -170,18 +160,19 @@ Condition="'$(WslcPruneAfterBuild)' == 'true'" /> - + - - + Condition="'%(WslcImage.TarLocation)' != ''" + ContinueOnError="WarnAndContinue" /> + Condition="'%(WslcImage.TarLocation)' == ''" + ContinueOnError="WarnAndContinue" /> diff --git a/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets b/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets index 629bb628e..aaece2afb 100644 --- a/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets +++ b/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets @@ -48,17 +48,14 @@ <_WslcTlogToolPath>$([System.IO.Path]::GetFullPath('$(WslcCliPath)')) - <_WslcMarkerFullPath>$([System.IO.Path]::GetFullPath('$(_WslcIntDir)wslc_$(_WslcName).built')) <_WslcTarFullPath>$([System.IO.Path]::GetFullPath('$(_WslcTarPath)')) @@ -66,7 +63,6 @@ <_WslcTlogReadLines Include="^$(_WslcTlogToolPath)" /> <_WslcTlogReadLines Include="@(_WslcSourceFiles->'%(FullPath)')" /> <_WslcTlogWriteLines Include="^$(_WslcTlogToolPath)" /> - <_WslcTlogWriteLines Include="$(_WslcMarkerFullPath)" /> <_WslcTlogWriteLines Include="$(_WslcTarFullPath)" /> @@ -90,8 +86,10 @@ <_WslcTlogDir>$([MSBuild]::EnsureTrailingSlash('$(TLogLocation)')) - - + + diff --git a/nuget/Microsoft.WSL.Containers/build/net/Microsoft.WSL.Containers.targets b/nuget/Microsoft.WSL.Containers/build/net/Microsoft.WSL.Containers.targets index 46d38ec68..d6f501e17 100644 --- a/nuget/Microsoft.WSL.Containers/build/net/Microsoft.WSL.Containers.targets +++ b/nuget/Microsoft.WSL.Containers/build/net/Microsoft.WSL.Containers.targets @@ -35,14 +35,13 @@ - + - Date: Mon, 11 May 2026 16:55:09 +0800 Subject: [PATCH 15/18] refine error message --- .../build/Microsoft.WSL.Containers.common.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets index d4cd5aa6b..2e3cb07d5 100644 --- a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets +++ b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets @@ -37,7 +37,7 @@ + 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." /> From e35b03ca96d204c462aa45281383cc38f2b9068c Mon Sep 17 00:00:00 2001 From: Shawn Yuan Date: Mon, 11 May 2026 17:19:30 +0800 Subject: [PATCH 16/18] resolve copilot comments --- .../Microsoft.WSL.Containers.common.targets | 23 +++++++++++++------ .../native/Microsoft.WSL.Containers.targets | 5 +++- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets index 2e3cb07d5..fa3b242fb 100644 --- a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets +++ b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets @@ -52,12 +52,13 @@ Text="WslcImage '%(WslcImage.Identity)' is missing required Context metadata." /> + Text="WslcImage with Include='%(WslcImage.Identity)' contains a character invalid for filesystem names (one of / \ : * ? " < > |). The Identity is used as the default tar filename and in .tlog filenames. Use a simple identity in Include (e.g. Include="my-image") and put the registry/org/tag in the Image metadata." /> + missing". Save writes .tmp and Moves on success so a failed save + can't leave a partial tar that would mislead later incremental checks. --> + (AV scanner, downstream consumer) doesn't block the rest of clean. + .tmp variants are removed too — a failed save can leave one behind. --> @@ -170,9 +171,17 @@ Condition="'%(WslcImage.TarLocation)' != ''" ContinueOnError="WarnAndContinue" /> + + + + diff --git a/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets b/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets index aaece2afb..07a911971 100644 --- a/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets +++ b/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets @@ -55,7 +55,10 @@ - <_WslcTlogToolPath>$([System.IO.Path]::GetFullPath('$(WslcCliPath)')) + + <_WslcTlogToolPath>$(WslcCliPath) <_WslcTarFullPath>$([System.IO.Path]::GetFullPath('$(_WslcTarPath)')) From 07f8972fe8307193954c9a494914dbb49ca9639b Mon Sep 17 00:00:00 2001 From: Shawn Yuan Date: Mon, 11 May 2026 20:04:12 +0800 Subject: [PATCH 17/18] resolve somments --- .../Microsoft.WSL.Containers.common.targets | 34 +++++++++++-------- .../native/Microsoft.WSL.Containers.targets | 12 +++---- .../Microsoft.WSL.ContainersConfig.cmake | 19 ++++++++--- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets index fa3b242fb..ea81a4021 100644 --- a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets +++ b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets @@ -51,11 +51,7 @@ Code="WSLC0003" Text="WslcImage '%(WslcImage.Identity)' is missing required Context metadata." /> - + @@ -98,9 +94,8 @@ - + <_WslcRawRef Condition="'$(_WslcImage)' != ''">$(_WslcImage) @@ -123,11 +118,8 @@ - + - + + IgnoreExitCode="true" + Condition="'$(WslcPruneAfterBuild)' == 'true'"> + + + + + + + - + <_WslcTlogToolPath>$(WslcCliPath) <_WslcTarFullPath>$([System.IO.Path]::GetFullPath('$(_WslcTarPath)')) diff --git a/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake b/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake index 2539bd00f..090758fb0 100644 --- a/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake +++ b/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake @@ -136,14 +136,14 @@ function(wslc_add_image _target_name) if(NOT WSLC_CLI_PATH) find_program(WSLC_CLI_PATH wslc) if(NOT WSLC_CLI_PATH) - message(FATAL_ERROR "wslc CLI not found on PATH. Install WSL by running: wsl --install --no-distribution") + message(FATAL_ERROR "wslc CLI not found on PATH. Install WSL by running 'wsl --install --no-distribution', or set the WSLC_CLI_PATH variable to a specific wslc.exe path.") endif() endif() - # Validate target name + # Validate target name (used as CMake target id and default tar filename). string(REGEX MATCH "[^a-zA-Z0-9_.+-]" _bad_char "${_target_name}") if(_bad_char) - message(FATAL_ERROR "wslc_add_image: '${_target_name}' contains unsupported character '${_bad_char}'. Supported characters are letters, digits, '_', '.', '+', and '-'.") + message(FATAL_ERROR "wslc_add_image: '${_target_name}' contains unsupported character '${_bad_char}'. The target name is used as a CMake target identifier and as the default tar filename, so it must be limited to letters, digits, '_', '.', '+', and '-'.") endif() # Normalize paths to be independent of the build directory @@ -159,10 +159,21 @@ function(wslc_add_image _target_name) get_filename_component(_tar_dir "${ARG_TAR_LOCATION}" DIRECTORY) + # Prune failure is swallowed by default (housekeeping); set + # WSLC_TREAT_PRUNE_FAILURE_AS_ERROR=ON to fail the build on prune failure. set(_prune_command "") set(_prune_comment "") if(ARG_PRUNE_AFTER_BUILD) - set(_prune_command COMMAND "${WSLC_CLI_PATH}" image prune) + if(WSLC_TREAT_PRUNE_FAILURE_AS_ERROR) + set(_prune_command COMMAND "${WSLC_CLI_PATH}" image prune) + else() + set(_prune_wrapper "${CMAKE_CURRENT_BINARY_DIR}/wslc_prune_ignore_failure.cmake") + if(NOT EXISTS "${_prune_wrapper}") + file(WRITE "${_prune_wrapper}" + "execute_process(COMMAND \"\${WSLC}\" image prune)\n") + endif() + set(_prune_command COMMAND "${CMAKE_COMMAND}" "-DWSLC=${WSLC_CLI_PATH}" -P "${_prune_wrapper}") + endif() set(_prune_comment ", and pruning dangling images") endif() From 3265399ce488cde79088b675b0ecbeeffe5050da Mon Sep 17 00:00:00 2001 From: Shawn Yuan Date: Mon, 11 May 2026 20:16:22 +0800 Subject: [PATCH 18/18] resolve copilot comments --- .../build/Microsoft.WSL.Containers.common.targets | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets index ea81a4021..97a443325 100644 --- a/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets +++ b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets @@ -62,10 +62,11 @@ 2. Wildcard expansion in Inputs requires an intermediate ItemGroup (_WslcCollectSources) that must run per-image in a separate evaluation context - Each image is built and saved atomically in one inner target so a failure - partway through the list leaves a coherent on-disk state (no half-state - where image N is in the daemon but its tar is missing). StopOnFirstFailure - halts the batch as soon as any image fails. --> + 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). -->