modmake

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: May 1, 2024 License: MIT Imports: 30 Imported by: 3

README

Modmake Build System

Go Report Card

This is an attempt at creating a build system for Go using Go code and its toolchain.

For an in-depth description of how Modmake works, check out the documentation here.

To report an issue or idea for improvement, click here

Project Goals

There are a few goals I'm trying to accomplish with this system that may also explain why I didn't want to use existing systems that attempt to solve this problem. If these goals aren't compatible with yours then Modmake may not be for you, and that's okay.

  • Eliminate the need for Makefiles for non-trivial builds.
  • Eliminate the need for OS-specific scripting.
  • Make the common paths easy and discoverable.
  • Informative and consistent error reporting.
  • Common sense, consistent base structure that isn't too restrictive.
  • Allow adding custom logic where needed.
  • Avoid doing work that isn't needed.
  • Easy to construct build logic that is invoked with go run.

Benefits

Besides accomplishing the goals set out above, there are some key benefits with this approach.

  • Modmake provides a DSL-like API that is simple to use and type-safe. All the power of Go code, but without the verbosity.
  • Low barrier of entry with a consistent starting point.
  • Go code works on many OS/architecture combinations, and Modmake inherits that ability.
  • Modmake includes a lot of common-use functionality with more to come:
    • Using the Go toolchain, resolved from GOROOT.
    • File system operations like creating, copying, moving, deleting files and directories with PathString.
    • Compressing and packaging with zip/tar.
    • go installing and executing external tools.
    • Downloading files over HTTP.
    • Git operations like getting the current branch and commit hash.
    • Orchestrating build operations in terms of build steps and their dependencies.

Additional Functionality

I've started creating bolt-on functionality that can help fill more build gaps.

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrCycleDetected = errors.New("possible dependency cycle detected")
)

Functions

func F added in v0.3.0

func F(fmt string, data ...EnvMap) string

F will format a string, replacing variables with their value as found in the environment data. Additional values may be added as the second parameter, which will override values in the original environment.

Variable placeholders may be specified like ${ENV_VAR_NAME}. The example below will replace ${BUILD_NUM} with a value from the environment, or an empty string. If a variable either doesn't exist in the environment, or has an empty value, then an empty string will replace the variable placeholder.

str := F("My string that references build ${BUILD_NUM}")

Note that the "${" prefix and "}" suffix are required, but the variable name may be space padded for readability if desired. Also, variable names are case insensitive.

A default value for interpolation can be specified with a colon (":") separator after the key. In the example below, if BUILD_NUM was not defined, then the string "0" would be used instead.

str := F("My string that references build ${BUILD_NUM:0}")

Note that whitespace characters in default values will always be trimmed. The string "${" can still be expressed in F strings, but it must be formatted to use a default value, which means that interpolation cannot be recursive. The string output from the function call below will be "My string has a variable reference ${BUILD_NUM}".

str := F("My string has a variable reference ${:$}{BUILD_NUM}")
Example
fmt.Println(F("My string has a variable reference ${SOME_BUILD_NUM}?", EnvMap{
	"SOME_BUILD_NUM": "1",
}))
fmt.Println(F("My string has a variable reference ${:$}{SOME_BUILD_NUM}?"))
fmt.Println(F("My string that references build ${SOME_BUILD_NUM}."))
fmt.Println(F("My string that references build ${SOME_BUILD_NUM:0}."))
Output:

My string has a variable reference 1?
My string has a variable reference ${SOME_BUILD_NUM}?
My string that references build .
My string that references build 0.

func FReader added in v0.3.0

func FReader(in io.Reader, data ...EnvMap) []byte

FReader will do the same thing as F, but operates on an io.Reader expressing a stream of UTF-8 encoded bytes instead.

func SetLogOutput

func SetLogOutput(w io.Writer)

SetLogOutput defines where logs should be written. By default, logs are written to stderr.

func SetStepDebug

func SetStepDebug(printDebug bool)

SetStepDebug sets the global policy on debug step logs.

Types

type AppBuild added in v0.2.0

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

AppBuild is a somewhat opinionated abstraction over the common pattern of building a static executable, including packaging. The build step may be customized as needed, and different OS/Arch variants may be created as needed. Each built executable will be output to ${MODROOT}/build/${APP}_${VARIANT_NAME}/${APP} Default packaging will write a zip or tar.gz to ${MODROOT}/dist/${APP}/${APP}_${VARIANT_NAME}_${VERSION}.(zip|tar.gz) Each variant may override or remove its packaging step.

func NewAppBuild added in v0.2.0

func NewAppBuild(appName, mainPath, version string) *AppBuild

NewAppBuild creates a new AppBuild with the given details. Empty values are not allowed and will result in a panic. If mainPath is not prefixed with the module name, then it will be added.

func (*AppBuild) Build added in v0.2.0

func (a *AppBuild) Build(bf AppBuildFunc) *AppBuild

Build allows customizing the GoBuild used to build all variants, which may be further customized for a specific AppVariant.

func (*AppBuild) HostVariant added in v0.2.0

func (a *AppBuild) HostVariant() *AppVariant

HostVariant creates an AppVariant with the current host's GOOS and GOARCH settings. Packaging will be disabled by default for this variant.

func (*AppBuild) Install added in v0.3.0

func (a *AppBuild) Install(pf AppPackageFunc) *AppBuild

Install allows specifying the AppPackageFunc used for installation. This defaults to PackageGoInstall.

func (*AppBuild) NamedVariant added in v0.2.0

func (a *AppBuild) NamedVariant(variant, os, arch string) *AppVariant

NamedVariant creates an AppVariant much like AppBuild.Variant, but with a custom variant name.

func (*AppBuild) Variant added in v0.2.0

func (a *AppBuild) Variant(os, arch string) *AppVariant

Variant creates an AppVariant with the given OS and architecture settings.

type AppBuildFunc added in v0.2.0

type AppBuildFunc func(gb *GoBuild)

AppBuildFunc is a function used to customize an AppBuild or AppVariant's build step.

func (AppBuildFunc) Then added in v0.2.0

func (bf AppBuildFunc) Then(other AppBuildFunc) AppBuildFunc

type AppPackageFunc added in v0.2.0

type AppPackageFunc func(binaryPath, destDir PathString, app, variant, version string) Task

AppPackageFunc is a function used to customize an AppVariant's package step.

func PackageGoInstall added in v0.3.0

func PackageGoInstall() AppPackageFunc

PackageGoInstall will copy the binary to GOPATH/bin. This is the default packaging for the AppBuild generated install step.

func PackageTar added in v0.2.0

func PackageTar() AppPackageFunc

PackageTar will package the binary into a tar.gz. This is the default for non-windows builds.

func PackageZip added in v0.2.0

func PackageZip() AppPackageFunc

PackageZip will package the binary into a zip. This is the default for windows builds.

func (AppPackageFunc) Then added in v0.2.0

type AppVariant added in v0.2.0

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

AppVariant is a variant of an AppBuild with an OS/Arch specified.

func (*AppVariant) Build added in v0.2.0

func (v *AppVariant) Build(bf AppBuildFunc) *AppVariant

Build sets the AppBuildFunc specific to this variant. AppBuildFunc.Then may be used to combine multiple build customizations.

func (*AppVariant) NoPackage added in v0.2.0

func (v *AppVariant) NoPackage() *AppVariant

NoPackage will mark this variant as one that doesn't include a packaging step.

func (*AppVariant) Package added in v0.2.0

func (v *AppVariant) Package(pf AppPackageFunc) *AppVariant

Package sets the AppPackageFunc specific to this variant. AppPackageFunc.Then may be used to combine multiple package steps.

type Build

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

Build is a collection of related Steps. Together, they form the structure and basis for a build pipeline.

func NewBuild

func NewBuild() *Build

NewBuild constructs a new Build with the standard structure. This includes the following steps:

  • tools for installing and verifying required tooling.
  • generate for code generation.
  • test for running unit tests.
  • benchmark for running benchmarks. This step will be skipped in Build.Execute by default, unless included with a flag or called for explicitly.
  • build for building binaries.
  • package for building distributable artifacts.

func (*Build) AddNewStep added in v0.3.1

func (b *Build) AddNewStep(name, description string, run Runner) *Step

AddNewStep is a shortcut for adding a new step to a build.

func (*Build) AddStep

func (b *Build) AddStep(step *Step)

AddStep adds an existing custom step to the build.

func (*Build) Benchmark

func (b *Build) Benchmark() *Step

Benchmark allows easily referencing the benchmark Step.

func (*Build) Build

func (b *Build) Build() *Step

Build allows easily referencing the build Step.

func (*Build) Execute

func (b *Build) Execute(args ...string)

Execute executes a Build as configured, as if it were a CLI application. It takes string arguments to allow overriding the default of capturing os.Args. Run this with the -h flag to see usage information. If an error occurs within Execute, then the error will be logged and os.Exit will be called with a non-zero exit code.

Note that the build will attempt to change its working directory to the root of the module, so all filesystem paths should be relative to the root. GoTools.ToModulePath may be useful to adhere to this constraint.

Example
var (
	ranTools    bool
	ranGenerate bool
)

b := NewBuild()
b.Tools().DependsOnRunner("print-tools", "", Task(func(ctx context.Context) error {
	fmt.Println("Running in tools")
	return nil
}))
b.Tools().Does(Task(func(ctx context.Context) error {
	ranTools = true
	return nil
}))
b.Generate().Does(Task(func(ctx context.Context) error {
	ranGenerate = true
	return nil
}))
b.Package().DependsOnRunner("print-pkg", "", Task(func(ctx context.Context) error {
	fmt.Println("Running in package")
	return nil
}))
b.Execute("--skip", "tools", "--skip", "generate", "package", "print-tools")
fmt.Println("Ran tools:", ranTools)
fmt.Println("Ran generate:", ranGenerate)
Output:

Running in tools
Running in package
Ran tools: false
Ran generate: false

func (*Build) ExecuteErr

func (b *Build) ExecuteErr(args ...string) (err error)

ExecuteErr executes a Build as configured, as if it were a CLI application, and returns an error if anything goes wrong. It takes string arguments to allow overriding the default of capturing os.Args. Run this with the -h flag to see usage information. If an error occurs within Execute, then the error will be logged and os.Exit will be called with a non-zero exit code.

