当Github遇到Golang: 试谈GOPATH与目录结构
前言
Github是全球最大的开源代码托管平台,有很多的著名开源项目都将代码托管到Github上,例如 Linux, React 以及 Redis 等具有极高知名度的项目。
Go 是由Google主导开发的一门编程语言,其源代码同样托管在Github上,并且使用Go语言开发的 kubernetes, etcd 以及 istio 等项目也将代码托管在Github。所以作为Go开发者,不可避免的要和Github产生交互,本文谨试谈作者在Github上使用Go的一些体会。
本文只是作者个人的经验,欢迎各位大佬指正。
Github与GOPATH
Golang使用GOPATH作为工作区的指定目录,当我们的项目在Github上托管时,我们经常将项目代码clone到GOPATH的子目录下,例如对于 autoscaler 这个项目:
>> echo $GOPATH
/Users/xxx/workspace/golang/lib:/Users/xxx/workspace/golang
>> cd /Users/xxx/workspace/golang/src/k8s.io
>> git clone https://github.com/kubernetes/autoscaler.git
至于为什么是使用http://k8s.io而不是http://github.com,是因为代码中使用的import路径为 http://k8s.io。基本的原则是: 需要将代码 clone 到import指定的路径下。
当使用Fork时
如果我们要参与autoscaler这个项目的开发,采取的步骤如下:
- 在github上fork该项目
- 使用前述的方式将代码clone到本地
>> git clone https://github.com/${YOUR_REPO_NAME}/autoscaler.git
3. 为项目添加upstream
>> git remote -v
origin git@github:comLxxx/autoscaler.git (fetch)
origin [email protected]:xxx/autoscaler.git (push)
upstream [email protected]:kubernetes/autoscaler.git (fetch)
upstream [email protected]:kubernetes/autoscaler.git (push)
之后继续采用原有的开发模式即可。在这种方式下不用对import的路径进行任何更改。
目录结构
在使用Go语言进行开发过程中,并没有强制规定的代码结构。但是为了更清晰的表名各目录的作用,一种推荐的代码结构如下:
>> tree -L 1
.
├── Gopkg.lock
├── Gopkg.toml
├── LICENSE
├── Makefile
├── README.md
├── build
├── cmd
├── codecov.yml
├── docs
├── hack
├── pkg
└── vendor
各个目录及文件的简要介绍如下:
- Gopkg*: 使用 dep 进行依赖管理时的重要文件,需要提交到 github 进行管理
- Makefile: 构建脚本,下一节会详细描述
- build: 存放一些构建相关的配置脚本
- cmd: 命令文件存放目录, 一般为程序的入口, 下面可能有多个子目录
- docs: 文档
- hack: 一些脚本, 一般是升级工具/测试工具一类
- pkg: 项目使用的代码包目录
- vendor: 各依赖包,可托管到github
- bin: 生成的可执行文件的目录
Makefile
make 是一个控制从程序的源文件生成可执行文件和其他非源文件的工具,可以从一个名为Makefile的文件中获得如何构建程序的知识,并管理编译过程中的各种依赖关系和规则。
我们可以使用 make 对Go程序的构建,测试和发布等进行管理,一个示例脚本如下:
# Current version of the project
VERSION ?= 1.0.0
GIT_SHA=$(shell git rev-parse --short HEAD)
TAGS=$(GIT_SHA)
# This repos root import path (under GOPATH)
ROOT := github.com/lsytj0413/xxx
# Target binaries. You can build multiple binaries for a single project
TARGETS := xxx
# A list of all packages
PKGS := $(shell go list ./... | grep -v /vendor | grep -v /test)
# Project main package location (can be multiple ones)
CMD_DIR := ./cmd
# Project output directory
OUTPUT_DIR := ./bin
# Build directory
BUILD_DIR := ./build
# Git commit sha
COMMIT := $(shell git rev-parse --short HEAD)
# Golang standard bin directory
BIN_DIR := $(firstword $(subst :, ,$(GOPATH)))/bin
GOMETALINTER := $(BIN_DIR)/gometalinter
GODEP := $(BIN_DIR)/dep
#
# all targets
#
.PHONY: clean lint test build dep
all: test build
# TODO: if vendor exists skip ensure?
dep: $(GODEP)
@if [ ! -d ./vendor ]; then
dep ensure;
else
echo "vendor exists, skip dep ensure";
fi
$(GODEP):
go get -u -v github.com/golang/dep/cmd/dep
test: dep
# go test $(PKGS)
@for pkg in $(PKGS); do
go test $${pkg};
done
build: build-local
build-local: dep
@for target in $(TARGETS); do
go build -i -v -o $(OUTPUT_DIR)/$${target}
-ldflags "-s -w -X $(ROOT)/pkg/version.Version=$(VERSION)
-X $(ROOT)/pkg/version.Commit=$(COMMIT)"
$(CMD_DIR)/$${target};
done
build-docker:
@for target in $(TARGETS); do
docker build --force-rm -t $${target}:$(VERSION)
-f $(BUILD_DIR)/$${target}/Dockerfile .;
done
# docker rmi -f $(shell docker images -q --filter label=stage=intermediate)
docker image prune -f
lint: $(GOMETALINTER)
gometalinter ./... --vendor
$(GOMETALINTER):
go get -u github.com/alecthomas/gometalinter
gometalinter --install &> /dev/null
clean:
-rm -vrf ${OUTPUT_DIR}
常见的构建目标如下:
- build: 构建可执行程序
- test: 执行测试
- build-docker: 构建docker镜像, 其中的dockerfile存放在 build 目录下
- push: 将镜像推送到registry
- clean: 清理构建过程中的临时文件
推荐阅读: