chore: bump minimum Go to 1.26 and adopt 1.26 features (#2920)

This commit is contained in:
Valentin Maerten
2026-08-20 12:24:04 +02:00
committed by GitHub
parent 325e3188f0
commit dd4463ec60
21 changed files with 76 additions and 73 deletions

View File

@@ -10,7 +10,7 @@
"osvVulnerabilityAlerts": true, "osvVulnerabilityAlerts": true,
"postUpdateOptions": ["gomodTidy"], "postUpdateOptions": ["gomodTidy"],
"constraints": { "constraints": {
"go": "1.25.10" "go": "1.26.4"
}, },
"customManagers": [ "customManagers": [
{ {

View File

@@ -21,7 +21,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
go-version: [1.25.x, 1.26.x] go-version: [1.26.x, 1.27.x]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 📥 Checkout - name: 📥 Checkout
@@ -40,7 +40,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
go-version: [1.25.x, 1.26.x] go-version: [1.26.x, 1.27.x]
platform: [ubuntu-latest, macos-latest, windows-latest] platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
@@ -63,7 +63,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
go-version: [1.25.x, 1.26.x] go-version: [1.26.x, 1.27.x]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 📥 Checkout - name: 📥 Checkout

View File

@@ -20,7 +20,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with: with:
go-version: 1.26.x go-version: 1.27.x
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7 uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7

View File

@@ -21,7 +21,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with: with:
go-version: 1.26.x go-version: 1.27.x
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with: with:

View File

@@ -1,5 +1,13 @@
# Changelog # Changelog
## Unreleased
### 📦 Package API
- Bumped the minimum Go version to 1.26. Task follows Go's two-latest support
window, and is now tested against 1.26 and 1.27. This only affects projects
importing Task as a Go module (#2920 by @vmaerten).
## v3.53.1 - 2026-08-18 ## v3.53.1 - 2026-08-18
### 🚀 Features ### 🚀 Features

View File

@@ -1,5 +1,4 @@
//go:build fsbench //go:build fsbench
// +build fsbench
package task_test package task_test

View File

@@ -25,8 +25,7 @@ type (
func NewTaskfileDecodeError(err error, node *yaml.Node) *TaskfileDecodeError { func NewTaskfileDecodeError(err error, node *yaml.Node) *TaskfileDecodeError {
// If the error is already a DecodeError, return it // If the error is already a DecodeError, return it
taskfileInvalidErr := &TaskfileDecodeError{} if taskfileInvalidErr, ok := errors.AsType[*TaskfileDecodeError](err); ok {
if errors.As(err, &taskfileInvalidErr) {
return taskfileInvalidErr return taskfileInvalidErr
} }
return &TaskfileDecodeError{ return &TaskfileDecodeError{
@@ -45,8 +44,7 @@ func (err *TaskfileDecodeError) Error() string {
fmt.Fprintln(buf, color.RedString("err: %s", err.Message)) fmt.Fprintln(buf, color.RedString("err: %s", err.Message))
} else { } else {
// Extract the errors from the TypeError // Extract the errors from the TypeError
te := &yaml.TypeError{} if te, ok := errors.AsType[*yaml.TypeError](err.Err); ok {
if errors.As(err.Err, &te) {
if len(te.Errors) > 1 { if len(te.Errors) > 1 {
fmt.Fprintln(buf, color.RedString("errs:")) fmt.Fprintln(buf, color.RedString("errs:"))
for _, message := range te.Errors { for _, message := range te.Errors {

View File

@@ -67,6 +67,13 @@ func As(err error, target any) bool {
return errors.As(err, target) return errors.As(err, target)
} }
// AsType wraps the standard errors.AsType function so that we don't need to alias
// that package. It returns the first error in err's tree that matches type T, and
// whether such an error was found.
func AsType[T error](err error) (T, bool) {
return errors.AsType[T](err)
}
// Unwrap wraps the standard errors.Unwrap function so that we don't need to alias that package. // Unwrap wraps the standard errors.Unwrap function so that we don't need to alias that package.
func Unwrap(err error) error { func Unwrap(err error) error {
return errors.Unwrap(err) return errors.Unwrap(err)

View File

@@ -48,12 +48,10 @@ func (err *TaskRunError) Code() int {
} }
func (err *TaskRunError) TaskExitCode() int { func (err *TaskRunError) TaskExitCode() int {
var exit interp.ExitStatus if exit, ok := errors.AsType[interp.ExitStatus](err.Err); ok {
if errors.As(err.Err, &exit) {
return int(exit) return int(exit)
} }
var timeout *TaskTimeoutError if _, ok := errors.AsType[*TaskTimeoutError](err.Err); ok {
if errors.As(err.Err, &timeout) {
return TimeoutExitCode return TimeoutExitCode
} }
return err.Code() return err.Code()

2
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/go-task/task/v3 module github.com/go-task/task/v3
go 1.25.10 go 1.26.4
require ( require (
charm.land/bubbles/v2 v2.1.1 charm.land/bubbles/v2 v2.1.1

View File

@@ -2,9 +2,11 @@ package fingerprint
import ( import (
"bufio" "bufio"
"cmp"
"maps"
"os" "os"
"path/filepath" "path/filepath"
"sort" "slices"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -95,17 +97,14 @@ func filterGitignored(files map[string]bool, dir string) map[string]bool {
// Shallow dirs first (lower priority): the matcher scans patterns last to // Shallow dirs first (lower priority): the matcher scans patterns last to
// first, so deeper rules win and can negate shallower ones. // first, so deeper rules win and can negate shallower ones.
dirs := make([]string, 0, len(dirSet)) dirs := slices.Collect(maps.Keys(dirSet))
for d := range dirSet { slices.SortFunc(dirs, func(a, b string) int {
dirs = append(dirs, d) da := strings.Count(a, string(filepath.Separator))
} db := strings.Count(b, string(filepath.Separator))
sort.Slice(dirs, func(i, j int) bool { if da != db {
di := strings.Count(dirs[i], string(filepath.Separator)) return cmp.Compare(da, db)
dj := strings.Count(dirs[j], string(filepath.Separator))
if di != dj {
return di < dj
} }
return dirs[i] < dirs[j] return cmp.Compare(a, b)
}) })
var patterns []gitignore.Pattern var patterns []gitignore.Pattern

View File

@@ -3,7 +3,7 @@ package fingerprint
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"sort" "slices"
"github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/filepathext"
@@ -65,6 +65,6 @@ func collectKeys(m map[string]bool) []string {
keys = append(keys, filepath.ToSlash(k)) keys = append(keys, filepath.ToSlash(k))
} }
} }
sort.Strings(keys) slices.Sort(keys)
return keys return keys
} }

View File

@@ -4,7 +4,7 @@ import (
"io/fs" "io/fs"
"os" "os"
"path/filepath" "path/filepath"
"sort" "slices"
"strings" "strings"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
@@ -74,6 +74,6 @@ func FastRecursiveGlob(pattern string) ([]string, bool, error) {
if err != nil { if err != nil {
return nil, true, err return nil, true, err
} }
sort.Strings(results) slices.Sort(results)
return results, true, nil return results, true, nil
} }

View File

@@ -1,8 +1,8 @@
package sort package sort
import ( import (
"cmp"
"slices" "slices"
"sort"
"strings" "strings"
) )
@@ -29,16 +29,16 @@ func AlphaNumericWithRootTasksFirst(items []string, namespaces []string) []strin
if len(namespaces) > 0 { if len(namespaces) > 0 {
return AlphaNumeric(items, namespaces) return AlphaNumeric(items, namespaces)
} }
sort.Slice(items, func(i, j int) bool { slices.SortFunc(items, func(a, b string) int {
iContainsColon := strings.Contains(items[i], ":") aContainsColon := strings.Contains(a, ":")
jContainsColon := strings.Contains(items[j], ":") bContainsColon := strings.Contains(b, ":")
if iContainsColon == jContainsColon { if aContainsColon == bContainsColon {
return items[i] < items[j] return cmp.Compare(a, b)
} }
if !iContainsColon && jContainsColon { if !aContainsColon && bContainsColon {
return true return -1
} }
return false return 1
}) })
return items return items
} }

View File

@@ -1,36 +1,36 @@
# @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html # @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html
[[tools.go]] [[tools.go]]
version = "1.26.6" version = "1.27.0"
backend = "core:go" backend = "core:go"
[tools.go."platforms.linux-arm64"] [tools.go."platforms.linux-arm64"]
checksum = "sha256:d0507e9e9d7fe012aae570108cbd76c15de879e17130ab8cb90d4d7445cb1f2e" checksum = "sha256:51798d2c42d0e1c6ed7fd9f48728b4193abac9e8aad6dbac2fe96a81f5909bda"
url = "https://dl.google.com/go/go1.26.6.linux-arm64.tar.gz" url = "https://dl.google.com/go/go1.27.0.linux-arm64.tar.gz"
[tools.go."platforms.linux-arm64-musl"] [tools.go."platforms.linux-arm64-musl"]
checksum = "sha256:d0507e9e9d7fe012aae570108cbd76c15de879e17130ab8cb90d4d7445cb1f2e" checksum = "sha256:51798d2c42d0e1c6ed7fd9f48728b4193abac9e8aad6dbac2fe96a81f5909bda"
url = "https://dl.google.com/go/go1.26.6.linux-arm64.tar.gz" url = "https://dl.google.com/go/go1.27.0.linux-arm64.tar.gz"
[tools.go."platforms.linux-x64"] [tools.go."platforms.linux-x64"]
checksum = "sha256:708effb774be8237570d0add163225abbdfaf4fca28b2611df167beba4feef89" checksum = "sha256:675c26c449cbb18fc24b74650de1eabbae6e16f64326fd85a283fb3b58280685"
url = "https://dl.google.com/go/go1.26.6.linux-amd64.tar.gz" url = "https://dl.google.com/go/go1.27.0.linux-amd64.tar.gz"
[tools.go."platforms.linux-x64-musl"] [tools.go."platforms.linux-x64-musl"]
checksum = "sha256:708effb774be8237570d0add163225abbdfaf4fca28b2611df167beba4feef89" checksum = "sha256:675c26c449cbb18fc24b74650de1eabbae6e16f64326fd85a283fb3b58280685"
url = "https://dl.google.com/go/go1.26.6.linux-amd64.tar.gz" url = "https://dl.google.com/go/go1.27.0.linux-amd64.tar.gz"
[tools.go."platforms.macos-arm64"] [tools.go."platforms.macos-arm64"]
checksum = "sha256:2dc95ce4675829f2df0e86b28bcef3283635902062a5f0580ca659bf570f3204" checksum = "sha256:90493b3bbd5e10f91d12153198bf1994fd756399b4fec93b49b0c6e2acdeeb3e"
url = "https://dl.google.com/go/go1.26.6.darwin-arm64.tar.gz" url = "https://dl.google.com/go/go1.27.0.darwin-arm64.tar.gz"
[tools.go."platforms.macos-x64"] [tools.go."platforms.macos-x64"]
checksum = "sha256:08b65a63f244115121ced6c3b55ad38d801a7442acad5c949a17aad84ae6d684" checksum = "sha256:d3314e25496e4381d71a5c51d2907e7af655d199f6780b549f015bd85fef4986"
url = "https://dl.google.com/go/go1.26.6.darwin-amd64.tar.gz" url = "https://dl.google.com/go/go1.27.0.darwin-amd64.tar.gz"
[tools.go."platforms.windows-x64"] [tools.go."platforms.windows-x64"]
checksum = "sha256:5b6c5b556525810463b5c897b50dc7a82d6a3dc0bfaf55d990a7e9f31d6b2318" checksum = "sha256:f0c0a0d33ba94f4d2c5dbc887334ce678b21813504ddb3aafcb06e60a5a667c4"
url = "https://dl.google.com/go/go1.26.6.windows-amd64.zip" url = "https://dl.google.com/go/go1.27.0.windows-amd64.zip"
[[tools."go:golang.org/x/exp/cmd/gorelease"]] [[tools."go:golang.org/x/exp/cmd/gorelease"]]
version = "0.0.0-20260603202125-055de637280b" version = "0.0.0-20260603202125-055de637280b"

View File

@@ -1,6 +1,6 @@
[tools] [tools]
# Runtimes # Runtimes
go = "1.26.6" go = "1.27.0"
node = "24" node = "24"
pnpm = "11.22.0" pnpm = "11.22.0"

View File

@@ -59,8 +59,7 @@ func (e *Executor) getRootNode() (taskfile.Node, error) {
taskfile.WithCert(e.Cert), taskfile.WithCert(e.Cert),
taskfile.WithCertKey(e.CertKey), taskfile.WithCertKey(e.CertKey),
) )
var taskNotFoundError errors.TaskfileNotFoundError if taskNotFoundError, ok := errors.AsType[errors.TaskfileNotFoundError](err); ok {
if errors.As(err, &taskNotFoundError) {
taskNotFoundError.AskInit = true taskNotFoundError.AskInit = true
return nil, taskNotFoundError return nil, taskNotFoundError
} }

View File

@@ -1,5 +1,4 @@
//go:build signals //go:build signals
// +build signals
// This file contains tests for signal handling on Unix. // This file contains tests for signal handling on Unix.
// Based on code from https://github.com/marco-m/timeit // Based on code from https://github.com/marco-m/timeit
@@ -154,9 +153,8 @@ func TestSignalSentToProcessGroup(t *testing.T) {
err := sut.Wait() err := sut.Wait()
var wantErr *exec.ExitError
const wantExitStatus = 201 const wantExitStatus = 201
if errors.As(err, &wantErr) { if wantErr, ok := errors.AsType[*exec.ExitError](err); ok {
if wantErr.ExitCode() != wantExitStatus { if wantErr.ExitCode() != wantExitStatus {
t.Errorf( t.Errorf(
"waiting for child process: got exit status %v; want %d\n"+ "waiting for child process: got exit status %v; want %d\n"+
@@ -166,7 +164,7 @@ func TestSignalSentToProcessGroup(t *testing.T) {
} }
} else { } else {
t.Errorf("waiting for child process: got unexpected error type %v (%T); want (%T)", t.Errorf("waiting for child process: got unexpected error type %v (%T); want (%T)",
err, err, wantErr) err, err, (*exec.ExitError)(nil))
} }
gotLines := strings.SplitAfter(out.String(), "\n") gotLines := strings.SplitAfter(out.String(), "\n")

15
task.go
View File

@@ -273,12 +273,9 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
e.Logger.VerboseErrf(logger.Red, "task: %q failed: %v\n", call.Task, err) e.Logger.VerboseErrf(logger.Red, "task: %q failed: %v\n", call.Task, err)
var exitCode interp.ExitStatus if exitCode, ok := errors.AsType[interp.ExitStatus](err); ok {
var timeout *errors.TaskTimeoutError
switch {
case errors.As(err, &exitCode):
deferredExitCode = uint8(exitCode) deferredExitCode = uint8(exitCode)
case errors.As(err, &timeout): } else if _, ok := errors.AsType[*errors.TaskTimeoutError](err); ok {
deferredExitCode = errors.TimeoutExitCode deferredExitCode = errors.TimeoutExitCode
} }
@@ -461,9 +458,11 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in
// isCommandFailure reports whether the command failed on its own terms - a // isCommandFailure reports whether the command failed on its own terms - a
// non-zero exit status or its timeout - rather than Task failing to run it. // non-zero exit status or its timeout - rather than Task failing to run it.
func isCommandFailure(err error) bool { func isCommandFailure(err error) bool {
var exitCode interp.ExitStatus if _, ok := errors.AsType[interp.ExitStatus](err); ok {
var timeout *errors.TaskTimeoutError return true
return errors.As(err, &exitCode) || errors.As(err, &timeout) }
_, ok := errors.AsType[*errors.TaskTimeoutError](err)
return ok
} }
// timedOut reports whether ctx was cancelled by the given timeout rather than by // timedOut reports whether ctx was cancelled by the given timeout rather than by

View File

@@ -413,8 +413,7 @@ func (r *Reader) readNode(ctx context.Context, node Node) (*ast.Taskfile, error)
var tf ast.Taskfile var tf ast.Taskfile
if err := yaml.Unmarshal(b, &tf); err != nil { if err := yaml.Unmarshal(b, &tf); err != nil {
// Decode the taskfile and add the file info the any errors // Decode the taskfile and add the file info the any errors
taskfileDecodeErr := &errors.TaskfileDecodeError{} if taskfileDecodeErr, ok := errors.AsType[*errors.TaskfileDecodeError](err); ok {
if errors.As(err, &taskfileDecodeErr) {
snippet := NewSnippet(b, snippet := NewSnippet(b,
WithLine(taskfileDecodeErr.Line), WithLine(taskfileDecodeErr.Line),
WithColumn(taskfileDecodeErr.Column), WithColumn(taskfileDecodeErr.Column),

View File

@@ -1,5 +1,4 @@
//go:build watch //go:build watch
// +build watch
package task_test package task_test