Note that the build will attempt to change its working directory to the root of the module, so all filesystem paths should be relative to the root. GoTools.ToModulePath may be useful to adhere to this constraint.

func (*Build) Generate

func (b *Build) Generate() *Step

Generate allows easily referencing the generate Step.

func (*Build) Graph

func (b *Build) Graph(verbose bool)
Example
b := NewBuild()
b.Tools().DependsOn(b.AddNewStep("print-tools", "", Task(func(ctx context.Context) error {
	fmt.Println("Running in tools")
	return nil
})))
b.Package().DependsOn(b.AddNewStep("print-pkg", "", Task(func(ctx context.Context) error {
	fmt.Println("Running in package")
	return nil
})))
b.Graph(true)
Output:

Printing build graph

tools - Installs external tools that will be needed later
  -> print-tools - No description
generate - Generates code, possibly using external tools
  -> tools *
test - Runs unit tests on the code base
  -> generate *
benchmark (skip step) - Runs benchmarking on the code base
  -> test *
build - Builds the code base and outputs an artifact
  -> benchmark (skip step) *
package - Bundles one or more built artifacts into one or more distributable packages
  -> build *
  -> print-pkg - No description
print-pkg *
print-tools *

* - duplicate reference

func (*Build) Import

func (b *Build) Import(prefix string, other *Build)

Import will import all steps in the given build, with the given prefix applied and a colon separator. No dependencies on the imported steps will be applied to the current build, dependencies must be applied on the parent Build after importing.

This is used to integrate build steps of components in the same go module. For building a separate go module (like a Git submodule, for example), use CallBuild.

func (b *Build) ImportAndLink(prefix string, other *Build)

ImportAndLink will import the build, and make its standard steps a dependency of the parent Build's standard steps.

func (*Build) ImportApp added in v0.2.0

func (b *Build) ImportApp(a *AppBuild)

ImportApp imports an AppBuild as a new build, attaching its build and package steps as dependencies of the parent build.

func (*Build) Package

func (b *Build) Package() *Step

Package allows easily referencing the package step.

func (*Build) Step

func (b *Build) Step(name string) *Step

func (*Build) StepOk

func (b *Build) StepOk(name string) (*Step, bool)

func (*Build) Steps

func (b *Build) Steps() []string
Example
b := NewBuild()
b.Execute("-v", "steps")
Output:

benchmark - Runs benchmarking on the code base
build - Builds the code base and outputs an artifact
generate - Generates code, possibly using external tools
package - Bundles one or more built artifacts into one or more distributable packages
test - Runs unit tests on the code base
tools - Installs external tools that will be needed later

func (*Build) Test

func (b *Build) Test() *Step

Test allows easily referencing the test Step.

func (*Build) Tools

func (b *Build) Tools() *Step

Tools allows easily referencing the tools Step.

func (*Build) Workdir

func (b *Build) Workdir(workdir string) *Build

Workdir sets the working directory for running build steps. Defaults to the current working directory of the calling executable.

type Command

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

func CallBuild

func CallBuild(buildFile PathString, args ...string) *Command

CallBuild allows easily referencing and calling another modmake build. os.Chdir will be called with the module root before go-running the build file, so the buildFile parameter should be relative to the module root. This is safe to use with Git submodules because a GoTools instance will be created based on the location of the Modmake build file and the closest module.

CallBuild is preferable over Build.Import for building separate go modules. If you're building a component of the same go module, then use Build.Import.

  • buildFile should be the filesystem path to the build that should be executed. CallBuild will panic if the file doesn't exist.
  • args are flags and steps that should be executed in the build. If none are passed, then CallBuild will panic.
Example
callHelloWorldExample := Task(func(ctx context.Context) error {
	return CallBuild("example/helloworld/build.go", "build").Run(ctx)
})
if err := callHelloWorldExample(context.TODO()); err != nil {
	panic(err)
}
Output:

func Exec

func Exec(cmdAndInitArgs ...string) *Command

Exec creates a new Command representing running an external application.

func (*Command) Arg

func (i *Command) Arg(args ...string) *Command

Arg adds the given arguments to the Command.

func (*Command) CaptureStdin

func (i *Command) CaptureStdin() *Command

CaptureStdin will make the Command pass os.Stdin to the executed process.

func (*Command) Env

func (i *Command) Env(key, value string) *Command

Env sets an environment variable for the running Command.

func (*Command) OptArg

func (i *Command) OptArg(condition bool, args ...string) *Command

OptArg will add the specified arg(s) if the condition evaluates to true.

func (*Command) Output added in v0.1.1

func (i *Command) Output(w io.Writer) *Command

Output will redirect all data written to either stdout or stderr to w.

func (*Command) Run

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

func (*Command) Silent

func (i *Command) Silent() *Command

Silent will prevent command output.

Example
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := Exec("go", "version").Silent().Run(ctx)
if err != nil {
	panic(err)
}
Output:

func (*Command) Stderr added in v0.1.1

func (i *Command) Stderr(w io.Writer) *Command

Stderr will capture all data written to the Command's stderr stream and write it to w.

func (*Command) Stdout added in v0.1.1

func (i *Command) Stdout(w io.Writer) *Command

Stdout will capture all data written to the Command's stdout stream and write it to w.

func (*Command) Task added in v0.3.1

func (i *Command) Task() Task

func (*Command) WorkDir

