go: cmd/go: go mod init doesn't import nested module, tidy picks older version
In this case, the glide.lock
file depends on a version of github.com/hashicorp/consul
that uses modules and submodules. Before go mod tidy
, we are depending on v1.5.1
, a commit with date 2019-05-22. After go mod tidy
, the dependency has regressed to the latest available api
submodule version, v1.1.0
, a commit with date 2019-05-08.
This is a dependency regression which could potentially have broken code relying on new features added between the two commits, something that go mod tidy
shouldn’t be able to do.
I suspect that go mod tidy
needs to use a pseudoversion commit in this case, perhaps github.com/hashicorp/consul/api v1.1.1-0.20190522201912-40cec98468b8
.
% go version
go version devel +a05c132064 Wed Jul 10 15:52:04 2019 +0000 linux/amd64
% ls
glide.lock main.go
% cat glide.lock
hash: 0a6384395a31012cdcb431685f7cbe2ab3e4fb82412f708c491a785002881ed0
updated: 2019-06-05T16:36:27.768346055+02:00
imports:
- name: github.com/hashicorp/consul
version: 40cec98468b829e5cdaacb0629b3e23a028db688
subpackages:
- api
% cat main.go
package main
import _ "github.com/hashicorp/consul/api"
func main() {
}
% go mod init m
go: creating new go.mod: module m
go: copying requirements from glide.lock
% cat go.mod
module m
go 1.13
require github.com/hashicorp/consul v1.5.1
% go mod tidy
% cat go.mod
module m
go 1.13
require (
github.com/hashicorp/consul/api v1.1.0
github.com/hashicorp/go-msgpack v0.5.4 // indirect
github.com/hashicorp/memberlist v0.1.4 // indirect
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c // indirect
golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b // indirect
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 // indirect
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e // indirect
)
About this issue
- Original URL
- State: open
- Created 5 years ago
- Comments: 16 (10 by maintainers)
To shed a little more light on the situation here the Consul repo contains 3 modules.
github.com/hashicorp/consul
github.com/hashicorp/consul/api
github.com/hashicorp/consul/sdk
Both the
api
andsdk
modules are nested under the root module. One big purpose we use the nested modules for is to limit the dependencies necessary for the Consul API client (theapi
module). Consul itself has many large dependencies that we don’t want to require everyone who uses our API client to have to download and compile.Our general strategy for modules regarding releases is:
sdk
module if it had updates and then update theapi
and root modules dependencies on it.api
module if it had updates and then update the root modules dependency on it.The versions of the
sdk
andapi
modules do not correspond to the Consul version. There will be plenty of releases where neither of those two modules have updates and we aren’t bumping the version. As of right now theapi/v1.1.0
tag represents the latest API client and would be fully compatible with thev1.5.3
tag of the root module.With regards to the nested module situation, it has been a source of pain for us since implementing it. So much so that we having been considering ways to automate away the nesting.
The root problem I see is that we want to be able to PR changes to our public facing API and the corresponding changes to the API client all at once. This means that both bits of code must live in the same repository (at least at the time of the PR). What we are trying to avoid is leaking all of the root modules dependencies to everyone who wants to pull in the API client.
One solution I have been thinking through is getting rid of the nested module in the same repo and instead at release time pushing the API client code to a secondary repository where the go.mod would live. This would:
go mod vendor
attempting to vendor our own nested modules and there are others)The downside is that many of our users would need to change the import path or now be required to pull in all of the root modules dependencies. I wrote a tool to automate fixing import paths though so the burden on our users would be minimal.
@rogpeppe: as far as I can tell, there are several conditions that must all be met in order for the version to actually regress:
require
the nested module at an equivalent version (that is, a version for which the code within the nested module has the same behavior as at the named commit).latest
version of the nested module must be before the named commit. a. The nested module must have a release (or pre-release) tag. b. The named commit must be after the latest tag.It certainly is possible to meet those conditions, because you presumably would not have filed this issue otherwise. But I doubt that they co-occur often in practice.
In general, the module configuration converted from another dependency manager will often require adjustment anyway: for example, some dependency managers have operated at the package (rather than repo) granularity, and migrating those to modules ends up bumping the module version for all of those packages upward to the package with the highest requirement, which can end up pulling in breaking changes.
So it’s probably more useful to view the converted
go.mod
as a “roughed-in” configuration rather than a high-fidelity equivalent, and given that, addressing this issue seems like it would be a lot of work (and a lot of complexity) in order to address a transitional problem — and hopefully a rare one even then.This seems like a problem with importing from glide.lock, rather than
go mod tidy
.Before you run
go mod tidy
, onlygithub.com/hashicorp/consul
is required.github.com/hashicorp/consul/api
is imported, but not required. Any build command will add a requirement on the latest version, which is isv1.1.0
.go mod tidy
will add the requirement ongithub.com/hashicorp/consul/api
, but it will also remove any requirements on modules that aren’t transitively imported, sogithub.com/hashicorp/consul
is removed.When
go mod init
imports from another package manager, it does a pretty simple translation. I don’t think there’s any package manager that supports Go nested modules, so the glide.lock file will just say the repository is required at tagv1.5.2
, including everything in theapi
subdirectory.Perhaps we can do something more sophisticated here. We could walk the import graph, figure out what version or commit each package should have been required at, then try to reverse-engineer a go.mod file that produces the same build list. This is close to what
go get
does now, so it’s not infeasible (though I’m sure there are cases where MVS can’t produce the same result), but it’s a bit of work.@rogpeppe
…
/api v1.5.1-
… ?