mirror of
				https://github.com/go-task/task.git
				synced 2025-10-30 23:58:01 +02:00 
			
		
		
		
	| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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
									
								
							
							
						
						
									
										62
									
								
								internal/goext/meta.go
									
									
									
									
									
										Normal 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
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								task.go
									
									
									
									
									
								
							| @@ -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 | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -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 | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										49
									
								
								taskfile/platforms_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								taskfile/platforms_test.go
									
									
									
									
									
										Normal 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) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user