func (i *Command) WorkDir(workdir PathString) *Command

WorkDir sets the working directory in which to execute the Command.

type EnvMap added in v0.3.0

type EnvMap map[string]string

EnvMap is a specialized map for holding environment variables that are used to interpolate strings. Note that map keys are changed to a consistent case when merged with the environment values. So if multiple keys with the same characters but different cases are merged, then the eventual merged value is non-deterministic.

func Environment added in v0.3.0

func Environment() EnvMap

Environment returns the currently set environment values as an EnvMap.

type GoBuild

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

func (*GoBuild) AddressSanitizer

func (b *GoBuild) AddressSanitizer() *GoBuild

AddressSanitizer will enable interoperation with address sanitizer. Not all build targets are supported.

func (*GoBuild) Arch

func (b *GoBuild) Arch(arch string) *GoBuild

Arch will set the CPU architecture for the go build command using the GOARCH environment variable.

func (*GoBuild) BuildArchive

func (b *GoBuild) BuildArchive() *GoBuild

BuildArchive builds listed non-main packages into .a files. Packages named main are ignored.

func (*GoBuild) BuildCArchive

func (b *GoBuild) BuildCArchive() *GoBuild

BuildCArchive builds the listed main package, plus all packages it imports, into a C archive file. The only callable symbols will be those functions exported using a cgo //export comment. Requires exactly one main package to be listed.

func (*GoBuild) BuildCShared

func (b *GoBuild) BuildCShared() *GoBuild

BuildCShared builds the listed main package, plus all packages it imports, into a C shared library. The only callable symbols will be those functions exported using a cgo //import comment. Requires exactly one main package to be listed.

func (*GoBuild) BuildExe

func (b *GoBuild) BuildExe() *GoBuild

BuildExe will build the listed main packages and everything they import into executables. Packages not named main are ignored.

func (*GoBuild) BuildPie

func (b *GoBuild) BuildPie() *GoBuild

BuildPie will build the listed main packages and everything they import into position independent executables (PIE). Packages not named main are ignored.

func (*GoBuild) BuildPlugin

func (b *GoBuild) BuildPlugin() *GoBuild

BuildPlugin builds the listed main packages, plus all packages that they import, into a Go plugin. Packages not named main are ignored. Note that this is not supported on all build targets, and as far as I know, there are still issues today with plugins. Use at your own risk.

func (*GoBuild) BuildShared

func (b *GoBuild) BuildShared() *GoBuild

BuildShared will combine all the listed non-main packages into a single shared library that will be used when building with the -linkshared option. Packages named main are ignored.

func (*GoBuild) ChangeDir

func (b *GoBuild) ChangeDir(newDir PathString) *GoBuild

ChangeDir will change the working directory from the default to a new location. If an absolute path cannot be derived from newDir, then this function will panic.

func (*GoBuild) DryRun

func (b *GoBuild) DryRun() *GoBuild

DryRun will print the commands, but not run them.

func (*GoBuild) Env

func (b *GoBuild) Env(key, value string) *GoBuild

func (*GoBuild) ForceRebuild

func (b *GoBuild) ForceRebuild() *GoBuild

ForceRebuild will force all source to be recompiled, rather than relying on build cache.

func (*GoBuild) GoCompileFlags

func (b *GoBuild) GoCompileFlags(flags ...string) *GoBuild

GoCompileFlags sets arguments to pass to each go tool compile invocation.

func (*GoBuild) LinkerFlags

func (b *GoBuild) LinkerFlags(flags ...string) *GoBuild

LinkerFlags sets linker flags (ldflags) values for this build.

func (*GoBuild) MemorySanitizer

func (b *GoBuild) MemorySanitizer() *GoBuild

MemorySanitizer will enable interoperation with memory sanitizer. Not all build targets are supported.

func (*GoBuild) OS

func (b *GoBuild) OS(os string) *GoBuild

OS sets the target OS for the go build command using the GOOS environment variable.

func (*GoBuild) OutputFilename

func (b *GoBuild) OutputFilename(filename PathString) *GoBuild

OutputFilename specifies the name of the built artifact.

func (*GoBuild) PrintCommands

func (b *GoBuild) PrintCommands() *GoBuild

PrintCommands will print commands as they are executed.

func (*GoBuild) PrintPackages

func (b *GoBuild) PrintPackages() *GoBuild

PrintPackages will print the names of built packages.

func (*GoBuild) Private

func (b *GoBuild) Private(privateHosts ...string) *GoBuild

Private specifies private hosts that should not go through proxy.golang.org for resolution.

func (*GoBuild) RaceDetector

func (b *GoBuild) RaceDetector() *GoBuild

RaceDetector will enable race detection.

func (*GoBuild) Run

func (b *GoBuild) Run(ctx context.Context) error

func (*GoBuild) SetVariable

func (b *GoBuild) SetVariable(pkg, varName, value string) *GoBuild

SetVariable sets an ldflag to set a variable at build time. The pkg parameter should be main, or the fully-qualified package name. The variable referenced doesn't have to be exposed (starting with a capital letter).

Examples

It's a little counter-intuitive how this works, but it's based on how the go tools themselves work.

Main package

Given a module named 'example.com/me/myproject', and a package directory named 'build' with go files in package 'main', the pkg parameter should just be 'main'.

