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..97a443325 --- /dev/null +++ b/nuget/Microsoft.WSL.Containers/build/Microsoft.WSL.Containers.common.targets @@ -0,0 +1,192 @@ + + + + + + + + + + wslc + + <_WslcIntDir Condition="'$(IntDir)' != ''">$(IntDir) + <_WslcIntDir Condition="'$(_WslcIntDir)' == '' AND '$(IntermediateOutputPath)' != ''">$(IntermediateOutputPath) + <_WslcIntDir Condition="'$(_WslcIntDir)' == ''">obj\ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_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)' != ''" /> + + + + + + + <_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 + + + + + + + + <_WslcTarPath Condition="'$(_WslcTarLocation)' != ''">$(_WslcTarLocation) + <_WslcTarPath Condition="'$(_WslcTarPath)' == ''">$(_WslcOutDir)$(_WslcName).tar + + <_WslcTarDir>$([System.IO.Path]::GetDirectoryName('$(_WslcTarPath)')) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..05553336d 100644 --- a/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets +++ b/nuget/Microsoft.WSL.Containers/build/native/Microsoft.WSL.Containers.targets @@ -31,4 +31,64 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + <_WslcTlogToolPath>$(WslcCliPath) + <_WslcTarFullPath>$([System.IO.Path]::GetFullPath('$(_WslcTarPath)')) + + + + <_WslcTlogReadLines Include="^$(_WslcTlogToolPath)" /> + <_WslcTlogReadLines Include="@(_WslcSourceFiles->'%(FullPath)')" /> + <_WslcTlogWriteLines Include="^$(_WslcTlogToolPath)" /> + <_WslcTlogWriteLines Include="$(_WslcTarFullPath)" /> + + + + + + + + + + + <_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..d6f501e17 100644 --- a/nuget/Microsoft.WSL.Containers/build/net/Microsoft.WSL.Containers.targets +++ b/nuget/Microsoft.WSL.Containers/build/net/Microsoft.WSL.Containers.targets @@ -28,4 +28,29 @@ - \ No newline at end of file + + + + + + + + + + + + + + <_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 4829bed47..090758fb0 100644 --- a/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake +++ b/nuget/Microsoft.WSL.Containers/cmake/Microsoft.WSL.ContainersConfig.cmake @@ -55,3 +55,143 @@ 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:latest +# DOCKERFILE container/Dockerfile +# CONTEXT container/ +# SOURCES container/src/*.cpp container/src/*.h +# TAR_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/my-server.tar +# ) +# +# add_dependencies(my_app my-server) +# +# The first positional argument is the CMake target name. +# IMAGE is the container image reference (required); may include a tag +# (e.g. 'my-server:v1'). ':latest' is appended automatically when omitted. +# TAR_LOCATION is the output path for the saved image tarball +# (optional; defaults to ${CMAKE_CURRENT_BINARY_DIR}/.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 + "PRUNE_AFTER_BUILD" # options (boolean flags) + "IMAGE;DOCKERFILE;CONTEXT;TAR_LOCATION" # one-value keywords + "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") + 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() + + # Append :latest when IMAGE has no tag. Detect by looking for ':' after the + # last '/', so registry-port refs like localhost:5000/repo aren't misread. + 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() + # Normalize TAR_LOCATION to an absolute path. A bare filename or relative + # path would leave _tar_dir empty below and break `make_directory ""`. + # 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 on PATH (the WSL MSI puts it there). + 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', or set the WSLC_CLI_PATH variable to a specific wslc.exe path.") + endif() + endif() + + # 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}'. 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 + 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}") + + # 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() + + 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) + 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() + + # 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 "${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}.tmp" "${_image_ref}" + COMMAND ${CMAKE_COMMAND} -E rename "${ARG_TAR_LOCATION}.tmp" "${ARG_TAR_LOCATION}" + ${_prune_command} + DEPENDS ${_resolved_sources} "${_dockerfile_path}" + COMMENT "WSLC: Building image '${_image_ref}', saving to '${ARG_TAR_LOCATION}'${_prune_comment}..." + VERBATIM + ) + + add_custom_target(${_target_name} DEPENDS "${ARG_TAR_LOCATION}") +endfunction()