gofrs / uuid

A UUID package for Go

Home Page:https://gof.rs/projects/uuid/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unstable Import Paths

markbates opened this issue · comments

With the introduction of the go.mod file and declaration of github.com/gofrs/uuid/v3 there is no longer a single import path that works correctly for both modules and non-modules users.

This has become an issue with gobuffalo/pop and other Buffalo projects.

Below you find a selection of go get statements that represent the different import paths and what happens when trying to use them in different environments.

Go >1.9.7 (or GO111MODULE=off)

$ go get github.com/gofrs/uuid/v3

package github.com/gofrs/uuid/v3: cannot find package "github.com/gofrs/uuid/v3" in any of:
    /usr/local/go/src/github.com/gofrs/uuid/v3 (from $GOROOT)
    /go/src/github.com/gofrs/uuid/v3 (from $GOPATH)

Go 1.11 (GO111MODULE=on)

$ go get github.com/gofrs/uuid
module github.com/gobuffalo/uuidmods

require github.com/gofrs/uuid v3.1.0+incompatible // indirect
$ go get github.com/gofrs/uuid/v3
module github.com/gobuffalo/uuidmods

require (
	github.com/gofrs/uuid/v3 v3.1.2 // indirect
)

Isn't github.com/gofrs/uuid/v3 v3.1.2 // indirect the desired outcome? I'm not aware of any cross Go version support for Go module import paths.

So, yes, when using modules and /v3 everything works correctly. But if you use that, then it breaks those not use modules.

Yea and I'm not sure of support for compiling Go <1.11 with module import paths. This sounds like a problem all Go projects that adopt modules and cross compile have.

There are two ways you can fix it.

https://github.com/golang/go/wiki/Modules#releasing-modules-v2-or-higher

If a branch is created and called v3, if that is set as the default branch does that make it so users of go get will fetch v3 as the root?

I believe the branch approach only works for >1.9.7, but don't quote me on that.

See golang/go#27430 and golang/go#25967 (comment).

For maximum compatibility, leave the original v3 package at its existing import path (with the +incompatible version suffix and no go.mod file), and start a new major version (/v4) in a subdirectory. That will be unambiguous for all importers, including those on pre-1.9.7 toolchains.

