fab

package module
v0.4.1 Latest Latest
Warning

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

Go to latest
Published: Aug 17, 2022 License: MIT Imports: 15 Imported by: 0

README

Fab - software fabricator

This is fab, a system for orchestrating software builds in Go.

Like Make, fab executes recipes to turn inputs into outputs, making sure to build prerequisites before the targets that depend on them, and avoiding recompilation of targets that don’t need it.

Unlike Make, recipes are written in Go. (Which is not to say that what you’re building has to be in Go; it doesn’t.)

How it works

You create a package of Go code in your project. By default the fab program looks for the package in the .fab subdir. Every exported symbol in that package whose type satisfies the fab.Target interface is a target that fab can run.

Under the hood

When you run fab, it compiles a new Go binary using your package of build rules and a custom main function.

Why not Mage?

Fab was strongly inspired by Mage, which has a similar feature set. However, the author found Mage a little cumbersome for a couple of particular use cases:

  • Adding persistent hashes of targets to determine when running one can be skipped, because its outputs are already up to date with respect to its inputs.
  • Propagating errors outward from within target implementations.
  • Defining targets as the result of suitably typed expressions assigned to top-level vars, instead of having to be Go funcs.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DefaultRunner = NewRunner()

DefaultRunner is a Runner used by default in Run.

View Source
var GoFiles embed.FS

GoFiles is an embedded filesystem containing the *.go files, plus go.mod and go.sum, of the fab package.

Functions

func GetDir

func GetDir(ctx context.Context) string

GetDir returns the value of the directory added to `ctx` with WithDir. The default, if WithDir was not used, is the empty string.

func GetVerbose

func GetVerbose(ctx context.Context) bool

GetVerbose returns the value of the verbose boolean added to `ctx` with WithVerbose. The default, if WithVerbose was not used, is false.

func ID

func ID(prefix string) string

ID produces an ID string by appending a unique counter value to the given prefix.

func Run

func Run(ctx context.Context, targets ...Target) error

Run runs the given targets with a Runner. If `ctx` contains a Runner (e.g., because this is a recursive call and the ctx has been decorated using WithRunner) then it uses that Runner, otherwise it uses DefaultRunner.

func WithDir

func WithDir(ctx context.Context, dir string) context.Context

WithDir decorates a context with the path to a directory in which rules should run. Retrieve it with GetDir.

func WithHashDB

func WithHashDB(ctx context.Context, db HashDB) context.Context

WithHashDB decorates a context with a HashDB. Retrieve it with GetHashDB.

func WithRunner

func WithRunner(ctx context.Context, r *Runner) context.Context

WithRunner decorates a context with a Runner. Retrieve it with GetRunner.

func WithVerbose

func WithVerbose(ctx context.Context, verbose bool) context.Context

WithVerbose decorates a context with the value of a "verbose" boolean. Retrieve it with GetVerbose.

Types

type Command

type Command struct {
	// Shell is the command to run,
	// as a single string with command name and arguments together.
	// It is parsed as if by a Unix shell,
	// with quoting and so on,
	// in order to produce the command name
	// and a list of individual argument strings.
	//
	// To bypass this parsing behavior,
	// you may specify Cmd and Args directly.
	Shell string

	// Cmd is the command to invoke,
	// either the path to a file,
	// or an executable file found in some directory
	// named in the PATH environment variable.
	//
	// Leave Cmd blank and specify Shell instead
	// to get shell-like parsing of a command and its arguments.
	Cmd string

	// Args is the list of command-line arguments
	// to pass to the command named in Cmd.
	Args []string

	// Dir is the directory in which to run the command.
	// The default is the value of GetDir(ctx) when the Run method is called.
	Dir string

	// Env is a list of VAR=VALUE strings to add to the environment when the command runs.
	Env []string

	// Verbose tells whether to connect the command's stdout and stderr
	// to this process's stdout and stderr.
	// If it's false,
	// the output is saved anyway in case the command fails.
	// If it does,
	// the returned error will be a CommandErr
	// containing the process's (combined) output.
	Verbose bool
	// contains filtered or unexported fields
}

Command is a target whose Run function executes a command in a subprocess.

func (*Command) ID

func (c *Command) ID() string

func (*Command) Run

func (c *Command) Run(ctx context.Context) error

type CommandErr

type CommandErr struct {
	Err    error
	Output []byte
}

CommandErr is a type of error that may be returned from Command.Run. If output was suppressed (because the Verbose field was false) this contains both the underlying error and the subprocess's combined output.

func (CommandErr) Error

func (e CommandErr) Error() string

Error implements error.Error.

