Skip to content

Latest commit

 

History

History
731 lines (522 loc) · 28.8 KB

minion.md

File metadata and controls

731 lines (522 loc) · 28.8 KB

Minion Reference

Introduction

To use Minion, your makefile include minion.mk after defining all Minion-related definitions (usually at the last line in the makefule).

The file minion.mk is self-contained; it depends on no other files. Minion is tested with GNU Make v3.81.

If you invoke make without any arguments, Minion will attempt to build a target named default, which your makefile may define using an alias (described below) or an ordinary Make rule.

If you pass arguments on the command line, Minion will treat the arguments as a set of goals to be built, and will build them. Each goal may be one of the following:

Ingredients

Within your makefile, each file that is an input to (prerequisite for) a Minion-generated target is identified by one of the following kinds of target IDs:

  1. The name of the file. This will be the case when naming a source file or a file generated by an ordinary Make rule.

  2. The instance that generates the file. This will be the case for files generated by Minion rules.

An ingredients list is a space-delimited list of IDs or indirections. Each indirection will be expanded to zero or more IDs.

Instances

An instance is an expression that takes the form: CLASS(ARGS).

Instances describe build steps for which Minion generates all the required Make rules. For example, CC(hello.c) describes the build step of compiling hello.c to generate an object file. Typing make 'CC(hello.c)' will build the object file, and using CC(hello.c) in an ingredient list will identify the object file as an input and prerequisite.

Aliases

Aliases are names that may be used on the command line as goals.

If your makefile defines a variable named Alias(NAME).in or Alias(NAME).command, then NAME is a valid alias. The value of the .in variable, if defined, is an ingredient list that identifies what the alias will cause to be built. The value of the .command variable, if defined, is a shell command to be executed when the alias is named as a goal. If both are defined, the command will be executed after all the ingredients are buit.

When defined, Alias(NAME) is a valid instance, so make NAME is really just a shorthand for make 'Alias(NAME)'. Alias names can be specified as goals (that is, on the make command line), but they are not supported in ingredient lists (in your makefile). If you want to make use of an alias definition in an ingredient list, you can use its instance name. For example:

Alias(all).in = Alias(default) Alias(tests)

Indirections

An indirection is an expression that takes the form @GROUP or CLASS@GROUP. GROUP is the name of a varible, or if it contains *, it is a pattern to be expanded with $(wildcard ...).

Indirections may appear in ingredient lists or on the command line. When Minion examines a goal or an ingredient list, it performs an expansion step, replacing indirections with the files/instances they reference. The result of this expansion is zero or more instances or file names.

The two forms are called "simple" and "mapped":

  • @GROUP : a simple indirection
  • CLASS@GROUP : a mapped indirection

A simple indirection expands to the members of GROUP. If GROUP is a variable name, then that value might contain other indirections, in which case they will be expanded recursively.

A mapped indirection applies the CLASS constructor individually to the names resulting from the expansion of @GROUP. For example, if @sources expands to a.c b.c, then CC@sources will expand to CC(a.c) CC(b.c).

Mapped indirections can also use a "chained" syntax to apply two or more classes at once. Using the same sources variable as in the example above, the indirection CExe@CC@sources would expand to CExe(CC(a.c)) CExe(CC(b.c)).

Indirections may not contain the characters ( or ). For example, the instance Link(CC@sources) is not treated as an indirection, and it will not be altered during the expansion step. Its argument, CC@sources, is an indirection, so when the inputs for Link(CC@sources) are computed the result will be CC(a.c) CC(b.c).

Classes

The behavior of an instance is determined by a set of properties that are computed for the instance. Property definitions are stored in Make variables whose names take the form CLASS.PROPERTY or CLASS(ARGS).PROPERTY, with the latter, instance-specific form taking precedence if both are defined.

Each class can inherit definitions from other classes. A variable named CLASS.inherit, if defined, is a space-delimited list of classes from which definitions will be inherited. When Minion is computing a property, if it finds no definition for that property associated with its instance name or its class name, then it will look for a definition of that property associated with one of the inherited class names. It examines them in order, using the first definition it finds. An inherited class can, in turn, inherit from other classes.

The values associated with definitions can, as with ordinary Make variables, use $(...) to substitute Make variables and call functions. However, they may not use ${...}, because { and } are reserved for Minion-specific functionality. Expressions of the form {...} expand to the value of the property named by .... The string {inherit} expands to the inherited value of the property currently being evaluated -- the value it would have taken on if the current definition had not been present. To include an actual { or } character in a property definition, use $([[) or $(]]).