Finally, I have a tool in the works that may help for cases like this one: the goforward command (under review in https://golang.org/cl/137076) makes it much easier to implement v3 in terms of v4 or vice-versa.

Hi all,

I wanted to circle back to the original problem report for this issue:

With the introduction of the go.mod file and declaration of github.com/gofrs/uuid/v3 there is no longer a single import path that works correctly for both modules and non-modules users.

...

Below you find a selection of go get statements that represent the different import paths and what happens when trying to use them in different environments.

Go >1.9.7 (or GO111MODULE=off)

$ go get github.com/gofrs/uuid/v3

package github.com/gofrs/uuid/v3: cannot find package "github.com/gofrs/uuid/v3" in any of:
/usr/local/go/src/github.com/gofrs/uuid/v3 (from $GOROOT)
/go/src/github.com/gofrs/uuid/v3 (from $GOPATH)

and:

when using modules and /v3 everything works correctly. But if you use that, then it breaks those not use modules.

The bit I wanted to try to clarify is that IF you are using Go 1.9.7 as a client of this uuid package, then things do work and you can avoid that error shown in the original problem report.

Part of the tricky bit is that if you are using Go 1.9.7, then I believe you should not include the /v3 in the 'go get' command. Using the /v3 in the 'go get' command is what I think is triggering the error in the original report.

It might be simplest to show by example. Here we'll download an older Go 1.9.7 toolset, then show getting, importing, and using github.com/gofrs/uuid in a trivial client:

# 1. Verify we do *not* have GO111MODULE set:

$ echo $GO111MODULE

# 2. Install Go 1.9.7 if we don't have it:

$ go get golang.org/dl/go1.9.7
$ go1.9.7 download

# 3. Using Go 1.9.7, 'go get' the package of interest.
#    KEY POINT: do not use '/v3' for 'go get' here:

$ go1.9.7 get github.com/gofrs/uuid

# 4. Still using Go 1.9.7, we create, build, and run a trivial client within GOPATH:

$ mkdir -p $GOPATH/src/example.com/sratchpad/uuidhello
$ cd $GOPATH/src/example.com/sratchpad/uuidhello
$ cat <<EOF > hello.go
package main

import (
	"fmt"
	"github.com/gofrs/uuid"
)

func main() { fmt.Println(uuid.Must(uuid.NewV4())) }

EOF

# 5. Build using Go 1.9.7, then run successfully:

$ go1.9.7 build .
$ ./uuidhello

e3ef4eea-6df5-420e-a6b7-39e3b2be0927

That behavior demonstrated there is part of the "minimal module awareness" that was added to Go versions 1.9.7+, 1.10.3+ and non-module-mode 1.11 so that code built with those releases can properly consume v2+ modules without requiring modification of pre-existing code. That behavior is covered in a little more detail here:

And as @markbates already demonstrated above in #61 (comment), code that has opted in to modules using Go 1.11 ends up working as well.

The net of all that is that IF you are willing to require clients to use versions Go 1.9.7+, 1.10.3+, and 1.11, then I think things work.

However, if you want to support versions older than Go 1.9.7, then the /v3 subdirectory approach mentioned by @markbates and @bcmills (#61 (comment)) is an avenue to support even older versions of Go.

All that said, I am not sure what is the best alternative to support the particular goals of this project. I am just a member of the community trying to share my personal understanding of how things currently work, but I am certainly happy to learn more, and apologies in advance if anything I've said here is off-base.

@thepudds you're missing the point. there is NO SINGLE import path that works for EVERYONE.

If you use gofrs/uuid it work for fine for everyone NOT USUING MODULES.

If use gofrs/uuid and your are using modules you STUCK at 3.1.0! Do not pass GO, do not collect $200.

If use gofrs/uuid/v3 with modules, life is grand. If you aren't use modules that things go BOOM.

As someone out there writing code in the real world, I need to support everyone.

I feel as though I have clearly demonstrated this is broken on this ticket.

I apologize if my tone is strong on that, but I continue to fight this same issue across many repos because it is not clearly understood and can easily be broken if not done correctly.

This is only a problem if you care about those either using mods/ or not using mods. If you don't care, then this isn't an issue.

Hi @markbates no worries, we are all on a voyage of discovery here. ;-)

The main distinction I was trying to draw is that IF support is restricted to Go 1.9.7+, 1.10.3+ and 1.11, then I think "things work" (and without requiring use of a /v3 subdirectory).

Supporting older Go versions like Go 1.8 is a bit trickier, though.

Thanks for the detailed report. I think we should do everything we can to support all users, and as many Go versions as we can. It is still not clear to me exactly what we should do, though.

@markbates says we should use a v3 subdirectory.

On the other hand, @bcmills says we should use a v4 subdirectory and leave the original v3 package at its current import path with no go.mod. But we do have a go.mod for v3, at v3.1.1. Is that one not good? Should we remove it?

In any case, it seems like moving one version of the package into a vX subdirectory is the way forward. In this case, where do we put the go.mod? I noticed in @markbates packr repository that both the root directory and the v2 subdirectory have their own go.mod.

I'm with fine with either /v3 or /v4 sub directory. I would tend to agree with @bcmills as it's best to just put the whole thing in the past to move it forward.

Packr is 1.x in the root and 2.x in /v2. From what I believe, and the docs aren't clear on it, is whether you need one in each directory or not. I err'd on the side of "go modules do weird things right now and better safe than sorry".

Hmm, looking at it more, it seems like we should have bumped major versions when adding modules support, but we didn't. Is that the cause of go get github.com/gofrs/uuid resolving to require github.com/gofrs/uuid v3.1.0+incompatible when using modules and not explicitly specifying /v3?

Given that we, unfortunately, didn't bump major versions, what is the best course of action now? Should we rectify the mistake by moving to v4?

Yeah, the "logic" (I mean modules logic) is to use the last version that didn't have a go.mod if you don't use /v3 but are using modules. That's the underlying issue with this this bug.

My vote, is /v4, and y'all should write a blog post to demonstrate this issue. :)

Okay. Sorry if I'm being obtuse, but I want to make sure we get everything right, so I will recap. My understanding is that for maximum compatibility, we should:

  • leave the current contents of the root directory untouched
  • create a copy of the code in a /v4 subdirectory
  • create a go.mod with module github.com/gofrs/uuid/v4 in the /v4 subdirectory
  • update the documentation to recommend github.com/gofrs/uuid/v4 as the one true import path that works for everyone
  • push all of this up, then cut v4.0.0

Does that sound right?

I agree with @markbates: if existing call sites are written against v3.x.x with the non-/v3 import path, to get them to build in module mode you'll want a v3.x.x+incompatible version, which implies removing the go.mod file and tagging the result (probably as v3.1.3).

(The presence of the go.mod file signals that you're using semantic import paths.)

@acln0:

create a copy of the code in a /v4 subdirectory

Note that /v4 doesn't have to be a copy. It can forward functions and types back to the non-/v4 path, or you can move (not copy) the code to /v4 and rewrite the non-/v4 path to forward to /v4. Either would reduce the maintenance overhead, since bug-fixes in one path would automatically apply to the other as well. (That's what the goforward tool is for.)

Two related but perhaps separable questions might be:

Q1. What does the gofrs organization view as the best way forward for the gofrs/uuid package regarding modules?

Q2. Is the gofrs/uuid repo broken as it stands today?

There is a discussion going on elsewhere within the gofrs team regarding Q1, I think.

One semi-related question: does anyone know the minimum version of Go required for gobuffalo/pop currently? The gobuffalo documentation seems to suggest Go 1.9.7+ is required:

https://gobuffalo.io/en/docs/installation#requirements

Before installing make sure you have the required dependencies installed:
...
Go version 1.9.7 or greater.

But I am not a gobuffalo user, so not sure if that is the right place to look.

Regarding:

Q2. Is the gofrs/uuid repo broken as it stands today?

Having looked at it a bit more, as far as I can tell:

  • It seems the gofrs/uuid repo in effect currently requires Go 1.9.7+, 1.10.3+ or 1.11.
  • The gofrs/uuid repo in its current state seems to work with those versions of Go.
  • However, it might not have been intentional that gofrs/uuid in effect requires Go 1.9.7+, 1.10.3+ or 1.11.

Of course, happy to learn more here, and this is not based off of any type of exhausting testing or assessment.

One semi-related question: does anyone know the minimum version of Go required for gobuffalo/pop currently?

Go 1.9.7+, the same requirements as gobuffalo/buffalo.

@markbates, we're all trying to figure out a useful path forward. Please be patient and charitable.

You say:

This has become an issue with gobuffalo/pop and other Buffalo projects.

But you haven't said what the concrete issue is. If gofrs/uuid already requires Go 1.9.7+, then all existing code that does not have a go.mod file can use the non-/v3 import path, and all existing code that does have a go.mod file can use the /v3 import path (in both module mode and GOPATH mode).

Is the problem:

  • that users of gobuffalo/pop are using Go versions that predate 1.9.7?
  • that users are confused as to how to enable the /v3 import path?
  • something else?

@bcmills I apologize for getting upset, but I have dealt with this issue on several repos now, and it gets wearisome having the same conversation with the same people. I have been "patient and charitable", I'm known for those qualities, but I can only keep having the same arguments so many times.

The problem, that I stated originally, and keep stating, is that if you import gofrs/uuid using go modules it stops at 3.1.0 because it is not compatible with semantic import paths and uses the last known version without a mod, which is 3.1.0 not 3.1.2. This import path works correctly for those not using modules.

To support module users you need to gofrs/uuid/v3 which breaks non-module users.

This issue has nothing to do with any version of Go, apart from that this package can not support ALL version of go under a unified import path.

All I want is ONE path I can use to support everyone.

Right now I'm forced to choose whether I want to support non-module users with gofrs/uuid and tell module users "sorry you're not going to get any updates because of this" or use gofrs/uuid/v3 and tell all of our non-module users to go bye-bye.

It's terrible decision to have to make, when the answer is to simple implement semantic import versioning correctly in the repo.

@bcmills For several reasons, we have some repos which don't have a go.mod file until we can migrate fully to go modules.

The following code don't work with go 1.11.1, modules disabled and no go.mod file:

package main

import (
	"fmt"

	"github.com/gofrs/uuid/v3"
)

func main() {
	id, err := uuid.NewV4()
	if err != nil {
		panic(err)
	}
	fmt.Println(id)
}

Here's the output from go run .:

main.go:6:2: cannot find package "github.com/gofrs/uuid/v3" in any of:
        /usr/local/go/src/github.com/gofrs/uuid/v3 (from $GOROOT)
        /home/michalakst/go/src/github.com/gofrs/uuid/v3 (from $GOPATH)

It works with a go.mod file:

module github.com/stanislas-m/uuid

require github.com/gofrs/uuid/v3 v3.1.2

Output:

$ go run .
ea2b6a6b-e08a-433d-9396-50ae9cf33dc4

With GO111MODULE=off and go.mod:

$ go run .
92506655-66cf-4f01-9835-7382364226ba

@stanislas-m

The following code don't work with go 1.11.1, modules disabled and no go.mod file

That's true. In non-module-aware code, you need to use the pre-v3 import path, which builds successfully against v3.1.0.

@markbates

if you import gofrs/uuid using go modules it stops at 3.1.0 because it is not compatible with semantic import paths and uses the last known version without a mod, which is 3.1.0 not 3.1.2.

Regardless of how you feel about the v3 / v4 split, v3.1.1 seems like a really awkward place to split out a /v3 module, since (as you note) that will prevent users in module mode from picking up any further patch releases at that import path (unless you also push alternating patch releases without the go.mod file).

The straightforward solution seems to be to add a /v4 module and leave a final v3.2.0 implementation (without a go.mod file) that forwards to it using the new /v4 path. See https://github.com/bcmills/uuid61 for an example: note that github.com/bcmills/uuid61/module still builds at every module/* tag, even using go1.9.7 in GOPATH mode.

How likely are we to hate the v4 directory in the future, and what's the potential to revert to a different practice?

If you're assuming Go 1.9.7 and up, you shouldn't even need a /v4 directory. If you want to follow the “major branch” layout instead, you can switch to a v4.x.y tag and leave everything in the directory where it is today: it's the v3.2.0 forwarding shim that does the heavy lifting, and that only has to be in place for a single commit (which doesn't even have to be on the master branch).

