mirror of
https://github.com/go-task/task.git
synced 2025-01-06 03:53:54 +02:00
feat: merger
This commit is contained in:
parent
6854b4c300
commit
fb9f6c20ab
3
setup.go
3
setup.go
@ -79,6 +79,9 @@ func (e *Executor) readTaskfile(node taskfile.Node) error {
|
|||||||
if err := graph.Visualize("./taskfile-dag.gv"); err != nil {
|
if err := graph.Visualize("./taskfile-dag.gv"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if e.Taskfile, err = graph.Merge(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
package ast
|
package ast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/dominikbraun/graph"
|
"github.com/dominikbraun/graph"
|
||||||
"github.com/dominikbraun/graph/draw"
|
"github.com/dominikbraun/graph/draw"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TaskfileGraph struct {
|
type TaskfileGraph struct {
|
||||||
@ -31,11 +35,118 @@ func NewTaskfileGraph() *TaskfileGraph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *TaskfileGraph) Visualize(filename string) error {
|
func (tfg *TaskfileGraph) Visualize(filename string) error {
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
return draw.DOT(r.Graph, f)
|
return draw.DOT(tfg.Graph, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tfg *TaskfileGraph) Merge() (*Taskfile, error) {
|
||||||
|
hashes, err := graph.TopologicalSort(tfg.Graph)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
predecessorMap, err := tfg.PredecessorMap()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(hashes) - 1; i >= 0; i-- {
|
||||||
|
hash := hashes[i]
|
||||||
|
|
||||||
|
// Get the current vertex
|
||||||
|
vertex, err := tfg.Vertex(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an error group to wait for all the included Taskfiles to be merged with all its parents
|
||||||
|
var g errgroup.Group
|
||||||
|
|
||||||
|
// Loop over each adjacent edge
|
||||||
|
for _, edge := range predecessorMap[hash] {
|
||||||
|
|
||||||
|
// TODO: Enable goroutines
|
||||||
|
// Start a goroutine to process each included Taskfile
|
||||||
|
// g.Go(
|
||||||
|
err := func() error {
|
||||||
|
// Get the child vertex
|
||||||
|
predecessorVertex, err := tfg.Vertex(edge.Source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the merge options
|
||||||
|
include, ok := edge.Properties.Data.(*Include)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("task: Failed to get merge options")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle advanced imports
|
||||||
|
// i.e. where additional data is given when a Taskfile is included
|
||||||
|
if include.AdvancedImport {
|
||||||
|
predecessorVertex.Taskfile.Vars.Range(func(k string, v Var) error {
|
||||||
|
o := v
|
||||||
|
o.Dir = include.Dir
|
||||||
|
predecessorVertex.Taskfile.Vars.Set(k, o)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
predecessorVertex.Taskfile.Env.Range(func(k string, v Var) error {
|
||||||
|
o := v
|
||||||
|
o.Dir = include.Dir
|
||||||
|
predecessorVertex.Taskfile.Env.Set(k, o)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
for _, task := range vertex.Taskfile.Tasks.Values() {
|
||||||
|
task.Dir = filepathext.SmartJoin(include.Dir, task.Dir)
|
||||||
|
if task.IncludeVars == nil {
|
||||||
|
task.IncludeVars = &Vars{}
|
||||||
|
}
|
||||||
|
task.IncludeVars.Merge(include.Vars)
|
||||||
|
task.IncludedTaskfileVars = vertex.Taskfile.Vars
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge the included Taskfile into the parent Taskfile
|
||||||
|
if err := predecessorVertex.Taskfile.Merge(
|
||||||
|
vertex.Taskfile,
|
||||||
|
include,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all the go routines to finish
|
||||||
|
if err := g.Wait(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the root vertex
|
||||||
|
rootVertex, err := tfg.Vertex(hashes[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rootVertex.Taskfile.Tasks.Range(func(name string, task *Task) error {
|
||||||
|
if task == nil {
|
||||||
|
task = &Task{}
|
||||||
|
rootVertex.Taskfile.Tasks.Set(name, task)
|
||||||
|
}
|
||||||
|
task.Task = name
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return rootVertex.Taskfile, nil
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package ast
|
package ast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -13,6 +14,9 @@ const NamespaceSeparator = ":"
|
|||||||
|
|
||||||
var V3 = semver.MustParse("3")
|
var V3 = semver.MustParse("3")
|
||||||
|
|
||||||
|
// ErrIncludedTaskfilesCantHaveDotenvs is returned when a included Taskfile contains dotenvs
|
||||||
|
var ErrIncludedTaskfilesCantHaveDotenvs = errors.New("task: Included Taskfiles can't have dotenv declarations. Please, move the dotenv declaration to the main Taskfile")
|
||||||
|
|
||||||
// Taskfile is the abstract syntax tree for a Taskfile
|
// Taskfile is the abstract syntax tree for a Taskfile
|
||||||
type Taskfile struct {
|
type Taskfile struct {
|
||||||
Location string
|
Location string
|
||||||
@ -36,6 +40,9 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
|
|||||||
if !t1.Version.Equal(t2.Version) {
|
if !t1.Version.Equal(t2.Version) {
|
||||||
return fmt.Errorf(`task: Taskfiles versions should match. First is "%s" but second is "%s"`, t1.Version, t2.Version)
|
return fmt.Errorf(`task: Taskfiles versions should match. First is "%s" but second is "%s"`, t1.Version, t2.Version)
|
||||||
}
|
}
|
||||||
|
if len(t2.Dotenv) > 0 {
|
||||||
|
return ErrIncludedTaskfilesCantHaveDotenvs
|
||||||
|
}
|
||||||
if t2.Output.IsSet() {
|
if t2.Output.IsSet() {
|
||||||
t1.Output = t2.Output
|
t1.Output = t2.Output
|
||||||
}
|
}
|
||||||
|
@ -54,20 +54,25 @@ func (t1 *Tasks) Merge(t2 Tasks, include *Include) {
|
|||||||
// taskfile are marked as internal
|
// taskfile are marked as internal
|
||||||
task.Internal = task.Internal || (include != nil && include.Internal)
|
task.Internal = task.Internal || (include != nil && include.Internal)
|
||||||
|
|
||||||
// Add namespaces to dependencies, commands and aliases
|
// Add namespaces to task dependencies
|
||||||
for _, dep := range task.Deps {
|
for _, dep := range task.Deps {
|
||||||
if dep != nil && dep.Task != "" {
|
if dep != nil && dep.Task != "" {
|
||||||
dep.Task = taskNameWithNamespace(dep.Task, include.Namespace)
|
dep.Task = taskNameWithNamespace(dep.Task, include.Namespace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add namespaces to task commands
|
||||||
for _, cmd := range task.Cmds {
|
for _, cmd := range task.Cmds {
|
||||||
if cmd != nil && cmd.Task != "" {
|
if cmd != nil && cmd.Task != "" {
|
||||||
cmd.Task = taskNameWithNamespace(cmd.Task, include.Namespace)
|
cmd.Task = taskNameWithNamespace(cmd.Task, include.Namespace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add namespaces to task aliases
|
||||||
for i, alias := range task.Aliases {
|
for i, alias := range task.Aliases {
|
||||||
task.Aliases[i] = taskNameWithNamespace(alias, include.Namespace)
|
task.Aliases[i] = taskNameWithNamespace(alias, include.Namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add namespace aliases
|
// Add namespace aliases
|
||||||
if include != nil {
|
if include != nil {
|
||||||
for _, namespaceAlias := range include.Aliases {
|
for _, namespaceAlias := range include.Aliases {
|
||||||
|
@ -140,7 +140,7 @@ func (r *Reader) include(node Node) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create an edge between the Taskfiles
|
// Create an edge between the Taskfiles
|
||||||
err = r.graph.AddEdge(node.Location(), includeNode.Location())
|
err = r.graph.AddEdge(node.Location(), includeNode.Location(), graph.EdgeData(include))
|
||||||
if errors.Is(err, graph.ErrEdgeAlreadyExists) {
|
if errors.Is(err, graph.ErrEdgeAlreadyExists) {
|
||||||
edge, err := r.graph.Edge(node.Location(), includeNode.Location())
|
edge, err := r.graph.Edge(node.Location(), includeNode.Location())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -16,9 +16,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrIncludedTaskfilesCantHaveDotenvs is returned when a included Taskfile contains dotenvs
|
|
||||||
ErrIncludedTaskfilesCantHaveDotenvs = errors.New("task: Included Taskfiles can't have dotenv declarations. Please, move the dotenv declaration to the main Taskfile")
|
|
||||||
|
|
||||||
defaultTaskfiles = []string{
|
defaultTaskfiles = []string{
|
||||||
"Taskfile.yml",
|
"Taskfile.yml",
|
||||||
"taskfile.yml",
|
"taskfile.yml",
|
||||||
@ -29,7 +26,6 @@ var (
|
|||||||
"Taskfile.dist.yaml",
|
"Taskfile.dist.yaml",
|
||||||
"taskfile.dist.yaml",
|
"taskfile.dist.yaml",
|
||||||
}
|
}
|
||||||
|
|
||||||
allowedContentTypes = []string{
|
allowedContentTypes = []string{
|
||||||
"text/plain",
|
"text/plain",
|
||||||
"text/yaml",
|
"text/yaml",
|
||||||
|
Loading…
Reference in New Issue
Block a user