ll.make – Object oriented make replacement

ll.make provides tools for building projects.

Like make it allows you to specify dependencies between files and actions to be executed when files don’t exist or are out of date with respect to one of their sources. But unlike make you can do this in an object oriented way and targets are not only limited to files.

Relevant classes are:

  • Project, which is the container for all actions in a project and
  • Action (and subclasses), which are used to transform input data and read and write files (or other entities like database records).

A simple script that copies a file foo.txt to bar.txt reencoding it from "latin-1" to "utf-8" in the process looks like this:

from ll import make

class MyProject(make.Project):
        name = "Acme.MyProject"

        def create(self):
                source = self.add(make.FileAction("foo.txt"))
                temp = source.callattr("decode", "iso-8859-1")
                temp = temp.callattr("encode", "utf-8")
                target = self.add(make.FileAction("bar.txt", temp))

p = MyProject()

if __name__ == "__main__":
class ll.make.Action[source]

Bases: object

An Action is responsible for transforming input data into output data. It may have no, one or many inputs which themselves may be other actions. It fetches, combines and transforms the output data of those actions and returns its own output data.


Create a new Action instance.


Return an iterator over the input actions of self.

call(*args, **kwargs)[source]

Return a CallAction for calling selfs output with positional arguments from args and keyword arguments from kwargs.

callattr(attrname, *args, **kwargs)[source]

Return a CallAttrAction for calling selfs attribute named attrname with positional arguments from args and keyword arguments from kwargs.

execute(project, *args, **kwargs)[source]

Execute the action: transform the input data in args and kwargs and return the resulting output data. This method must be implemented in subclasses.


Find dependency paths leading from self to the other action input. I.e. if self depends directly or indirectly on input, this generator will produce all paths p where p[0] is self and p[-1] is input and p[i+1] in p[i] for all i in range(len(p)-1).

get(project, since)[source]

This method (i.e. the implementations in subclasses) is the workhorse of ll.make. get() must return the output data of the action if this data has changed since since (which is a datetime.datetime object in UTC). If the data hasn’t changed since since the special object nodata must be returned.

In both cases the action must make sure that the data is internally consistent, i.e. if the input data is the output data of other actions self has to ensure that those other actions update their data too, independent from the fact whether get() will return new data or not.

Two special values can be passed for since:

This timestamp is older than any timestamp that can appear in real life. Since all data is newer than this, get() must always return output data.
This timestamp is newer than any timestamp that can appear in real life. Since there can be no data newer than this, get() can only return output data in this case if ensuring internal consistency resulted in new data.

In all cases get() must set the instance attribute changed to the timestamp of the last change to the data before returning. In most cases this if the newest changed timestamp of the input actions.


Return a GetAttrAction for getting selfs attribute named attrname.


Get the nearest key from self or its inputs. This is used by ModuleAction for the filename.


Return an iterator over all input actions of self (i.e. recursively).

class ll.make.AlwaysAction[source]

Bases: ll.make.Action

This action always returns None as new data.

class ll.make.CacheAction(input=None)[source]

Bases: ll.make.TransformAction

A CacheAction is a TransformAction that passes through its input data, but caches it, so that it can be reused during the same build round.

class ll.make.CallAction(func, *args, **kwargs)[source]

Bases: ll.make.Action

This action calls a function or any other callable object with a number of arguments. Both positional and keyword arguments are supported and the function and the arguments can be static objects or actions.

class ll.make.CallAttrAction(obj, attrname, *args, **kwargs)[source]

Bases: ll.make.Action

This action calls an attribute of an object with a number of arguments. Both positional and keyword arguments are supported and the object, the attribute name and the arguments can be static objects or actions.

class ll.make.CollectAction(input=None, *otherinputs)[source]

Bases: ll.make.TransformAction

A CollectAction is a TransformAction that simply outputs its input data unmodified, but updates a number of other actions in the process.


Register all actions in otherinputs as additional actions that have to be updated before self is updated.

class ll.make.CommandAction(command, input=None)[source]

Bases: ll.make.TransformAction

This action executes a system command (via os.system()) and passes through the input data.

__init__(command, input=None)[source]

