go: embed: go:embed produces error when "go.mod" has go version < 1.16

What version of Go are you using (go version)?

$ go version
go version go1.16rc1 darwin/arm64

Does this issue reproduce with the latest release?

with the rc release? yes.

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
go version go1.16rc1 darwin/arm64
lestrrat@finch jwk % go env
GO111MODULE="on"
GOARCH="arm64"
GOBIN=""
GOCACHE="/Users/lestrrat/Library/Caches/go-build"
GOENV="/Users/lestrrat/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="arm64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/lestrrat/dev/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/lestrrat/dev"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_arm64"
GOVCS=""
GOVERSION="go1.16rc1"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/lestrrat/dev/src/github.com/lestrrat-go/jwx/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/vb/gf1mcqdd2s509731hrnnvkjm0000gn/T/go-build3140323037=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

Using github.com/lestrrat-go/jwx@09b6dea (note that it’s at a tracking branch)

lestrrat@finch jwx % cd jwk
lestrrat@finch jwk % go test               

What did you expect to see?

lestrrat@finch jwx % cd jwk
lestrrat@finch jwk % go test   
PASS
ok  	github.com/lestrrat-go/jwx/jwk	5.437s
lestrrat@finch jwk % 

What did you see instead?

lestrrat@finch jwx % cd jwk
lestrrat@finch jwk % go test               
# github.com/lestrrat-go/jwx/jwk_test [github.com/lestrrat-go/jwx/jwk.test]
./fs_test.go:13:3: go:embed requires go1.16 or later (-lang was set to go1.13; check go.mod)
FAIL	github.com/lestrrat-go/jwx/jwk [build failed]
lestrrat@finch jwk % 

Discussion

My go.mod contains the following

lestrrat@finch jwk % cat ../go.mod
module github.com/lestrrat-go/jwx

go 1.13

require (
	github.com/goccy/go-json v0.3.4
	github.com/lestrrat-go/backoff/v2 v2.0.7
	github.com/lestrrat-go/codegen v1.0.0
	github.com/lestrrat-go/httpcc v1.0.0
	github.com/lestrrat-go/iter v1.0.0
	github.com/lestrrat-go/option v1.0.0
	github.com/lestrrat-go/pdebug/v3 v3.0.1
	github.com/pkg/errors v0.9.1
	github.com/stretchr/testify v1.6.1
	golang.org/dl v0.0.0-20210128152142-7744cb1878f1 // indirect
	golang.org/x/crypto v0.0.0-20201217014255-9d1352758620
	golang.org/x/mod v0.4.1 // indirect
	golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963
)

If I change the line go 1.13 to go 1.16, then it works on go1.16rc1

lestrrat@finch jwk % go test
# github.com/lestrrat-go/jwx/jwk_test [github.com/lestrrat-go/jwx/jwk.test]
./fs_test.go:13:3: go:embed requires go1.16 or later (-lang was set to go1.13; check go.mod)
FAIL	github.com/lestrrat-go/jwx/jwk [build failed]
lestrrat@finch jwk % go mod edit -go 1.16
lestrrat@finch jwk % go test             
PASS
ok  	github.com/lestrrat-go/jwx/jwk	5.648s
lestrrat@finch jwk % 

go1.16beta1 worked just fine.

lestrrat@finch jwk % go install golang.org/dl/go1.16beta1
lestrrat@finch jwk % go1.16beta1 download
go1.16beta1: already downloaded in /Users/lestrrat/sdk/go1.16beta1
lestrrat@finch jwk % go1.16beta1 test .
ok  	github.com/lestrrat-go/jwx/jwk	6.066s
lestrrat@finch jwk % 

Shouldn’t features like this just depend on the runtime version, and not contents of go.mod?

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 32 (14 by maintainers)

Commits related to this issue

Most upvoted comments

The expectation is that you don’t use embed until you are ready to bump your users forward. Build tags are not sufficient, precisely because of tools that need to analyze everything, such as go mod tidy. The same thing happens (or will happen) with any language change gated by the go.mod version.

