embed

GoDoc (https://pkg.go.dev/embed)

Package embed provides access to files embedded in the running Go program.

Go source files that import “embed” can use the //go:embed directive to initialize a variable of type string, []byte, or FS with the contents of files read from the package directory or subdirectories at compile time.

1
2
3
4
5
import _ "embed"

//go:embed hello.txt
var s string
print(s)
1
2
3
4
5
import _ "embed"

//go:embed hello.txt
var b []byte
print(string(b))
1
2
3
4
5
6
import "embed"

//go:embed hello.txt
var f embed.FS
data, _ := f.ReadFile("hello.txt")
print(string(data))
  • embeded files must under current folder. any file outside current folder will fail, for example /root/somefile will not work.
  • "go:embed image/*" embeds all files under image/, including all file start with .dot and _underscore.
  • * will not recursive sub folders. so .dotfile and _underscore will not be embeded with image/subfolder/ sub folder.
  • "go:embed image/" or "go:embed image" embeds all file but ignored which start with .dot and _underscore.
  • symbolic links are not followed.
1
2
3
4
5
6
7
8
9
10
11
// embed multi files into a single filesystem
//go:embed image/* template/*
//go:embed html/index.html
var content embed.FS

//go:embed image template html/index.html
var content embed.FS

// embed files with space
//go:embed "he llo.txt"
var content embed.FS

Example with http-fileserver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import (
"embed"
"log"
"net/http"
)

//go:embed internal/embedtest/testdata/*.txt
var content embed.FS

func main() {
mutex := http.NewServeMux()
mutex.Handle("/", http.FileServer(http.FS(content)))
err := http.ListenAndServe(":8080", mutex)
if err != nil {
log.Fatal(err)
}
}

embed filesystem need to access with full path from where embed declare, as follow:

1
2
//go:embed pg/migration/init.sql
var PgMigrationFs embed.FS

you should access init.sql with full-path as pg/migration/init.sql.

vendor

vendor可以为docker image编译提供更快的速度,在测试时可以使用vendor模式来编译docker image。

1
2
3
4
5
6
7
8
9
10
11
12
13
.PHONY: build-with-vendor
build-with-vendor:
@if test -d "./vendor/"; then echo "checking vendor folder... [OK]"; else echo "checking vendor folder... [FAILED]"; echo "run go mod vendor first"; exit 2; fi;
@echo "start building with vendor..."
go build -mod vendor -o bin/execute
@echo "building done"

.PHONY: build-image-with-vendor
build-image-with-vendor:
@if test -d "./vendor/"; then echo "checking vendor folder... [OK]"; else echo "checking vendor folder... [FAILED]"; echo "run go mod vendor first"; exit 2; fi;
@echo "start building docker image with vendor..."
@docker build -t local/project -f ./vendor.Dockerfile .
@echo "building done"

fmt & lint

goimports: https://pkg.go.dev/golang.org/x/tools/cmd/goimports
golangci-lint: https://github.com/golangci/golangci-lint

1
2
goimports -d $(grep --files-without-match 'DO NOT EDIT' $(find . -type f -name '*.go' -not -path "./vendor/*"))
golangci-lint run --go=1.18 -D errcheck -D gosimple -D govet -j 8 -v ./...

Makefile

1
2
3
COMMIT_SHA := $(shell git describe --all --first-parent --abbrev=8 --long --dirty --always)
TIMESTAMP := $(shell date '+%Y-%m-%d_%H:%M:%S')
APP_LDFLAGS := "-X 'github.com/myproject/common.CommitSha=$(COMMIT_SHA)' -X 'github.com/myproject/common.BuildTime=$(TIMESTAMP)'"

Mock

Mock should be generated with interface, always implement side-effect logic with interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
// mock while file
//go:generate mockgen -source=$GOFILE -destination=path/to/source/root/mocks/mock_$GOFILE -package=mocks
type ILogic interface {
Foo() string
Bar() int
}

// mock specify interface
//go:generate mockgen -destination=./mock_ILogic.go -package=packageName github.com/myuser/myproject/path/to/package/ ILogic
type ILogic interface {
Foo() string
Bar() int
}
1
2
go generate ./...
mockgen -source path/to/interface.go -destination path/to/mocks/mock_interface.go -package packageName

Unit Test