Non-main package

Given a module named 'example.com/me/myproject', and a package directory named 'build' with go files in package 'other', the pkg parameter should be 'example.com/me/myproject/build'.

GoTools.ToModulePackage is provided as a convenience to make it easier to create these reference strings.

See this article for more examples.

func (*GoBuild) Silent

func (b *GoBuild) Silent() *GoBuild

func (*GoBuild) StripDebugSymbols

func (b *GoBuild) StripDebugSymbols() *GoBuild

StripDebugSymbols will remove debugging information from the built artifact, reducing file size. Assumes TrimPath as well.

func (*GoBuild) Tags

func (b *GoBuild) Tags(tags ...string) *GoBuild

Tags sets build tags to be activated.

func (*GoBuild) Task added in v0.3.1

func (b *GoBuild) Task() Task

func (*GoBuild) TrimPath added in v0.4.0

func (b *GoBuild) TrimPath() *GoBuild

TrimPath will remove build host filesystem path prefix information from the binary.

func (*GoBuild) Verbose

func (b *GoBuild) Verbose() *GoBuild

Verbose will print both commands and built packages.

func (*GoBuild) Workdir

func (b *GoBuild) Workdir(workdir PathString) *GoBuild

type GoClean

type GoClean struct {
	*Command
	// contains filtered or unexported fields
}

func (*GoClean) BuildCache

func (c *GoClean) BuildCache() *GoClean

func (*GoClean) FuzzCache

func (c *GoClean) FuzzCache() *GoClean

func (*GoClean) ModCache

func (c *GoClean) ModCache() *GoClean

func (*GoClean) Run

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

func (*GoClean) Task added in v0.3.1

func (c *GoClean) Task() Task

func (*GoClean) TestCache

func (c *GoClean) TestCache() *GoClean

type GoTools

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

GoTools provides some utility functions for interacting with the go tool chain. To get an instance of GoTools, use the Go function. Upon first invocation, Go will cache filesystem and module details for reference later.

To invalidate this cache, use GoTools.InvalidateCache.

If your build logic needs to cross into another go module, try using CallBuild.

func Go

func Go() *GoTools

Go will retrieve or initialize an instance of GoTools. This indirection is desirable to support caching of tool chain, filesystem, and module details. This function is concurrency safe, and may be called by multiple goroutines if desired.

func (*GoTools) Benchmark

func (g *GoTools) Benchmark(pattern string) *Command

func (*GoTools) BenchmarkAll

func (g *GoTools) BenchmarkAll() *Command

func (*GoTools) Build

func (g *GoTools) Build(targets ...string) *GoBuild

Build creates a GoBuild to hold parameters for a new go build run.

func (*GoTools) Clean

func (g *GoTools) Clean() *GoClean

func (*GoTools) Command

func (g *GoTools) Command(command string, args ...string) *Command

func (*GoTools) Generate

func (g *GoTools) Generate(patterns ...string) *Command

func (*GoTools) GenerateAll

func (g *GoTools) GenerateAll() *Command

func (*GoTools) Get

func (g *GoTools) Get(pkg string) *Command

func (*GoTools) GetEnv added in v0.3.0

func (g *GoTools) GetEnv(key string) string

GetEnv will call "go env $key", and return the value of the named environment variable. If an error occurs, then the call will panic.

func (*GoTools) GetUpdated

func (g *GoTools) GetUpdated(pkg string) *Command

func (*GoTools) Install

func (g *GoTools) Install(pkg string) *Command

func (*GoTools) InvalidateCache

func (g *GoTools) InvalidateCache()

InvalidateCache will break the instance cache, forcing the next call to Go to scan the filesystem's information again.

func (*GoTools) ModTidy

func (g *GoTools) ModTidy() *Command

func (*GoTools) ModuleName

func (g *GoTools) ModuleName() string

ModuleName returns the name of the current module as specified in the go.mod.

func (*GoTools) ModuleRoot

func (g *GoTools) ModuleRoot() PathString

ModuleRoot returns a filesystem path to the root of the current module.

func (*GoTools) Run

func (g *GoTools) Run(target string, args ...string) *Command

func (*GoTools) Test

func (g *GoTools) Test(patterns ...string) *Command

func (*GoTools) TestAll

func (g *GoTools) TestAll() *Command

func (*GoTools) ToModulePackage

func (g *GoTools) ToModulePackage(pkg string) string

ToModulePackage is specifically provided to construct a package reference for GoBuild.SetVariable by prepending the module name to the package name, separated by '/'. This is not necessary for setting variables in the main package, as 'main' can be used instead.

// When run in a module named 'example.com/me/myproject', this will output 'example.com/me/myproject/other'.
Go().ToModulePackage("other")

See GoBuild.SetVariable for more details.

func (*GoTools) ToModulePath

func (g *GoTools) ToModulePath(dir string) string

ToModulePath takes a path to a file or directory within the module, relative to the module root, and translates it to a module path. If a path to a file is given, then a module path to the file's parent directory is returned. If ToModulePath is unable to stat the given path, then this function will panic.

For example, given a module name of 'github.com/example/mymodule', and a relative path of 'app/main.go', the module path 'github.com/example/mymodule/app' is returned.

type PathString

type PathString string