This is intentional, because a //go:embed comment will only work correctly with Go 1.16 or later. A //go:embed comment requires importing the “embed” package, which is only available in Go 1.16. So compiling a package with an earlier language version will fail.

As a developer I often rely on the compiler to tell me when I have broken the code. When I change the signature of a function for instance, the compiler will tell me if I have neglected to fix any call sites.

The fact that it will fail on errors on files guarded by build tags is a good thing. I develop on an intel machine. If my change breaks the build on s390 machines, the compiler will tell me. If it didn’t I might end up shipping code which failed to compile on my first s390 user.

Of course people will tell me I should make sure that my CI system builds and test on every possible permutation of supported architectures, OSes, compiler versions etc. And they may be right. But it is far better that compiler tells me about these problems straight away.

If this issue was “fixed” by allowing the code above to compile without error, it would make development and testing far harder. Please don’t do it.

To @lestrrat, my suggestion would be: if you really have to support Go versions < 1.16, then don’t use embed. In general I would aggressively kill off support for old Go versions, and only use language features and APIs available on the oldest Go version you do support.

The confusion is in the fact that we (well, at least I) have used build tags to switch between certain features that were newly introduced. For example, I have the following successfully compiling in go1.16rc1

// +build go1.15

package ecutil

import (
  "math/big"
)

func bigIntFillBytes(v *big.Int, buf []byte) []byte {
  v.FillBytes(buf)
  return buf
}
// +build !go1.15

package ecutil

import (
  "math/big"
)

func bigIntFillBytes(v *big.Int, buf []byte) []byte {
  data := v.Bytes()
  if len(data) > len(buf) {
    panic("ecutil: invalid call to bigIntFillBytes (len(data) > len(buf))")
  }

  copy(buf[len(buf)-len(data):], data)
  return buf
}
// go.mod
module ...
go 1.13 // FillBytes doesn't exist in 1.13, let alone 1.14

In this scenario, I was able to provide support for go 1.14 that doesn’t have (math/big).FillBytes() while utilizing the new feature that only appeared in go 1.15

At first glance I fail to see the difference between the above and below

// +build go1.16
import "embed"
// +build !go1.16
// (code without embed)
// go.mod file
module ...
go 1.15 // kaput

(math/big).Int.FillBytes doesn’t exist in go 1.14, why should it compile, when import "embed" doesn’t? I still think this is inconsistent, but I closed this issue because I realized that this was not something new in go1.16 (and hence unlikely to be changed).

Hope that clears up my reasoning on this issue, at least.

I believe that the build tag solution now works correctly with 1.15.11 - go mod tidy is now resilient to stdlib imports which don’t exist.

For the record, you can work around this issue by declaring go 1.16 in your go.mod file and using build tags to guard the use of go:embed. For example:

// +build go1.16

package m

import (
	_ "embed"
	"fmt"
)

//go:embed hello.txt
var s string

Funny enough, I can “workaround” the issue by removing go 1.11 statement from library’s go.mod. 😃

It would be great if there is an official document to list the features each version supports (and to explain features and functionalities are different).

https://tip.golang.org/doc/go1.16 explains which new features were added to go1.16 which are not in go1.15. Once 1.16 is released, anything before 1.15 becomes unsupported.

If we try to “help” our users by maintaining a table mapping features to versions, this will in effect be supporting unsupported versions of Go and telling people that it is OK to use them. This effort will be far better spent encouraging the community to upgrade to the current tools.

Having spent little time playing around with //go:embed and encountering the error myself, I would say that error message is simple, clear, and reasonable.

If you want to use a language feature introduced in Go 1.16, you will need tools >= 1.16 and version 1.16 or greater specified in your go.mod. I may be missing something, but I can’t think of any scenario where someone would want to add embedding while keeping the Go version specified as 1.15 or less.

I have learned that go mod tidy on 1.15 which uses io/fs, regardless of build tags, will fail. Fair enough! Closing.

For reference, that’s #40067, and it even affects users of modules that try and use new standard library packages.

No module can successfully guard its use of a new standard library package with a build tag and have it not still break downstream users with older Go versions. (I expect this to start happening pretty often now that everyone is excited for embed, unlike when maphash was added.)