If someone asked me the question "what layout should I use for my Go code repository?", I'd start by asking back "what are you building: an executable, or a library?"
Create a directory named however you want your final executable to be called (e.g. "mycommand"), change into that directory, and create the following files:
go.mod # module github.com/me/mycommand (create using "go mod init github.com/me/mycommand")
main.go # package main
The source file does not need to be called "main.go": call it whatever you like.
Usage:
- If your code imports any third-party libraries, type
go mod tidy
to fetch them - Run locally with
go run .
- Format source with
go fmt .
- Verify source with
go vet .
- Build locally with
go build .
(creates executable "mycommand" in top-level directory) - To add tests, create
main_test.go
and run withgo test .
- Normally this would be
package main
too - You can use
package main_test
instead, but then it has to explicitly import the main package, and can only access public exported names (starting with a capital letter)
- Normally this would be
- Turn this into a git repository and publish
git init git add . git commit -m "first commit" git branch -M main git remote add origin [email protected]:me/mycommand.git git push -u origin main
- End-user can install it with
go install github.com/me/mycommand@latest
and will get a binary called "mycommand"
Simply add additional files into top level directory, all with "package main"
go.mod # module github.com/me/mycommand
main.go # package main
other.go # package main
stuff.go # package main
some_test.go # package main
Usage as before.
This is when you want to break your application into semi-independent pieces, perhaps with a view to making them their own public library at some point in the future.
go.mod # module github.com/me/mycommand
main.go # package main; import "github.com/me/mycommand/internal/foo"
...
internal/foo/xxx.go # package foo
internal/foo/yyy.go # package foo
internal/foo/test_xxx.go # package foo // OR:
internal/foo/test_yyy.go # package foo_test; import "github.com/me/mycommand/internal/foo"
Recommendations:
- Use the "internal" subtree to prevent your subpackages being importable by anyone else
- Name the innermost subdirectory the same as the package to avoid confusion
- That is, preferably
"github.com/me/mycommand/internal/foo"
provides a package called "foo" - You can, however, change the package name on import:
import bar "github.com/me/mycommand/internal/foo"
- That is, preferably
Usage:
go test ./...
to run tests in all subdirectoriesgo fmt ./...
to reformat code in all subdirectoriesgo run .
andgo build .
still work as before
Create separate subdirectories for each executable, where the directory name matches the desired executable name. There is an optional convention that these live under a "cmd" directory.
go.mod # module github.com/me/mytools
cmd/foo/main.go # package main
cmd/foo/more.go # package main
cmd/bar/main.go # package main
cmd/bar/stuff.go # package main
Usage:
- Run locally with
go run ./cmd/foo
,go run ./cmd/bar
(or:cd cmd/foo; go run .
) - Build locally with
mkdir bin; go build -o bin/ ./...
(creates executables "foo" and "bar" in "bin" directory" go test ./...
to run all testsgo fmt ./...
to reformat all code- End-user installs with
go install github.com/me/mytools/cmd/...@latest
(or replace "..." with "foo" or "bar" to get a single executable)
go.mod # module github.com/me/mytools
cmd/foo/main.go # package main; import "github.com/me/mytools/internal/baz"
cmd/bar/main.go # package main; import "github.com/me/mytools/internal/baz"
...
internal/baz/xxx.go # package baz
internal/baz/yyy.go # package baz
Usage as above.
Create a directory named however you want your final library to be called (e.g. "mylib"), change into that directory, and create some files:
go.mod # module github.com/me/mylib (create using "go mod init github.com/me/mylib")
some.go # package mylib
files.go # package mylib
some_test.go # package mylib or package mylib_test
Usage:
- End users will
import "github.com/me/mylib"
go test .
to run your tests
Note: it's usual, but not always the case, that the last component of the module path (which in
this cases is the repo name) matches the name of the package. If not, the user may be surprised
that the imported package name doesn't match import path. However, they can always override it:
import foo "github.com/me/mylib"
These should go under internal
; they can be imported by the library itself, but no external direct imports.
go.mod # module github.com/me/mylib (create using "go mod init github.com/me/mylib")
foo/whatever.go # package foo
bar/whatever.go # package bar
Usage:
- End users will
import "github.com/me/mylib/foo"
,import "github.com/me/mylib/bar"
go test ./...
to run all your tests
Again, it's suggested that each package name match the innermost directory name (last part of path)
These libraries can import each other, using their full module import path just like an end-user.
You can have a top-level package as well, i.e. import "github.com/me/mylib"
.
You can have a single repo which contains one or more executables ("package main") and is a library providing one or more importable packages.