Manually fetch dependencies from go.mod? - go

I'm using go 1.11 with module support. I understand that the go tool now installs dependencies automatically on build/install. I also understand the reasoning.
I'm using docker to build my binaries. In many other ecosystems its common to copy over your dependency manifest (package.json, requirements.txt, etc) and install dependencies as a separate stage from build. This takes advantage of docker's layer caching, and makes rebuilds much faster since generally code changes vastly outnumber dependency changes.
I was wondering if vgo has any way to do this?

It was an issue #26610, which is fixed now.
So now you can just use:
go mod download
For this to work you need just the go.mod / go.sum files.
For example, here's how to have a cached multistage Docker build: (source)
FROM golang:1.17-alpine as builder
RUN apk --no-cache add ca-certificates git
WORKDIR /build
# Fetch dependencies
COPY go.mod go.sum ./
RUN go mod download
# Build
COPY . ./
RUN CGO_ENABLED=0 go build
# Create final image
FROM alpine
WORKDIR /
COPY --from=builder /build/myapp .
EXPOSE 8080
CMD ["./myapp"]
Also see the article Containerize Your Go Developer Environment – Part 2, which describes how to leverage the Go compiler cache to speed up builds even further.

You may use the go mod vendor command which will create a vendor folder in the main module's root folder, and copy all dependencies into it. After this you may pass the -mod=vendor param to the go tool, and then dependencies from the vendor folder will be used to build / compile / test your app.
So what you may do to speed up your builds:
Run the go mod vendor command to have an actual version of your dependencies.
Save / cache this vendor folder.
During builds, restore this vendor folder, and build / install your app by passing the -mod=vendor argument to the go tool, so no dependencies will be downloaded, but the content of the vendor folder will be used.
Quoting from go help mod:
Modules and vendoring
When using modules, the go command completely ignores vendor directories.
By default, the go command satisfies dependencies by downloading modules
from their sources and using those downloaded copies (after verification,
as described in the previous section). To allow interoperation with older
versions of Go, or to ensure that all files used for a build are stored
together in a single file tree, 'go mod vendor' creates a directory named
vendor in the root directory of the main module and stores there all the
packages from dependency modules that are needed to support builds and
tests of packages in the main module.
To build using the main module's top-level vendor directory to satisfy
dependencies (disabling use of the usual network sources and local
caches), use 'go build -mod=vendor'. Note that only the main module's
top-level vendor directory is used; vendor directories in other locations
are still ignored.

I wanted to re-download all the dependencies using go mod, this is what I did:
Go to your GOROOT
sudo rm -rf pkg/mod/
Go to the directory where the go.mod file exists
go mod download

You can use a package manager, There are many of them like dep, glide, and govendor. dep is newer and is going to be integrated into go toolchain as official dependency management tool.
We also make docker images for go applications and we use dind to make those images and we prepared a CI/CD image with all dependencies preinstalled to make the builds faster. Though, it took a little bit of scripting to glue everything together.
Moreover, layering up the dependencies could result in big size of docker images. I suggest try multi-stage builds which could help making images super lite.

Related

Installing all dependencies from a go.mod file

What is the golang command that is equivalent to npm install
npm install downloads all the dependencies listed in the package.json file.
Having said that, what is the command that downloads all the dependencies in the go.mod file?
If you have only a go.mod and you have Go 1.16 or later:
If you just want to run your code, use go build or go run . - your dependencies will be downloaded and built automatically
If you want to save a copy of your dependencies locally, use go mod vendor
Both the above will create a go.sum file (this is maintained by the Go Tools - you can ignore it)
The vendor command will create a vendor folder with a copy of all the source code from your dependencies. Note: If you do use the vendor approach, you will need to run go mod vendor if there is a change in your dependencies, so that a copy is downloaded to the vendor folder. The advantage is your code will build without an internet connection. The disadvantage is you need to keep it up to date.
That should get you started for every day use.
If you'd like to know all about modules, this is a good source.

Using Go Modules with packages that require "make install"

I have an external package that apart from the usual go get, needs to run make install in its $GOPATH/src directory in order to use it (performs some makefile and git magic).
Trying to use this package with modules means a copy of it is downloaded to the vendor library using go mod vendor. However, this copy is not a git repository so running make install inside the package's vendor folder fails.
Does this mean that the package cannot be used in a module and I have to revert to using GOPATH?
Does this mean that the package cannot be used in a module
Yes.
Contact the author and make him to check in what the makefile does.

Where is the go project working directory when using modules?

I am trying to download, make some tweaks and build a golang project from GitHub. The project's instructions are:
go get github.com/<vendor>/<projectName>
cd src/github.com/<vendor>/<projectName>
go build .
That used to work in the past — before enabling Go Modules.
Now I have GO111MODULE=on (go version go1.15.4 linux/amd64). When running the first command, go downloads the project as a module and all its dependencies.
But then there is no src/github.com/<vendor>/<projectName> folder anymore. Moreover, the is no folder named <projectName> anywhere in the system.
Instead, there is folder pkg/mod/github.com/<vendor> which contains the project folder with weird symbols in its name (exclamation marks etc.) and version identifiers.
How do I get the project folder available for making tweaks and builds?
As pointed by #Volker, good old git clone should be used.
It turns out that it should be used instead of go get github.com/<vendor>/<projectName> (no idea why the project vendor recommends that):
git clone git://github.com/<vendor>/<projectName>
cd <projectName>
go get ./...
# do tweaks here
go build .
If your goal is tweaks, the easiest way it use to use go mod vendor.
https://golang.org/ref/mod#go-mod-vendor
The go mod vendor command constructs a directory named vendor in the main module's root directory that contains copies of all packages needed to support builds and tests of packages in the main module

Using 'go build' fetches dependencies even though they are in vendor/

I am trying to fetch a Go project and copy the dependencies under the vendor/ directory so I have the complete source code of the project and its dependencies in my project. However, even after doing that, deleting the packages under $GOPATH/pkg/mod and rebuilding cause the Go compiler to re-fetch all dependencies, which takes a considerable of time.
This is what I did:
# Fetch the project, e.g. influx/telegraf
go get -d github.com/influxdata/telegraf
# CD into the project
cd $GOPATH/src/influxdata/telegraf
# Fetch the modules under vendor/ directory
go mod vendor
After calling the last command, Go will fetch all the dependencies under pkg/mod. Not sure why it is doing that, but I assume it is because it needs to build the project normally, and then move the fetched dependencies under the vendor/ folder. After that, I can build successfully. However, to make sure I no longer require those dependencies, I deleted the pkg/mod directory completely and rebuilt the project. Go compiler, for some reason, fetched the packages again.
Is there anything I am doing wrong?
Thanks!
The vendor folder is not used automatically in all cases.
To make sure dependencies are loaded from the main module's vendor folder, pass -mod=vendor to the go tool.
The vendor folder if present is only used automatically (if not specified otherwise with -mod=mod) if the go version specified by go.mod file is 1.14 or higher.
These are detailed in Command go: Maintaining module requirements:
If invoked with -mod=vendor, the go command loads packages from the main module's vendor directory instead of downloading modules to and loading packages from the module cache. The go command assumes the vendor directory holds correct copies of dependencies, and it does not compute the set of required module versions from go.mod files. However, the go command does check that vendor/modules.txt (generated by 'go mod vendor') contains metadata consistent with go.mod.
If invoked with -mod=mod, the go command loads modules from the module cache even if there is a vendor directory present.
If the go command is not invoked with a -mod flag and the vendor directory is present and the "go" version in go.mod is 1.14 or higher, the go command will act as if it were invoked with -mod=vendor.

Using "go get" to download binaries without adding them to go.mod

I'm using Go modules in my project and in my build system (e.g. Travis CI) I'm downloading a command-line utility (written in Go) with go get to assist with my build process, e.g.:
go get github.com/mitchellh/gox
However, this go get causes the file to be added to my go.mod file. This is contaminating the build environment, causing it to be "dirty" (since there are changes to some files tracked in git, in this case go.mod and go.sum), and I use git describe --always --dirty --tag to describe my build, which shows up as "dirty".
Is there a way to "go get" a binary just to download it, without adding it to the go.mod/go.sum?
I've tried setting GOPATH to somewhere else, even then, go get updates the go.mod/go.sum to add this as an // indirect dependency.
dir="$(mktemp -d)"; \
env GOPATH="$dir" go get github.com/mitchellh/gox && \
mv "$dir/bin/gox" "$(go env GOPATH)"/bin/gox
Go 1.16 onwards
Go 1.16 (released February 2021) includes a change that makes it possible to install a binary without affecting go.mod.
Issue 40276 tracks the proposal:
cmd/go: 'go install' should install executables in module mode outside a module
This was implemented in CL 254365. As part of this change, you can run e.g.:
go install golang.org/x/tools/cmd/goimports#latest
to install a binary without affecting go.mod.
To install a specific version, replace #latest with e.g. #v0.1.5.
Hopefully in Go 1.14 there will be a new flag for go get that does exactly what you are asking. This is tracked in issue #30515 "cmd/go: offer a consistent global install command".
Prior to that, you have a few different options.
Go 1.12 and 1.13: change directory
If you are using Go 1.12 or later, the simplest solution is probably to move outside your current module to a directory without a go.mod prior to doing the go get, such as:
$ cd /tmp
$ go get github.com/foo/bar#v1.2.3
$ cd - # return to prior directory
Go 1.11, 1.12, 1.13+: gobin
gobin is a module-aware command to install or run binaries that provides additional flexibility, including the ability to install without altering your current module's go.mod. See the gobin README and FAQ for more details.
Go 1.11: temporary module
If you are using Go 1.11 with modules, the first step is probably to upgrade to Go 1.12 or 1.13 given there are many improvements in modules. If you are required to use Go 1.11 and want to use the #version syntax without updating your current module's go.mod, then one approach is to create a temporary module:
cd $(mktemp -d) && go mod init tempmod && go get github.com/foo/bar#v1.2.3
This is because in Go 1.11, you can't use the #version syntax unless you are in a module, which was relaxed in Go 1.12. This approach has been automated by a simple shell script by #rogpeppe.
Additional Details
In general, the go command in module-module always determines what module it is "in", which is based on the current working directory when you invoke the go command. (You could make an analogy to how make without any args will look for a makefile in the current working directory, or how historically go build without any args will build the current working directory, etc.).
With modules, go get looks for a go.mod file in the current working directory or any of its parents, and go get will use the constraints listed in any go.mod as part of solving for versions, as well as update the go.mod if needed based on doing the go get. That is why your go.mod file is updated if you run go get from within an existing module.
On the other hand, starting with Go 1.12, if you are in a directory that is not part of any module (that is, the directory does not have a go.mod, nor do any of its parents), then there is no go.mod to update, but the go command is still able to operate in module mode and use the #version syntax.
From the Go 1.12 release notes:
When GO111MODULE is set to on, the go command now supports module-aware operations outside of a module directory, provided that those operations do not need to resolve import paths relative to the current directory or explicitly edit the go.mod file. Commands such as go get, go list, and go mod download behave as if in a module with initially-empty requirements. In this mode, go env GOMOD reports the system's null device (/dev/null or NUL).
Per go help build:
The -mod build flag provides additional control over updating and use of go.mod.
If invoked with -mod=readonly, the go command is disallowed from the implicit automatic updating of go.mod

Resources