While a property definition is being expanded, various functions and variables exported by Minion are available for use. These can be used directly within a property definition, or within a function or variable that is referenced by a property definition.

Minion property evaluation is memoized, so each property definition is expanded no more than once for each instance.

Note that instances have no associated "state". All property definitions are purely functional in nature. As such, there is no notion of "creating" or "destroying" instances. We cannot say whether an instance "exists" in a makefile or not ... but we can talk about whether it is mentioned in a goal or an ingredient list. You can think of a class as a function -- somewhat elaborate and multifacted, but ultimately a function -- that yields a set of property definitions. An instance identifies the function (class) and its inputs (argument list).

Simple and Recursive Variables

Make supports two "flavors" of variables, "simple" (using :=) and "recursive" (using =). The above discussion assumes that properties are defined using recursive (=) assignments. Properties may be defined using simple assignments. With :=, values are expanded when and where the assignment appears (prior to inclusion of minion.mk), so they may not make use of {...} syntax or Minion exported functions and variables.

Argument Lists

An instance's argument list may contain multiple comma-delimited values, each with an optional NAME: prefix. Arguments without such a prefix are called "unnamed arguments". For example, Link(a.o,b.o) has an argument list with two unnamed values. Copy(@program,out:deploy/program) has one unnamed argument and one named argument.

Remember that argument lists, being part of an instance, may not contain whitespace.

Within a property definition, the _args variable and the _namedArgs and _namedArg1 functions, described below, can be used to access the named and unnamed arguments of the current instance.

Cached Rules

Ordinarily, every time you invoke make, Minion computes the rule property for all goals and their transitive dependencies prior to Make's rule processing phase. If your makefile describes hundreds or thousands of build steps, this can take a perceptible amount of time. To accelerate incremental builds, Minion can write many or all of its generated rules to a "cache" file, and avoid re-computing them every time make is invoked.

To enable caching, define in your makefile the variable minionCache, setting it to a list of goals to be cached. The rules of these goals and their transitive dependencies will be written to a cache file.

When using minionCache, you can still build uncached goals. Minion will use cached rules when they are present, and dynamically generate any other required rules.

The cache file will be re-generated whenever your makefile changes, so generally the results of building with cached rules will be the same as when you build without cached rules. Be aware that cached rules could be "stale" if the your makefile specifies isntances that depend on things other than the contents of makefiles. If your rules depend on, for example, directory contents or environment variables, or if you invoke make with command-line assignments (e.g. make CC.compiler=gcc), then these external variables will not be detected.

When it comes to command-line variable assignments, they are generally incompatible with rule caching, but if you assign minionCache to an empty value on the same command line, it will disable caching for that invocation, ensuring the build results would be as you expect.

When your build depends on directory contents, via $(wildcard ...), or other system state via $(shell ...), your makefile can set minionNoCache to a list the affected instances, and they will be ecluded from the cache. For example:

...
minionCache = default
minionNoCache = CExe(@prog)
...
prog = $(wildcard *.c)
...

Note that whereas minionCache includes all transitive dependencies of the listed instances, minionNoCache does not. It is intended to target individual build steps.

Builders

All instances being built must implement this interface. It consists of just three properties:

  • rule : Make source code that defines a rule for the instance.

  • needs: a list of target IDs that generate rules that are prerequisites of rule.

  • out: the resulting file path, or phony target name.

User-defined classes do not need to implement these directly. Generally, they will benefit from inheriting from Builder and overriding properties to customize behavior to their needs.

Builder

Builder is a built-in class that implements the builder interface and handles a number of low-level responsibilities, including the following:

  • An output file name and location is computed.

  • Inputs are obtained from instance arguments.

  • The build command is escaped for Make syntax to avoid unintended evaluation, and the output file's directory is created prior to execution of the command.

  • After changes to makefiles affect the way artifacts are built, the affected artifacts (and only those) will be rebuilt. See (validity values)[#validity-values].

  • Directories are created as necessary to hold files written to by the build recipe.

Subclasses can leverage this functionality by inheriting from Builder, and can then customize their functionality by defining or overriding some of these properties.

{rule}

A Minion instance's {rule} property evaluates to the Make source code necessary to define an ordinary Make target. Builder provides a definition that insulates most subclasses from many peculiarities of Make. Subclasses can customize behavior by defining the following:

  • {command}: (see below)
  • {message}: (see minion.mk)
  • {vvFile}: (see minion.mk)

{needs}

Builder's {needs} definition is computed from {in}, {up}, {deps}, and {oo}.

{out} and related properties

The value of {out} is a file path. It identifies the file that is generated by the instance, or, in the case of a phony instance, the phony target name.

Users should rely on Builder's definition of {out} because it satisfies a very important requirement that different instances will not have conficting output file paths. Users can customize this behavior by overriding the following properties:

  • {outExt}: This is the extension to replace the input file's extension when constructing the output file name. For example, .o or %.gz. A % in {outExt} is replaced with the extension of the input file name.

    It is expected that most subclasses wil override {outExt} to appropriately designate output file types.

  • {outDir}: By default, this identifies a directory that is underneath $(OUTDIR).

  • {outName}: By default, this is constructed from {outExt} and the input file name. The input file name is taken from the {out} property of the first input to the instance, unless the instance's first argument is an indirection, in which case the indirection group name is used.

When using Minion, we generally don't care where intermediate output files are located or what they are named, since we refer to them by their instance names. As a result, most derived classes override only {outExt} and leave the other properties unchanged. Users overriding {outDir} or {outName} shoud take care to avoid conflicts with other instances.

Overriding {outDir} will affect the location of the target, but not the location of ancillary files like {vvFile} or {depsMF} (if they exist).

[To simplify the output directory computation, you can override {outBasis}. In particular, you can use Builder's definition, replacing {outExt} with %, in order to remove the ".EXT" suffix from output directories.]

Remember that these properties -- outExt, outDir, and outName -- are inputs to the computation of out, and out can be overridden independently by subclasses or instances. Do not assume that, for example, {outDir} is always the same as $(dir {out}).

The Copy class exists to deploy files to a specific, well-known location, rather than an automatically-generated unique location, so it allows the output file location to be specified by an argument with the name out:

_Copy.out = $(or $(call _namedArg1,out),{inherit})

For example, Copy(CC(foo.c),out:deploy/foo.o) will place its result in "deploy/foo.o".

{command}

Subclasses of Builder must provide a definition for the command property. Builder will provide defaults for all other properties. The value is one or more shell commands separated by newline characters. Its definition may make use of the following property references:

  • {@}: the output file
  • {<}: the first input file
  • {^}: all input files

These properties correspond to Make's "automatic variables" $@, $<, and $^, which are unavailable in Minion property definitions, since command expansion happens prior to the rule processing phase. The value of {^} is not identical to Make's $^, but it is more often what is relevant to rule construction: it is a list of the files that correspond to target IDs in {in}, and it does not include {deps}, {oo}, or implicit dependencies (declared as prerequisited via {depsFile}), or other files that would not normally appear as command line arguments. Also, duplicate entries are not pruned, so in that respect it is more like $+ than $^.

For example, the Copy class inherits this definition of command:

_Copy.command = cp {<} {@}

{in}

The value of {in} is an ingredient list that describes prerequisites of this instance. Builder defines {in} as $(_args), which defaults to all unnamed instance arguments. Other classes may define {in} differently.

Expansion of indirections and inference of intermediate targets is performed to generate {inIDs}, which is a list of target IDs. These are in turn translated to file names -- each instance is replaced with its {out} property -- to generate {^}, which can be used to construct the command line.

For convenience or readability, instead of listing all ingredients in arguments, users can use a placeholder argument and then define in for that instance, as in:

Alias(default).in = Exe(prog) Exe(prog).in = foo.c bar.c baz.c

Exe(prog) will create an executable equivalent to Exe(foo.c,bar.c,baz.c), but with a different default name ("prog" vs "foo"). Alternatively, one could define use an indirection, as in Exe(@prog) and place all the ingredients in the variable prog.

{up}

An instance may have dependencies that are not specified by the user via arguments or the in property, and instead are built into the definition of the class. An example is when a build step uses a tool that is itself a build product. We would prefer the dependency on the tool to be "baked into" the class definition, so when using the class one needs to name only the files that will be processed by the tool. This is akin to the notion of captured values in lexically scoped programming languages. These built-in dependencies are stored in the up property, which is an ingredient list. The {ooIDs} property gives the target IDs that result from expansion of {oo}, and {up^} contains the files corresponding to those target IDs.

{oo}

Order-only dependencies can be specified by defining the oo property (an ingredient list). This type of dependency can be appropriate when we know there is a dependency on the existence of a file, but not necessarily on the content of the file.

Order-only dependencies are typically used to deal with implicit dependencies that are generated by the Makefile.

Implicit dependencies are files that are not specified on the command line, but are read during execution of the build command. The classic example is files included by C sources using #include. In Make, implicit dependencies are trakced using the following strategy:

a) The command that compiles the C file also generates a dependency makefile expressing dependencies of the output file on the files that were included.

b) An -include directive is used to include the dependency makefile if it has already been generated by a previous invocation of Make.

