Source code
Revision control
Copy as Markdown
Other Tools
# Editing toolkit/moz.configure
## Prerequisites
Some of the files that configure the build system are written in a restricted python dialect. It is probably easiest to think of them as "python-like DSLs". They must be formatted using `black`. Correct formatting is checked on CI.
To run `black` on `toolkit/moz.configure`:
```
./mach lint -l black toolkit/moz.configure
```
## moz.configure
These files describe one of the first steps of the build. This step does not run tool chains or produce any other kind of artifacts. It only produces a few key/value dictionaries that later parts of the build will use.
Two important dictionaries declared in `moz.configure` are *configs* and *defines*. The former is used in `moz.build` files, the later is used to feed C and C++ compilers, as shown below.
This is typically the right place to add logic for:
- Declaring options for the mozconfig file.
- Deciding whether to enable/disable some build-time features based on the build configuration and environment.
- Generating some `#define` identifiers for the C++ code based on the build configuration or environment.
It contains a lot of code that looks like:
```python
# In toolkit/moz.configure:
# Adds a config key/value pair
set_config("FOO", foo)
# Adds a define key/value pair
set_define("BAR", bar)
```
We'll see later how the lower case `foo` symbol above is defined.
Configurations can be accessed in various parts of the build system, such as `moz.build` files for example:
```python
# In a moz.build file:
if CONFIG["FOO"]:
# For example let's add an exported header for our C++ code.
EXPORTS.mozilla += [
"foo.h"
]
# or
if CONFIG["FOO"] == "something":
# etc.
```
Defines map directly to C++ defines in the code as well as other files that use a C-like preprocessor, for example `modules/libref/init/all.js`, or `toolkit/content/license.html`.
### The dependency graph
It is tempting to look at the code in `moz.configure` and read its logic in with an imperative programming mindset, however a better mental model is to imagine this file as a script that declares a task graph which is evaluated later.
Let's look at a simple example:
```python
# In toolki/moz.configure
# Declare a build option that can be set via `ac_add_option` in the `mozconfig` file.
option("--enable-doodad", help="Enable a fancy feature")
@depends("--enable-doodad", target)
def doodad(enabled, target):
# Return True if --enable-doodad was set in mozconfig and
# if we are on Windows.
return enabled and target.os =!== "WINNT"
```
The code above declares a `doodad` function that is decorated with `@depends`.
We will never directly call this `doodad` function ourselves. The `@depends`
decoration wraps it into a node of the dependency graph that will
be lazily evaluated later. Elsewhere in `moz.configure`, when we write `doodad`, it refers to the node that wraps the function.
The parameters in `@depends` correspond to `doodad`'s node dependency and map to the function parameters. So `enabled` inside the function will only evaluate to `True` if `--enable-doodad` is set in mozconfig.
The body of the function is evaluated in the second stage when the graph is evaluated. It runs in a sand-boxed environment and has access to very few things other than what is provided as input to the node.
Only declaring a node has no effect, unless that node is used, so let's use our `doodad` node:
```python
# Specify `doodad` as a dependency to resolving the "DOODAD" config key.
set_config("DOODAD", doodad)
# Specify a define. The syntax is the same as with `set_config`.
set_define("MOZ_DOODAD", 1, when=doodad)
```
Note the `when=` syntax: the define will only be set if doodad evaluates to `True`. This syntax can also be used with `set_config` and `@depends`.
Since `set_config` is run when declaring the graph, and before evaluating it, we could not have expressed this condition using an `if` statement:
```python
# This does *not* work. `doodad` is not a value, it is a node.
if doodad:
set_define("MOZ_DOODAD", 1)
```
Another way to express this condition is via `with only_when` blocks:
```python
# This works!
with only_when(doodad):
set_define("MOZ_DOODAD", 1)
```
Now let's add a slightly more complicated example. This time the node will not evaluate to
```python
with only_when(compile_environment):
# Depend on the doodad node we defined earlier
@depends(doodad, target)
def advanced_doodad(basic_doodad, target):
# If the doodad is not enabled, don't enable the advanced
# version.
if not basic_doodad:
return Namespace(enabled=False)
header_name = "doodad_" + target.cpu + ".h"
return Namespace(
enabled=True,
header_name=header_name
)
with only_when(advanced_doodad.enabled):
set_config("DOODAD_ARCH_HEADER", advanced_doodad.header_name)
```
The `advanced_doodad` node evaluates to a dictionary instead of just a boolean.
This is useful to write more expressive configurations and for, example, generate strings or path names based on earlier configuration.