func (CommandErr) Unwrap

func (e CommandErr) Unwrap() error

Unwrap produces the underlying error.

type FilesCommand

type FilesCommand struct {
	*Command
	In  []string
	Out []string
}

FilesCommand is a HashTarget. It contains a Command, a list of input files, and a list of expected output files.

func (FilesCommand) Hash

func (fc FilesCommand) Hash(_ context.Context) ([]byte, error)

Hash implements HashTarget.

type HashDB

type HashDB interface {
	// Has tells whether the database contains the given entry.
	Has(context.Context, []byte) (bool, error)

	// Add adds an entry to the database.
	Add(context.Context, []byte) error
}

HashDB is the type of a database for storing hashes. It must permit concurrent operations safely. It may expire entries to save space.

func GetHashDB

func GetHashDB(ctx context.Context) HashDB

GetHashDB returns the value of the HashDB added to `ctx` with WithHashDB. The default, if WithHashDB was not used, is nil.

type HashTarget

type HashTarget interface {
	Target
	Hash(context.Context) ([]byte, error)
}

HashTarget is a Target that knows how to produce a hash (or "digest") representing the complete state of the target: the inputs, the outputs, and the rules for turning one into the other. Any change in any of those should produce a distinct hash value.

When a HashTarget is executed by Runner.Run, its Run method is skipped and it succeeds trivially if the Runner can determine that the outputs are up to date with respect to the inputs and build rules. It does this by consulting a HashDB that is populated with the hashes of HashTargets whose Run methods succeeded in the past.

Using such a hash to decide whether a target's outputs are up to date is preferable to using file modification times (like Make does, for example). Those aren't always sufficient for this purpose, nor are they entirely reliable, considering the limited resolution of filesystem timestamps, the possibility of clock skew, etc.

type Runner

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

Runner is an object that knows how to run Targets without ever running the same Target twice.

A zero runner is not usable. Use NewRunner to obtain one instead.

func GetRunner

func GetRunner(ctx context.Context) *Runner

GetRunner returns the value of the Runner added to `ctx` with WithRunner. The default, if WithRunner was not used, is nil.

func NewRunner

func NewRunner() *Runner

NewRunner produces a new Runner.

func (*Runner) Run

func (r *Runner) Run(ctx context.Context, targets ...Target) error

Run runs the given targets, skipping any that have already run.

THEORY OF OPERATION

A Runner remembers which targets it has already run (whether in this call or any previous call to Run), distinguishing targets by their ID() values.

A separate goroutine is created for each Target passed to Run. If the Runner has never yet run the Target, it does so, and caches the result (error or no error). If the Target did already run, the cached error value is used. If another goroutine concurrently requests the same Target, it blocks until the first one completes, then uses the first one's result.

As a special case, if the Target is a HashTarget and there is a HashDB attached to the context, then the HashTarget's hash is computed and sought in the HashDB. If it's found, the target's outputs are already up to date and its Run method can be skipped. Otherwise, Run is invoked and (if it succeeds) a new hash is computed for the target and added to the HashDB.

This function waits for all goroutines to complete. The return value may be an accumulation of multiple errors. These can be retrieved with multierr.Errors.

The runner is added to the context with WithRunner and can be retrieved with GetRunner. Calls to Run (the global function, not the Runner method) will use this Runner instead of DefaultRunner by finding it in the context.

type Target

type Target interface {
	// Run invokes the target's logic.
	//
	// Callers generally should not invoke a target's Run method.
	// Instead, pass the target to a Runner's Run method,
	// or to the global Run function.
	// That will handle concurrency properly
	// and make sure that the target is not rerun
	// when it doesn't need to be.
	Run(context.Context) error

	// ID is a unique ID for the target.
	ID() string
}

Target is the interface that Fab targets must implement.

func All added in v0.3.0

func All(targets ...Target) Target

All produces a target that runs a collection of targets in parallel.

func Deps added in v0.3.0

func Deps(target Target, depTargets ...Target) Target

Deps wraps a target with a set of dependencies, making sure those run first.

It is equivalent to Seq(All(depTargets...), target).

func F

func F(f func(context.Context) error) Target

F produces a target whose Run function invokes the given function.

func Named added in v0.3.0

func Named(name string, target Target) Target

Named produces a Target with a give name prefix (the suffix is a session-unique number) that wraps another Target.

func Seq added in v0.4.0

func Seq(targets ...Target) Target

Seq produces a target that runs a collection of targets in sequence. It exits early when a target in the sequence fails.

Directories

Path Synopsis
cmd
fab command

Jump to

Keyboard shortcuts

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