diff options
Diffstat (limited to 'tools/bazel.mk')
-rw-r--r-- | tools/bazel.mk | 263 |
1 files changed, 139 insertions, 124 deletions
diff --git a/tools/bazel.mk b/tools/bazel.mk index 3a7de427f..2b20457e9 100644 --- a/tools/bazel.mk +++ b/tools/bazel.mk @@ -14,49 +14,77 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Make hacks. -EMPTY := -SPACE := $(EMPTY) $(EMPTY) +## +## Docker options. +## +## This file supports targets that wrap bazel in a running Docker +## container to simplify development. Some options are available to +## control the behavior of this container: +## +## USER - The in-container user. +## DOCKER_RUN_OPTIONS - Options for the container (default: --privileged, required for tests). +## DOCKER_NAME - The container name (default: gvisor-bazel-HASH). +## DOCKER_PRIVILEGED - Docker privileged flags (default: --privileged). +## BAZEL_CACHE - The bazel cache directory (default: detected). +## GCLOUD_CONFIG - The gcloud config directory (detect: detected). +## DOCKER_SOCKET - The Docker socket (default: detected). +## +## To opt out of these wrappers, set DOCKER_BUILD=false. +DOCKER_BUILD := true +ifeq ($(DOCKER_BUILD),true) +-include bazel-server +endif # See base Makefile. -SHELL=/bin/bash -o pipefail BRANCH_NAME := $(shell (git branch --show-current 2>/dev/null || \ - git rev-parse --abbrev-ref HEAD 2>/dev/null) | \ - xargs -n 1 basename 2>/dev/null) + git rev-parse --abbrev-ref HEAD 2>/dev/null) | \ + xargs -n 1 basename 2>/dev/null) BUILD_ROOTS := bazel-bin/ bazel-out/ # Bazel container configuration (see below). USER := $(shell whoami) HASH := $(shell readlink -m $(CURDIR) | md5sum | cut -c1-8) -BUILDER_BASE := gvisor.dev/images/default -BUILDER_IMAGE := gvisor.dev/images/builder -BUILDER_NAME := gvisor-builder-$(HASH) -DOCKER_NAME := gvisor-bazel-$(HASH) +BUILDER_NAME := gvisor-builder-$(HASH)-$(ARCH) +DOCKER_NAME := gvisor-bazel-$(HASH)-$(ARCH) DOCKER_PRIVILEGED := --privileged -BAZEL_CACHE := $(shell readlink -m ~/.cache/bazel/) -GCLOUD_CONFIG := $(shell readlink -m ~/.config/gcloud/) +BAZEL_CACHE := $(HOME)/.cache/bazel/ +GCLOUD_CONFIG := $(HOME)/.config/gcloud/ DOCKER_SOCKET := /var/run/docker.sock -DOCKER_CONFIG := /etc/docker/daemon.json +DOCKER_CONFIG := /etc/docker -# Bazel flags. -BAZEL := bazel $(STARTUP_OPTIONS) -OPTIONS += --color=no --curses=no +## +## Bazel helpers. +## +## Bazel will be run with standard flags. You can specify the following flags +## to control which flags are passed: +## +## STARTUP_OPTIONS - Startup options passed to Bazel. +## +STARTUP_OPTIONS := +BAZEL_OPTIONS := +BAZEL := bazel $(STARTUP_OPTIONS) +BASE_OPTIONS := --color=no --curses=no +TEST_OPTIONS := $(BASE_OPTIONS) \ + --test_output=errors \ + --keep_going \ + --verbose_failures=true \ + --build_event_json_file=.build_events.json # Basic options. UID := $(shell id -u ${USER}) GID := $(shell id -g ${USER}) USERADD_OPTIONS := -FULL_DOCKER_RUN_OPTIONS := $(DOCKER_RUN_OPTIONS) -FULL_DOCKER_RUN_OPTIONS += --user $(UID):$(GID) -FULL_DOCKER_RUN_OPTIONS += --entrypoint "" -FULL_DOCKER_RUN_OPTIONS += --init -FULL_DOCKER_RUN_OPTIONS += -v "$(BAZEL_CACHE):$(BAZEL_CACHE)" -FULL_DOCKER_RUN_OPTIONS += -v "$(GCLOUD_CONFIG):$(GCLOUD_CONFIG)" -FULL_DOCKER_RUN_OPTIONS += -v "/tmp:/tmp" -FULL_DOCKER_EXEC_OPTIONS := --user $(UID):$(GID) -FULL_DOCKER_EXEC_OPTIONS += --interactive -ifeq (true,$(shell [[ -t 0 ]] && echo true)) -FULL_DOCKER_EXEC_OPTIONS += --tty +DOCKER_RUN_OPTIONS := +DOCKER_RUN_OPTIONS += --user $(UID):$(GID) +DOCKER_RUN_OPTIONS += --entrypoint "" +DOCKER_RUN_OPTIONS += --init +DOCKER_RUN_OPTIONS += -v "$(shell readlink -m $(BAZEL_CACHE)):$(BAZEL_CACHE)" +DOCKER_RUN_OPTIONS += -v "$(shell readlink -m $(GCLOUD_CONFIG)):$(GCLOUD_CONFIG)" +DOCKER_RUN_OPTIONS += -v "/tmp:/tmp" +DOCKER_EXEC_OPTIONS := --user $(UID):$(GID) +DOCKER_EXEC_OPTIONS += --interactive +ifeq (true,$(shell test -t 0 && echo true)) +DOCKER_EXEC_OPTIONS += --tty endif # Add basic UID/GID options. @@ -72,7 +100,7 @@ endif # out of disk space. ifneq ($(UID),0) USERADD_DOCKER += useradd -l --uid $(UID) --non-unique --no-create-home \ - --gid $(GID) $(USERADD_OPTIONS) -d $(HOME) $(USER) && + --gid $(GID) $(USERADD_OPTIONS) -d $(HOME) $(USER) && endif ifneq ($(GID),0) GROUPADD_DOCKER += groupadd --gid $(GID) --non-unique $(USER) && @@ -80,126 +108,113 @@ endif # Add docker passthrough options. ifneq ($(DOCKER_PRIVILEGED),) -FULL_DOCKER_RUN_OPTIONS += -v "$(DOCKER_SOCKET):$(DOCKER_SOCKET)" -# TODO(gvisor.dev/issue/1624): Remove docker config volume. This is required -# temporarily for checking VFS1 vs VFS2 by some tests. -FULL_DOCKER_RUN_OPTIONS += -v "$(DOCKER_CONFIG):$(DOCKER_CONFIG)" -FULL_DOCKER_RUN_OPTIONS += $(DOCKER_PRIVILEGED) -FULL_DOCKER_EXEC_OPTIONS += $(DOCKER_PRIVILEGED) +DOCKER_RUN_OPTIONS += -v "$(DOCKER_SOCKET):$(DOCKER_SOCKET)" +DOCKER_RUN_OPTIONS += -v "$(DOCKER_CONFIG):$(DOCKER_CONFIG)" +DOCKER_RUN_OPTIONS += $(DOCKER_PRIVILEGED) +DOCKER_EXEC_OPTIONS += $(DOCKER_PRIVILEGED) DOCKER_GROUP := $(shell stat -c '%g' $(DOCKER_SOCKET)) ifneq ($(GID),$(DOCKER_GROUP)) USERADD_OPTIONS += --groups $(DOCKER_GROUP) GROUPADD_DOCKER += groupadd --gid $(DOCKER_GROUP) --non-unique docker-$(HASH) && -FULL_DOCKER_RUN_OPTIONS += --group-add $(DOCKER_GROUP) +DOCKER_RUN_OPTIONS += --group-add $(DOCKER_GROUP) endif endif # Add KVM passthrough options. ifneq (,$(wildcard /dev/kvm)) -FULL_DOCKER_RUN_OPTIONS += --device=/dev/kvm +DOCKER_RUN_OPTIONS += --device=/dev/kvm KVM_GROUP := $(shell stat -c '%g' /dev/kvm) ifneq ($(GID),$(KVM_GROUP)) USERADD_OPTIONS += --groups $(KVM_GROUP) GROUPADD_DOCKER += groupadd --gid $(KVM_GROUP) --non-unique kvm-$(HASH) && -FULL_DOCKER_RUN_OPTIONS += --group-add $(KVM_GROUP) +DOCKER_RUN_OPTIONS += --group-add $(KVM_GROUP) endif endif -# Load the appropriate config. -ifneq (,$(BAZEL_CONFIG)) -OPTIONS += --config=$(BAZEL_CONFIG) +# Top-level functions. +# +# This command runs a bazel server, and the container sticks around +# until the bazel server exits. This should ensure that it does not +# exit in the middle of running a build, but also it won't stick around +# forever. The build commands wrap around an appropriate exec into the +# container in order to perform work via the bazel client. +ifeq ($(DOCKER_BUILD),true) +wrapper = docker exec $(DOCKER_EXEC_OPTIONS) $(DOCKER_NAME) $(1) +else +wrapper = $(1) endif -bazel-image: load-default - @if docker ps --all | grep $(BUILDER_NAME); then docker rm -f $(BUILDER_NAME); fi - docker run --user 0:0 --entrypoint "" --name $(BUILDER_NAME) \ - $(BUILDER_BASE) \ - sh -c "$(GROUPADD_DOCKER) \ - $(USERADD_DOCKER) \ - if [[ -e /dev/kvm ]]; then chmod a+rw /dev/kvm; fi" - docker commit $(BUILDER_NAME) $(BUILDER_IMAGE) - @docker rm -f $(BUILDER_NAME) -.PHONY: bazel-image - -## -## Bazel helpers. -## -## This file supports targets that wrap bazel in a running Docker -## container to simplify development. Some options are available to -## control the behavior of this container: -## USER - The in-container user. -## DOCKER_RUN_OPTIONS - Options for the container (default: --privileged, required for tests). -## DOCKER_NAME - The container name (default: gvisor-bazel-HASH). -## BAZEL_CACHE - The bazel cache directory (default: detected). -## GCLOUD_CONFIG - The gcloud config directory (detect: detected). -## DOCKER_SOCKET - The Docker socket (default: detected). -## -bazel-server-start: bazel-image ## Starts the bazel server. - @mkdir -p $(BAZEL_CACHE) - @mkdir -p $(GCLOUD_CONFIG) - @if docker ps --all | grep $(DOCKER_NAME); then docker rm -f $(DOCKER_NAME); fi - # This command runs a bazel server, and the container sticks around - # until the bazel server exits. This should ensure that it does not - # exit in the middle of running a build, but also it won't stick around - # forever. The build commands wrap around an appropriate exec into the - # container in order to perform work via the bazel client. - docker run -d --rm --name $(DOCKER_NAME) \ - -v "$(CURDIR):$(CURDIR)" \ - --workdir "$(CURDIR)" \ - $(FULL_DOCKER_RUN_OPTIONS) \ - $(BUILDER_IMAGE) \ - sh -c "tail -f --pid=\$$($(BAZEL) info server_pid) /dev/null" -.PHONY: bazel-server-start - bazel-shutdown: ## Shuts down a running bazel server. - @docker exec $(FULL_DOCKER_EXEC_OPTIONS) $(DOCKER_NAME) $(BAZEL) shutdown; \ - rc=$$?; docker kill $(DOCKER_NAME) || [[ $$rc -ne 0 ]] + @$(call wrapper,$(BAZEL) shutdown) .PHONY: bazel-shutdown bazel-alias: ## Emits an alias that can be used within the shell. - @echo "alias bazel='docker exec $(FULL_DOCKER_EXEC_OPTIONS) $(DOCKER_NAME) bazel'" + @echo "alias bazel='$(call wrapper,$(BAZEL))'" .PHONY: bazel-alias -bazel-server: ## Ensures that the server exists. Used as an internal target. - @docker exec $(FULL_DOCKER_EXEC_OPTIONS) $(DOCKER_NAME) true || $(MAKE) bazel-server-start -.PHONY: bazel-server +bazel-image: load-default ## Ensures that the local builder exists. + @$(call header,DOCKER BUILD) + @docker rm -f $(BUILDER_NAME) 2>/dev/null || true + @docker run --user 0:0 --entrypoint "" --name $(BUILDER_NAME) gvisor.dev/images/default \ + bash -c "$(GROUPADD_DOCKER) $(USERADD_DOCKER) if test -e /dev/kvm; then chmod a+rw /dev/kvm; fi" >&2 + @docker commit $(BUILDER_NAME) gvisor.dev/images/builder >&2 +.PHONY: bazel-image -build_cmd = docker exec $(FULL_DOCKER_EXEC_OPTIONS) $(DOCKER_NAME) sh -o pipefail -c '$(BAZEL) build $(OPTIONS) "$(TARGETS)"' - -build_paths = $(build_cmd) 2>&1 \ - | tee /proc/self/fd/2 \ - | grep -A1 -E '^Target' \ - | grep -E '^ ($(subst $(SPACE),|,$(BUILD_ROOTS)))' \ - | sed "s/ /\n/g" \ - | strings -n 10 \ - | awk '{$$1=$$1};1' \ - | xargs -n 1 -I {} readlink -f "{}" \ - | xargs -n 1 -I {} sh -c "$(1)" - -build: bazel-server - @$(call build_cmd) -.PHONY: build - -copy: bazel-server -ifeq (,$(DESTINATION)) - $(error Destination not provided.) +# Note: when starting the bazel server, we tie the life of the container to the +# bazel server's life, so that the container disappears naturally. +ifneq (true,$(shell $(wrapper echo true))) +bazel-server: bazel-image ## Ensures that the server exists. + @$(call header,DOCKER RUN) + @docker rm -f $(DOCKER_NAME) 2>/dev/null || true + @mkdir -p $(GCLOUD_CONFIG) + @mkdir -p $(BAZEL_CACHE) + @docker run -d --rm --name $(DOCKER_NAME) \ + -v "$(CURDIR):$(CURDIR)" \ + --workdir "$(CURDIR)" \ + $(DOCKER_RUN_OPTIONS) \ + gvisor.dev/images/builder \ + bash -c "set -x; tail -f --pid=\$$($(BAZEL) info server_pid) /dev/null" +else +bazel-server: + @ endif - @$(call build_paths,cp -fa {} $(DESTINATION)) - -run: bazel-server - @$(call build_paths,{} $(ARGS)) -.PHONY: run - -sudo: bazel-server - @$(call build_paths,sudo -E {} $(ARGS)) -.PHONY: sudo - -test: OPTIONS += --test_output=errors --keep_going --verbose_failures=true -test: bazel-server - @docker exec $(FULL_DOCKER_EXEC_OPTIONS) $(DOCKER_NAME) $(BAZEL) test $(OPTIONS) $(TARGETS) -.PHONY: test +.PHONY: bazel-server -query: - @$(MAKE) bazel-server >&2 # If we need to start, ensure stdout is not polluted. - @docker exec $(FULL_DOCKER_EXEC_OPTIONS) $(DOCKER_NAME) sh -o pipefail -c '$(BAZEL) query $(OPTIONS) "$(TARGETS)" 2>/dev/null' -.PHONY: query +# build_paths extracts the built binary from the bazel stderr output. +# +# This could be alternately done by parsing the bazel build event stream, but +# this is a complex schema, and begs the question: what will build the thing +# that parses the output? Bazel? Do we need a separate bootstrapping build +# command here? Yikes, let's just stick with the ugly shell pipeline. +# +# The last line is used to prevent terminal shenanigans. +build_paths = \ + (set -euo pipefail; \ + $(call wrapper,$(BAZEL) build $(BASE_OPTIONS) $(BAZEL_OPTIONS) $(1)) 2>&1 \ + | tee /proc/self/fd/2 \ + | sed -n -e '/^Target/,$$p' \ + | sed -n -e '/^ \($(subst /,\/,$(subst $(SPACE),\|,$(BUILD_ROOTS)))\)/p' \ + | sed -e 's/ /\n/g' \ + | awk '{$$1=$$1};1' \ + | strings \ + | xargs -r -n 1 -I {} readlink -f "{}" \ + | xargs -r -n 1 -I {} bash -c 'set -xeuo pipefail; $(2)') + +clean = $(call header,CLEAN) && $(call wrapper,$(BAZEL) clean) +build = $(call header,BUILD $(1)) && $(call build_paths,$(1),echo {}) +copy = $(call header,COPY $(1) $(2)) && $(call build_paths,$(1),cp -fa {} $(2)) +run = $(call header,RUN $(1) $(2)) && $(call build_paths,$(1),{} $(2)) +sudo = $(call header,SUDO $(1) $(2)) && $(call build_paths,$(1),sudo -E {} $(2)) +test = $(call header,TEST $(1)) && $(call wrapper,$(BAZEL) test $(TEST_OPTIONS) $(1)) + +clean: ## Cleans the bazel cache. + @$(call clean) +.PHONY: clean + +testlogs: ## Returns the most recent set of test logs. + @if test -f .build_events.json; then \ + cat .build_events.json | jq -r \ + 'select(.testSummary?.overallStatus? | tostring | test("(FAILED|FLAKY|TIMEOUT)")) | "\(.id.testSummary.label) \(.testSummary.failed[].uri)"' | \ + sed -e 's|file://||'; \ + fi +.PHONY: testlogs |