The project descriptor

The anatomy

A project descriptor consists of several parts:


It’s python code after all, so PyBuilder functions we use must be imported first.

import os
from pybuilder.core import task, init, use_plugin, Author

Plugin imports

Through usage of the use_plugin function, a plugin is loaded (its initializers are registered for execution and any tasks are added to the available tasks).


Project fields

Assigning to variables in the top-level of the will set the corresponding fields on the project object. Some of these fields are standardized (like authors, name and version).

authors = [Author('John Doe', 'john@doe.invalid'),
           Author('Jane Doe', 'jane@doe.invalid')]

description = "This is the best project ever!"
name = 'myproject'
license = 'GNU GPL v3'
version = '0.0.1'

default_task = ['clean', 'analyze', 'publish']

Note that the above is equivalent to setting all the fields on project in an initializer, though the first variant above is preferred for brevity’s sake.

def initialize(project): = 'myproject'


An initializer is a function decorated by the @init decorator. It is automatically collected by PyBuilder and executed before actual tasks are executed. The main use case of initializers is to mutate the project object in order to configure plugins.

Dependency injection

PyBuilder will automatically inject arguments by name into an initializer (provided the initializer accepts it). The arguments project and logger are available currently.

This means that all of these are fine and will work as expected:

def initialize():

def initialize2(logger):

def initialize3(project, logger):

def initialize3(logger, project):


It’s possible to execute an initializer only when a specific command-line switch was passed. This is a bit akin to Maven’s profiles:

def initialize():

The above initializer will only get executed if we call pyb with the -E myenv switch.

The project object

The project object is used to describe the project and plugin settings. It also provides useful functions we can use to implement build logic.

Setting properties

PyBuilder uses a key-value based configuration for plugins. In order to set configuration, project.set_property(name, value) is used. For example we can tell the flake8 plugin to also lint our test sources with:

project.set_property('flake8_include_test_sources', True)

In some cases we just want to mutate the properties (for example adding an element to a list), this can be achieved with project.get_property(name). For example we can tell the filter_resources plugin to apply on all files named setup.cfg:


Note that append mutates the list.

Project dependencies

The project object tracks our project’s dependencies. There are several variants to add dependencies:

  • project.depends_on(name) (runtime dependency)
  • project.build_depends_on(name) (build-time dependency)
  • project.depends_on(name, version) (where version is a pip version string like ‘==1.1.0’ or ‘>=1.0’)
  • project.build_depends_on(name, version) (where version is a pip version string like ‘==1.1.0’)

This will result on the install_dependencies plugin installing these dependencies when its task is called. Runtime dependencies will also be added as metadata when packaging the project, for example building a python setuptools tarball with a will fill the install_requires list.

Installing files

Installing non-python files is easily done with project.install_file(target, source). The target path may be absolute, or relative to the installation prefix (/usr/ on most linux systems).

As an important sidenote, the path to source must be relative to the distribution directory. Since non-python files are not copied to the distribution directory by default, it is necessary to use the copy_resources plugin to include them.

Consider you want to install src/main/resources/my-config.yaml in /etc/defaults. It would be done like so:

First, we use copy_resources to copy the file into the distribution directory:


def initialize(project):
    project.set_property("copy_resources_target", "$dir_dist")

Now, whenever copy_resources run, we will have the path src/main/resources/my-config.yaml copied into target/dist/myproject-0.0.1/src/main/resources/my-config.yaml. We’re now able to do:


def initialize(project):
    project.set_property("copy_resources_target", "$dir_dist")
    project.install_file("/etc/defaults", "src/main/resources/my-config.yaml")


It’s important to realize that the source path src/main/resources/my-config.yaml is NOT relative to the project root directory, but relative to the distribution directory instead. It just incidentally happens to be the same here.

Including files

Simply use the include_file directive:

project.include_file(package_name, filename)


Creating a task

To create a task, one can simply write a function in the and annotate it with the @task decorator.

from pybuilder.core import task, init

def initialize(project):

def mytask(project, logger):"Hello from my task")

Like with initializer, PyBuilder will inject the arguments project and logger if the task function accepts them.

We’ll now be able to call pyb mytask.

The project API can be used to get configuration properties (so that the task is configurable). It’s also possible to compute paths by using expand_path:

from pybuilder.core import task

def mytask(project, logger):"Will build the distribution in %s" % project.expand_path("$dir_dist"))

Task dependencies

A task can declare dependencies on other tasks by using the @depends decorator:

from pybuilder.core import task, depends

def task1(logger):"Hello from task1")

def task2(logger):"Hello from task2")

@depends("task2", "run_unit_tests")
def task3(logger):"Hello from task3")

Here, running task1 will just run task1. Running task2 will run task1 first, then task2. Running task3 will run task1 first (dependency of task2), then run task2, then run unit tests, and finally run task3.