PathString is a string that represents a filesystem path.

String inputs to all PathString functions, including Path, is expected to be a filesystem path with forward slash ('/') separators. These will be translated to actual OS filesystem path strings, making them incompatible with module path strings on Windows.

func Getwd

func Getwd() (PathString, error)

Getwd gets the current working directory as a PathString like os.Getwd.

func Path

func Path(path string, segments ...string) PathString

Path creates a new PathString from the input path segments.

func UserHomeDir

func UserHomeDir() (PathString, error)

UserHomeDir will return the current user's home directory as a PathString.

func (PathString) Abs added in v0.2.1

func (p PathString) Abs() (PathString, error)

Abs attempts to translate the PathString into an absolute path using filepath.Abs.

func (PathString) Base

func (p PathString) Base() PathString

Base calls filepath.Base on the string representation of this PathString, returning the last element of the path. Trailing path separators are removed before extracting the last element. If the path is empty, Base returns ".". If the path consists entirely of separators, Base returns a single separator.

func (PathString) Cat added in v0.3.0

func (p PathString) Cat() ([]byte, error)

Cat will - assuming the PathString points to a file - read all data from the file and return it as a byte slice.

func (PathString) Chdir

func (p PathString) Chdir() error

Chdir changes the current working directory to the named directory like os.Chdir.

func (PathString) CopyTo

func (p PathString) CopyTo(other PathString) error

CopyTo copies the contents of the file referenced by this PathString to the file referenced by other, creating or truncating the file.

func (PathString) Create

func (p PathString) Create() (*os.File, error)

Create will create or truncate the named file like os.Create.

func (PathString) Dir

func (p PathString) Dir() PathString

Dir calls filepath.Dir on the string representation of this PathString, returning all but the last element of the path. After dropping the final element, Dir calls filepath.Clean on the path and trailing slashes are removed. If the path is empty, Dir returns ".". If the path consists entirely of separators, Dir returns a single separator. The returned path does not end in a separator unless it is the root directory.

func (PathString) Exists

func (p PathString) Exists() bool

Exists returns true if the path references an existing file or directory.

func (PathString) IsDir

func (p PathString) IsDir() bool

IsDir returns true if this PathString references an existing directory.

func (PathString) IsFile

func (p PathString) IsFile() bool

IsFile returns true if this PathString references a file that exists, and it is not a directory.

func (PathString) Join

func (p PathString) Join(segments ...string) PathString

Join will append path segments to this PathString and return a new PathString.

func (PathString) JoinPath

func (p PathString) JoinPath(segments ...PathString) PathString

JoinPath will append PathString segments to this PathString and return a new PathString.

func (PathString) Mkdir

func (p PathString) Mkdir(mode os.FileMode) error

Mkdir creates a new directory with the specified name and permissions like os.Mkdir.

func (PathString) MkdirAll

func (p PathString) MkdirAll(mode os.FileMode) error

MkdirAll creates the named directory and any non-existent path in between like os.MkdirAll.

func (PathString) Open

func (p PathString) Open() (*os.File, error)

Open opens the named file for reading like os.Open.

func (PathString) OpenFile

func (p PathString) OpenFile(flag int, mode os.FileMode) (*os.File, error)

OpenFile is a generalized open call like os.OpenFile.

func (PathString) Rel added in v0.2.1

func (p PathString) Rel(other PathString) (PathString, error)

Rel attempts to construct a relative path to other, with the current PathString as the base, much like filepath.Rel.

func (PathString) Remove

func (p PathString) Remove() error

Remove removes the named file or directory like os.Remove.

func (PathString) RemoveAll

func (p PathString) RemoveAll() error

RemoveAll removes the path and any children it contains like os.RemoveAll.

func (PathString) Stat

func (p PathString) Stat() (os.FileInfo, error)

Stat will return os.FileInfo for the file referenced by this PathString like os.Stat.

func (PathString) String

func (p PathString) String() string

String returns this PathString as a string.

func (PathString) ToSlash

func (p PathString) ToSlash() string

ToSlash will change the PathString to use slash separators if the OS representation is different.

type RunState

type RunState int

RunState indicates the state of a Step.

const (
	StateNotRun     RunState = iota // StateNotRun means that this Step has not yet run.
	StateSuccessful                 // StateSuccessful means that the Step has already run successfully.
	StateFailed                     // StateFailed means that the Step has already run and failed.
)

type Runner

type Runner interface {
	// Run should immediately execute in the current goroutine when called to ensure predictable build semantics.
	// Run may initiate other goroutines, but they should complete and be cleaned up before Run returns.
	Run(context.Context) error
}

Runner is any type that may be executed.

func ContextAware

func ContextAware(r Runner) Runner

ContextAware creates a Runner that wraps the parameter with context handling logic. In the event that the context is done, the context's error is returned. This should not be used if custom context.Context handling is desired.

func Script

func Script(fns ...Runner) Runner

Script will execute each Task in order, returning the first error.

type Step

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

Step is a step in a Build, a consistent fixture that may be invoked. A Step may depend on others to set up pre-conditions that must be done before this Step executes. Additionally, a Step may have actions that take place before and/or after this Step runs.

Operations on a Step will change the underlying logic. The current Step will be returned as a convenience to allow chaining multiple mutations.

func NewStep

