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 :func:`~quixote.build.shell.command` built-in (such as, for example, the :mod:`~quixote.build.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 :func:`~quixote.build.shell.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 :mod:`~quixote.build.apt` module, like shown below: .. code-block:: python 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 :func:`~quixote.build.shell.command` function: .. code-block:: python 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). .. code-block:: python 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``. .. code:: none -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. .. code-block:: python 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 :func:`~quixote.build.shell.command` built-in comes into play. .. code-block:: python 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: .. code-block:: python 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 :download:`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!