當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: 清理構建過程中的臨時文件
推薦閱讀: