goyek

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2022 License: MIT Imports: 15 Imported by: 7

README

goyek

Create build pipelines in Go

Go Reference Keep a Changelog GitHub Release go.mod LICENSE

Build Status Go Report Card codecov Mentioned in Awesome Go

Please ⭐ Star this repository if you find it valuable and worth maintaining.

Table of Contents:

Description

goyek (/ˈɡɔɪæk/ 🔊 listen) is used to create build pipelines in Go. As opposed to many other tools, it is just a Go library.

Here are some good parts:

goyek API is mainly inspired by the testing, http, and flag packages.

Quick start

Copy and paste the following code into build/build.go:

package main

import (
	"github.com/goyek/goyek"
)

func main() {
	flow := &goyek.Flow{}

	flow.Register(goyek.Task{
		Name:  "hello",
		Usage: "demonstration",
		Action: func(tf *goyek.TF) {
			tf.Log("Hello world!")
		},
	})

	flow.Main()
}

Run:

go mod tidy

Sample usage:

$ go run ./build -h
Usage: [flag(s) | task(s)]...
Flags:
  -v     Default: false    Verbose: log all tasks as they are run.
  -wd    Default: .        Working directory: set the working directory.
Tasks:
  hello    demonstration
$ go run ./build hello
ok     0.000s
$ go run ./build hello -v
===== TASK  hello
      main.go:14: Hello world!
----- PASS: hello (0.00s)
ok      0.001s

Wrapper scripts

Instead of executing go run ./build, you can use the wrapper scripts, which can be invoked from any location.

Simply add them to your repository's root directory and make sure to add the +x permission:

curl -sSfL https://raw.githubusercontent.com/goyek/goyek/main/goyek.sh -O
curl -sSfL https://raw.githubusercontent.com/goyek/goyek/main/goyek.ps1 -O
git add --chmod=+x goyek.sh goyek.ps1

Repository template

You can use goyek/template to create a new repository

For an existing repository you can copy most of its files.

Examples

Features

Task registration

The registered tasks are required to have a non-empty name, matching the regular expression TaskNamePattern. This means the following are acceptable:

  • letters (a-z and A-Z)
  • digits (0-9)
  • underscore (_)
  • hyphen (-) - except at the beginning
  • plus (+) - except at the beginning
  • colon (:) - except at the beginning

A task with a given name can be only registered once.

A task without description is not listed in CLI usage.

Task action

Task action is a function which is executed when a task is executed.

It is not required to set a action. Not having a action is very handy when registering "pipelines".

Task dependencies

During task registration it is possible to add a dependency to an already registered task. When the flow is processed, it makes sure that the dependency is executed before the current task is run. Take note that each task will be executed at most once.

Helpers for running programs

Use func (tf *TF) Cmd(name string, args ...string) *exec.Cmd to run a program inside a task's action.

You can use it create your own helpers, for example:

import (
	"fmt"
	"os/exec"

	"github.com/goyek/goyek"
	"github.com/mattn/go-shellwords"
)

func Exec(cmdLine string) func(tf *goyek.TF) {
	args, err := shellwords.Parse(cmdLine)
	if err != nil {
		panic(fmt.Sprintf("parse command line: %v", err))
	}
	return func(tf *goyek.TF) {
    tf.Logf("Run '%s'", cmdLine)
		if err := tf.Cmd(args[0], args[1:]...).Run(); err != nil {
			tf.Error(err)
		}
	}
}

Here is the explanation why argument splitting is not included out-of-the-box.

Verbose mode

Enable verbose output using the -v CLI flag. It works similar to go test -v. Verbose mode streams all logs to the output. If it is disabled, only logs from failed task are send to the output.

Use func (f *Flow) VerboseParam() BoolParam if you need to check if verbose mode was set within a task's action.

The default value for the verbose parameter can be overwritten with func (f *Flow) RegisterVerboseParam(p BoolParam) RegisteredBoolParam.

Default task

Default task can be assigned via the Flow.DefaultTask field.

When the default task is set, then it is run if no task is provided via CLI.

Parameters

The parameters can be set via CLI using the flag syntax.

On the CLI, flags can be set in the following ways:

  • -param simple - for simple single-word values
  • -param "value with blanks"
  • -param="value with blanks"
  • -param - setting boolean parameters implicitly to true