@stanislas-m Based on your comments, I am operating under the assumption that you are OK with Go 1.9.7+.

The gofrs organization might be shortly picking a different way forward here, but right now, as far as I can tell, the gofrs/uuid repo in effect currently requires Go 1.9.7+, 1.10.3+ or 1.11, and "things work" (if you are using those versions of Go, which seems to be your personal case, if I followed).

I wanted to show three quick examples of how "things work", where these examples are based off of your example.

PREP: start with clean slate.

rm -rf $GOPATH/src/github.com/gofrs/uuid/
rm -rf $GOPATH/src/example.com/scratchpad/ 

SCENARIO 1:

  • This is @stanislas-m's first example, which is a non-module based consumer.
  • go 1.11.1, modules disabled and no go.mod file
  • BUT, we change the import path from his example to NOT use uuid/v3 because this code has not opted in to modules, and code that has not opted in to modules should not use /v3 in the import path when importing this v3 module.
mkdir -p $GOPATH/src/example.com/scratchpad/nomodconsumer
cd $GOPATH/src/example.com/scratchpad/nomodconsumer

# Verify we do *not* have GO111MODULE set:
echo $GO111MODULE

# 'go get'. Note that we do a normal 'go get' here without `/v3'
go get github.com/gofrs/uuid

# Create our code:
cat <<EOF > nomodconsumer.go
package main

