转自:http://www.tuicool.com/articles/MNNjmiE
这里来分析下docker的Makefile作为之后对于docker实现的一个切入口。
首先是all:
all: build
$(DOCKER_RUN_DOCKER) hack/make.sh
build做的操作为:
build: bundles
docker build -t "$(DOCKER_IMAGE)" .
bundles做的操作为:
bundles:
mkdir bundles
这里有些奇怪,既然我的目的是编译docker,那么这里的docker命令又是哪里来的呢?docker在这里确实很有新意,其编译后并不是安装到本地,而是安装在一个docker container中,因此编译的时候我们得保证有docker安装了才行。
所以这里首先会建立一个bundles目录,然后会执行docker build -t “$(DOCKER_IMAGE)”,也就是生成一个image。默认这个image的名字是docker-dev,也就是这里会从仓库里下载个image。之后DOCKER_RUN_DOCKER,也就是:
DOCKER_RUN_DOCKER := docker run --rm -it --privileged $(DOCKER_ENVS) $(DOCKER_MOUNT) "$(DOCKER_IMAGE)"
会运行这个image,并在里边执行hack/make.sh。因此我们来看下make.sh。
首先是检查下是不是跑在container中:
# We're a nice, sexy, little shell script, and people might try to run us;
# but really, they shouldn't. We want to be in a container!
if [ "$PWD" != "/go/src/$DOCKER_PKG" ] || [ -z "$DOCKER_CROSSPLATFORMS" ]; then
{
echo "# WARNING! I don't seem to be running in the Docker container."
echo "# The result of this command might be an incorrect build, and will not be"
echo "# officially supported."
echo "#"
echo "# Try this instead: make all"
echo "#"
} >&2
fi
echo
然后是列出了默认要建立的bundle:
# List of bundles to create when no argument is passed
DEFAULT_BUNDLES=(
validate-dco
validate-gofmt
validate-test
validate-toml
validate-vet
binary
test-unit
test-integration-cli
test-docker-py
dynbinary
cover
cross
tgz
ubuntu
)
接着检查是不是git上拷贝下来的源码:
VERSION=$(< ./VERSION)
if command -v git &> /dev/null && git rev-parse &> /dev/null; then
GITCOMMIT=$(git rev-parse --short HEAD)
if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
GITCOMMIT="$GITCOMMIT-dirty"
fi
elif [ "$DOCKER_GITCOMMIT" ]; then
GITCOMMIT="$DOCKER_GITCOMMIT"
else
echo >&2 'error: .git directory missing and DOCKER_GITCOMMIT not specified'
echo >&2 ' Please either build with the .git directory accessible, or specify the'
echo >&2 ' exact (--short) commit hash you are building using DOCKER_GITCOMMIT for'
echo >&2 ' future accountability in diagnosing build issues. Thanks!'
exit 1
fi
建立GOPATH目录,我们的可执行文件会被安装到这个下面:
if [ "$AUTO_GOPATH" ]; then
rm -rf .gopath
mkdir -p .gopath/src/"$(dirname "${DOCKER_PKG}")"
ln -sf ../../../.. .gopath/src/"${DOCKER_PKG}"
export GOPATH="${PWD}/.gopath:${PWD}/vendor"
fi
if [ ! "$GOPATH" ]; then
echo >&2 'error: missing GOPATH; please see https://golang.org/doc/code.html#GOPATH'
echo >&2 ' alternatively, set AUTO_GOPATH=1'
exit 1
fi
检查DOCKER_EXPERIMENTAL是否被设置,如果设置的话打个warning:
if [ "$DOCKER_EXPERIMENTAL" ]; then
echo >&2 '# WARNING! DOCKER_EXPERIMENTAL is set: building experimental features'
echo >&2
DOCKER_BUILDTAGS+=" experimental"
fi
是否要编译daemon:
if [ -z "$DOCKER_CLIENTONLY" ]; then
DOCKER_BUILDTAGS+=" daemon"
fi
是否是lxc:
if [ "$DOCKER_EXECDRIVER" = 'lxc' ]; then
DOCKER_BUILDTAGS+=' test_no_exec'
fi
是否能编译btrfs:
# test whether "btrfs/version.h" exists and apply btrfs_noversion appropriately
if \
command -v gcc &> /dev/null \
&& ! gcc -E - &> /dev/null <<<'#include <btrfs/version.h>' \
; then
DOCKER_BUILDTAGS+=' btrfs_noversion'
fi
是否支持延迟remove:
# test whether "libdevmapper.h" is new enough to support deferred remove
# functionality.
if \
command -v gcc &> /dev/null \
&& ! ( echo -e '#include <libdevmapper.h>\nint main() { dm_task_deferred_remove(NULL); }'| gcc -ldevmapper -xc - &> /dev/null ) \
; then
DOCKER_BUILDTAGS+=' libdm_no_deferred_remove'
fi
设置一些编译选项:
# Use these flags when compiling the tests and final binary
IAMSTATIC='true'
source "$SCRIPTDIR/make/.go-autogen"
if [ -z "$DOCKER_DEBUG" ]; then
LDFLAGS='-w'
fi
LDFLAGS_STATIC='-linkmode external'
# Cgo -H windows is incompatible with -linkmode external.
if [ "$(go env GOOS)" == 'windows' ]; then
LDFLAGS_STATIC=''
fi
EXTLDFLAGS_STATIC='-static'
# ORIG_BUILDFLAGS is necessary for the cross target which cannot always build
# with options like -race.
ORIG_BUILDFLAGS=( -a -tags "netgo static_build $DOCKER_BUILDTAGS" -installsuffix netgo )
# see https://github.com/golang/go/issues/9369#issuecomment-69864440 for why -installsuffix is necessary here
BUILDFLAGS=( $BUILDFLAGS "${ORIG_BUILDFLAGS[@]}" )
# Test timeout.
: ${TIMEOUT:=30m}
TESTFLAGS+=" -test.timeout=${TIMEOUT}"
# A few more flags that are specific just to building a completely-static binary (see hack/make/binary)
# PLEASE do not use these anywhere else.
EXTLDFLAGS_STATIC_DOCKER="$EXTLDFLAGS_STATIC -lpthread -Wl,--unresolved-symbols=ignore-in-object-files"
LDFLAGS_STATIC_DOCKER="
$LDFLAGS_STATIC
-extldflags \"$EXTLDFLAGS_STATIC_DOCKER\"
"
FreeBSD做个特殊处理:
if [ "$(uname -s)" = 'FreeBSD' ]; then
# Tell cgo the compiler is Clang, not GCC
# https://code.google.com/p/go/source/browse/src/cmd/cgo/gcc.go?spec=svne77e74371f2340ee08622ce602e9f7b15f29d8d3&r=e6794866ebeba2bf8818b9261b54e2eef1c9e588#752
export CC=clang
# "-extld clang" is a workaround for
# https://code.google.com/p/go/issues/detail?id=6845
LDFLAGS="$LDFLAGS -extld clang"
fi
处理sqlite3.h的路径问题:
# If sqlite3.h doesn't exist under /usr/include,
# check /usr/local/include also just in case
# (e.g. FreeBSD Ports installs it under the directory)
if [ ! -e /usr/include/sqlite3.h ] && [ -e /usr/local/include/sqlite3.h ]; then
export CGO_CFLAGS='-I/usr/local/include'
export CGO_LDFLAGS='-L/usr/local/lib'
fi
根据GO的支持程度设置test相关变量:
HAVE_GO_TEST_COVER=
if \
go help testflag | grep -- -cover > /dev/null \
&& go tool -n cover > /dev/null 2>&1 \
; then
HAVE_GO_TEST_COVER=1
fi
接着是一些函数定义,大部分都是根据名字就能知道功能的。我们从main开始看:
main() {
# We want this to fail if the bundles already exist and cannot be removed.
# This is to avoid mixing bundles from different versions of the code.
mkdir -p bundles
if [ -e "bundles/$VERSION" ]; then
echo "bundles/$VERSION already exists. Removing."
rm -fr "bundles/$VERSION" && mkdir "bundles/$VERSION" || exit 1
echo
fi
if [ "$(go env GOHOSTOS)" != 'windows' ]; then
# Windows and symlinks don't get along well
rm -f bundles/latest
ln -s "$VERSION" bundles/latest
fi
if [ $# -lt 1 ]; then
bundles=(${DEFAULT_BUNDLES[@]})
else
bundles=($@)
fi
for bundle in ${bundles[@]}; do
export DEST="bundles/$VERSION/$(basename "$bundle")"
# Cygdrive paths don't play well with go build -o.
if [[ "$(uname -s)" == CYGWIN* ]]; then
export DEST="$(cygpath -mw "$DEST")"
fi
mkdir -p "$DEST"
ABS_DEST="$(cd "$DEST" && pwd -P)"
bundle "$bundle"
echo
done
}
这里通过for循环一次调用bundle函数,bundle的实现为:
bundle() {
local bundle="$1"; shift
echo "---> Making bundle: $(basename "$bundle") (in $DEST)"
source "$SCRIPTDIR/make/$bundle" "$@"
}
bundle数组我们在一开始的时候就列出来了,这里看下binary,对应的文件为hack/make/binary,内容很短:
#!/bin/bash
set -e
BINARY_NAME="docker-$VERSION"
BINARY_EXTENSION="$(binary_extension)"
BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION"
source "${MAKEDIR}/.go-autogen"
echo "Building: $DEST/$BINARY_FULLNAME"
go build \
-o "$DEST/$BINARY_FULLNAME" \
"${BUILDFLAGS[@]}" \
-ldflags "
$LDFLAGS
$LDFLAGS_STATIC_DOCKER
" \
./docker
echo "Created binary: $DEST/$BINARY_FULLNAME"
ln -sf "$BINARY_FULLNAME" "$DEST/docker$BINARY_EXTENSION。"
hash_files "$DEST/$BINARY_FULLNAME"
可以看到其调用go build直接编译生成docker-$VERSION文件,并做了个软链到$DEST/docker$BINARY_EXTENSION。同时这个命令也告诉了我们编译文件的入口,也就是main文件的位置,就是docker/docker.go。下面要看代码的时候,就从这个docker.go的main开始看咯。