Codegens: Do not auto-set boilerplate path

Make each invocation pass it explicitly.  This will make later commits
cleaner.
This commit is contained in:
Tim Hockin 2022-09-17 17:28:44 -07:00
parent afebf498d7
commit 436bebf47d
18 changed files with 29 additions and 79 deletions

View File

@ -133,6 +133,9 @@ endif
# #
# This is the header we stick at the top of every generated file.
BOILERPLATE_FILENAME := staging/src/k8s.io/code-generator/hack/boilerplate.go.txt
# prerelease-lifecycle generation # prerelease-lifecycle generation
# #
# Any package that wants prerelease-lifecycle functions generated must include a # Any package that wants prerelease-lifecycle functions generated must include a
@ -179,6 +182,7 @@ gen_prerelease_lifecycle: $(PRERELEASE_LIFECYCLE_GEN) $(META_DIR)/$(PRERELEASE_L
./hack/run-in-gopath.sh $(PRERELEASE_LIFECYCLE_GEN) \ ./hack/run-in-gopath.sh $(PRERELEASE_LIFECYCLE_GEN) \
--v $(KUBE_VERBOSE) \ --v $(KUBE_VERBOSE) \
--logtostderr \ --logtostderr \
-h $(BOILERPLATE_FILENAME) \
-i "$$pkgs" \ -i "$$pkgs" \
-O $(PRERELEASE_LIFECYCLE_BASENAME) \ -O $(PRERELEASE_LIFECYCLE_BASENAME) \
"$$@"; \ "$$@"; \
@ -274,6 +278,7 @@ gen_deepcopy: $(DEEPCOPY_GEN) $(META_DIR)/$(DEEPCOPY_GEN).todo
./hack/run-in-gopath.sh $(DEEPCOPY_GEN) \ ./hack/run-in-gopath.sh $(DEEPCOPY_GEN) \
--v $(KUBE_VERBOSE) \ --v $(KUBE_VERBOSE) \
--logtostderr \ --logtostderr \
-h $(BOILERPLATE_FILENAME) \
-i "$$pkgs" \ -i "$$pkgs" \
--bounding-dirs $(PRJ_SRC_PATH),"k8s.io/api" \ --bounding-dirs $(PRJ_SRC_PATH),"k8s.io/api" \
-O $(DEEPCOPY_BASENAME) \ -O $(DEEPCOPY_BASENAME) \
@ -378,6 +383,7 @@ gen_defaulter: $(DEFAULTER_GEN) $(META_DIR)/$(DEFAULTER_GEN).todo
./hack/run-in-gopath.sh $(DEFAULTER_GEN) \ ./hack/run-in-gopath.sh $(DEFAULTER_GEN) \
--v $(KUBE_VERBOSE) \ --v $(KUBE_VERBOSE) \
--logtostderr \ --logtostderr \
-h $(BOILERPLATE_FILENAME) \
-i "$$pkgs" \ -i "$$pkgs" \
--extra-peer-dirs $$(echo $(DEFAULTER_EXTRA_PEER_PKGS) | sed 's/ /,/g') \ --extra-peer-dirs $$(echo $(DEFAULTER_EXTRA_PEER_PKGS) | sed 's/ /,/g') \
-O $(DEFAULTER_BASENAME) \ -O $(DEFAULTER_BASENAME) \
@ -494,6 +500,7 @@ gen_conversion: $(CONVERSION_GEN) $(META_DIR)/$(CONVERSION_GEN).todo
--extra-dirs $$(echo $(CONVERSION_EXTRA_PKGS) | sed 's/ /,/g') \ --extra-dirs $$(echo $(CONVERSION_EXTRA_PKGS) | sed 's/ /,/g') \
--v $(KUBE_VERBOSE) \ --v $(KUBE_VERBOSE) \
--logtostderr \ --logtostderr \
-h $(BOILERPLATE_FILENAME) \
-i "$$pkgs" \ -i "$$pkgs" \
-O $(CONVERSION_BASENAME) \ -O $(CONVERSION_BASENAME) \
"$$@"; \ "$$@"; \
@ -546,7 +553,6 @@ $(CONVERSION_GEN): $(GODEPS_k8s.io/kubernetes/vendor/k8s.io/code-generator/cmd/c
# The result file, in each pkg, of open-api generation. # The result file, in each pkg, of open-api generation.
OPENAPI_BASENAME := $(GENERATED_FILE_PREFIX)openapi OPENAPI_BASENAME := $(GENERATED_FILE_PREFIX)openapi
OPENAPI_FILENAME := $(OPENAPI_BASENAME).go OPENAPI_FILENAME := $(OPENAPI_BASENAME).go
BOILERPLATE_FILENAME := vendor/k8s.io/code-generator/hack/boilerplate.go.txt
IGNORED_REPORT_FILENAME := $(OUT_DIR)/ignored_violations.report IGNORED_REPORT_FILENAME := $(OUT_DIR)/ignored_violations.report
API_RULE_CHECK_FAILURE_MESSAGE = "ERROR: \n\t $(1) API rule check failed. Reported violations differ from known violations. Please read api/api-rules/README.md to resolve the failure in $(2). \n" API_RULE_CHECK_FAILURE_MESSAGE = "ERROR: \n\t $(1) API rule check failed. Reported violations differ from known violations. Please read api/api-rules/README.md to resolve the failure in $(2). \n"
@ -633,10 +639,10 @@ $$($(prefix)_OPENAPI_OUTFILE): $$(OPENAPI_GEN) $$($(prefix)_KNOWN_VIOLATION_FILE
./hack/run-in-gopath.sh $$(OPENAPI_GEN) \ ./hack/run-in-gopath.sh $$(OPENAPI_GEN) \
--v $$(KUBE_VERBOSE) \ --v $$(KUBE_VERBOSE) \
--logtostderr \ --logtostderr \
-h $$(BOILERPLATE_FILENAME) \
-i $$$$(echo $$(addprefix $(PRJ_SRC_PATH)/, $$($(prefix)_OPENAPI_DIRS)) | sed 's/ /,/g') \ -i $$$$(echo $$(addprefix $(PRJ_SRC_PATH)/, $$($(prefix)_OPENAPI_DIRS)) | sed 's/ /,/g') \
-p $$(PRJ_SRC_PATH)/$$($(prefix)_OPENAPI_OUTPUT_PKG) \ -p $$(PRJ_SRC_PATH)/$$($(prefix)_OPENAPI_OUTPUT_PKG) \
-O $$(OPENAPI_BASENAME) \ -O $$(OPENAPI_BASENAME) \
-h $$(BOILERPLATE_FILENAME) \
-r $$($(prefix)_REPORT_FILENAME) \ -r $$($(prefix)_REPORT_FILENAME) \
"$$$$@" "$$$$@"
test -f $$($(prefix)_KNOWN_VIOLATION_FILENAME) || touch $$($(prefix)_KNOWN_VIOLATION_FILENAME) test -f $$($(prefix)_KNOWN_VIOLATION_FILENAME) || touch $$($(prefix)_KNOWN_VIOLATION_FILENAME)

View File

@ -0,0 +1,16 @@
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

View File

@ -28,7 +28,9 @@ CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-
bash "${CODEGEN_PKG}/generate-groups.sh" all \ bash "${CODEGEN_PKG}/generate-groups.sh" all \
k8s.io/apiextensions-apiserver/examples/client-go/pkg/client k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis \ k8s.io/apiextensions-apiserver/examples/client-go/pkg/client k8s.io/apiextensions-apiserver/examples/client-go/pkg/apis \
cr:v1 \ cr:v1 \
--output-base "$(dirname "${BASH_SOURCE[0]}")/../../../../.." --output-base "$(dirname "${BASH_SOURCE[0]}")/../../../../.." \
--go-header-file "${SCRIPT_ROOT}/hack/boilerplate.go.txt"
# To use your own boilerplate text append: # To use your own boilerplate text append:
# --go-header-file ${SCRIPT_ROOT}/hack/custom-boilerplate.go.txt # --go-header-file ${SCRIPT_ROOT}/hack/custom-boilerplate.go.txt

View File

@ -25,13 +25,11 @@ import (
generatorargs "k8s.io/code-generator/cmd/applyconfiguration-gen/args" generatorargs "k8s.io/code-generator/cmd/applyconfiguration-gen/args"
"k8s.io/code-generator/cmd/applyconfiguration-gen/generators" "k8s.io/code-generator/cmd/applyconfiguration-gen/generators"
"k8s.io/code-generator/pkg/util"
) )
func main() { func main() {
klog.InitFlags(nil) klog.InitFlags(nil)
genericArgs, customArgs := generatorargs.NewDefaults() genericArgs, customArgs := generatorargs.NewDefaults()
genericArgs.GoHeaderFilePath = util.BoilerplatePath()
genericArgs.AddFlags(pflag.CommandLine) genericArgs.AddFlags(pflag.CommandLine)
customArgs.AddFlags(pflag.CommandLine, "k8s.io/kubernetes/pkg/apis") // TODO: move this input path out of applyconfiguration-gen customArgs.AddFlags(pflag.CommandLine, "k8s.io/kubernetes/pkg/apis") // TODO: move this input path out of applyconfiguration-gen
if err := flag.Set("logtostderr", "true"); err != nil { if err := flag.Set("logtostderr", "true"); err != nil {

View File

@ -34,7 +34,6 @@ func main() {
// Override defaults. // Override defaults.
// TODO: move this out of client-gen // TODO: move this out of client-gen
genericArgs.GoHeaderFilePath = util.BoilerplatePath()
genericArgs.OutputPackagePath = "k8s.io/kubernetes/pkg/client/clientset_generated/" genericArgs.OutputPackagePath = "k8s.io/kubernetes/pkg/client/clientset_generated/"
genericArgs.AddFlags(pflag.CommandLine) genericArgs.AddFlags(pflag.CommandLine)

View File

@ -102,17 +102,12 @@ import (
generatorargs "k8s.io/code-generator/cmd/conversion-gen/args" generatorargs "k8s.io/code-generator/cmd/conversion-gen/args"
"k8s.io/code-generator/cmd/conversion-gen/generators" "k8s.io/code-generator/cmd/conversion-gen/generators"
"k8s.io/code-generator/pkg/util"
) )
func main() { func main() {
klog.InitFlags(nil) klog.InitFlags(nil)
genericArgs, customArgs := generatorargs.NewDefaults() genericArgs, customArgs := generatorargs.NewDefaults()
// Override defaults.
// TODO: move this out of conversion-gen
genericArgs.GoHeaderFilePath = util.BoilerplatePath()
genericArgs.AddFlags(pflag.CommandLine) genericArgs.AddFlags(pflag.CommandLine)
customArgs.AddFlags(pflag.CommandLine) customArgs.AddFlags(pflag.CommandLine)
flag.Set("logtostderr", "true") flag.Set("logtostderr", "true")

View File

@ -53,17 +53,12 @@ import (
"k8s.io/klog/v2" "k8s.io/klog/v2"
generatorargs "k8s.io/code-generator/cmd/deepcopy-gen/args" generatorargs "k8s.io/code-generator/cmd/deepcopy-gen/args"
"k8s.io/code-generator/pkg/util"
) )
func main() { func main() {
klog.InitFlags(nil) klog.InitFlags(nil)
genericArgs, customArgs := generatorargs.NewDefaults() genericArgs, customArgs := generatorargs.NewDefaults()
// Override defaults.
// TODO: move this out of deepcopy-gen
genericArgs.GoHeaderFilePath = util.BoilerplatePath()
genericArgs.AddFlags(pflag.CommandLine) genericArgs.AddFlags(pflag.CommandLine)
customArgs.AddFlags(pflag.CommandLine) customArgs.AddFlags(pflag.CommandLine)
flag.Set("logtostderr", "true") flag.Set("logtostderr", "true")

View File

@ -49,17 +49,12 @@ import (
"k8s.io/klog/v2" "k8s.io/klog/v2"
generatorargs "k8s.io/code-generator/cmd/defaulter-gen/args" generatorargs "k8s.io/code-generator/cmd/defaulter-gen/args"
"k8s.io/code-generator/pkg/util"
) )
func main() { func main() {
klog.InitFlags(nil) klog.InitFlags(nil)
genericArgs, customArgs := generatorargs.NewDefaults() genericArgs, customArgs := generatorargs.NewDefaults()
// Override defaults.
// TODO: move this out of defaulter-gen
genericArgs.GoHeaderFilePath = util.BoilerplatePath()
genericArgs.AddFlags(pflag.CommandLine) genericArgs.AddFlags(pflag.CommandLine)
customArgs.AddFlags(pflag.CommandLine) customArgs.AddFlags(pflag.CommandLine)
flag.Set("logtostderr", "true") flag.Set("logtostderr", "true")

View File

@ -30,7 +30,6 @@ import (
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
"k8s.io/code-generator/pkg/util"
"k8s.io/gengo/args" "k8s.io/gengo/args"
"k8s.io/gengo/generator" "k8s.io/gengo/generator"
"k8s.io/gengo/namer" "k8s.io/gengo/namer"
@ -56,8 +55,7 @@ type Generator struct {
func New() *Generator { func New() *Generator {
sourceTree := args.DefaultSourceTree() sourceTree := args.DefaultSourceTree()
common := args.GeneratorArgs{ common := args.GeneratorArgs{
OutputBase: sourceTree, OutputBase: sourceTree,
GoHeaderFilePath: util.BoilerplatePath(),
} }
defaultProtoImport := filepath.Join(sourceTree, "k8s.io", "kubernetes", "vendor", "github.com", "gogo", "protobuf", "protobuf") defaultProtoImport := filepath.Join(sourceTree, "k8s.io", "kubernetes", "vendor", "github.com", "gogo", "protobuf", "protobuf")
cwd, err := os.Getwd() cwd, err := os.Getwd()

View File

@ -21,7 +21,6 @@ import (
"os" "os"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"k8s.io/code-generator/pkg/util"
"k8s.io/gengo/args" "k8s.io/gengo/args"
"k8s.io/gengo/examples/import-boss/generators" "k8s.io/gengo/examples/import-boss/generators"
@ -32,8 +31,6 @@ func main() {
klog.InitFlags(nil) klog.InitFlags(nil)
arguments := args.Default() arguments := args.Default()
// Override defaults.
arguments.GoHeaderFilePath = util.BoilerplatePath()
pflag.CommandLine.BoolVar(&arguments.IncludeTestFiles, "include-test-files", false, "If true, include *_test.go files.") pflag.CommandLine.BoolVar(&arguments.IncludeTestFiles, "include-test-files", false, "If true, include *_test.go files.")
if err := arguments.Execute( if err := arguments.Execute(

View File

@ -33,7 +33,6 @@ func main() {
// Override defaults. // Override defaults.
// TODO: move out of informer-gen // TODO: move out of informer-gen
genericArgs.GoHeaderFilePath = util.BoilerplatePath()
genericArgs.OutputPackagePath = "k8s.io/kubernetes/pkg/client/informers/informers_generated" genericArgs.OutputPackagePath = "k8s.io/kubernetes/pkg/client/informers/informers_generated"
customArgs.VersionedClientSetPackage = "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" customArgs.VersionedClientSetPackage = "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
customArgs.InternalClientSetPackage = "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" customArgs.InternalClientSetPackage = "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"

View File

@ -33,7 +33,6 @@ func main() {
// Override defaults. // Override defaults.
// TODO: move this out of lister-gen // TODO: move this out of lister-gen
genericArgs.GoHeaderFilePath = util.BoilerplatePath()
genericArgs.OutputPackagePath = "k8s.io/kubernetes/pkg/client/listers" genericArgs.OutputPackagePath = "k8s.io/kubernetes/pkg/client/listers"
genericArgs.AddFlags(pflag.CommandLine) genericArgs.AddFlags(pflag.CommandLine)

View File

@ -40,7 +40,6 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
generatorargs "k8s.io/code-generator/cmd/prerelease-lifecycle-gen/args" generatorargs "k8s.io/code-generator/cmd/prerelease-lifecycle-gen/args"
statusgenerators "k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators" statusgenerators "k8s.io/code-generator/cmd/prerelease-lifecycle-gen/prerelease-lifecycle-generators"
"k8s.io/code-generator/pkg/util"
"k8s.io/klog/v2" "k8s.io/klog/v2"
) )
@ -48,10 +47,6 @@ func main() {
klog.InitFlags(nil) klog.InitFlags(nil)
genericArgs, customArgs := generatorargs.NewDefaults() genericArgs, customArgs := generatorargs.NewDefaults()
// Override defaults.
// TODO: move this out of prerelease-lifecycle-gen
genericArgs.GoHeaderFilePath = util.BoilerplatePath()
genericArgs.AddFlags(pflag.CommandLine) genericArgs.AddFlags(pflag.CommandLine)
customArgs.AddFlags(pflag.CommandLine) customArgs.AddFlags(pflag.CommandLine)
flag.Set("logtostderr", "true") flag.Set("logtostderr", "true")

View File

@ -24,13 +24,11 @@ import (
generatorargs "k8s.io/code-generator/cmd/register-gen/args" generatorargs "k8s.io/code-generator/cmd/register-gen/args"
"k8s.io/code-generator/cmd/register-gen/generators" "k8s.io/code-generator/cmd/register-gen/generators"
"k8s.io/code-generator/pkg/util"
) )
func main() { func main() {
klog.InitFlags(nil) klog.InitFlags(nil)
genericArgs := generatorargs.NewDefaults() genericArgs := generatorargs.NewDefaults()
genericArgs.GoHeaderFilePath = util.BoilerplatePath()
genericArgs.AddFlags(pflag.CommandLine) genericArgs.AddFlags(pflag.CommandLine)
flag.Set("logtostderr", "true") flag.Set("logtostderr", "true")
pflag.CommandLine.AddGoFlagSet(flag.CommandLine) pflag.CommandLine.AddGoFlagSet(flag.CommandLine)

View File

@ -27,7 +27,6 @@ package main
import ( import (
"os" "os"
"k8s.io/code-generator/pkg/util"
"k8s.io/gengo/args" "k8s.io/gengo/args"
"k8s.io/gengo/examples/set-gen/generators" "k8s.io/gengo/examples/set-gen/generators"
@ -39,7 +38,6 @@ func main() {
arguments := args.Default() arguments := args.Default()
// Override defaults. // Override defaults.
arguments.GoHeaderFilePath = util.BoilerplatePath()
arguments.InputDirs = []string{"k8s.io/kubernetes/pkg/util/sets/types"} arguments.InputDirs = []string{"k8s.io/kubernetes/pkg/util/sets/types"}
arguments.OutputPackagePath = "k8s.io/apimachinery/pkg/util/sets" arguments.OutputPackagePath = "k8s.io/apimachinery/pkg/util/sets"

View File

@ -8,7 +8,6 @@ require (
github.com/gogo/protobuf v1.3.2 github.com/gogo/protobuf v1.3.2
github.com/google/gnostic v0.5.7-v3refs github.com/google/gnostic v0.5.7-v3refs
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
golang.org/x/tools v0.1.12
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
k8s.io/gengo v0.0.0-20220902162205-c0856e24416d k8s.io/gengo v0.0.0-20220902162205-c0856e24416d
k8s.io/klog/v2 v2.80.1 k8s.io/klog/v2 v2.80.1
@ -33,6 +32,7 @@ require (
github.com/stretchr/testify v1.8.0 // indirect github.com/stretchr/testify v1.8.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect

View File

@ -18,16 +18,10 @@ package util
import ( import (
gobuild "go/build" gobuild "go/build"
"os"
"path/filepath" "path/filepath"
"reflect"
"strings" "strings"
"golang.org/x/tools/go/packages"
) )
type empty struct{}
// CurrentPackage returns the go package of the current directory, or "" if it cannot // CurrentPackage returns the go package of the current directory, or "" if it cannot
// be derived from the GOPATH. // be derived from the GOPATH.
func CurrentPackage() string { func CurrentPackage() string {
@ -57,39 +51,6 @@ func hasSubdir(root, dir string) (rel string, ok bool) {
return filepath.ToSlash(dir[len(root):]), true return filepath.ToSlash(dir[len(root):]), true
} }
// BoilerplatePath returns the path to the boilerplate file in code-generator,
// or "" if the default boilerplate.go.txt file cannot be located.
func BoilerplatePath() string {
// set up paths to check
paths := []string{
// works when run from root of $GOPATH containing k8s.io/code-generator
filepath.Join(reflect.TypeOf(empty{}).PkgPath(), "/../../hack/boilerplate.go.txt"),
// works when run from root of module vendoring k8s.io/code-generator
"vendor/k8s.io/code-generator/hack/boilerplate.go.txt",
// works when run from root of $GOPATH containing k8s.io/kubernetes
"k8s.io/kubernetes/vendor/k8s.io/code-generator/hack/boilerplate.go.txt",
}
// see if we can locate the module directory and add that to the list
config := packages.Config{Mode: packages.NeedModule}
if loadedPackages, err := packages.Load(&config, "k8s.io/code-generator/pkg/util"); err == nil {
for _, loadedPackage := range loadedPackages {
if loadedPackage.Module != nil && loadedPackage.Module.Dir != "" {
paths = append(paths, filepath.Join(loadedPackage.Module.Dir, "hack/boilerplate.go.txt"))
}
}
}
// try all paths and return the first that exists
for _, path := range paths {
if _, err := os.Stat(path); err == nil {
return path
}
}
// cannot be located, invoker will have to explicitly specify boilerplate file
return ""
}
// Vendorless trims vendor prefix from a package path to make it canonical // Vendorless trims vendor prefix from a package path to make it canonical
func Vendorless(p string) string { func Vendorless(p string) string {
if pos := strings.LastIndex(p, "/vendor/"); pos != -1 { if pos := strings.LastIndex(p, "/vendor/"); pos != -1 {

1
vendor/modules.txt vendored
View File

@ -1950,7 +1950,6 @@ k8s.io/cluster-bootstrap/util/tokens
k8s.io/code-generator/cmd/go-to-protobuf k8s.io/code-generator/cmd/go-to-protobuf
k8s.io/code-generator/cmd/go-to-protobuf/protobuf k8s.io/code-generator/cmd/go-to-protobuf/protobuf
k8s.io/code-generator/cmd/go-to-protobuf/protoc-gen-gogo k8s.io/code-generator/cmd/go-to-protobuf/protoc-gen-gogo
k8s.io/code-generator/pkg/util
k8s.io/code-generator/third_party/forked/golang/reflect k8s.io/code-generator/third_party/forked/golang/reflect
# k8s.io/component-base v0.0.0 => ./staging/src/k8s.io/component-base # k8s.io/component-base v0.0.0 => ./staging/src/k8s.io/component-base
## explicit; go 1.19 ## explicit; go 1.19