Writing your own build built-in

There are two different types of built-ins for the build phase. The first category consists of the built-ins relying on the command() built-in (such as, for example, the apt built-in). These are usually wrappers around existing commands, allowing to use them more conveniently in Quixote. The second category is more complex, and used for lower-level built-ins.

Writing a command()-based built-in

For this part, we will pick the dpkg command as a case study, and see how we can wrap it as a built-in.

Note

dpkg is a rather complex command, so our wrapper will only provide basic actions, such as installing or removing packages.

We want our module to be used in a way similar to the apt module, like shown below:

import dpkg

dpkg.install("path/to/my_package.deb", "my_other_package.deb")
dpkg.remove("yet_another_package")

So first, in order to start, we will create a Python module and call it dpkg, and import the command() function:

from quixote.build.shell import command

Next, we will create functions for the pieces of dpkg we want to wrap. As said earlier, we will wrap package installation (which matches the -i switch) and package removal (which matches the -r switch).

def install():
    pass

def remove():
    pass

So far, these functions do nothing, but we will now fill them up, starting with the install function. In order to implement it correctly, we need to look at the corresponding section in the man page for dpkg.

-i, --install package-file...
          Install the package. If --recursive or -R option is specified, package-file must
          refer to a directory instead.

With this information, we know that the command accepts an arbitrary number of paths to package files, and therefore that our function must accept an arbitrary number of strings to represent those.

def install(*args: str):
    pass

Now that our function accepts parameters, we need to transfer them to the actual dpkg command. This is where the command() built-in comes into play.

def install(*args: str):
    return shell.command("dpkg -i " + ' '.join(args))

Are we done yet? No, as the man page still mentions the -R option, to install packages contained in a directory.

In order to handle that option in our wrapper, we will simply add a recursive parameter to our function. By default, it will be set to False, but if the caller sets it to True, we will add a -R to our invocation of the dpkg command:

def install(*args: str, recursive=False):
    if recursive is True:
        return command("dpkg -R -i " + ' '.join(args))
    else:
        return command("dpkg -i " + ' '.join(args))

And we’re done! Of course, we haven’t implemented the remove function yet, but this is left as an exercise for you to practice. As always, the full dpkg module (including the solution) can still be downloaded here.

Writing an advanced built-in

Built-ins for the build phase are somewhat special, in the way that they constitute a small DSL allowing Quixote to construct the specified environment. In order to do that, Quixote translates from that DSL to the language of the desired environment builder (e.g. Docker or bash scripts).

Warning

This tutorial is still work-in-progress!