import (
        "fmt"
        "github.com/gofrs/uuid"
)

func main() {
        id, err := uuid.NewV4()
        if err != nil {
                panic(err)
        }
        fmt.Println(id)
}

EOF

# Build, which succeeds:
go build .

# Run:
./nomodconsumer
4b8d47d9-db8f-4f07-8b1c-9a594da15e3f

SCENARIO 2:

  • A module-based consumer library
  • The import path DOES use uuid/v3 because this code HAS opted in to modules.
mkdir -p $GOPATH/src/example.com/scratchpad/modconsumer
cd $GOPATH/src/example.com/scratchpad/modconsumer

# Init our module:
GO111MODULE=on go mod init example.com/scratchpad/modconsumer

# Create our code:
cat <<EOF > modconsumer.go
package modconsumer

import (
        "github.com/gofrs/uuid/v3"
)

func ModConsumer() uuid.UUID {
        id, _ := uuid.NewV4()
        return id
}

EOF

# Build, which is successful
GO111MODULE=on go build .

# Review the resulting go.mod
cat go.mod

module example.com/scratchpad/modconsumer

require github.com/gofrs/uuid/v3 v3.1.2

SCENARIO 3:

  • Mixed mode: this effectively mixes both of our prior scenarios together in a single build - a non-module main, with a module-based lib.
  • Key point: it might seem surprising, but we are mixing the uuid/v3 import path and plain uuid import path (without /v3) in a single build.
  • The main package does NOT use uuid/v3 because this code has not opted in to modules, and code that has not opted in to modules should not use /v3 in the import path when importing this v3 module.
  • The lib is the exact lib from Scenario 2 (which DOES use uuid/v3 when importing because the lib HAS opted in to modules).