Minion's CC and CC++ clases implement this strategy. This ensures accurate rebuilds of object files when included headers are modified.

Order-only dependencies enter the picture when some of the implicit dependencies are generated by our makefile. When we are building an object file for the first time, we do not know beforehand what include files will be included, so we need to ensure that any potential dependencies are generated before C files are compiled. The solution is to name all generated header files as order-only dependencies of all object files. It might look something like this:

CC.oo = IDLCompile@idlFiles

{deps}

This property lists dependencies that are but are not listed on the command line and are not inherent in the class.

One use case for {deps} is when there are implicit dependencies that are not automatically detected. Users can manually express these using {deps}.

One example of this is ordering of unit tests. For example, we might want to execute test A before test B because A validates some of the code relied upon by test B. Running B before A might waste the user's time. Automatic detection of these kinds of dependencies might not be avialable.

{inferClasses}

By default, rule inference is performed on input files. Each class can define its own inference rules by overriding {inferClasses}. This consists of a list of entries of the form CLASS.EXT, each indicating that CLASS should be applied to a input file ending in .EXT.

For example, inference allows a ".c" file to be supplied where a ".o" file is expected. The instance CExe(hello.c) will infer the instance CC(hello.c), because CExe.inferClasses contains CC.c.

Validity Values

By default, Builder treats the command used to build a file as a dependency. That is, if any changes to makefiles or he environment result in changes to {command}, previous build results will be considered invalid and a rebuild will be forced on a subsequent invocation of make.

This {vvFile} property identifies a makefile that will store this dependency information. Subclasses or user makefiles can set {vvFile} to an empty value to disable this behavior, or the can override {vvValue} to include information other than {command} that might affect target validity.

Debugging

The variable minionDebug can be used to turn on debug messages. Each debug message has a value and an associated name, and the value of minionDebug is a pattern to be matched with the name using filter (so minionDebug=% will turn on all debug messages). Notably, minionDebug=EVAL% will enable messages for all text that Minion feeds to Make's $(eval ...) function.

The _? function makes it very easy to instrument your Make functions or property definitions with debugging output. Replacing $(call func,...args...) with $(call _?,func,...args...) will retain the behavior but write inputs and outputs to stdout.

Built-in Targets

make clean

By default, make clean will remove $(VOUTDIR). User makefiles can change this behavior by defining in or command properties for Alias(clean).

When goals not named clean are named along with clean on the command line, it changes the handling of those other goals and clean. Instead of deleting $(VOUTDIR), it will delete the targets of the listed goals and all of their dependencies. This can be useful for removing build outputs that fall outside of $(VOUTDIR), or for triggering more limited cleanup.

The Clean class is used by make clean. For example, make 'Clean(Alias(default))' is equivalent to make clean default. Instances of Clean can be named as dependencies of Alias(clean) to ensure that outputs outside of $(VOUTDIR) are cleaned. For example,

Alias(default).in = Copy(Exe(prog.c),dir:../deploy)
Alias(clean).in = Clean(Alias(default))

make help [TARGETS...]

When help is named as the only goal, a message describing basic usage is displayed. User makefiles can override this by defining in or command properties for Alias(help). The message displayed by default is available in $(_helpMessage).

When help is listed with other command line goals, it changes the handling of those goals. Minion will output a description of the other goals, rather than build them. Additionally, instance properties can be listed as goals along with help. For example:

make help 'CC(foo.c).command'

will show the value of out, the property definitions that were inherited, and the inheritance chain for CC.

make graph

The graph targets draws a textual representation of the dependency tree for the default target. Use make 'Graph(INSTANCE)' to see the dependencies of some other given instance.

Exported Definitions

Naming Conventions

Camel case is generally used for naming in Minion and recommended for user makefiles. Function, variable, and property names begin lowercase, while class names begin uppercase. All-caps names might conflict with environment variables or Make's built-in variable settings (e.g LINK.c, COMPILE.c, etc.). Variables defined by by minion.mk begin with "minion" or "_" to avoid unintentional conflicts with user makefiles, except for built-in classes and the following "unprefixed" names:

V, OUTDIR, VOUTDIR            Control of build output
., get                        Core object system functions
\s \t \n \e \H ; [ ] [[ ]]    Character constants
minionStart
minionEnd
minionDebug
minionCache
minionNoCache

We generally avoid single-letter global variables so that they can be used as "local" variables (in Make foreach expressions). Underscore ("_") may be used as a namespace delimiter for variables that are associated with a class but not property definitions.

Supported Functions

