Right now there is no easy cross platform way to output multiple artifacts from multiple packages into a single directory.
Right now you can do env GOBIN=bin/ go install cmd/...
on many platforms, but not when cross compiling or on windows.
Suggesting making go build -o bin/ pkg1 pkg2
build both packages into the "bin" folder.
@robpike thinks this would make the meaning different based on if it is a file or directory and suggests using a different flag like "-odir". That seems fine however I'm not sure how -o would interact with -odir. If they were forbidden from being set at the same time, I would say they should be the same flag, namely -o.
Reference thread: https://groups.google.com/forum/#!topic/golang-dev/a1wvIZdnic0
Flag -o would name the file and -odir would name the directory. Seems clear to me. If both are set one could complain if -o is a rooted path.
Not sure this is worth doing at all, but it seems easy to design and understand.
@robpike That's a fair design. I think it would be useful to have, but I'm unsure if it would pay for a new flag.
I'm curious if others would also find it useful or if it is just me :).
FWIW I have been looking for something like this as well. For company policy reasons I need to build a .deb on a build box. There are several binaries, which I currently build individually and then package up. Not a terrible nuisance, but could probably save me some compile time since the binaries share most code.
if you use go install instead of go build, then it doesn't matter if you're
building one at time or building everything together. The shared code will
only be built once.
I think a lot of problems come from the overuse of "go build" where "go
install" should be used instead.
@minux There are times when I want the functionality of "go install", but placed into a local folder.
The above may come up more often for me then others as I work on a contract basis and juggle different customers accounts. I use a single GOPATH, but often architect projects to run within a non-global context. (Sorry, that might not make complete sense).
This is so close to working. I'm not sure why it doesn't. We should fix it for Go 1.12.
Clearly -o can't be used with multiple binary targets, but the implicit "write to the current directory" should work, and instead it does nothing.
$ cd /tmp/zzz
$ go build cmd/doc cmd/gofmt
$ ls -l
$ go build cmd/doc
$ go build cmd/gofmt
$ ls -l
total 14808
-rwxr-xr-x 1 rsc eng 4266200 Aug 9 15:00 doc
-rwxr-xr-x 1 rsc eng 3310440 Aug 9 15:00 gofmt
$
You could imagine -o or a variant specifying a directory rather than or as well as a file.
This isn't exactly a necessity, but it would be a huge convenience in my workflow - not to mention it would bring it in line with the install command. I never looked this up in the go docs. I just assumed it would work, but it doesn't 😞
I have a few projects with numerous small tools/utils and it would greatly simplify my testing if I could build them all at once to my cwd. Currently I have a bash script that runs consecutive go build ./path/to/tool
commands which is less than ideal.
Same here, we do most of our local dev on mac but than build and package for our standard linux image so have have similar problems since the GOBIN=... go install ...
trick doesn't work. I've hacked together Makefile
s to support cross-compiling with explicit go build -o ...
for each target binary as a workaround. You could probably take this further with wildcard
magic over the cmd/
directory but I decided I like readable makefiles.
This works for now but would prefer a go build
or go install
solution.
Change https://golang.org/cl/143139 mentions this issue: cmd/go: make go build write multiple executables
One side effect of this CL is that it breaks the venerable "go build -a std cmd" for Go maintainers (when run from $GOROOT/src, it will complain that "go" is already a directory; when run from any different directory, it will drop many binaries in it). The current workaround is "go build -o /dev/null -a std cmd".
Comments? /cc @rsc @robpike
There is a bit of discussion on the proposed CL, as well s the comment that the CL as implemented breaks go build -a std cmd
. I would also comment the current CL breaks a personal use case I sometimes run go build ./cmd/...
just to make sure everything my cmds rely on still build as a quick smoke test while in development.
I would make the following proposal:
Make go build
without -o
and multiple packages specified behave as before. Only output binaries from go build
with multiple packages are specified when -o
is specified. This prevents go build -a std cmd
from breaking, preserves all existing behavior, while adding useful method such as go build -o ./bin/ ./cmd/...
. Either that, or we could teach go install
a -o
switch that points to the output directory.
In the CL there are comments that suggest either:
go build $(go list -f '{{if eq .Name "main"}}{{.ImportPath}}{{end}}' ./...)
or for pkg in $(go list -f '{{if eq .Name "main"}}{{.ImportPath}}{{end}}' ./...); go build $pkg; end
. Yeah, that's missing the point. I can get around this behavior, On *nix I can have a couple of helper scripts and on Windows another helper scripts, or I could make it part of my build system for individual projects. Again, that's kinda missing the point.
Sometimes it would be really useful to install a bunch of commands into a specific directory. Most of the semantics are already defined.
Another option is to add an explicit -dryrun option (or similar name) to avoid writing binaries; similar to the undocumented -o /dev/null but more explicit. This would help those who want to “just test it compiles”, in an explicit way.
Then, building multiple executables would just work, by default. This would make the behavior of go build more consistent; by default, it would always build executables, rather than changing behavior depending on how many packages were specified. It would be easier to explain and document, and less surprising for beginners.
Ping, can we get this unstuck? It just needs a decision on a small detail, we all want the main feature in.
/cc @andybons
go build ./...
is useful today, and its meaning should not change. We can either take the -o
approach that @kardianos suggests, or require an explicit list of targets per my comment on the CL. I'll talk to @rsc to decide which.
@kardianos In module mode, it may be useful to issue a go get
command outside of any module (#24250) with the path to a binary plus some specific versions of dependencies to upgrade (or downgrade). In those cases, it seems redundant to require the user to pass -o $GOBIN
or -o $GOPATH/bin
explicitly just to get the binary to install in the usual place.
@bcmills Thank you for working on defining how go get
works outside of any module. I think I may be missing something. Here's how I could envision the go command working in module mode:
## Outside of a module
# In all cases, if $GOBIN is not defined, it assumes $GOBIN is $HOME/bin
# Outside a module. Fetches cmd1 and cmd2, compiles it, and installs it in $GOBIN.
go get github.com/user/repo/cmd/...
# (maybe) Outside a module. Fetches cmd1 and cmd2 installs in ./bin.
go get -o ./bin github.com/user/repo/cmd/...
# Outside a module. Fails.
go build github.com/user/repo/cmd/...
# (maybe) Outside a module. Builds cmd1 and cmd1 and outputs to ./bin
go build -o ./bin /home/user1/code/moduleroot/cmd/...
# Outside a module. Fails.
go install github.com/user/repo/cmd/...
# (maybe) Outside a module. Builds cmd1 and cmd1 and outputs to $GOBIN.
go install /home/user1/code/moduleroot/cmd/...
## Inside a module.
# Inside a module. Fetches cmd1 and cmd2, constrains get to local go.mod file, installs in $GOBIN.
go get github.com/user/repo/cmd/...
# (maybe) Inside a module. Fetches cmd1 and cmd2, constrains get to local go.mod file, installs in ./bin.
go get -o ./bin github.com/user/repo/cmd/...
# Inside a module, builds the local commands cmd1 and cmd2, and discards the results
go build ./cmd/...
# Inside a module. Installs cmd1 and cmd2 in ./bin.
go build -o ./bin ./cmd/...
# (maybe) Inside a module. Installs cmd1 in ./bin (assuming bin is a folder).
# This would be useful in reusing the same script for windows and linux.
go build -o ./bin ./cmd/cmd1
# Inside a module, builds the local commands cmd1 and cmd2, installs in $GOBIN.
go install ./cmd/...
Can you explain why you would need to pass in -o $GOBIN
to go get
with this change?
We may all want this "in", but it's too late for Go 1.12.
This is very sad.
It is the second release that this feature is postponed because the community doesn't get answers from the Go team regarding it in time for the release cycle (see also #23616 where I didn't get an answer for months). I have even sent a CL well in time, to try to unblock things.
How can we break this cycle? Is there a better channel than this? The community can't do development for things we care about if the Go team doesn't even prioritize answering to questions. /cc @andybons @bradfitz @bcmills @FiloSottile @cassandraoid
I'm sorry this one got stuck. As you know there has been a great deal of work on cmd/go last cycle and this one, and that has made looking at CLs for cmd/go even slower than usual. Also it seems that this feature interacts with modules, which makes it even worse since modules is in a steady state of flux.
@bcmills Let me know if there is something (proposal or code) that could make this move in 1.13.
Another use case I encountered today is building a main package in a different dir, without needing to worry about if I should append ".exe" at the end go build -o ./bin/ ./cmd/thecmd
.
Another use case I encountered today is building a main package in a different dir, without needing to worry about if I should append ".exe" at the end
go build -o ./bin/ ./cmd/thecmd
.
@kardianos For that specific use-case, you might find go env GOEXE
helpful.
~ $ env GOOS=linux go env GOEXE
~ $ env GOOS=windows go env GOEXE
.exe
I am aware of that. But again, I'm talking about slightly nicer default behavior.
To move forward with this, let's make -o
with an argument that is a directory write a new executable to that directory. Note that the key thing is that the argument _is_ a directory, not that it syntactically ends in slash. That is, go build -o
becomes more like mv
, not rsync
.
Separately, go build
with multiple command targets should be able to write multiple binaries to the current directory.
Change https://golang.org/cl/167679 mentions this issue: cmd/go: allow -o to point to a folder that writes multiple execs
It looks like only -o
behavior was changed, but go build
doesn't yet build multiple packages to the current directory by default, so reopening this.
Separately, go build with multiple command targets should be able to write multiple binaries to the current directory.
Change https://golang.org/cl/190839 mentions this issue: cmd/go: fix "go help build -o" docs
For reference, my CL 143139 did work also without -o
. Not sure why it was redone from scratch.
Sorry for the scattered discussion. CL comment https://go-review.googlesource.com/c/go/+/143139/4#message-fb8adc4368e18ba081fe2fe221448bce17d990bc showed that go build ./...
that outputs executables (no -o) breaks things both in current std lib tests, and I'm sure in many tools that rely on that to discard output. Requiring -o .
to output to the current directory keeps the current command line compatibility, while still gaining the functionality.
Makes sense, thanks for closing the loop.
Change https://golang.org/cl/190907 mentions this issue: doc/go1.13: mention '-o <directory>' support for 'go build'
Is go build -o build/ ./cmd/foo ./cmd/bar main.go
going to be supported? Workaround is:
$ go build -o build/
$ go build -o build/ ./cmd/foo ./cmd/bar
Note that it does not work without ./
. Probably intentional.
@odiferousmint If a package is specified as a list of .go
files on the command line, all the positional arguments must be .go
files. So main.go
can't be mixed with ./cmd/foo ./cmd/bar
in the same command.
I just got 1.14 and I'm confused. This works great with regular GOOS and GOARCH on non-empty directories (nice work!):
gledr@LAPTOP-AH6IAHBO MINGW64 ~/Polyapp_Apps/gocode/src/gitlab.com/polyapp-open-source/poly/testdata (CI-expansion)
$ go list ./...
gitlab.com/polyapp-open-source/poly/testdata/cmd
gitlab.com/polyapp-open-source/poly/testdata/cmd/hello
gledr@LAPTOP-AH6IAHBO MINGW64 ~/Polyapp_Apps/gocode/src/gitlab.com/polyapp-open-source/poly/testdata (CI-expansion)
$ ls ./cmd
hello main.go wasm1 wasm2 wasm3
gledr@LAPTOP-AH6IAHBO MINGW64 ~/Polyapp_Apps/gocode/src/gitlab.com/polyapp-open-source/poly/testdata (CI-expansion)
$ go build -o ./cmd ./...
gledr@LAPTOP-AH6IAHBO MINGW64 ~/Polyapp_Apps/gocode/src/gitlab.com/polyapp-open-source/poly/testdata (CI-expansion)
$ ls ./cmd
cmd.exe hello hello.exe main.go wasm1 wasm2 wasm3
But doesn't work with GOOS=js GOARCH=wasm:
gledr@LAPTOP-AH6IAHBO MINGW64 ~/Polyapp_Apps/gocode/src/gitlab.com/polyapp-open-source/poly/testdata (CI-expansion)
$ GOOS=js GOARCH=wasm go list ./...
gitlab.com/polyapp-open-source/poly/testdata/cmd
gitlab.com/polyapp-open-source/poly/testdata/cmd/hello
gitlab.com/polyapp-open-source/poly/testdata/cmd/wasm1
gitlab.com/polyapp-open-source/poly/testdata/cmd/wasm2
gitlab.com/polyapp-open-source/poly/testdata/cmd/wasm3
gledr@LAPTOP-AH6IAHBO MINGW64 ~/Polyapp_Apps/gocode/src/gitlab.com/polyapp-open-source/poly/testdata (CI-expansion)
$ ls ./cmd
hello main.go wasm1 wasm2 wasm3
gledr@LAPTOP-AH6IAHBO MINGW64 ~/Polyapp_Apps/gocode/src/gitlab.com/polyapp-open-source/poly/testdata (CI-expansion)
$ GOOS=js GOARCH=wasm go build -o ./cmd ./...
go build gitlab.com/polyapp-open-source/poly/testdata/cmd/wasm1: build output "cmd\\wasm1" already exists and is a directory
go build gitlab.com/polyapp-open-source/poly/testdata/cmd/wasm2: build output "cmd\\wasm2" already exists and is a directory
go build gitlab.com/polyapp-open-source/poly/testdata/cmd/wasm3: build output "cmd\\wasm3" already exists and is a directory
go build gitlab.com/polyapp-open-source/poly/testdata/cmd/hello: build output "cmd\\hello" already exists and is a directory
It works great with GOOS=js GOARCH=wasm if I make a new directory:
gledr@LAPTOP-AH6IAHBO MINGW64 ~/Polyapp_Apps/gocode/src/gitlab.com/polyapp-open-source/poly/testdata
(CI-expansion)
$ ls ./cmd
hello main.go wasm1 wasm2 wasm3
gledr@LAPTOP-AH6IAHBO MINGW64 ~/Polyapp_Apps/gocode/src/gitlab.com/polyapp-open-source/poly/testdata
(CI-expansion)
$ ls ./bin
ls: cannot access './bin': No such file or directory
gledr@LAPTOP-AH6IAHBO MINGW64 ~/Polyapp_Apps/gocode/src/gitlab.com/polyapp-open-source/poly/testdata (CI-expansion)
$ GOOS=js GOARCH=wasm go list ./...
gitlab.com/polyapp-open-source/poly/testdata/cmd
gitlab.com/polyapp-open-source/poly/testdata/cmd/hello
gitlab.com/polyapp-open-source/poly/testdata/cmd/wasm1
gitlab.com/polyapp-open-source/poly/testdata/cmd/wasm2
gitlab.com/polyapp-open-source/poly/testdata/cmd/wasm3
gledr@LAPTOP-AH6IAHBO MINGW64 ~/Polyapp_Apps/gocode/src/gitlab.com/polyapp-open-source/poly/testdata
(CI-expansion)
$ mkdir ./bin
gledr@LAPTOP-AH6IAHBO MINGW64 ~/Polyapp_Apps/gocode/src/gitlab.com/polyapp-open-source/poly/testdata (CI-expansion)
$ GOOS=js GOARCH=wasm go build -o ./bin ./...
gledr@LAPTOP-AH6IAHBO MINGW64 ~/Polyapp_Apps/gocode/src/gitlab.com/polyapp-open-source/poly/testdata (CI-expansion)
$ ls ./bin
cmd hello wasm1 wasm2 wasm3
And it works great if I rebuild into the bin directory:
gledr@LAPTOP-AH6IAHBO MINGW64 ~/Polyapp_Apps/gocode/src/gitlab.com/polyapp-open-source/poly/testdata (CI-expansion)
$ GOOS=js GOARCH=wasm go build -o ./bin ./...
gledr@LAPTOP-AH6IAHBO MINGW64 ~/Polyapp_Apps/gocode/src/gitlab.com/polyapp-open-source/poly/testdata (CI-expansion)
$ rm ./bin/cmd ./bin/hello ./bin/wasm2
gledr@LAPTOP-AH6IAHBO MINGW64 ~/Polyapp_Apps/gocode/src/gitlab.com/polyapp-open-source/poly/testdata (CI-expansion)
$ GOOS=js GOARCH=wasm go build -o ./bin ./...
Is this intended to work with non-empty parent directories of the binaries? Why does it work with GOOS=windows GOARCH=amd64 but not GOOS=js GOARCH=wasm?
@Gregory-Ledray, please open a new issue with steps to reproduce. Probably there is something special about wasm
binaries that is interfering with the feature.
Most helpful comment
@minux There are times when I want the functionality of "go install", but placed into a local folder.
The above may come up more often for me then others as I work on a contract basis and juggle different customers accounts. I use a single GOPATH, but often architect projects to run within a non-global context. (Sorry, that might not make complete sense).