master
cmake 244 lines 10.3 KB
Raw
1 if(TARGET Microsoft.WSL.Containers::SDK)
2 return()
3 endif()
4
5 if(NOT WIN32)
6 message(FATAL_ERROR "Microsoft.WSL.Containers: This package only supports Windows.")
7 endif()
8
9 # Determine target architecture
10 if(CMAKE_GENERATOR_PLATFORM)
11 string(TOLOWER "${CMAKE_GENERATOR_PLATFORM}" _wslcsdk_platform)
12 if(_wslcsdk_platform STREQUAL "x64")
13 set(_wslcsdk_arch "x64")
14 elseif(_wslcsdk_platform STREQUAL "arm64")
15 set(_wslcsdk_arch "arm64")
16 else()
17 message(FATAL_ERROR
18 "Microsoft.WSL.Containers: Unsupported platform '${CMAKE_GENERATOR_PLATFORM}'."
19 " Supported: x64, ARM64.")
20 endif()
21 unset(_wslcsdk_platform)
22 elseif(CMAKE_SYSTEM_PROCESSOR)
23 string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _wslcsdk_platform)
24 if(_wslcsdk_platform MATCHES "amd64|x86_64|x64")
25 set(_wslcsdk_arch "x64")
26 elseif(_wslcsdk_platform MATCHES "arm64|aarch64")
27 set(_wslcsdk_arch "arm64")
28 else()
29 message(FATAL_ERROR
30 "Microsoft.WSL.Containers: Unsupported architecture '${CMAKE_SYSTEM_PROCESSOR}'."
31 " Supported: x64, ARM64.")
32 endif()
33 unset(_wslcsdk_platform)
34 else()
35 message(FATAL_ERROR
36 "Microsoft.WSL.Containers: Could not determine target architecture."
37 " Set CMAKE_GENERATOR_PLATFORM or CMAKE_SYSTEM_PROCESSOR.")
38 endif()
39
40 # Compute paths relative to package root (<root>/cmake/ -> <root>/)
41 get_filename_component(_wslcsdk_root "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
42 set(_wslcsdk_include_dir "${_wslcsdk_root}/include")
43 set(_wslcsdk_lib_dir "${_wslcsdk_root}/runtimes/win-${_wslcsdk_arch}")
44
45 # Create imported target
46 add_library(Microsoft.WSL.Containers::SDK SHARED IMPORTED GLOBAL)
47 set_target_properties(Microsoft.WSL.Containers::SDK PROPERTIES
48 INTERFACE_INCLUDE_DIRECTORIES "${_wslcsdk_include_dir}"
49 IMPORTED_IMPLIB "${_wslcsdk_lib_dir}/wslcsdk.lib"
50 IMPORTED_LOCATION "${_wslcsdk_lib_dir}/native/wslcsdk.dll"
51 )
52
53 # Clean up temporary variables
54 unset(_wslcsdk_arch)
55 unset(_wslcsdk_root)
56 unset(_wslcsdk_include_dir)
57 unset(_wslcsdk_lib_dir)
58
59 #[[
60 wslc_add_image(<target>
61 IMAGE <ref> DOCKERFILE <path> CONTEXT <dir>
62 [SOURCES <file>...] [TAR_LOCATION <path>]
63 [BUILD_ARGS <KEY=VALUE>...] [LABELS <KEY=VALUE>...])
64
65 Adds a target that builds a container image with 'wslc image build' and saves
66 it to a tarball with 'wslc image save'. The image is rebuilt when the
67 Dockerfile, a tracked source file, or an image build option changes.
68
69 Required:
70 <target> Name of the CMake target to create (first, positional).
71 IMAGE Image reference to tag; ':latest' is appended when the
72 reference has no tag.
73 DOCKERFILE Path to the Dockerfile.
74 CONTEXT Path to the build context directory.
75
76 Optional:
77 SOURCES Files whose changes trigger a rebuild (globs allowed).
78 Defaults to every file under CONTEXT.
79 TAR_LOCATION Output path for the saved tarball.
80 Defaults to ${CMAKE_CURRENT_BINARY_DIR}/<target>.tar.
81 BUILD_ARGS Build-time variables (KEY=VALUE), each passed as --build-arg.
82 LABELS Image labels (KEY=VALUE), each passed as --label.
83
84 Global variables (apply to every target created by wslc_add_image):
85 WSLC_IMAGE_BUILD_PULL
86 Always attempt to pull newer base images (--pull).
87 WSLC_IMAGE_BUILD_NO_CACHE
88 Build images without the layer cache (--no-cache).
89 WSLC_PRUNE_AFTER_BUILD
90 Run 'wslc image prune' after each image is saved.
91 WSLC_TREAT_PRUNE_FAILURE_AS_ERROR
92 Fail the build when the post-build prune fails. By default a
93 prune failure is ignored.
94
95 Example:
96 find_package(Microsoft.WSL.Containers REQUIRED)
97
98 wslc_add_image(my-server
99 IMAGE ghcr.io/myorg/my-server:latest
100 DOCKERFILE container/Dockerfile
101 CONTEXT container/
102 BUILD_ARGS VERSION=1.2.3 COMMIT=abcdef
103 LABELS org.opencontainers.image.source=https://example.com/repo)
104
105 add_dependencies(my_app my-server)
106 ]]
107
108 function(wslc_add_image _target_name)
109 cmake_parse_arguments(
110 PARSE_ARGV 1 ARG
111 "" # options (boolean flags)
112 "IMAGE;DOCKERFILE;CONTEXT;TAR_LOCATION" # one-value keywords
113 "SOURCES;BUILD_ARGS;LABELS" # multi-value keywords
114 )
115
116 # Reject typos / unknown keywords so they can't silently slip through.
117 if(ARG_UNPARSED_ARGUMENTS)
118 message(FATAL_ERROR "wslc_add_image: unknown argument(s): ${ARG_UNPARSED_ARGUMENTS}")
119 endif()
120
121 # Validate required arguments
122 if(NOT ARG_IMAGE)
123 message(FATAL_ERROR "wslc_add_image: IMAGE is required")
124 endif()
125 if(NOT ARG_DOCKERFILE)
126 message(FATAL_ERROR "wslc_add_image: DOCKERFILE is required")
127 endif()
128 if(NOT ARG_CONTEXT)
129 message(FATAL_ERROR "wslc_add_image: CONTEXT is required")
130 endif()
131
132 # Append :latest when IMAGE has no tag. Detect by looking for ':' after the
133 # last '/', so registry-port refs like localhost:5000/repo aren't misread.
134 string(FIND "${ARG_IMAGE}" "/" _last_slash_pos REVERSE)
135 string(FIND "${ARG_IMAGE}" ":" _last_colon_pos REVERSE)
136 if(_last_colon_pos GREATER _last_slash_pos)
137 set(_image_ref "${ARG_IMAGE}")
138 else()
139 set(_image_ref "${ARG_IMAGE}:latest")
140 endif()
141
142 # Defaults
143 if(NOT ARG_TAR_LOCATION)
144 set(ARG_TAR_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/${_target_name}.tar")
145 endif()
146 # Normalize TAR_LOCATION to an absolute path. A bare filename or relative
147 # path would leave _tar_dir empty below and break `make_directory ""`.
148 # Skip the normalization when the path contains a generator expression
149 # (e.g. $<TARGET_FILE_DIR:...>) — those resolve to absolute paths at
150 # build time and would otherwise get BASE_DIR prepended at configure
151 # time, producing a doubled path like build/$<...>/foo.tar.
152 if(NOT ARG_TAR_LOCATION MATCHES "\\$<")
153 get_filename_component(ARG_TAR_LOCATION "${ARG_TAR_LOCATION}" ABSOLUTE
154 BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
155 endif()
156
157 # Find wslc CLI on PATH (the WSL MSI puts it there).
158 if(NOT WSLC_CLI_PATH)
159 find_program(WSLC_CLI_PATH wslc)
160 if(NOT WSLC_CLI_PATH)
161 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.")
162 endif()
163 endif()
164
165 # Validate target name (used as CMake target id and default tar filename).
166 string(REGEX MATCH "[^a-zA-Z0-9_.+-]" _bad_char "${_target_name}")
167 if(_bad_char)
168 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 '-'.")
169 endif()
170
171 # Normalize paths to be independent of the build directory
172 get_filename_component(_dockerfile_path "${ARG_DOCKERFILE}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
173 get_filename_component(_context_path "${ARG_CONTEXT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
174
175 # Resolve source globs to file lists; default to CONTEXT contents if SOURCES omitted
176 if(ARG_SOURCES)
177 file(GLOB_RECURSE _resolved_sources CONFIGURE_DEPENDS ${ARG_SOURCES})
178 else()
179 file(GLOB_RECURSE _resolved_sources CONFIGURE_DEPENDS "${_context_path}/*")
180 endif()
181
182 get_filename_component(_tar_dir "${ARG_TAR_LOCATION}" DIRECTORY)
183
184 set(_build_options "")
185 list(APPEND _build_options -t "${_image_ref}")
186 foreach(_build_arg IN LISTS ARG_BUILD_ARGS)
187 if(NOT _build_arg STREQUAL "")
188 list(APPEND _build_options --build-arg "${_build_arg}")
189 endif()
190 endforeach()
191 foreach(_label IN LISTS ARG_LABELS)
192 if(NOT _label STREQUAL "")
193 list(APPEND _build_options --label "${_label}")
194 endif()
195 endforeach()
196 if(WSLC_IMAGE_BUILD_PULL)
197 list(APPEND _build_options --pull)
198 endif()
199 if(WSLC_IMAGE_BUILD_NO_CACHE)
200 list(APPEND _build_options --no-cache)
201 endif()
202 list(APPEND _build_options -f "${_dockerfile_path}")
203
204 # Track the effective build command as an input. file(GENERATE) preserves
205 # the timestamp when content is unchanged, so only option changes make the
206 # custom command out of date.
207 set(_build_signature_file "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${_target_name}-$<CONFIG>.wslc-options")
208 string(JOIN "\n" _build_signature ${_build_options} "${_context_path}")
209 file(GENERATE OUTPUT "${_build_signature_file}" CONTENT "${_build_signature}\n")
210
211 set(_prune_command "")
212 set(_prune_comment "")
213 if(WSLC_PRUNE_AFTER_BUILD)
214 if(WSLC_TREAT_PRUNE_FAILURE_AS_ERROR)
215 set(_prune_command COMMAND "${WSLC_CLI_PATH}" image prune)
216 else()
217 set(_prune_wrapper "${CMAKE_CURRENT_BINARY_DIR}/wslc_prune_ignore_failure.cmake")
218 if(NOT EXISTS "${_prune_wrapper}")
219 file(WRITE "${_prune_wrapper}"
220 "execute_process(COMMAND \"\${WSLC}\" image prune)\n")
221 endif()
222 set(_prune_command COMMAND "${CMAKE_COMMAND}" "-DWSLC=${WSLC_CLI_PATH}" -P "${_prune_wrapper}")
223 endif()
224 set(_prune_comment ", and pruning dangling images")
225 endif()
226
227 # Save to a .tmp and atomically rename on success — wslc image save uses
228 # CREATE_ALWAYS, which truncates the destination on entry, so a failed
229 # save would otherwise leave a partial tar with newer mtime than sources
230 # (and break incremental). The rename only happens if save succeeded.
231 add_custom_command(
232 OUTPUT "${ARG_TAR_LOCATION}"
233 COMMAND ${CMAKE_COMMAND} -E make_directory "${_tar_dir}"
234 COMMAND "${WSLC_CLI_PATH}" image build ${_build_options} "${_context_path}"
235 COMMAND "${WSLC_CLI_PATH}" image save -o "${ARG_TAR_LOCATION}.tmp" "${_image_ref}"
236 COMMAND ${CMAKE_COMMAND} -E rename "${ARG_TAR_LOCATION}.tmp" "${ARG_TAR_LOCATION}"
237 ${_prune_command}
238 DEPENDS ${_resolved_sources} "${_dockerfile_path}" "${_build_signature_file}"
239 COMMENT "WSLC: Building image '${_image_ref}', saving to '${ARG_TAR_LOCATION}'${_prune_comment}..."
240 VERBATIM
241 )
242
243 add_custom_target(${_target_name} DEPENDS "${ARG_TAR_LOCATION}")
244 endfunction()