mkdir -p $GOPATH/src/example.com/scratchpad/mixedconsumer
cd $GOPATH/src/example.com/scratchpad/mixedconsumer

# Verify we do *not* have GO111MODULE set:
echo $GO111MODULE

# 'go get'. Note that we do a normal 'go get' here without `/v3'
go get github.com/gofrs/uuid

# Create our main package code:
cat <<EOF > mixedconsumer.go
package main

import (
        "fmt"
        "github.com/gofrs/uuid"
        "example.com/scratchpad/modconsumer"
)

func main() {
        id1, _ := uuid.NewV4()
        id2 := modconsumer.ModConsumer()
        fmt.Println(id1)
        fmt.Println(id2)
        fmt.Println("validate comparison compiles. result:", id1 != id2)
}

EOF

# Build, which succeeds:
go build .

# Run:
./mixedconsumer

7eb7f462-9ed5-40b1-92c2-3c88b6a9e20b
8fff234f-f563-48e0-8ff9-24dccf336858
validate comparison compiles. result: true

SUMMARY

  • Things work when using Go 1.9.7+, 1.10.3+ or 1.11. (The above results were on Go 1.11.1, but last night I also tried Go 1.9.7).
  • As @bcmills said:

existing code that does not have a go.mod file can use the non-/v3 import path, and all existing code that does have a go.mod file can use the /v3 import path (in both module mode and GOPATH mode).

  • The examples here are entirely about the current state of this repo.
  • There is an entirely different set of questions for the gofrs organization about how they want to proceed moving forward.

edit: added a simple id1 != id2 comparison to Scenario 3.

I would like to apologize to all on this issue for my tone earlier. I was, unfortunately, and regrettably, overly emotional. I’m sorry for this non-professional behavior.

Hope you have a good holiday Mark

Hmm, come to think of it, you might not even need to switch to major version 4. You could make v3.2.0 a forwarding package (without a go.mod) to github.com/gofrs/uuid/v3), with a corresponding v3.3.0 (with a go.mod) that provides the /v3 module.

@bcmills: do you happen to know the reason for the two different resolutions of commits for a user running gofrs/uuid path and a user who imports gofrs/uuid/v3. Why is the user who hasn't opted into running the modules import path penalized by the automatic resolution code if the library does have a go.mod file.

I believe this behaviour is the crux of the issue in this case.

I got another case, with an existing Gopkg.toml from dep:

[[constraint]]
  name = "github.com/gofrs/uuid"
  branch = "master"

(same behavior with a version constraint)

The version found by go modules (via go mod init):

github.com/gofrs/uuid v0.0.0-20181030014311-7077aa611296

Then, go run .:

$ go run .
go: github.com/gofrs/uuid@v0.0.0-20181030014311-7077aa611296: go.mod has post-v0 module path "github.com/gofrs/uuid/v3" at revision 7077aa611296
go: error loading module requirements

Hi @stanislas-m

The version found by go modules (via go mod init):

github.com/gofrs/uuid v0.0.0-20181030014311-7077aa611296

Is that part of the require statements in the resulting go.mod?

Could you try putting a /v3 at the end of the path , so that it reads:

github.com/gofrs/uuid/v3 v0.0.0-20181030014311-7077aa611296

And then try building?

Is that part of the require statements in the resulting go.mod?

Yes.

It got rewritten by the go build to:

github.com/gofrs/uuid v3.1.0+incompatible
github.com/gofrs/uuid/v3 v3.1.2

This one seems to compile, even if it's not straightforward.

@stanislas-m

Good, that is hopefully progress.

I think the way that it works is go mod init focuses on converting things like commit hashes or VCS tags from dep (in your case) into terminology understood by go.mod, but I think go mod init does not modify your .go code itself to change your code to use semantic import versioning -- that is, go mod init does not edit your .go files to add /vN to import statements, at least as far as I am aware. (And that is probably a good thing).