func NewStep(name, description string) *Step

NewStep creates a new Step with the given name and description. If no description is given (indicated by an empty string), then the default description "No description" will be assigned. By default, Step.Run will do nothing, have no dependencies, and have no before/after hooks.

func (*Step) AfterRun

func (s *Step) AfterRun(op Runner) *Step

AfterRun adds an operation that will execute after this Step. AfterRun operations will happen before any dependent Step.

func (*Step) BeforeRun

func (s *Step) BeforeRun(op Runner) *Step

BeforeRun adds an operation that will execute before this Step. BeforeRun operations will happen after this Step's dependencies.

func (*Step) Debounce added in v0.4.0

func (s *Step) Debounce(interval time.Duration) Task

func (*Step) Debug

func (s *Step) Debug(msg string, args ...any)

Debug emits log messages that are really only useful for digging deep into how a build step is executing.

func (*Step) DependsOn

func (s *Step) DependsOn(dependency *Step) *Step

DependsOn makes this Step depend on the given Step. The given step must execute successfully for this Step to be executed.

func (*Step) DependsOnRunner

func (s *Step) DependsOnRunner(name, description string, fn Runner) *Step

DependsOnRunner wraps the given Runner in a Step using the name and description parameters, and calls DependsOn with it.

func (*Step) Does

func (s *Step) Does(operation Runner) *Step

Does specifies the operation that should happen as a result of executing this Step.

func (*Step) DryRun

func (s *Step) DryRun(ctx context.Context) error

func (*Step) Error

func (s *Step) Error(msg string, args ...any)

Error emits log messages about errors while performing a step.

func (*Step) Info

func (s *Step) Info(msg string, args ...any)

Info emits informational log messages that are generally useful for user awareness.

func (*Step) ResetState added in v0.4.0

func (s *Step) ResetState() *Step

ResetState resets the state of a Step such that it can be run again. This includes all of its dependencies.

func (*Step) Run

func (s *Step) Run(ctx context.Context) error

func (*Step) Skip

func (s *Step) Skip() *Step

Skip will skip execution of this step, including its before/after hooks. Dependencies will still be executed unless SkipDependencies is executed.

func (*Step) SkipDependencies

func (s *Step) SkipDependencies() *Step

SkipDependencies will prevent running dependency Steps for this Step.

func (*Step) UnSkip

func (s *Step) UnSkip() *Step

UnSkip is the opposite of Skip, and is useful in the case where a Step is skipped by default.

func (*Step) Warn

func (s *Step) Warn(msg string, args ...any)

Warn emits warning log messages that indicate something might not be right.

type TarArchive

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

TarArchive represents a Runner that performs operations on a tar archive that uses gzip compression. The Runner is created with Tar.

func Tar

func Tar(location PathString) *TarArchive

Tar will create a new TarArchive to contextualize follow-on operations that act on a tar.gz file. Adding a ".tar.gz" suffix to the location path string is recommended, but not required.

func (*TarArchive) AddFile

func (t *TarArchive) AddFile(sourcePath PathString) *TarArchive

AddFile adds the referenced file with the same archive path as what is given. The archive path will be converted to slash format.

func (*TarArchive) AddFileWithPath

func (t *TarArchive) AddFileWithPath(sourcePath, archivePath PathString) *TarArchive

AddFileWithPath adds the referenced file with an archive path specified. The archive path will be converted to slash format.

func (*TarArchive) Create

func (t *TarArchive) Create() Task

Create will return a Runner that creates a new tar file with the given files loaded. If a file with the given name already exists, then it will be truncated first. Ensure that all files referenced with AddFile (or AddFileWithPath) and directories exist before running this Runner, because it doesn't try to create them.

func (*TarArchive) Extract

func (t *TarArchive) Extract(extractDir PathString) Task

Extract will extract the named tar archive to the given directory. Any errors encountered while doing so will be immediately returned.

func (*TarArchive) Update

func (t *TarArchive) Update() Task

Update will return a Runner that creates a new tar file with the given files loaded. If a file with the given name already exists, then it will be updated. If a file with the given name does not exist, then this Runner will return an error. Ensure that all files referenced with AddFile (or AddFileWithPath) and directories exist before running this Runner, because it doesn't try to create them.

type Task

type Task func(ctx context.Context) error

Task is a convenient way to make a function that satisfies the Runner interface, and allows for more flexible invocation options.

func CallRemote added in v0.3.0

func CallRemote(module string, buildPath PathString, args ...string) Task

CallRemote will execute a Modmake build on a remote module. The module parameter is the module name and version in the same form as would be used to 'go get' the module. The buildPath parameter is the path within the module, relative to the module root, where the Modmake build file is located. Finally, the args parameters are all steps and flags that should be used to invoke the build.

Example
callHelloWorldExample := Task(func(ctx context.Context) error {
	module := "github.com/saylorsolutions/modmake@v0.2.2"
	buildPath := Path("example/helloworld/build.go")
	return CallRemote(module, buildPath, "build").Run(context.TODO())
})
if err := callHelloWorldExample(context.TODO()); err != nil {
	panic(err)
}
Output:

func Chdir

func Chdir(newWorkdir PathString, runner Runner) Task

Chdir will change the current working directory to newWorkdir and run the Runner in that context. Whether the Runner executes successfully or not, the working directory will be reset back to its original state.

