aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbootstrap.sh2
-rw-r--r--doc/manual.cli1020
-rw-r--r--libbuild2/buildfile4
-rw-r--r--libbuild2/c/init.cxx26
-rw-r--r--libbuild2/cc/buildfile8
-rw-r--r--libbuild2/cc/common.cxx14
-rw-r--r--libbuild2/cc/compile-rule.cxx302
-rw-r--r--libbuild2/cc/guess.cxx44
-rw-r--r--libbuild2/cc/link-rule.cxx70
-rw-r--r--libbuild2/cc/predefs-rule.cxx2
-rw-r--r--libbuild2/cc/std.compat.cppm996
-rw-r--r--libbuild2/cc/std.cppm134
-rw-r--r--libbuild2/cxx/init.cxx51
-rw-r--r--libbuild2/diagnostics.cxx36
-rw-r--r--libbuild2/functions-filesystem.cxx51
-rw-r--r--libbuild2/functions-path.cxx438
-rw-r--r--libbuild2/functions-regex.cxx3
-rw-r--r--libbuild2/functions-string.cxx246
-rw-r--r--libbuild2/scheduler.cxx12
-rw-r--r--libbuild2/test/script/parser.cxx11
-rw-r--r--libbuild2/utility.hxx28
-rw-r--r--libbuild2/utility.txx21
-rw-r--r--libbuild2/variable.hxx15
-rw-r--r--libbuild2/variable.txx35
-rw-r--r--manifest1
-rw-r--r--repositories.manifest6
-rw-r--r--tests/function/filesystem/testscript48
-rw-r--r--tests/function/path/testscript82
-rw-r--r--tests/function/string/testscript72
29 files changed, 2583 insertions, 1195 deletions
diff --git a/bootstrap.sh b/bootstrap.sh
index 9bd13b4..26796f9 100755
--- a/bootstrap.sh
+++ b/bootstrap.sh
@@ -147,4 +147,4 @@ done
# mode since 4.9 doesn't recognize c++1z.
#
set -x
-"$cxx" "-I$libbutl" -I. -DBUILD2_BOOTSTRAP '-DBUILD2_HOST_TRIPLET="'"$host"'"' -finput-charset=UTF-8 -std=c++1y "$@" -o build2/b-boot $r -lpthread
+"$cxx" "-I$libbutl" -I. -DBUILD2_BOOTSTRAP '-DBUILD2_HOST_TRIPLET="'"$host"'"' -finput-charset=UTF-8 -std=c++1y "$@" -o build2/b-boot $r -pthread
diff --git a/doc/manual.cli b/doc/manual.cli
index 847691d..4c55374 100644
--- a/doc/manual.cli
+++ b/doc/manual.cli
@@ -7040,7 +7040,7 @@ managing projects that conform to the \c{build2} \i{standard version}
format. If you are starting a new project that uses \c{build2}, you are
strongly encouraged to use this versioning scheme. It is based on much thought
and, often painful, experience. If you decide not to follow this advice, you
-are essentially on your own when version management is concerned.
+are essentially on your own where version management is concerned.
The standard \c{build2} project version conforms to \l{http://semver.org
Semantic Versioning} and has the following form:
@@ -8568,6 +8568,7 @@ hxx{*}: extension = hpp
mxx{*}: extension = cppm
\
+
\h#cxx-modules|C++ Modules Support|
This section describes the build system support for C++ modules.
@@ -8575,7 +8576,9 @@ This section describes the build system support for C++ modules.
\h2#cxx-modules-intro|Modules Introduction|
The goal of this section is to provide a practical introduction to C++ Modules
-and to establish key concepts and terminology.
+and to establish key concepts and terminology. You can skip directly to
+\l{#cxx-modules-build Building Modules} if you are already familiar with this
+topic.
A pre-modules C++ program or library consists of one or more \i{translation
units} which are customarily referred to as C++ source files. Translation
@@ -8626,7 +8629,7 @@ called \i{module interface}, that become \i{visible} once the module is
imported:
\
-import hello.core
+import hello.core;
\
What exactly does \i{visible} mean? To quote the standard: \i{An
@@ -8646,8 +8649,8 @@ module name and its namespace names need not be related, it usually makes
sense to have a parallel naming scheme, as discussed below. Finally, the
\c{import} declaration does not imply any additional visibility for names
declared inside namespaces. Specifically, to access such names we must
-continue using the standard mechanisms, such as qualification or using
-declaration/directive. For example:
+continue using the existing mechanisms, such as qualification or using
+declaration/directive. For example:
\
import hello.core; // Exports hello::say().
@@ -8666,7 +8669,7 @@ link an object file or a library that provides them. In this respect, modules
are similar to headers and as with headers, module's use is not limited to
libraries; they make perfect sense when structuring programs. Furthermore,
a library may also have private or implementation modules that are not
-meant to be consumed by the library's users.
+meant to be imported by the library's consumers.
The producer perspective on modules is predictably more complex. In
pre-modules C++ we only had one kind of translation unit (or source
@@ -8674,6 +8677,11 @@ file). With modules there are three kinds: \i{module interface unit},
\i{module implementation unit}, and the original kind which we will
call a \i{non-module translation unit}.
+\N|There are two additional modular translation units: module interface
+partition and module implementation partition. While partitions are supported,
+they are not covered in this introduction. A link to a complete example
+that uses both types of partitions will be given in the next section.|
+
From the producer's perspective, a module is a collection of module translation
units: one interface unit and zero or more implementation units. A simple
module may consist of just the interface unit that includes implementations
@@ -8684,14 +8692,14 @@ A translation unit is a module interface unit if it contains an \i{exporting
module declaration}:
\
-export module hello.core;
+export module hello;
\
A translation unit is a module implementation unit if it contains a
\i{non-exporting module declaration}:
\
-module hello.core;
+module hello;
\
While module interface units may use the same file extension as normal source
@@ -8703,17 +8711,37 @@ are using some other naming scheme, then perhaps now is a good opportunity to
switch to one of the above. Continuing using the source file extension for
module implementation units appears reasonable and that's what we recommend.
+A modular translation unit (that is, either module interface or
+implementation) that does not start with one of the above module declarations
+must then start with the module introducer:
+
+\
+module;
+
+...
+
+export module hello;
+\
+
+The fragment from the module introducer and until the module declaration is
+called the \i{global module fragment}. Any name declared in the global module
+fragment belongs to the \i{global module}, an implied module containing
+\"old\" or non-modular declarations that don't belong to any named module.
+
A module declaration (exporting or non-exporting) starts a \i{module purview}
that extends until the end of the module translation unit. Any name declared
in a module's purview \i{belongs} to the said module. For example:
\
-#include <string> // Not in purview.
+module; // Start of global module fragment.
-export module hello.core; // Start of purview.
+#include <cassert> // Not in purview.
-void
-say_hello (const std::string&); // In purview.
+export module hello; // Start of purview.
+
+import std; // In purview.
+
+void say_hello (const std::string&); // In purview.
\
A name that belongs to a module is \i{invisible} to the module's consumers
@@ -8723,26 +8751,24 @@ ways to accomplish this. We can start the declaration with the \c{export}
specifier, for example:
\
-export module hello.core;
+export module hello;
export enum class volume {quiet, normal, loud};
-export void
-say_hello (const char*, volume);
+export void say_hello (const char*, volume);
\
Alternatively, we can enclose one or more declarations into an \i{exported
group}, for example:
\
-export module hello.core;
+export module hello;
export
{
enum class volume {quiet, normal, loud};
- void
- say_hello (const char*, volume);
+ void say_hello (const char*, volume);
}
\
@@ -8750,42 +8776,33 @@ Finally, if a namespace definition is declared exported, then every name
in its body is exported, for example:
\
-export module hello.core;
+export module hello;
export namespace hello
{
enum class volume {quiet, normal, loud};
- void
- say (const char*, volume);
+ void say_hello (const char*, volume);
}
namespace hello
{
- void
- impl (const char*, volume); // Not exported.
+ void impl (const char*, volume); // Not exported.
}
\
Up until now we've only been talking about names belonging to a module. What
-about the corresponding symbols? For exported names, the resulting symbols
-would be the same as if those names were declared outside of a module's
-purview (or as if no modules were used at all). Non-exported names, on the
-other hand, have \i{module linkage}: their symbols can be resolved from this
-module's units but not from other translation units. They also cannot clash
-with symbols for identical names from other modules (and non-modules). This is
-usually achieved by decorating the non-exported symbols with the module name.
-
-This ownership model has an important backwards compatibility implication: a
-library built with modules enabled can be linked to a program that still uses
-headers. And even the other way around: we can build and use a module for a
-library that was built with headers.
+about the corresponding symbols? All the major C++ compilers have chosen to
+implement the so-called strong ownership model, where for both exported and
+non-exported names, the corresponding symbols are decorated with the module
+name. As a result, they cannot clash with symbols for identical names from
+other named modules or the global module.
What about the preprocessor? Modules do not export preprocessor macros,
only C++ names. A macro defined in the module interface unit cannot affect
the module's consumers. And macros defined by the module's consumers cannot
affect the module interface they are importing. In other words, module
-producers and consumers are isolated from each other when the preprocessor
+producers and consumers are isolated from each other where the preprocessor
is concerned. For example, consider this module interface:
\
@@ -8810,9 +8827,10 @@ import hello;
#endif
\
-This is not to say that the preprocessor cannot be used by either, it just
-doesn't \"leak\" through the module interface. One practical implication of
-this model is the insignificance of the import order.
+This is not to say that the preprocessor cannot be used by either the module
+interface or its consumer, it just that macros don't \"leak\" through the
+module interface. One practical consequence of this model is the
+insignificance of the importation order.
If a module imports another module in its purview, the imported module's
names are not made automatically visible to the consumers of the importing
@@ -8822,10 +8840,9 @@ interface as an example:
\
export module hello;
-import std.core;
+import std;
-export void
-say_hello (const std::string&);
+export std::string formal_hello (const std::string&);
\
And its consumer:
@@ -8836,26 +8853,25 @@ import hello;
int
main ()
{
- say_hello (\"World\");
+ std::string s (format_hello (\"World\"));
}
\
This example will result in a compile error and the diagnostics may
-confusingly indicate that there is no known conversion from a C string to
-\"something\" called \c{std::string}. But with the understanding of the
-difference between \c{import} and \c{#include} the reason should be clear:
-while the module interface \"sees\" \c{std::string} (because it imported its
-module), we (the consumer) do not (since we did not). So the fix is to
-explicitly import \c{std.core}:
+confusingly indicate that there is no member \c{string} in namespace \c{std}.
+But with the understanding of the difference between \c{import} and
+\c{#include} the reason should be clear: while the module interface \"sees\"
+\c{std::string} (because it imported its module), we (the consumer) do not
+(since we did not). So the fix is to explicitly import \c{std}:
\
-import std.core;
+import std;
import hello;
int
main ()
{
- say_hello (\"World\");
+ std::string s (format_hello (\"World\"));
}
\
@@ -8868,10 +8884,9 @@ this is a good design choice is debatable, as discussed below):
\
export module hello;
-export import std.core;
+export import std;
-export void
-say_hello (const std::string&);
+export std::string formal_hello (const std::string&);
\
One way to think of a re-export is \i{as if} an import of a module also
@@ -8896,41 +8911,41 @@ export
\
Besides starting a module purview, a non-exporting module declaration in the
-implementation unit makes non-internal linkage names declared or made visible
-in the \i{interface purview} also visible in the \i{implementation purview}.
-In this sense non-exporting module declaration acts as an extended
-\c{import}. For example:
+implementation unit makes (non-internal linkage) names declared or made
+visible (via import) in the module purview of an interface unit also visible
+in the module purview of the implementation unit. In this sense a
+non-exporting module declaration acts as a special \c{import}. The following
+example illustrates this point:
\
+module;
+
import hello.impl; // Not visible (exports impl()).
-void
-extra_impl (); // Not visible.
+#include <string.h> // Not visible (declares strlen()).
-export module hello.extra; // Start of interface purview.
+export module hello.extra; // Start of module purview (interface).
import hello.core; // Visible (exports core()).
-void
-extra (); // Visible.
+void extra (); // Visible.
-static void
-extra2 (); // Not visible (internal linkage).
+static void extra2 (); // Not visible (internal linkage).
\
And this is the implementation unit:
\
-module hello.extra; // Start of implementation purview.
+module hello.extra; // Start of module purview (implementation).
void
f ()
{
- impl (); // Error.
- extra_impl (); // Error.
- core (); // Ok.
- extra (); // Ok.
- extra2 (); // Error.
+ impl (); // Error.
+ strlen (\"\"); // Error.
+ core (); // Ok.
+ extra (); // Ok.
+ extra2 (); // Error.
}
\
@@ -8940,9 +8955,9 @@ to the module declaration can be.
The final perspective that we consider is that of the build system. From its
point of view the central piece of the module infrastructure is the \i{binary
-module interface}: a binary file that is produced by compiling the module
-interface unit and that is required when compiling any translation unit that
-imports this module as well as the module's implementation units.
+module interface} or BMI: a binary file that is produced by compiling the
+module interface unit and that is required when compiling any translation unit
+that imports this module as well as the module's implementation units.
Then, in a nutshell, the main functionality of a build system when it comes to
modules support is figuring out the order in which all the translation units
@@ -8979,53 +8994,58 @@ compile them, again, on the side.
\h2#cxx-modules-build|Building Modules|
-Compiler support for C++ Modules is still experimental. As a result, it is
-currently only enabled if the C++ standard is set to \c{experimental}. After
-loading the \c{cxx} module we can check if modules are enabled using the
-\c{cxx.features.modules} boolean variable. This is what the relevant
+Compiler support for C++ modules is still experimental, incomplete, and often
+buggy. Also, in \c{build2}, the presence of modules changes the C++
+compilation model in ways that would introduce unnecessary overheads for
+headers-only code. As a result, a project must explicitly enable modules using
+the \c{cxx.features.modules} boolean variable. This is what the relevant
\c{root.build} fragment could look like for a modularized project:
\
-cxx.std = experimental
+cxx.std = latest
+cxx.features.modules = true
using cxx
-assert $cxx.features.modules 'compiler does not support modules'
-
mxx{*}: extension = mxx
cxx{*}: extension = cxx
\
-To support C++ modules the \c{cxx} module (build system) defines several
-additional target types. The \c{mxx{\}} target is a module interface unit.
-As you can see from the above \c{root.build} fragment, in this project we
-are using the \c{.mxx} extension for our module interface files. While
-you can use the same extension as for \c{cxx{\}} (source files), this is
-not recommended since some functionality, such as wildcard patterns, will
-become unusable.
+\N|Note that you must explicitly enable modules in your project even if you
+are only importing other modules, including standard library modules (\c{std}
+or \c{std.compat}).|
+
+To support C++ modules the \c{cxx} build system module defines several
+additional target types. The \c{mxx{\}} target is a module interface unit. As
+you can see from the above \c{root.build} fragment, in this project we are
+using the \c{.mxx} extension for our module interface files. While you can use
+the same extension as for \c{cxx{\}} (source files), this is not recommended
+since some functionality, such as wildcard patterns, will become unusable.
The \c{bmi{\}} group and its \c{bmie{\}}, \c{bmia{\}}, and \c{bmis{\}} members
are used to represent binary module interfaces targets. We normally do not
-need to mention them explicitly in our buildfiles except, perhaps, to specify
-additional, module interface-specific compile options. We will see some
-examples of this below.
+need to mention them explicitly in our \c{buildfiles} except, perhaps, to
+specify additional, module interface-specific compile options.
To build a modularized executable or library we simply list the module
interfaces as its prerequisites, just as we do for source files. As an
example, let's build the \c{hello} program that we have started in the
introduction (you can find the complete project in the
-\l{https://build2.org/pkg/hello Hello Repository} under
-\c{mhello}). Specifically, we assume our project contains the following files:
+\l{https://github.com/build2/cxx20-modules-examples/tree/named-only-import-std
+\c{cxx20-modules-examples}} repository under \c{hello-module}). Specifically,
+we assume our project contains the following files:
\
// file: hello.mxx (module interface)
export module hello;
-import std.core;
+import std;
-export void
-say_hello (const std::string&);
+export namespace hello
+{
+ void say_hello (const std::string_view& name);
+}
\
\
@@ -9033,27 +9053,24 @@ say_hello (const std::string&);
module hello;
-import std.io;
-
-using namespace std;
-
-void
-say_hello (const string& name)
+namespace hello
{
- cout << \"Hello, \" << name << '!' << endl;
+ void say_hello (const std::string_view& n)
+ {
+ std::cout << \"Hello, \" << n << '!' << std::endl;
+ }
}
\
\
-// file: driver.cxx
+// file: main.cxx
-import std.core;
import hello;
int
main ()
{
- say_hello (\"World\");
+ hello::say_hello (\"World\");
}
\
@@ -9061,7 +9078,7 @@ To build a \c{hello} executable from these files we can write the following
\c{buildfile}:
\
-exe{hello}: cxx{driver} {mxx cxx}{hello}
+exe{hello}: cxx{main} {mxx cxx}{hello}
\
Or, if you prefer to use wildcard patterns:
@@ -9070,14 +9087,40 @@ Or, if you prefer to use wildcard patterns:
exe{hello}: {mxx cxx}{*}
\
-Alternatively, we can package the module into a library and then link the
-library to the executable:
+\N|Module partitions, both interface and implementation, are compiled to BMIs
+and as a result must be listed as \c{mxx{\}} prerequisites. See
+\c{hello-partition} in the
+\l{https://github.com/build2/cxx20-modules-examples/tree/named-only-import-std
+\c{cxx20-modules-examples}} repository for a complete example.|
+
+Alternatively, we can place the module into a library and then link the
+library to the executable (see \c{hello-library-module} in the
+\l{https://github.com/build2/cxx20-modules-examples/tree/named-only-import-std
+\c{cxx20-modules-examples}} repository):
\
-exe{hello}: cxx{driver} lib{hello}
+exe{hello}: cxx{main} lib{hello}
lib{hello}: {mxx cxx}{hello}
\
+Note that a library consisting of only module interface units is by default
+always binful (see \l{#intro-lib Library Exportation and Versioning} for
+background) since compiling a module interface always results in an object
+file, even if the module interface does not contain any non-inline/template
+functions or global variables. However, you can explicitly request for such a
+library to be treated as binless:
+
+\
+lib{hello}: mxx{hello}
+{
+ bin.binless = true
+}
+\
+
+\N|Note that if such a binless library has non-inline/template functions or
+global variables, then whether it can used in all situations without causing
+duplicate symbols is platform-dependent.|
+
As you might have surmised from this example, the modules support in
\c{build2} automatically resolves imports to module interface units that are
specified either as direct prerequisites or as prerequisites of library
@@ -9096,9 +9139,11 @@ guess is verified.
The practical implication of this implementation detail is that our module
interface files must embed a portion of a module name, or, more precisely, a
sufficient amount of \"module name tail\" to unambiguously resolve all the
-modules used in a project. Note also that this guesswork is only performed for
+modules used in a project. Note that this guesswork is only performed for
direct module interface prerequisites; for those that come from libraries the
-module names are known and are therefore matched exactly.
+module names are known and are therefore matched exactly. And the guesses are
+always verified before the actual compilation, so misguesses cannot go
+unnoticed.
As an example, let's assume our \c{hello} project had two modules:
\c{hello.core} and \c{hello.extra}. While we could call our interface files
@@ -9133,46 +9178,19 @@ with module names, for example:
mxx{foobar}@./: cxx.module_name = hello
\
-Note also that standard library modules (\c{std} and \c{std.*}) are treated
-specially: they are not fuzzy-matched and they need not be resolvable to
-the corresponding \c{mxx{\}} or \c{bmi{\}} in which case it is assumed
-they will be resolved in an ad hoc way by the compiler. This means that if
-you want to build your own standard library module (for example, because
-your compiler doesn't yet ship one; note that this may not be supported
-by all compilers), then you have to specify the module name explicitly.
-For example:
-
-\
-exe{hello}: cxx{driver} {mxx cxx}{hello} mxx{std-core}
-
-mxx{std-core}@./: cxx.module_name = std.core
-\
+Note also that the standard library modules (\c{std} and \c{std.compat}) are
+treated specially and are resolved in a compiler-specific manner.
When C++ modules are enabled and available, the build system makes sure the
-\c{__cpp_modules} feature test macro is defined. Currently, its value is
-\c{201703} for VC and \c{201704} for GCC and Clang but this will most likely
-change in the future.
+\c{__cpp_modules} feature test macro is defined. However, if the compiler
+version being used does not claim complete modules support, its value may not
+be \c{201907}.
-One major difference between the current C++ modules implementation in VC and
-the other two compilers is the use of the \c{export module} syntax to identify
-the interface units. While both GCC and Clang have adopted this new syntax,
-VC is still using the old one without the \c{export} keyword. We can use the
-\c{__cpp_modules} macro to provide a portable declaration:
-
-\
-#if __cpp_modules >= 201704
-export
-#endif
-module hello;
-\
-
-Note, however, that the modules support in \c{build2} provides temporary
-\"magic\" that allows us to use the new syntax even with VC (don't ask how).
\h2#cxx-modules-symexport|Module Symbols Exporting|
When building a shared library, some platforms (notably Windows) require that
-we explicitly export symbols that must be accessible to the library users.
+we explicitly export symbols that must be accessible to the library consumers.
If you don't need to support such platforms, you can thank your lucky stars
and skip this section.
@@ -9180,20 +9198,26 @@ When using headers, the traditional way of achieving this is via an \"export
macro\" that is used to mark exported APIs, for example:
\
-LIBHELLO_EXPORT void
-say_hello (const string&);
+LIBHELLO_EXPORT void say_hello (const string&);
\
This macro is then appropriately defined (often in a separate \"export
header\") to export symbols when building the shared library and to import
-them when building the library's users.
+them when building the library's consumers (and to nothing when either
+building or consuming the static library).
The introduction of modules changes this in a number of ways, at least as
-implemented by VC (hopefully other compilers will follow suit). While we
-still have to explicitly mark exported symbols in our module interface
-unit, there is no need (and, in fact, no way) to do the same when said
-module is imported. Instead, the compiler automatically treats all
-such explicitly exported symbols (note: symbols, not names) as imported.
+implemented by MSVC and Clang. While we still have to explicitly mark exported
+symbols in our module interface unit, there is no need (and, in fact, no way)
+to do the same when said module is imported. Instead, the compiler
+automatically treats all such explicitly exported symbols (note: symbols, not
+names) as imported.
+
+\N|While the automatic importing may look like the same mechanism as what's
+used to support \l{#cc-auto-symexport Automatic DLL Symbol Exporting}, it
+appears not to be since it also works for global variables, not only
+functions. However, reportedly, it does appear to incur the same additional
+overhead as auto-importing, at least for functions.|
One notable aspect of this new model is the locality of the export macro: it
is only defined when compiling the module interface unit and is not visible to
@@ -9202,20 +9226,19 @@ have a unique per-library name (that \c{LIBHELLO_} prefix) because a header
from one library can be included while building another library.
We can continue using the same export macro and header with modules and, in
-fact, that's the recommended approach when maintaining the dual, header/module
-arrangement for backwards compatibility (discussed below). However, for
-modules-only codebases, we have an opportunity to improve the situation in two
-ways: we can use a single, keyword-like macro instead of a library-specific
-one and we can make the build system manage it for us thus getting rid of the
-export header.
+fact, that's the recommended approach if maintaining the dual, header/module
+arrangement for backwards compatibility. However, for modules-only codebases,
+we have an opportunity to improve the situation in two ways: we can use a
+single, keyword-like macro instead of a library-specific one and we can make
+the build system manage it for us thus getting rid of the export header.
To enable this functionality in \c{build2} we set the
\c{cxx.features.symexport} boolean variable to \c{true} before loading the
\c{cxx} module. For example:
\
-cxx.std = experimental
-
+cxx.std = latest
+cxx.features.modules = true
cxx.features.symexport = true
using cxx
@@ -9231,25 +9254,22 @@ in our module interface units, for example:
\
export module hello;
-import std.core;
+import std;
-export __symexport void
-say_hello (const std::string&);
+export __symexport void say_hello (const std::string&);
\
-As an aside, you may be wondering why can't a module export automatically mean
-a symbol export? While you will normally want to export symbols of all your
+\N|You may be wondering why can't a module export automatically mean a symbol
+export? While you will normally want to export symbols of all your
module-exported names, you may also need to do so for some non-module-exported
ones. For example:
\
export module foo;
-__symexport void
-f_impl ();
+__symexport void f_impl ();
-export __symexport inline void
-f ()
+export __symexport inline void f ()
{
f_impl ();
}
@@ -9258,7 +9278,7 @@ f ()
Furthermore, symbol exporting is a murky area with many limitations and
pitfalls (such as auto-exporting of base classes). As a result, it would not
be unreasonable to expect such an automatic module exporting to only further
-muddy the matter.
+muddy the matter.|
\h2#cxx-modules-install|Modules Installation|
@@ -9287,6 +9307,15 @@ Libs: -L/usr/lib -lhello
cxx.modules = hello.core=/usr/include/hello/core.mxx hello.extra=/usr/include/hello/extra.mxx
\
+\N|The \c{:} character in a module partition name is encoded as \c{..}. For
+example, for \c{hello:core} we would have:
+
+\
+cxx.modules = hello..core=/usr/...
+\
+
+|
+
Additional module properties are specified with variables in the
\c{cxx.module_<property>.<name>} form, for example:
@@ -9317,13 +9346,12 @@ different compared to headers. This section provides basic guidelines for
designing modules. We start with the overall considerations such as module
granularity and partitioning into translation units then continue with the
structure of typical module interface and implementation units. The following
-section discusses practical approaches to modularizing existing code and
-providing dual, header/module interfaces for backwards-compatibility.
+section discusses practical approaches to modularizing existing code.
Unlike headers, the cost of importing modules should be negligible. As a
result, it may be tempting to create \"mega-modules\", for example, one per
library. After all, this is how the standard library is modularized with its
-fairly large \c{std.core} and \c{std.io} modules.
+\c{std} and \c{std.compat} modules.
There is, however, a significant drawback to this choice: every time we make a
change, all consumers of such a mega-module will have to be recompiled,
@@ -9344,11 +9372,58 @@ The sensible approach is then to create modules of conceptually-related and
commonly-used entities possibly complemented with aggregate modules for ease
of importation. This also happens to be generally good design.
-As an example, let's consider an XML library that provides support for both
+As an example, let's consider a JSON library that provides support for both
parsing and serialization. Since it is common for applications to only use one
-of the functionalities, it makes sense to provide the \c{xml.parser} and
-\c{xml.serializer} modules. While it is not too tedious to import both, for
-convenience we could also provide the \c{xml} module that re-exports the two.
+of the functionalities, it makes sense to provide the \c{json.parser} and
+\c{json.serializer} modules. Depending on the representation of JSON we use in
+our library, it will most likely have some shared types so it probably makes
+sense to have the \c{json.types} module that is re-exported by the parser and
+serializer modules. While it is not too tedious to import both \c{json.parser}
+and \c{json.serializer} if both a needed, for convenience we could also
+provide the \c{json} module that re-exports the two. Something along these
+lines:
+
+\
+// types.mxx
+
+export module json.types;
+
+export class json
+{
+ ...
+};
+\
+
+\
+// parser.mxx
+
+export module json.parser;
+
+export import json.types;
+
+export json parse (...);
+\
+
+
+\
+// serializer.mxx
+
+export module json.serializer;
+
+export import json.types;
+
+export ... serialize (const json&);
+\
+
+\
+// json.mxx
+
+export module json;
+
+export import json.types;
+export import json.parser;
+export import json.serializer;
+\
Once we are past selecting an appropriate granularity for our modules, the
next question is how to partition them into translation units. A module can
@@ -9363,9 +9438,10 @@ recompiled. If we keep everything in a single file, then every time we change
the implementation we trigger recompilations that would have been avoided had
the implementation been factored out into a separate unit. Note that a build
system in cooperation with the compiler could theoretically avoid such
-unnecessary recompilations: if the compiler produces identical binary
-interface files when the module interface is unchanged, then the build system
-could detect this and skip recompiling the module's consumers.
+unnecessary recompilations in certain cases: if the compiler produces
+identical binary interface files when the module interface is unchanged, then
+the build system could detect this and skip recompiling the module's
+consumers.
A related issue with single-file modules is the reduction in the build
parallelization opportunities. If the implementation is part of the interface
@@ -9391,12 +9467,12 @@ should be rare.
Once we start writing our first real module the immediate question that
normally comes up is where to put \c{#include} directives and \c{import}
declarations and in what order. To recap, a module unit, both interface and
-implementation, is split into two parts: before the module declaration which
-obeys the usual or \"old\" translation unit rules and after the module
-declaration which is the module purview. Inside the module purview all
-non-exported declarations have module linkage which means their symbols are
-invisible to any other module (including the global module). With this
-understanding, consider the following module interface:
+implementation, is split into two parts: before the module declaration, called
+the global module fragment, which obeys the usual or \"old\" translation unit
+rules and after the module declaration which is the module purview. Inside the
+module purview all declarations have their symbols invisible to any other
+module (including the global module). With this understanding, consider the
+following module interface:
\
export module hello;
@@ -9422,6 +9498,8 @@ following module interface that uses an export header (which presumably sets
up symbols exporting macros) as well as an inline file:
\
+module;
+
#include <string>
export module hello;
@@ -9440,30 +9518,33 @@ A note on inline/template files: in header-based projects we could include
additional headers in those files, for example, if the included declarations
are only needed in the implementation. For the reasons just discussed, this
does not work with modules and we have to move all the includes into the
-interface file, before the module purview. On the other hand, with modules, it
-is safe to use namespace-level using-directives (for example, \c{using
-namespace std;}) in inline/template files (and, with care, even in the
-interface file).
+interface file, into the global module fragment. On the other hand, with
+modules, it is safe to use namespace-level using-directives (for example,
+\c{using namespace std;}) in inline/template files (and, with care, even in
+the interface file).
What about imports, where should we import other modules? Again, to recap,
unlike a header inclusion, an \c{import} declaration only makes exported names
-visible without redeclaring them. As result, in module implementation
-units, it doesn't really matter where we place imports, in or out of the
-module purview. There are, however, two differences when it comes to module
-interface units: only imports in the purview are visible to implementation
-units and we can only re-export an imported module from the purview.
+visible without redeclaring them. As result, in module implementation units,
+it doesn't really matter where we place imports, in the module purview or the
+global module fragment. There are, however, two differences when it comes to
+module interface units: only imports in the purview are visible to
+implementation units and we can only re-export an imported module from the
+purview.
The guideline is then for interface units to import in the module purview
unless there is a good reason not to make the import visible to the
implementation units. And for implementation units to always import in the
-purview for consistency. For example:
+purview for simplicity. For example:
\
+module;
+
#include <cassert>
export module hello;
-import std.core;
+import std;
#include <libhello/export.hxx>
@@ -9481,6 +9562,8 @@ unit template:
\
// Module interface unit.
+module; // Start of global module fragment.
+
<header includes>
export module <name>; // Start of module purview.
@@ -9499,6 +9582,8 @@ As well as the module implementation unit template:
\
// Module implementation unit.
+module; // Start of global module fragment.
+
<header includes>
module <name>; // Start of module purview.
@@ -9512,7 +9597,8 @@ Let's now discuss module naming. Module names are in a separate \"name plane\"
and do not collide with namespace, type, or function names. Also, as mentioned
earlier, the standard does not assign a hierarchical meaning to module names
though it is customary to assume module \c{hello.core} is a submodule of
-\c{hello} and importing the latter also imports the former.
+\c{hello} and, unless stated explicitly otherwise, importing the latter also
+imports the former.
It is important to choose good names for public modules (that is, modules
packaged into libraries and used by a wide range of consumers) since changing
@@ -9523,7 +9609,7 @@ worth coming up with a consistent naming scheme here as well.
The general guideline is to start names of public modules with the library's
namespace name followed by a name describing the module's functionality. In
particular, if a module is dedicated to a single class (or, more generally,
-has a single primary entity), then it makes sense to use its name as the
+has a single primary entity), then it makes sense to use that name as the
module name's last component.
As a concrete example, consider \c{libbutl} (the \c{build2} utility library):
@@ -9536,27 +9622,25 @@ the \c{butl::string_parser} namespace with the corresponding module called
When is it a good idea to re-export a module? The two straightforward cases
are when we are building an aggregate module out of submodules, for example,
-\c{xml} out of \c{xml.parser} and \c{xml.serializer}, or when one module
-extends or supersedes another, for example, as \c{std.core} extends
-\c{std.fundamental}. It is also clear that there is no need to re-export a
+\c{json} out of \c{json.parser} and \c{json.serializer}, or when one module
+extends or supersedes another, for example, as \c{json.parser} extends
+\c{json.types}. It is also clear that there is no need to re-export a
module that we only use in the implementation. The case when we use a module
in our interface is, however, a lot less clear cut.
But before considering the last case in more detail, let's understand the
issue with re-export. In other words, why not simply re-export any module we
import in our interface? In essence, re-export implicitly injects another
-module import anywhere our module is imported. If we re-export \c{std.core}
+module import anywhere our module is imported. If we re-export \c{std}
then consumers of our module will also automatically \"see\" all the names
-exported by \c{std.core}. They can then start using names from \c{std} without
-explicitly importing \c{std.core} and everything will compile until one day
+exported by \c{std}. They can then start using names from \c{std} without
+explicitly importing \c{std} and everything will compile until one day
they no longer need to import our module or we no longer need to import
-\c{std.core}. In a sense, re-export becomes part of our interface and it is
+\c{std}. In a sense, re-export becomes part of our interface and it is
generally good design to keep interfaces minimal.
And so, at the outset, the guideline is then to only re-export the minimum
-necessary. This, by the way, is the reason why it may make sense to divide
-\c{std.core} into submodules such as \c{std.core.string}, \c{std.core.vector},
-etc.
+necessary.
Let's now discuss a few concrete examples to get a sense of when re-export
might or might not be appropriate. Unfortunately, there does not seem to be a
@@ -9568,17 +9652,16 @@ interface:
\
export module hello;
-import std.core;
+import std;
export namespace hello
{
- void say (const std::string&);
+ std::string format_hello (const std::string&);
}
\
-Should we re-export \c{std.core} (or, \c{std.core.string}) in this case? Most
-likely not. If consumers of our module want to use \c{std::string} in order to
-pass an argument to our function, then it is natural to expect them to
+Should we re-export \c{std} in this case? Most likely not. If consumers of our
+module want to refer to \c{std::string}, then it is natural to expect them to
explicitly import the necessary module. In a sense, this is analogous to
scoping: nobody expects to be able to use just \c{string} (without \c{std::})
because of \c{using namespace hello;}.
@@ -9592,7 +9675,7 @@ Let's now consider a more interesting case (inspired by real events):
\
export module small_vector;
-import std.core;
+import std;
template <typename T, std::size_t N>
export class small_vector: public std::vector<T, ...>
@@ -9620,10 +9703,10 @@ implementation re-uses the comparison operators provided by \c{std::vector}
(via implicit to-base conversion) but they aren't visible.
There is a palpable difference between the two cases: the first merely uses
-\c{std.core} interface while the second is \i{based on} and, in a sense,
-\i{extends} it which feels like a stronger relationship. Re-exporting
-\c{std.core} (or, better yet, \c{std.core.vector}, should it become available)
-does not seem unreasonable.
+\c{std} interface while the second is \i{based on} and, in a sense,
+\i{extends} it which feels like a stronger relationship. Re-exporting \c{std}
+(or, better yet, \c{std.vector}, if it were available) seems less
+unreasonable.
Note also that there is no re-export of headers nor header inclusion
visibility in the implementation units. Specifically, in the previous example,
@@ -9637,113 +9720,17 @@ incur some development overhead compared to the old, headers-only approach.
\h2#cxx-modules-existing|Modularizing Existing Code|
The aim of this section is to provide practical guidelines to modularizing
-existing codebases as well as supporting the dual, header/module interface for
-backwards-compatibility.
+existing codebases.
Predictably, a well modularized (in the general sense) set of headers makes
conversion to C++ modules easier. Inclusion cycles will be particularly hard
to deal with (C++ modules do not allow circular interface dependencies).
-Furthermore, as we will see below, if you plan to provide the dual
-header/module interface, then having a one-to-one header to module mapping
-will simplify this task. As a result, it may make sense to spend some time
-cleaning and re-organizing your headers prior to attempting modularization.
-
-Let's first discuss why the modularization approach illustrated by the
-following example does not generally work:
-
-\
-export module hello;
-
-export
-{
-#include \"hello.hxx\"
-}
-\
-
-There are several issue that usually make this unworkable. Firstly, the header
-we are trying to export most likely includes other headers. For example, our
-\c{hello.hxx} may include \c{<string>} and we have already discussed why
-including it in the module purview, let alone exporting its names, is a bad
-idea. Secondly, the included header may declare more names than what should be
-exported, for example, some implementation details. In fact, it may declare
-names with internal linkage (uncommon for headers but not impossible) which
-are illegal to export. Finally, the header may define macros which will no
-longer be visible to the consumers.
-
-Sometimes, however, this can be the only approach available (for example, if
-trying to non-intrusively modularize a third-party library). It is possible to
-work around the first issue by \i{pre-including} outside of the module purview
-headers that should not be exported. Here we rely on the fact that the second
-inclusion of the same header will be ignored. For example:
+Having a one-to-one header to module mapping will simplify this task. As a
+result, it may make sense to spend some time cleaning and re-organizing your
+headers prior to attempting modularization.
-\
-#include <string> // Pre-include to suppress inclusion below.
-
-export module hello;
-
-export
-{
-#include \"hello.hxx\"
-}
-\
-
-Needless to say this approach is very brittle and usually requires that you
-place all the inter-related headers into a single module. As a result, its use
-is best limited to exploratory modularization and early prototyping.
-
-When starting modularization of a codebase there are two decisions we have to
-make at the outset: the level of the C++ modules support we can assume and the
-level of backwards compatibility we need to provide.
-
-The two modules support levels we distinguish are just modules and modules
-with the modularized standard library. The choice we have to make then is
-whether to support the standard library only as headers, only as modules, or
-both. Note that some compiler/standard library combinations may not be usable
-in some of these modes.
-
-The possible backwards compatibility levels are \i{modules-only} (consumption
-via headers is no longer supported), \i{modules-or-headers} (consumption
-either via headers or modules), and \i{modules-and-headers} (as the previous
-case but with support for consuming a library built with modules via headers
-and vice versa).
-
-What kind of situations call for the last level? We may need to continue
-offering the library as headers if we have a large number of existing
-consumers that cannot possibly be all modularized at once (or even ever). So
-the situation we may end up in is a mixture of consumers trying to use the
-same build of our library with some of them using modules and some \-
-headers. The case where we may want to consume a library built with headers
-via modules is not as far fetched as it may seem: the library might have been
-built with an older version of the compiler (for example, it was installed
-from a distribution's package) while the consumer is being built with a
-compiler version that supports modules. Note also that as discussed earlier
-the modules ownership semantics supports both kinds of such \"cross-usage\".
-
-Generally, compiler implementations do not support mixing inclusion and
-importation of the same entities in the same translation unit. This makes
-migration tricky if you plan to use the modularized standard library because
-of its pervasive use. There are two plausible strategies to handling this
-aspect of migration: If you are planning to consume the standard library
-exclusively as modules, then it may make sense to first change your entire
-codebase to do that. Simply replace all the standard library header inclusions
-with importation of the relevant \c{std.*} modules.
-
-The alternative strategy is to first complete the modularization of our entire
-project (as discussed next) while continuing consuming the standard library as
-headers. Once this is done, we can normally switch to using the modularized
-standard library quite easily. The reason for waiting until the complete
-modularization is to eliminate header inclusions between components which
-would often result in conflicting styles of the standard library consumption.
-
-Note also that due to the lack of header re-export and include visibility
-support discussed earlier, it may make perfect sense to only support the
-modularized standard library when modules are enabled even when providing
-backwards compatibility with headers. In fact, if all the compiler/standard
-library implementations that your project caters to support the modularized
-standard library, then there is little sense not to impose such a restriction.
-
-The overall strategy for modularizing our own components is to identify and
-modularize inter-dependent sets of headers one at a time starting from the
+The recommended strategy for modularizing our own components is to identify
+and modularize inter-dependent sets of headers one at a time starting from the
lower-level components. This way any newly modularized set will only depend on
the already modularized ones. After converting each set we can switch its
consumers to using imports keeping our entire project buildable and usable.
@@ -9757,394 +9744,6 @@ example, it's not uncommon to end up importing the module in its
implementation unit which is not something that all the compilers can handle
gracefully.
-Let's now explore how we can provide the various levels of backwards
-compatibility discussed above. Here we rely on two feature test macros to
-determine the available modules support level: \c{__cpp_modules} (modules are
-available) and \c{__cpp_lib_modules} (standard library modules are available,
-assumes \c{__cpp_modules} is also defined).
-
-If backwards compatibility is not necessary (the \i{modules-only} level), then
-we can use the module interface and implementation unit templates presented
-earlier and follow the above guidelines. If we continue consuming the standard
-library as headers, then we don't need to change anything in this area. If we
-only want to support the modularized standard library, then we simply replace
-the standard library header inclusions with the corresponding module
-imports. If we want to support both ways, then we can use the following
-templates. The module interface unit template:
-
-\
-// C includes, if any.
-
-#ifndef __cpp_lib_modules
-<std includes>
-#endif
-
-// Other includes, if any.
-
-export module <name>;
-
-#ifdef __cpp_lib_modules
-<std imports>
-#endif
-
-<module interface>
-\
-
-The module implementation unit template:
-
-\
-// C includes, if any.
-
-#ifndef __cpp_lib_modules
-<std includes>
-
-<extra std includes>
-#endif
-
-// Other includes, if any.
-
-module <name>;
-
-#ifdef __cpp_lib_modules
-<extra std imports> // Only additional to interface.
-#endif
-
-<module implementation>
-\
-
-For example:
-
-\
-// hello.mxx (module interface)
-
-#ifndef __cpp_lib_modules
-#include <string>
-#endif
-
-export module hello;
-
-#ifdef __cpp_lib_modules
-import std.core;
-#endif
-
-export void say_hello (const std::string& name);
-\
-
-\
-// hello.cxx (module implementation)
-
-#ifndef __cpp_lib_modules
-#include <string>
-
-#include <iostream>
-#endif
-
-module hello;
-
-#ifdef __cpp_lib_modules
-import std.io;
-#endif
-
-using namespace std;
-
-void say_hello (const string& n)
-{
- cout << \"Hello, \" << n << '!' << endl;
-}
-\
-
-If we need support for symbol exporting in this setup (that is, we are
-building a library and need to support Windows), then we can use the
-\c{__symexport} mechanism discussed earlier, for example:
-
-\
-// hello.mxx (module interface)
-
-...
-
-export __symexport void say_hello (const std::string& name);
-\
-
-The consumer code in the \i{modules-only} setup is straightforward: they
-simply import the desired modules.
-
-To support consumption via headers when modules are unavailable (the
-\i{modules-or-headers} level) we can use the following setup. Here we also
-support the dual header/modules consumption for the standard library (if this
-is not required, replace \c{#ifndef __cpp_lib_modules} with \c{#ifndef
-__cpp_modules} and remove \c{#ifdef __cpp_lib_modules}). The module interface
-unit template:
-
-\
-#ifndef __cpp_modules
-#pragma once
-#endif
-
-// C includes, if any.
-
-#ifndef __cpp_lib_modules
-<std includes>
-#endif
-
-// Other includes, if any.
-
-#ifdef __cpp_modules
-export module <name>;
-
-#ifdef __cpp_lib_modules
-<std imports>
-#endif
-#endif
-
-<module interface>
-\
-
-The module implementation unit template:
-
-\
-#ifndef __cpp_modules
-#include <module interface file>
-#endif
-
-// C includes, if any.
-
-#ifndef __cpp_lib_modules
-<std includes>
-
-<extra std includes>
-#endif
-
-// Other includes, if any
-
-#ifdef __cpp_modules
-module <name>;
-
-#ifdef __cpp_lib_modules
-<extra std imports> // Only additional to interface.
-#endif
-#endif
-
-<module implementation>
-\
-
-Notice the need to repeat \c{<std includes>} in the implementation file due to
-the lack of include visibility discussed above. This is necessary when modules
-are enabled but the standard library is not modularized since in this case the
-implementation does not \"see\" any of the headers included in the interface.
-
-Besides these templates we will most likely also need an export header that
-appropriately defines a module export macro depending on whether modules are
-used or not. This is also the place where we can handle symbol exporting. For
-example, here is what it could look like for our \c{libhello} library:
-
-\
-// export.hxx (module and symbol export)
-
-#pragma once
-
-#ifdef __cpp_modules
-# define LIBHELLO_MODEXPORT export
-#else
-# define LIBHELLO_MODEXPORT
-#endif
-
-#if defined(LIBHELLO_SHARED_BUILD)
-# ifdef _WIN32
-# define LIBHELLO_SYMEXPORT __declspec(dllexport)
-# else
-# define LIBHELLO_SYMEXPORT
-# endif
-#elif defined(LIBHELLO_SHARED)
-# ifdef _WIN32
-# define LIBHELLO_SYMEXPORT __declspec(dllimport)
-# else
-# define LIBHELLO_SYMEXPORT
-# endif
-#else
-# define LIBHELLO_SYMEXPORT
-#endif
-\
-
-And this is the module that uses it and provides the dual header/module
-support:
-
-\
-// hello.mxx (module interface)
-
-#ifndef __cpp_modules
-#pragma once
-#endif
-
-#ifndef __cpp_lib_modules
-#include <string>
-#endif
-
-#ifdef __cpp_modules
-export module hello;
-
-#ifdef __cpp_lib_modules
-import std.core;
-#endif
-#endif
-
-#include <libhello/export.hxx>
-
-LIBHELLO_MODEXPORT namespace hello
-{
- LIBHELLO_SYMEXPORT void say (const std::string& name);
-}
-\
-
-\
-// hello.cxx (module implementation)
-
-#ifndef __cpp_modules
-#include <libhello/hello.mxx>
-#endif
-
-#ifndef __cpp_lib_modules
-#include <string>
-
-#include <iostream>
-#endif
-
-#ifdef __cpp_modules
-module hello;
-
-#ifdef __cpp_lib_modules
-import std.io;
-#endif
-#endif
-
-using namespace std;
-
-namespace hello
-{
- void say (const string& n)
- {
- cout << \"Hello, \" << n << '!' << endl;
- }
-}
-\
-
-The consumer code in the \i{modules-or-headers} setup has to use either
-inclusion or importation depending on the modules support availability, for
-example:
-
-\
-#ifdef __cpp_modules
-import hello;
-#else
-#include <libhello/hello.mxx>
-#endif
-\
-
-Predictably, the final backwards compatibility level (\i{modules-and-headers})
-is the most onerous to support. Here existing consumers have to continue
-working with the modularized version of our library which means we have to
-retain all the existing header files. We also cannot assume that just because
-modules are available they are used (a consumer may still prefer headers),
-which means we cannot rely on (only) the \c{__cpp_modules} and
-\c{__cpp_lib_modules} macros to make the decisions.
-
-One way to arrange this is to retain the headers and adjust them according to
-the \i{modules-or-headers} template but with one important difference: instead
-of using the standard module macros we use our custom ones (and we can also
-have unconditional \c{#pragma once}). For example:
-
-\
-// hello.hxx (module header)
-
-#pragma once
-
-#ifndef LIBHELLO_LIB_MODULES
-#include <string>
-#endif
-
-#ifdef LIBHELLO_MODULES
-export module hello;
-
-#ifdef LIBHELLO_LIB_MODULES
-import std.core;
-#endif
-#endif
-
-#include <libhello/export.hxx>
-
-LIBHELLO_MODEXPORT namespace hello
-{
- LIBHELLO_SYMEXPORT void say (const std::string& name);
-}
-\
-
-Now if this header is included (for example, by an existing consumer) then
-none of the \c{LIBHELLO_*MODULES} macros will be defined and the header will
-act as, well, a plain old header. Note that we will also need to make the
-equivalent change in the export header.
-
-We also provide the module interface files which appropriately define the two
-custom macros and then simply includes the corresponding headers:
-
-\
-// hello.mxx (module interface)
-
-#ifdef __cpp_modules
-#define LIBHELLO_MODULES
-#endif
-
-#ifdef __cpp_lib_modules
-#define LIBHELLO_LIB_MODULES
-#endif
-
-#include <libhello/hello.hxx>
-\
-
-The module implementation unit can remain unchanged. In particular, we
-continue including \c{hello.mxx} if modules support is unavailable. However,
-if you find the use of different macros in the header and source files
-confusing, then instead it can be adjusted as follows (note also that now we
-are including \c{hello.hxx}):
-
-\
-// hello.cxx (module implementation)
-
-#ifdef __cpp_modules
-#define LIBHELLO_MODULES
-#endif
-
-#ifdef __cpp_lib_modules
-#define LIBHELLO_LIB_MODULES
-#endif
-
-#ifndef LIBHELLO_MODULES
-#include <libhello/hello.hxx>
-#endif
-
-#ifndef LIBHELLO_LIB_MODULES
-#include <string>
-
-#include <iostream>
-#endif
-
-#ifdef LIBHELLO_MODULES
-module hello;
-
-#ifdef LIBHELLO_LIB_MODULES
-import std.io;
-#endif
-#endif
-
-...
-\
-
-In this case it may also make sense to factor the \c{LIBHELLO_*MODULES} macro
-definitions into a common header.
-
-In the \i{modules-and-headers} setup the existing consumers that would like to
-continue using headers don't require any changes. And for those that would
-like to use modules if available the arrangement is the same as for the
-\i{modules-or-headers} compatibility level.
-
If our module needs to \"export\" macros then the recommended approach is to
simply provide an additional header that the consumer includes. While it might
be tempting to also wrap the module import into this header, some may prefer
@@ -10153,6 +9752,7 @@ macros may not be needed by all consumers. This way we can also keep the
header macro-only which means it can be included freely, in or out of module
purviews.
+
\h#cxx-objcxx|Objective-C++ Compilation|
The \c{cxx} module provides the \c{cxx.objcxx} submodule which can be loaded
diff --git a/libbuild2/buildfile b/libbuild2/buildfile
index 3518d93..d9711e6 100644
--- a/libbuild2/buildfile
+++ b/libbuild2/buildfile
@@ -293,7 +293,7 @@ if ($install.root != [null])
if ($cxx.target.class != 'windows')
{
- libul{build2}: cxx.libs += -lpthread
+ libul{build2}: cxx.libs += -pthread
# Note: only linking libdl in shared build.
#
@@ -323,7 +323,7 @@ lib{build2}:
# needed for some std::thread implementations (like libstdc++).
#
if ($cxx.target.class != 'windows')
- lib{build2}: cxx.export.libs += -lpthread
+ lib{build2}: cxx.export.libs += -pthread
liba{build2}: cxx.export.poptions += -DLIBBUILD2_STATIC
libs{build2}: cxx.export.poptions += -DLIBBUILD2_SHARED
diff --git a/libbuild2/c/init.cxx b/libbuild2/c/init.cxx
index 8bc2f7d..f100abc 100644
--- a/libbuild2/c/init.cxx
+++ b/libbuild2/c/init.cxx
@@ -111,23 +111,31 @@ namespace build2
// From version 16.8 VC now supports /std:c11 and /std:c17 options
// which enable C11/17 conformance. However, as of version 16.10,
// neither SDK nor CRT can be compiled in these modes (see the /std
- // option documentation for details/updates).
+ // option documentation for details/updates). There is also now
+ // /std:clatest which can be used to enable C23 typeof as of MSVC
+ // 17.9. So let's map C23 to that.
//
if (v == nullptr)
;
else if (!stdcmp ("90"))
{
- uint64_t cver (ci.version.major);
-
- if ((stdcmp ("99") && cver < 16) || // Since VS2010/10.0.
- ((stdcmp ("11") ||
- stdcmp ("17") ||
- stdcmp ("18")) && cver < 18) || // Since VS????/11.0.
- (stdcmp ("23", "2x") ))
+ uint64_t mj (ci.version.major);
+ uint64_t mi (ci.version.minor);
+
+ if (stdcmp ("99") && mj >= 16) // Since VS2010/10.0.
+ ;
+ else if ((stdcmp ("11") ||
+ stdcmp ("17") ||
+ stdcmp ("18")) && mj >= 18) // Since VS????/11.0.
+ ;
+ else if (stdcmp ("23", "2x") &&
+ (mj > 19 || (mj == 19 && mi >= 39))) // Since 17.9.
{
+ mode.insert (mode.begin (), "/std:clatest");
+ }
+ else
fail << "C " << *v << " is not supported by " << ci.signature <<
info << "required by " << project (rs) << '@' << rs;
- }
}
break;
}
diff --git a/libbuild2/cc/buildfile b/libbuild2/cc/buildfile
index 7dcd811..05e4c8c 100644
--- a/libbuild2/cc/buildfile
+++ b/libbuild2/cc/buildfile
@@ -11,7 +11,7 @@ libpkgconf = $config.build2.libpkgconf
if $libpkgconf
import impl_libs += libpkgconf%lib{pkgconf}
else
- import impl_libs += libpkg-config%lib{pkg-config}
+ import impl_libs += libbutl%lib{butl-pkg-config}
include ../bin/
intf_libs = ../bin/lib{build2-bin}
@@ -25,14 +25,14 @@ libul{build2-cc}: cxx{pkgconfig-libpkg-config}: include = (!$libpkgconf)
libul{build2-cc}: $intf_libs $impl_libs
-# libc++ std module interface translation unit.
+# libc++ std module interface translation units.
#
# Hopefully temporary, see llvm-project GH issues #73089.
#
# @@ TMP: make sure sync'ed with upstream before release (keep this note).
#
-lib{build2-cc}: file{std.cppm}
-file{std.cppm}@./: install = data/libbuild2/cc/
+lib{build2-cc}: file{std.cppm std.compat.cppm}
+file{std.cppm}@./ file{std.compat.cppm}@./: install = data/libbuild2/cc/
# Unit tests.
#
diff --git a/libbuild2/cc/common.cxx b/libbuild2/cc/common.cxx
index 2a8bc50..9a4a07c 100644
--- a/libbuild2/cc/common.cxx
+++ b/libbuild2/cc/common.cxx
@@ -1691,6 +1691,10 @@ namespace build2
// Note that clang-cl appears to use -fansi-escape-codes. See GH
// issue #312 for background.
//
+ // Note that MSVC ignores /diagnostics:color if diagnostics is
+ // written to a pipe. See GH issue #312 for details and a link to
+ // the MSVC bug report.
+ //
if (show_diag_color ())
{
if (cvariant.empty () &&
@@ -1716,7 +1720,8 @@ namespace build2
//
// Supported from GCC 4.9 (8.1 on Windows) and (at least) from Clang
// 3.5. Clang supports -f[no]color-diagnostics in addition to the
- // GCC's spelling.
+ // GCC's spelling. Note that to enable color on Windows Clang also
+ // needs -fansi-escape-codes.
//
if (
#ifndef _WIN32
@@ -1742,7 +1747,14 @@ namespace build2
show_diag_color () ? "-fdiagnostics-color" :
stderr_term ? "-fno-diagnostics-color" :
nullptr))
+ {
args.push_back (o);
+
+#ifdef _WIN32
+ if (ctype == compiler_type::clang && o[2] != 'n')
+ args.push_back ("-fansi-escape-codes");
+#endif
+ }
}
}
diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx
index 2e4775e..7629ed5 100644
--- a/libbuild2/cc/compile-rule.cxx
+++ b/libbuild2/cc/compile-rule.cxx
@@ -6151,180 +6151,185 @@ namespace build2
}
};
- // Pre-resolve std modules in an ad hoc way for certain compilers.
+ // Pre-resolve standard library modules (std and std.compat) in an ad
+ // hoc way.
//
- // @@ TODO: cache x_stdlib value.
+
+ // Similar logic to check_exact() above.
//
- if ((ctype == compiler_type::msvc) ||
- (ctype == compiler_type::clang &&
- cmaj >= 17 &&
- cast<string> (rs[x_stdlib]) == "libc++"))
+ done = true;
+
+ for (size_t i (0); i != n; ++i)
{
- // Similar logic to check_exact() above.
- //
- done = true;
+ module_import& m (imports[i]);
- for (size_t i (0); i != n; ++i)
+ if (m.name == "std" || m.name == "std.compat")
{
- module_import& m (imports[i]);
+ otype ot (otype::e);
+ const target* mt (nullptr);
- if (m.name == "std" || m.name == "std.compat")
+ switch (ctype)
{
- otype ot (otype::e);
- const target* mt (nullptr);
-
- switch (ctype)
+ case compiler_type::clang:
{
- case compiler_type::clang:
- {
- if (m.name != "std")
- fail << "module " << m.name << " not yet provided by libc++";
+ // @@ TODO: cache x_stdlib value.
+ //
+ if (cast<string> (rs[x_stdlib]) != "libc++")
+ fail << "standard library module '" << m.name << "' is "
+ << "currently only supported in libc++" <<
+ info << "try adding -stdlib=libc++ as compiler mode option";
- // Find or insert std.cppm (similar code to pkgconfig.cxx).
- //
- // Note: build_install_data is absolute and normalized.
- //
- mt = &ctx.targets.insert_locked (
- *x_mod,
- (dir_path (build_install_data) /= "libbuild2") /= "cc",
- dir_path (),
- "std",
- string ("cppm"), // For C++14 during bootstrap.
- target_decl::implied,
- trace).first;
-
- // Which output type should we use, static or shared? The
- // correct way would be to detect whether static or shared
- // version of libc++ is to be linked and use the corresponding
- // type. And we could do that by looking for -static-libstdc++
- // in loption (and no, it's not -static-libc++).
- //
- // But, looking at the object file produced from std.cppm, it
- // only contains one symbol, the static object initializer.
- // And this is unlikely to change since all other non-inline
- // or template symbols should be in libc++. So feels like it's
- // not worth the trouble and one variant should be good enough
- // for both cases. Let's use the shared one for less
- // surprising diagnostics (as in, "why are you linking obje{}
- // to a shared library?")
- //
- // (Of course, theoretically, std.cppm could detect via a
- // macro whether it's being compiled with -fPIC or not and do
- // things differently, but this seems far-fetched).
- //
- ot = otype::s;
+ if (cmaj < 18)
+ fail << "standard library module '" << m.name << "' is "
+ << "only supported in Clang 18 or later";
- break;
- }
- case compiler_type::msvc:
+ // Find or insert std*.cppm (similar code to pkgconfig.cxx).
+ //
+ // Note: build_install_data is absolute and normalized.
+ //
+ mt = &ctx.targets.insert_locked (
+ *x_mod,
+ (dir_path (build_install_data) /= "libbuild2") /= "cc",
+ dir_path (),
+ m.name,
+ string ("cppm"), // For C++14 during bootstrap.
+ target_decl::implied,
+ trace).first;
+
+ // Which output type should we use, static or shared? The
+ // correct way would be to detect whether static or shared
+ // version of libc++ is to be linked and use the corresponding
+ // type. And we could do that by looking for -static-libstdc++
+ // in loption (and no, it's not -static-libc++).
+ //
+ // But, looking at the object file produced from std*.cppm, they
+ // only contain one symbol, the static object initializer. And
+ // this is unlikely to change since all other non-inline or
+ // template symbols should be in libc++. So feels like it's not
+ // worth the trouble and one variant should be good enough for
+ // both cases. Let's use the shared one for less surprising
+ // diagnostics (as in, "why are you linking obje{} to a shared
+ // library?")
+ //
+ // (Of course, theoretically, std*.cppm could detect via a macro
+ // whether they are being compiled with -fPIC or not and do
+ // things differently, but this seems far-fetched).
+ //
+ ot = otype::s;
+
+ break;
+ }
+ case compiler_type::msvc:
+ {
+ // For MSVC, the source files std.ixx and std.compat.ixx are
+ // found in the modules/ subdirectory which is a sibling of
+ // include/ in the MSVC toolset (and "that is a contract with
+ // customers" to quote one of the developers).
+ //
+ // The problem of course is that there are multiple system
+ // header search directories (for example, as specified in the
+ // INCLUDE environment variable) and which one of them is for
+ // the MSVC toolset is not specified. So what we are going to do
+ // is search for one of the well-known standard C++ headers and
+ // assume that the directory where we found it is the one we are
+ // looking for. Or we could look for something MSVC-specific
+ // like vcruntime.h.
+ //
+ dir_path modules;
+ if (optional<path> p = find_system_header (path ("vcruntime.h")))
{
- // For MSVC, the source files std.ixx and std.compat.ixx are
- // found in the modules/ subdirectory which is a sibling of
- // include/ in the MSVC toolset (and "that is a contract with
- // customers" to quote one of the developers).
- //
- // The problem of course is that there are multiple system
- // header search directories (for example, as specified in the
- // INCLUDE environment variable) and which one of them is for
- // the MSVC toolset is not specified. So what we are going to
- // do is search for one of the well-known standard C++ headers
- // and assume that the directory where we found it is the one
- // we are looking for. Or we could look for something
- // MSVC-specific like vcruntime.h.
- //
- dir_path modules;
- if (optional<path> p = find_system_header (path ("vcruntime.h")))
+ p->make_directory (); // Strip vcruntime.h.
+ if (p->leaf () == path ("include")) // Sanity check.
{
- p->make_directory (); // Strip vcruntime.h.
- if (p->leaf () == path ("include")) // Sanity check.
- {
- modules = path_cast<dir_path> (move (p->make_directory ()));
- modules /= "modules";
- }
+ modules = path_cast<dir_path> (move (p->make_directory ()));
+ modules /= "modules";
}
+ }
- if (modules.empty ())
- fail << "unable to locate MSVC standard modules directory";
-
- mt = &ctx.targets.insert_locked (
- *x_mod,
- move (modules),
- dir_path (),
- m.name,
- string ("ixx"), // For C++14 during bootstrap.
- target_decl::implied,
- trace).first;
+ if (modules.empty ())
+ fail << "unable to locate MSVC standard modules directory";
- // For MSVC it's easier to detect the runtime being used since
- // it's specified with the compile options (/MT[d], /MD[d]).
- //
- // Similar semantics as in extract_headers() except here we
- // use options visible from the root scope. Note that
- // find_option_prefixes() looks in reverse, so look in the
- // cmode, x_coptions, c_coptions order.
- //
- initializer_list<const char*> os {"/MD", "/MT", "-MD", "-MT"};
+ mt = &ctx.targets.insert_locked (
+ *x_mod,
+ move (modules),
+ dir_path (),
+ m.name,
+ string ("ixx"), // For C++14 during bootstrap.
+ target_decl::implied,
+ trace).first;
- const string* o;
- if ((o = find_option_prefixes (os, cmode)) != nullptr ||
- (o = find_option_prefixes (os, rs, x_coptions)) != nullptr ||
- (o = find_option_prefixes (os, rs, c_coptions)) != nullptr)
- {
- ot = (*o)[2] == 'D' ? otype::s : otype::a;
- }
- else
- ot = otype::s; // The default is /MD.
+ // For MSVC it's easier to detect the runtime being used since
+ // it's specified with the compile options (/MT[d], /MD[d]).
+ //
+ // Similar semantics as in extract_headers() except here we use
+ // options visible from the root scope. Note that
+ // find_option_prefixes() looks in reverse, so look in the
+ // cmode, x_coptions, c_coptions order.
+ //
+ initializer_list<const char*> os {"/MD", "/MT", "-MD", "-MT"};
- break;
+ const string* o;
+ if ((o = find_option_prefixes (os, cmode)) != nullptr ||
+ (o = find_option_prefixes (os, rs, x_coptions)) != nullptr ||
+ (o = find_option_prefixes (os, rs, c_coptions)) != nullptr)
+ {
+ ot = (*o)[2] == 'D' ? otype::s : otype::a;
}
- case compiler_type::gcc:
- case compiler_type::icc:
- assert (false);
- };
+ else
+ ot = otype::s; // The default is /MD.
+
+ break;
+ }
+ case compiler_type::gcc:
+ case compiler_type::icc:
+ {
+ fail << "standard library module '" << m.name << "' is "
+ << "not yet supported in this compiler";
+ }
+ };
- pair<target&, ulock> tl (
- this->make_module_sidebuild ( // GCC 4.9
- a, bs, nullptr, ot, *mt, m.name));
+ pair<target&, ulock> tl (
+ this->make_module_sidebuild ( // GCC 4.9
+ a, bs, nullptr, ot, *mt, m.name));
- if (tl.second.owns_lock ())
+ if (tl.second.owns_lock ())
+ {
+ // Special compile options for the std modules.
+ //
+ if (ctype == compiler_type::clang)
{
- // Special compile options for the std modules.
- //
- if (ctype == compiler_type::clang)
- {
- value& v (tl.first.append_locked (x_coptions));
+ value& v (tl.first.append_locked (x_coptions));
- if (v.null)
- v = strings {};
+ if (v.null)
+ v = strings {};
- strings& cops (v.as<strings> ());
+ strings& cops (v.as<strings> ());
- switch (ctype)
+ switch (ctype)
+ {
+ case compiler_type::clang:
{
- case compiler_type::clang:
- {
- cops.push_back ("-Wno-reserved-module-identifier");
- break;
- }
- case compiler_type::msvc:
- // It appears nothing special is needed to compile MSVC
- // standard modules.
- case compiler_type::gcc:
- case compiler_type::icc:
- assert (false);
- };
- }
-
- tl.second.unlock ();
+ cops.push_back ("-Wno-reserved-module-identifier");
+ break;
+ }
+ case compiler_type::msvc:
+ // It appears nothing special is needed to compile MSVC
+ // standard modules.
+ case compiler_type::gcc:
+ case compiler_type::icc:
+ assert (false);
+ };
}
- pts[start + i].target = &tl.first;
- m.score = match_max (m.name) + 1;
- continue; // Scan the rest to detect if all done.
+ tl.second.unlock ();
}
- done = false;
+ pts[start + i].target = &tl.first;
+ m.score = match_max (m.name) + 1;
+ continue; // Scan the rest to detect if all done.
}
+
+ done = false;
}
// Go over prerequisites and try to resolve imported modules with them.
@@ -6672,6 +6677,15 @@ namespace build2
//
string extra;
+ // @@ What happens if different projects used different standards?
+ // Specifically, how do we detect this and what can the user do
+ // about it? For the latter question, forcing the same standard
+ // with config.cxx.std seems like the only sensible option. For
+ // the former, we could read the value of cxx.std using our
+ // buildfile first-line peeking mechanism. But doing that for
+ // every module interface feels inefficient so we will probably
+ // need to cache it on the per-project basis. Maybe/later.
+ //
if (const string* std = cast_null<string> (rs[x_std]))
extra += string (x) + ".std = " + *std + '\n';
diff --git a/libbuild2/cc/guess.cxx b/libbuild2/cc/guess.cxx
index d7e9c63..5ae6fb2 100644
--- a/libbuild2/cc/guess.cxx
+++ b/libbuild2/cc/guess.cxx
@@ -2580,27 +2580,29 @@ namespace build2
//
// Note that this is Apple Clang version and not XCode version.
//
- // 4.2 -> 3.2svn
- // 5.0 -> 3.3svn
- // 5.1 -> 3.4svn
- // 6.0 -> 3.5svn
- // 6.1.0 -> 3.6svn
- // 7.0.0 -> 3.7
- // 7.3.0 -> 3.8
- // 8.0.0 -> 3.9
- // 8.1.0 -> ?
- // 9.0.0 -> 4.0
- // 9.1.0 -> 5.0
- // 10.0.0 -> 6.0
- // 11.0.0 -> 7.0
- // 11.0.3 -> 8.0 (yes, seriously!)
- // 12.0.0 -> 9.0
- // 12.0.5 -> 10.0 (yes, seriously!)
- // 13.0.0 -> 11.0
- // 13.1.6 -> 12.0
- // 14.0.0 -> 12.0 (_LIBCPP_VERSION=130000)
- // 14.0.3 -> 15.0 (_LIBCPP_VERSION=150006)
- // 15.0.0 -> 16.0 (_LIBCPP_VERSION=160002)
+ // 4.2 -> 3.2svn
+ // 5.0 -> 3.3svn
+ // 5.1 -> 3.4svn
+ // 6.0 -> 3.5svn
+ // 6.1.0 -> 3.6svn
+ // 7.0.0 -> 3.7
+ // 7.3.0 -> 3.8
+ // 8.0.0 -> 3.9
+ // 8.1.0 -> ?
+ // 9.0.0 -> 4.0
+ // 9.1.0 -> 5.0
+ // 10.0.0 -> 6.0
+ // 11.0.0 -> 7.0
+ // 11.0.3 -> 8.0 (yes, seriously!)
+ // 12.0.0 -> 9.0
+ // 12.0.5 -> 10.0 (yes, seriously!)
+ // 13.0.0 -> 11.0
+ // 13.1.6 -> 12.0
+ // 14.0.0 -> 12.0 (_LIBCPP_VERSION=130000)
+ // 14.0.3 -> 15.0 (_LIBCPP_VERSION=150006)
+ // 15.0.0.0 -> 16.0 (_LIBCPP_VERSION=160002)
+ // 15.0.0.1 -> 16.0 (_LIBCPP_VERSION=160006)
+ // 15.0.0.3 -> 16.0 (_LIBCPP_VERSION=170006)
//
uint64_t mj (var_ver->major);
uint64_t mi (var_ver->minor);
diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx
index 08a60b9..417cba5 100644
--- a/libbuild2/cc/link-rule.cxx
+++ b/libbuild2/cc/link-rule.cxx
@@ -2585,6 +2585,24 @@ namespace build2
// We don't rpath system libraries. Why, you may ask? There are many
// good reasons and I have them written on a napkin somewhere...
//
+ // Well, the main reason is that we naturally assume the dynamic
+ // linker searches there by default and so there is no need for rpath.
+ // Plus, rpath would prevent "overriding" distribution-system
+ // (/usr/lib) libraries with user-system (/usr/local/lib).
+ //
+ // Note, however, that some operating systems don't search in
+ // /usr/local/lib by default (for example, Fedora, RHEL, Mac OS since
+ // version 13). In a sense, on these platforms /usr/local is
+ // "half-system" in that the system compiler by default searches in
+ // /usr/local/include and/or /usr/local/lib (see config_module::init()
+ // for background) but the dynamic linker does not. While we could
+ // hack this test for such platforms and add rpath for /usr/local/lib,
+ // this is still feels wrong (the user can always "fix" such an
+ // operating system by instructing the dynamic linker to search in
+ // /usr/local/lib, as many, including ourselves, do). So for now we
+ // are not going to do anything. In the end, the user can always add
+ // an rpath for /usr/local/lib manually.
+ //
// We also assume system libraries can only depend on other system
// libraries and so can prune the traversal.
//
@@ -2596,18 +2614,26 @@ namespace build2
size_t p (path::traits_type::rfind_separator (f));
assert (p != string::npos);
+ // For good measure, also suppress duplicates at the options level.
+ // This will take care of different libraries built in the same
+ // directory, system-installed, etc.
+
if (d.rpath)
{
string o ("-Wl,-rpath,");
o.append (f, 0, (p != 0 ? p : 1)); // Don't include trailing slash.
- d.args.push_back (move (o));
+
+ if (find (d.args.begin (), d.args.end (), o) == d.args.end ())
+ d.args.push_back (move (o));
}
if (d.rpath_link)
{
string o ("-Wl,-rpath-link,");
o.append (f, 0, (p != 0 ? p : 1));
- d.args.push_back (move (o));
+
+ if (find (d.args.begin (), d.args.end (), o) == d.args.end ())
+ d.args.push_back (move (o));
}
};
@@ -2660,7 +2686,9 @@ namespace build2
if ((c
? f.compare (p, string::npos, e)
: icasecmp (f.c_str () + p, e)) == 0)
+ {
append (f);
+ }
}
}
@@ -2671,13 +2699,22 @@ namespace build2
{
// Top-level shared library dependency.
//
+ // As above, suppress duplicates.
+ //
+ if (find (d.ls.begin (), d.ls.end (), &l) != d.ls.end ())
+ return;
+
if (!l.path ().empty ()) // Not binless.
{
// It is either matched or imported so should be a cc library.
//
if (!cast_false<bool> (l.vars[c_system]))
{
- args.push_back ("-Wl,-rpath," + l.path ().directory ().string ());
+ string o ("-Wl,-rpath," + l.path ().directory ().string ());
+
+ if (find (args.begin (), args.end (), o) == args.end ())
+ args.push_back (move (o));
+
ls.push_back (&l);
}
}
@@ -3332,6 +3369,9 @@ namespace build2
origin = p.directory ();
}
+ // Note: suppress duplicates at the options level, similar to
+ // rpath_libraries().
+
bool origin_used (false);
for (const dir_path& p: cast<dir_paths> (l))
{
@@ -3368,7 +3408,8 @@ namespace build2
else
o += p.string ();
- sargs.push_back (move (o));
+ if (find (sargs.begin (), sargs.end (), o) == sargs.end ())
+ sargs.push_back (move (o));
}
// According to the Internet, `-Wl,-z,origin` is not needed except
@@ -3386,7 +3427,12 @@ namespace build2
fail << ctgt << " does not support rpath-link";
for (const dir_path& p: cast<dir_paths> (l))
- sargs.push_back ("-Wl,-rpath-link," + p.string ());
+ {
+ string o ("-Wl,-rpath-link," + p.string ());
+
+ if (find (sargs.begin (), sargs.end (), o) == sargs.end ())
+ sargs.push_back (move (o));
+ }
}
}
@@ -3433,13 +3479,19 @@ namespace build2
append_args (sargs1);
}
- else
+ else if (b != x)
{
- append_option_values (
- args,
+ // Use the more canonical combined form (-L/usr/local/lib) even
+ // though it's less efficient (the split one is just too much of an
+ // eye-sore in the logs).
+ //
+ append_combined_option_values (
+ sargs1,
"-L",
b, x,
- [] (const dir_path& d) {return d.string ().c_str ();});
+ [] (const dir_path& d) -> const string& {return d.string ();});
+
+ append_args (sargs1);
}
}
diff --git a/libbuild2/cc/predefs-rule.cxx b/libbuild2/cc/predefs-rule.cxx
index e74192d..606db06 100644
--- a/libbuild2/cc/predefs-rule.cxx
+++ b/libbuild2/cc/predefs-rule.cxx
@@ -278,7 +278,7 @@ namespace build2
args.push_back ("/Zc:preprocessor"); // Preproc. conformance mode.
// Output (note that while the /Fi: variant is only availbale
- // starting with VS2013, /Zc:preprocessor is only available in
+ // starting with VS2013, /Zc:preprocessor is only available
// starting from VS2019).
//
args.push_back ("/Fi:");
diff --git a/libbuild2/cc/std.compat.cppm b/libbuild2/cc/std.compat.cppm
new file mode 100644
index 0000000..2668b30
--- /dev/null
+++ b/libbuild2/cc/std.compat.cppm
@@ -0,0 +1,996 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// WARNING, this entire header is generated by
+// utils/generate_libcxx_cppm_in.py
+// DO NOT MODIFY!
+
+module;
+
+#include <__config>
+
+#if _LIBCPP_VERSION < 180000
+#error libc++ version 18.0.0 or later required
+#endif
+
+// The headers of Table 24: C++ library headers [tab:headers.cpp]
+// and the headers of Table 25: C++ headers for C library facilities [tab:headers.cpp.c]
+#include <cassert>
+#include <cctype>
+#include <cerrno>
+#include <cfenv>
+#include <cfloat>
+#include <cinttypes>
+#include <climits>
+#if !defined(_LIBCPP_HAS_NO_LOCALIZATION)
+# include <clocale>
+#endif
+#include <cmath>
+#include <csetjmp>
+#include <csignal>
+#include <cstdarg>
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+#include <cuchar>
+#if !defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS)
+# include <cwchar>
+#endif
+#if !defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS)
+# include <cwctype>
+#endif
+
+#if 0
+// *** Headers not yet available ***
+#if __has_include(<debugging>)
+# error "please update the header information for <debugging> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<debugging>)
+#if __has_include(<flat_map>)
+# error "please update the header information for <flat_map> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<flat_map>)
+#if __has_include(<flat_set>)
+# error "please update the header information for <flat_set> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<flat_set>)
+#if __has_include(<generator>)
+# error "please update the header information for <generator> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<generator>)
+#if __has_include(<hazard_pointer>)
+# error "please update the header information for <hazard_pointer> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<hazard_pointer>)
+#if __has_include(<linalg>)
+# error "please update the header information for <linalg> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<linalg>)
+#if __has_include(<rcu>)
+# error "please update the header information for <rcu> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<rcu>)
+#if __has_include(<spanstream>)
+# error "please update the header information for <spanstream> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<spanstream>)
+#if __has_include(<stacktrace>)
+# error "please update the header information for <stacktrace> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<stacktrace>)
+#if __has_include(<stdfloat>)
+# error "please update the header information for <stdfloat> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<stdfloat>)
+#if __has_include(<text_encoding>)
+# error "please update the header information for <text_encoding> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<text_encoding>)
+#endif
+
+export module std.compat;
+export import std;
+
+// cassert.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ // This module exports nothing.
+} // export
+
+// cctype.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ using ::isalnum;
+ using ::isalpha;
+ using ::isblank;
+ using ::iscntrl;
+ using ::isdigit;
+ using ::isgraph;
+ using ::islower;
+ using ::isprint;
+ using ::ispunct;
+ using ::isspace;
+ using ::isupper;
+ using ::isxdigit;
+ using ::tolower;
+ using ::toupper;
+} // export
+
+// cerrno.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ // This module exports nothing.
+} // export
+
+// cfenv.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ // types
+ using ::fenv_t;
+ using ::fexcept_t;
+
+ // functions
+ using ::feclearexcept;
+ using ::fegetexceptflag;
+ using ::feraiseexcept;
+ using ::fesetexceptflag;
+ using ::fetestexcept;
+
+ using ::fegetround;
+ using ::fesetround;
+
+ using ::fegetenv;
+ using ::feholdexcept;
+ using ::fesetenv;
+ using ::feupdateenv;
+} // export
+
+// cfloat.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ // This module exports nothing.
+} // export
+
+// cinttypes.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ using ::imaxdiv_t;
+
+ using ::imaxabs;
+ using ::imaxdiv;
+ using ::strtoimax;
+ using ::strtoumax;
+ using ::wcstoimax;
+ using ::wcstoumax;
+
+ // abs is conditionally here, but always present in cmath.cppm. To avoid
+ // conflicing declarations omit the using here.
+
+ // div is conditionally here, but always present in cstdlib.cppm. To avoid
+ // conflicing declarations omit the using here.
+} // export
+
+// climits.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ // This module exports nothing.
+} // export
+
+// clocale.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+#ifndef _LIBCPP_HAS_NO_LOCALIZATION
+ using ::lconv;
+
+ using ::localeconv;
+ using ::setlocale;
+#endif // _LIBCPP_HAS_NO_LOCALIZATION
+} // export
+
+// cmath.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ using ::double_t;
+ using ::float_t;
+
+ using ::acos;
+ using ::acosf;
+ using ::acosl;
+
+ using ::asin;
+ using ::asinf;
+ using ::asinl;
+
+ using ::atan;
+ using ::atanf;
+ using ::atanl;
+
+ using ::atan2;
+ using ::atan2f;
+ using ::atan2l;
+
+ using ::cos;
+ using ::cosf;
+ using ::cosl;
+
+ using ::sin;
+ using ::sinf;
+ using ::sinl;
+
+ using ::tan;
+ using ::tanf;
+ using ::tanl;
+
+ using ::acosh;
+ using ::acoshf;
+ using ::acoshl;
+
+ using ::asinh;
+ using ::asinhf;
+ using ::asinhl;
+
+ using ::atanh;
+ using ::atanhf;
+ using ::atanhl;
+
+ using ::cosh;
+ using ::coshf;
+ using ::coshl;
+
+ using ::sinh;
+ using ::sinhf;
+ using ::sinhl;
+
+ using ::tanh;
+ using ::tanhf;
+ using ::tanhl;
+
+ using ::exp;
+ using ::expf;
+ using ::expl;
+
+ using ::exp2;
+ using ::exp2f;
+ using ::exp2l;
+
+ using ::expm1;
+ using ::expm1f;
+ using ::expm1l;
+
+ using ::frexp;
+ using ::frexpf;
+ using ::frexpl;
+
+ using ::ilogb;
+ using ::ilogbf;
+ using ::ilogbl;
+
+ using ::ldexp;
+ using ::ldexpf;
+ using ::ldexpl;
+
+ using ::log;
+ using ::logf;
+ using ::logl;
+
+ using ::log10;
+ using ::log10f;
+ using ::log10l;
+
+ using ::log1p;
+ using ::log1pf;
+ using ::log1pl;
+
+ using ::log2;
+ using ::log2f;
+ using ::log2l;
+
+ using ::logb;
+ using ::logbf;
+ using ::logbl;
+
+ using ::modf;
+ using ::modff;
+ using ::modfl;
+
+ using ::scalbn;
+ using ::scalbnf;
+ using ::scalbnl;
+
+ using ::scalbln;
+ using ::scalblnf;
+ using ::scalblnl;
+
+ using ::cbrt;
+ using ::cbrtf;
+ using ::cbrtl;
+
+ // [c.math.abs], absolute values
+ using ::abs;
+
+ using ::fabs;
+ using ::fabsf;
+ using ::fabsl;
+
+ using ::hypot;
+ using ::hypotf;
+ using ::hypotl;
+
+ // [c.math.hypot3], three-dimensional hypotenuse
+
+ using ::pow;
+ using ::powf;
+ using ::powl;
+
+ using ::sqrt;
+ using ::sqrtf;
+ using ::sqrtl;
+
+ using ::erf;
+ using ::erff;
+ using ::erfl;
+
+ using ::erfc;
+ using ::erfcf;
+ using ::erfcl;
+
+ using ::lgamma;
+ using ::lgammaf;
+ using ::lgammal;
+
+ using ::tgamma;
+ using ::tgammaf;
+ using ::tgammal;
+
+ using ::ceil;
+ using ::ceilf;
+ using ::ceill;
+
+ using ::floor;
+ using ::floorf;
+ using ::floorl;
+
+ using ::nearbyint;
+ using ::nearbyintf;
+ using ::nearbyintl;
+
+ using ::rint;
+ using ::rintf;
+ using ::rintl;
+
+ using ::lrint;
+ using ::lrintf;
+ using ::lrintl;
+
+ using ::llrint;
+ using ::llrintf;
+ using ::llrintl;
+
+ using ::round;
+ using ::roundf;
+ using ::roundl;
+
+ using ::lround;
+ using ::lroundf;
+ using ::lroundl;
+
+ using ::llround;
+ using ::llroundf;
+ using ::llroundl;
+
+ using ::trunc;
+ using ::truncf;
+ using ::truncl;
+
+ using ::fmod;
+ using ::fmodf;
+ using ::fmodl;
+
+ using ::remainder;
+ using ::remainderf;
+ using ::remainderl;
+
+ using ::remquo;
+ using ::remquof;
+ using ::remquol;
+
+ using ::copysign;
+ using ::copysignf;
+ using ::copysignl;
+
+ using ::nan;
+ using ::nanf;
+ using ::nanl;
+
+ using ::nextafter;
+ using ::nextafterf;
+ using ::nextafterl;
+
+ using ::nexttoward;
+ using ::nexttowardf;
+ using ::nexttowardl;
+
+ using ::fdim;
+ using ::fdimf;
+ using ::fdiml;
+
+ using ::fmax;
+ using ::fmaxf;
+ using ::fmaxl;
+
+ using ::fmin;
+ using ::fminf;
+ using ::fminl;
+
+ using ::fma;
+ using ::fmaf;
+ using ::fmal;
+
+ // [c.math.lerp], linear interpolation
+ // [support.c.headers.other]/1
+ // ... placed within the global namespace scope, except for the functions
+ // described in [sf.cmath], the std::lerp function overloads ([c.math.lerp])
+ // ...
+
+ // [c.math.fpclass], classification / comparison functions
+ using ::fpclassify;
+ using ::isfinite;
+ using ::isgreater;
+ using ::isgreaterequal;
+ using ::isinf;
+ using ::isless;
+ using ::islessequal;
+ using ::islessgreater;
+ using ::isnan;
+ using ::isnormal;
+ using ::isunordered;
+ using ::signbit;
+
+ // [sf.cmath], mathematical special functions
+} // export
+
+// csetjmp.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ using ::jmp_buf;
+ using ::longjmp;
+} // export
+
+// csignal.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ using ::sig_atomic_t;
+
+ // [support.signal], signal handlers
+ using ::signal;
+
+ using ::raise;
+} // export
+
+// cstdarg.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export { using ::va_list; } // export
+
+// cstddef.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ using ::max_align_t;
+ using ::nullptr_t;
+ using ::ptrdiff_t;
+ using ::size_t;
+
+ // [support.c.headers]/1
+ // ... placed within the global namespace scope, except for ... the
+ // declaration of std::byte ([cstddef.syn]), and the functions and
+ // function templates described in [support.types.byteops]. ...
+
+ // [support.types.byteops], byte type operations
+} // export
+
+// cstdint.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ // signed
+ using ::int8_t _LIBCPP_USING_IF_EXISTS;
+ using ::int16_t _LIBCPP_USING_IF_EXISTS;
+ using ::int32_t _LIBCPP_USING_IF_EXISTS;
+ using ::int64_t _LIBCPP_USING_IF_EXISTS;
+
+ using ::int_fast16_t;
+ using ::int_fast32_t;
+ using ::int_fast64_t;
+ using ::int_fast8_t;
+
+ using ::int_least16_t;
+ using ::int_least32_t;
+ using ::int_least64_t;
+ using ::int_least8_t;
+
+ using ::intmax_t;
+
+ using ::intptr_t _LIBCPP_USING_IF_EXISTS;
+
+ // unsigned
+ using ::uint8_t _LIBCPP_USING_IF_EXISTS;
+ using ::uint16_t _LIBCPP_USING_IF_EXISTS;
+ using ::uint32_t _LIBCPP_USING_IF_EXISTS;
+ using ::uint64_t _LIBCPP_USING_IF_EXISTS;
+
+ using ::uint_fast16_t;
+ using ::uint_fast32_t;
+ using ::uint_fast64_t;
+ using ::uint_fast8_t;
+
+ using ::uint_least16_t;
+ using ::uint_least32_t;
+ using ::uint_least64_t;
+ using ::uint_least8_t;
+
+ using ::uintmax_t;
+
+ using ::uintptr_t _LIBCPP_USING_IF_EXISTS;
+} // export
+
+// cstdio.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ using ::FILE;
+ using ::fpos_t;
+ using ::size_t;
+
+ using ::clearerr;
+ using ::fclose;
+ using ::feof;
+ using ::ferror;
+ using ::fflush;
+ using ::fgetc;
+ using ::fgetpos;
+ using ::fgets;
+ using ::fopen;
+ using ::fprintf;
+ using ::fputc;
+ using ::fputs;
+ using ::fread;
+ using ::freopen;
+ using ::fscanf;
+ using ::fseek;
+ using ::fsetpos;
+ using ::ftell;
+ using ::fwrite;
+ using ::getc;
+ using ::getchar;
+ using ::perror;
+ using ::printf;
+ using ::putc;
+ using ::putchar;
+ using ::puts;
+ using ::remove;
+ using ::rename;
+ using ::rewind;
+ using ::scanf;
+ using ::setbuf;
+ using ::setvbuf;
+ using ::snprintf;
+ using ::sprintf;
+ using ::sscanf;
+ using ::tmpfile;
+ using ::tmpnam;
+ using ::ungetc;
+ using ::vfprintf;
+ using ::vfscanf;
+ using ::vprintf;
+ using ::vscanf;
+ using ::vsnprintf;
+ using ::vsprintf;
+ using ::vsscanf;
+
+} // export
+
+// cstdlib.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ using ::div_t;
+ using ::ldiv_t;
+ using ::lldiv_t;
+ using ::size_t;
+
+ // [support.start.term], start and termination
+ using ::_Exit;
+ using ::abort;
+ using ::at_quick_exit _LIBCPP_USING_IF_EXISTS;
+ using ::atexit;
+ using ::exit;
+ using ::quick_exit _LIBCPP_USING_IF_EXISTS;
+
+ using ::getenv;
+ using ::system;
+
+ // [c.malloc], C library memory allocation
+ using ::aligned_alloc _LIBCPP_USING_IF_EXISTS;
+ using ::calloc;
+ using ::free;
+ using ::malloc;
+ using ::realloc;
+
+ using ::atof;
+ using ::atoi;
+ using ::atol;
+ using ::atoll;
+ using ::strtod;
+ using ::strtof;
+ using ::strtol;
+ using ::strtold;
+ using ::strtoll;
+ using ::strtoul;
+ using ::strtoull;
+
+ // [c.mb.wcs], multibyte / wide string and character conversion functions
+ using ::mblen;
+#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+ using ::mbstowcs;
+ using ::mbtowc;
+ using ::wcstombs;
+ using ::wctomb;
+#endif
+ // [alg.c.library], C standard library algorithms
+ using ::bsearch;
+ using ::qsort;
+
+ // [c.math.rand], low-quality random number generation
+ using ::rand;
+ using ::srand;
+
+ // [c.math.abs], absolute values
+ using ::abs;
+
+ using ::labs;
+ using ::llabs;
+
+ using ::div;
+ using ::ldiv;
+ using ::lldiv;
+
+} // export
+
+// cstring.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ using ::size_t;
+
+ using ::memchr;
+ using ::memcmp;
+ using ::memcpy;
+ using ::memmove;
+ using ::memset;
+ using ::strcat;
+ using ::strchr;
+ using ::strcmp;
+ using ::strcoll;
+ using ::strcpy;
+ using ::strcspn;
+ using ::strerror;
+ using ::strlen;
+ using ::strncat;
+ using ::strncmp;
+ using ::strncpy;
+ using ::strpbrk;
+ using ::strrchr;
+ using ::strspn;
+ using ::strstr;
+ using ::strtok;
+ using ::strxfrm;
+
+} // export
+
+// ctime.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ using ::clock_t;
+ using ::size_t;
+ using ::time_t;
+
+ using ::timespec;
+ using ::tm;
+
+ using ::asctime;
+ using ::clock;
+ using ::ctime;
+ using ::difftime;
+ using ::gmtime;
+ using ::localtime;
+ using ::mktime;
+ using ::strftime;
+ using ::time;
+ using ::timespec_get _LIBCPP_USING_IF_EXISTS;
+} // export
+
+// cuchar.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+ // Note the Standard does not mark these symbols optional, but libc++'s header
+ // does. So this seems strictly not to be conforming.
+
+ // mbstate_t is conditionally here, but always present in cwchar.cppm. To avoid
+ // conflicing declarations omit the using here.
+
+ // size_t is conditionally here, but always present in cstddef.cppm. To avoid
+ // conflicing declarations omit the using here.
+
+#if !defined(_LIBCPP_HAS_NO_C8RTOMB_MBRTOC8)
+ using ::mbrtoc8 _LIBCPP_USING_IF_EXISTS;
+ using ::c8rtomb _LIBCPP_USING_IF_EXISTS;
+#endif
+ using ::mbrtoc16 _LIBCPP_USING_IF_EXISTS;
+ using ::c16rtomb _LIBCPP_USING_IF_EXISTS;
+ using ::mbrtoc32 _LIBCPP_USING_IF_EXISTS;
+ using ::c32rtomb _LIBCPP_USING_IF_EXISTS;
+} // export
+
+// cwchar.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+ using ::mbstate_t;
+ using ::size_t;
+ using ::wint_t;
+
+ using ::tm;
+
+ using ::btowc;
+ using ::fgetwc;
+ using ::fgetws;
+ using ::fputwc;
+ using ::fputws;
+ using ::fwide;
+ using ::fwprintf;
+ using ::fwscanf;
+ using ::getwc;
+ using ::getwchar;
+ using ::putwc;
+ using ::putwchar;
+ using ::swprintf;
+ using ::swscanf;
+ using ::ungetwc;
+ using ::vfwprintf;
+ using ::vfwscanf;
+ using ::vswprintf;
+ using ::vswscanf;
+ using ::vwprintf;
+ using ::vwscanf;
+ using ::wcscat;
+ using ::wcschr;
+ using ::wcscmp;
+ using ::wcscoll;
+ using ::wcscpy;
+ using ::wcscspn;
+ using ::wcsftime;
+ using ::wcslen;
+ using ::wcsncat;
+ using ::wcsncmp;
+ using ::wcsncpy;
+ using ::wcspbrk;
+ using ::wcsrchr;
+ using ::wcsspn;
+ using ::wcsstr;
+ using ::wcstod;
+ using ::wcstof;
+ using ::wcstok;
+ using ::wcstol;
+ using ::wcstold;
+ using ::wcstoll;
+ using ::wcstoul;
+ using ::wcstoull;
+ using ::wcsxfrm;
+ using ::wctob;
+ using ::wmemchr;
+ using ::wmemcmp;
+ using ::wmemcpy;
+ using ::wmemmove;
+ using ::wmemset;
+ using ::wprintf;
+ using ::wscanf;
+
+ // [c.mb.wcs], multibyte / wide string and character conversion functions
+ using ::mbrlen;
+ using ::mbrtowc;
+ using ::mbsinit;
+ using ::mbsrtowcs;
+ using ::wcrtomb;
+ using ::wcsrtombs;
+#endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS
+} // export
+
+// cwctype.inc
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+export {
+#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+ using ::wctrans_t;
+ using ::wctype_t;
+ using ::wint_t;
+
+ using ::iswalnum;
+ using ::iswalpha;
+ using ::iswblank;
+ using ::iswcntrl;
+ using ::iswctype;
+ using ::iswdigit;
+ using ::iswgraph;
+ using ::iswlower;
+ using ::iswprint;
+ using ::iswpunct;
+ using ::iswspace;
+ using ::iswupper;
+ using ::iswxdigit;
+ using ::towctrans;
+ using ::towlower;
+ using ::towupper;
+ using ::wctrans;
+ using ::wctype;
+#endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS
+} // export
diff --git a/libbuild2/cc/std.cppm b/libbuild2/cc/std.cppm
index 5368d1c..575e6a4 100644
--- a/libbuild2/cc/std.cppm
+++ b/libbuild2/cc/std.cppm
@@ -8,15 +8,15 @@
//===----------------------------------------------------------------------===//
// WARNING, this entire header is generated by
-// utils/generate_std_cppm_in.py
+// utils/generate_libcxx_cppm_in.py
// DO NOT MODIFY!
module;
#include <__config>
-#if _LIBCPP_VERSION < 170000
-#error libc++ version 17.0.0 or later required
+#if _LIBCPP_VERSION < 180000
+#error libc++ version 18.0.0 or later required
#endif
// The headers of Table 24: C++ library headers [tab:headers.cpp]
@@ -153,11 +153,8 @@ module;
# include <strstream>
#endif
#if !defined(_LIBCPP_HAS_NO_LOCALIZATION)
-#if __has_include(<syncstream>)
-# define _LIPCPP_HAS_YES_SYNCSTREAM
# include <syncstream>
#endif
-#endif
#include <system_error>
#if !defined(_LIBCPP_HAS_NO_THREADS)
# include <thread>
@@ -177,38 +174,38 @@ module;
#if 0
// *** Headers not yet available ***
#if __has_include(<debugging>)
-# error "update the header information for <debugging> in libcxx/utils/generate_std_cppm_in.py"
-#endif // __has_include(<debugging>)
+# error "please update the header information for <debugging> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<debugging>)
#if __has_include(<flat_map>)
-# error "update the header information for <flat_map> in libcxx/utils/generate_std_cppm_in.py"
-#endif // __has_include(<flat_map>)
+# error "please update the header information for <flat_map> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<flat_map>)
#if __has_include(<flat_set>)
-# error "update the header information for <flat_set> in libcxx/utils/generate_std_cppm_in.py"
-#endif // __has_include(<flat_set>)
+# error "please update the header information for <flat_set> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<flat_set>)
#if __has_include(<generator>)
-# error "update the header information for <generator> in libcxx/utils/generate_std_cppm_in.py"
-#endif // __has_include(<generator>)
+# error "please update the header information for <generator> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<generator>)
#if __has_include(<hazard_pointer>)
-# error "update the header information for <hazard_pointer> in libcxx/utils/generate_std_cppm_in.py"
-#endif // __has_include(<hazard_pointer>)
+# error "please update the header information for <hazard_pointer> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<hazard_pointer>)
#if __has_include(<linalg>)
-# error "update the header information for <linalg> in libcxx/utils/generate_std_cppm_in.py"
-#endif // __has_include(<linalg>)
+# error "please update the header information for <linalg> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<linalg>)
#if __has_include(<rcu>)
-# error "update the header information for <rcu> in libcxx/utils/generate_std_cppm_in.py"
-#endif // __has_include(<rcu>)
+# error "please update the header information for <rcu> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<rcu>)
#if __has_include(<spanstream>)
-# error "update the header information for <spanstream> in libcxx/utils/generate_std_cppm_in.py"
-#endif // __has_include(<spanstream>)
+# error "please update the header information for <spanstream> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<spanstream>)
#if __has_include(<stacktrace>)
-# error "update the header information for <stacktrace> in libcxx/utils/generate_std_cppm_in.py"
-#endif // __has_include(<stacktrace>)
+# error "please update the header information for <stacktrace> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<stacktrace>)
#if __has_include(<stdfloat>)
-# error "update the header information for <stdfloat> in libcxx/utils/generate_std_cppm_in.py"
-#endif // __has_include(<stdfloat>)
+# error "please update the header information for <stdfloat> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<stdfloat>)
#if __has_include(<text_encoding>)
-# error "update the header information for <text_encoding> in libcxx/utils/generate_std_cppm_in.py"
-#endif // __has_include(<text_encoding>)
+# error "please update the header information for <text_encoding> in headers_not_available in utils/libcxx/header_information.py"
+#endif // __has_include(<text_encoding>)
#endif
export module std;
@@ -232,7 +229,9 @@ export namespace std {
using std::ranges::in_in_result;
using std::ranges::in_out_out_result;
using std::ranges::in_out_result;
- // using std::ranges::in_value_result;
+#if _LIBCPP_STD_VER >= 23
+ using std::ranges::in_value_result;
+#endif
using std::ranges::min_max_result;
// using std::ranges::out_value_result;
} // namespace ranges
@@ -256,13 +255,15 @@ export namespace std {
using std::ranges::none_of;
}
+#if _LIBCPP_STD_VER >= 23
// [alg.contains], contains
-#if 0
namespace ranges {
using std::ranges::contains;
+#if 0
using std::ranges::contains_subrange;
- } // namespace ranges
#endif
+ } // namespace ranges
+#endif // _LIBCPP_STD_VER >= 23
// [alg.foreach], for each
using std::for_each;
@@ -370,20 +371,18 @@ export namespace std {
// [alg.starts.with], starts with
using std::ranges::starts_with;
-#if _LIBCPP_VERSION >= 180000
// [alg.ends.with], ends with
using std::ranges::ends_with;
-#endif
-# if 0
// [alg.fold], fold
using std::ranges::fold_left;
+ using std::ranges::fold_left_with_iter;
+ using std::ranges::fold_left_with_iter_result;
+# if 0
using std::ranges::fold_left_first;
using std::ranges::fold_right;
using std::ranges::fold_right_last;
using std::ranges::fold_left_with_iter;
- using std::ranges::fold_left_with_iter_result;
- using std::ranges::fold_left_with_iter;
using std::ranges::fold_left_first_with_iter;
using std::ranges::fold_left_first_with_iter;
# endif
@@ -955,7 +954,9 @@ export namespace std {
using std::atomic_char;
using std::atomic_char16_t;
using std::atomic_char32_t;
+#ifndef _LIBCPP_HAS_NO_CHAR8_T
using std::atomic_char8_t;
+#endif
using std::atomic_int;
using std::atomic_llong;
using std::atomic_long;
@@ -1993,11 +1994,13 @@ export namespace std {
export namespace std {
#ifndef _LIBCPP_HAS_NO_LOCALIZATION
+# if _LIBCPP_STD_VER < 26 || defined(_LIBCPP_ENABLE_CXX26_REMOVED_CODECVT)
using std::codecvt_mode;
using std::codecvt_utf16;
using std::codecvt_utf8;
using std::codecvt_utf8_utf16;
+# endif // _LIBCPP_STD_VER < 26 || defined(_LIBCPP_ENABLE_CXX26_REMOVED_CODECVT)
#endif // _LIBCPP_HAS_NO_LOCALIZATION
} // namespace std
@@ -2617,7 +2620,7 @@ export namespace std {
using std::mktime;
using std::strftime;
using std::time;
- using std::timespec_get;
+ using std::timespec_get _LIBCPP_USING_IF_EXISTS;
} // namespace std
// cuchar.inc
@@ -3108,6 +3111,9 @@ export namespace std {
#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
using std::wformat_string;
#endif
+#if _LIBCPP_STD_VER >= 26
+ using std::runtime_format;
+#endif //_LIBCPP_STD_VER >= 26
// [format.functions], formatting functions
using std::format;
@@ -3590,9 +3596,11 @@ export namespace std {
#endif
using std::u16streampos;
using std::u32streampos;
+#ifndef _LIBCPP_HAS_NO_CHAR8_T
using std::u8streampos;
+#endif
-#ifdef _LIBCPP_HAS_YES_SYNCSTREAM
+#ifndef _LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM
using std::basic_osyncstream;
using std::basic_syncbuf;
#endif
@@ -3600,13 +3608,11 @@ export namespace std {
using std::istreambuf_iterator;
using std::ostreambuf_iterator;
-#ifdef _LIBCPP_HAS_YES_SYNCSTREAM
+#ifndef _LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM
using std::osyncstream;
using std::syncbuf;
-#endif
#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
-#ifdef _LIBCPP_HAS_YES_SYNCSTREAM
using std::wosyncstream;
using std::wsyncbuf;
#endif
@@ -4112,9 +4118,7 @@ export namespace std {
// [mdspan.layout], layout mapping
using std::layout_left;
using std::layout_right;
-#if _LIBCPP_VERSION >= 180000
using std::layout_stride;
-#endif
// [mdspan.accessor.default], class template default_accessor
using std::default_accessor;
@@ -4171,7 +4175,9 @@ export namespace std {
#if _LIBCPP_STD_VER >= 23
using std::allocation_result;
- using std::allocate_at_least;
+ // Note: no longer in Clang 19.
+ //
+ //using std::allocate_at_least;
#endif
// [default.allocator], the default allocator
@@ -4283,7 +4289,9 @@ export namespace std {
using std::reinterpret_pointer_cast;
using std::static_pointer_cast;
+#ifndef _LIBCPP_HAS_NO_RTTI
using std::get_deleter;
+#endif // _LIBCPP_HAS_NO_RTTI
// [util.smartptr.shared.io], shared_ptr I/O
@@ -4555,6 +4563,16 @@ export namespace std {
// [numeric.ops.midpoint], midpoint
using std::midpoint;
+
+#if _LIBCPP_STD_VER >= 26
+ // [numeric.sat], saturation arithmetic
+ using std::add_sat;
+ using std::div_sat;
+ using std::mul_sat;
+ using std::saturate_cast;
+ using std::sub_sat;
+#endif
+
} // namespace std
// optional.inc
@@ -4626,14 +4644,17 @@ export namespace std {
# endif
using std::operator<<;
-# if 0
+# if _LIBCPP_STD_VER >= 23
// [ostream.formatted.print], print functions
using std::print;
using std::println;
using std::vprint_nonunicode;
+# ifndef _LIBCPP_HAS_NO_UNICODE
using std::vprint_unicode;
-# endif
+# endif // _LIBCPP_HAS_NO_UNICODE
+# endif // _LIBCPP_STD_VER >= 23
+
#endif // _LIBCPP_HAS_NO_LOCALIZATION
} // namespace std
@@ -5013,13 +5034,11 @@ export namespace std {
using std::ranges::views::drop_while;
} // namespace views
-#ifdef _LIBCPP_ENABLE_EXPERIMENTAL
using std::ranges::join_view;
namespace views {
using std::ranges::views::join;
} // namespace views
-#endif // _LIBCPP_ENABLE_EXPERIMENTAL
#if 0
using std::ranges::join_with_view;
@@ -5123,14 +5142,12 @@ export namespace std {
#endif
#if _LIBCPP_STD_VER >= 23
-#if _LIBCPP_VERSION >= 180000
// [range.chunk.by], chunk by view
using std::ranges::chunk_by_view;
namespace views {
using std::ranges::views::chunk_by;
}
-#endif
#endif // _LIBCPP_STD_VER >= 23
#if 0
@@ -5783,7 +5800,9 @@ export namespace std {
using std::string;
using std::u16string;
using std::u32string;
+#ifndef _LIBCPP_HAS_NO_CHAR8_T
using std::u8string;
+#endif
#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
using std::wstring;
#endif
@@ -5807,7 +5826,9 @@ export namespace std {
using std::pmr::string;
using std::pmr::u16string;
using std::pmr::u32string;
+#ifndef _LIBCPP_HAS_NO_CHAR8_T
using std::pmr::u8string;
+#endif
#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
using std::pmr::wstring;
#endif
@@ -5816,17 +5837,12 @@ export namespace std {
// [basic.string.hash], hash support
using std::hash;
- // TODO MODULES is this a bug?
-#if _LIBCPP_STD_VER >= 23
- using std::operator""s;
-#else
inline namespace literals {
inline namespace string_literals {
// [basic.string.literals], suffix for basic_string literals
using std::literals::string_literals::operator""s;
} // namespace string_literals
- } // namespace literals
-#endif
+ } // namespace literals
} // namespace std
// string_view.inc
@@ -5859,7 +5875,9 @@ export namespace std {
using std::string_view;
using std::u16string_view;
using std::u32string_view;
+#ifndef _LIBCPP_HAS_NO_CHAR8_T
using std::u8string_view;
+#endif
#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
using std::wstring_view;
#endif
@@ -5904,8 +5922,6 @@ export namespace std {
//
//===----------------------------------------------------------------------===//
-#ifdef _LIBCPP_HAS_YES_SYNCSTREAM
-
export namespace std {
#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
using std::basic_syncbuf;
@@ -5926,8 +5942,6 @@ export namespace std {
#endif // !defined(_LIBCPP_HAS_NO_LOCALIZATION) && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
} // namespace std
-#endif
-
// system_error.inc
// -*- C++ -*-
//===----------------------------------------------------------------------===//
diff --git a/libbuild2/cxx/init.cxx b/libbuild2/cxx/init.cxx
index 8159d18..3185eaa 100644
--- a/libbuild2/cxx/init.cxx
+++ b/libbuild2/cxx/init.cxx
@@ -335,7 +335,12 @@ namespace build2
{
case compiler_type::gcc:
{
- if (mj >= 11)
+ if (mj >= 14)
+ {
+ o = "-std=c++26";
+ cplusplus = 202400;
+ }
+ else if (mj >= 11)
{
o = "-std=c++23";
cplusplus = 202302;
@@ -367,25 +372,29 @@ namespace build2
}
case compiler_type::clang:
{
- // Clang 10.0.0 targeting MSVC 16.4 and 16.5 (preview) in the
- // c++2a mode uncovers some Concepts-related bugs in MSVC STL
- // (LLVM bug #44956). So in this case we map `latest` to
- // c++17.
- //
- // While reportedly this has been fixed in the later versions
- // of MSVC, instead of somehow passing the version of MSVC
- // Clang is targeting, we will just assume that Clang 11
- // and later are used with a sufficiently new version of
- // MSVC.
- //
-
- if (mj >= 13)
+ if (mj >= 18)
+ {
+ o = "-std=c++26";
+ cplusplus = 202400;
+ }
+ else if (mj >= 13)
{
o = "-std=c++2b";
cplusplus = 202302;
}
else if (mj == 10 && latest && tt.system == "win32-msvc")
{
+ // Clang 10.0.0 targeting MSVC 16.4 and 16.5 (preview) in
+ // the c++2a mode uncovers some Concepts-related bugs in
+ // MSVC STL (LLVM bug #44956). So in this case we map
+ // `latest` to c++17.
+ //
+ // While reportedly this has been fixed in the later
+ // versions of MSVC, instead of somehow passing the version
+ // of MSVC Clang is targeting, we will just assume that
+ // Clang 11 and later are used with a sufficiently new
+ // version of MSVC.
+ //
o = "-std=c++17";
cplusplus = 201703;
}
@@ -446,7 +455,7 @@ namespace build2
// 26 to 2c for compatibility with older versions of the
// compilers.
//
- // @@ TMP: update C++26 __cplusplus value once known.
+ // @@ TMP: update C++26 __cplusplus value once known (and above).
//
o = "-std=";
@@ -481,6 +490,12 @@ namespace build2
{
case compiler_type::msvc:
{
+ // Let's enable the new preprocessor in this mode. For background,
+ // see MSVC issue 10537317.
+ //
+ if (mj > 19 || (mj == 19 && mi >= 39))
+ prepend ("/Zc:preprocessor");
+
// Starting with 15.5 (19.12) Visual Studio-created projects
// default to the strict mode. However, this flag currently tends
// to trigger too many compiler bugs. So for now we leave it to
@@ -498,8 +513,8 @@ namespace build2
// Unless disabled by the user, try to enable C++ modules.
//
- // NOTE: see also diagnostics about modules support required in compile
- // rule.
+ // NOTE: see also diagnostics about modules support required (if
+ // attempting to use) in compile rule.
//
if (!modules.value || *modules.value)
{
@@ -580,7 +595,7 @@ namespace build2
// around Clang 16 so we don't support anything earlier than
// that (it's not practically usable anyway).
//
- // Clang enable modules by default in c++20 or later but they
+ // Clang enables modules by default in c++20 or later but they
// don't yet (as of Clang 18) define __cpp_modules. When they
// do, we can consider enabling modules by default on our side.
// For now, we only enable modules if forced with explicit
diff --git a/libbuild2/diagnostics.cxx b/libbuild2/diagnostics.cxx
index 4a46756..c795e44 100644
--- a/libbuild2/diagnostics.cxx
+++ b/libbuild2/diagnostics.cxx
@@ -55,31 +55,31 @@ namespace build2
if (st)
{
- // @@ TMP: eventually we want to enable on Windows by default.
+ // Only attempt to enable if explicitly requested by the user. Note that
+ // while we may enable color for our process, who knows if this gets
+ // inherited by other processes we start (e.g., compilers) and/or
+ // whether they will do something sensible about any of this.
//
-#ifdef _WIN32
- if (c && *c)
+ try
{
-#endif
- stderr_term_color = fdterm_color (stderr_fd (), !c || *c /* enable */);
+ stderr_term_color = fdterm_color (stderr_fd (), c && *c /* enable */);
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to query terminal color support for stderr: " << e;
+ }
- // If the user specified --diag-color on POSIX we will trust the color
- // is supported (e.g., wrong TERM value, etc).
- //
- if (!stderr_term_color && c && *c)
- {
+ // If the user specified --diag-color on POSIX we will trust the color
+ // is supported (e.g., wrong TERM value, etc).
+ //
+ if (!stderr_term_color && c && *c)
+ {
#ifdef _WIN32
- fail << "unable to enable diagnostics color support for stderr";
+ fail << "unable to enable diagnostics color support for stderr";
#else
- stderr_term_color = true;
+ stderr_term_color = true;
#endif
- }
-
-#ifdef _WIN32
}
- else
- stderr_term_color = false;
-#endif
}
else
stderr_term_color = false;
diff --git a/libbuild2/functions-filesystem.cxx b/libbuild2/functions-filesystem.cxx
index 665a0f3..340c2bc 100644
--- a/libbuild2/functions-filesystem.cxx
+++ b/libbuild2/functions-filesystem.cxx
@@ -5,6 +5,7 @@
#include <libbuild2/function.hxx>
#include <libbuild2/variable.hxx>
+#include <libbuild2/filesystem.hxx>
using namespace std;
using namespace butl;
@@ -95,14 +96,60 @@ namespace build2
return r;
}
+ static bool
+ file_exists (path&& f)
+ {
+ if (f.relative () && path_traits::thread_current_directory () != nullptr)
+ f.complete ();
+
+ return exists (f);
+ }
+
+ static bool
+ directory_exists (dir_path&& d)
+ {
+ if (d.relative () && path_traits::thread_current_directory () != nullptr)
+ d.complete ();
+
+ return exists (d);
+ }
+
void
filesystem_functions (function_map& m)
{
- // @@ Maybe we should have the ability to mark the whole family as not
- // pure?
+ // NOTE: anything that depends on relative path must handle the
+ // thread-specific curren directory override explicitly.
function_family f (m, "filesystem");
+ // $file_exists(<path>)
+ //
+ // Return true if a filesystem entry at the specified path exists and is a
+ // regular file (or is a symlink to a regular file) and false otherwise.
+ //
+ // Note that this function is not pure.
+ //
+ {
+ auto e (f.insert ("file_exists", false));
+
+ e += [](path f) {return file_exists (move (f));};
+ e += [](names ns) {return file_exists (convert<path> (move (ns)));};
+ }
+
+ // $directory_exists(<path>)
+ //
+ // Return true if a filesystem entry at the specified path exists and is a
+ // directory (or is a symlink to a directory) and false otherwise.
+ //
+ // Note that this function is not pure.
+ //
+ {
+ auto e (f.insert ("directory_exists", false));
+
+ e += [](path f) {return directory_exists (path_cast<dir_path> (move (f)));};
+ e += [](names ns) {return directory_exists (convert<dir_path> (move (ns)));};
+ }
+
// $path_search(<pattern>[, <start-dir>])
//
// Return filesystem paths that match the shell-like wildcard pattern. If
diff --git a/libbuild2/functions-path.cxx b/libbuild2/functions-path.cxx
index 4b114f5..2f7f159 100644
--- a/libbuild2/functions-path.cxx
+++ b/libbuild2/functions-path.cxx
@@ -193,6 +193,35 @@ namespace build2
#endif
}
+ template <typename P>
+ static bool
+ try_normalize (P& p)
+ {
+ try
+ {
+ p.normalize ();
+ return true;
+ }
+ catch (const invalid_path&) {}
+
+ return false;
+ }
+
+ template <typename P>
+ static bool
+ try_actualize (P& p)
+ {
+ try
+ {
+ p.normalize (true);
+ return true;
+ }
+ catch (const invalid_path&) {}
+ catch (const system_error&) {}
+
+ return false;
+ }
+
void
path_functions (function_map& m)
{
@@ -344,138 +373,76 @@ namespace build2
return ns;
};
- // $canonicalize(<paths>)
- // $path.canonicalize(<untyped>)
+ // $absolute(<path>)
+ // $path.absolute(<untyped>)
//
- // Canonicalize the path (or list of paths) by converting all the
- // directory separators to the canonical form for the host platform. Note
- // that multiple directory separators are not collapsed.
- //
-
- // @@ TODO: add ability to specify alternative separator.
+ // Return true if the path is absolute and false otherwise.
//
- f["canonicalize"] += [](path p) {p.canonicalize (); return p;};
- f["canonicalize"] += [](dir_path p) {p.canonicalize (); return p;};
-
- f["canonicalize"] += [](paths v)
- {
- for (auto& p: v)
- p.canonicalize ();
- return v;
- };
-
- f["canonicalize"] += [](dir_paths v)
+ f["absolute"] += [](path p)
{
- for (auto& p: v)
- p.canonicalize ();
- return v;
+ return p.absolute ();
};
- f[".canonicalize"] += [](names ns)
+ f[".absolute"] += [](names ns)
{
- // For each path decide based on the presence of a trailing slash
- // whether it is a directory. Return as untyped list of (potentially
- // mixed) paths.
- //
- for (name& n: ns)
- {
- if (n.directory ())
- n.dir.canonicalize ();
- else
- n.value = convert<path> (move (n)).canonicalize ().string ();
- }
- return ns;
+ return convert<path> (move (ns)).absolute ();
};
- // $normalize(<paths>)
- // $path.normalize(<untyped>)
+ // $simple(<path>)
+ // $path.simple(<untyped>)
//
- // Normalize the path (or list of paths) by collapsing the `.` and `..`
- // components if possible, collapsing multiple directory separators, and
- // converting all the directory separators to the canonical form for the
- // host platform.
+ // Return true if the path is simple, that is, has no direcrory component,
+ // and false otherwise.
//
- f["normalize"] += [](path p) {p.normalize (); return p;};
- f["normalize"] += [](dir_path p) {p.normalize (); return p;};
+ // Note that on POSIX `/foo` is not a simple path (it is `foo` in the root
+ // directory) while `/` is (it is the root directory).
+ //
+ f["simple"] += [](path p)
+ {
+ return p.simple ();
+ };
- f["normalize"] += [](paths v)
+ f[".simple"] += [](names ns)
{
- for (auto& p: v)
- p.normalize ();
- return v;
+ return convert<path> (move (ns)).simple ();
};
- f["normalize"] += [](dir_paths v)
+ // $sub_path(<path>, <path>)
+ // $path.sub_path(<untyped>, <untyped>)
+ //
+ // Return true if the path specified as the first argument is a sub-path
+ // of the one specified as the second argument (in other words, the second
+ // argument is a prefix of the first) and false otherwise. Both paths are
+ // expected to be normalized. Note that this function returns true if the
+ // paths are equal. Empty path is considered a prefix of any path.
+ //
+ f["sub_path"] += [](path p, value v)
{
- for (auto& p: v)
- p.normalize ();
- return v;
+ return p.sub (convert_to_base<path> (move (v)));
};
- f[".normalize"] += [](names ns)
+ f[".sub_path"] += [](names ns, value v)
{
- // For each path decide based on the presence of a trailing slash
- // whether it is a directory. Return as untyped list of (potentially
- // mixed) paths.
- //
- for (name& n: ns)
- {
- if (n.directory ())
- n.dir.normalize ();
- else
- n.value = convert<path> (move (n)).normalize ().string ();
- }
- return ns;
+ return convert<path> (move (ns)).sub (convert_to_base<path> (move (v)));
};
- // $actualize(<paths>)
- // $path.actualize(<untyped>)
- //
- // Actualize the path (or list of paths) by first normalizing it and then
- // for host platforms with case-insensitive filesystems obtaining the
- // actual spelling of the path.
+ // $super_path(<path>, <path>)
+ // $path.super_path(<untyped>, <untyped>)
//
- // Note that only an absolute path can be actualized. If a path component
- // does not exist, then its (and all subsequent) spelling is
- // unchanged. This is a potentially expensive operation.
- //
- // Note that this function is not pure.
+ // Return true if the path specified as the first argument is a super-path
+ // of the one specified as the second argument (in other words, the second
+ // argument is a suffix of the first) and false otherwise. Both paths are
+ // expected to be normalized. Note that this function returns true if the
+ // paths are equal. Empty path is considered a suffix of any path.
//
+ f["super_path"] += [](path p, value v)
{
- auto e (f.insert ("actualize", false));
-
- e += [](path p) {p.normalize (true); return p;};
- e += [](dir_path p) {p.normalize (true); return p;};
-
- e += [](paths v)
- {
- for (auto& p: v)
- p.normalize (true);
- return v;
- };
-
- e += [](dir_paths v)
- {
- for (auto& p: v)
- p.normalize (true);
- return v;
- };
- }
+ return p.sup (convert_to_base<path> (move (v)));
+ };
- f.insert (".actualize", false) += [](names ns)
+ f[".super_path"] += [](names ns, value v)
{
- // For each path decide based on the presence of a trailing slash
- // whether it is a directory. Return as untyped list of (potentially
- // mixed) paths.
- //
- for (name& n: ns)
- {
- if (n.directory ())
- n.dir.normalize (true);
- else
- n.value = convert<path> (move (n)).normalize (true).string ();
- }
- return ns;
+ return convert<path> (move (ns)).sup (convert_to_base<path> (move (v)));
};
// $directory(<paths>)
@@ -615,6 +582,8 @@ namespace build2
// specified paths). Issue diagnostics and fail if a relative path cannot
// be derived (for example, paths are on different drives on Windows).
//
+ // Note: to check if a path if relative, use `$path.absolute()`.
+ //
f["relative"] += [](path p, dir_path d)
{
return relative (p, d);
@@ -701,6 +670,261 @@ namespace build2
return extension (convert<path> (move (ns)));
};
+ // $complete(<paths>)
+ // $path.complete(<untyped>)
+ //
+ // Complete the path (or list of paths) by prepending the current working
+ // directory unless the path is already absolute.
+ //
+ f["complete"] += [](path p) {p.complete (); return p;};
+ f["complete"] += [](dir_path p) {p.complete (); return p;};
+
+ f["complete"] += [](paths v)
+ {
+ for (auto& p: v)
+ p.complete ();
+ return v;
+ };
+
+ f["complete"] += [](dir_paths v)
+ {
+ for (auto& p: v)
+ p.complete ();
+ return v;
+ };
+
+ f[".complete"] += [](names ns)
+ {
+ // For each path decide based on the presence of a trailing slash
+ // whether it is a directory. Return as untyped list of (potentially
+ // mixed) paths.
+ //
+ for (name& n: ns)
+ {
+ if (n.directory ())
+ n.dir.complete ();
+ else
+ n.value = convert<path> (move (n)).complete ().string ();
+ }
+ return ns;
+ };
+
+ // $canonicalize(<paths>)
+ // $path.canonicalize(<untyped>)
+ //
+ // Canonicalize the path (or list of paths) by converting all the
+ // directory separators to the canonical form for the host platform. Note
+ // that multiple directory separators are not collapsed.
+ //
+ f["canonicalize"] += [](path p) {p.canonicalize (); return p;};
+ f["canonicalize"] += [](dir_path p) {p.canonicalize (); return p;};
+
+ f["canonicalize"] += [](paths v)
+ {
+ for (auto& p: v)
+ p.canonicalize ();
+ return v;
+ };
+
+ f["canonicalize"] += [](dir_paths v)
+ {
+ for (auto& p: v)
+ p.canonicalize ();
+ return v;
+ };
+
+ f[".canonicalize"] += [](names ns)
+ {
+ // For each path decide based on the presence of a trailing slash
+ // whether it is a directory. Return as untyped list of (potentially
+ // mixed) paths.
+ //
+ for (name& n: ns)
+ {
+ if (n.directory ())
+ n.dir.canonicalize ();
+ else
+ n.value = convert<path> (move (n)).canonicalize ().string ();
+ }
+ return ns;
+ };
+
+ // $normalize(<paths>)
+ // $path.normalize(<untyped>)
+ // $try_normalize(<path>)
+ // $path.try_normalize(<untyped>)
+ //
+ // Normalize the path (or list of paths) by collapsing the `.` and `..`
+ // components if possible, collapsing multiple directory separators, and
+ // converting all the directory separators to the canonical form for the
+ // host platform.
+ //
+ // If the resulting path would be invalid, the `$normalize()` version
+ // issues diagnostics and fails while the `$try_normalize()` version
+ // returns `null`. Note that `$try_normalize()` only accepts a single
+ // path.
+ //
+ f["normalize"] += [](path p) {p.normalize (); return p;};
+ f["normalize"] += [](dir_path p) {p.normalize (); return p;};
+
+ f["normalize"] += [](paths v)
+ {
+ for (auto& p: v)
+ p.normalize ();
+ return v;
+ };
+
+ f["normalize"] += [](dir_paths v)
+ {
+ for (auto& p: v)
+ p.normalize ();
+ return v;
+ };
+
+ f[".normalize"] += [](names ns)
+ {
+ // For each path decide based on the presence of a trailing slash
+ // whether it is a directory. Return as untyped list of (potentially
+ // mixed) paths.
+ //
+ for (name& n: ns)
+ {
+ if (n.directory ())
+ n.dir.normalize ();
+ else
+ n.value = convert<path> (move (n)).normalize ().string ();
+ }
+ return ns;
+ };
+
+ f["try_normalize"] += [](path p)
+ {
+ return try_normalize (p) ? value (move (p)) : value (nullptr);
+ };
+
+ f["try_normalize"] += [](dir_path p)
+ {
+ return try_normalize (p) ? value (move (p)) : value (nullptr);
+ };
+
+ f[".try_normalize"] += [](names ns)
+ {
+ if (ns.size () != 1)
+ throw invalid_argument ("multiple paths");
+
+ name& n (ns.front ());
+
+ bool r;
+ if (n.directory ())
+ r = try_normalize (n.dir);
+ else
+ {
+ path p (convert<path> (move (n)));
+ if ((r = try_normalize (p)))
+ n.value = move (p).string ();
+ }
+
+ return r ? value (move (ns)) : value (nullptr);
+ };
+
+ // $actualize(<paths>)
+ // $path.actualize(<untyped>)
+ // $try_actualize(<path>)
+ // $path.try_actualize(<untyped>)
+ //
+ // Actualize the path (or list of paths) by first normalizing it and then
+ // for host platforms with case-insensitive filesystems obtaining the
+ // actual spelling of the path.
+ //
+ // Only an absolute path can be actualized. If a path component does not
+ // exist, then its (and all subsequent) spelling is unchanged. Note that
+ // this is a potentially expensive operation.
+ //
+ // If the resulting path would be invalid or in case of filesystem errors
+ // (other than non-existent component), the `$actualize()` version issues
+ // diagnostics and fails while the `$try_actualize()` version returns
+ // `null`. Note that `$try_actualize()` only accepts a single path.
+ //
+ // Note that this function is not pure.
+ //
+ {
+ auto e (f.insert ("actualize", false));
+
+ e += [](path p) {p.normalize (true); return p;};
+ e += [](dir_path p) {p.normalize (true); return p;};
+
+ e += [](paths v)
+ {
+ for (auto& p: v)
+ p.normalize (true);
+ return v;
+ };
+
+ e += [](dir_paths v)
+ {
+ for (auto& p: v)
+ p.normalize (true);
+ return v;
+ };
+ }
+
+ f.insert (".actualize", false) += [](names ns)
+ {
+ // For each path decide based on the presence of a trailing slash
+ // whether it is a directory. Return as untyped list of (potentially
+ // mixed) paths.
+ //
+ for (name& n: ns)
+ {
+ if (n.directory ())
+ n.dir.normalize (true);
+ else
+ n.value = convert<path> (move (n)).normalize (true).string ();
+ }
+ return ns;
+ };
+
+ {
+ auto e (f.insert ("try_actualize", false));
+
+ e += [](path p)
+ {
+ return try_actualize (p) ? value (move (p)) : value (nullptr);
+ };
+
+ e += [](dir_path p)
+ {
+ return try_actualize (p) ? value (move (p)) : value (nullptr);
+ };
+ }
+
+ f.insert (".try_actualize", false) += [](names ns)
+ {
+ if (ns.size () != 1)
+ throw invalid_argument ("multiple paths");
+
+ name& n (ns.front ());
+
+ bool r;
+ if (n.directory ())
+ r = try_actualize (n.dir);
+ else
+ {
+ path p (convert<path> (move (n)));
+ if ((r = try_actualize (p)))
+ n.value = move (p).string ();
+ }
+
+ return r ? value (move (ns)) : value (nullptr);
+ };
+
+
+ // Note that we currently do not expose realize(). For one, it might be
+ // tricky to handle CWD overrides (on POSIX we just call realize(3)).
+ // Also, our implementation for Windows currently does not handle
+ // symlinks.
+
+
// $size(<paths>)
// $size(<path>)
//
diff --git a/libbuild2/functions-regex.cxx b/libbuild2/functions-regex.cxx
index cf3ffd0..c46f6f5 100644
--- a/libbuild2/functions-regex.cxx
+++ b/libbuild2/functions-regex.cxx
@@ -688,6 +688,9 @@ namespace build2
// If both `return_match` and `return_subs` flags are specified then the
// sub-string that matches the whole regular expression comes first.
//
+ // See also `$string.contains()`, `$string.starts_with()`,
+ // `$string.ends_with()`.
+ //
f[".search"] += [](value v, string re, optional<names> flags)
{
return search (move (v), re, move (flags));
diff --git a/libbuild2/functions-string.cxx b/libbuild2/functions-string.cxx
index b7e0a17..eccc6c7 100644
--- a/libbuild2/functions-string.cxx
+++ b/libbuild2/functions-string.cxx
@@ -8,6 +8,136 @@ using namespace std;
namespace build2
{
+ // Look for the substring forwards in the [p, n) range.
+ //
+ static inline size_t
+ find (const string& s, size_t p, const string& ss, bool ic)
+ {
+ size_t sn (ss.size ());
+
+ for (size_t n (s.size ()); p != n; ++p)
+ {
+ if (n - p >= sn &&
+ (ic
+ ? icasecmp (ss, s.c_str () + p, sn)
+ : s.compare (p, sn, ss)) == 0)
+ return p;
+ }
+
+ return string::npos;
+ }
+
+ // Look for the substring backwards in the [0, n) range.
+ //
+ static inline size_t
+ rfind (const string& s, size_t n, const string& ss, bool ic)
+ {
+ size_t sn (ss.size ());
+
+ if (n >= sn)
+ {
+ n -= sn; // Don't consider characters out of range.
+
+ for (size_t p (n);; )
+ {
+ if ((ic
+ ? icasecmp (ss, s.c_str () + p, sn)
+ : s.compare (p, sn, ss)) == 0)
+ return p;
+
+ if (--p == 0)
+ break;
+ }
+ }
+
+ return string::npos;
+ }
+
+ static bool
+ contains (const string& s, value&& ssv, optional<names>&& fs)
+ {
+ bool ic (false), once (false);
+ if (fs)
+ {
+ for (name& f: *fs)
+ {
+ string s (convert<string> (move (f)));
+
+ if (s == "icase")
+ ic = true;
+ else if (s == "once")
+ once = true;
+ else
+ throw invalid_argument ("invalid flag '" + s + '\'');
+ }
+ }
+
+ const string ss (convert<string> (move (ssv)));
+
+ if (ss.empty ())
+ throw invalid_argument ("empty substring");
+
+ size_t p (find (s, 0, ss, ic));
+
+ if (once && p != string::npos && p != rfind (s, s.size (), ss, ic))
+ p = string::npos;
+
+ return p != string::npos;
+ }
+
+ static bool
+ starts_with (const string& s, value&& pfv, optional<names>&& fs)
+ {
+ bool ic (false);
+ if (fs)
+ {
+ for (name& f: *fs)
+ {
+ string s (convert<string> (move (f)));
+
+ if (s == "icase")
+ ic = true;
+ else
+ throw invalid_argument ("invalid flag '" + s + '\'');
+ }
+ }
+
+ const string pf (convert<string> (move (pfv)));
+
+ if (pf.empty ())
+ throw invalid_argument ("empty prefix");
+
+ return find (s, 0, pf, ic) == 0;
+ }
+
+ static bool
+ ends_with (const string& s, value&& sfv, optional<names>&& fs)
+ {
+ bool ic (false);
+ if (fs)
+ {
+ for (name& f: *fs)
+ {
+ string s (convert<string> (move (f)));
+
+ if (s == "icase")
+ ic = true;
+ else
+ throw invalid_argument ("invalid flag '" + s + '\'');
+ }
+ }
+
+ const string sf (convert<string> (move (sfv)));
+
+ if (sf.empty ())
+ throw invalid_argument ("empty suffix");
+
+ size_t n (s.size ());
+ size_t p (rfind (s, n, sf, ic));
+
+ return p != string::npos && p + sf.size () == n;
+ }
+
static string
replace (string&& s, value&& fv, value&& tv, optional<names>&& fs)
{
@@ -43,52 +173,13 @@ namespace build2
size_t fn (f.size ());
- // Look for the substring forward in the [p, n) range.
- //
- auto find = [&s, &f, fn, ic] (size_t p) -> size_t
- {
- for (size_t n (s.size ()); p != n; ++p)
- {
- if (n - p >= fn &&
- (ic
- ? icasecmp (f, s.c_str () + p, fn)
- : s.compare (p, fn, f)) == 0)
- return p;
- }
-
- return string::npos;
- };
-
- // Look for the substring backard in the [0, n) range.
- //
- auto rfind = [&s, &f, fn, ic] (size_t n) -> size_t
- {
- if (n >= fn)
- {
- n -= fn; // Don't consider characters out of range.
-
- for (size_t p (n);; )
- {
- if ((ic
- ? icasecmp (f, s.c_str () + p, fn)
- : s.compare (p, fn, f)) == 0)
- return p;
-
- if (--p == 0)
- break;
- }
- }
-
- return string::npos;
- };
-
if (fo || lo)
{
- size_t p (lo ? rfind (s.size ()) : find (0));
+ size_t p (lo ? rfind (s, s.size (), f, ic) : find (s, 0, f, ic));
if (fo && lo && p != string::npos)
{
- if (p != find (0))
+ if (p != find (s, 0, f, ic))
p = string::npos;
}
@@ -97,7 +188,9 @@ namespace build2
}
else
{
- for (size_t p (0); (p = find (0)) != string::npos; p += fn)
+ size_t tn (t.size ());
+
+ for (size_t p (0); (p = find (s, p, f, ic)) != string::npos; p += tn)
s.replace (p, fn, t);
}
}
@@ -173,6 +266,75 @@ namespace build2
convert<string> (move (y))) == 0;
};
+ // $string.contains(<untyped>, <untyped>[, <flags>])
+ // $contains(<string>, <string>[, <flags>])
+ //
+ // Check if the string (first argument) contains the given substring
+ // (second argument). The substring must not be empty.
+ //
+ // The following flags are supported:
+ //
+ // icase - compare ignoring case
+ //
+ // once - check if the substring occurs exactly once
+ //
+ // See also `$string.starts_with()`, `$string.ends_with()`,
+ // `$regex.search()`.
+ //
+ f["contains"] += [](string s, value ss, optional<names> fs)
+ {
+ return contains (move (s), move (ss), move (fs));
+ };
+
+ f[".contains"] += [](names s, value ss, optional<names> fs)
+ {
+ return contains (convert<string> (move (s)), move (ss), move (fs));
+ };
+
+ // $string.starts_with(<untyped>, <untyped>[, <flags>])
+ // $starts_with(<string>, <string>[, <flags>])
+ //
+ // Check if the string (first argument) begins with the given prefix
+ // (second argument). The prefix must not be empty.
+ //
+ // The following flags are supported:
+ //
+ // icase - compare ignoring case
+ //
+ // See also `$string.contains()`.
+ //
+ f["starts_with"] += [](string s, value pf, optional<names> fs)
+ {
+ return starts_with (move (s), move (pf), move (fs));
+ };
+
+ f[".starts_with"] += [](names s, value pf, optional<names> fs)
+ {
+ return starts_with (convert<string> (move (s)), move (pf), move (fs));
+ };
+
+ // $string.ends_with(<untyped>, <untyped>[, <flags>])
+ // $ends_with(<string>, <string>[, <flags>])
+ //
+ // Check if the string (first argument) ends with the given suffix (second
+ // argument). The suffix must not be empty.
+ //
+ // The following flags are supported:
+ //
+ // icase - compare ignoring case
+ //
+ // See also `$string.contains()`.
+ //
+ f["ends_with"] += [](string s, value sf, optional<names> fs)
+ {
+ return ends_with (move (s), move (sf), move (fs));
+ };
+
+ f[".ends_with"] += [](names s, value sf, optional<names> fs)
+ {
+ return ends_with (convert<string> (move (s)), move (sf), move (fs));
+ };
+
// $string.replace(<untyped>, <from>, <to> [, <flags>])
// $replace(<string>, <from>, <to> [, <flags>])
//
diff --git a/libbuild2/scheduler.cxx b/libbuild2/scheduler.cxx
index e3fbcc1..69673e6 100644
--- a/libbuild2/scheduler.cxx
+++ b/libbuild2/scheduler.cxx
@@ -97,6 +97,10 @@ namespace build2
{
// Note: assume non-serial execution.
+ // Note: increment progress before/after every wait.
+ //
+ progress_.fetch_add (1, memory_order_relaxed);
+
lock l (move (rl)); // Make sure unlocked on exception.
active_--;
@@ -135,6 +139,10 @@ namespace build2
{
// Note: assume non-serial execution.
+ // Note: increment progress before/after every wait.
+ //
+ progress_.fetch_add (1, memory_order_relaxed);
+
lock l (mutex_);
if (collision)
@@ -1053,7 +1061,9 @@ namespace build2
// Re-check active/external counts for good measure (in case we were
// spinning too fast).
//
- if (np == op && s.active_ == 0 && s.external_ == 0 && !s.shutdown_)
+ if (np == op &&
+ s.active_ == 0 && s.external_ == 0 && !s.shutdown_ &&
+ s.progress_.load (memory_order_consume) == op)
{
// Shutting things down cleanly is tricky: we could have handled it
// in the scheduler (e.g., by setting a flag and then waking
diff --git a/libbuild2/test/script/parser.cxx b/libbuild2/test/script/parser.cxx
index b712c21..337b162 100644
--- a/libbuild2/test/script/parser.cxx
+++ b/libbuild2/test/script/parser.cxx
@@ -1609,6 +1609,17 @@ namespace build2
{
runner_->enter (*scope_, scope_->start_loc_);
+ // Set thread-specific current directory override. In particular, this
+ // makes sure functions like $path.complete() work correctly.
+ //
+ auto wdg = make_guard (
+ [old = path_traits::thread_current_directory ()] ()
+ {
+ path_traits::thread_current_directory (old);
+ });
+
+ path_traits::thread_current_directory (&scope_->work_dir.path->string ());
+
// Note that we rely on "small function object" optimization for the
// exec_*() lambdas.
//
diff --git a/libbuild2/utility.hxx b/libbuild2/utility.hxx
index b534f41..151b409 100644
--- a/libbuild2/utility.hxx
+++ b/libbuild2/utility.hxx
@@ -877,17 +877,29 @@ namespace build2
//
template <typename I, typename F>
void
- append_option_values (cstrings&,
- const char* opt,
- I begin, I end,
- F&& get = [] (const string& s) {return s.c_str ();});
+ append_option_values (
+ cstrings&,
+ const char* opt,
+ I begin, I end,
+ F&& get = [] (const string& s) {return s.c_str ();});
template <typename I, typename F>
void
- append_option_values (sha256&,
- const char* opt,
- I begin, I end,
- F&& get = [] (const string& s) {return s;});
+ append_option_values (
+ sha256&,
+ const char* opt,
+ I begin, I end,
+ F&& get = [] (const string& s) -> const string& {return s;});
+
+ // As above but in a combined form (e.g., -L/usr/local/lib).
+ //
+ template <typename I, typename F>
+ void
+ append_combined_option_values (
+ strings&,
+ const char* opt,
+ I begin, I end,
+ F&& get = [] (const string& s) -> const string& {return s;});
// As above but append a single option (used for append/hash uniformity).
//
diff --git a/libbuild2/utility.txx b/libbuild2/utility.txx
index d2fc29c..cdf510f 100644
--- a/libbuild2/utility.txx
+++ b/libbuild2/utility.txx
@@ -5,16 +5,16 @@ namespace build2
{
template <typename I, typename F>
void
- append_option_values (cstrings& args, const char* o, I b, I e, F&& get)
+ append_option_values (cstrings& ss, const char* o, I b, I e, F&& get)
{
if (b != e)
{
- args.reserve (args.size () + (e - b));
+ ss.reserve (ss.size () + (e - b));
for (; b != e; ++b)
{
- args.push_back (o);
- args.push_back (get (*b));
+ ss.push_back (o);
+ ss.push_back (get (*b));
}
}
}
@@ -30,6 +30,19 @@ namespace build2
}
}
+ template <typename I, typename F>
+ void
+ append_combined_option_values (strings& ss, const char* o, I b, I e, F&& get)
+ {
+ if (b != e)
+ {
+ ss.reserve (ss.size () + (e - b));
+
+ for (; b != e; ++b)
+ ss.push_back (string (o) += get (*b));
+ }
+ }
+
template <typename K>
basic_path<char, K>
relative (const basic_path<char, K>& p)
diff --git a/libbuild2/variable.hxx b/libbuild2/variable.hxx
index aed3350..6dfbbc6 100644
--- a/libbuild2/variable.hxx
+++ b/libbuild2/variable.hxx
@@ -92,6 +92,9 @@ namespace build2
// static_cast to const T*. If it is NULL, then cast data_ directly. Note
// that this function is used for both const and non-const values.
//
+ // @@ This is currently ignored by as<T>() which is now used in quite a
+ // few places (in particular, grep for as<T>).
+ //
const void* (*const cast) (const value&, const value_type*);
// If NULL, then the types are compared as PODs using memcmp().
@@ -702,14 +705,18 @@ namespace build2
template <typename T> T convert (names&&);
// Convert value to T. If value is already of type T, then simply cast it.
- // Otherwise call convert(names) above. If value is NULL, then throw
- // invalid_argument (with an appropriate message).
+ // Otherwise call convert(names) above. If the value is NULL, then throw
+ // invalid_argument (with an appropriate message). See also
+ // convert_to_base() below.
//
template <typename T> T convert (value&&);
+ template <typename T> T convert (const value&);
- // As above but preserving the value.
+ // As above but also allow the derived-to-base conversions (where T is
+ // base). Note that this call may potentially slice the value.
//
- template <typename T> T convert (const value&);
+ template <typename T> T convert_to_base (value&&);
+ template <typename T> T convert_to_base (const value&);
// Default implementations of the dtor/copy_ctor/copy_assing callbacks for
// types that are stored directly in value::data_ and the provide all the
diff --git a/libbuild2/variable.txx b/libbuild2/variable.txx
index 0b831e9..a1ee340 100644
--- a/libbuild2/variable.txx
+++ b/libbuild2/variable.txx
@@ -38,6 +38,11 @@ namespace build2
{
if (v.type == nullptr)
return convert<T> (move (v).as<names> ());
+ //
+ // Note that while it may be tempting to use is_a() here like in cast(),
+ // the implications are unclear (i.e., we may end up relaxing things we
+ // don't want to). So we have the convert_to_base() variants instead.
+ //
else if (v.type == &value_traits<T>::value_type)
return move (v).as<T> ();
}
@@ -61,6 +66,36 @@ namespace build2
}
template <typename T>
+ T
+ convert_to_base (value&& v)
+ {
+ if (v)
+ {
+ if (v.type == nullptr)
+ return convert<T> (move (v).as<names> ());
+ else if (v.type->is_a<T> ())
+ return move (v).as<T> ();
+ }
+
+ convert_throw (v ? v.type : nullptr, value_traits<T>::value_type);
+ }
+
+ template <typename T>
+ T
+ convert_to_base (const value& v)
+ {
+ if (v)
+ {
+ if (v.type == nullptr)
+ return convert<T> (names (v.as<names> ()));
+ else if (v.type->is_a<T> ())
+ return v.as<T> ();
+ }
+
+ convert_throw (v ? v.type : nullptr, value_traits<T>::value_type);
+ }
+
+ template <typename T>
void
default_dtor (value& v)
{
diff --git a/manifest b/manifest
index 4781eef..fecd6c5 100644
--- a/manifest
+++ b/manifest
@@ -19,4 +19,3 @@ depends: * bpkg >= 0.16.0-
# @@ DEP Should probably become conditional dependency.
#requires: ? cli ; Only required if changing .cli files.
depends: libbutl [0.17.0-a.0.1 0.17.0-a.1)
-depends: libpkg-config ~0.1.1
diff --git a/repositories.manifest b/repositories.manifest
index 81d0645..25be350 100644
--- a/repositories.manifest
+++ b/repositories.manifest
@@ -3,8 +3,4 @@ summary: build2 build system repository
:
role: prerequisite
-location: ../libbutl.git##HEAD
-
-:
-role: prerequisite
-location: https://github.com/build2/libpkg-config.git##HEAD
+location: ../libbutl.git#HEAD
diff --git a/tests/function/filesystem/testscript b/tests/function/filesystem/testscript
index cf93b8b..c7c08f1 100644
--- a/tests/function/filesystem/testscript
+++ b/tests/function/filesystem/testscript
@@ -73,3 +73,51 @@
EOE
}
}
+
+: file_exists
+:
+{
+ : file
+ :
+ touch f;
+ $* <'print $file_exists(f)' >'true'
+
+ : symlink
+ :
+ touch f && ln -s f s;
+ $* <'print $file_exists([path] s)' >'true'
+
+ : directory
+ :
+ mkdir d;
+ $* <'print $file_exists([dir_path] d)' >'false'
+
+ : testscript
+ :
+ touch f;
+ echo $file_exists(f) >'true'
+}
+
+: directory_exists
+:
+{
+ : directory
+ :
+ mkdir d;
+ $* <'print $directory_exists(d)' >'true'
+
+ : symlink
+ :
+ mkdir d && ln -s d s;
+ $* <'print $directory_exists([dir_path] d)' >'true'
+
+ : file
+ :
+ touch f;
+ $* <'print $directory_exists([path] f)' >'false'
+
+ : testscript
+ :
+ mkdir d;
+ echo $directory_exists(d) >'true'
+}
diff --git a/tests/function/path/testscript b/tests/function/path/testscript
index 1ed89ca..6321b3d 100644
--- a/tests/function/path/testscript
+++ b/tests/function/path/testscript
@@ -80,36 +80,35 @@ s = ($posix ? '/' : '\')
}
}
-: canonicalize
+: absolute
:
{
- $* <'print $canonicalize([path] a/b)' >"a$(s)b" : path
- $* <'print $canonicalize([paths] a/b a/c)' >"a$(s)b a$(s)c" : paths
- $* <'print $canonicalize([dir_path] a/b)' >"a$(s)b$s" : dir-path
- $* <'print $canonicalize([dir_paths] a/b a/c/)' >"a$(s)b$s a$(s)c$s" : dir-paths
- $* <'print $path.canonicalize(a/b)' >"a$(s)b" : untyped
- $* <'print $path.canonicalize(a/b/ a/c)' >"a$(s)b$s a$(s)c" : mixed
+ $* <'print $absolute($src_root)' >"true" : true
+ $* <'print $path.absolute(a/b)' >"false" : false
}
-: normalize
+: simple
:
{
- $* <'print $normalize([path] a/../b)' >"b" : path
- $* <'print $normalize([paths] a/../b a/../c)' >"b c" : paths
- $* <'print $normalize([dir_path] a/../b)' >"b$s" : dir-path
- $* <'print $normalize([dir_paths] a/../b a/../c/)' >"b$s c$s" : dir-paths
- $* <'print $path.normalize(a/../b)' >"b" : untyped
- $* <'print $path.normalize(a/../b/ a/../c)' >"b$s c" : mixed
+ $* <'print $simple([path] a)' >"true" : true
+ $* <'print $path.simple(a/b)' >"false" : false
}
-: actualize
+: sub_path
:
-if! $posix
{
- mkdir Foo;
- $* <'print $path.actualize($out_base/foo)' >~'/.+\\Foo/'
+ $* <'print $sub_path($src_base, $src_root)' >"true" : true-absolute
+ $* <'print $path.sub_path(a/b/c, a/b)' >"true" : true-relative
+ $* <'print $path.sub_path(a/b/c, a/d)' >"false" : false
}
+: super_path
+:
+{
+ $* <'print $super_path($src_base, true-absolute)' >"true" : true-absolute
+ $* <'print $path.super_path(a/b/c, b/c)' >"true" : true-relative
+ $* <'print $path.super_path(a/b/c, c/a)' >"false" : false
+}
: directory
:
@@ -205,6 +204,53 @@ if! $posix
EOO
}
+: complete
+:
+{
+ $* <'print $complete([path] a)' >"$~$(s)a" : path
+ $* <'print $complete([dir_path] a)' >"$~$(s)a$(s)" : dir-path
+ $* <'print $path.complete(a)' >"$~$(s)a" : untyped
+
+ echo $path.complete(a) > "$~$(s)a" : testscript
+}
+
+: canonicalize
+:
+{
+ $* <'print $canonicalize([path] a/b)' >"a$(s)b" : path
+ $* <'print $canonicalize([paths] a/b a/c)' >"a$(s)b a$(s)c" : paths
+ $* <'print $canonicalize([dir_path] a/b)' >"a$(s)b$s" : dir-path
+ $* <'print $canonicalize([dir_paths] a/b a/c/)' >"a$(s)b$s a$(s)c$s" : dir-paths
+ $* <'print $path.canonicalize(a/b)' >"a$(s)b" : untyped
+ $* <'print $path.canonicalize(a/b/ a/c)' >"a$(s)b$s a$(s)c" : mixed
+}
+
+: normalize
+:
+{
+ $* <'print $normalize([path] a/../b)' >"b" : path
+ $* <'print $normalize([paths] a/../b a/../c)' >"b c" : paths
+ $* <'print $normalize([dir_path] a/../b)' >"b$s" : dir-path
+ $* <'print $normalize([dir_paths] a/../b a/../c/)' >"b$s c$s" : dir-paths
+ $* <'print $path.normalize(a/../b)' >"b" : untyped
+ $* <'print $path.normalize(a/../b/ a/../c)' >"b$s c" : mixed
+}
+
+: try_normalize
+:
+{
+ $* <'print $try_normalize([path] a/../b)' >"b" : valid
+ $* <'print $path.try_normalize($root_directory($src_root)/..)' >"[null]" : invalid
+}
+
+: actualize
+:
+if! $posix
+{
+ mkdir Foo;
+ $* <'print $path.actualize($out_base/foo)' >~'/.+\\Foo/'
+}
+
: sort
:
{
diff --git a/tests/function/string/testscript b/tests/function/string/testscript
index 244ace8..8eb5760 100644
--- a/tests/function/string/testscript
+++ b/tests/function/string/testscript
@@ -25,17 +25,79 @@
}
}
+: contains
+:
+{
+ : basics
+ :
+ {
+ $* <'print $string.contains( abcd, bc)' >'true' : true
+ $* <'print $string.contains( abcd, ac)' >'false' : false
+ $* <'print $contains([string] abcd, cd)' >'true' : typed
+ }
+
+ : icase
+ :
+ {
+ $* <'print $string.contains(aBcD, bC, icase)' >'true' : true
+ }
+
+ : once
+ :
+ {
+ $* <'print $string.contains(abcdabcd, da, once)' >'true' : true
+ $* <'print $string.contains(abcdabcd, bc, once)' >'false' : false
+ }
+}
+
+: starts_with
+:
+{
+ : basics
+ :
+ {
+ $* <'print $string.starts_with( abcd, ab)' >'true' : true
+ $* <'print $string.starts_with( abcd, bc)' >'false' : false
+ $* <'print $starts_with([string] abcd, abcd)' >'true' : typed
+ }
+
+ : icase
+ :
+ {
+ $* <'print $string.starts_with(aBcD, Ab, icase)' >'true' : true
+ }
+}
+
+: ends_with
+:
+{
+ : basics
+ :
+ {
+ $* <'print $string.ends_with( abcd, cd)' >'true' : true
+ $* <'print $string.ends_with( abcd, bc)' >'false' : false
+ $* <'print $ends_with([string] abcd, abcd)' >'true' : typed
+ }
+
+ : icase
+ :
+ {
+ $* <'print $string.ends_with(aBcD, Cd, icase)' >'true' : true
+ }
+}
+
: replace
:
{
: basics
:
{
- $* <'print $string.replace( abcb, b, BB)' >'aBBcBB' : expand
- $* <'print $string.replace( aabbccbb, bb, B)' >'aaBccB' : shrink
- $* <'print $replace([string] abc, b, B)' >'aBc' : typed
- $* <'print $replace([string] "", b, B)' >'' : empty
- $* <'print $replace([string] bbb, b, "")' >'' : to-empty
+ $* <'print $string.replace( abcb, b, BB)' >'aBBcBB' : expand
+ $* <'print $string.replace( aabbccbb, bb, B)' >'aaBccB' : shrink
+ $* <'print $replace([string] abc, b, B)' >'aBc' : typed
+ $* <'print $replace([string] "", b, B)' >'' : empty
+ $* <'print $replace([string] bbb, b, "")' >'' : to-empty
+ $* <'print $replace([string] bb, b, Bb)' >'BbBb' : no-recursion
}
: icase