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,
"postUpdateOptions": ["gomodTidy"],
"constraints": {
"go": "1.25.10"
"go": "1.26.4"
},
"customManagers": [
{

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,13 @@
# 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
### 🚀 Features

View File

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

View File

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

View File

@@ -67,6 +67,13 @@ func As(err error, target any) bool {
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.
func Unwrap(err error) error {
return errors.Unwrap(err)

View File

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

2
go.mod
View File

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

View File

@@ -2,9 +2,11 @@ package fingerprint
import (
"bufio"
"cmp"
"maps"
"os"
"path/filepath"
"sort"
"slices"
"strings"
"sync"
"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
// first, so deeper rules win and can negate shallower ones.
dirs := make([]string, 0, len(dirSet))
for d := range dirSet {
dirs = append(dirs, d)
}
sort.Slice(dirs, func(i, j int) bool {
di := strings.Count(dirs[i], string(filepath.Separator))
dj := strings.Count(dirs[j], string(filepath.Separator))
if di != dj {
return di < dj
dirs := slices.Collect(maps.Keys(dirSet))
slices.SortFunc(dirs, func(a, b string) int {
da := strings.Count(a, string(filepath.Separator))
db := strings.Count(b, string(filepath.Separator))
if da != db {
return cmp.Compare(da, db)
}
return dirs[i] < dirs[j]
return cmp.Compare(a, b)
})
var patterns []gitignore.Pattern

View File

@@ -3,7 +3,7 @@ package fingerprint
import (
"os"
"path/filepath"
"sort"
"slices"
"github.com/go-task/task/v3/internal/execext"
"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))
}
}
sort.Strings(keys)
slices.Sort(keys)
return keys
}

View File

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

View File

@@ -1,8 +1,8 @@
package sort
import (
"cmp"
"slices"
"sort"
"strings"
)
@@ -29,16 +29,16 @@ func AlphaNumericWithRootTasksFirst(items []string, namespaces []string) []strin
if len(namespaces) > 0 {
return AlphaNumeric(items, namespaces)
}
sort.Slice(items, func(i, j int) bool {
iContainsColon := strings.Contains(items[i], ":")
jContainsColon := strings.Contains(items[j], ":")
if iContainsColon == jContainsColon {
return items[i] < items[j]
slices.SortFunc(items, func(a, b string) int {
aContainsColon := strings.Contains(a, ":")
bContainsColon := strings.Contains(b, ":")
if aContainsColon == bContainsColon {
return cmp.Compare(a, b)
}
if !iContainsColon && jContainsColon {
return true
if !aContainsColon && bContainsColon {
return -1
}
return false
return 1
})
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
[[tools.go]]
version = "1.26.6"
version = "1.27.0"
backend = "core:go"
[tools.go."platforms.linux-arm64"]
checksum = "sha256:d0507e9e9d7fe012aae570108cbd76c15de879e17130ab8cb90d4d7445cb1f2e"
url = "https://dl.google.com/go/go1.26.6.linux-arm64.tar.gz"
checksum = "sha256:51798d2c42d0e1c6ed7fd9f48728b4193abac9e8aad6dbac2fe96a81f5909bda"
url = "https://dl.google.com/go/go1.27.0.linux-arm64.tar.gz"
[tools.go."platforms.linux-arm64-musl"]
checksum = "sha256:d0507e9e9d7fe012aae570108cbd76c15de879e17130ab8cb90d4d7445cb1f2e"
url = "https://dl.google.com/go/go1.26.6.linux-arm64.tar.gz"
checksum = "sha256:51798d2c42d0e1c6ed7fd9f48728b4193abac9e8aad6dbac2fe96a81f5909bda"
url = "https://dl.google.com/go/go1.27.0.linux-arm64.tar.gz"
[tools.go."platforms.linux-x64"]
checksum = "sha256:708effb774be8237570d0add163225abbdfaf4fca28b2611df167beba4feef89"
url = "https://dl.google.com/go/go1.26.6.linux-amd64.tar.gz"
checksum = "sha256:675c26c449cbb18fc24b74650de1eabbae6e16f64326fd85a283fb3b58280685"
url = "https://dl.google.com/go/go1.27.0.linux-amd64.tar.gz"
[tools.go."platforms.linux-x64-musl"]
checksum = "sha256:708effb774be8237570d0add163225abbdfaf4fca28b2611df167beba4feef89"
url = "https://dl.google.com/go/go1.26.6.linux-amd64.tar.gz"
checksum = "sha256:675c26c449cbb18fc24b74650de1eabbae6e16f64326fd85a283fb3b58280685"
url = "https://dl.google.com/go/go1.27.0.linux-amd64.tar.gz"
[tools.go."platforms.macos-arm64"]
checksum = "sha256:2dc95ce4675829f2df0e86b28bcef3283635902062a5f0580ca659bf570f3204"
url = "https://dl.google.com/go/go1.26.6.darwin-arm64.tar.gz"
checksum = "sha256:90493b3bbd5e10f91d12153198bf1994fd756399b4fec93b49b0c6e2acdeeb3e"
url = "https://dl.google.com/go/go1.27.0.darwin-arm64.tar.gz"
[tools.go."platforms.macos-x64"]
checksum = "sha256:08b65a63f244115121ced6c3b55ad38d801a7442acad5c949a17aad84ae6d684"
url = "https://dl.google.com/go/go1.26.6.darwin-amd64.tar.gz"
checksum = "sha256:d3314e25496e4381d71a5c51d2907e7af655d199f6780b549f015bd85fef4986"
url = "https://dl.google.com/go/go1.27.0.darwin-amd64.tar.gz"
[tools.go."platforms.windows-x64"]
checksum = "sha256:5b6c5b556525810463b5c897b50dc7a82d6a3dc0bfaf55d990a7e9f31d6b2318"
url = "https://dl.google.com/go/go1.26.6.windows-amd64.zip"
checksum = "sha256:f0c0a0d33ba94f4d2c5dbc887334ce678b21813504ddb3aafcb06e60a5a667c4"
url = "https://dl.google.com/go/go1.27.0.windows-amd64.zip"
[[tools."go:golang.org/x/exp/cmd/gorelease"]]
version = "0.0.0-20260603202125-055de637280b"

View File

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

View File

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

View File

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

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)
var exitCode interp.ExitStatus
var timeout *errors.TaskTimeoutError
switch {
case errors.As(err, &exitCode):
if exitCode, ok := errors.AsType[interp.ExitStatus](err); ok {
deferredExitCode = uint8(exitCode)
case errors.As(err, &timeout):
} else if _, ok := errors.AsType[*errors.TaskTimeoutError](err); ok {
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
// non-zero exit status or its timeout - rather than Task failing to run it.
func isCommandFailure(err error) bool {
var exitCode interp.ExitStatus
var timeout *errors.TaskTimeoutError
return errors.As(err, &exitCode) || errors.As(err, &timeout)
if _, ok := errors.AsType[interp.ExitStatus](err); ok {
return true
}
_, ok := errors.AsType[*errors.TaskTimeoutError](err)
return ok
}
// 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
if err := yaml.Unmarshal(b, &tf); err != nil {
// Decode the taskfile and add the file info the any errors
taskfileDecodeErr := &errors.TaskfileDecodeError{}
if errors.As(err, &taskfileDecodeErr) {
if taskfileDecodeErr, ok := errors.AsType[*errors.TaskfileDecodeError](err); ok {
snippet := NewSnippet(b,
WithLine(taskfileDecodeErr.Line),
WithColumn(taskfileDecodeErr.Column),

View File

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