Skip to content

Commit

Permalink
Add CHANGELOG + improvements to #980
Browse files Browse the repository at this point in the history
Closes #978
  • Loading branch information
andreynering committed Jan 7, 2023
1 parent aa6c7e4 commit 2efb353
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 70 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/api_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
19 changes: 12 additions & 7 deletions docs/docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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'
```
Expand Down
62 changes: 62 additions & 0 deletions internal/goext/meta.go
Original file line number Diff line number Diff line change
@@ -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 changes: 7 additions & 8 deletions task.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"os"
"runtime"
"sort"
"strings"
"sync"
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}
}
Expand Down
83 changes: 30 additions & 53 deletions taskfile/platforms.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -14,98 +15,74 @@ 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
}

// 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": {},
type ErrInvalidPlatform struct {
Platform string
}

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
}
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")
}
if p.Arch != "" {
return fmt.Errorf("task: Multiple Arch values provided")
}
if isSupportedArch(arch) {
if goext.IsKnownArch(arch) {
p.Arch = arch
return nil
}
Expand Down
49 changes: 49 additions & 0 deletions taskfile/platforms_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}

0 comments on commit 2efb353

Please sign in to comment.