So if you just now had run go mod init inside some package, you probably should also check the import statements in the .go code in that package, and where needed, try editing the code to add the /v3 to any import "github.com/gofrs/uuid" so that it reads import "github.com/gofrs/uuid/v3", and try building again.

There is some tooling to automate this code edit, I think, but might be best to start with manual inspecting/manual edit.

try editing the code to add the /v3 to any import "github.com/gofrs/uuid" so that it reads import "github.com/gofrs/uuid/v3", and try building again.

My second case is from a project outside buffalo's scope. I can't do that in this project (and some others). I'm only testing go modules to convert an enterprise project, so go modules have to live beside the dep tooling. In other terms, it have to work with both dep and go modules, and with the same versions. I can alter the current code if it still works with the dep version, though.

@stanislas-m Switching back to gobuffalo for a moment, did the concrete examples in Scenarios 1-3 in the comment above #61 (comment) seem applicable to any of what you are facing on the gobuffalo front?

Scenario 1 there was your exact failing example from your comment above in #61 (comment), but with one change made to make it work rather than fail.

Separately, is there a different scenario that you'd like to see, perhaps something like a Scenario 4 that builds on Scenarios 1-3 in some way?

In some cases, it is helpful to discuss the theory. In other cases, it is easier to discuss concrete examples, and I was wondering if those specific concrete examples were helpful, or not.

Hmm, come to think of it, you might not even need to switch to major version 4. You could make v3.2.0 a forwarding package (without a go.mod) to github.com/gofrs/uuid/v3), with a corresponding v3.3.0 (with a go.mod) that provides the /v3 module.

I want to understand this better, so I'm going to try to spell it out.

At v3.2.0, there would be no go.mod in the root, we would have a v3 subdirectory containing all the code, and forwarding declarations from github.com/gofrs/uuid to github.com/gofrs/uuid/v3. This means that at v3.2.0, all clients could consume github.com/gofrs/uuid as well as github.com/gofrs/uuid/v3 (modulo +incompatible for modules users). At v3.3.0, the v3 subdirectory would be removed and github.com/gofrs/uuid would provide a go.mod with module github.com/gofrs/uuid/v3.

Is that right?

Current Issues

Buffalo users can't build.

@markbates I believe that you should get the behaviour you want if you upgrade all of your code that is module-aware to use the gofrs/uuid/v3 path.

This will limit correct behaviour to these versions of Go that are "minimal module aware" (Go 1.9.7+, 1.10.3+, 1.11) until we address part 2, but is a good stop-gap. However I believe that works for your user-group in the immediate based on your need so long as they don't explicitly disable modules when building.


gofrs/uuid cannot be used by a module aware library for Go versions < 1.9.7 || < 1.10.3 || < 1.11 that wants to support non-module users

@gofrs/leadership if we want to support less than those versions in an open environment where other libraries import us which use modules, and then non-module users use them, we need to create a v3 subdirectory as discussed above and alias our API there. I believe that we should since it's a relative small lift, especially with automation going forward.

@bcmills has a tool that will help with that going forward (heh) goforward.

Mixed mode: this effectively mixes both of our prior scenarios together in a single build - a non-module main, with a module-based lib.

@thepudds With pop, this is the other way. Pop is a non-module lib right now, and I don't expect it to migrate to go modules in the few weeks.

If we switch some libs used in buffalo to /v3 path, will it still be compatible with functions not-using the /v3 path? (can I call libA.New(v3uuid), with libA not using the /v3 path?)

I haven't tested your examples yet, I'm still at work.

It doesn’t solve the problem. In Go 1.11 if you turn off mods and go get /v3 it fails. As I keep trying to point out.

I would like to ask that I no longer be mentioned on this ticket. Please.

I’m taking a break from OSS for the next week for my mental health. I can no longer keep discussing this topic without serious health effects

I ask that everyone respects this.

Thank you

I want to understand this better, so I'm going to try to spell it out.

At v3.2.0, there would be no go.mod in the root, we would have a v3 subdirectory containing all the code, and forwarding declarations from github.com/gofrs/uuid to github.com/gofrs/uuid/v3.

You don't strictly need the v3 subdirectory: you can just have a v3.2.0 with no go.mod.

If you do have the subdirectory, you can do the whole thing in one commit and one tag (instead of two commits and two tags).