Create a new CommandAction object. command is the command that will executed when execute() is called.

class ll.make.FOPAction(input=None)[source]

Bases: ll.make.TransformAction

This action transforms an XML string (containing XSL-FO) into PDF. For it to work Apache FOP is required. The command line is hardcoded but it’s simple to overwrite the class attribute command in a subclass.

class ll.make.FileAction(key, input=None, encoding=None, errors=None)[source]

Bases: ll.make.TransformAction

A FileAction is used for reading and writing files (and other objects providing the appropriate interface).

__init__(key, input=None, encoding=None, errors=None)[source]

Create a FileAction object with key as the “filename”. key must be an object that provides a method open() for opening readable and writable streams to the file. input is the data written to the file (or the action producing the data). encoding is the encoding to be used for reading/writing. If encoding is None binary i/o will be used. errors is the codec error handling name for encoding/decoding text.


Return a ModeAction that will change the file permissions of self to mode.

chown(user=None, group=None)[source]

Return an OwnerAction that will change the user and/or group ownership of self.

get(project, since)[source]

If a FileAction object doesn’t have an input action it reads the input file and returns the content if the file has changed since since (otherwise nodata is returned).

If a FileAction object does have an input action and the output data from this input action is newer than the file self.key the data will be written to the file. Otherwise (i.e. the file is up to date) the data will be read from the file.


Read the content from the file and return it.

write(project, data)[source]

Write data to the file and return it.

class ll.make.GetAttrAction(input=None, attrname=None)[source]

Bases: ll.make.TransformAction

This action gets an attribute from its input object.

class ll.make.Level(action, since, reportable, reported=False)[source]

Bases: object

Stores information about the recursive execution of Actions.

class ll.make.MkDirAction(key, mode=511)[source]

Bases: ll.make.TransformAction

This action creates the a directory (passing through its input data).

__init__(key, mode=511)[source]

Create a MkDirAction instance. mode (which defaults to 0o777) will be used as the permission bit pattern for the new directory.

execute(project, data)[source]

Create the directory with the permission bits specified in the constructor.

class ll.make.ModeAction(input=None, mode=420)[source]

Bases: ll.make.TransformAction

ModeAction changes file permissions and passes through the input data.

__init__(input=None, mode=420)[source]

Create an ModeAction object. mode (which defaults to 0644) will be use as the permission bit pattern.

execute(project, data, mode)[source]

Change the permission bits of the file self.getkey().

class ll.make.ModuleAction(input=None)[source]

Bases: ll.make.TransformAction

This action will turn the input string into a Python module.


Create an ModuleAction.

This object must have an input action (which might be a FileAction that creates the source file).


Register all actions in inputs as modules used by this module. These actions must be ModuleAction objects too.

Normally it isn’t neccessary to call the method directly. Instead fetch the required module inside your module like this:

from ll import make

mymodule = make.currentproject.get("mymodule.py")

This will record your module as depending on mymodule, so if mymodule changes, your module will be reloaded too. For this to work you need to have an ModuleAction added to the project with the key "mymodule.py".

class ll.make.NeverAction[source]

Bases: ll.make.Action

This action never returns new data.

class ll.make.ObjectAction(object=None)[source]

Bases: ll.make.Action

An ObjectAction returns an object.

class ll.make.OwnerAction(input=None, user=None, group=None)[source]

Bases: ll.make.TransformAction

OwnerAction changes the user and/or group ownership of a file and passes through the input data.

__init__(input=None, user=None, group=None)[source]

Create a new OwnerAction object. user can either be a numerical user id or a user name or None. If it is None no user ownership will be changed. The same applies to group.

execute(project, data, user, group)[source]

Change the ownership of the file self.getkey().

class ll.make.PhonyAction(*inputs, **kwargs)[source]

Bases: ll.make.Action

A PhonyAction doesn’t do anything. It may depend on any number of additonal input actions which will be updated when this action gets updated. If there’s new data from any of these actions, a PhonyAction will return None (and nodata otherwise as usual).

__init__(*inputs, **kwargs)[source]

Create a PhonyAction object. doc describes the action and is printed by the method Project.writephonytargets().


