Modules¶
lbuild calls these five functions for any module file:
init(module): provides module name, parent and documentation.prepare(module, options): enables modules, adds options and submodules by taking the repository options into consideration.validate(env)(optional): validate your inputs before building anything.build(env): generate your library and add metadata to build log.post_build(env)(optional): access the build log after the build step completed.
Module files are provided with these additional global classes:
Module: Base class for generated modules.ValidateException: Exception to be raised when thevalidate(env)step fails.
Accessing an option or query of another module via env[...] or env.query(...)
logs a warning if your module does not depend on that module, since it may not
be built. Use lbuild validate --strict to turn these warnings into errors.
Note that in contrast to a repository, modules must return a boolean from the
prepare(module, options) function, which indicates that the module is available
for the repository option configuration. This allows for modules to "share" a
name, but have completely different implementations.
The validate(env) step is used to validate the input for the build step,
allowing for computations that can fail to raise a ValidateException("reason").
lbuild will collect these exceptions for all modules and display them
together before aborting the build. This step is performed before each build,
and you cannot generate any files in this step, only read the repository's state.
You can manually call this step via the lbuild validate command.
The build(env) step is where the actual file generation happens. Here you can
copy and generate files and folders from Jinja2 templates with the substitutions
of you choice and the configuration of the modules. Each file operation is
appended to a global build log, which you can also explicitly add metadata to.
The post_build(env) step is meant for modules that need to generate
files which receive information from all built modules. The typically use-case
here is generating scripts for build systems, which need to know about what
files were generated and all module's metadata.
def init(module):
# give your module a hierarchical name, the repository name is implicit
module.name = "repo:name"
module.name = ":name" # same as this
# You can set a module description here
module.description = "Description"
module.description = """Multiline"""
module.description = FileReader("module.md")
# modules can have their own formatters, works the same as for repositories
module.format_description = custom_format_description
module.format_short_description = custom_format_short_description
# Add Jinja2 filters for this modules and all submodules
# NOTE: the filter is namespace with the repository! {{ 65 | repo.character }} -> "A"
module.add_filter("repo.character", lambda number: chr(number))
# Modules with a higher order are built after all modules with a lower
# order, see Execution Order. Default is 0.
module.order = 10000
def prepare(module, options):
# Access repository options via the `options` resolver
if options["repo:option"] == "value":
# Returning False from this step disables this module
return False
# modules can depend on other modules
module.depends("repo:module1", ":module2", ":module3:submodule", ...)
# You can add more submodules in files
module.add_submodule("folder/submodule.lb")
# You can generate more modules here. This is useful if you have a lot of
# very similar modules (like instances of hardware peripherals) that you
# don't want to create a module file for each for.
class Instance(Module):
def __init__(self, instance):
self.instance = instance
def init(self, module):
module.name = str(self.instance)
def prepare(self, module, options):
return True
def validate(self, env): # optional
pass
def build(self, env):
pass
def post_build(self, env): # optional
pass
# You can statically create and add these submodules
for index in range(0, 5):
module.add_submodule(Instance(index))
# or make the creation dependent on a repository option
for index in options["repo:instances"]:
module.add_submodule(Instance(index))
# See Options for more option types
module.add_option(StringOption(name="option", description="Option", default="world"))
def common_operation(args):
"""
You can share any function with other modules.
This is useful to not have to duplicate code across module.lb files.
"""
return args
# See Queries for more query types
module.add_query(Query(name="shared_function", function=common_operation))
# You can collect information from active modules, to use any post_build step
# See Collectors for more collector types
module.add_collector(
PathCollector(name="include_path", description="Global header search paths"))
# Make this module available
return True
# store data computed in validate step for build step.
build_data = None
# The validation step is optional
def validate(env):
# Perform your input validations here
# Access all options
repo_option = env["repo:option"]
defaulted_option = env.get("repo:module:option", default="hello")
# Use proper logging instead of print() please
# env.log.warning(...) and env.log.error(...) also available
env.log.debug("Repo option: '{}'".format(repo_option))
# You can check if a module is built or if an option exists. Only modules
# that are built and their options are visible to this check!
if env.has_option("repo:module:option") or env.has_module("repo:module"):
env.log.info("Module option: '{}'".format(env["repo:module:option"]))
# Call shared functions from other modules with arguments
shared_function = env.query("repo:module:shared_function")
result = shared_function("argument")
# Or just precomputed properties without arguments
data = env.query("repo:module:shared_property")
# You may also use incomplete queries, see Name Resolution
env.has_module(":module") # instead of repo:module
env.has_option("::option") # repo:module:option
# And use fnmatch queries
# matches any module starting with `mod` and option starting with `name`.
env.has_option(":mod*:name*")
env.has_query("::shared_*")
env.has_collector("::collector")
# Raise a ValidateException if something is wrong
if defaulted_option + repo_option != "hello world":
raise ValidateException("Options are invalid because ...")
# If you do heavy computations here for validation, you can store the
# data in a global variable and reuse this for the build step
global build_data
build_data = defaulted_option * 2
The build(env) and post_build(env) steps are described in Environment.