Bob's "features" allow module definitions to be changed depending on configuration options, e.g.:
bob_binary {
...
warnings_as_errors: {
cflags: ["-Werror"],
},
}
However, this is limited to one level deep, so isn't much use on its own. Bob therefore uses a separate configuration system ("Mconfig") to allow more complicated options and dependencies, which are translated into a set of boolean options at build time. The language is based on the Linux kernel's configuration mechanism, Kconfig, and is partially compatible with it.
After bootstrapping a build, there should be two scripts in the build
directory: config
and menuconfig
. Generally, at least one of
these needs to be run before starting a build for the first time.
This is a command-line tool to enable and disable options. It will attempt to satisfy all the options passed to it within a single invocation, and all other options will be reset to their default values:
$BUILDDIR/config ANDROID=y TARGET_TOOLCHAIN_CLANG=y ALLOW_HOST_EXPLORE=n
If incompatible options are requested, an error will be printed and the config file will not be written:
$BUILDDIR/config TARGET_TOOLCHAIN_CLANG=y TARGET_TOOLCHAIN_GNU=y
ERROR: TARGET_TOOLCHAIN_CLANG=y was ignored or overridden. Value is n
User-set options are not preserved between invocations - i.e. specifying e.g.
ENABLE_FOO=y
, where ENABLE_FOO
is disabled by default, will not be
preserved if $BUILDDIR/config
is run a subsequent time without specifying
ENABLE_FOO=y
.
This is an ncurses-based graphical tool to enable and disable options, and is
invoked by simply running $BUILDDIR/menuconfig
.
Unlike config
, menuconfig
will remember previously user-set options, so
can be used to incrementally change the build configuration.
Note that running config
after menuconfig
will clear any any previously set
options.
Configuration options live in a file called Mconfig
at the root of a project
directory.
Furthermore, Mconfig
files can include other ones using the source
or
source_local
statements. For compatibility with kernel Kconfig files, the
argument to source
is a directory relative to the project root, not relative
to the Mconfig file containing the source
statement. source_local
takes a
path relative to the current file. E.g., for the following layout:
project
|-- subcomponent
| |-- subsubcomponent
| | `-- Mconfig
| `-- Mconfig
`-- Mconfig
project/Mconfig
would contain:
config WARNINGS_AS_ERRORS
bool "Fail the build if the compiler issues a warning"
default y
# ...More config options...
source "subcomponent/Mconfig"
And subcomponent/Mconfig
would contain:
# ...
source "subcomponent/subsubcomponent/Mconfig"
# OR: source_local "subsubcomponent/Mconfig"
# NOT: source "subsubcomponent/Mconfig"
The fundamental unit of Mconfig is the config option. Each one is mapped to a
value which can be used inside build.bp
files during the build. They are
defined as follows:
config OPTION_NAME
bool|int|string "User-visible option name"
depends on (A && B) || C
default n|y|"hello"|1234 if D || E
default n
select ANOTHER_OPTION
warning "warning text when option enabled"
help
This is a longer, possibly multiline help text
describing OPTION_NAME.
In Mconfig, these have values y
and n
, which are translated into 1
and
0
when used in a build.bp
.
String processing is limited, but options can contain constant string values chosen using defaults, or be overridden by the user if user-visible:
config INSTALL_PATH
string "Location to install the binary"
default "/usr/local/bin" if LINUX
default "$(TARGET_OUT)/bin" if ANDROID
String values can also be compared in expressions (default if
and depends on
) using the =
and !=
operators.
Like strings, int options can contain constant values chosen using defaults, or
be overridden by the user if user-visible. They can also be compared in
expressions using the =
, !=
, <
, >
, <=
and >=
operators.
Hidden options
The "user visible option name" in the example above is optional:
config HIDDEN_DERIVED_VALUE
bool
default y if OPT_A && !OPT_B
default n
Options like the above will not be visible in menuconfig
, or settable on the
command-line using config
. Instead, they are always given their default value
based on their default if
conditions.
Options can have dependencies using the depends on
construct. If the
expression is not true, it will not be possible to enable the option, and the
option will not be visible in menuconfig
:
config BUILD_UNIT_TESTS
bool "Build the unit tests"
depends on DEBUG_SYMBOLS && UNIT_TEST_FRAMEWORK_FOUND
default y
help
Build the unit tests. Disable if you are not a developer.
If no default value is specified, options will be n
, 0
, or ""
by default
(for bools, ints, and strings, respectively). However, options can specify a
default value, which can change depending on other options using default if
:
config OS_NAME
string "Operating system name"
default "Android" if ANDROID
default "Linux" if LINUX
default "Unknown"
Mconfig can be used to negate config options:
config ASSERTIONS
bool "Enable assertions"
default n
config NO_ASSERTIONS
bool
default y if !ASSERTIONS
bob_defaults {
...
no_assertions: {
cflags: ["-DNDEBUG"],
},
}
The canonical way for a negative option is default y if !POSITIVE_OPTION
, as
shown above. It could also be done using depends
, but this is not
recommended, because to be technically correct the dependency should be
circular:
config ASSERTIONS
bool "Enable assertions"
depends on !NO_ASSERTIONS # Don't do this
default y
config NO_ASSERTIONS:
bool
depends on !ASSERTIONS
default n
Choice groups are used to indicate mutually exclusive config options:
choice
prompt "Build type"
default DEBUG_BUILD # If this is missing, the first option whose
# dependencies are satisfied will be the default.
help
Choose either an optimized build, for distribution, or an
unoptimized build with debug symbols, for debugging and
development.
config FAST_BUILD
bool "Optimized build"
depends on HAS_OPTIMIZING_COMPILER
config DEBUG_BUILD
bool "Debug build"
endchoice
Only one of FAST_BUILD
and DEBUG_BUILD
can be enabled, and an attempt to
enable them both using config
will result in an error.
The default value is chosen by adding a default
clause to the top-level
choice
section, instead of by putting one on a single config
option.
The select
keyword means that, when a given option is enabled, it will also
enable other option(s). Adding if
and a condition will make this conditional.
If those options cannot be enabled, for example if their dependencies are not satisfied, an error will be raised.
For example, the DEBUG_BUILD
option above could be modified as follows:
# ...inside "Build type" choice group
config DEBUG_BUILD
bool "Debug build"
select ASSERTIONS
select BUILD_UNIT_TESTS if UNIT_TEST_FRAMEWORK_FOUND
Putting options between menu
and endmenu
keywords will make them appear in
a separate menu within menuconfig
. Menus also support visible if
and
depends on
:
-
visible if
: If the associated condition is not satisfied, the menu will not appear inmenuconfig
. However, options within the menu may still become enabled. -
depends on
: If the associated condition is not satisfied, none of the options within the menu will be able to be enabled.
Using menuconfig
(instead of just menu
) creates a config option with an
associated sub-menu, which contains the options which depend on it:
menuconfig ENABLE_NEW_FEATURE
bool "Enable a new feature"
depends on A_PREREQUISITE
default y
config NEW_FEATURE_DOES_FOO
bool "Enable the foo functionality of the new feature"
default y
depends on ENABLE_NEW_FEATURE
config NEW_FEATURE_NAME
string "The unnecessarily-configurable name of the new feature"
default "bar"
depends on ENABLE_NEW_FEATURE
In the above case, NEW_FEATURE_DOES_FOO
and NEW_FEATURE_NAME
will not be
visible in the ncurses GUI menu unless ENABLE_NEW_FEATURE
is enabled.
The mainmenu
construct sets the title of the menuconfig
window:
mainmenu "Configuration for MyProgram"
config ...
Not all options can be determined statically, or from user settings. The configuration system allows Python plugins to inspect and change options.
A configuration plugin is a Python script containing the following:
import config_system
def plugin_exec():
# Read options using get_config
if config_system.get_config("TARGET_TOOLCHAIN_CLANG")["value"] == "y":
...
# Write them using set_config
if find_unit_test_framework():
config_system.set_config("UNIT_TEST_FRAMEWORK_FOUND", "y")
Config plugins are included in the build system by adding them to the
BOB_CONFIG_PLUGINS
variable during bootstrap:
export BOB_CONFIG_PLUGINS="path/to/config_plugin" # No .py extension
... # Other exports for Bob bootstrap
bob/bootstrap_linux.bash # or bootstrap_androidmk.bash