Register all actions in inputs as additional actions that have to be updated once self is updated.

class ll.make.PipeAction(input, command)[source]

Bases: ll.make.TransformAction

This action pipes the input through an external shell command.

__init__(input, command)[source]

Create a PipeAction instance. command is the shell command to be executed (which must read it’s input from stdin and write its output to stdout).

class ll.make.Project[source]

Bases: dict

A Project collects all Action objects from a project. It is responsible for initiating the build process and for generating a report about the progress of the build process.


Return whether the target with the key key exists in the project.


Return the target with the key key. If an key can’t be found, it will be wrapped in a ll.url.URL object and retried. If key still can’t be found a UndefinedTargetError will be raised.

__setitem__(key, target)[source]

Add the action target to self as a target and register it under the key key.

add(target, key=None)[source]

Add the action target as a target to self. If key is not None, target will be registered under this key, otherwise it will be registered under its own key (i.e. target.key).


Return an argparse parser for parsing the command line arguments. This can be overwritten in subclasses to add more arguments.


Rebuild all targets in targets. Items in targets must be actions registered with self (or their ids).


For calling make scripts from the command line. args defaults to sys.argv. Any positional arguments in the command line will be treated as target ids. If there are no positional arguments, a list of all registered PhonyAction objects will be output.


Create all dependencies for the project. Overwrite in subclasses.

This method should only be called once, otherwise you’ll get lots of RedefinedTargetWarnings. But you can call clear() to remove all targets before calling create(). You can also use the method recreate() for that.

findpaths(target, source)[source]

Find dependency paths leading from the action target to the action source.


Get up-to-date output data from the target target (which must be an action registered with self (or the id of one). During the call the global variable currentproject will be set to self.


Return whether the target with the key key exists in the project.


Use the parser returned by argparser() to parse the argument sequence args, modify self accordingly and return the result of the parsers parse_args() call.


Calls clear() and create() to recreate all project dependencies.


Return a nicely formatted and colored string for the action action.


Return a nicely formatted and colored string for the counter value counter.


Return a nicely formatted and colored string for the datetime.datetime value dt.


Return a nicely formatted and colored string for the error text text.


Return a nicely formatted and colored string for the action key key.


Return a nicely formatted and colored string for the datetime.timedelta value delta. delta may also be None in with case "0" will be returned.

warn(warning, stacklevel)[source]

Issue a warning through the Python warnings framework


All screen output is done through this method. This makes it possible to redirect the output (e.g. to logfiles) in subclasses.


Can be called at the end of an overwritten create() to report the number of registered targets.


Output an error.


All screen output is done through this method. This makes it possible to redirect the output (e.g. to logfiles) in subclasses.

writenote(action, *texts)[source]

Output texts as the note for the data transformation done by the action action.


Show a list of all PhonyAction objects in the project and their documentation.


Output texts indented properly for the current nesting of action execution.

writestacklevel(level, *texts)[source]

Output texts indented level levels.

writestep(action, *texts)[source]

Output texts as the description of the data transformation done by the action arction.

exception ll.make.RedefinedTargetWarning(key)[source]

Bases: Warning

Warning that will be issued when a target is added to a project and a target with the same key already exists.

class ll.make.TransformAction(input=None)[source]

Bases: ll.make.Action

A TransformAction depends on exactly one input action and transforms the input data into output data.

exception ll.make.UndefinedTargetError(key)[source]

Bases: KeyError

Exception that will be raised when a target with the specified key doesn’t exist within the project.


Get the last modified date (or bigbang, if the file doesn’t exist).

ll.make.getoutputs(project, since, input)[source]

Recursively iterate through the object input (if it’s a tuple, list or dict) and return a tuple containing:

  • An object (data) of the same structure as input, where every action object encountered is replaced with the output of that action;
  • A timestamp (changed) which the newest timestamp among all the change timestamps of the actions encountered.

If none of the actions has any data newer than since (i.e. none of the actions produced any new data) data will be nodata.


Standard decorator for Action.get() methods.

This decorator handles proper reporting of nested action calls. If it isn’t used, only the output of calls to Project.writestep() will be visible to the user.