For example, ./goyek.sh test -v -pkg ./... would run the test task with v bool parameter (verbose mode) set to true, and pkg string parameter set to "./...".

Parameters must first be registered via func (f *Flow) RegisterValueParam(newValue func() ParamValue, info ParamInfo) ValueParam, or one of the provided methods like RegisterStringParam.

The registered parameters are required to have a non-empty name, matching the regular expression ParamNamePattern. This means the following are acceptable:

  • letters (a-z and A-Z)
  • digits (0-9)
  • underscore (_) - except at the beginning
  • hyphen (-) - except at the beginning
  • plus (+) - except at the beginning
  • colon (:) - except at the beginning

After registration, tasks need to specify which parameters they will read. Do this by assigning the RegisteredParam instance from the registration result to the Task.Params field. If a task tries to retrieve the value from an unregistered parameter, the task will fail.

When registration is done, the task's action can retrieve the parameter value using the Get(*TF) method from the registration result instance during the task's Action execution.

See examples/parameters/main.go for a detailed example.

See examples/validation/main.go for a detailed example of cross-parameter validation.

Flow will fail execution if there are unused parameters.

Supported Go versions

Minimal supported Go version is 1.11.

Alternatives

Make

While Make (Makefile) is currently the de facto standard, it has some pitfalls:

  • Requires to learn Make, which is not so easy.
  • It is hard to develop a Makefile which is truly cross-platform.
  • Debugging and testing Make targets is not fun.

However, if you know Make and are happy with it, do not change it. Make is very powerful and a lot of stuff can be made faster, if you know how to use it.

goyek is intended to be simpler and easier to learn, more portable, while still being able to handle most use cases.

Mage

Mage is a framework/tool which magically discovers the targets from magefiles, which results in some drawbacks:

  • Requires using build tags.
  • Reusing tasks is hacky.
  • Requires installation or using zero install option which is slow.
  • Debugging would be extermly complex.
  • Magical by design (of course one may like it).

goyek is a non-magical alternative for Mage. Write regular Go code. No build tags, special names for functions, tricky imports.

Task

While Task is simpler and easier to use than Make, but it still has similar problems:

  • Requires to learn Task's YAML structure and the minimalistic, cross-platform interpreter which it uses.
  • Debugging and testing tasks is not fun.
  • Hard to make reusable tasks.
  • Requires to "install" the tool.
Bazel

Bazel is a very sophisticated tool which is created to efficiently handle complex and long-running build pipelines. It requires the build target inputs and outputs to be fully specified.

goyek is just a simple Go library that is mainly supposed to create a build pipeline consisting of commands like go vet, go test, go build. However, nothing prevents you from, for example, using the Mage's target package to make your build pipeline more efficient.

Presentations

Date Presentation Description
2021-05-05 goyek - Create build pipelines in Go goyek v0.3.0 demo
2020-12-14 Build pipeline for a Go project build pipeline using Make, Mage, and taskflow v0.1.0

Note: goyek was named taskflow before v0.3.0.

Contributing

See CONTRIBUTING.md if you want to help us.

License

goyek is licensed under the terms of the MIT license.

Documentation

Overview

Package goyek helps implementing build automation.

Example
package main

import (
	"github.com/goyek/goyek"
)

func main() {
	flow := &goyek.Flow{}

	task1 := flow.Register(goyek.Task{
		Name:  "task-1",
		Usage: "Print Go version",
		Action: func(tf *goyek.TF) {
			if err := tf.Cmd("go", "version").Run(); err != nil {
				tf.Fatal(err)
			}
		},
	})

	task2 := flow.Register(goyek.Task{
		Name: "task-2",
		Action: func(tf *goyek.TF) {
			tf.Skip("skipping")
		},
	})

	task3 := flow.Register(goyek.Task{
		Name: "task-3",
		Action: func(tf *goyek.TF) {
			tf.Error("hello from", tf.Name())
			tf.Log("this will be printed")
		},
	})

	flow.Register(goyek.Task{
		Name: "all",
		Deps: goyek.Deps{task1, task2, task3},
	})

	flow.Main()
}
Output:

Index

Examples

Constants

