-
Notifications
You must be signed in to change notification settings - Fork 8
Out of Order Execution
One of the great things about Python is that it is a procedural language: programs are easy to read and understand, because they are written sequentially: one step at a time.
But this could be seen as a limitation for build scripts, which have traditionally used declarative DSLs. Sometimes you want to do something first but you want the behavior there to depend on something that might happen later.
For example, a complex project may combine several compilation phases (compiling different programming languages), and then a complex linking phase, which may be constructed entirely differently depending on the platform (Linux, Windows, etc.). Also, the way you structure the linking phase may mean that certain things in the compilation phases would also need to change.
Of course, there are procedural solutions to this: if
statements, and more if
statements, and
helper functions, etc. But it can make the script hard to read.
More importantly, such procedural solutions make it difficult to create building blocks that you can reuse among many build scripts. If a compile phase changes according to the link phase, then it would have to know about this link phase somehow, even if the code for it exists in its own file, to be imported by other build scripts.
Rōnin's solution is to support lazy execution throughout, using some simple Pythonic constructs.
Almost anywhere that Rōnin expects a string, you can give it a lambda instead. The lambda will only be executed at the very end, when writing the Ninja file. The lambda always has a single argument, which is the current context (when called), though you can ignore the context if you don't need it:
project.variant = lambda ctx: ctx.myproject.crosscompile_for
There are a few helper functions to create complex lambdas for common use cases, for example a string join:
command_line = join_later(['--no-warning', lambda _: get_platform_flag()],
separator=',')
In these cases, even the arguments can be lambdas! This includes the elements of the array to join, and even the separator string. String formatting:
announce = format_later('moving file {0} to {1}',
file, lambda ctx: ctx.myproject.dest))
Note that we call it "lambda", but it can really be any Python callable: a full function, or even a
class that implements __call__
.
Many of Rōnin's standard tools (gcc executors, the pkg-config extension, etc.) make heavy use of lambdas in order to make them as reusable as possible. Take a look at the source code to get a sense of how to make them work well for you.
You can attach custom functions to projects, phases, and executors. They will be called in order when writing the Ninja file.
Each hook is called with a single argument, which is the object to which it's attached. Example:
def debug_hook(phase):
with current_context() as ctx:
if ctx.build.debug:
phase.inputs.append(input_file('debug.c'))
compile = Phase()
compile.hooks.append(debug_hook)
You can mix hooks and lambdas freely to best fit your coding flow.