-
Notifications
You must be signed in to change notification settings - Fork 17.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cmd/go: 'go install' should install executables in module mode outside a module #40276
Comments
Change https://golang.org/cl/243077 mentions this issue: |
Thanks for the proposal!
Would it also be accurate to say that the go.mod being used as the main module is that of the module being built rather than no "main" module? Erroring on replaces is then simply a consequence of the implementation preventing replaces from being respected because we consider the module the "main". If the intention was not to have a main module, one might expect replaces to be ignored just like go get does today outside of a module. Regardless on how
Since |
That's not quite the intent: there will actually be no main module. In an earlier iteration on #30515, I suggested that
There is some use for |
I think we are on the same page here then regardless! |
Based on discussion in https://groups.google.com/g/golang-tools/c/BRCgqwWLwoY/m/pKuttL9cAwAJ and on Slack, I'd like to take this in a different direction, so I've updated CL 243077 and the copy above. Instead of adding
PTAL and comment on CL 243077 if you have any thoughts. This would be a significant change, but it would be good to have a clear separation of responsibility for |
Change https://golang.org/cl/254365 mentions this issue: |
With this change, 'go install' will install executables in module mode without using or modifying the module in the current directory, if there is one. For #40276 Change-Id: I922e71719b3a4e0c779ce7a30429355fc29930bf Reviewed-on: https://go-review.googlesource.com/c/go/+/254365 Run-TryBot: Jay Conrod <jayconrod@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Bryan C. Mills <bcmills@google.com> Reviewed-by: Michael Matloob <matloob@golang.org>
Moving conversation over from #40728, my only sticking point is that I agree with you here: #30515 (comment) 💯 When called from inside a module, the current behavior in |
@abursavich There are a couple significant problems with that approach. It would be a break from current It is confusing that there will be two variants of |
I think it's important to keep |
I'm just gonna withdraw my comments and step out of the way of progress. Leave it like it is in tip :) |
Is it too late to roll back the whole "global install command" thing completely? Because I feel like the way it's being impelmented I'd rather not have it in the ecosystem at all. I think the original impetus behind it was that approximately everyone had a README file that said "use go get to install this" and in modules mode it just did a bunch of weird things. But at this point we've lived without it for 2 years, READMEs have been changed, the community has adapted, do we still need it? And if it isn't going to do ~100% the same thing as A side effect of the way it's being implemented is that it discourages applications from using replace directives, heavily, if it gets adopted by the community as the default way to install applications. It's strange that replace directives are in the language but the tooling discourages from using them. If this is the design we settle in then replace directives should get a deprecation warning in the documentation. But IMHO replace directives are in fact useful, more useful than a global install command, so if they can't be accomodated maybe it's the global install command that should go away. |
This proposal hasn't been formally accepted yet. However, I think there's a pretty firm consensus to move forward with this in 1.16. The lack of a global install command has been a significant usability problem, and many people have asked for something that solves this problem.
I don't think the community has adapted. Many instructions I've seen recommend changing to a directory outside a module, then running
In module mode, this would be unexpected behavior. The go command usually fetches dependencies from a proxy. Even in
This is also discussed in the design doc and at length in the previous discussion in #30515. |
I agree with jayconrad that people haven't really adapted and switching to modules has made things more confusing between
I also think that anything that doesn't build the same binary as |
I want to give extra emphasis to a disadvantage of
|
I don't disagree with this, in fact I would also like a global install command. The problem that I have is that I dislike the way this is being implemented more than I would like having a global install.
I disagree, I think that this would be the expected behavior and that the current behavior of the go command is actually a regression. Address the "Why can't go get clone a git repository and build from there?" list of problems:
It wouldn't be a new build mode, it would be the same build mode as executing a
In reality most code is developed by running
I concede that this is a valid point, however correctness is more important than performance. Speaking of unexpected behavior, I think not respecting replace directives is a far more unexpected behavior than the changing details of how a module is downloaded from the internet.
When was it decided that replace directives are discouraged from release versions? I've scanned through the vgo blog series and the current Go Modules reference and I can't find anything suggesting this.
It seems to me that the design doc and the previous discussion is simply arguing consistency with pathological behaviors of |
This comment was marked as off-topic.
This comment was marked as off-topic.
Why wasn't the removal of |
@philipwhiuk please read https://go.dev/doc/go1compat:
Of course they are still careful when breaking the tooling, but it's allowed by the compatibility guarantee. |
That "compatibility guarantee" is a big "yeah we don't care about semver for tools, good luck" isn't it. |
@philipwhiuk Again, the compatibility promise is about the language and libraries, not the tooling. It's more about being able to write code that will compile in the future. The toolchain is disposable and can change, the language is stable. |
I know this is probably the wrong place to put this and that this feedback is likely going to be ignored, but this whole "the tooling is exempt from the compatibility promise" thing kind of feels like a cop-out. I understand the intent of this, as you use Go in production places then you will learn more things and realize what mistakes were made; however the community starts to standardize and expect the behaviour of commands to stay consistent unless there is a major version change. This violation of expectations is why you see people make more heated or angry comments. It feels like the tool changed out from under them and now they have to re-learn the tool. I'd be willing to argue that the semantics of how commands like Again, I get the point of why the tooling isn't behind the compat promise. It allows you to make the tooling better and fix behaviours that were outright bugs, but overall I'm really not sure if continuing to keep Go at 1.x post-modules was really a good idea. I know that politically making a "Go 2" is a difficult thing, but the kinds of changes that modules have given developers at nearly every level of the stack really seems like it should have been a candidate for being released as "Go 2". It's sad that this is politically difficult though, because "Go 2" has been lauded as a magical fairy unicorn thing where the standard library is going to be fixed forever or whatever. If anything what we have now may as well be "Go 2" because the Go I started learning near a decade ago is almost a completely different language than the Go I use today. |
It barely matters if the code still "compiles" if I can't actually get it to compile by running the same commands. Code that works on 1.13.4 will have to be rewritten to use modules most likely because there's no explanation of how to do what I was doing with |
I think we're getting off topic for this thread, especially as it's been closed for a while. I would suggest to give #37755 a read, which was a proposal to actually keep the pre-modules workflow working forever. For any other meta discussion about the backwards compatibility guarantee, I would personally suggest https://groups.google.com/g/golang-dev or perhaps a new issue if you have a specific proposal in mind. |
This comment was marked as off-topic.
This comment was marked as off-topic.
There were some minor typographical errors, which I fixed. I also updated the installation instructions to reference `go install`, instead of `go get`. Starting with Go 1.16, using `go get` to install executables has been deprecated. References: - https://go-review.googlesource.com/c/go/+/266360/ - golang/go#40276
For golang/go#33637 For golang/go#40276 Change-Id: I25ef2024867194bd7dc2e70157fef9123498f49d Reviewed-on: https://go-review.googlesource.com/c/website/+/285452 Trust: Jay Conrod <jayconrod@google.com> Run-TryBot: Jay Conrod <jayconrod@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Bryan C. Mills <bcmills@google.com>
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
So that the app can be installed with `go install`. See golang/go#40276 for details.
Authors: Jay Conrod, Daniel Martí
Last Updated: 2020-09-29
Design doc: CL 243077
Comments on the CL are preferred over comments on this issue.
Abstract
Authors of executables need a simple, reliable, consistent way for users to
build and install exectuables in module mode without updating module
requirements in the current module's
go.mod
file.Background
go get
is used to download and install executables, but it's also responsiblefor managing dependencies in
go.mod
files. This causes confusion andunintended side effects: for example, the command
go get golang.org/x/tools/gopls
builds and installsgopls
. If there's ago.mod
file in the current directory or any parent, this command also adds arequirement on the module
golang.org/x/tools/gopls
, which is usually notintended. When
GO111MODULE
is not set,go get
will also run in GOPATH modewhen invoked outside a module.
These problems lead authors to write complex installation commands such as:
Proposal
We propose augmenting the
go install
command to build and install packagesat specific versions, regardless of the current module context.
To eliminate redundancy and confusion, we also propose deprecating and removing
go get
functionality for building and installing packages.Details
The new
go install
behavior will be enabled when an argument has a versionsuffix like
@latest
or@v1.5.2
. Currently,go install
does not allowversion suffixes. When a version suffix is used:
go install
runs in module mode, regardless of whether ago.mod
file ispresent. If
GO111MODULE=off
,go install
reports an error, similar towhat
go mod download
and other module commands do.go install
acts as if nogo.mod
file is present in the current directoryor parent directory.
dependencies are used by users and module authors. See Rationale below.
all
,std
,cmd
)or local directories (
./foo
,/tmp/bar
).argument has a wildcard (
...
), it will only match main packages.version. All version suffixes must be identical. The versions of the
installed packages' dependencies are determined by that module's
go.mod
file (if it has one).
go.mod
file, it must not contain directives thatwould cause it to be interpreted differently if the module were the main
module. In particular, it must not contain
replace
orexclude
directives.
If
go install
has arguments without version suffixes, its behavior will notchange. It will operate in the context of the main module. If run in module mode
outside of a module,
go install
will report an error.With these restrictions, users can install executables using consistent commands.
Authors can provide simple installation instructions without worrying about
the user's working directory.
With this change,
go install
would overlap withgo get
even more, so we alsopropose deprecating and removing the ability for
go get
to install packages.go get
is invoked outside a module or whengo get
isinvoked without the
-d
flag with arguments matching one or more mainpackages,
go get
would print a deprecation warning recommending anequivalent
go install
command.go get
would no longer build or installpackages. The
-d
flag would be enabled by default. Setting-d=false
wouldbe an error. If
go get
is invoked outside a module, it would print an errorrecommending an equivalent
go install
command.Examples
Current
go install
andgo get
functionalitygo install
is used for building and installing packages within the context ofthe main module.
go install
reports an error when invoked outside of a moduleor when given arguments with version queries like
@latest
.go get
is used both for updating module dependencies ingo.mod
and forbuilding and installing executables.
go get
also works differently dependingon whether it's invoked inside or outside of a module.
These overlapping responsibilities lead to confusion. Ideally, we would have one
command (
go install
) for installing executables and one command (go get
) forchanging dependencies.
Currently, when
go get
is invoked outside a module in module mode (withGO111MODULE=on
), its primary purpose is to build and install executables. Inthis configuration, there is no main module, even if only one module provides
packages named on the command line. The build list (the set of module versions
used in the build) is calculated from requirements in
go.mod
files of modulesproviding packages named on the command line.
replace
orexclude
directivesfrom all modules are ignored. Vendor directories are also ignored.
When
go get
is invoked inside a module, its primary purpose is to updaterequirements in
go.mod
. The-d
flag is often used, which instructsgo get
not to build or install packages. Explicit
go build
orgo install
commandsare often better for installing tools when dependency versions are specified in
go.mod
and no update is desired. Like other build commands,go get
loads thebuild list from the main module's
go.mod
file, applying anyreplace
orexclude
directives it finds there.replace
andexclude
directives in othermodules'
go.mod
files are never applied. Vendor directories in the main moduleand in other modules are ignored; the
-mod=vendor
flag is not allowed.The motivation for the current
go get
behavior was to make usage in modulemode similar to usage in GOPATH mode. In GOPATH mode,
go get
would downloadrepositories for any missing packages into
$GOPATH/src
, then build and installthose packages into
$GOPATH/bin
or$GOPATH/pkg
.go get -u
would updaterepositories to their latest versions.
go get -d
would download repositorieswithout building packages. In module mode,
go get
works with requirements ingo.mod
instead of repositories in$GOPATH/src
.Rationale
Why can't
go get
clone a git repository and build from there?In module mode, the
go
command typically fetches dependencies from aproxy. Modules are distributed as zip files that contain sources for specific
module versions. Even when
go
connects directly to a repository instead of aproxy, it still generates zip files so that builds work consistently no matter
how modules are fetched. Those zip files don't contain nested modules or vendor
directories.
If
go get
cloned repositories, it would work very differently from other buildcommands. That causes several problems:
go
command to support a new buildmode.
built with both
go get
andgo install
.proxy when the original repository is unavailable. Fetching modules from a
proxy is roughly 5-7x faster than cloning git repositories.
Why can't vendor directories be used?
Vendor directories are not included in module zip files. Since they're not
present when a module is downloaded, there's no way to build with them.
We don't plan to include vendor directories in zip files in the future
either. Changing the set of files included in module zip files would break
go.sum
hashes.Why can't directory
replace
directives be used?For example:
replace
directives with a directory path on the right side can't be usedbecause the directory must be outside the module. These directories can't be
present when the module is downloaded, so there's no way to build with them.
Why can't module
replace
directives be used?For example:
It is technically possible to apply these directives. If we did this, we would
still want some restrictions. First, an error would be reported if more than one
module provided packages named on the command line: we must be able to identify
a main module. Second, an error would be reported if any directory
replace
directives were present: we don't want to introduce a new configuration where
some
replace
directives are applied but others are silently ignored.However, there are two reasons to avoid applying
replace
directives at all.First, applying
replace
directives would create inconsistency for users insideand outside a module. When a package is built within a module with
go build
orgo install
, onlyreplace
directives from the main module are applied, notthe module providing the package. When a package is built outside a module with
go get
, noreplace
directives are applied. Ifgo install
appliedreplace
directives from the module providing the package, it would not be consistent
with the current behavior of any other build command. To eliminate confusion
about whether
replace
directives are applied, we propose thatgo install
reports errors when encountering them.
Second, if
go install
appliedreplace
directives, it would take power awayfrom developers that depend on modules that provide tools. For example, suppose
the author of a popular code generation tool
gogen
forks a dependencygenutil
to add a feature. They add areplace
directive pointing to theirfork of
genutil
while waiting for a PR to merge. A user ofgogen
wants totrack the version they use in their
go.mod
file to ensure everyone on theirteam uses a consistent version. Unfortunately, they can no longer build
gogen
with
go install
because thereplace
is ignored. The author ofgogen
mightinstruct their users to build with
go install
, but then users can't track thedependency in their
go.mod
file, and they can't apply their ownrequire
andreplace
directives to upgrade or fix other transitive dependencies. The authorof
gogen
could also instruct their users to copy thereplace
directive, butthis may conflict with other
require
andreplace
directives, and it maycause similar problems for users further downstream.
Why report errors instead of ignoring
replace
?If
go install
ignoredreplace
directives, it would be consistent with thecurrent behavior of
go get
when invoked outside a module. However, in#30515 and related discussions, we found that
many developers are surprised by that behavior.
It seems better to be explicit that
replace
directives are only appliedlocally within a module during development and not when users build packages
from outside the module. We'd like to encourage module authors to release
versions of their modules that don't rely on
replace
directives so that usersin other modules may depend on them easily.
If this behavior turns out not to be suitable (for example, authors prefer to
keep
replace
directives ingo.mod
at release versions and understand thatthey won't affect users), then we could start ignoring
replace
directives inthe future, matching current
go get
behavior.Should
go.sum
files be checked?Because there is no main module,
go install
will not use ago.sum
file toauthenticate any downloaded module or
go.mod
file. Thego
command will stilluse the checksum database (sum.golang.org) to
authenticate downloads, subject to privacy settings. This is consistent with the
current behavior of
go get
: when invoked outside a module, nogo.sum
file isused.
The new
go install
command requires that only one module may provide packagesnamed on the command line, so it may be logical to use that module's
go.sum
file to verify downloads. This avoids a problem in
#28802, a related proposal to verify downloads
against all
go.sum
files in dependencies: the build can't be broken by one badgo.sum
file in a dependency.However, using the
go.sum
from the module named on the command line onlyprovides a marginal security benefit: it lets us authenticate private module
dependencies (those not available to the checksum database) when the module on
the command line is public. If the module named on the command line is private
or if the checksum database isn't used, then we can't authenticate the download
of its content (including the
go.sum
file), and we must trust the proxy. Ifall dependencies are public, we can authenticate all downloads without
go.sum
.Why require a version suffix when outside a module?
If no version suffix were required when
go install
is invoked outside amodule, then the meaning of the command would depend on whether the user's
working directory is inside a module. For example:
When invoked outside of a module, this command would run in
GOPATH
mode,unless
GO111MODULE=on
is set. In module mode, it would install the latestversion of the executable.
When invoked inside a module, this command would use the main module's
go.mod
file to determine the versions of the modules needed to build the package.
We currently have a similar problem with
go get
. Requiring the version suffixmakes the meaning of a
go install
command unambiguous.Why not a
-g
flag instead of@latest
?To install the latest version of an executable, the two commands below would be
equivalent:
The
-g
flag has the advantage of being shorter for a common use case. However,it would only be useful when installing the latest version of a package, since
-g
would be implied by any version suffix.The
@latest
suffix is clearer, and it implies that the command istime-dependent and not reproducible. We prefer it for those reasons.
Compatibility
The
go install
part of this proposal only applies to commands with versionsuffixes on each argument.
go install
reports an error for these, and thisproposal does not recommend changing other functionality of
go install
, sothat part of the proposal is backward compatible.
The
go get
part of this proposal recommends deprecating and removingfunctionality, so it's certainly not backward compatible.
go get -d
commandswill continue to work without modification though, and eventually, the
-d
flagcan be dropped.
Parts of this proposal are more strict than is technically necessary (for
example, requiring one module, forbidding
replace
directives). We could relaxthese restrictions without breaking compatibility in the future if it seems
expedient. It would be much harder to add restrictions later.
Implementation
An initial implementation of this feature was merged in
CL 254365. Please try it
out!
Future directions
The behavior with respect to
replace
directives was discussed extensivelybefore this proposal was written. There are three potential behaviors:
replace
directives in all modules. This would be consistent withother module-aware commands, which only apply
replace
directives from themain module (defined in the current directory or a parent directory).
go install pkg@version
ignores the current directory and anygo.mod
file that might be present, so there is no main module.
treat that module as the main module, applying its module
replace
directives from it. Report errors for directory
replace
directives. Thisis feasible, but it may have wider ecosystem effects; see "Why can't module
replace
directives be used?" above.report errors for any
replace
directives it contains. This is the behaviorcurrently proposed.
Most people involved in this discussion have advocated for either (1) or (2).
The behavior in (3) is a compromise. If we find that the behavior in (1) is
strictly better than (2) or vice versa, we can switch to that behavior from
(3) without an incompatible change. Additionally, (3) eliminates
ambiguity about whether
replace
directives are applied for users and moduleauthors.
Note that applying directory
replace
directives is not considered here forthe reasons in "Why can't directory
replace
directives be used?".Appendix: FAQ
Why not apply
replace
directives from all modules?In short,
replace
directives from different modules would conflict, andthat would make dependency management harder for most users.
For example, consider a case where two dependencies replace the same module
with different forks.
Another conflict would occur where two dependencies pin different versions
of the same module.
To avoid the possibility of conflict, the
go
command ignoresreplace
directives in modules other than the main module.
Modules are intended to scale to a large ecosystem, and in order for upgrades
to be safe, fast, and predictable, some rules must be followed, like semantic
versioning and import compatibility.
Not relying on
replace
is one of these rules.How can module authors avoid
replace
?replace
is useful in several situations for local or short-term development,for example:
golang.org/x/lint
imported asgithub.com/golang/lint
. Many of these problems should be fixed by lazymodule loading (#36460).
replace
is safe to use in a module that is not depended on by other modules.It's also safe to use in revisions that aren't depended on by other modules.
replace
directive is just meant for temporary local development by oneperson, avoid checking it in. The
-modfile
flag may be used to build withan alternative
go.mod
file. See also#26640 a feature request for a
go.mod.local
file containing replacements and other local modifications.replace
directive must be checked in to fix a short-term problem,ensure at least one release or pre-release version is tagged before checking
it in. Don't tag a new release version with
replace
checked in (pre-releaseversions may be okay, depending on how they're used). When the
go
commandlooks for a new version of a module (for example, when running
go get
withno version specified), it will prefer release versions. Tagging versions lets
you continue development on the main branch without worrying about users
fetching arbitrary commits.
replace
directive must be checked in to solve a long-term problem,consider solutions that won't cause issues for dependent modules. If possible,
tag versions on a release branch with
replace
directives removed.When would
go install
be reproducible?The new
go install
command will build an executable with the same set ofmodule versions on every invocation if both the following conditions are true:
go install example.com/cmd/foo@v1.0.0
.directly or indirectly by the
go.mod
file of the module providing theexecutable. If the executable only imports standard library packages or
packages from its own module, no
go.mod
file is necessary.An executable may not be bit-for-bit reproducible for other reasons. Debugging
information will include system paths (unless
-trimpath
is used). A packagemay import different packages on different platforms (or may not build at all).
The installed Go version and the C toolchain may also affect binary
reproducibility.
What happens if a module depends on a newer version of itself?
go install
will report an error, asgo get
already does.This sometimes happens when two modules depend on each other, and releases
are not tagged on the main branch. A command like
go get example.com/m@master
will resolve
@master
to a pseudo-version lower than any release version.The
go.mod
file at that pseudo-version may transitively depend on a newerrelease version.
go get
reports an error in this situation. In general,go get
reportsan error when command line arguments different versions of the same module,
directly or indirectly.
go install
doesn't support this yet, but this shouldbe one of the conditions checked when running with version suffix arguments.
Appendix: usage of replace directives
In this proposal,
go install
would report errors forreplace
directives inthe module providing packages named on the command line.
go get
ignores these,but the behavior may still surprise module authors and users. I've tried to
estimate the impact on the existing set of open source modules.
main
packages that Russ Cox built during anearlier study.
tests, or experiments. 187,805 packages remained.
version
go get
would fetch.modules were left.
go.mod
file. 4,519 were left.replace
at all.replace
only.replace
only.go.mod
files using modulereplace
only, I tried toclassify why
replace
was used. A module may have multiplereplace
directives and multiple classifications, so the percentages below don't add
to 100%.
replace
as a soft fork, for example, to point to a bug fix PRinstead of the original module.
replace
to pin a specific version of a dependency (the modulepath is the same on both sides).
replace
to rename a dependency that was imported with anothername, for example, replacing
github.com/golang/lint
with the correct path,golang.org/x/lint
.replace
to renamegolang.org/x
repos with theirgithub.com/golang
mirrors.replace
to bypass semantic import versioning.replace
withk8s.io
modules. Kubernetes has usedreplace
tobypass MVS, and dependent modules have been forced to do the same.
replace
directives I couldn't automaticallyclassify. The ones I looked at seemed to mostly be forks or pins.
The modules I'm most concerned about are those that use
replace
as a soft forkwhile submitting a bug fix to an upstream module; other problems have other
solutions that I don't think we need to design for here. Modules using soft fork
replacements are about 4% of the the modules with
go.mod
files I sampled (165/ 4519). This is a small enough set that I think we should move forward with the
proposal above.
The text was updated successfully, but these errors were encountered: