gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/tools/bazel.mk (about) 1 #!/usr/bin/make -f 2 3 # Copyright 2018 The gVisor Authors. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 ## 18 ## Docker options. 19 ## 20 ## This file supports targets that wrap bazel in a running Docker 21 ## container to simplify development. Some options are available to 22 ## control the behavior of this container: 23 ## 24 ## USER - The in-container user. 25 ## DOCKER_RUN_OPTIONS - Options for the container (default: --privileged, required for tests). 26 ## DOCKER_NAME - The container name (default: gvisor-bazel-HASH). 27 ## DOCKER_HOSTNAME - The container name (default: same as DOCKER_NAME). 28 ## DOCKER_PRIVILEGED - Docker privileged flags (default: --privileged). 29 ## UNSANDBOXED_RUNTIME - Name of the Docker runtime to use for the 30 ## unsandboxed build container. Defaults to runc. 31 ## PRE_BAZEL_INIT - If set, run this command with bash outside the Bazel 32 ## server container. 33 ## BAZEL_CACHE - The bazel cache directory (default: detected). 34 ## GCLOUD_CONFIG - The gcloud config directory (detect: detected). 35 ## DOCKER_SOCKET - The Docker socket (default: detected). 36 ## DEVICE_FILE - An optional device file to expose in the container 37 ## (default: no device file is exposed). 38 ## 39 ## To opt out of these wrappers, set DOCKER_BUILD=false. 40 DOCKER_BUILD := true 41 ifeq ($(DOCKER_BUILD),true) 42 -include bazel-server 43 endif 44 45 # See base Makefile. 46 BRANCH_NAME := $(shell (git branch --show-current 2>/dev/null || \ 47 git rev-parse --abbrev-ref HEAD 2>/dev/null) | \ 48 xargs -n 1 basename 2>/dev/null) 49 BUILD_ROOTS := bazel-bin/ bazel-out/ 50 RACE_FLAGS := --config=race 51 52 # Bazel container configuration (see below). 53 USER := $(shell whoami) 54 HASH := $(shell realpath -m $(CURDIR) | md5sum | cut -c1-8) 55 BUILDER_NAME := gvisor-builder-$(HASH)-$(ARCH) 56 BUILDER_HOSTNAME := $(BUILDER_NAME) 57 DOCKER_NAME := gvisor-bazel-$(HASH)-$(ARCH) 58 DOCKER_HOSTNAME := $(DOCKER_NAME) 59 DOCKER_PRIVILEGED := --privileged 60 UNSANDBOXED_RUNTIME ?= runc 61 BAZEL_CACHE := $(HOME)/.cache/bazel/ 62 GCLOUD_CONFIG := $(HOME)/.config/gcloud/ 63 DOCKER_SOCKET := /var/run/docker.sock 64 DOCKER_CONFIG := /etc/docker 65 DEVICE_FILE ?= 66 PRE_BAZEL_INIT ?= 67 68 ## 69 ## Bazel helpers. 70 ## 71 ## Bazel will be run with standard flags. You can specify the following flags 72 ## to control which flags are passed: 73 ## 74 ## STARTUP_OPTIONS - Startup options passed to Bazel. 75 ## BAZEL_TEST_OUTPUT - Test output policy; shown on failures only by 76 ## default, set to "streamed" to show test logs as 77 ## they happen. 78 ## 79 STARTUP_OPTIONS := 80 BAZEL_OPTIONS ?= 81 BAZEL_REMOTE_CACHE ?= 82 BAZEL := bazel $(STARTUP_OPTIONS) 83 BASE_OPTIONS := --color=no --curses=no $(BAZEL_REMOTE_CACHE) 84 BAZEL_TEST_OUTPUT ?= errors 85 TEST_OPTIONS += $(BASE_OPTIONS) \ 86 --incompatible_sandbox_hermetic_tmp=false \ 87 --test_output=$(BAZEL_TEST_OUTPUT) \ 88 --keep_going \ 89 --verbose_failures=true \ 90 --build_event_json_file=.build_events.json 91 92 # Basic options. 93 UID := $(shell id -u ${USER}) 94 GID := $(shell id -g ${USER}) 95 USERADD_OPTIONS := 96 DOCKER_RUN_OPTIONS := 97 DOCKER_RUN_OPTIONS += --rm 98 DOCKER_RUN_OPTIONS += --user $(UID):$(GID) 99 DOCKER_RUN_OPTIONS += --entrypoint "" 100 DOCKER_RUN_OPTIONS += --init 101 ifneq (,$(UNSANDBOXED_RUNTIME)) 102 DOCKER_RUN_OPTIONS += --runtime=$(UNSANDBOXED_RUNTIME) 103 endif 104 DOCKER_RUN_OPTIONS += -v "$(shell realpath -m $(BAZEL_CACHE)):$(BAZEL_CACHE)" 105 DOCKER_RUN_OPTIONS += -v "$(shell realpath -m $(GCLOUD_CONFIG)):$(GCLOUD_CONFIG)" 106 DOCKER_RUN_OPTIONS += -v "/tmp:/tmp" 107 DOCKER_EXEC_OPTIONS := --user $(UID):$(GID) 108 DOCKER_EXEC_OPTIONS += --interactive 109 ifeq (true,$(shell test -t 1 && echo true)) 110 DOCKER_EXEC_OPTIONS += --tty 111 endif 112 113 # If kernel headers are available, mount them too. 114 ifneq (,$(wildcard /lib/modules)) 115 DOCKER_RUN_OPTIONS += -v "/lib/modules:/lib/modules" 116 endif 117 KERNEL_HEADERS_DIR := $(shell realpath -m /lib/modules/$(shell uname -r)/build) 118 ifneq (,$(wildcard $(KERNEL_HEADERS_DIR))) 119 DOCKER_RUN_OPTIONS += -v "$(KERNEL_HEADERS_DIR):$(KERNEL_HEADERS_DIR)" 120 ifneq ($(shell realpath -m $(KERNEL_HEADERS_DIR)/Makefile),$(KERNEL_HEADERS_DIR)/Makefile) 121 KERNEL_HEADERS_DIR_LINKED := $(dir $(shell realpath -m $(KERNEL_HEADERS_DIR)/Makefile)) 122 DOCKER_RUN_OPTIONS += -v "$(KERNEL_HEADERS_DIR_LINKED):$(KERNEL_HEADERS_DIR_LINKED)" 123 endif 124 endif 125 126 # Add basic UID/GID options. 127 # 128 # Note that USERADD_DOCKER and GROUPADD_DOCKER are both defined as "deferred" 129 # variables in Make terminology, that is they will be expanded at time of use 130 # and may include other variables, including those defined below. 131 # 132 # NOTE: we pass -l to useradd below because otherwise you can hit a bug 133 # best described here: 134 # https://github.com/moby/moby/issues/5419#issuecomment-193876183 135 # TLDR; trying to add to /var/log/lastlog (sparse file) runs the machine out 136 # out of disk space. 137 ifneq ($(UID),0) 138 USERADD_DOCKER += useradd -l --uid $(UID) --non-unique --no-create-home \ 139 --gid $(GID) $(USERADD_OPTIONS) -d $(HOME) $(USER) && 140 endif 141 ifneq ($(GID),0) 142 GROUPADD_DOCKER += groupadd --gid $(GID) --non-unique $(USER) && 143 endif 144 145 # Add docker passthrough options. 146 ifneq ($(DOCKER_PRIVILEGED),) 147 DOCKER_RUN_OPTIONS += -v "$(DOCKER_SOCKET):$(DOCKER_SOCKET)" 148 DOCKER_RUN_OPTIONS += -v "$(DOCKER_CONFIG):$(DOCKER_CONFIG)" 149 DOCKER_RUN_OPTIONS += $(DOCKER_PRIVILEGED) 150 DOCKER_RUN_OPTIONS += --cap-add SYS_MODULE 151 DOCKER_EXEC_OPTIONS += $(DOCKER_PRIVILEGED) 152 DOCKER_GROUP := $(shell stat -c '%g' $(DOCKER_SOCKET)) 153 ifneq ($(GID),$(DOCKER_GROUP)) 154 USERADD_OPTIONS += --groups $(DOCKER_GROUP) 155 GROUPADD_DOCKER += groupadd --gid $(DOCKER_GROUP) --non-unique docker-$(HASH) && 156 DOCKER_RUN_OPTIONS += --group-add $(DOCKER_GROUP) 157 endif 158 endif 159 160 # Add KVM passthrough options. 161 ifneq (,$(wildcard /dev/kvm)) 162 DOCKER_RUN_OPTIONS += --device=/dev/kvm 163 KVM_GROUP := $(shell stat -c '%g' /dev/kvm) 164 ifneq ($(GID),$(KVM_GROUP)) 165 USERADD_OPTIONS += --groups $(KVM_GROUP) 166 GROUPADD_DOCKER += groupadd --gid $(KVM_GROUP) --non-unique kvm-$(HASH) && 167 DOCKER_RUN_OPTIONS += --group-add $(KVM_GROUP) 168 endif 169 endif 170 171 # Add other device file, if specified. 172 ifneq ($(DEVICE_FILE),) 173 DOCKER_RUN_OPTIONS += --device "$(DEVICE_FILE):$(DEVICE_FILE)" 174 endif 175 176 # Check if Docker API version supports cgroupns (supported in >=1.41). 177 # If not, don't include it in options. 178 ifeq ($(DOCKER_BUILD),true) 179 DOCKER_API_VERSION := $(shell docker version --format='{{.Server.APIVersion}}') 180 ifeq ($(shell echo $(DOCKER_API_VERSION) | tr '.' '\n' | wc -l),2) 181 ifeq ($(shell test $(shell echo $(DOCKER_API_VERSION) | cut -d. -f1) -gt 1 && echo true),true) 182 DOCKER_RUN_OPTIONS += --cgroupns=host 183 else # If API version 1, check second version component. 184 ifeq ($(shell test $(shell echo $(DOCKER_API_VERSION) | cut -d. -f2) -ge 41 && echo true),true) 185 DOCKER_RUN_OPTIONS += --cgroupns=host 186 endif 187 endif 188 endif 189 endif 190 191 # Top-level functions. 192 # 193 # This command runs a bazel server, and the container sticks around 194 # until the bazel server exits. This should ensure that it does not 195 # exit in the middle of running a build, but also it won't stick around 196 # forever. The build commands wrap around an appropriate exec into the 197 # container in order to perform work via the bazel client. 198 ifeq ($(DOCKER_BUILD),true) 199 wrapper = docker exec $(DOCKER_EXEC_OPTIONS) $(DOCKER_NAME) $(1) 200 wrapper_timeout = timeout $(1) docker exec $(DOCKER_EXEC_OPTIONS) $(DOCKER_NAME) $(2) 201 else 202 wrapper = $(1) 203 wrapper_timeout = timeout $(1) $(2) 204 endif 205 206 bazel-shutdown: ## Shuts down a running bazel server. 207 @$(call wrapper_timeout,--signal=KILL 30s,$(BAZEL) shutdown) || true 208 ifeq ($(DOCKER_BUILD),true) 209 # Docker can bug out and get stuck in `docker exec` despite the container 210 # already having been terminated. So this uses multiple ways to try to get the 211 # container to exit, and ignores which ones work and which ones don't. 212 # Instead, it just checks that the container no longer exists by the end of it. 213 @timeout --signal=KILL 10s docker wait $(DOCKER_NAME) 2>/dev/null || true 214 @docker stop --time=10 $(DOCKER_NAME) 2>/dev/null || true 215 # Double check that the container isn't running. 216 @bash -c "! docker inspect $(DOCKER_NAME) &>/dev/null" 217 endif 218 .PHONY: bazel-shutdown 219 220 bazel-alias: ## Emits an alias that can be used within the shell. 221 @echo "alias bazel='$(call wrapper,$(BAZEL))'" 222 .PHONY: bazel-alias 223 224 bazel-image: load-default ## Ensures that the local builder exists. 225 @$(call header,DOCKER BUILD) 226 @docker rm -f $(BUILDER_NAME) 2>/dev/null || true 227 @docker run --user 0:0 --entrypoint "" \ 228 --name $(BUILDER_NAME) --hostname $(BUILDER_HOSTNAME) \ 229 $(shell test -n "$(UNSANDBOXED_RUNTIME)" && echo "--runtime=$(UNSANDBOXED_RUNTIME)") \ 230 gvisor.dev/images/default \ 231 bash -c "$(GROUPADD_DOCKER) $(USERADD_DOCKER) if test -e /dev/kvm; then chmod a+rw /dev/kvm; fi" >&2 232 @docker commit $(BUILDER_NAME) gvisor.dev/images/builder >&2 233 .PHONY: bazel-image 234 235 ifneq (true,$(shell $(wrapper echo true))) 236 bazel-server: bazel-image ## Ensures that the server exists. 237 ifneq (,$(PRE_BAZEL_INIT)) 238 @$(call header,PRE_BAZEL_INIT) 239 @bash -euxo pipefail -c "$(PRE_BAZEL_INIT)" 240 endif 241 @$(call header,DOCKER RUN) 242 @docker rm -f $(DOCKER_NAME) 2>/dev/null || true 243 @mkdir -p $(BAZEL_CACHE) 244 @mkdir -p $(GCLOUD_CONFIG) 245 @docker run -d --name $(DOCKER_NAME) --hostname $(DOCKER_HOSTNAME) \ 246 -v "$(CURDIR):$(CURDIR)" \ 247 --workdir "$(CURDIR)" \ 248 --pid=host \ 249 $(DOCKER_RUN_OPTIONS) \ 250 gvisor.dev/images/builder \ 251 bash -c "set -x; tail -f --pid=\$$($(BAZEL) info server_pid) /dev/null" 252 else 253 bazel-server: 254 @ 255 endif 256 .PHONY: bazel-server 257 258 # build_paths extracts the built binary from the bazel stderr output. 259 # 260 # The last line is used to prevent terminal shenanigans. 261 build_paths = \ 262 (set -euo pipefail; \ 263 $(call wrapper,$(BAZEL) build $(BASE_OPTIONS) $(BAZEL_OPTIONS) $(1)) && \ 264 $(call wrapper,$(BAZEL) cquery $(BASE_OPTIONS) $(BAZEL_OPTIONS) $(1) --output=starlark --starlark:file=tools/show_paths.bzl) \ 265 | xargs -r -I {} bash -c 'test -e "{}" || exit 0; realpath -m "{}"' \ 266 | xargs -r -I {} bash -c 'set -euo pipefail; $(2)') 267 268 clean = $(call header,CLEAN) && $(call wrapper,$(BAZEL) clean) 269 build = $(call header,BUILD $(1)) && $(call build_paths,$(1),echo {}) 270 copy = $(call header,COPY $(1) $(2)) && $(call build_paths,$(1),cp -fa {} $(2)) 271 run = $(call header,RUN $(1) $(2)) && $(call build_paths,$(1),{} $(2)) 272 sudo = $(call header,SUDO $(1) $(2)) && $(call build_paths,$(1),sudo -E {} $(2)) 273 test = $(call header,TEST $(1)) && $(call wrapper,$(BAZEL) test --strip=never $(BAZEL_OPTIONS) $(TEST_OPTIONS) $(1)) 274 275 clean: ## Cleans the bazel cache. 276 @$(call clean) 277 .PHONY: clean 278 279 runsc-race: 280 @$(call build,--config=race runsc:runsc-race) 281 282 testlogs: ## Returns the most recent set of test logs. 283 @if test -f .build_events.json; then \ 284 cat .build_events.json | jq -r \ 285 'select(.testSummary?.overallStatus? | tostring | test("(FAILED|FLAKY|TIMEOUT)")) | "\(.id.testSummary.label) \(.testSummary.failed[].uri)"' | \ 286 sed -e 's|file://||'; \ 287 fi 288 .PHONY: testlogs