1
0
mirror of https://github.com/go-task/task.git synced 2024-12-04 10:24:45 +02:00

Add CHANGELOG + improvements to #980

Closes #978
This commit is contained in:
Andrey Nering 2023-01-06 21:39:57 -03:00
parent aa6c7e4b94
commit 2efb3533ec
7 changed files with 171 additions and 70 deletions

View File

@ -1,5 +1,14 @@
# Changelog
## Unreleased
- Add new `platforms:` attribute to `task` and `cmd`, so it's now possible to
choose in which platforms that given task or command will be run on. Possible
values are operating system (GOOS), architecture (GOARCH) or a combination of
the two. Example: `platforms: [linux]`, `platforms: [amd64]` or
`platforms: [linux/amd64]`. Other platforms will be skipped
([#978](https://github.com/go-task/task/issues/978), [#980](https://github.com/go-task/task/pull/980) by @leaanthony).
## v3.19.1 - 2022-12-31
- Small bug fix: closing `Taskfile.yml` once we're done reading it

View File

@ -139,7 +139,7 @@ includes:
| `prefix` | `string` | | Defines a string to prefix the output of tasks running in parallel. Only used when the output mode is `prefixed`. |
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing commands. |
| `run` | `string` | The one declared globally in the Taskfile or `always` | Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`. |
| `platforms` | `[]string` | All platforms | Specifies which platforms the task should be run on. |
| `platforms` | `[]string` | All platforms | Specifies which platforms the task should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/master/src/go/build/syslist.go). Task will be skipped otherwise. |
:::info
@ -190,7 +190,7 @@ tasks:
| `vars` | [`map[string]Variable`](#variable) | | Optional additional variables to be passed to the referenced task. Only relevant when setting `task` instead of `cmd`. |
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing the command. |
| `defer` | `string` | | Alternative to `cmd`, but schedules the command to be executed at the end of this task instead of immediately. This cannot be used together with `cmd`. |
| `platforms` | `[]string` | All platforms | Specifies which platforms the command should be run on. |
| `platforms` | `[]string` | All platforms | Specifies which platforms the command should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/master/src/go/build/syslist.go). Command will be skipped otherwise. |
:::info

View File

@ -442,8 +442,13 @@ tasks:
## Platform specific tasks and commands
If you want to restrict the running of tasks to explicit platforms, this can be achieved
using the `platforms` key. Tasks can be restricted to a specific OS, architecture or a
using the `platforms:` key. Tasks can be restricted to a specific OS, architecture or a
combination of both.
On a mismatch, the task or command will be skipped, and no error will be thrown.
The values allowed as OS or Arch are valid `GOOS` and `GOARCH` values, as
defined by the Go language
[here](https://github.com/golang/go/blob/master/src/go/build/syslist.go).
The `build-windows` task below will run only on Windows, and on any architecture:
@ -454,7 +459,7 @@ tasks:
build-windows:
platforms: [windows]
cmds:
- echo 'Running command on windows'
- echo 'Running command on Windows'
```
This can be restricted to a specific architecture as follows:
@ -466,7 +471,7 @@ tasks:
build-windows-amd64:
platforms: [windows/amd64]
cmds:
- echo 'Running command on windows (amd64)'
- echo 'Running command on Windows (amd64)'
```
It is also possible to restrict the task to specific architectures:
@ -487,10 +492,10 @@ Multiple platforms can be specified as follows:
version: '3'
tasks:
build-windows:
build:
platforms: [windows/amd64, darwin]
cmds:
- echo 'Running command on windows (amd64) and darwin'
- echo 'Running command on Windows (amd64) and macOS'
```
Individual commands can also be restricted to specific platforms:
@ -499,9 +504,9 @@ Individual commands can also be restricted to specific platforms:
version: '3'
tasks:
build-windows:
build:
cmds:
- cmd: echo 'Running command on windows (amd64) and darwin'
- cmd: echo 'Running command on Windows (amd64) and macOS'
platforms: [windows/amd64, darwin]
- cmd: echo 'Running on all platforms'
```

62
internal/goext/meta.go Normal file
View File

@ -0,0 +1,62 @@
package goext
// NOTE(@andreynering): The lists in this file were copied from:
//
// https://github.com/golang/go/blob/master/src/go/build/syslist.go
func IsKnownOS(str string) bool {
_, known := knownOS[str]
return known
}
func IsKnownArch(str string) bool {
_, known := knownArch[str]
return known
}
var knownOS = map[string]struct{}{
"aix": {},
"android": {},
"darwin": {},
"dragonfly": {},
"freebsd": {},
"hurd": {},
"illumos": {},
"ios": {},
"js": {},
"linux": {},
"nacl": {},
"netbsd": {},
"openbsd": {},
"plan9": {},
"solaris": {},
"windows": {},
"zos": {},
}
var knownArch = map[string]struct{}{
"386": {},
"amd64": {},
"amd64p32": {},
"arm": {},
"armbe": {},
"arm64": {},
"arm64be": {},
"loong64": {},
"mips": {},
"mipsle": {},
"mips64": {},
"mips64le": {},
"mips64p32": {},
"mips64p32le": {},
"ppc": {},
"ppc64": {},
"ppc64le": {},
"riscv": {},
"riscv64": {},
"s390": {},
"s390x": {},
"sparc": {},
"sparc64": {},
"wasm": {},
}

15
task.go
View File

@ -5,6 +5,7 @@ import (
"fmt"
"io"
"os"
"runtime"
"sort"
"strings"
"sync"
@ -135,9 +136,7 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
defer release()
return e.startExecution(ctx, t, func(ctx context.Context) error {
// Check platform
if !ShouldRunOnCurrentPlatform(t.Platforms) {
if !shouldRunOnCurrentPlatform(t.Platforms) {
e.Logger.VerboseOutf(logger.Yellow, `task: "%s" not for current platform - ignored`, call.Task)
return nil
}
@ -259,11 +258,11 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
}
return nil
case cmd.Cmd != "":
// Check platform
if !ShouldRunOnCurrentPlatform(cmd.Platforms) {
if !shouldRunOnCurrentPlatform(cmd.Platforms) {
e.Logger.VerboseOutf(logger.Yellow, `task: [%s] %s not for current platform - ignored`, t.Name(), cmd.Cmd)
return nil
}
if e.Verbose || (!cmd.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) {
e.Logger.Errf(logger.Green, "task: [%s] %s", t.Name(), cmd.Cmd)
}
@ -468,12 +467,12 @@ func FilterOutInternal() FilterFunc {
})
}
func ShouldRunOnCurrentPlatform(platforms []*taskfile.Platform) bool {
func shouldRunOnCurrentPlatform(platforms []*taskfile.Platform) bool {
if len(platforms) == 0 {
return true
}
for _, platform := range platforms {
if platform.MatchesCurrentPlatform() {
for _, p := range platforms {
if (p.OS == "" || p.OS == runtime.GOOS) && (p.Arch == "" || p.Arch == runtime.GOARCH) {
return true
}
}

View File

@ -2,10 +2,11 @@ package taskfile
import (
"fmt"
"runtime"
"strings"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/internal/goext"
)
// Platform represents GOOS and GOARCH values
@ -14,67 +15,23 @@ type Platform struct {
Arch string
}
// ParsePlatform takes a string representing an OS/Arch combination (or either on their own)
// and parses it into the Platform struct. It returns an error if the input string is invalid.
// Valid combinations for input: OS, Arch, OS/Arch
func (p *Platform) ParsePlatform(input string) error {
// tidy up input
platformString := strings.ToLower(strings.TrimSpace(input))
splitValues := strings.Split(platformString, "/")
if len(splitValues) > 2 {
return fmt.Errorf("task: Invalid OS/Arch provided: %s", input)
}
err := p.parseOsOrArch(splitValues[0])
if err != nil {
return err
}
if len(splitValues) == 2 {
return p.parseArch(splitValues[1])
}
return nil
type ErrInvalidPlatform struct {
Platform string
}
// supportedOSes is a list of supported OSes
var supportedOSes = map[string]struct{}{
"windows": {},
"darwin": {},
"linux": {},
"freebsd": {},
}
func isSupportedOS(input string) bool {
_, exists := supportedOSes[input]
return exists
}
// supportedArchs is a list of supported architectures
var supportedArchs = map[string]struct{}{
"amd64": {},
"arm64": {},
"386": {},
}
func isSupportedArch(input string) bool {
_, exists := supportedArchs[input]
return exists
}
// MatchesCurrentPlatform returns true if the platform matches the current platform
func (p *Platform) MatchesCurrentPlatform() bool {
return (p.OS == "" || p.OS == runtime.GOOS) &&
(p.Arch == "" || p.Arch == runtime.GOARCH)
func (err *ErrInvalidPlatform) Error() string {
return fmt.Sprintf(`task: Invalid platform "%s"`, err.Platform)
}
// UnmarshalYAML implements yaml.Unmarshaler interface.
func (p *Platform) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.ScalarNode:
var platform string
if err := node.Decode(&platform); err != nil {
return err
}
if err := p.ParsePlatform(platform); err != nil {
if err := p.parsePlatform(platform); err != nil {
return err
}
return nil
@ -82,22 +39,42 @@ func (p *Platform) UnmarshalYAML(node *yaml.Node) error {
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into platform", node.Line, node.ShortTag())
}
// parsePlatform takes a string representing an OS/Arch combination (or either on their own)
// and parses it into the Platform struct. It returns an error if the input string is invalid.
// Valid combinations for input: OS, Arch, OS/Arch
func (p *Platform) parsePlatform(input string) error {
splitValues := strings.Split(input, "/")
if len(splitValues) > 2 {
return &ErrInvalidPlatform{Platform: input}
}
if err := p.parseOsOrArch(splitValues[0]); err != nil {
return &ErrInvalidPlatform{Platform: input}
}
if len(splitValues) == 2 {
if err := p.parseArch(splitValues[1]); err != nil {
return &ErrInvalidPlatform{Platform: input}
}
}
return nil
}
// parseOsOrArch will check if the given input is a valid OS or Arch value.
// If so, it will store it. If not, an error is returned
func (p *Platform) parseOsOrArch(osOrArch string) error {
if osOrArch == "" {
return fmt.Errorf("task: Blank OS/Arch value provided")
}
if isSupportedOS(osOrArch) {
if goext.IsKnownOS(osOrArch) {
p.OS = osOrArch
return nil
}
if isSupportedArch(osOrArch) {
if goext.IsKnownArch(osOrArch) {
p.Arch = osOrArch
return nil
}
return fmt.Errorf("task: Invalid OS/Arch value provided (%s)", osOrArch)
}
func (p *Platform) parseArch(arch string) error {
if arch == "" {
return fmt.Errorf("task: Blank Arch value provided")
@ -105,7 +82,7 @@ func (p *Platform) parseArch(arch string) error {
if p.Arch != "" {
return fmt.Errorf("task: Multiple Arch values provided")
}
if isSupportedArch(arch) {
if goext.IsKnownArch(arch) {
p.Arch = arch
return nil
}

View File

@ -0,0 +1,49 @@
package taskfile
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPlatformParsing(t *testing.T) {
tests := []struct {
Input string
ExpectedOS string
ExpectedArch string
Error string
}{
{Input: "windows", ExpectedOS: "windows", ExpectedArch: ""},
{Input: "linux", ExpectedOS: "linux", ExpectedArch: ""},
{Input: "darwin", ExpectedOS: "darwin", ExpectedArch: ""},
{Input: "386", ExpectedOS: "", ExpectedArch: "386"},
{Input: "amd64", ExpectedOS: "", ExpectedArch: "amd64"},
{Input: "arm64", ExpectedOS: "", ExpectedArch: "arm64"},
{Input: "windows/386", ExpectedOS: "windows", ExpectedArch: "386"},
{Input: "windows/amd64", ExpectedOS: "windows", ExpectedArch: "amd64"},
{Input: "windows/arm64", ExpectedOS: "windows", ExpectedArch: "arm64"},
{Input: "invalid", Error: `task: Invalid platform "invalid"`},
{Input: "invalid/invalid", Error: `task: Invalid platform "invalid/invalid"`},
{Input: "windows/invalid", Error: `task: Invalid platform "windows/invalid"`},
{Input: "invalid/amd64", Error: `task: Invalid platform "invalid/amd64"`},
}
for _, test := range tests {
t.Run(test.Input, func(t *testing.T) {
var p Platform
err := p.parsePlatform(test.Input)
if test.Error != "" {
assert.Error(t, err)
assert.Equal(t, test.Error, err.Error())
} else {
assert.NoError(t, err)
assert.Equal(t, test.ExpectedOS, p.OS)
assert.Equal(t, test.ExpectedArch, p.Arch)
}
})
}
}