mirror of
https://github.com/docker/cli.git
synced 2026-08-24 10:05:37 -05:00
This package imports all "importable" packages, i.e., packages that:
- are not applications ("main")
- are not internal
- and that have non-test go-files
We do this to verify that our code can be consumed as a dependency
in "module mode". When using a dependency that does not have a go.mod
(i.e.; is not a "module"), go implicitly generates a go.mod. Lacking
information from the dependency itself, it assumes "go1.16" language
(see [DefaultGoModVersion]). Starting with Go1.21, go downgrades the
language version used for such dependencies, which means that any
language feature used that is not supported by go1.16 results in a
compile error;
# github.com/docker/cli/cli/context/store
/go/pkg/mod/github.com/docker/cli@v25.0.0-beta.2+incompatible/cli/context/store/storeconfig.go:6:24: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
/go/pkg/mod/github.com/docker/cli@v25.0.0-beta.2+incompatible/cli/context/store/store.go:74:12: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
These errors do NOT occur when using GOPATH mode, nor do they occur
when using "pseudo module mode" (the "-mod=mod -modfile=vendor.mod"
approach used in this repository).
As a workaround for this situation, we must include "//go:build" comments
in any file that uses newer go-language features (such as the "any" type
or the "min()", "max()" builtins).
From the go toolchain docs (https://go.dev/doc/toolchain):
> The go line for each module sets the language version the compiler enforces
> when compiling packages in that module. The language version can be changed
> on a per-file basis by using a build constraint.
>
> For example, a module containing code that uses the Go 1.21 language version
> should have a go.mod file with a go line such as go 1.21 or go 1.21.3.
> If a specific source file should be compiled only when using a newer Go
> toolchain, adding //go:build go1.22 to that source file both ensures that
> only Go 1.22 and newer toolchains will compile the file and also changes
> the language version in that file to Go 1.22.
This file is a generated module that imports all packages provided in
the repository, which replicates an external consumer using our code
as a dependency in go-module mode, and verifies all files in those
packages have the correct "//go:build <go language version>" set.
To test this package:
make shell
make -C ./internal/gocompat/
make: Entering directory '/go/src/github.com/docker/cli/internal/gocompat'
GO111MODULE=off go generate .
GO111MODULE=on go mod tidy
GO111MODULE=on go test -v
# github.com/docker/cli/templates
../../templates/templates.go:13:17: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
# github.com/docker/cli/cli/compose/template
../../cli/compose/template/template.go:98:45: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/compose/template/template.go:105:27: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/compose/template/template.go:141:28: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
# github.com/docker/cli/cli/compose/types
../../cli/compose/types/types.go:53:22: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/compose/types/types.go:86:34: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/compose/types/types.go:105:22: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/compose/types/types.go:137:34: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/compose/types/types.go:211:20: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/compose/types/types.go:343:35: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/compose/types/types.go:442:40: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/compose/types/types.go:469:24: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/compose/types/types.go:490:24: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/compose/types/types.go:587:28: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/compose/types/types.go:442:40: too many errors
# github.com/docker/cli/cli/context/store
../../cli/context/store/storeconfig.go:6:24: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/context/store/store.go:74:12: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/context/store/store.go:75:23: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/context/store/metadatastore.go:43:58: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/context/store/metadatastore.go:48:22: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/context/store/metadatastore.go:80:30: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
# github.com/docker/cli/cli/command/idresolver
../../cli/command/idresolver/idresolver.go:6:2: "github.com/docker/docker/api/types" imported and not used
../../cli/command/idresolver/idresolver.go:7:2: "github.com/docker/docker/api/types/swarm" imported and not used
../../cli/command/idresolver/idresolver.go:9:2: "github.com/pkg/errors" imported and not used
../../cli/command/idresolver/idresolver.go:28:49: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/command/idresolver/idresolver.go:58:53: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
# github.com/docker/cli/cli/compose/schema
../../cli/compose/schema/schema.go:20:46: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/compose/schema/schema.go:27:53: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/compose/schema/schema.go:45:32: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
../../cli/compose/schema/schema.go:66:33: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
FAIL gocompat [build failed]
make: *** [Makefile:3: verify] Error 1
make: Leaving directory '/go/src/github.com/docker/cli/internal/gocompat'
[DefaultGoModVersion]: https://github.com/golang/go/blob/58c28ba286dd0e98fe4cca80f5d64bbcb824a685/src/cmd/go/internal/gover/version.go#L15-L24
[2]: https://go.dev/doc/toolchain
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
200 lines
5.8 KiB
Go
200 lines
5.8 KiB
Go
//go:build ignore
|
|
// +build ignore
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"golang.org/x/mod/modfile"
|
|
)
|
|
|
|
func main() {
|
|
if err := generateApp(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
rootDir := "../../"
|
|
if err := generateModule(rootDir); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func generateApp() error {
|
|
cmd := exec.Command("go", "list", "-find", "-f", `{{- if ne .Name "main"}}{{if .GoFiles}}{{.ImportPath}}{{end}}{{end -}}`, "../../...")
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("go list failed: %w\nOutput:\n%s", err, string(out))
|
|
}
|
|
|
|
var pkgs []string
|
|
for _, p := range strings.Split(string(out), "\n") {
|
|
if strings.TrimSpace(p) == "" || strings.Contains(p, "/internal") {
|
|
continue
|
|
}
|
|
pkgs = append(pkgs, p)
|
|
}
|
|
tmpl, err := template.New("main").Parse(appTemplate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
err = tmpl.Execute(&buf, appContext{Generator: cmd.String(), Packages: pkgs})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile("main_test.go", buf.Bytes(), 0o644)
|
|
}
|
|
|
|
func generateModule(rootDir string) (retErr error) {
|
|
modFile := filepath.Join(rootDir, "go.mod")
|
|
_, err := os.Stat(modFile)
|
|
if err == nil {
|
|
return errors.New("go.mod exists in the repository root")
|
|
}
|
|
if !errors.Is(err, os.ErrNotExist) {
|
|
return fmt.Errorf("failed to stat go.mod: %w", err)
|
|
}
|
|
|
|
// create an empty go.mod without go version.
|
|
//
|
|
// this go.mod must exist when running the test.
|
|
err = os.WriteFile(modFile, []byte("module github.com/docker/cli\n"), 0o644)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write go.mod: %w", err)
|
|
}
|
|
defer func() {
|
|
if retErr != nil {
|
|
_ = os.Remove(modFile)
|
|
}
|
|
}()
|
|
|
|
content, err := os.ReadFile(filepath.Join(rootDir, "vendor.mod"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mod, err := modfile.Parse(filepath.Join(rootDir, "vendor.mod"), content, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := mod.AddModuleStmt("example.com/gocompat"); err != nil {
|
|
return err
|
|
}
|
|
if err := mod.AddReplace("github.com/docker/cli", "", rootDir, ""); err != nil {
|
|
return err
|
|
}
|
|
if err := mod.AddGoStmt("1.24"); err != nil {
|
|
return err
|
|
}
|
|
out, err := mod.Format()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tmpl, err := template.New("mod").Parse(modTemplate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
gen, err := os.Executable()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
err = tmpl.Execute(&buf, appContext{Generator: gen, Dependencies: string(out)})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile("go.mod", buf.Bytes(), 0o644)
|
|
}
|
|
|
|
type appContext struct {
|
|
Generator string
|
|
Packages []string
|
|
Dependencies string
|
|
}
|
|
|
|
const appTemplate = `// Code generated by "{{ .Generator }}". DO NOT EDIT.
|
|
|
|
package main_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
// Import all importable packages, i.e., packages that:
|
|
//
|
|
// - are not applications ("main")
|
|
// - are not internal
|
|
// - and that have non-test go-files
|
|
{{- range .Packages }}
|
|
_ "{{ . }}"
|
|
{{- end}}
|
|
)
|
|
|
|
// This file imports all "importable" packages, i.e., packages that:
|
|
//
|
|
// - are not applications ("main")
|
|
// - are not internal
|
|
// - and that have non-test go-files
|
|
//
|
|
// We do this to verify that our code can be consumed as a dependency
|
|
// in "module mode". When using a dependency that does not have a go.mod
|
|
// (i.e.; is not a "module"), go implicitly generates a go.mod. Lacking
|
|
// information from the dependency itself, it assumes "go1.16" language
|
|
// (see [DefaultGoModVersion]). Starting with Go1.21, go downgrades the
|
|
// language version used for such dependencies, which means that any
|
|
// language feature used that is not supported by go1.16 results in a
|
|
// compile error;
|
|
//
|
|
// # github.com/docker/cli/cli/context/store
|
|
// /go/pkg/mod/github.com/docker/cli@v25.0.0-beta.2+incompatible/cli/context/store/storeconfig.go:6:24: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
|
|
// /go/pkg/mod/github.com/docker/cli@v25.0.0-beta.2+incompatible/cli/context/store/store.go:74:12: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod)
|
|
//
|
|
// These errors do NOT occur when using GOPATH mode, nor do they occur
|
|
// when using "pseudo module mode" (the "-mod=mod -modfile=vendor.mod"
|
|
// approach used in this repository).
|
|
//
|
|
// As a workaround for this situation, we must include "//go:build" comments
|
|
// in any file that uses newer go-language features (such as the "any" type
|
|
// or the "min()", "max()" builtins).
|
|
//
|
|
// From the go toolchain docs (https://go.dev/doc/toolchain):
|
|
//
|
|
// > The go line for each module sets the language version the compiler enforces
|
|
// > when compiling packages in that module. The language version can be changed
|
|
// > on a per-file basis by using a build constraint.
|
|
// >
|
|
// > For example, a module containing code that uses the Go 1.21 language version
|
|
// > should have a go.mod file with a go line such as go 1.21 or go 1.21.3.
|
|
// > If a specific source file should be compiled only when using a newer Go
|
|
// > toolchain, adding //go:build go1.22 to that source file both ensures that
|
|
// > only Go 1.22 and newer toolchains will compile the file and also changes
|
|
// > the language version in that file to Go 1.22.
|
|
//
|
|
// This file is a generated module that imports all packages provided in
|
|
// the repository, which replicates an external consumer using our code
|
|
// as a dependency in go-module mode, and verifies all files in those
|
|
// packages have the correct "//go:build <go language version>" set.
|
|
//
|
|
// [DefaultGoModVersion]: https://github.com/golang/go/blob/58c28ba286dd0e98fe4cca80f5d64bbcb824a685/src/cmd/go/internal/gover/version.go#L15-L24
|
|
// [2]: https://go.dev/doc/toolchain
|
|
func TestModuleCompatibility(t *testing.T) {
|
|
t.Log("all packages have the correct go version specified through //go:build")
|
|
}
|
|
`
|
|
|
|
const modTemplate = `// Code generated by "{{ .Generator }}". DO NOT EDIT.
|
|
|
|
{{.Dependencies}}
|
|
`
|