# Panifex: A Python Build System
## Overview
Panifex is a Python build system that easily allows you to tie together various
parts of a multi-stage build and to create reusable recipes.  It is a good
replacement for `make` and related tools.

## Example Usage
The following example defines a `bake.py` script that builds a tetris game from
its project root directory:

```
#!/usr/bin/env python
from pathlib import Path
from panifex import build, sh

sh.env(CC="clang", CFLAGS=("-g", "-I./include"), LDFLAGS=("-lncurses", "-lpanel"))

def compile(src):
    return sh(
        "{CC} -c {CFLAGS} {input} -o {output}",
        input=src,
        output=Path(src).with_suffix(".o"),
    )

def link(executable, objects):
    return sh("{CC} {LDFLAGS} {input} -o {output}", input=objects, output=executable)

@build
class ntetris2:
    def sources(self):
        return Path.cwd().glob("**/*.c")

    def objects(self, sources):
        return [compile(src) for src in sources]

    def executable(self, objects):
        return link("ntetris2", objects)
```

In the above example, the following usage patterns are presented:

- `build` and `sh` are imported from `panifex`.
    - `build` is a decorator function which wraps a class that defines the
        targets for the build.  Each named function in the class is a build
        target that can be invoked directly from the `bake.py` script.  All
        targets are built if none are provided.
    - `sh` is a recipe factory for generating shell-based file recipes.
- `CC`, `CFLAGS`, and `LDFLAGS` are defined as environment variables through
    `sh.env`.  `sh.env` extends the environment of the `sh` recipe factory with
    additional variables that are interpolated into the command string and added
    to the environment of commands run with this factory.  In addition to OS
    environment variables which are added automatically as strings, lists or
    tuples can be specified in `sh.env` which will be interpolated correctly
    into command format strings.
- `compile` is defined.  It is a normal function which calls the `sh` factory to
    generate a recipe for creating an `.o` output file for the given source
    file.  `compile` is referred to as a "recipe function", because it is a
    function that generates a recipe from arguments.  Calling this function
    itself does not run any commands, but instead the recipe generated should be
    returned from a build target, where it can be built in parallel with other
    dependencies in the overall dependency tree.  The `input` and `output`
    parameters to `sh` are special and essential:
    - `output` specifies the name of the file that will be generated by the
        shell recipe.  This is used to determine if the recipe actually needs
        to be run, and identifies the file that should be cleaned up while
        cleaning.
    - `input` is optional, and specifies the file(s) that will be used to
        create the output.  If the output already exists but any of the input
        files are newer, the output will be re-created.
- `link` is defined as another recipe function for creating the final executable
    from a list of object files.
- The `ntetris2` class is defined.  This is the "build class" and defines the
    targets to be built.  The `@build` decorator is required, and is the
    catalyst which starts the build process and makes the targets available for
    building.  A build script should define only one build class, as control is
    given over to Panifex once the class decorator is called.
- `sources` is the first target.  This target doesn't actually generate any
    files, rather it defines the list of source files to be built.  Since the
    result of the target isn't a recipe, the source files won't be affected by
    cleaning or building.  This doesn't actually need to be a target, as the
    list of files to be compiled could be specified outside the build class, but
    it is made a target to illustrate the previous points.
- `objects` is the next target.  It depends on `sources` because it has a
    parameter named `sources` which is automatically injected with the result
    from `sources`, that being a list of source filenames.  It uses `compile` to
    create a list of recipes and returns them.  Panifex will build these recipes
    in parallel, spawning at maximum as many simultaneous processes as the
    system has available processor cores.
- `executable` is the final target.  It depends on `objects`, and as such won't
    actually run until all of the object file recipes generated by `objects` are
    done building.  `executable` doesn't receive the list of object recipes,
    rather it receives a list containing all of the outputs specified by each
    recipe, that being a list of object filenames.

More examples and usage info coming soon!

## Release Notes
### v0.1: 01/02/20
- Initial release.