The two options are as follows:


Following the major subdirectory model (see https://github.com/golang/go/wiki/Modules#releasing-modules-v2-or-higher):

v3.2.0
forward.go:
	package uuid // import "github.com/gofrs/uuid"
	import "github.com/gofrs/uuid/v3"
	[…]
v3/go.mod:
	module "github.com/gofrs/uuid/v3"
	[…]
v3/codec.go:
	package uuid // import "github.com/gofrs/uuid/v3"
	[…]
v3/[…]

The two corresponding require statements for importers are then:

require (
	github.com/gofrs/uuid    v3.2.0+incompatible // for the non-v3 import path
	github.com/gofrs/uuid/v3 v3.2.0              // for the v3 import path
)

At that point you would effectively have two modules in the repository: one at the original import path (without a go.mod), and one at the new path. Users could update those two modules independently, but the non-v3 path would always forward to whatever version of the /v3 module is actually active.
(The v3.2.0+incompatible requirement sets the version of the forwarding package, not the version it forwards to.)


Following the major branch model, the require statements would need to refer to different commits:

v3.2.0
forward.go:
	package uuid // import "github.com/gofrs/uuid"
	import "github.com/gofrs/uuid/v3"
	[…]
v3.3.0
go.mod:
	module github.com/gofrs/uuid/v3
	[…]
codec.go
	package uuid // import "github.com/gofrs/uuid/v3"
	[…]
[…]
require (
	github.com/gofrs/uuid    v3.2.0+incompatible // for the non-v3 import path
	github.com/gofrs/uuid/v3 v3.3.0              // for the v3 import path
)

The user's view of the modules would be similar, except that they would see v3.2.0+incompatible as the last available version of the non-v3 path. (It would still forward to whichever version of the /v3 module is active.)


In both cases, note that adding another declaration to the forwarding package (without a go.mod file) is a breaking change: the forwarding package requires a version of /v3 that supplies a complete implementation, and without a go.mod file it has no way to declare that version.

(You could play some games with blank-imports and dummy modules to synthesize the right requirement graph for additions, but that seems excessive: if someone is updating their code to make use of a new part of the API, they may as well update the import path to /v3 in the process.)

@bcmills Thanks.

Using the major subdirectory model, would it be possible to do it the other way around? To have the contents of /v3 be forwarding declarations to the root package?

I dislike how if github.com/gofrs/uuid forwards everything to /v3, the godoc for uuid is incomplete, since it consists of forwarding declarations only. Meanwhile, the godoc for uuid/v3 is complete, but contains the unpleasant name mismatch between v3 and uuid.

Using the major subdirectory model, would it be possible to do it the other way around? To have the contents of /v3 be forwarding declarations to the root package?

The forwarding itself should work fine, but if you're doing active development in a part of the tree that lacks a go.mod file, you'll need to periodically bump the requirements in the /v3 module to pick up the changes. (Probably every time you push a new tag to the +incompatible path you'll want to add that same tag — and the corresponding require directive — to the v3/go.mod file.)

I dislike how if github.com/gofrs/uuid forwards everything to /v3, the godoc for uuid is incomplete, since it consists of forwarding declarations only.

The approach I'm taking with goforward is to explicitly duplicate the documentation comments in the forwarding package. (That feature accounts for a significant fraction of the complexity of the tool.) The duplication is a bit unfortunate, but (absent adding godoc logic to somehow propagate comments automatically) it seems preferable to dropping the documentation from one package or the other.

Meanwhile, the godoc for uuid/v3 is complete, but contains the unpleasant name mismatch between v3 and uuid.

Package paths that end in /vN will likely be much more common going forward, so I expect that Go users in general will learn to read uuid/v3 as “uuid” rather than “v3”.

@stanislas-m

@thepudds With pop, this is the other way. Pop is a non-module lib right now, and I don't expect it to migrate to go modules in the few weeks.

In case it helps, over the weekend I put together two more small runnable examples using the current state of the uuid repo, including flipping around the "mixed mode" example in Scenario 3 from my prior examples in #61 (comment) .

You can see the new examples here in this gist, if interested.

Scenario 5 in that gist is a simple scenario that is closer to what I think you were suggesting as an additional example in your comment above. For Scenario 5:

  • It is "mixed mode"
  • It has a module-based main package that imports uuid
  • The main package imports a non-module library that also imports uuid
  • The top-level build enables modules via GO111MODULE=on
  • It builds successfully
  • The versions of uuid included in this mixed build are:
    • github.com/gofrs/uuid v3.1.0+incompatible
    • github.com/gofrs/uuid/v3 v3.1.2
  • Given the import paths are different, the uuid types are different, e.g.:
    • "github.com/gofrs/uuid/v3".UUID vs.
    • "github.com/gofrs/uuid".UUID

Sorry for the delay in reading this; been a busy few weeks including a last-minute trip to Vegas for re:Invent. 😪

So it sounds like we need to remove the go.mod file from the 3.x line, mark those releases as unstable, put some messaging up on the README about intentionally breaking the github.com/gofrs/uuid/v3 import path and that we have no intent on explicitly supporting modules until the Go authors have a better story? Then later decide whether we want to cut a v4.0.0 tag with the go.mod file?

Edit: To be clear, I firmly believe in Rob Pike's "A little copying is better than a little dependency", but creating a separate directory for different code versions isn't acceptable to me.

What about the forwarding declarations? Did you catch that part of the proposed solution? Have you looked at c6f696b? Is this not acceptable either? Are we breaking current /v3 callers altogether?

@markbates Please don't apologize for being frustrated; it's on the Go authors for pushing modules on to us. I'm getting more and more frustrated trying to defend the choices to outsides / newcomers to Go, especially when I don't believe in it myself.

@acln0 I think the streams may have been crossed with my edit:

Edit: To be clear, I firmly believe in Rob Pike's "A little copying is better than a little dependency", but creating a separate directory for different code versions isn't acceptable to me.

You are proposing to intentionally break the builds of callers who are using the /v3 import path. Under any other circumstances, I don't think you would have agreed to a change that breaks someone's build intentionally. But in this particular case, you are letting your personal opinions about modules guide your reasoning on this matter, and you are going in a direction that I do not want to be a part of.

We made a mistake introducing the go.mod file as we did, and now we need to own up to that mistake, not break people's builds because of our personal opinions.

@acln0 Everyone here has commit access and can pursue anything they want. I am not a decider, gate-keeper, or benevolent dictator. I am offering my opinion of what I don't wish to support or pursue, if others in the Gofrs wish to purse that I am not a blocking voice.

And to that point, since it seems my dislike of modules has become so contentious to the Gofrs organization and my intent when starting the group, I hereby relieve myself of technical involvement in deciding in this issue and others.

In other words, move forward how y'all feel appropriate on this issue. Let me know what I can do to support that decision.

It's worth doing some homework on this to understand what exactly we're going to break and how. We should test with multiple toolchain versions, dep, glide, and with modules support.

We're also going to provide a detailed experience report to the Go authors if any interesting findings come out of it.

Hi @markbates,

After quite a bit of deliberation over the span of a month, we decided that the best thing to do to deal with this issue was to remove module support until it becomes more mature.

We realize that this is likely less than ideal for you. But this decision comes based on several factors:

  1. At this time, SIV does not support having one import that always resolves to latest for both module and non-module users without using type forwarding or subdirectories.
  2. Our view is the usage of version number subpackages and type forwarding is untenable for us, and we worry it will be a long term maintenance issue for the library while modules is in its experimental stage.
  3. Currently the behavior where the importer will inconsistently resolve to 3.1.0 or 3.1.2 depending on configuration must be addressed, and we need to return this library to a "stable" state as early as possible while impacting the least amount of people.

We feel that the best solution to address the above problems and provide a single canonical path that resolves to the latest version is to drop support for modules in the interim and release 3.2.0. This will allow all consumers on 3.2.0 to once again import without the /vN path and have version consistency. Module-based consumers will not be able to upgrade to 3.2.0 until they remove the /v3 from their gofrs import path, but this is the workflow that is most likely to signal to our user base of the decision to remove module support (for now).

The README update in #67 offers some additional context and what precisely will be conveyed to consumers of this dependency as they upgrade to v3.2.0 or beyond from the module supported versions.

Thanks for reporting this issue and your continued patience with us as we worked through this.

@DamareYoh Thank you for adding the summary here.

In the interest of full transparency because there's a lot of context in this issue: as modules becomes mature, and other things happen in the ecosystem to better support interoperability, we will adopt modules so that we are able to meet the needs of the community. As is probably clear, TBD on a lot of that.