proposal: switch to using nested modules for -m mode resolution
myitcv opened this issue · comments
Building on the ideas started in #44.
Currently, the -m
flag tells gobin
to use the main module for resolution of dependencies. -m
can also, therefore, be thought of as "non-global" mode. In -m
mode, we use the guidance at golang/go#25922 (comment) for keeping track of the tools we require in a build-ignored tools.go
file.
Under this behaviour however, -m
mode ends up cluttering our main module.
#44 proposed an alternative means of capturing the list of tools we require in a go.tools
file. This issue more precisely specifies the behaviour as follows:
go.tools
will capture the import paths of tools we require, e.g.:
# go.tools
golang.org/x/tools/cmd/stringer
example.com/blah/v2
gobin
will then use synthetic nested modules (in a .gobin
directory) per main package for module resolution (including the main package's module). For example:
// $PWD/.gobin/golang.org/x/tools/cmd/stringer/go.mod
module mod
require (
golang.org/x/tools v0.0.0-20190826060629-95c3470cfb70
)
Points to note:
- The filepath of nested modules will need to use the same escaping as seen in the
cmd/go
module cache - All non-directory
replace
directives in the main package's main module will appear in the synthetic nested module for a main package
Do we need one go.mod file per tool dependency? For example, I see no value in different tools using different versions of x/tools/go/packages
, and it's just going to make the builds slower and the setup more complex.
Going even further - why not add the go.mod/go.sum data right into go.tools? As a silly example, using txtar itself:
$ cat go.tools
golang.org/x/tools/cmd/stringer
example.com/blah/v2
-- go.mod --
module mod
require (
golang.org/x/tools v0.0.0-20190826060629-95c3470cfb70
)
-- go.sum --
etc etc
To clarify; my point about using a single go.tools
file is to reduce the overhead that is adding more files or directories to the root of a module.
However, one disadvantage is that not having the plain files on disk somewhere would make it harder to use vanilla cmd/go
to use or update the go.mod
and go.sum
files. But this is something that gobin
could allow; for example:
$ gobin go mod graph
# extracts go.mod and go.sum into a temporary folder
# runs the cmd/go command in said folder (in this case 'go mod graph')
# saves go.mod and go.sum back into go.tools
Do we need one go.mod file per tool dependency?
I would argue yes, and I suspect @rogpeppe would too.
Various reasons:
- brings versioned tool use within a project (via
go.tools
) inline with the global use, i.e. each tool is a distinct and separate entry point - means tools' respective dependencies don't ever mix and influence each other, which ensures reproducibility over time, especially globally vs locally
- allows us to make more aggressive assertions about the install target. That was the intent of #80 but precisely because the tools' dependencies interact with each other (via the main module in this instance) we can't make these assertions
- allows use to share the same
gobin
cache target for a project's versioned (i.e. non main module and non directory-replaced) module packages, and global tools
Just to confirm, the "overhead" for modules requiring tools under this proposal would be:
- a
go.tools
- a directory
.gobin/
containing a tree of directories for each main package, each containing ago.{mod,sum}
pair
Is your concern about the number of files in this .gobin
directory?
Gotcha, thanks. I hadn't thought about the side effects as much as you two have. Not convinced that this is a clear decision, but I see it's a tradeoff.
Is your concern about the number of files in this .gobin directory?
My concern is the number of files in the root of a module. Right now we already have go.mod
and go.sum
, and we've been thinking about go.tools
for a while. .gobin/
(or whatever other directory) would be the fourth, which is already a non-trivial amount of clutter.
Not convinced that this is a clear decision, but I see it's a tradeoff.
Agreed.
... which is already a non-trivial amount of clutter.
Indeed.
note that you only need such a nested module for tools that don't contain a go.mod file. Hopefully that will become increasingly uncommon as time goes on.
There are two reasons why I think we should still have a go.{mod,sum}
per main package module:
- handles the case where the main package's module might not be tidy/complete
- allows us to bake version information into the target binary, which can then be revealed using
go version -m /path/to/binary
in Go 1.13. If we simply install from the main module we will only get adevel
version identifier
handles the case where the main package's module might not be tidy/complete
If we live in a world where proxy.golang.org
is the standard for open source software, wouldn't (or couldn't) this already be covered?
If we simply install from the main module we will only get a devel version identifier
Are you positive about this? I understand why a go build ./some/tool
doesn't contain version info at the moment, as it does no module version resolution. But surely a go install some-tool
would contain the version info, as it would be pulled from the main go.mod
.
handles the case where the main package's module might not be tidy/complete
If we live in a world where
proxy.golang.org
is the standard for open source software, wouldn't (or couldn't) this already be covered?
I don't think so. Assume either the main module or a (transitive) dependency has incomplete dependencies. At time t1
missing dependency d1
resolves to v1.0.0
of; but some time later at t2
, after v1.1.0
has been released, it resolves to v1.1.0
. We don't have reproducible builds this way, and that's something I think we want and need.
If we simply install from the main module we will only get a devel version identifier
Are you positive about this? I understand why a
go build ./some/tool
doesn't contain version info at the moment, as it does no module version resolution. But surely ago install some-tool
would contain the version info, as it would be pulled from the maingo.mod
.
But the go.mod
itself has no version information. Perhaps I not quite getting what you're referring to; could you explain further with a cmd/go
"equivalent" perhaps? i.e. the steps that would in effect be followed by gobin
?