mirror of
				https://github.com/go-task/task.git
				synced 2025-10-30 23:58:01 +02:00 
			
		
		
		
	Add support for 'platforms' in both task and command (#980)
This commit is contained in:
		| @@ -139,6 +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. | | ||||
|  | ||||
| :::info | ||||
|  | ||||
| @@ -189,6 +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. | | ||||
|  | ||||
| :::info | ||||
|  | ||||
|   | ||||
| @@ -439,6 +439,73 @@ tasks: | ||||
|       - echo {{.TEXT}} | ||||
| ``` | ||||
|  | ||||
| ## 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 | ||||
| combination of both. | ||||
|  | ||||
| The `build-windows` task below will run only on Windows, and on any architecture: | ||||
|  | ||||
| ```yaml | ||||
| version: '3' | ||||
|  | ||||
| tasks: | ||||
|   build-windows: | ||||
|     platforms: [windows] | ||||
|     cmds: | ||||
|       - echo 'Running command on windows' | ||||
| ``` | ||||
|  | ||||
| This can be restricted to a specific architecture as follows: | ||||
|  | ||||
| ```yaml | ||||
| version: '3' | ||||
|  | ||||
| tasks: | ||||
|   build-windows-amd64: | ||||
|     platforms: [windows/amd64] | ||||
|     cmds: | ||||
|       - echo 'Running command on windows (amd64)' | ||||
| ``` | ||||
|  | ||||
| It is also possible to restrict the task to specific architectures: | ||||
|  | ||||
| ```yaml | ||||
| version: '3' | ||||
|  | ||||
| tasks: | ||||
|   build-amd64: | ||||
|     platforms: [amd64] | ||||
|     cmds: | ||||
|       - echo 'Running command on amd64' | ||||
| ``` | ||||
|  | ||||
| Multiple platforms can be specified as follows: | ||||
|  | ||||
| ```yaml | ||||
| version: '3' | ||||
|  | ||||
| tasks: | ||||
|   build-windows: | ||||
|     platforms: [windows/amd64, darwin] | ||||
|     cmds: | ||||
|       - echo 'Running command on windows (amd64) and darwin' | ||||
| ``` | ||||
|  | ||||
| Individual commands can also be restricted to specific platforms: | ||||
|  | ||||
| ```yaml | ||||
| version: '3' | ||||
|  | ||||
| tasks: | ||||
|   build-windows: | ||||
|     cmds: | ||||
|       - cmd: echo 'Running command on windows (amd64) and darwin' | ||||
|         platforms: [windows/amd64, darwin] | ||||
|       - cmd: echo 'Running on all platforms' | ||||
| ``` | ||||
|  | ||||
| ## Calling another task | ||||
|  | ||||
| When a task has many dependencies, they are executed concurrently. This will | ||||
|   | ||||
							
								
								
									
										14
									
								
								docs/static/schema.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								docs/static/schema.json
									
									
									
									
										vendored
									
									
								
							| @@ -155,6 +155,13 @@ | ||||
|           "run": { | ||||
|             "description": "Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`.", | ||||
|             "$ref": "#/definitions/3/run" | ||||
|           }, | ||||
|           "platforms": { | ||||
|             "description": "Specifies which platforms the task should be run on.", | ||||
|             "type": "array", | ||||
|             "items": { | ||||
|               "type": "string" | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
| @@ -233,6 +240,13 @@ | ||||
|           "defer": { | ||||
|             "description": "", | ||||
|             "type": "boolean" | ||||
|           }, | ||||
|           "platforms": { | ||||
|             "description": "Specifies which platforms the command should be run on.", | ||||
|             "type": "array", | ||||
|             "items": { | ||||
|               "type": "string" | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "additionalProperties": false, | ||||
|   | ||||
							
								
								
									
										24
									
								
								task.go
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								task.go
									
									
									
									
									
								
							| @@ -135,6 +135,13 @@ 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) { | ||||
| 			e.Logger.VerboseOutf(logger.Yellow, `task: "%s" not for current platform - ignored`, call.Task) | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		e.Logger.VerboseErrf(logger.Magenta, `task: "%s" started`, call.Task) | ||||
| 		if err := e.runDeps(ctx, t); err != nil { | ||||
| 			return err | ||||
| @@ -252,6 +259,11 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi | ||||
| 		} | ||||
| 		return nil | ||||
| 	case cmd.Cmd != "": | ||||
| 		// Check platform | ||||
| 		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) | ||||
| 		} | ||||
| @@ -455,3 +467,15 @@ func FilterOutInternal() FilterFunc { | ||||
| 		return task.Internal | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func ShouldRunOnCurrentPlatform(platforms []*taskfile.Platform) bool { | ||||
| 	if len(platforms) == 0 { | ||||
| 		return true | ||||
| 	} | ||||
| 	for _, platform := range platforms { | ||||
| 		if platform.MatchesCurrentPlatform() { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|   | ||||
							
								
								
									
										11
									
								
								task_test.go
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								task_test.go
									
									
									
									
									
								
							| @@ -1696,3 +1696,14 @@ func TestUserWorkingDirectory(t *testing.T) { | ||||
| 	assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"})) | ||||
| 	assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String()) | ||||
| } | ||||
| func TestPlatforms(t *testing.T) { | ||||
| 	var buff bytes.Buffer | ||||
| 	e := task.Executor{ | ||||
| 		Dir:    "testdata/platforms", | ||||
| 		Stdout: &buff, | ||||
| 		Stderr: &buff, | ||||
| 	} | ||||
| 	assert.NoError(t, e.Setup()) | ||||
| 	assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build-" + runtime.GOOS})) | ||||
| 	assert.Equal(t, fmt.Sprintf("task: [build-%s] echo 'Running task on %s'\nRunning task on %s\n", runtime.GOOS, runtime.GOOS, runtime.GOOS), buff.String()) | ||||
| } | ||||
|   | ||||
| @@ -14,6 +14,7 @@ type Cmd struct { | ||||
| 	Vars        *Vars | ||||
| 	IgnoreError bool | ||||
| 	Defer       bool | ||||
| 	Platforms   []*Platform | ||||
| } | ||||
|  | ||||
| // Dep is a task dependency | ||||
| @@ -40,11 +41,13 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error { | ||||
| 			Cmd         string | ||||
| 			Silent      bool | ||||
| 			IgnoreError bool `yaml:"ignore_error"` | ||||
| 			Platforms   []*Platform | ||||
| 		} | ||||
| 		if err := node.Decode(&cmdStruct); err == nil && cmdStruct.Cmd != "" { | ||||
| 			c.Cmd = cmdStruct.Cmd | ||||
| 			c.Silent = cmdStruct.Silent | ||||
| 			c.IgnoreError = cmdStruct.IgnoreError | ||||
| 			c.Platforms = cmdStruct.Platforms | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
|   | ||||
							
								
								
									
										113
									
								
								taskfile/platforms.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								taskfile/platforms.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| package taskfile | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
|  | ||||
| 	"gopkg.in/yaml.v3" | ||||
| ) | ||||
|  | ||||
| // Platform represents GOOS and GOARCH values | ||||
| type Platform struct { | ||||
| 	OS   string | ||||
| 	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":   {}, | ||||
| } | ||||
|  | ||||
| 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) | ||||
| } | ||||
|  | ||||
| // 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 { | ||||
| 			return err | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	return fmt.Errorf("yaml: line %d: cannot unmarshal %s into platform", node.Line, node.ShortTag()) | ||||
| } | ||||
|  | ||||
| // 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) { | ||||
| 		p.OS = osOrArch | ||||
| 		return nil | ||||
| 	} | ||||
| 	if isSupportedArch(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) { | ||||
| 		p.Arch = arch | ||||
| 		return nil | ||||
| 	} | ||||
| 	return fmt.Errorf("task: Invalid Arch value provided (%s)", arch) | ||||
| } | ||||
| @@ -36,6 +36,7 @@ type Task struct { | ||||
| 	IncludeVars          *Vars | ||||
| 	IncludedTaskfileVars *Vars | ||||
| 	IncludedTaskfile     *IncludedTaskfile | ||||
| 	Platforms            []*Platform | ||||
| } | ||||
|  | ||||
| func (t *Task) Name() string { | ||||
| @@ -90,6 +91,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error { | ||||
| 			Prefix        string | ||||
| 			IgnoreError   bool `yaml:"ignore_error"` | ||||
| 			Run           string | ||||
| 			Platforms     []*Platform | ||||
| 		} | ||||
| 		if err := node.Decode(&task); err != nil { | ||||
| 			return err | ||||
| @@ -115,6 +117,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error { | ||||
| 		t.Prefix = task.Prefix | ||||
| 		t.IgnoreError = task.IgnoreError | ||||
| 		t.Run = task.Run | ||||
| 		t.Platforms = task.Platforms | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| @@ -150,6 +153,7 @@ func (t *Task) DeepCopy() *Task { | ||||
| 		IncludeVars:          t.IncludeVars.DeepCopy(), | ||||
| 		IncludedTaskfileVars: t.IncludedTaskfileVars.DeepCopy(), | ||||
| 		IncludedTaskfile:     t.IncludedTaskfile.DeepCopy(), | ||||
| 		Platforms:            deepCopySlice(t.Platforms), | ||||
| 	} | ||||
| 	return c | ||||
| } | ||||
|   | ||||
							
								
								
									
										55
									
								
								testdata/platforms/Taskfile.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								testdata/platforms/Taskfile.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| version: '3' | ||||
|  | ||||
| tasks: | ||||
|   build-windows: | ||||
|     platforms: [windows] | ||||
|     cmds: | ||||
|       - echo 'Running task on windows' | ||||
|  | ||||
|   build-darwin: | ||||
|     platforms: [darwin] | ||||
|     cmds: | ||||
|       - echo 'Running task on darwin' | ||||
|  | ||||
|   build-linux: | ||||
|     platforms: [linux] | ||||
|     cmds: | ||||
|       - echo 'Running task on linux' | ||||
|  | ||||
|   build-freebsd: | ||||
|     platforms: [freebsd] | ||||
|     cmds: | ||||
|       - echo 'Running task on freebsd' | ||||
|  | ||||
|   build-blank-os: | ||||
|     platforms: [] | ||||
|     cmds: | ||||
|       - echo 'Running command' | ||||
|  | ||||
|   build-multiple: | ||||
|     platforms: [] | ||||
|     cmds: | ||||
|       - cmd: echo 'Running command' | ||||
|       - cmd: echo 'Running on Windows' | ||||
|         platforms: [windows] | ||||
|       - cmd: echo 'Running on Darwin' | ||||
|         platforms: [darwin] | ||||
|  | ||||
|   build-amd64: | ||||
|     platforms: [amd64] | ||||
|     cmds: | ||||
|       - echo "Running command on amd64" | ||||
|  | ||||
|   build-arm64: | ||||
|     platforms: [arm64] | ||||
|     cmds: | ||||
|       - echo "Running command on arm64" | ||||
|  | ||||
|   build-mixed: | ||||
|     cmds: | ||||
|       - cmd: echo 'building on windows/arm64' | ||||
|         platforms: [windows/arm64] | ||||
|       - cmd: echo 'building on linux/amd64' | ||||
|         platforms: [linux/amd64] | ||||
|       - cmd: echo 'building on darwin' | ||||
|         platforms: [darwin] | ||||
| @@ -68,6 +68,7 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf | ||||
| 		Run:                  r.Replace(origTask.Run), | ||||
| 		IncludeVars:          origTask.IncludeVars, | ||||
| 		IncludedTaskfileVars: origTask.IncludedTaskfileVars, | ||||
| 		Platforms:            origTask.Platforms, | ||||
| 	} | ||||
| 	new.Dir, err = execext.Expand(new.Dir) | ||||
| 	if err != nil { | ||||
| @@ -130,6 +131,7 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf | ||||
| 				Vars:        r.ReplaceVars(cmd.Vars), | ||||
| 				IgnoreError: cmd.IgnoreError, | ||||
| 				Defer:       cmd.Defer, | ||||
| 				Platforms:   cmd.Platforms, | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user