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