View Source
const (
	// CodePass indicates that flow passed.
	CodePass = 0
	// CodeFail indicates that flow failed.
	CodeFail = 1
	// CodeInvalidArgs indicates that flow got invalid input.
	CodeInvalidArgs = 2
)
View Source
const (
	// TaskNamePattern describes the regular expression a task name must match.
	TaskNamePattern = "^[a-zA-Z0-9_][a-zA-Z0-9:_+-]*$"

	// ParamNamePattern describes the regular expression a parameter name must match.
	ParamNamePattern = "^[a-zA-Z0-9][a-zA-Z0-9:_+-]*$"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type BoolParam

type BoolParam struct {
	Name    string
	Usage   string
	Default bool
}

BoolParam represents a named boolean parameter that can be registered.

type Deps

type Deps []RegisteredTask

Deps represents a collection of registered Tasks.

type Flow added in v0.6.0

type Flow struct {
	Output io.Writer // output where text is printed; os.Stdout by default

	DefaultTask RegisteredTask // task which is run when non is explicitly provided
	// contains filtered or unexported fields
}

Flow is the root type of the package. Use Register methods to register all tasks and Run or Main method to execute provided tasks.

func (*Flow) Main added in v0.6.0

func (f *Flow) Main()

Main parses the command-line arguments and runs the provided tasks. The usage is printed when invalid arguments are passed.

func (*Flow) Params added in v0.6.0

func (f *Flow) Params() []RegisteredParam

Params returns all registered parameters.

func (*Flow) Register added in v0.6.0

func (f *Flow) Register(task Task) RegisteredTask

Register registers the task. It panics in case of any error.

func (*Flow) RegisterBoolParam added in v0.6.0

func (f *Flow) RegisterBoolParam(p BoolParam) RegisteredBoolParam

RegisterBoolParam registers a boolean parameter.

func (*Flow) RegisterIntParam added in v0.6.0

func (f *Flow) RegisterIntParam(p IntParam) RegisteredIntParam

RegisterIntParam registers an integer parameter.

func (*Flow) RegisterStringParam added in v0.6.0

func (f *Flow) RegisterStringParam(p StringParam) RegisteredStringParam

RegisterStringParam registers a string parameter.

func (*Flow) RegisterValueParam added in v0.6.0

func (f *Flow) RegisterValueParam(p ValueParam) RegisteredValueParam

RegisterValueParam registers a generic parameter that is defined by the calling code. Use this variant in case the primitive-specific implementations cannot cover the parameter.

The value is provided via a factory function since flow could be executed multiple times, requiring a new Value instance each time.

func (*Flow) RegisterVerboseParam added in v0.6.1

func (f *Flow) RegisterVerboseParam(p BoolParam) RegisteredBoolParam

RegisterVerboseParam overwrites the default name, usage and value for the verbose parameter. If this function is used, the default 'v' parameter will be replaced with this parameter.

func (*Flow) RegisterWorkDirParam added in v0.6.1

func (f *Flow) RegisterWorkDirParam(p StringParam) RegisteredStringParam

RegisterWorkDirParam overwrites the default name, usage and value for the work dir parameter. If this function is used, the default 'wd' parameter will be replaced with this parameter.

func (*Flow) Run added in v0.6.0

func (f *Flow) Run(ctx context.Context, args ...string) int

Run runs provided tasks and all their dependencies. Each task is executed at most once.

func (*Flow) Tasks added in v0.6.0

func (f *Flow) Tasks() []RegisteredTask

Tasks returns all registered tasks.

func (*Flow) VerboseParam added in v0.6.0

func (f *Flow) VerboseParam() RegisteredBoolParam

VerboseParam returns the out-of-the-box verbose parameter which controls the output behavior. This can be overridden with RegisterVerboseParam.

func (*Flow) WorkDirParam added in v0.6.0

func (f *Flow) WorkDirParam() RegisteredStringParam

WorkDirParam returns the out-of-the-box working directory parameter which controls the working directory.

type IntParam

type IntParam struct {
	Name    string
	Usage   string
	Default int
}

IntParam represents a named integer parameter that can be registered.

type ParamError

type ParamError struct {
	Key string // the parameter's key
	Err error  // the reason the conversion failure, e.g. *strconv.NumError
}

ParamError records an error during parameter conversion.

func (*ParamError) Error

func (e *ParamError) Error() string

func (*ParamError) Unwrap

func (e *ParamError) Unwrap() error

Unwrap unpacks the wrapped error.

type ParamValue

type ParamValue interface {
	// String returns the current value formatted as string.
	// The returned format should be in a single line, representing the parameter
	// as it could be provided on the command line.
	String() string
	// IsBool marks parameters that do not explicitly need to be set a value.
	// Set will be called in case the flag is not explicitly parameterized.
	IsBool() bool
	// Get returns the current value, properly typed.
	// Values must return their default value if Set() has not yet been called.
	Get() interface{}
	// Set parses the given string and sets the typed value.
	Set(string) error
}

ParamValue represents an instance of a generic parameter.

type Params

type Params []RegisteredParam

Params represents a collection of registered Params.

type RegisteredBoolParam

type RegisteredBoolParam struct {
	// contains filtered or unexported fields
}

RegisteredBoolParam represents a registered boolean parameter.

func (RegisteredBoolParam) Default added in v0.6.0

func (p RegisteredBoolParam) Default() string

Default returns the parameter's default value formatted as string.

func (RegisteredBoolParam) Get

func (p RegisteredBoolParam) Get(tf *TF) bool

Get returns the boolean value of the parameter in the given flow.

func (RegisteredBoolParam) Name

func (p RegisteredBoolParam) Name() string

Name returns the key of the parameter.

func (RegisteredBoolParam) Usage added in v0.6.0

func (p RegisteredBoolParam) Usage() string

Usage returns the parameter's description.

type RegisteredIntParam

type RegisteredIntParam struct {
	// contains filtered or unexported fields
}

RegisteredIntParam represents a registered integer parameter.

func (RegisteredIntParam) Default added in v0.6.0

func (p RegisteredIntParam) Default() string

Default returns the parameter's default value formatted as string.

func (RegisteredIntParam) Get

func (p RegisteredIntParam) Get(tf *TF) int

Get returns the integer value of the parameter in the given flow.

func (RegisteredIntParam) Name

func (p RegisteredIntParam) Name() string

Name returns the key of the parameter.

func (RegisteredIntParam) Usage added in v0.6.0

func (p RegisteredIntParam) Usage() string

Usage returns the parameter's description.

type RegisteredParam

type RegisteredParam interface {
	Name() string
	Usage() string
	Default() string
	// contains filtered or unexported methods
}

RegisteredParam represents a parameter that has been registered to a Flow. It can be used as a parameter for a Task.

type RegisteredStringParam

type RegisteredStringParam struct {
	// contains filtered or unexported fields
}

RegisteredStringParam represents a registered string parameter.

func (RegisteredStringParam) Default added in v0.6.0

func (p RegisteredStringParam) Default() string

Default returns the parameter's default value formatted as string.

func (RegisteredStringParam) Get

func (p RegisteredStringParam) Get(tf *TF) string

Get returns the string value of the parameter in the given flow.

func (RegisteredStringParam) Name

func (p RegisteredStringParam) Name() string

Name returns the key of the parameter.

func (RegisteredStringParam) Usage added in v0.6.0

func (p RegisteredStringParam) Usage() string

Usage returns the parameter's description.

type RegisteredTask

type RegisteredTask struct {
	// contains filtered or unexported fields
}

RegisteredTask represents a task that has been registered to a Flow. It can be used as a dependency for another Task.

func (RegisteredTask) Deps added in v0.6.0

func (r RegisteredTask) Deps() Deps

Deps returns the task's dependencies.

func (RegisteredTask) Name added in v0.6.0

func (r RegisteredTask) Name() string

Name returns the name of the task.

func (RegisteredTask) Params added in v0.6.0

func (r RegisteredTask) Params() Params

Params returns the task's parameters.

func (RegisteredTask) Usage added in v0.6.0

func (r RegisteredTask) Usage() string

Usage returns the description of the task.

type RegisteredValueParam

type RegisteredValueParam struct {
	// contains filtered or unexported fields
}

RegisteredValueParam represents a registered parameter based on a generic implementation.

func (RegisteredValueParam) Default added in v0.6.0

func (p RegisteredValueParam) Default() string

Default returns the parameter's default value formatted as string.

func (RegisteredValueParam) Get

func (p RegisteredValueParam) Get(tf *TF) interface{}

Get returns the concrete instance of the generic value in the given flow.

func (RegisteredValueParam) Name

func (p RegisteredValueParam) Name() string

Name returns the key of the parameter.

func (RegisteredValueParam) Usage added in v0.6.0

func (p RegisteredValueParam) Usage() string

Usage returns the parameter's description.

type StringParam

type StringParam struct {
	Name    string
	Usage   string
	Default string
}

StringParam represents a named string parameter that can be registered.

type TF

type TF struct {
	// contains filtered or unexported fields
}

TF is a type passed to Task's Action function to manage task state.

A Task ends when its Action function returns or calls any of the methods FailNow, Fatal, Fatalf, SkipNow, Skip, or Skipf.

All methods must be called only from the goroutine running the Action function.

func (*TF) Cmd

func (tf *TF) Cmd(name string, args ...string) *exec.Cmd

Cmd is like exec.Command, but it assigns tf's context and assigns Stdout and Stderr to tf's output, and Stdin to os.Stdin.

func (*TF) Context

func (tf *TF) Context() context.Context

Context returns the flows' run context.

func (*TF) Error

func (tf *TF) Error(args ...interface{})

Error is equivalent to Log followed by Fail.

func (*TF) Errorf

func (tf *TF) Errorf(format string, args ...interface{})

Errorf is equivalent to Logf followed by Fail.

func (*TF) Fail

func (tf *TF) Fail()

Fail marks the function as having failed but continues execution.

func (*TF) FailNow

func (tf *TF) FailNow()

FailNow marks the function as having failed and stops its execution by calling runtime.Goexit (which then runs all deferred calls in the current goroutine). It finishes the whole flow.

func (*TF) Failed

func (tf *TF) Failed() bool

Failed reports whether the function has failed.

func (*TF) Fatal

func (tf *TF) Fatal(args ...interface{})

Fatal is equivalent to Log followed by FailNow.

func (*TF) Fatalf

func (tf *TF) Fatalf(format string, args ...interface{})

Fatalf is equivalent to Logf followed by FailNow.

func (*TF) Log

func (tf *TF) Log(args ...interface{})

Log formats its arguments using default formatting, analogous to Println, and prints the text to Output. A final newline is added. The text will be printed only if the task fails or flow is run in Verbose mode.

func (*TF) Logf

func (tf *TF) Logf(format string, args ...interface{})

Logf formats its arguments according to the format, analogous to Printf, and prints the text to Output. A final newline is added. The text will be printed only if the task fails or flow is run in Verbose mode.

func (*TF) Name

func (tf *TF) Name() string

Name returns the name of the running task.

func (*TF) Output

func (tf *TF) Output() io.Writer

Output returns the io.Writer used to print output.

func (*TF) Skip

func (tf *TF) Skip(args ...interface{})

Skip is equivalent to Log followed by SkipNow.

func (*TF) SkipNow

func (tf *TF) SkipNow()

SkipNow marks the task as having been skipped and stops its execution by calling runtime.Goexit (which then runs all deferred calls in the current goroutine). If a test fails (see Error, Errorf, Fail) and is then skipped, it is still considered to have failed. Flow will continue at the next task.

func (*TF) Skipf

func (tf *TF) Skipf(format string, args ...interface{})

Skipf is equivalent to Logf followed by SkipNow.

func (*TF) Skipped

func (tf *TF) Skipped() bool

Skipped reports whether the task was skipped.

type Task

type Task struct {
	// Name uniquely identifies the task.
	// Names may not be empty and should be easily representable on the CLI.
	Name string

	// Usage provides information what the task does.
	// If it is empty, this task will not be listed in the usage output.
	Usage string

	// Action executes the task in the given flow context.
	// A task can be registered without a action and can act as a "collector" task
	// for a list of dependencies.
	Action func(tf *TF)

	// Deps lists all registered tasks that need to be run before this task is executed.
	Deps Deps

	// Params is a list of registered parameters that the action may need during executions.
	// Not all parameters need to be queried during execution, yet accessing a parameter
	// that was not registered will fail the task.
	Params Params
}

Task represents a named task that can be registered. It can consist of a action (function that will be called when task is run), dependencies (tasks which has to be run before this one) parameters (which can be used within the action).

type ValueParam

type ValueParam struct {
	Name     string
	Usage    string
	NewValue func() ParamValue
}

ValueParam represents a named parameter for a custom type that can be registered. NewValue field must be set with a default value factory.

Directories

Path Synopsis
build module
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL