Skip to content

Commit 9fd47ad

Browse files
committed
Add CMake build to repo, integrate with CI
Use CMake to codify common repo actions (e.g., go module dependency management, code gen, linting, building). Currently initial sketch/outline of work, focused on Golang on Windows and integrating with the GH CI; will need to add C binaries, building on Linux, integrating with the (Linux) Makefile for building the rootfs, running tests, better tracking of proto source files, compiling manifests, etc. Add `cmake\FindGo.cmake` to check that Golang (and `golangci-lint`) is available. Update default version used by `scripts/New-ResourceObjectFile.ps1` to `0.0.1` instead of `1.0.0`, and downgrade errors about missing VERSION file to warnings, since GH CI does not checkout git tags. Signed-off-by: Hamza El-Saawy <hamzaelsaawy@microsoft.com>
1 parent 90d47ab commit 9fd47ad

9 files changed

Lines changed: 865 additions & 300 deletions

File tree

.github/workflows/ci.yml

Lines changed: 99 additions & 273 deletions
Large diffs are not rendered by default.

.gitignore

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,12 @@ go.work.sum
5757
*.pem
5858
*.cose
5959

60-
rust/target/
60+
rust/target/
61+
62+
# cmake
63+
CMakeFiles/
64+
CMakeScripts/
65+
CMakeCache.txt
66+
CMakeUserPresets.json
67+
UserConfig.cmake
68+
cmake_install.cmake