func CopyFile

func CopyFile(source, target PathString) Task

CopyFile creates a Runner that copies a source file to target. The source and target file names are expected to be relative to the build's working directory, unless they are absolute paths. The target file will be created or truncated as appropriate.

func Download

func Download(url string, location PathString) Task

func Error

func Error(msg string, args ...any) Task

Error will create a Task returning an error, creating it by passing msg and args to fmt.Errorf.

func IfError

func IfError(canErr Runner, handler Runner) Task

IfError will create a Runner with an error handler Runner that is only executed if the base Runner returns an error. If both the base Runner and the handler return an error, then the handler's error will be returned.

Example
canError := Error("An error occurred!")
err := IfError(canError, Print("Error handled")).Run(context.Background())
if err != nil {
	fmt.Println("Error should not have been returned:", err)
}
Output:

func IfExists

func IfExists(file PathString, r Runner) Task

IfExists will execute the Runner if the file exists, returning nil otherwise.

func IfNotExists

func IfNotExists(file PathString, r Runner) Task

IfNotExists will skip executing the Runner if the given file exists, returning nil. This is similar to the default Make behavior of skipping a task if the target file already exists.

func Mkdir

func Mkdir(dir PathString, perm os.FileMode) Task

Mkdir makes a directory named dir, if it doesn't exist already. If the directory already exists, then nothing is done and err will be nil. Any error encountered while making the directory is returned.

func MkdirAll

func MkdirAll(dir PathString, perm os.FileMode) Task

MkdirAll makes the target directory, and any directories in between. Any error encountered while making the directories is returned.

func MoveFile

func MoveFile(source, target PathString) Task

MoveFile creates a Runner that will move the file indicated by source to target. The source and target file names are expected to be relative to the build's working directory, unless they are absolute paths. The target file will be created or truncated as appropriate.

func NoOp

func NoOp() Task

NoOp is a Task placeholder that immediately returns nil.

func Plain

func Plain(fn func()) Task

Plain is a convenience function that translates a no-argument, no-return function into a Task, combining the logic of WithoutContext and WithoutErr.

func Print

func Print(msg string, args ...any) Task

Print will create a Runner that logs a message.

func RemoveDir

func RemoveDir(file PathString) Task

RemoveDir will create a Runner that removes the directory specified and all of its contents from the filesystem. If the directory doesn't exist, then this Runner returns nil. Any other error encountered while removing the directory is returned.

func RemoveFile

func RemoveFile(file PathString) Task

RemoveFile will create a Runner that removes the specified file from the filesystem. If the file doesn't exist, then this Runner returns nil. Any other error encountered while removing the file is returned.

func WithoutContext

func WithoutContext(fn func() error) Task

WithoutContext is a convenience function that handles the inbound context.Context in cases where it isn't needed. If the context is cancelled when this Task executes, then the context's error will be returned.

func WithoutErr

func WithoutErr(fn func(context.Context)) Task

WithoutErr is a convenience function that allows passing a function that should never return an error and translating it to a Task. The returned Task will recover panics by returning them as errors.

func (Task) Catch

func (t Task) Catch(catch func(error) Task) Task

Catch runs the catch function if this Task returns an error.

func (Task) Debounce added in v0.4.0

func (t Task) Debounce(interval time.Duration) Task

func (Task) Run

func (t Task) Run(ctx context.Context) error

func (Task) Then

func (t Task) Then(other Runner) Task

Then returns a Task that runs if this Task executed successfully.

type ZipArchive

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

ZipArchive represents a Runner that performs operations on a tar archive that uses gzip compression. The Runner is created with Zip.

func Zip

func Zip(location PathString) *ZipArchive

Zip will create a new ZipArchive to contextualize follow-on operations that act on a zip file. Adding a ".zip" suffix to the location path string is recommended, but not required.

func (*ZipArchive) AddFile

func (z *ZipArchive) AddFile(sourcePath PathString) *ZipArchive

AddFile adds the referenced file with the same archive path as what is given. The archive path will be converted to slash format.

func (*ZipArchive) AddFileWithPath

func (z *ZipArchive) AddFileWithPath(sourcePath, archivePath PathString) *ZipArchive

AddFileWithPath adds the referenced file with an archive path specified. The archive path will be converted to slash format.

func (*ZipArchive) Create

func (z *ZipArchive) Create() Task

Create will return a Runner that creates a new zip file with the given files loaded. If a file with the given name already exists, then it will be truncated first. Ensure that all files referenced with AddFile (or AddFileWithPath) and directories exist before running this Runner, because it doesn't try to create them.

func (*ZipArchive) Extract

func (z *ZipArchive) Extract(extractDir PathString) Task

Extract will extract the named zip archive to the given directory. Any errors encountered while doing so will be immediately returned.

func (*ZipArchive) Update

func (z *ZipArchive) Update() Task

Update will return a Runner that creates a new zip file with the given files loaded. If a file with the given name already exists, then it will be updated. If a file with the given name does not exist, then this Runner will return an error. Ensure that all files referenced with AddFile (or AddFileWithPath) and directories exist before running this Runner, because it doesn't try to create them.

Directories

Path Synopsis
cmd
example
pkg
git

Jump to

Keyboard shortcuts

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