Categorygithub.com/jgbaldwinbrown/makem
repositorypackage
0.0.0-20220125004522-d20fc593b6e7
Repository: https://github.com/jgbaldwinbrown/makem.git
Documentation: pkg.go.dev

# README

Makem

Short for "Make 'em", this small library assists in the generation of makefiles for running large numbers of concurrent jobs. It is a lightweight alternative to programs such as snakemake. Its advantage is in building parallel pipelines using the Go language, rather than attempting complex substitutions using a more traditional make-like tool. The tool is very explicit, and can be overly verbose for very small makefiles. It excels, however, when dealing with very large files. It is great for pipelines that will use hundreds or thousands of inputs and outputs, as recipes can be written programmatically rather than one-by-one. Being written in an easy-to-use, high-level, general-purpose language allows for arbitrarily complex scripting.

all:

Installation

Simply run:

go get github.com/jgbaldwinbrown/makem

Then, in your file, import the library as follows:

import (
	"github.com/jgbaldwinbrown/makem"
)

A simple example

This complete example creates a makefile, then runs it using all available computing cores. If the . The makefile specifies that 7 new files, a0, a1, a2, a3, a4, b0, b1, and b2, should be created using "touch" and "cat", with the "b" files depending on the "a" files.

package main

import (
	"fmt"
	"github.com/jgbaldwinbrown/makem"
)

func main() {
	makefile := new(makem.MakeData)

	/* Add targets in a for loop to generate multiple similar recipes: */

	for i:=0; i<5; i++ {
		name := fmt.Sprintf("a%d", i)
		new_rec := makem.Recipe{}
		new_rec.AddTargets(name)
		new_rec.AddScripts(fmt.Sprintf("touch %s", name))
		makefile.Add(new_rec)
	}

	/* Multiple targets and dependencies are possible, as are multiple scripts per recipe: */

	new_rec := makem.Recipe{}
	new_rec.AddTargets("b0", "b1")
	new_rec.AddDeps("a0", "a1")
	new_rec.AddScripts("cat a0 > b0", "cat a1 > b1")
	makefile.Add(new_rec)

	/* Alternative literal syntax: */

	makefile.Add(makem.Recipe{
		Targets: []string{"b2"},
		Deps: []string{"a2"},
		Scripts: []string{cat a2 > b2"},
	})

	/* Run the makefile */

	makefile.Exec(makem.UseAllCores())
}

If the makefile were printed using the line makefile.Fprint(os.Stdout), it would produce the following output:

all: a0 a1 a2 a3 a4 b0 b1 b2

a0:
	touch a0

a1:
	touch a1

a2:
	touch a2

a3:
	touch a3

a4:
	touch a4

b0 b1: a0 a1
	cat a0 > b0
	cat a1 > b1
b2: a2
	cat a2 > b2

Documentation

MakeData

type MakeData struct {
	All Recipe
	Preamble string
	Recipes []Recipe
}

This is the main type in the library. It holds all of the recipes in the makefile, plus the special "All" recipe which depends on all other recipes, plus any preamble added by the user.

MakeData.Add

func (m *MakeData) Add(rs ...Recipe)

This function adds a set of recipes to the makefile.

MakeData.Fprint

func (m *MakeData) Fprint(w io.Writer)

This method prints the full makefile to the specified io.Writer.

MakeData.Exec

func (m *MakeData) Exec(options ...ExecOption) (err error)

This method runs the makefile with the specified options. It return nil on success and an error on failure.

MakeData.AppendPreamble

func (m *MakeData) AppendPreamble(s string)

This appends to the existing preamble. Note that, for full flexibility, no trailing newline is appended by default.

MakeData.SetPreamble

func (m *MakeData) SetPreamble(s string)

This sets the preamble. Note that, for full flexibility, no trailing newline is appended by default. This is a good place to put build options and make-specific variables.

Recipe

type Recipe struct {
	Targets []string
	Deps []string
	Scripts []string
}

A recipe consists of a target file to create, all dependencies of the target, and all scripts used to generate the target. Tabs will automatically be prepended to script lines. Multiple targets and multiple dependencies are supported, and should be represented as multiple items in a []string. Recipes are intended to be generated with the helper methods below, but can be generated with literals as well:

myrecipe := Recipe {
	Targets: []string{"a.txt", "b.txt"},
	Deps: []string{"c.txt"},
	Scripts: []string{"cat c.txt > a.txt", "cat c.txt > b.txt"},
}

Recipe.AddTargets

func (r *Recipe) AddTargets(ts ...string)

Add the specified targets to the recipe.

Recipe.AddDeps

func (r *Recipe) AddDeps(ts ...string)

Add the specified dependencies to the recipe.

Recipe.AddScripts

func (r *Recipe) AddScripts(ts ...string)

Add the specified scripts to the recipe.

Recipe.Fprint

func (r Recipe) Fprint(w io.Writer)

Print the recipe to a specified io.Writer.

FprintRecipes

FprintRecipes(w io.Writer, rs ...Recipe)

Print all specified recipes to an io.Writer.

ExecInternal

type ExecInternal struct {
	Parallel bool
	Cores int
	AllCores bool
}

This is mainly for internal use. It holds all of the options for executing the makefile. These should not be set manually, but rather should be set using an ExecOption function.

ExecOption

type ExecOption func(e *ExecInternal)

This type is the only argument type for the Exec

UseCores

func UseCores(corenum int) ExecOption

This function is used as an option when running "MakeData.Exec". It sets Exec to use a fixed number of cores (corenum) when running. Example:

m := MakeData{}
/* ... */
done := m.Exec(UseCores(8))

UseAllCores

func UseAllCores() ExecOption

This function is used as an option when running "MakeData.Exec". It sets Exec to use all available cores (corenum) when running. Example:

m := MakeData{}
/* ... */
done := m.Exec(UseAllCores())