133 lines
5 KiB
Markdown
133 lines
5 KiB
Markdown
# 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.
|