diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2020-04-04 12:23:40 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2020-04-08 13:12:45 +0200 |
commit | 7ef1fb36f19adc7e31939fcfd874b6ad4b6d25c1 (patch) | |
tree | 88db97d6a941d3fb62fa5844a82cecf5512c0842 | |
parent | 87d896f1107a077f3d1876f8feb9dcf9ee93ea3c (diff) |
Document project-specific configuration support
-rw-r--r-- | doc/manual.cli | 793 | ||||
-rw-r--r-- | libbuild2/config/utility.hxx | 2 | ||||
-rw-r--r-- | libbuild2/file.cxx | 3 |
3 files changed, 778 insertions, 20 deletions
diff --git a/doc/manual.cli b/doc/manual.cli index 5aea9c1..8821771 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -565,15 +565,15 @@ summary: hello C++ executable \ The \c{config} module provides support for persistent configurations. While -project configuration is a large topic that we will discuss in detail later, -in a nutshell \c{build2} support for configuration is an integral part of the -build system with the same mechanisms available to the build system core, -modules, and your projects. However, without \c{config}, the configuration -information is \i{transient}. That is, whatever configuration information was -automatically discovered or that you have supplied on the command line is -discarded after each build system invocation. With the \c{config} module, -however, we can \i{configure} a project to make the configuration -\i{persistent}. We will see an example of this shortly. +build configuration is a large topic that we will be discussing in more detail +later, in a nutshell \c{build2} support for configuration is an integral part +of the build system with the same mechanisms available to the build system +core, modules, and your projects. However, without \c{config}, the +configuration information is \i{transient}. That is, whatever configuration +information was automatically discovered or that you have supplied on the +command line is discarded after each build system invocation. With the +\c{config} module, however, we can \i{configure} a project to make the +configuration \i{persistent}. We will see an example of this shortly. Next up are the \c{test}, \c{install}, and \c{dist} modules. As their names suggest, they provide support for testing, installation and preparation of @@ -583,9 +583,11 @@ operations, and the \c{dist} module defines the \c{dist} (meta-)operation. Again, we will try them out in a moment. Moving on, the \c{root.build} file is optional though most projects will have -it. This is the place where we normally establish project-wide settings as -well as load build system modules that provide support for the languages/tools -that we use. Here is what it could look like for our \c{hello} example: +it. This is the place where we define project's configuration variables +(subject of \l{#proj-config Project Configuration}), establish project-wide +settings, as well as load build system modules that provide support for the +languages/tools that we use. Here is what it could look like for our \c{hello} +example: \ cxx.std = latest @@ -1391,7 +1393,7 @@ if ($c.target.class != 'windows') c.libs += -lpthread # only C \ -Additionally, as we will see in \l{#intro-operations-config Configuration}, +Additionally, as we will see in \l{#intro-operations-config Configuring}, there are also the \c{config.cc.*}, \c{config.c.*}, and \c{config.cxx.*} sets which are used by the users of our projects to provide external configuration. The initial values of the \c{cc.*}, \c{c.*}, and \c{cxx.*} variables are taken @@ -1620,11 +1622,11 @@ Other than \c{version}, all the modules we load define new operations. Let's examine each of them starting with \c{config}. -\h2#intro-operations-config|Configuration| +\h2#intro-operations-config|Configuring| As mentioned briefly earlier, the \c{config} module provides support for persisting configurations by having us \i{configure} our projects. At first it -may feel natural to call \c{configure} another operation. There is, however, a +may feel natural to call \c{configure} an operation. There is, however, a conceptual problem: we don't really configure a target. And, perhaps after some meditation, it should become clear that what we are really doing is configuring operations on targets. For example, configuring updating a C++ @@ -2102,7 +2104,7 @@ For details on the unit test support implementation see \l{#intro-unit-test Implementing Unit Testing}.| -\h2#intro-operations-install|Installation| +\h2#intro-operations-install|Installing| The \c{install} module defines the \c{install} and \c{uninstall} operations. As the name suggests, this module provides support for project installation. @@ -2287,7 +2289,7 @@ header would have been installed as \c{.../include/libhello/details/utility.hxx}. -\h2#intro-operations-dist|Distribution| +\h2#intro-operations-dist|Distributing| The last module that we load in our \c{bootstrap.build} is \c{dist} which provides support for the preparation of distributions and defines the \c{dist} @@ -3063,7 +3065,7 @@ libhello/ \ And we want to build them with several compilers, let's say GCC and Clang. As -we have already seen in \l{#intro-operations-config Configuration}, we can +we have already seen in \l{#intro-operations-config Configuring}, we can configure several out of source builds for each compiler, for example: \ @@ -4336,6 +4338,737 @@ the number of jobs to perform in parallel, the stack size, queue depths, etc. See the \l{b(1)} man pages for details. +\h1#proj-config|Project Configuration| + +As discussed in the introduction (specifically, \l{#intro-proj-struct Project +Structure}) support for build configurations is an integral part of \c{build2} +with the same mechanism used by the build system core (for example, for +project importation via the \c{config.import.*} variables), by the build +system modules (for example, for supplying compile options such as +\c{config.cxx.coptions}), as well as by our projects to provide any +project-specific configurability. Project configuration is the topic of this +chapter. + +\N|The \c{build2} build system currently provides no support for +\c{autoconf}-style probing of the build environment in order to automatically +discover available libraries, functions, features, etc. + +The main reason for omitting this support is the fundamental ambiguity and the +resulting brittleness of such probing due to the reliance on compiler, linker, +or test execution failures. Specifically, in many such tests it is impossible +for a build system to distinguish between a missing feature, a broken test, +and a misconfigured build environment. This leads to requiring a user +intervention in the best case and to a silently misconfigured build in the +worst. Other issues with this approach include portability, speed (compiling +and linking takes time), as well as limited applicability during +cross-compilation (specifically, inability to run tests). + +As a result, we recommend using \i{expectation-based} configuration where your +project assumes a feature to be available if certain conditions are +met. Examples of such conditions at the source code level include C++ feature +test macros, platform macros, runtime library macros, compiler macros, etc., +with the build system modules exposing some of the same information via +variables to allow making similar decisions in \c{buildfiles}. Another +alternative is to automatically adapt to missing features using more advanced +techniques such as C++ SFINAE. And in situations where none of this is +possible, we recommend delegating the decision to the user via a configuration +value. Our experience with \c{build2} as well as those of other large +cross-platform projects such as Boost show that this is a viable strategy. + +Having said that, \c{build2} does provide the ability to extract configuration +information from the environment (\c{$getenv()} function) or other tools +(\c{$process.run*()} family of functions). Note, however, that for this to +work reliably there should be no ambiguity between the \"no configuration +available\" case (if such a case is possible) and the \"something went wrong\" +case. We show a realistic example of this in \l{#proj-config-report +Configuration Report} where we extract the GCC plugin directory while dealing +with the possibility of it Bunin configured without plugin support.| + +Before we delve into the technical details, let's discuss the overall need for +project configurability. While it may seem that making ones project more +user-configurable is always a good idea, there are costs: by having a choice +we increase the complexity and open the door for potential incompatibility. +Specifically, we may end up with two projects in the same build needing a +shared dependency with incompatible configurations. + +\N|While some languages, such as Rust, support having multiple +differently-configured projects in the same build, this is not something that +is done often in C/C++. This ability is also not without its drawbacks, most +notably code bloat.| + +As a result, our recommendation is to strive for simplicity and avoid user +configurability whenever possible. For example, there is a common desire to +make certain functionality optional in order not to make the user pay for +things they don't need. This, however, is often better addressed either by +always providing the optional functionality if it's fairly small or by +factoring it into a separate project if it's substantial. If a configuration +value is to be provided, it should have a sensible default with a bias for +simplicity and compatibility rather than the optimal result. For example, in +the optional functionality case, the default should probably be to provide it. + +As discussed in the introduction, the central part of the build configuration +functionality are the \i{configuration variables}. One of the key features +that make them special is support for automatic persistence in the +\c{build/config.build} file provided by the \c{config} module (see +\l{#intro-operations-config Configuring} for details). The following example, +based on the \c{libhello} project from the introduction, gives an overview of +the project configuration functionality with the remainder of the chapter +providing the detailed explanation of all the parts shown as well as the +alternative approaches. + +\ +libhello/ +├── build/ +│ ├── root.build +│ └── ... +├── libhello/ +│ ├── hello.cxx +│ ├── buildfile +│ └── ... +└── ... +\ + +\ +# build/root.build + +config [string] config.libhello.greeting ?= 'Hello' +\ + +\ +# libhello/buildfile + +cxx.poptions += \"-DLIBHELLO_GREETING=\\\"$config.libhello.greeting\\\"\" +\ + +\ +// lihello/hello.cxx + +void say_hello (ostream& o, const string& n) +{ + o << LIBHELLO_GREETING \", \" << n << '!' << endl; +} +\ + +\ +$ b configure config.libhello.greeting=Hi -v +config libhello@/tmp/libhello/ + greeting Hi + +$ cat build/config.build +config.libhello.greeting = Hi + +$ b -v +g++ ... -DLIBHELLO_GREETING=\"Hi\" ... +\ + +By (enforced) convention, configuration variables start with \c{config.}, for +example, \c{config.import.libhello}. In case of a build system module, the +second component in its configuration variables should be the module name, for +example, \c{config.cxx}, \c{config.cxx.coptions}. Similarly, project-specific +configuration variables should have the project name as their second +component, for example, \c{config.libhello.greeting}. + +\N|More precisely, a project configuration variable must match the +\c{config[.**].<project>.**} pattern where additional components may be +present after \c{config.} in case of subprojects. Overall, the recommendation +is to use hierarchical names, such as \c{config.libcurl.tests.remote} for +subprojects, similar to build system submodules. + +If a build system module for a tool (such as a source code generator) and the +tool itself share a name, then they may need to coordinate their configuration +variable names in order to avoid clashes. + +The build system core reserves \c{build} and \c{import} as the second +component in configuration variables as well as \c{configured} as the third +and further components.| + + +\h#proj-config-directive|\c{config} Directive| + +To define a project configuration variable we add the \c{config} directive +into the project's \c{build/root.build} file (see \l{#intro-proj-struct +Project Structure}). For example: + + +\ +config [bool] config.libhello.fancy ?= false +config [string] config.libhello.greeting ?= 'Hello' +\ + +\N|The irony does not escape us: these configuration variables are exactly of +the kind that we advocate against. However, finding reasonable example of +build-time configurability in a \i{\"Hello, World!\"} library is not easy. In +fact, it probably shouldn't have any. So, for this chapter, do as we say, not +as we do.| + +Similar to \c{import} (see \l{#intro-import Target Importation}), the +\c{config} directive is a special kind of variable assignment. Let's examine +all its parts in turn. + +First comes the optional list of variable attributes inside \c{[\ ]}. The only +attribute that we have in the above example is the variable type, \c{bool} and +\c{string}, respectively. It is generally a good idea to assign static types +to configuration variables because their values will be specified by the users +of our project and the more automatic validation we provide the better (see +\l{#variables Variables} for the list of available types). For example, this +is what will happen if we misspell the value of the \c{fancy} variable: + +\ +$ b configure config.libhello.fancy=fals +error: invalid bool value 'fals' in variable config.libhello.fancy +\ + +After the attribute list we have the variable name. The \c{config} directive +will validate that it matches the \c{config[.**].<project>.**} pattern (with +one exception discussed in \l{#proj-config-report Configuration Report}). + +Finally, after the variable name comes optional default value. Note that +unlike normal variables, default value assignment (\c{?=}) is the only valid +form of assignment in the \c{config} directive. + +The semantics of the \c{config} directive is as follows: First an overridable +variable is entered with the specified name, type (if any), and global +visibility. Then, if the variable is undefined and the default value is +specified, it is assigned the default value. After this, if the variable is +defined (either as user-defined or default), it is marked for persistence. +Finally, a defined variable is also marked for reporting as discussed in +\l{#proj-config-report Configuration Report}. Note that if the variable +is user-defined, then the default value is not evaluated. + +Note also that if the configuration value is not specified by the user and you +haven't provided the default, the variable will be undefined, not \c{null}, +and, as a result, omitted from the persistent configuration +(\c{build/config.build} file). However, \c{null} is a valid default value. It +is is traditionally used for \i{optional} configuration values. For example: + +\ +config [string] config.libhello.fallback_name ?= [null] +\ + +A common approach for representing an C/C++ enum-like value is to use +\c{string} as a type and pattern matching for validation. In fact, validation +and propagation can often be combined. For example, if our library needed to +use a database for some reason, we could handle it like this: + +\ +config [string] config.libhello.database ?= [null] + +using cxx + +switch $config.libhello.database +{ + case [null] + { + # No database in use. + } + case 'sqlite' + { + cxx.poptions += -DLIBHELLO_WITH_SQLITE + } + case 'pgsql' + { + cxx.poptions += -DLIBHELLO_WITH_PGSQL + } + default + { + fail \"invalid config.libhello.database value \ +'$config.libhello.database'\" + } +} +\ + +While it is generally a good idea to provide a sensible default for all your +configuration variables, if you need to force the user to specify its value +explicitly, this can be achieved with an extra check. For example: + +\ +config [string] config.libhello.database + +if! $defined(config.libhello.database) + fail 'config.libhello.database must be specified' +\ + +And if you want to also disallow \c{null} values, then the above check should +be rewritten like this: \N{An undefined variable expands into a \c{null} +value.} + +\ +if ($config.libhello.database == [null]) + fail 'config.libhello.database must be specified' +\ + +If computing the default value is expensive or requires elaborate logic, then +handling of a configuration variable can be broken down into two steps along +these lines: + +\ +config [string] config.libhello.greeting + +if! $defined(config.libhello.greeting) +{ + greeting = ... # Calculate default value. + + if ($greeting == [null]) + fail \"unable to calculate default greeting, specify manually \ +with config.libhello.greeting\" + + config config.libhello.greeting ?= $greeting +} +\ + +Other than assigning the default value via the \c{config} directive, +configuration variables should not be modified by the project's +\c{buildfiles}. Instead, if further processing of the configuration value is +necessary, we should assign the configuration value to a different, +non-\c{config.*}, variable and modify that. The two situations where this is +commonly required are post-processing of configuration values to be more +suitable for use in \c{buildfiles} as well as further customization of +configuration values. Let's see examples of both. + +To illustrate the first situation, let's say we need to translate the database +identifiers specified by the user: + +\ +config [string] config.libhello.database ?= [null] + +switch $config.libhello.database +{ + case [null] + database = [null] + + case 'sqlite' + database = 'SQLITE' + + case 'pgsql' + database = 'PGSQL' + + case 'mysql' + case 'mariadb' + database = 'MYSQL' + + default + fail \"...\" + } +} + +using cxx + +if ($database != [null]) + cxx.poptions += \"-DLIBHELLO_WITH_$database\" +\ + +For the second situation, the typical pattern looks like this: + +\ +config [strings] config.libhello.options + +options = # Overridable options go here. +options += $config.libhello.options +options += # Non-overridable options go here. +\ + +That is, assuming that the subsequently specified options (for example, +command line options) override any previously specified, we first set default +\c{buildfile} options that are allowed to be overridden by options from the +configuration value, then append such options, if any, and finish off by +appending \c{buildfile} options that should always be in effect. + +As a concrete example of this approach, let's say we want to make the compiler +warning level of our project configurable (likely a bad idea; also ignores +compiler differences): + +\ +config [strings] config.libhello.woptions + +woptions = -Wall -Wextra +woptions += $config.libhello.woptions +woptions += -Werror + +using cxx + +cxx.coptions += $woptions +\ + +With this arrangement, the users of our project can customize the warning +level but cannot disable the treatment of warnings as errors. For example: + +\ +$ b -v config.libhello.woptions=-Wno-extra +g++ ... -Wall -Wextra -Wno-extra -Werror ... +\ + +While we have already seen some examples of how to propagate the configuration +values to our source code, \l{#proj-config-propag Configuration Propagation} +discusses this topic in more detail. + +this topic is discussed further in +. + + +\h#proj-config-report|Configuration Report| + +One of the effects of the \c{config} directive is to mark a defined +configuration variable for reporting. The project configuration report is +printed automatically at the sufficiently high verbosity level along with the +build system module configuration. For example (some of the \c{cxx} module +configuration is omitted for brevity): + +\ +$ b config.libhello.greeting=Hey -v +cxx libhello@/tmp/libhello/ + cxx g++@/usr/bin/g++ + id gcc + version 9.1.0 + ... +config libhello@/tmp/libhello/ + fancy false + greeting Hey +\ + +\N|The configuration report is printed immediately after loading the project's +\c{build/root.build} file. It is always printed at verbosity level \c{3} +(\c{-V}) or higher. It is also printed at verbosity level \c{2} (\c{-v}) if +any of the reported configuration variables have a \i{new} value. A value is +considered new if it was set to default or was overridden on the command +line.| + +The project configuration report header (the first line) starts with the +special \c{config} module name (the \c{config} module itself does not have a +report) followed by the project name and its \c{out_root} path. After the +header come configuration variables with the \c{config[.**].<project>} prefix +removed. The configuration report for each variable can be customized using a +number of \c{config.report*} attributes as discussed next. + +The \c{config.report} attribute controls whether the variable is included into +the report and, if so, the format to print its value in. For example, this is +how we can exclude a variable from the report: + +\ +config [bool, config.report=false] config.libhello.selftest ?= false +\ + +While we would normally want to report all our configuration variables , if +some of them are internal and not meant to be used by the users of our +project, it probably makes sense to exclude them. + +The only currently supported alternative printing format is \c{multiline} +which prints a list value one element per line. \N{Other printing formats may +be supported in the future.} For example: + +\ +config [dir_paths, config.report=multiline] config.libhello.search_dirs +\ + +\ +$ b config.libhello.search_dirs=\"/etc/default /etc\" -v +config libhello@/tmp/libhello/ + search_dirs + /etc/default/ + /etc/ +\ + +The \c{config.report} attribute can also be used to include a non-\c{config.*} +variable into a report. This is primarily useful for configuration values +that are always discovered automatically but that are still useful to report +for troubleshooting. Here is a realistic example: + +\ +using cxx + +# Determine the GCC plugin directory. +# +if ($cxx.id == 'gcc') +{ + plugin_dir = [dir_path] $process.run($cxx.path -print-file-name=plugin) + + # If plugin support is disabled, then -print-file-name will print + # the name we have passed (the real plugin directory will always + # be absolute). + # + if (\"$plugin_dir\" == plugin) + fail \"$recall($cxx.path) does not support plugins\" + + config [config.report] plugin_dir +} +\ + +\N|This is the only situation where a variable that does not match the +\c{config[.**].<project>.**} pattern is allowed in the \c{config} directive. +Note also that a value of such a variable is never considered new.| + +Note that this mechanism should not be used to report configuration values +that require post-processing because of the loss of the new value status +(unless you are reporting both the original and post-processed values). +Instead, use the \c{config.report.variable} attribute to specify an +alternative variable for the report. For example: + +\ +config [strings, config.report.variable=woptions] \ + config.libhello.woptions + +woptions = -Wall -Wextra +woptions += $config.libhello.woptions +woptions += -Werror +\ + +\ +$ b config.libhello.woptions=-Wno-extra -v +config libhello@/tmp/libhello/ + woptions -Wall -Wextra -Wno-extra -Werror +\ + + +\h#proj-config-propag|Configuration Propagation| + +Using configuration values in our \c{buildfiles} is straightforward: they are +like any other \c{buildfile} variables and we can access them directly. For +example, this is how we could provide optional functionality in our library by +conditionally including certain source files: \N{See \l{#intro-if-else +Conditions (\c{if-else})} for why we should not use \c{if} to implement +this.} + +\ +# build/root.build + +config [strings] config.libhello.io ?= true +\ + +\ +# libhello/buildfile + +lib{hello}: {hxx ixx txx cxx}{** -version -hello-io} hxx{version} +lib{hello}: {hxx cxx}{hello-io}: include = $config.libhello.io +\ + +On the other hand, it is often required to propagate the configuration +information to our source code. In fact, we have already seen one way to do +it: we can pass this information via C/C++ preprocessor macros defined on the +compiler's command line. For example: + +\ +# build/root.build + +config [bool] config.libhello.fancy ?= false +config [string] config.libhello.greeting ?= 'Hello' +\ + +\ +# libhello/buildfile + +if $config.libhello.fancy + cxx.poptions += -DLIBHELLO_FANCY + +cxx.poptions += \"-DLIBHELLO_GREETING=\\\"$config.libhello.greeting\\\"\" +\ + +\ +// lihello/hello.cxx + +void say_hello (ostream& o, const string& n) +{ +#ifdef LIBHELLO_FANCY + // TODO: something fancy. +#else + o << LIBHELLO_GREETING \", \" << n << '!' << endl; +#endif +} +\ + +We can even use the same approach to export certain configuration information +to our library's users (see \l{#intro-lib Library Exportation and Versioning} +for details): + +\ +# libhello/buildfile + +# Export options. +# +if $config.libhello.fancy + lib{hello}: cxx.export.poptions += -DLIBHELLO_FANCY +\ + +This mechanism is simple and works well across compilers so there is no reason +not to use it when the number of configuration values passed and their size +are small. However, it can quickly get unwieldy as these numbers grow. For +such cases, it may make sense to save this information into a separate +auto-generated source file with the help of the \l{#module-in \c{in}} module, +similar to how we do it for the version header. + +The often-used approach is to generate a header file and include it into +source files that need access to the configuration information. Historically, +this was a C header full of macros called \c{config.h}. However, for C++ +projects, there is no reason not to make it a C++ header and, if desired, to +use modern C++ features instead of macros. Which is what we will do here. + +As an example of this approach, let's convert the above command line-based +implementation to use the configuration header. We will continue using macros +as a start (or in case this is a C project) and try more modern techniques +later. The \c{build/root.build} file is unchanged except for loading the +\c{in} module: + +\ +# build/root.build + +config [bool] config.libhello.fancy ?= false +config [string] config.libhello.greeting ?= 'Hello' + +using in +\ + +The \c{libhello/config.hxx.in} file is new: + +\ +// libhello/config.hxx.in + +#pragma once + +#define LIBHELLO_FANCY $config.libhello.fancy$ +#define LIBHELLO_GREETING \"$config.libhello.greeting$\" +\ + +As you can see, we can reference our configuration variables directly in the +\c{config.hxx.in} substitutions (see \l{#module-in \c{in} Module} for details +on how this works). The rest is changed as follows: + +\ +# libhello/buildfile + +lib{hello}: {hxx ixx txx cxx}{** -version -config} hxx{version config} + +hxx{config}: in{config} +{ + install = false +} +\ + +\ +// lihello/hello.cxx + +#include <libhello/config.hxx> + +void say_hello (ostream& o, const string& n) +{ +#if LIBHELLO_FANCY + // TODO: something fancy. +#else + o << LIBHELLO_GREETING \", \" << n << '!' << endl; +#endif +} +\ + +\N|With this setup, the way to export configuration information to our +library's users is to make the configuration header public and install it, +similar to how we do it for the version header.| + +Now that the macro-based version is working, let's see how we can take +advantage of modern C++ features to hopefully improve on some of their +drawbacks. As a first step, we can replace the \c{LIBHELLO_FANCY} macro with a +compile-time constant and use \c{if\ constexpr} instead of \c{#ifdef} in our +implementation: + +\ +// libhello/config.hxx.in + +namespace hello +{ + inline constexpr bool fancy = $config.libhello.fancy$; +} +\ + +\ +// lihello/hello.cxx + +#include <libhello/config.hxx> + +void say_hello (ostream& o, const string& n) +{ + if constexpr (fancy) + { + // TODO: something fancy. + } + else + o << LIBHELLO_GREETING \", \" << n << '!' << endl; +} +\ + +\N|Note that with \c{if\ constexpr} the branch not taken must still be valid, +parsable code. This is both one of the main benefits of using it instead of +\c{#if} (the code we are not using is still guaranteed to be syntactically +correct) as well as its main drawbacks (it cannot be used, for example, for +platform-specific code without extra efforts, such as providing shims for +missing declarations, etc).| + +Next, we can do the same for \c{LIBHELLO_GREETING}: + +\ +// libhello/config.hxx.in + +namespace hello +{ + inline constexpr char greeting[] = \"$config.libhello.greeting$\"; +} +\ + +\ +// lihello/hello.cxx + +#include <libhello/config.hxx> + +void say_hello (ostream& o, const string& n) +{ + if constexpr (fancy) + { + // TODO: something fancy. + } + else + o << greeting << \", \" << n << '!' << endl; +} +\ + +\N|Note that for \c{greeting} we can achieve the same result without using +inline variables or \c{constexpr} and which would be usable in older C++ and +even C. All we have to do is add the \c{config.cxx.in} source file next to +our header with the definition of the \c{greeting} variable. For example: + +\ +// libhello/config.hxx.in + +namespace hello +{ + extern const char greeting[]; +} +\ + +\ +// libhello/config.cxx.in + +#include <libhello/config.hxx> + +namespace hello +{ + const char greeting[] = \"$config.libhello.greeting$\"; +} +\ + +\ +# libhello/buildfile + +lib{hello}: {hxx ixx txx cxx}{** -config} {hxx cxx}{config} + +hxx{config}: in{config} +{ + install = false +} + +cxx{config}: in{config} +\ + +As this illustrates, the \c{in} module can produce as many auto-generated +source files as we need. For example, we could use this to split the +configuration header into two, one public and installed while the other +private.| + + \h1#attributes|Attributes| \N{This chapter is a work in progress and is incomplete.} @@ -4576,6 +5309,30 @@ name). Project-qualified names are never considered to be patterns. \N{This chapter is a work in progress and is incomplete.} +The following variable/value types can currently be used in \c{buildfiles}: + +\ +bool + +uint64 +uint64s + +string +strings + +path +paths +dir_path +dir_paths + +name +names +name_pair + +project_name +target_triplet +\ + Note that while expansions in the target and prerequisite-specific assignments happen in the corresponding target and prerequisite contexts, respectively, for type/pattern-specific assignments they happen in the scope context. Plus, diff --git a/libbuild2/config/utility.hxx b/libbuild2/config/utility.hxx index 38a3ae1..ee266f1 100644 --- a/libbuild2/config/utility.hxx +++ b/libbuild2/config/utility.hxx @@ -97,7 +97,7 @@ namespace build2 // // Note also that we can first do the lookup without the default value and // then, if there is no value, call the version with the default value and - // end up with the same result if we called the default value version + // end up with the same result as if we called the default value version // straight away. This is useful when computing the default value is // expensive. It is also ok to call both versions multiple times provided // the flags are the same. diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx index 405e1f0..3e6282c 100644 --- a/libbuild2/file.cxx +++ b/libbuild2/file.cxx @@ -1309,7 +1309,8 @@ namespace build2 // @@ TODO/MAYBE: // - // - Should we be printing NULL values? + // - Should we be printing NULL values? Maybe make this configurable? + // - Quoted printing format (single/double)? // // Use the special `config` module name (which doesn't have its own |