CMakeLists.txt

Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
cmake_minimum_required(VERSION 3.25)
2+
3+
project(hcsshim
4+
DESCRIPTION "project contains Golang interface for the Windows Host Compute Service and OCI shim used by containerd"
5+
LANGUAGES NONE
6+
)
7+
8+
cmake_path(SET CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
9+
find_package(Go 1.24 REQUIRED
10+
OPTIONAL_COMPONENTS Linter
11+
)
12+
13+
# NOTES:
14+
# add_custom_target -> always out of date; we always call go since it has its own source tree dependency graph
15+
# USES_TERMINAL -> go doesn't like being called in parallel (i.e., go fmt, go mod, go build), so force only one single instances
16+
17+
# TODO:
18+
# - update `.github\workflows\ci.yml` when Linux host support is finished
19+
# - update `FindGo.cmake` flow to use functions and better control flow
20+
# - use ctest to run `gotestsum`
21+
# - add `gotest.tools/gotestsum` as tool to go.mod
22+
# - once nested test go module is removed (or workspaces are enabled), remove go_mod/go_fmt functions
23+
# - use clang both as CGo backend/linker and manifest via /MANIFEST:EMBED /MANIFESTINPUT:
24+
# - build Linux init binaries regardless of host (use WSL on Windows?)
25+
# - make CMakeLists.txt next to each binary/proto target instead of consolidated one here
26+
# - dedicated protoc `custom_command`s that tracks source .proto file
27+
# - download protoc and protobuf imports
28+
# - may not need protobuild, since its configuration is somewhat limited (i.e., edition vs version)
29+
# - call scripts\New-ResourceObjectFile.ps1 (not in go generate so its tracked as output)
30+
# - break out manifest generation in CMake (if not via clang)
31+
# - call simplify scripts\Set-VersionInfo.ps1 logic (and reuse with manifest/resource file)
32+
# - look at WSL's findVersion.cmake
33+
34+
if(WIN32)
35+
# don't rely on GOEXE since that depends on GOOS
36+
set(binary_ext .exe)
37+
endif()
38+
39+
set(go_common_env GOTOOLCHAIN=local GO111MODULE=on GOWORK=off)
40+
41+
# want the configuration-specific directory, but not the "build" directory
42+
cmake_path(GET CMAKE_BINARY_DIR PARENT_PATH OUT_DIR)
43+
# have one directory for both regular and testing binaries
44+
cmake_path(SET BIN_DIR ${OUT_DIR}/bin)
45+
46+
cmake_path(SET CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BIN_DIR})
47+
# mostly for completeness's sake
48+
cmake_path(SET CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${OUT_DIR}/lib)
49+
cmake_path(SET CMAKE_LIBRARY_OUTPUT_DIRECTORY ${OUT_DIR}/lib)
50+
51+
file(MAKE_DIRECTORY ${BIN_DIR})
52+
53+
message(STATUS "bin directory: ${BIN_DIR}")
54+
55+
if(Go_GOBIN)
56+
cmake_path(SET go_bin Go_GOBIN)
57+
elseif(Go_GOPATH)
58+
cmake_path(SET go_bin "${Go_GOPATH}/bin")
59+
else()
60+
message(FATAL_ERROR "Missing both 'GOPATH' and 'GOBIN' env settings")
61+
endif()
62+
message(STATUS "go bin directory: ${go_bin}")
63+
64+
# need pwsh for ./scripts
65+
find_program(PWSH pwsh PATH_SUFFIXES PowerShell 7)
66+
if (NOT EXISTS ${PWSH})
67+
message(FATAL_ERROR "Missing pwsh binary")
68+
endif()
69+
mark_as_advanced(PWSH)
70+
message(VERBOSE "pwsh executable: ${PWSH}")
71+
72+
##########################################################################################
73+
# go mod tidy and vendor
74+
##########################################################################################
75+
76+
# catch all target for `go mod tidy/vendor` on entire repo
77+
add_custom_target(go_mod ALL)
78+
set_target_properties(go_mod PROPERTIES FOLDER validation)
79+
80+
# go_mod(name [VENDOR] [DEPENDS <depend>...])
81+
function(go_mod name)
82+
cmake_parse_arguments(PARSE_ARGV 1 arg "VENDOR" "" "DEPENDS")
83+
84+
string(PREPEND name "go_mod_")
85+
86+
set(flags "-e")
87+
set(comment "tidying")
88+
if (arg_VENDOR)
89+
set(vendor_cmd ${CMAKE_COMMAND} -E env ${go_common_env} -- ${Go_EXECUTABLE} mod vendor ${flags})
90+
string(APPEND comment " and vendoring")
91+
endif()
92+
93+
add_custom_target(${name}
94+
COMMAND ${CMAKE_COMMAND} -E env ${go_common_env} -- ${Go_EXECUTABLE} mod tidy -v ${flags}
95+
COMMAND ${vendor_cmd}
96+
DEPENDS "${arg_DEPENDS}"
97+
COMMENT "${comment} go module ${CMAKE_CURRENT_SOURCE_DIR}"
98+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
99+
VERBATIM
100+
USES_TERMINAL
101+
)
102+
103+
add_dependencies(go_mod ${name})
104+
set_target_properties(${name} PROPERTIES FOLDER validation)
105+
106+
message(VERBOSE "go mod ${name}")
107+
endfunction()
108+
109+
go_mod(root VENDOR)
110+
111+
##########################################################################################
112+
# go generate
113+
##########################################################################################
114+
115+
# go generate (specifically, the New-ResourceObjectFile.ps1 calls) require the version to be set
116+
add_custom_target(set_version
117+
COMMAND ${PWSH} -NonInteractive -NoProfile -NoLogo -Command "./scripts/Set-VersionInfo.ps1"
118+
COMMENT "run Set-Version script"
119+
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
120+
VERBATIM
121+
)
122+
123+
# catch all target for `go generate` on entire repo
124+
add_custom_target(go_gen ALL)
125+
set_target_properties(go_gen PROPERTIES FOLDER generation)
126+
127+
function(go_gen name)
128+
string(PREPEND name "go_gen_")
129+
130+
add_custom_target(${name}
131+
COMMAND ${CMAKE_COMMAND} -E env ${go_common_env} GOOS=windows -- ${Go_EXECUTABLE} generate -x ./...
132+
DEPENDS set_version
133+
COMMENT "running 'go generate' on ${CMAKE_CURRENT_SOURCE_DIR}"
134+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
135+
VERBATIM
136+
USES_TERMINAL
137+
)
138+
139+
add_dependencies(go_gen ${name})
140+
set_target_properties(${name} PROPERTIES FOLDER generation)
141+
142+
message(VERBOSE "go gen ${name}")
143+
endfunction()
144+
145+
go_gen(root)
146+
147+
##########################################################################################
148+
# lint
149+
##########################################################################################
150+
151+
add_custom_target(lint_all)
152+
set_target_properties(lint_all PROPERTIES FOLDER validation)
153+
154+
if (Go_Linter_FOUND)
155+
cmake_path(SET _GO_LINT_CONFIG ${CMAKE_SOURCE_DIR}/.golangci.yml)
156+
cmake_path(SET _GO_LINT_VALIDATE_CONFIG_TARGET ${CMAKE_BINARY_DIR}/CmakeFiles/.golangci)
157+
158+
# validate the config, but only if its been modified
159+
add_custom_command(
160+
OUTPUT ${_GO_LINT_VALIDATE_CONFIG_TARGET}
161+
COMMAND ${Go_Linter_EXECUTABLE} config verify "--config=${_GO_LINT_CONFIG}"
162+
COMMAND ${CMAKE_COMMAND} -E touch ${_GO_LINT_VALIDATE_CONFIG_TARGET}
163+
DEPENDS ${_GO_LINT_CONFIG}
164+
COMMENT "validating linter config ${_GO_LINT_CONFIG}"
165+
VERBATIM
166+
USES_TERMINAL
167+
)
168+
endif()
169+
170+
function(go_lint name)
171+
if (NOT Go_Linter_FOUND)
172+
message(WARNING "skipping go lint target \"${name}\" due to missing linter binary")
173+
endif()
174+
175+
cmake_parse_arguments(PARSE_ARGV 1 arg "" "TARGET_OS" "TAGS")
176+
177+
string(PREPEND name "go_lint_")
178+
179+
parse_goos("${arg_TARGET_OS}" os)
180+
set(env ${go_common_env} "GOOS=${os}")
181+
set(comment_extra "")
182+
183+
set(flags "--timeout=10m" "--max-issues-per-linter=0" "--max-same-issues=0"
184+
"--modules-download-mode=readonly" "--verbose" "--fix" "--path-mode=abs"
185+
"--config=")
186+
187+
if (NOT "${arg_TAGS}" STREQUAL "")
188+
list(APPEND flags "--build-tags=$<JOIN:${arg_TAGS},,>")
189+
set(comment_extra " with tags ${arg_TAGS}")
190+
endif()
191+
192+
add_custom_target(${name}
193+
COMMAND ${CMAKE_COMMAND} -E env ${env} -- ${Go_Linter_EXECUTABLE} run "${flags}"
194+
DEPENDS ${_GO_LINT_VALIDATE_CONFIG_TARGET}
195+
COMMENT "linting ${CMAKE_CURRENT_SOURCE_DIR} for ${os}${comment_extra}"
196+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
197+
COMMAND_EXPAND_LISTS
198+
VERBATIM
199+
USES_TERMINAL
200+
)
201+
add_dependencies(lint_all ${name})
202+
set_target_properties(${name} PROPERTIES FOLDER validation)
203+
204+
message(VERBOSE "golangci-lint ${name}")
205+
endfunction()
206+
207+
go_lint(root TAGS admin integration)
208+
go_lint(root_lcow TAGS lcow admin integration)
209+
go_lint(root_wcow TAGS wcow admin integration)
210+
go_lint(root_linux TARGET_OS linux TAGS admin integration)
211+
212+
##########################################################################################
213+
# protobuf
214+
##########################################################################################
215+
216+
add_custom_target(proto
217+
COMMAND ${PWSH} -NonInteractive -NoProfile -NoLogo -Command "./scripts/Update-Proto.ps1"
218+
COMMENT building protofiles
219+
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
220+
VERBATIM
221+
)
222+
set_target_properties(proto PROPERTIES FOLDER generation)
223+
224+
##########################################################################################
225+
# build (testing) binaries
226+
##########################################################################################
227+
228+
add_custom_target(build_all
229+
COMMENT "build all binaries"
230+
)
231+
set_target_properties(build_all PROPERTIES FOLDER build)
232+
233+
add_custom_target(build_all_test
234+
COMMENT "build all testing binaries"
235+
)
236+
set_target_properties(build_all_test PROPERTIES FOLDER build/test)
237+
238+
# go_build(package [NAME <name>] [TARGET_OS <WINDOWS|LINUX>] [TEST] [RACE] [COVERAGE] [DEPENDS <depend>...] [TAGS <tags>...])
239+
function(go_build package)
240+
set(options TEST RACE COVERAGE)
241+
set(oneValueArgs NAME TARGET_OS BINARY_NAME)
242+
set(multiValueArgs DEPENDS TAGS)
243+
cmake_parse_arguments(PARSE_ARGV 1 arg "${options}" "${oneValueArgs}" "${multiValueArgs}")
244+
245+
246+
if ("${arg_BINARY_NAME}" STREQUAL "")
247+
cmake_path(GET package FILENAME binary)
248+
else()
249+
set(binary ${arg_BINARY_NAME})
250+
endif()
251+
252+
cmake_path(SET package "${CMAKE_CURRENT_SOURCE_DIR}/${package}")
253+
cmake_path(SET out_dir "${BIN_DIR}")
254+
255+
# for now, building will always require running `go gen` to create the needed syso files
256+
# will have to move those targets directly into CMake
257+
list(PREPEND arg_DEPENDS "set_version" "go_mod_root" "go_gen")
258+
259+
parse_goos("${arg_TARGET_OS}" os)
260+
set(env ${go_common_env} "GOOS=${os}")
261+
262+
if (arg_TEST)
263+
set(go_cmd "test")
264+
string(APPEND binary ".test")
265+
266+
list(APPEND flags "-c" "-gcflags=all=-d=checkptr")
267+
268+
set(comment_type "testing binary")
269+
else()
270+
set(go_cmd "build")
271+
272+
list(APPEND ld_flags "$<$<NOT:$<CONFIG:Debug>>:-s;-w>")
273+
list(APPEND flags "$<$<NOT:$<CONFIG:Debug>>:-trimpath>")
274+
275+
set(comment_type "binary")
276+
endif()
277+
278+
if (arg_RACE)
279+
list(APPEND flags "-race")
280+
list(APPEND env "CGO_ENABLED=1")
281+
endif()
282+
283+
if (os STREQUAL "windows")
284+
string(APPEND binary ".exe")
285+
list(APPEND ld_flags "-linkmode=external")
286+
endif()
287+
288+
cmake_path(SET out "${out_dir}/${binary}")
289+
290+
if (arg_COVERAGE)
291+
# only enable coverage for debug builds
292+
list(APPEND flags "$<$<CONFIG:Debug>:-cover;-coverpkg=\"github.com/Microsoft/hcsshim/...\">")
293+
endif()
294+
295+
if (NOT "${ld_flags}" STREQUAL "")
296+
list(APPEND flags "-ldflags=$<JOIN:${ld_flags}, >")
297+
endif()
298+
299+
if (NOT "${arg_TAGS}" STREQUAL "")
300+
list(APPEND flags "-tags=$<JOIN:${arg_TAGS},,>")
301+
endif()
302+
303+
if ("${arg_NAME}" STREQUAL "")
304+
set(arg_NAME ${binary})
305+
endif()
306+
307+
add_custom_target(${arg_NAME}
308+
BYPRODUCTS ${out}
309+
COMMAND ${CMAKE_COMMAND} -E env ${env} -- ${Go_EXECUTABLE} ${go_cmd} "${flags}" -o=${out}
310+
DEPENDS ${arg_DEPENDS}
311+
COMMENT "building go ${comment_type} ${binary}"
312+
WORKING_DIRECTORY "${package}"
313+
COMMAND_EXPAND_LISTS
314+
VERBATIM
315+
USES_TERMINAL
316+
)
317+
318+
if (arg_TEST)
319+
add_dependencies(build_all_test ${arg_NAME})
320+
set_target_properties(${arg_NAME} PROPERTIES FOLDER build/test)
321+
else()
322+
add_dependencies(build_all ${arg_NAME})
323+
set_target_properties(${arg_NAME} PROPERTIES FOLDER build)
324+
endif()
325+
326+
message(STATUS "go ${comment_type} ${arg_NAME}: ${out}")
327+
endfunction()
328+
329+
go_build(cmd/containerd-shim-runhcs-v1 NAME shim)
330+
go_build(cmd/containerd-shim-lcow-v2 TAGS lcow NAME shim-lcow)
331+
go_build(cmd/device-util)
332+
go_build(cmd/gcs TARGET_OS linux)
333+
go_build(cmd/gcs-sidecar)
334+
go_build(cmd/gcstools TARGET_OS linux)
335+
go_build(cmd/hooks/wait-paths TARGET_OS linux)
336+
go_build(cmd/jobobject-util)
337+
go_build(cmd/mkuvmcim)
338+
go_build(cmd/ncproxy)
339+
go_build(cmd/runhcs)
340+
go_build(cmd/shimdiag)
341+
go_build(cmd/tar2ext4)
342+
go_build(cmd/tar2ext4 TARGET_OS linux NAME tar2ext4)
343+
go_build(cmd/wclayer)
344+
345+
go_build(internal/tools/extendedtask)
346+
go_build(internal/tools/grantvmgroupaccess)
347+
go_build(internal/tools/hvsocketaddr)
348+
go_build(internal/tools/networkagent)
349+
go_build(internal/tools/policyenginesimulator)
350+
go_build(internal/tools/policyenginesimulator TARGET_OS linux)
351+
go_build(internal/tools/rootfs)
352+
go_build(internal/tools/rootfs TARGET_OS linux)
353+
go_build(internal/tools/securitypolicy)
354+
go_build(internal/tools/securitypolicy TARGET_OS linux)
355+
go_build(internal/tools/snp-report TARGET_OS linux)
356+
go_build(internal/tools/uvmboot)
357+
go_build(internal/tools/zapdir)
358+
359+
##########################################################################################
360+
# validation
361+
##########################################################################################
362+
363+
add_custom_target(validate DEPENDS go_mod go_gen proto lint_all)
364+
set_target_properties(validate PROPERTIES FOLDER validation)
365+
366+
##########################################################################################
367+
# add test directory after the custom commands and functions have been defined
368+
##########################################################################################
369+
370+
add_subdirectory(test)

0 commit comments

Comments
 (0)