Minion defines a number of variables and functions for use by user Makefiles within recursive property definitions.

  • Character constants

    • $(\n): newline
    • $(\t): tab
    • $(\s): space
    • $(\e): escape
    • $(\H): #
    • $; : ,
    • $[ : (
    • $] : )
    • $([[): {
    • $(]]): }
  • $(call get,PROP,IDS)

    Evaluate property PROP for each member of IDS. IDS is a space-delimited list of IDs. The result is a space-delimited list of the corresponding values.

    If a file name (not an instance name) is passed to get, it will be treated as an instances of the _File class. This defines the property out, which evaluates to the file name, and the properties rule and needs, which are empty.

  • $(call .,PROP,$0)

    Evaluate property PROP for the current instance. In property definitions, $(call .,PROP,$0) is equivalent to {PROP}. In other variables and functions, {...} syntax is not supported, but . is available if they are called during the evaluation of a property definition. The second argument is used to construct a diagnostic message only in the event PROP is undefined. It should be the name of the function calling ., which is available in Make as $0.

  • $(call _shellQuote,STR)

    Quote STR as an argument for /bin/sh or /bin/bash.

  • $(call _printfEsc,STR)

    Escape STR for inclusion in a printf command line argument.

  • $(call _printf,STR)

    Return a shell command that writes STR to stdout.

  • $(call _eq,A,B)

    Return "1" if A and B are equal, "" otherwise.

  • $(call _once,VAR)

    Return the value of VAR, evaluating it at most once.

  • $(_self)

    Return the name of the current instance.

  • $(_class)

    Return the class of the current instance.

  • $(_argText)

    Return the portion of the current instance name that describes the argument list.

    For example, in Class(a,b,x:1,x:2), $(_argText) returns "a,b,x:1,x:2".

  • $(_args)

    Return all unnamed arguments for the current instance.

    For example, in Class(a,b,x:1,x:2), $(_args) returns "a b".

  • $(_arg1)

    Same as $(word 1,$(_args)).

  • $(call _namedArgs,NAME)

    Return all arguments associated with NAME for the current instance.

    For example, in Class(a,b,x:1,x:2), $(call _namedArgs,x) returns "1 2".

  • $(call _namedArg1,NAME)

    Same as $(word 1,$(call _namedArgs,NAME)).

  • $(call _relpath,FROM,TO)

    Construct a path that can be used to refer to file TO from file FROM. If TO is an absolute path, it is returned as the result. Otherwise, both FROM must be a relative path and the result will be a relative path.

  • $(call _traverse,CF,CC,ROOTS)

    Traverse a graph, starting at root nodes ROOTS, returning a list of nodes ordered such that each parent precedes all its children.

    CF = name of a function to get children of a node CC = a context value to pass to CF

    $(call CF,CC,node) -> children

Syntax

The following BNF describes target names:

TargetID  := Name | Instance
Instance  := Class '(' ArgList ')'
Class     := ClassChar+
ArgList   := ( Arg ( ',' Arg )* )?
Arg       := ( Name `:` )? Value
Value     := ( ArgList | ValueChar )+
Name      := NameChar+
Property  := PropChar+

These definitions rely on the following character classes:

ClassChar:  A-Z a-z 0-9 _ - + / ^ ~ { }
NameChar:   A-Z a-z 0-9 _ - + / ^ ~ { } .
PropChar:   A-Z a-z 0-9 _ - + / ^ ~       @ < >
ValueChar:  A-Z a-z 0-9 _ - + / ^ ~ { } . @ < > ! *

Note that an argument list contains zero or more arguments, and each argument must contain at least one character. Each argument may contain other instances embedded within it, which means it may contain ( and ), characters, but only in balanced pairs. An argument may also contain , and :, but only within nested parentheses.

In general, instances will contain special shell characters, so they may have to be quoted when being passed on the command line.

Notes on Special Characters

Characters that are "special" in POSIX shells or GNU Make can complicate things so we generally assume they are not present in input and output file names. Quoting every file name for the shell is tedious, and the additional layer of quoting in Make can make things confusing. Make quoting can vary by context and sometimes even be impossible. That makes for a fairly large set of undesirable characters, including whitespace characters and the following:

Make specials:  ~   :        [ ] * ? # $ % ; \ =
Bash specials:  ~ !  ( ) < > [ ] * ? # $ % ; \   | & ` ' " { }

Minion instances and indirections may contain a number of these special characters because instances generally do not appear as targets and prerequisites in Make rules, and they are not passed to the shell as file names. When Minion constructs output file paths from instance names, but it is careful to use shell-safe characters to escape any unsafe characters in instance names. The following shell-unsafe characters may appear in instances or indirections:

( ) < > { } : ! * ~ < >