diff --git a/.circleci/config.yml b/.circleci/config.yml index 801ee5bf5082..ca64e0d10fd6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,6 +42,7 @@ jobs: docker: [{image: 'docker:19.03-git'}] environment: DOCKER_BUILDKIT: 1 + BUILDX_VERSION: "v0.5.1" parallelism: 3 steps: - checkout @@ -55,21 +56,14 @@ jobs: - run: name: "Docker info" command: docker info - - run: - name: "Cross - build image" - command: | - docker build --progress=plain -f dockerfiles/Dockerfile.cross --tag cli-builder:$CIRCLE_BUILD_NUM . - - run: - name: "Cross" - command: | - name=cross-$CIRCLE_BUILD_NUM-$CIRCLE_NODE_INDEX - docker run \ - -e CROSS_GROUP=$CIRCLE_NODE_INDEX \ - --name $name cli-builder:$CIRCLE_BUILD_NUM \ - make cross - docker cp \ - $name:/go/src/github.com/docker/cli/build \ - /work/build + - run: apk add make curl + - run: mkdir -vp ~/.docker/cli-plugins/ + - run: curl -fsSL --output ~/.docker/cli-plugins/docker-buildx https://github.com/docker/buildx/releases/download/${BUILDX_VERSION}/buildx-${BUILDX_VERSION}.linux-amd64 + - run: chmod a+x ~/.docker/cli-plugins/docker-buildx + - run: docker buildx version + - run: docker context create buildctx + - run: docker buildx create --use buildctx && docker buildx inspect --bootstrap + - run: GROUP_INDEX=$CIRCLE_NODE_INDEX GROUP_TOTAL=$CIRCLE_NODE_TOTAL docker buildx bake cross --progress=plain - store_artifacts: path: /work/build diff --git a/.dockerignore b/.dockerignore index 80d9bd96ddda..1718dd67c28f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,5 @@ .circleci .dockerignore -.git .github .gitignore appveyor.yml diff --git a/.gitignore b/.gitignore index a1f1bc9dafe5..c479555c8809 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,7 @@ Thumbs.db .editorconfig /build/ -cli/winresources/rsrc_386.syso -cli/winresources/rsrc_amd64.syso +cli/winresources/rsrc_*.syso /man/man1/ /man/man5/ /man/man8/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000000..719b0873aa97 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,61 @@ +#syntax=docker/dockerfile:1.2 + +ARG BASE_VARIANT=alpine +ARG GO_VERSION=1.13.15 + +FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-${BASE_VARIANT} AS gostable +FROM --platform=$BUILDPLATFORM golang:1.16-${BASE_VARIANT} AS golatest + +FROM gostable AS go-linux +FROM golatest AS go-darwin +FROM golatest AS go-windows-amd64 +FROM golatest AS go-windows-386 +FROM golatest AS go-windows-arm +FROM --platform=$BUILDPLATFORM tonistiigi/golang:497feff1-${BASE_VARIANT} AS go-windows-arm64 +FROM go-windows-${TARGETARCH} AS go-windows + +FROM --platform=$BUILDPLATFORM tonistiigi/xx@sha256:620d36a9d7f1e3b102a5c7e8eff12081ac363828b3a44390f24fa8da2d49383d AS xx + +FROM go-${TARGETOS} AS build-base-alpine +COPY --from=xx / / +RUN apk add --no-cache clang lld llvm file git +WORKDIR /go/src/github.com/docker/cli + +FROM build-base-alpine AS build-alpine +ARG TARGETPLATFORM +# gcc is installed for libgcc only +RUN xx-apk add --no-cache musl-dev gcc + +FROM go-${TARGETOS} AS build-base-buster +COPY --from=xx / / +RUN apt-get update && apt-get install --no-install-recommends -y clang lld file +WORKDIR /go/src/github.com/docker/cli + +FROM build-base-buster AS build-buster +ARG TARGETPLATFORM +RUN xx-apt install --no-install-recommends -y libc6-dev libgcc-8-dev + +FROM build-${BASE_VARIANT} AS build +# GO_LINKMODE defines if static or dynamic binary should be produced +ARG GO_LINKMODE=static +# GO_BUILDTAGS defines additional build tags +ARG GO_BUILDTAGS +# GO_STRIP strips debugging symbols if set +ARG GO_STRIP +# CGO_ENABLED manually sets if cgo is used +ARG CGO_ENABLED +# VERSION sets the version for the produced binary +ARG VERSION +RUN --mount=ro --mount=type=cache,target=/root/.cache \ + --mount=from=dockercore/golang-cross:xx-sdk-extras,target=/xx-sdk,src=/xx-sdk \ + --mount=type=tmpfs,target=cli/winresources \ + xx-go --wrap && \ + # export GOCACHE=$(go env GOCACHE)/$(xx-info)$([ -f /etc/alpine-release ] && echo "alpine") && \ + TARGET=/out ./scripts/build/binary && \ + xx-verify $([ "$GO_LINKMODE" = "static" ] && echo "--static") /out/docker + +FROM build-base-${BASE_VARIANT} AS dev +COPY . . + +FROM scratch AS binary +COPY --from=build /out . diff --git a/Makefile b/Makefile index 96c6379c9383..47f43eff8089 100644 --- a/Makefile +++ b/Makefile @@ -30,8 +30,7 @@ lint: ## run all the lint tools gometalinter --config gometalinter.json ./... .PHONY: binary -binary: ## build executable for Linux - @echo "WARNING: binary creates a Linux executable. Use cross for macOS or Windows." +binary: ./scripts/build/binary .PHONY: plugins @@ -39,28 +38,20 @@ plugins: ## build example CLI plugins ./scripts/build/plugins .PHONY: cross -cross: ## build executable for macOS and Windows - ./scripts/build/cross - -.PHONY: binary-windows -binary-windows: ## build executable for Windows - ./scripts/build/windows +cross: + ./scripts/build/binary .PHONY: plugins-windows plugins-windows: ## build example CLI plugins for Windows ./scripts/build/plugins-windows -.PHONY: binary-osx -binary-osx: ## build executable for macOS - ./scripts/build/osx - .PHONY: plugins-osx plugins-osx: ## build example CLI plugins for macOS ./scripts/build/plugins-osx .PHONY: dynbinary dynbinary: ## build dynamically linked binary - ./scripts/build/dynbinary + GO_LINKMODE=dynamic ./scripts/build/binary vendor: vendor.conf ## check that vendor matches vendor.conf rm -rf vendor diff --git a/README.md b/README.md index e21bccec0c04..770d68c1db8f 100644 --- a/README.md +++ b/README.md @@ -12,18 +12,31 @@ Development `docker/cli` is developed using Docker. -Build a linux binary: +Build CLI from source: ``` -$ make -f docker.Makefile binary +$ docker buildx bake ``` Build binaries for all supported platforms: ``` -$ make -f docker.Makefile cross +$ docker buildx bake cross ``` +Build for a specific platform: + +``` +$ docker buildx bake --set binary.platform=linux/arm64 +``` + +Build dynamic binary for glibc or musl: + +``` +$ USE_GLIBC=1 docker buildx bake dynbinary +``` + + Run all linting: ``` @@ -44,12 +57,6 @@ Start an interactive development environment: $ make -f docker.Makefile shell ``` -In the development environment you can run many tasks, including build binaries: - -``` -$ make binary -``` - Legal ===== *Brought to you courtesy of our legal counsel. For more context, diff --git a/cli/winresources/res_windows.go b/cli/winresources/res_windows.go index 3f755cc4bdbf..033995d0bcec 100644 --- a/cli/winresources/res_windows.go +++ b/cli/winresources/res_windows.go @@ -5,7 +5,7 @@ These resources are used to provide * An icon * A Windows manifest declaring Windows version support -The resource object files are generated with go generate. +The resource object files are generated when building with scripts/build/binary . The resource source files are located in scripts/winresources. This occurs automatically when you run scripts/build/windows. @@ -14,5 +14,3 @@ is included. */ package winresources - -//go:generate ../../scripts/gen/windows-resources diff --git a/docker-bake.hcl b/docker-bake.hcl new file mode 100644 index 000000000000..909dd4fc189e --- /dev/null +++ b/docker-bake.hcl @@ -0,0 +1,63 @@ +variable "VERSION" { + default = "" +} + +variable "USE_GLIBC" { + default = "" +} + +variable "STRIP_TARGET" { + default = "" +} + +group "default" { + targets = ["binary"] +} + +target "binary" { + target = "binary" + platforms = ["local"] + output = ["build"] + args = { + BASE_VARIANT = USE_GLIBC != "" ? "buster" : "alpine" + VERSION = VERSION + GO_STRIP = STRIP_TARGET + } +} + +target "dynbinary" { + inherits = ["binary"] + args = { + GO_LINKMODE = "dynamic" + } +} + +variable "GROUP_TOTAL" { + default = "1" +} + +variable "GROUP_INDEX" { + default = "0" +} + +function "platforms" { + params = [] + result = ["linux/amd64", "linux/386", "linux/arm64", "linux/arm", "linux/ppc64le", "linux/s390x", "darwin/amd64", "darwin/arm64", "windows/amd64"] +} + +function "glen" { + params = [platforms, GROUP_TOTAL] + result = ceil(length(platforms)/GROUP_TOTAL) +} + +target "_all_platforms" { + platforms = slice(platforms(), GROUP_INDEX*glen(platforms(), GROUP_TOTAL),min(length(platforms()), (GROUP_INDEX+1)*glen(platforms(), GROUP_TOTAL))) +} + +target "cross" { + inherits = ["binary", "_all_platforms"] +} + +target "dynbinary-cross" { + inherits = ["dynbinary", "_all_platforms"] +} diff --git a/docker.Makefile b/docker.Makefile index 804133b2fb5f..4f582b7fb7c3 100644 --- a/docker.Makefile +++ b/docker.Makefile @@ -38,11 +38,6 @@ build_linter_image: # build dockerfile from stdin so that we don't send the build-context; source is bind-mounted in the development environment cat ./dockerfiles/Dockerfile.lint | docker build ${DOCKER_BUILD_ARGS} --build-arg=GO_VERSION -t $(LINTER_IMAGE_NAME) - -.PHONY: build_cross_image -build_cross_image: - # build dockerfile from stdin so that we don't send the build-context; source is bind-mounted in the development environment - cat ./dockerfiles/Dockerfile.cross | docker build ${DOCKER_BUILD_ARGS} --build-arg=GO_VERSION -t $(CROSS_IMAGE_NAME) - - .PHONY: build_shell_validate_image build_shell_validate_image: # build dockerfile from stdin so that we don't send the build-context; source is bind-mounted in the development environment @@ -80,22 +75,10 @@ test-unit: build_docker_image ## run unit tests (using go test) .PHONY: test ## run unit and e2e tests test: test-unit test-e2e -.PHONY: cross -cross: build_cross_image ## build the CLI for macOS and Windows - $(DOCKER_RUN) $(CROSS_IMAGE_NAME) make cross - -.PHONY: binary-windows -binary-windows: build_cross_image ## build the CLI for Windows - $(DOCKER_RUN) $(CROSS_IMAGE_NAME) make $@ - .PHONY: plugins-windows plugins-windows: build_cross_image ## build the example CLI plugins for Windows $(DOCKER_RUN) $(CROSS_IMAGE_NAME) make $@ -.PHONY: binary-osx -binary-osx: build_cross_image ## build the CLI for macOS - $(DOCKER_RUN) $(CROSS_IMAGE_NAME) make $@ - .PHONY: plugins-osx plugins-osx: build_cross_image ## build the example CLI plugins for macOS $(DOCKER_RUN) $(CROSS_IMAGE_NAME) make $@ @@ -120,9 +103,6 @@ fmt: ## run gofmt vendor: build_docker_image vendor.conf ## download dependencies (vendor/) listed in vendor.conf $(DOCKER_RUN) -it $(DEV_DOCKER_IMAGE_NAME) make vendor -dynbinary: build_cross_image ## build the CLI dynamically linked - $(DOCKER_RUN) -it $(CROSS_IMAGE_NAME) make dynbinary - .PHONY: authors authors: ## generate AUTHORS file from git history $(DOCKER_RUN) -it $(DEV_DOCKER_IMAGE_NAME) make authors diff --git a/scripts/build/.variables b/scripts/build/.variables index 094209cd5039..ece560174a7b 100755 --- a/scripts/build/.variables +++ b/scripts/build/.variables @@ -1,8 +1,10 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh set -eu +TARGET=${TARGET:-"build"} + PLATFORM=${PLATFORM:-} -VERSION=${VERSION:-"unknown-version"} +VERSION=${VERSION:-$(git describe --match 'v[0-9]*' --dirty='.m' --always --tags | sed 's/^v//' 2>/dev/null || echo "unknown-version" )} GITCOMMIT=${GITCOMMIT:-$(git rev-parse --short HEAD 2> /dev/null || true)} BUILDTIME=${BUILDTIME:-$(date -u +"%Y-%m-%dT%H:%M:%SZ")} @@ -20,15 +22,15 @@ export LDFLAGS="\ ${LDFLAGS:-} \ " -GOOS="${GOOS:-$(go env GOHOSTOS)}" -GOARCH="${GOARCH:-$(go env GOHOSTARCH)}" +GOOS="$(go env GOOS)" +GOARCH="$(go env GOARCH)" if [ "${GOARCH}" = "arm" ]; then - GOARM="${GOARM:-$(go env GOHOSTARM)}" + GOARM="$(go env GOARM)" fi -TARGET="build/docker-$GOOS-$GOARCH" +TARGET="$TARGET/docker-${GOOS}-${GOARCH}" if [ "${GOARCH}" = "arm" ] && [ -n "${GOARM}" ]; then - TARGET="${TARGET}-v${GOARM}" + TARGET="${TARGET}-v${GOARM}" fi if [ "${GOOS}" = "windows" ]; then diff --git a/scripts/build/binary b/scripts/build/binary index 41c4196cc801..e4c5e12a6b44 100755 --- a/scripts/build/binary +++ b/scripts/build/binary @@ -1,14 +1,80 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh # # Build a static binary for the host OS/ARCH # -set -eu -o pipefail +set -eu -source ./scripts/build/.variables +: "${CGO_ENABLED=}" +: "${GO_LINKMODE=static}" +: "${GO_BUILDMODE=}" +: "${GO_BUILDTAGS=}" +: "${GO_STRIP=}" -echo "Building statically linked $TARGET" -export CGO_ENABLED=0 -go build -o "${TARGET}" --ldflags "${LDFLAGS}" "${SOURCE}" +. ./scripts/build/.variables -ln -sf "$(basename "${TARGET}")" build/docker +if [ -z "$CGO_ENABLED" ]; then + case "$(go env GOOS)" in + linux) + case "$(go env GOARCH)" in + amd64|arm64|arm|s390x) + CGO_ENABLED=1 + ;; + *) + CGO_ENABLED=0 + ;; + esac + ;; + darwin|windows) + CGO_ENABLED=1 + ;; + *) + CGO_ENABLED=0 + ;; + esac +fi +export CGO_ENABLED +if [ "$CGO_ENABLED" = "1" ] && [ "$(go env GOOS)" != "windows" ]; then + case "$(go env GOARCH)" in + mips*|ppc64) + # pie build mode is not supported on mips architectures + ;; + *) + GO_BUILDMODE="-buildmode=pie" + ;; + esac + GO_BUILDTAGS="$GO_BUILDTAGS pkcs11" +fi + +if [ "$CGO_ENABLED" = "1" ] && [ "$GO_LINKMODE" = "static" ] && [ "$(go env GOOS)" = "linux" ]; then + LDFLAGS="$LDFLAGS -extldflags -static" +fi + +if [ -n "$GO_STRIP" ]; then + LDFLAGS="$LDFLAGS -s -w" +fi + +if [ "$(go env GOOS)" = "windows" ]; then + # Generate a Windows file version of the form major,minor,patch,build + VERSION_QUAD=$(printf "%s" "$VERSION" | sed -re 's/^([0-9.]*).*$/\1/' | tr . , | sed -re 's/^[0-9]+$/\0,0/' | sed -re 's/^[0-9]+,[0-9]+$/\0,0/' | sed -re 's/^[0-9]+,[0-9]+,[0-9]+$/\0,0/') + + set -- + [ -n "$VERSION" ] && set -- "$@" -D "DOCKER_VERSION=\"$VERSION\"" + [ -n "$VERSION_QUAD" ] && set -- "$@" -D "DOCKER_VERSION_QUAD=$VERSION_QUAD" + [ -n "$GITCOMMIT" ] && set -- "$@" -D "DOCKER_COMMIT=\"$GITCOMMIT\"" + + windres=$($(go env CC) --print-prog-name=windres) + + target="$(dirname "$0")/../../cli/winresources/rsrc_$(go env GOARCH).syso" + mkdir -p "$(dirname "${target}")" + "$windres" -i "$(dirname "$0")/../winresources/docker.rc" -o "$target" "$@" + echo "package winresources" > "$(dirname "${target}")/stub_windows.go" +fi + +echo "Building $GO_LINKMODE $(basename "${TARGET}")" + +export GO111MODULE=auto + +go build -o "${TARGET}" -tags "${GO_BUILDTAGS}" --ldflags "${LDFLAGS}" ${GO_BUILDMODE} "${SOURCE}" + +ln -sf "$(basename "${TARGET}")" "$(dirname "${TARGET}")/docker" diff --git a/scripts/build/cross b/scripts/build/cross deleted file mode 100755 index 51c22c3607a6..000000000000 --- a/scripts/build/cross +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash -# -# Build a binary for all supported platforms -# - -set -eu -o pipefail - -BUILDDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -export SHELL=bash - -jobs=( - "$BUILDDIR/windows" \ - "$BUILDDIR/osx" \ - "GOOS=linux GOARCH=amd64 $BUILDDIR/binary" \ - "GOOS=linux GOARCH=arm $BUILDDIR/binary" \ - "GOOS=linux GOARCH=ppc64le $BUILDDIR/binary" \ - "GOOS=linux GOARCH=s390x $BUILDDIR/binary" \ -) - -# Outside of circleCI run all at once. On circleCI run two at a time because -# each container has access to two cores. -group=${CROSS_GROUP-"all"} - -if [ "$group" = "all" ]; then - - echo "Building binaries for all platforms" - parallel ::: "${jobs[@]}" - exit 0 - -fi - -declare -i start="$group*2" -parallel ::: "${jobs[@]:$start:2}" diff --git a/scripts/build/dynbinary b/scripts/build/dynbinary deleted file mode 100755 index 67bc785dae55..000000000000 --- a/scripts/build/dynbinary +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash -# -# Build a dynamically linked binary for the host OS/ARCH -# - -set -eu -o pipefail - -source ./scripts/build/.variables - -echo "Building dynamically linked $TARGET" -export CGO_ENABLED=1 -case "$(go env GOARCH)" in - mips*|ppc64) - # pie build mode is not supported on mips architectures - GO_BUILDMODE="" - ;; - *) - GO_BUILDMODE="-buildmode=pie" - ;; -esac - -go build -o "${TARGET}" -tags pkcs11 --ldflags "${LDFLAGS}" ${GO_BUILDMODE} "${SOURCE}" - -ln -sf "$(basename "${TARGET}")" build/docker diff --git a/scripts/build/osx b/scripts/build/osx deleted file mode 100755 index a075ede41271..000000000000 --- a/scripts/build/osx +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -# -# Build an osx binary from linux -# - -set -eu -o pipefail - -source ./scripts/build/.variables - -export CGO_ENABLED=1 -export GOOS=darwin -export GOARCH=amd64 -export CC=o64-clang -export CXX=o64-clang++ -export LDFLAGS="$LDFLAGS -linkmode external -s" -export LDFLAGS_STATIC_DOCKER='-extld='${CC} - -# Override TARGET -TARGET="build/docker-$GOOS-$GOARCH" - -echo "Building $TARGET" -go build -o "${TARGET}" -tags pkcs11 --ldflags "${LDFLAGS}" "${SOURCE}" diff --git a/scripts/build/windows b/scripts/build/windows deleted file mode 100755 index cd6d2eea9064..000000000000 --- a/scripts/build/windows +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -# -# Build a windows binary from linux -# - -set -eu -o pipefail - -source ./scripts/build/.variables - -export CC=x86_64-w64-mingw32-gcc -export CGO_ENABLED=1 -export GOOS=windows -export GOARCH=amd64 - -# Override TARGET -TARGET="build/docker-$GOOS-$GOARCH.exe" - -echo "Generating windows resources" -go generate ./cli/winresources - -echo "Building $TARGET" -# TODO: -tags pkcs11 -go build -o "${TARGET}" --ldflags "${LDFLAGS}" "${SOURCE}" diff --git a/scripts/gen/windows-resources b/scripts/gen/windows-resources deleted file mode 100755 index e9cdff8bbab7..000000000000 --- a/scripts/gen/windows-resources +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -# -# Compile the Windows resources into the sources -# - -set -eu -o pipefail - -SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -# shellcheck source=/go/src/github.com/docker/cli/scripts/build/.variables -source "$SCRIPTDIR"/../build/.variables - -RESOURCES=$SCRIPTDIR/../winresources - -TEMPDIR=$(mktemp -d) -trap 'rm -rf $TEMPDIR' EXIT - -if [ "$(go env GOHOSTOS)" = "windows" ]; then - WINDRES=windres -else - # Cross compiling - WINDRES=x86_64-w64-mingw32-windres -fi - -# Generate a Windows file version of the form major,minor,patch,build (with any part optional) -VERSION_QUAD=$(printf "%s" "$VERSION" | sed -re 's/^([0-9.]*).*$/\1/' | tr . ,) - -# Pass version and commit information into the resource compiler -defs= -[ -n "$VERSION" ] && defs+=( "-D DOCKER_VERSION=\"$VERSION\"") -[ -n "$VERSION_QUAD" ] && defs+=( "-D DOCKER_VERSION_QUAD=$VERSION_QUAD") -[ -n "$GITCOMMIT" ] && defs+=( "-D DOCKER_COMMIT=\"$GITCOMMIT\"") - -makeres() { - # shellcheck disable=SC2086 - "$WINDRES" \ - -i "$RESOURCES/$1" \ - -o "$3" \ - -F "$2" \ - --use-temp-file \ - -I "$TEMPDIR" \ - ${defs[*]} -} - -makeres docker.rc pe-x86-64 rsrc_amd64.syso -makeres docker.rc pe-i386 rsrc_386.syso