From 2168c5b125020a1841450730edc1b0ed2141d239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Wed, 14 Aug 2024 11:34:21 +0200 Subject: [PATCH] Upgrade to Go 1.23 Fixes #12763 --- .circleci/config.yml | 4 +- .github/workflows/test.yml | 2 +- go.mod | 2 +- hugofs/fs.go | 3 +- modules/client.go | 12 - scripts/fork_go_templates/main.go | 2 +- testscripts/commands/mod.txt | 3 +- testscripts/commands/mod_npm.txt | 2 + testscripts/commands/mod_npm_withexisting.txt | 1 + tpl/internal/go_templates/cfg/cfg.go | 2 + tpl/internal/go_templates/fmtsort/sort.go | 113 +---- .../go_templates/fmtsort/sort_test.go | 17 +- .../go_templates/htmltemplate/content.go | 5 +- tpl/internal/go_templates/htmltemplate/doc.go | 10 +- .../htmltemplate/examplefiles_test.go | 3 - .../go_templates/htmltemplate/exec_test.go | 6 +- tpl/internal/go_templates/htmltemplate/js.go | 5 +- .../go_templates/htmltemplate/template.go | 22 +- .../go_templates/htmltemplate/transition.go | 2 +- tpl/internal/go_templates/testenv/testenv.go | 21 +- tpl/internal/go_templates/texttemplate/doc.go | 7 + .../go_templates/texttemplate/example_test.go | 2 +- .../texttemplate/examplefiles_test.go | 3 - .../go_templates/texttemplate/exec.go | 39 +- .../go_templates/texttemplate/exec_test.go | 102 +++- .../go_templates/texttemplate/funcs.go | 46 +- .../go_templates/texttemplate/helper.go | 34 +- .../texttemplate/hugo_template.go | 13 +- .../go_templates/texttemplate/link_test.go | 8 +- .../go_templates/texttemplate/parse/node.go | 12 +- .../go_templates/texttemplate/parse/parse.go | 60 +-- .../texttemplate/parse/parse_test.go | 435 ++++++++++++------ .../go_templates/texttemplate/template.go | 6 +- tpl/tplimpl/tplimpl_integration_test.go | 14 + 34 files changed, 616 insertions(+), 402 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index beee3b536..0ff955936 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ parameters: defaults: &defaults resource_class: large docker: - - image: bepsays/ci-hugoreleaser:1.22200.20501 + - image: bepsays/ci-hugoreleaser:1.22300.20000 environment: &buildenv GOMODCACHE: /root/project/gomodcache version: 2 @@ -60,7 +60,7 @@ jobs: environment: <<: [*buildenv] docker: - - image: bepsays/ci-hugoreleaser-linux-arm64:1.22200.20501 + - image: bepsays/ci-hugoreleaser-linux-arm64:1.22300.20000 steps: - *restore-cache - &attach-workspace diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b5491090e..120262098 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: test: strategy: matrix: - go-version: [1.21.x, 1.22.x] + go-version: [1.22.x, 1.23.x] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: diff --git a/go.mod b/go.mod index f9776279e..40f90c45f 100644 --- a/go.mod +++ b/go.mod @@ -170,4 +170,4 @@ require ( software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect ) -go 1.21.8 +go 1.22.6 diff --git a/hugofs/fs.go b/hugofs/fs.go index fc0ea71c6..fab0d3886 100644 --- a/hugofs/fs.go +++ b/hugofs/fs.go @@ -147,7 +147,8 @@ func isWrite(flag int) bool { // TODO(bep) move this to a more suitable place. func MakeReadableAndRemoveAllModulePkgDir(fs afero.Fs, dir string) (int, error) { // Safe guard - if !strings.Contains(dir, "pkg") { + // Note that the base directory changed from pkg to gomod_cache in Go 1.23. + if !strings.Contains(dir, "pkg") && !strings.Contains(dir, "gomod") { panic(fmt.Sprint("invalid dir:", dir)) } diff --git a/modules/client.go b/modules/client.go index a6caec23c..dce40d2db 100644 --- a/modules/client.go +++ b/modules/client.go @@ -365,18 +365,6 @@ func (c *Client) Get(args ...string) error { } func (c *Client) get(args ...string) error { - var hasD bool - for _, arg := range args { - if arg == "-d" { - hasD = true - break - } - } - if !hasD { - // go get without the -d flag does not make sense to us, as - // it will try to build and install go packages. - args = append([]string{"-d"}, args...) - } if err := c.runGo(context.Background(), c.logger.Out(), append([]string{"get"}, args...)...); err != nil { return fmt.Errorf("failed to get %q: %w", args, err) } diff --git a/scripts/fork_go_templates/main.go b/scripts/fork_go_templates/main.go index 476fe480b..c4dad3224 100644 --- a/scripts/fork_go_templates/main.go +++ b/scripts/fork_go_templates/main.go @@ -16,7 +16,7 @@ import ( ) func main() { - // The current is built with 8e1fdea8316d840fd07e9d6e026048e53290948b go1.22.5 + // The current is built with 6885bad7dd86880be6929c02085e5c7a67ff2887 go1.23.0 // TODO(bep) preserve the staticcheck.conf file. fmt.Println("Forking ...") defer fmt.Println("Done ...") diff --git a/testscripts/commands/mod.txt b/testscripts/commands/mod.txt index 56cea2c00..2fa17dbbe 100644 --- a/testscripts/commands/mod.txt +++ b/testscripts/commands/mod.txt @@ -18,7 +18,8 @@ hugo mod clean ! stderr . stdout 'hugo: removed 1 dirs in module cache for \"github.com/bep/empty-hugo-module\"' hugo mod clean --all -stdout 'Deleted 2\d{2} files from module cache\.' +# Currently this is 299 on MacOS and 301 on Linux. +stdout 'Deleted (2|3)\d{2} files from module cache\.' cd submod hugo mod init testsubmod cmpenv go.mod $WORK/golden/go.mod.testsubmod diff --git a/testscripts/commands/mod_npm.txt b/testscripts/commands/mod_npm.txt index 32cc37f06..3d8903e6a 100644 --- a/testscripts/commands/mod_npm.txt +++ b/testscripts/commands/mod_npm.txt @@ -2,6 +2,7 @@ dostounix golden/package.json + hugo mod npm pack cmp package.json golden/package.json @@ -41,3 +42,4 @@ path="github.com/gohugoio/hugoTestModule2" } -- go.mod -- module github.com/gohugoio/hugoTestModule +go 1.20 diff --git a/testscripts/commands/mod_npm_withexisting.txt b/testscripts/commands/mod_npm_withexisting.txt index e92eba3fd..073af0f07 100644 --- a/testscripts/commands/mod_npm_withexisting.txt +++ b/testscripts/commands/mod_npm_withexisting.txt @@ -55,3 +55,4 @@ path="github.com/gohugoio/hugoTestModule2" } -- go.mod -- module github.com/gohugoio/hugoTestModule +go 1.20 diff --git a/tpl/internal/go_templates/cfg/cfg.go b/tpl/internal/go_templates/cfg/cfg.go index 2af0ec707..08d210b79 100644 --- a/tpl/internal/go_templates/cfg/cfg.go +++ b/tpl/internal/go_templates/cfg/cfg.go @@ -36,6 +36,7 @@ const KnownEnv = ` GOAMD64 GOARCH GOARM + GOARM64 GOBIN GOCACHE GOCACHEPROG @@ -57,6 +58,7 @@ const KnownEnv = ` GOPPC64 GOPRIVATE GOPROXY + GORISCV64 GOROOT GOSUMDB GOTMPDIR diff --git a/tpl/internal/go_templates/fmtsort/sort.go b/tpl/internal/go_templates/fmtsort/sort.go index 278a89bd7..f51cdc708 100644 --- a/tpl/internal/go_templates/fmtsort/sort.go +++ b/tpl/internal/go_templates/fmtsort/sort.go @@ -9,25 +9,23 @@ package fmtsort import ( + "cmp" "reflect" - "sort" + "slices" ) // Note: Throughout this package we avoid calling reflect.Value.Interface as // it is not always legal to do so and it's easier to avoid the issue than to face it. -// SortedMap represents a map's keys and values. The keys and values are -// aligned in index order: Value[i] is the value in the map corresponding to Key[i]. -type SortedMap struct { - Key []reflect.Value - Value []reflect.Value -} +// SortedMap is a slice of KeyValue pairs that simplifies sorting +// and iterating over map entries. +// +// Each KeyValue pair contains a map key and its corresponding value. +type SortedMap []KeyValue -func (o *SortedMap) Len() int { return len(o.Key) } -func (o *SortedMap) Less(i, j int) bool { return compare(o.Key[i], o.Key[j]) < 0 } -func (o *SortedMap) Swap(i, j int) { - o.Key[i], o.Key[j] = o.Key[j], o.Key[i] - o.Value[i], o.Value[j] = o.Value[j], o.Value[i] +// KeyValue holds a single key and value pair found in a map. +type KeyValue struct { + Key, Value reflect.Value } // Sort accepts a map and returns a SortedMap that has the same keys and @@ -48,7 +46,7 @@ func (o *SortedMap) Swap(i, j int) { // Otherwise identical arrays compare by length. // - interface values compare first by reflect.Type describing the concrete type // and then by concrete value as described in the previous rules. -func Sort(mapValue reflect.Value) *SortedMap { +func Sort(mapValue reflect.Value) SortedMap { if mapValue.Type().Kind() != reflect.Map { return nil } @@ -56,18 +54,14 @@ func Sort(mapValue reflect.Value) *SortedMap { // of a concurrent map update. The runtime is responsible for // yelling loudly if that happens. See issue 33275. n := mapValue.Len() - key := make([]reflect.Value, 0, n) - value := make([]reflect.Value, 0, n) + sorted := make(SortedMap, 0, n) iter := mapValue.MapRange() for iter.Next() { - key = append(key, iter.Key()) - value = append(value, iter.Value()) + sorted = append(sorted, KeyValue{iter.Key(), iter.Value()}) } - sorted := &SortedMap{ - Key: key, - Value: value, - } - sort.Stable(sorted) + slices.SortStableFunc(sorted, func(a, b KeyValue) int { + return compare(a.Key, b.Key) + }) return sorted } @@ -82,43 +76,19 @@ func compare(aVal, bVal reflect.Value) int { } switch aVal.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - a, b := aVal.Int(), bVal.Int() - switch { - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } + return cmp.Compare(aVal.Int(), bVal.Int()) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - a, b := aVal.Uint(), bVal.Uint() - switch { - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } + return cmp.Compare(aVal.Uint(), bVal.Uint()) case reflect.String: - a, b := aVal.String(), bVal.String() - switch { - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } + return cmp.Compare(aVal.String(), bVal.String()) case reflect.Float32, reflect.Float64: - return floatCompare(aVal.Float(), bVal.Float()) + return cmp.Compare(aVal.Float(), bVal.Float()) case reflect.Complex64, reflect.Complex128: a, b := aVal.Complex(), bVal.Complex() - if c := floatCompare(real(a), real(b)); c != 0 { + if c := cmp.Compare(real(a), real(b)); c != 0 { return c } - return floatCompare(imag(a), imag(b)) + return cmp.Compare(imag(a), imag(b)) case reflect.Bool: a, b := aVal.Bool(), bVal.Bool() switch { @@ -130,28 +100,12 @@ func compare(aVal, bVal reflect.Value) int { return -1 } case reflect.Pointer, reflect.UnsafePointer: - a, b := aVal.Pointer(), bVal.Pointer() - switch { - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } + return cmp.Compare(aVal.Pointer(), bVal.Pointer()) case reflect.Chan: if c, ok := nilCompare(aVal, bVal); ok { return c } - ap, bp := aVal.Pointer(), bVal.Pointer() - switch { - case ap < bp: - return -1 - case ap > bp: - return 1 - default: - return 0 - } + return cmp.Compare(aVal.Pointer(), bVal.Pointer()) case reflect.Struct: for i := 0; i < aVal.NumField(); i++ { if c := compare(aVal.Field(i), bVal.Field(i)); c != 0 { @@ -198,22 +152,3 @@ func nilCompare(aVal, bVal reflect.Value) (int, bool) { } return 0, false } - -// floatCompare compares two floating-point values. NaNs compare low. -func floatCompare(a, b float64) int { - switch { - case isNaN(a): - return -1 // No good answer if b is a NaN so don't bother checking. - case isNaN(b): - return 1 - case a < b: - return -1 - case a > b: - return 1 - } - return 0 -} - -func isNaN(a float64) bool { - return a != a -} diff --git a/tpl/internal/go_templates/fmtsort/sort_test.go b/tpl/internal/go_templates/fmtsort/sort_test.go index e86b4c673..0986dbb6d 100644 --- a/tpl/internal/go_templates/fmtsort/sort_test.go +++ b/tpl/internal/go_templates/fmtsort/sort_test.go @@ -5,12 +5,13 @@ package fmtsort_test import ( + "cmp" "fmt" "github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort" "math" "reflect" "runtime" - "sort" + "slices" "strings" "testing" "unsafe" @@ -67,10 +68,6 @@ func TestCompare(t *testing.T) { switch { case i == j: expect = 0 - // NaNs are tricky. - if typ := v0.Type(); (typ.Kind() == reflect.Float32 || typ.Kind() == reflect.Float64) && math.IsNaN(v0.Float()) { - expect = -1 - } case i < j: expect = -1 case i > j: @@ -142,13 +139,13 @@ func sprint(data any) string { return "nil" } b := new(strings.Builder) - for i, key := range om.Key { + for i, m := range om { if i > 0 { b.WriteRune(' ') } - b.WriteString(sprintKey(key)) + b.WriteString(sprintKey(m.Key)) b.WriteRune(':') - fmt.Fprint(b, om.Value[i]) + fmt.Fprint(b, m.Value) } return b.String() } @@ -200,8 +197,8 @@ func makeChans() []chan int { for i := range cs { pin.Pin(reflect.ValueOf(cs[i]).UnsafePointer()) } - sort.Slice(cs, func(i, j int) bool { - return uintptr(reflect.ValueOf(cs[i]).UnsafePointer()) < uintptr(reflect.ValueOf(cs[j]).UnsafePointer()) + slices.SortFunc(cs, func(a, b chan int) int { + return cmp.Compare(reflect.ValueOf(a).Pointer(), reflect.ValueOf(b).Pointer()) }) return cs } diff --git a/tpl/internal/go_templates/htmltemplate/content.go b/tpl/internal/go_templates/htmltemplate/content.go index 9c61cfac0..d19b1ec12 100644 --- a/tpl/internal/go_templates/htmltemplate/content.go +++ b/tpl/internal/go_templates/htmltemplate/content.go @@ -29,7 +29,6 @@ const ( // indirect returns the value, after dereferencing as many times // as necessary to reach the base type (or nil). -// Signature modified by Hugo. TODO(bep) script this. func doIndirect(a any) any { if a == nil { return nil @@ -46,8 +45,8 @@ func doIndirect(a any) any { } var ( - errorType = reflect.TypeOf((*error)(nil)).Elem() - fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() + errorType = reflect.TypeFor[error]() + fmtStringerType = reflect.TypeFor[fmt.Stringer]() ) // indirectToStringerOrError returns the value, after dereferencing as many times diff --git a/tpl/internal/go_templates/htmltemplate/doc.go b/tpl/internal/go_templates/htmltemplate/doc.go index 6be5e0f84..7442194f7 100644 --- a/tpl/internal/go_templates/htmltemplate/doc.go +++ b/tpl/internal/go_templates/htmltemplate/doc.go @@ -232,11 +232,9 @@ Least Surprise Property: knows that contextual autoescaping happens should be able to look at a {{.}} and correctly infer what sanitization happens." -As a consequence of the Least Surprise Property, template actions within an -ECMAScript 6 template literal are disabled by default. -Handling string interpolation within these literals is rather complex resulting -in no clear safe way to support it. -To re-enable template actions within ECMAScript 6 template literals, use the -GODEBUG=jstmpllitinterp=1 environment variable. +Previously, ECMAScript 6 template literal were disabled by default, and could be +enabled with the GODEBUG=jstmpllitinterp=1 environment variable. Template +literals are now supported by default, and setting jstmpllitinterp has no +effect. */ package template diff --git a/tpl/internal/go_templates/htmltemplate/examplefiles_test.go b/tpl/internal/go_templates/htmltemplate/examplefiles_test.go index 43cc3bf01..24b22d984 100644 --- a/tpl/internal/go_templates/htmltemplate/examplefiles_test.go +++ b/tpl/internal/go_templates/htmltemplate/examplefiles_test.go @@ -2,9 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build go1.13 -// +build go1.13 - package template_test import ( diff --git a/tpl/internal/go_templates/htmltemplate/exec_test.go b/tpl/internal/go_templates/htmltemplate/exec_test.go index 428cddc0d..e01813e68 100644 --- a/tpl/internal/go_templates/htmltemplate/exec_test.go +++ b/tpl/internal/go_templates/htmltemplate/exec_test.go @@ -273,8 +273,8 @@ type execTest struct { // of the max int boundary. // We do it this way so the test doesn't depend on ints being 32 bits. var ( - bigInt = fmt.Sprintf("0x%x", int(1<': `\u003e`, } - var jsRegexpReplacementTable = []string{ 0: `\u0000`, '\t': `\t`, diff --git a/tpl/internal/go_templates/htmltemplate/template.go b/tpl/internal/go_templates/htmltemplate/template.go index d7454f101..4582ddd5f 100644 --- a/tpl/internal/go_templates/htmltemplate/template.go +++ b/tpl/internal/go_templates/htmltemplate/template.go @@ -179,7 +179,7 @@ func (t *Template) DefinedTemplates() string { // definition of t itself. // // Templates can be redefined in successive calls to Parse, -// before the first use of Execute on t or any associated template. +// before the first use of [Template.Execute] on t or any associated template. // A template definition with a body containing only white space and comments // is considered empty and will not replace an existing template's body. // This allows using Parse to add new named template definitions without @@ -238,8 +238,8 @@ func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error // Clone returns a duplicate of the template, including all associated // templates. The actual representation is not copied, but the name space of -// associated templates is, so further calls to Parse in the copy will add -// templates to the copy but not to the original. Clone can be used to prepare +// associated templates is, so further calls to [Template.Parse] in the copy will add +// templates to the copy but not to the original. [Template.Clone] can be used to prepare // common templates and use them with variant definitions for other templates // by adding the variants after the clone is made. // @@ -342,7 +342,7 @@ func (t *Template) Funcs(funcMap FuncMap) *Template { } // Delims sets the action delimiters to the specified strings, to be used in -// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template +// subsequent calls to [Template.Parse], [ParseFiles], or [ParseGlob]. Nested template // definitions will inherit the settings. An empty delimiter stands for the // corresponding default: {{ or }}. // The return value is the template, so calls can be chained. @@ -359,7 +359,7 @@ func (t *Template) Lookup(name string) *Template { return t.set[name] } -// Must is a helper that wraps a call to a function returning (*Template, error) +// Must is a helper that wraps a call to a function returning ([*Template], error) // and panics if the error is non-nil. It is intended for use in variable initializations // such as // @@ -371,10 +371,10 @@ func Must(t *Template, err error) *Template { return t } -// ParseFiles creates a new Template and parses the template definitions from +// ParseFiles creates a new [Template] and parses the template definitions from // the named files. The returned template's name will have the (base) name and // (parsed) contents of the first file. There must be at least one file. -// If an error occurs, parsing stops and the returned *Template is nil. +// If an error occurs, parsing stops and the returned [*Template] is nil. // // When parsing multiple files with the same name in different directories, // the last one mentioned will be the one that results. @@ -436,12 +436,12 @@ func parseFiles(t *Template, readFile func(string) (string, []byte, error), file return t, nil } -// ParseGlob creates a new Template and parses the template definitions from +// ParseGlob creates a new [Template] and parses the template definitions from // the files identified by the pattern. The files are matched according to the // semantics of filepath.Match, and the pattern must match at least one file. // The returned template will have the (base) name and (parsed) contents of the // first file matched by the pattern. ParseGlob is equivalent to calling -// ParseFiles with the list of files matched by the pattern. +// [ParseFiles] with the list of files matched by the pattern. // // When parsing multiple files with the same name in different directories, // the last one mentioned will be the one that results. @@ -485,7 +485,7 @@ func IsTrue(val any) (truth, ok bool) { return template.IsTrue(val) } -// ParseFS is like ParseFiles or ParseGlob but reads from the file system fs +// ParseFS is like [ParseFiles] or [ParseGlob] but reads from the file system fs // instead of the host operating system's file system. // It accepts a list of glob patterns. // (Note that most file names serve as glob patterns matching only themselves.) @@ -493,7 +493,7 @@ func ParseFS(fs fs.FS, patterns ...string) (*Template, error) { return parseFS(nil, fs, patterns) } -// ParseFS is like ParseFiles or ParseGlob but reads from the file system fs +// ParseFS is like [Template.ParseFiles] or [Template.ParseGlob] but reads from the file system fs // instead of the host operating system's file system. // It accepts a list of glob patterns. // (Note that most file names serve as glob patterns matching only themselves.) diff --git a/tpl/internal/go_templates/htmltemplate/transition.go b/tpl/internal/go_templates/htmltemplate/transition.go index d5a05f66d..c430389a3 100644 --- a/tpl/internal/go_templates/htmltemplate/transition.go +++ b/tpl/internal/go_templates/htmltemplate/transition.go @@ -414,7 +414,7 @@ func tJSDelimited(c context, s []byte) (context, int) { // If " 0 && i+7 <= len(s) && bytes.Compare(bytes.ToLower(s[i-1:i+7]), []byte(" 0 && i+7 <= len(s) && bytes.Equal(bytes.ToLower(s[i-1:i+7]), []byte("