aboutsummaryrefslogtreecommitdiff
path: root/libbuild2
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2')
-rw-r--r--libbuild2/adhoc-rule-cxx.cxx42
-rw-r--r--libbuild2/algorithm.cxx13
-rw-r--r--libbuild2/buildfile82
-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/predefs-rule.cxx2
-rw-r--r--libbuild2/cc/std.compat.cppm996
-rw-r--r--libbuild2/cc/std.cppm134
-rw-r--r--libbuild2/config/host-config.cxx.in3
-rw-r--r--libbuild2/config/init.cxx18
-rw-r--r--libbuild2/config/operation.cxx8
-rw-r--r--libbuild2/cxx/init.cxx51
-rw-r--r--libbuild2/diagnostics.cxx36
-rw-r--r--libbuild2/dist/operation.cxx2
-rw-r--r--libbuild2/dyndep.cxx10
-rw-r--r--libbuild2/file.cxx11
-rw-r--r--libbuild2/functions-filesystem.cxx51
-rw-r--r--libbuild2/functions-path.cxx438
-rw-r--r--libbuild2/functions-regex.cxx5
-rw-r--r--libbuild2/functions-string.cxx294
-rw-r--r--libbuild2/scheduler.cxx12
-rw-r--r--libbuild2/scope.cxx8
-rw-r--r--libbuild2/scope.hxx8
-rw-r--r--libbuild2/script/parser.cxx14
-rw-r--r--libbuild2/script/parser.hxx9
-rw-r--r--libbuild2/script/run.cxx2
-rw-r--r--libbuild2/script/script.cxx5
-rw-r--r--libbuild2/script/script.hxx10
-rw-r--r--libbuild2/search.cxx27
-rw-r--r--libbuild2/search.hxx2
-rw-r--r--libbuild2/test/script/parser.cxx11
-rw-r--r--libbuild2/variable.hxx15
-rw-r--r--libbuild2/variable.txx35
35 files changed, 2293 insertions, 411 deletions
diff --git a/libbuild2/adhoc-rule-cxx.cxx b/libbuild2/adhoc-rule-cxx.cxx
index 2ac97eb..8a91809 100644
--- a/libbuild2/adhoc-rule-cxx.cxx
+++ b/libbuild2/adhoc-rule-cxx.cxx
@@ -358,6 +358,46 @@ namespace build2
// This way the configuration will be always in sync with ~build2
// and we can update the recipe manually (e.g., for debugging).
//
+ // Should we use ~build2 or ~build2-no-warnings? This case is similar
+ // to private host/module configurations in that the user doesn't have
+ // any control over the options used, etc. So it would be natural to
+ // use the no-warnings variant. However, unlike with tools/modules
+ // which can be configured in a user-created configuration (and which
+ // will normally be the case during development), for recipes it's
+ // always this automatically-create configuration. It feels like the
+ // best we can do is use ~build2-no-warnings by default but switch to
+ // ~build2 if the project is configured for development
+ // (config.<project>.develop).
+ //
+ string cfg;
+ {
+ const project_name& pn (named_project (rs));
+
+ if (!pn.empty ())
+ {
+ string var ("config." + pn.variable () + ".develop");
+
+ if (lookup l = rs[var])
+ {
+ // The value could be untyped if the project didn't declare this
+ // variable. Let's handle that case gracefully.
+ //
+ try
+ {
+ if (convert<bool> (*l))
+ cfg = "~build2";
+ }
+ catch (const invalid_argument& e)
+ {
+ fail << "invalid " << var << " value: " << e;
+ }
+ }
+ }
+
+ if (cfg.empty ())
+ cfg = "~build2-no-warnings";
+ }
+
create_project (
pd,
dir_path (), /* amalgamation */
@@ -366,7 +406,7 @@ namespace build2
{"cxx."}, /* root_modules */
"", /* root_post */
string ("config"), /* config_module */
- string ("config.config.load = ~build2"), /* config_file */
+ "config.config.load = " + cfg, /* config_file */
false, /* buildfile */
"build2 core", /* who */
verbosity); /* verbosity */
diff --git a/libbuild2/algorithm.cxx b/libbuild2/algorithm.cxx
index 62c500d..16f1503 100644
--- a/libbuild2/algorithm.cxx
+++ b/libbuild2/algorithm.cxx
@@ -1282,6 +1282,19 @@ namespace build2
// has no own prerequisites and the group's ones will be matched
// by the group.
}
+ else
+ {
+ // Similar to catch(failed) below.
+ //
+ s.state = target_state::failed;
+ l.offset = target::offset_applied;
+
+ // Make sure we don't relock a failed target.
+ //
+ match_extra& me (s.match_extra);
+ me.cur_options = match_extra::all_options;
+ me.cur_options_.store (me.cur_options, memory_order_relaxed);
+ }
}
else
l.offset = target::offset_tried;
diff --git a/libbuild2/buildfile b/libbuild2/buildfile
index 5ef0006..3518d93 100644
--- a/libbuild2/buildfile
+++ b/libbuild2/buildfile
@@ -73,6 +73,12 @@ libul{build2}: config/{hxx ixx txx cxx}{** -host-config -**.test...} \
# options which could cause spurious rebuilds when we filter out entire
# groups.
#
+# For ~host also filter out config.bin.lib/config.bin.*.lib (static/shared
+# library build/link preferences). In particular, we don't want to force
+# config.bin.lib=shared since that will cause static libraries to link shared
+# versions of their prerequisites (see mysql-client for a case where this can
+# make a difference).
+#
# For ~build2 also filter out config.install.chroot -- we definitely don't
# want it carried through. Also filter out variables that control tests
# execution.
@@ -82,41 +88,91 @@ libul{build2}: config/{hxx ixx txx cxx}{** -host-config -**.test...} \
# on the users of ~host/~build2; they can decide for themselves if they
# want it).
#
-build2_config_lines = [strings]
+# The *_no_warnings variants are with the suppressed C/C++ compiler warnings
+# (in particular, used for private host configuration in bpkg).
+#
+#
host_config_lines = [strings]
+build2_config_lines = [strings]
+
+host_config_no_warnings_lines = [strings]
+build2_config_no_warnings_lines = [strings]
for l: $regex.replace_lines( \
$config.save(), \
'^( *(#|(config\.(test[. ]|dist\.|install\.chroot|config\.hermetic))).*|)$', \
[null])
{
- build2_config_lines += $l
-
# Note: also preserve config.version.
#
+ h = [null]
if $regex.match( \
$l, \
' *config\.(c[. ]|cxx[. ]|cc[.]|bin[.]|config.environment |version ).*')
{
- # Filter out sanitizer options in ~host. We run the toolchain with various
- # sanitizers on CI but sanitizers cause issues in some packages. Note that
- # we can have both -fsanitize and -fno-sanitize forms. For example:
- #
- # -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all
- #
- if $regex.match($l, ' *config\.(c|cxx|cc)\.(coptions|loptions)[ =].*')
+ if! ($regex.match(\
+ $l, \
+ ' *config\.bin\.(lib|exe\.lib|liba\.lib|libs\.lib)[ =].*'))
{
- l = $regex.replace($l, ' ?-f(no-)?sanitize[=-][^ ]+', '')
+ # Filter out sanitizer options in ~host. We run the toolchain with
+ # various sanitizers on CI but sanitizers cause issues in some packages.
+ # Note that we can have both -fsanitize and -fno-sanitize forms. For
+ # example:
+ #
+ # -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all
+ #
+ if $regex.match($l, ' *config\.(c|cxx|cc)\.(coptions|loptions)[ =].*')
+ {
+ h = $regex.replace($l, ' ?-f(no-)?sanitize[=-][^ ]+', '')
+ }
+ else
+ h = $l
}
+ }
+
+ if ($h != [null])
+ host_config_lines += $h
+
+ build2_config_lines += $l
- host_config_lines += $l
+ # Append the warning suppressing option to config.{c,cxx}.coptions rather
+ # than config.cc.coptions since the former could re-enable them.
+ #
+ if ($regex.match($l, ' *config\.(c|cxx)\.coptions[ =].*'))
+ {
+ # Note that in MSVC overriding one warning option (say /W3) with another
+ # (say /w) triggers a warning. However, our compile_rule sanitizes the
+ # command line to resolve such overrides (see msvc_sanitize_cl()).
+ #
+ o = ($cxx.class == 'gcc' ? -w : $cxx.class == 'msvc' ? /w : )
+
+ if ($regex.match($l, '[^=]+= *\[null\] *'))
+ {
+ l = $regex.replace($l, '= *\[null\] *$', "= $o")
+ h = $regex.replace($h, '= *\[null\] *$', "= $o")
+ }
+ else
+ {
+ l = $regex.replace($l, '=(.*)$', "=\\1 $o")
+ h = $regex.replace($h, '=(.*)$', "=\\1 $o")
+ }
}
+
+ if ($h != [null])
+ host_config_no_warnings_lines += $h
+
+ build2_config_no_warnings_lines += $l
}
config/cxx{host-config}: config/in{host-config}
{
- build2_config = $regex.merge($build2_config_lines, '(.+)', '\1\n')
host_config = $regex.merge($host_config_lines, '(.+)', '\1\n')
+ build2_config = $regex.merge($build2_config_lines, '(.+)', '\1\n')
+
+ host_config_no_warnings = $regex.merge($host_config_no_warnings_lines, \
+ '(.+)', '\1\n')
+ build2_config_no_warnings = $regex.merge($build2_config_no_warnings_lines, \
+ '(.+)', '\1\n')
}
libul{build2}: dist/{hxx ixx txx cxx}{** -**.test...}
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/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/config/host-config.cxx.in b/libbuild2/config/host-config.cxx.in
index 9e3e0c2..6b1ce77 100644
--- a/libbuild2/config/host-config.cxx.in
+++ b/libbuild2/config/host-config.cxx.in
@@ -9,5 +9,8 @@ namespace build2
//
extern const char host_config[] = R"###($host_config$)###";
extern const char build2_config[] = R"###($build2_config$)###";
+
+ extern const char host_config_no_warnings[] = R"###($host_config_no_warnings$)###";
+ extern const char build2_config_no_warnings[] = R"###($build2_config_no_warnings$)###";
}
}
diff --git a/libbuild2/config/init.cxx b/libbuild2/config/init.cxx
index 38590ae..2f134c4 100644
--- a/libbuild2/config/init.cxx
+++ b/libbuild2/config/init.cxx
@@ -210,6 +210,9 @@ namespace build2
#ifndef BUILD2_BOOTSTRAP
extern const char host_config[];
extern const char build2_config[];
+
+ extern const char host_config_no_warnings[];
+ extern const char build2_config_no_warnings[];
#endif
bool
@@ -484,14 +487,23 @@ namespace build2
const string& s (f.string ());
- if (s[0] != '~')
+ if (s.empty ())
+ fail << "empty path in config.config.load";
+ else if (s[0] != '~')
load_config_file (f, l);
- else if (s == "~host" || s == "~build2")
+ else if (s == "~host" || s == "~host-no-warnings" ||
+ s == "~build2" || s == "~build2-no-warnings")
{
#ifdef BUILD2_BOOTSTRAP
assert (false);
#else
- istringstream is (s[1] == 'h' ? host_config : build2_config);
+ istringstream is (s[1] == 'h'
+ ? (s.size () == 5
+ ? host_config
+ : host_config_no_warnings)
+ : (s.size () == 7
+ ? build2_config
+ : build2_config_no_warnings));
load_config (is, path_name (s), l);
#endif
}
diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx
index 5983e4b..150bf1a 100644
--- a/libbuild2/config/operation.cxx
+++ b/libbuild2/config/operation.cxx
@@ -759,6 +759,11 @@ namespace build2
lookup l (rs[*c_s]);
if (l && (l.belongs (rs) || l.belongs (ctx.global_scope)))
{
+ const path& f (cast<path> (l));
+
+ if (f.empty ())
+ fail << "empty path in " << *c_s;
+
// While writing the complete configuration seems like a natural
// default, there might be a desire to take inheritance into
// account (if, say, we are exporting at multiple levels). One can
@@ -766,8 +771,7 @@ namespace build2
// still want to support this mode somehow in the future (it seems
// like an override of config.config.persist should do the trick).
//
- save_config (
- rs, cast<path> (l), false /* inherit */, mod, projects);
+ save_config (rs, f, false /* inherit */, mod, projects);
}
}
}
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/dist/operation.cxx b/libbuild2/dist/operation.cxx
index cd88eac..cfc90cf 100644
--- a/libbuild2/dist/operation.cxx
+++ b/libbuild2/dist/operation.cxx
@@ -156,7 +156,7 @@ namespace build2
{
const path& n (e.path ());
- if (n.string ()[0] != '.')
+ if (!n.empty () && n.string ().front () != '.')
try
{
if (e.type () == entry_type::directory) // Can throw.
diff --git a/libbuild2/dyndep.cxx b/libbuild2/dyndep.cxx
index 68260fb..dbeb47e 100644
--- a/libbuild2/dyndep.cxx
+++ b/libbuild2/dyndep.cxx
@@ -442,7 +442,7 @@ namespace build2
// which case return the target that would have been inserted.
//
// The directory is only moved from if insert is true. Note that it must
- // be normalized.
+ // be absolute and normalized.
//
auto find = [&trace, what, &bs, &t,
&map_extension,
@@ -632,7 +632,7 @@ namespace build2
//
// While it may seem like there is not much difference, the caller may
// actually do more than just issue more specific diagnostics. For
- // example, if may defer the failure to the tool diagnostics.
+ // example, it may defer the failure to the tool diagnostics.
//
#if 0
r = &search (t, *tts[0], d, out, n, &e, s);
@@ -642,7 +642,11 @@ namespace build2
r = pk.tk.type->search (ctx, &t, pk);
if (r == nullptr && pk.tk.out->empty ())
- r = &create_new_target (ctx, pk);
+ {
+ auto p (ctx.scopes.find (d, false));
+ if (*p.first != nullptr || ++p.first == p.second)
+ r = &create_new_target (ctx, pk);
+ }
#endif
}
diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx
index c0957ad..18147a2 100644
--- a/libbuild2/file.cxx
+++ b/libbuild2/file.cxx
@@ -859,6 +859,13 @@ namespace build2
for (const dir_entry& de:
dir_iterator (d, dir_iterator::detect_dangling))
{
+ const path& n (de.path ());
+
+ // Skip hidden entries.
+ //
+ if (n.empty () || n.string ().front () == '.')
+ continue;
+
if (de.type () != entry_type::directory)
{
if (de.type () == entry_type::unknown)
@@ -867,13 +874,13 @@ namespace build2
warn << "skipping "
<< (sl ? "dangling symlink" : "inaccessible entry") << ' '
- << d / de.path ();
+ << d / n;
}
continue;
}
- dir_path sd (d / path_cast<dir_path> (de.path ()));
+ dir_path sd (d / path_cast<dir_path> (n));
bool src (false);
optional<bool> altn;
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 a7fcf55..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));
@@ -775,6 +778,8 @@ namespace build2
// If both `format_first_only` and `format_no_copy` flags are specified
// then the result will only contain the replacement of the first match.
//
+ // See also `$string.replace()`.
+ //
f[".replace"] += [](value v, string re, string fmt, optional<names> flags)
{
return replace (move (v), re, fmt, move (flags));
diff --git a/libbuild2/functions-string.cxx b/libbuild2/functions-string.cxx
index 367923f..eccc6c7 100644
--- a/libbuild2/functions-string.cxx
+++ b/libbuild2/functions-string.cxx
@@ -8,6 +8,196 @@ 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)
+ {
+ bool ic (false), fo (false), lo (false);
+ if (fs)
+ {
+ for (name& f: *fs)
+ {
+ string s (convert<string> (move (f)));
+
+ if (s == "icase")
+ ic = true;
+ else if (s == "first_only")
+ fo = true;
+ else if (s == "last_only")
+ lo = true;
+ else
+ throw invalid_argument ("invalid flag '" + s + '\'');
+ }
+ }
+
+ string f (convert<string> (move (fv)));
+ string t (convert<string> (move (tv)));
+
+ if (f.empty ())
+ throw invalid_argument ("empty <from> substring");
+
+ if (!s.empty ())
+ {
+ // Note that we don't cache s.size () since the string size will be
+ // changing as we are replacing. In fact, we may end up with an empty
+ // string after a replacement.
+
+ size_t fn (f.size ());
+
+ if (fo || lo)
+ {
+ size_t p (lo ? rfind (s, s.size (), f, ic) : find (s, 0, f, ic));
+
+ if (fo && lo && p != string::npos)
+ {
+ if (p != find (s, 0, f, ic))
+ p = string::npos;
+ }
+
+ if (p != string::npos)
+ s.replace (p, fn, t);
+ }
+ else
+ {
+ 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);
+ }
+ }
+
+ return move (s);
+ }
+
static size_t
find_index (const strings& vs, value&& v, optional<names>&& fs)
{
@@ -32,7 +222,7 @@ namespace build2
}));
return i != vs.end () ? i - vs.begin () : vs.size ();
- };
+ }
void
string_functions (function_map& m)
@@ -76,6 +266,108 @@ 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>])
+ //
+ // Replace occurences of substring <from> with <to> in a string. The
+ // <from> substring must not be empty.
+ //
+ // The following flags are supported:
+ //
+ // icase - compare ignoring case
+ //
+ // first_only - only replace the first match
+ //
+ // last_only - only replace the last match
+ //
+ //
+ // If both `first_only` and `last_only` flags are specified, then <from>
+ // is replaced only if it occurs in the string once.
+ //
+ // See also `$regex.replace()`.
+ //
+ f["replace"] += [](string s, value f, value t, optional<names> fs)
+ {
+ return replace (move (s), move (f), move (t), move (fs));
+ };
+
+ f[".replace"] += [](names s, value f, value t, optional<names> fs)
+ {
+ return names {
+ name (
+ replace (
+ convert<string> (move (s)), move (f), move (t), move (fs)))};
+ };
+
// $string.trim(<untyped>)
// $trim(<string>)
//
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/scope.cxx b/libbuild2/scope.cxx
index 6ed7bab..23781a8 100644
--- a/libbuild2/scope.cxx
+++ b/libbuild2/scope.cxx
@@ -1205,8 +1205,8 @@ namespace build2
}
auto scope_map::
- find (const dir_path& k) const -> pair<scopes::const_iterator,
- scopes::const_iterator>
+ find (const dir_path& k, bool sno) const -> pair<scopes::const_iterator,
+ scopes::const_iterator>
{
assert (k.normalized (false));
auto i (map_.find_sup (k));
@@ -1215,9 +1215,9 @@ namespace build2
auto b (i->second.begin ());
auto e (i->second.end ());
- // Skip NULL first element.
+ // Skip NULL first element if requested.
//
- if (*b == nullptr)
+ if (sno && *b == nullptr)
++b;
assert (b != e);
diff --git a/libbuild2/scope.hxx b/libbuild2/scope.hxx
index 968727b..09d61e9 100644
--- a/libbuild2/scope.hxx
+++ b/libbuild2/scope.hxx
@@ -793,6 +793,8 @@ namespace build2
// The first element, if not NULL, is for the "owning" out path. The rest
// of the elements are for the src path shallow references.
//
+ // Note that the global scope is in the first element.
+ //
struct scopes: small_vector<scope*, 3>
{
scopes () = default;
@@ -832,6 +834,10 @@ namespace build2
// Find all the scopes that encompass this path (out or src).
//
+ // If skip_null_out is false, then the first element always corresponds to
+ // the out scope and is NULL if there is none (see struct scopes above for
+ // details).
+ //
// Note that the returned range will never be empty (there is always the
// global scope).
//
@@ -864,7 +870,7 @@ namespace build2
// "island append" restriction we have on loading additional buildfile.
//
LIBBUILD2_SYMEXPORT pair<scopes::const_iterator, scopes::const_iterator>
- find (const dir_path&) const;
+ find (const dir_path&, bool skip_null_out = true) const;
const_iterator begin () const {return map_.begin ();}
const_iterator end () const {return map_.end ();}
diff --git a/libbuild2/script/parser.cxx b/libbuild2/script/parser.cxx
index ae6da76..84d2afc 100644
--- a/libbuild2/script/parser.cxx
+++ b/libbuild2/script/parser.cxx
@@ -1134,9 +1134,10 @@ namespace build2
if (t.value == "env")
{
parsed_env r (parse_env_builtin (t, tt));
- c.cwd = move (r.cwd);
- c.variables = move (r.variables);
- c.timeout = r.timeout;
+ c.cwd = move (r.cwd);
+ c.variables = move (r.variables);
+ c.timeout = r.timeout;
+ c.timeout_success = r.timeout_success;
env = true;
}
else if (t.value == "for")
@@ -1601,6 +1602,10 @@ namespace build2
{
r.timeout = chrono::seconds (*v);
}
+ else if (o == "-s" || o == "--timeout-success")
+ {
+ r.timeout_success = true;
+ }
else if (optional<dir_path> v = dir ("--cwd", "-c"))
{
r.cwd = move (*v);
@@ -1615,6 +1620,9 @@ namespace build2
break;
}
+ if (r.timeout_success && !r.timeout)
+ fail (l) << "env: -s|--timeout-success specified without -t|--timeout";
+
// Parse arguments (variable sets).
//
for (; i != e; ++i)
diff --git a/libbuild2/script/parser.hxx b/libbuild2/script/parser.hxx
index cadf909..795ce4e 100644
--- a/libbuild2/script/parser.hxx
+++ b/libbuild2/script/parser.hxx
@@ -163,14 +163,15 @@ namespace build2
pre_parse_line_start (token&, token_type&, lexer_mode);
// Parse the env pseudo-builtin arguments up to the program name. Return
- // the program execution timeout, CWD, the list of the variables that
- // should be unset ("name") and/or set ("name=value") in the command
- // environment, and the token/type that starts the program name. Note
- // that the variable unsets come first, if present.
+ // the program execution timeout and its success flag, CWD, the list of
+ // the variables that should be unset ("name") and/or set ("name=value")
+ // in the command environment, and the token/type that starts the
+ // program name. Note that the variable unsets come first, if present.
//
struct parsed_env
{
optional<duration> timeout;
+ bool timeout_success = false;
optional<dir_path> cwd;
environment_vars variables;
};
diff --git a/libbuild2/script/run.cxx b/libbuild2/script/run.cxx
index 6d73a7e..f8f98c1 100644
--- a/libbuild2/script/run.cxx
+++ b/libbuild2/script/run.cxx
@@ -2069,7 +2069,7 @@ namespace build2
if (c.timeout)
{
- deadline d (system_clock::now () + *c.timeout, false /* success */);
+ deadline d (system_clock::now () + *c.timeout, c.timeout_success);
if (!dl || d < *dl)
dl = d;
}
diff --git a/libbuild2/script/script.cxx b/libbuild2/script/script.cxx
index 4a6ca33..b53fc23 100644
--- a/libbuild2/script/script.cxx
+++ b/libbuild2/script/script.cxx
@@ -425,9 +425,14 @@ namespace build2
// Timeout.
//
if (c.timeout)
+ {
o << " -t "
<< chrono::duration_cast<chrono::seconds> (*c.timeout).count ();
+ if (c.timeout_success)
+ o << " -s";
+ }
+
// CWD.
//
if (c.cwd)
diff --git a/libbuild2/script/script.hxx b/libbuild2/script/script.hxx
index cccad98..f5bd69a 100644
--- a/libbuild2/script/script.hxx
+++ b/libbuild2/script/script.hxx
@@ -331,9 +331,13 @@ namespace build2
process_path program;
strings arguments;
- optional<dir_path> cwd; // From env builtin.
- environment_vars variables; // From env builtin.
- optional<duration> timeout; // From env builtin.
+
+ // These come from the env builtin.
+ //
+ optional<dir_path> cwd;
+ environment_vars variables;
+ optional<duration> timeout;
+ bool timeout_success = false;
optional<redirect> in;
optional<redirect> out;
diff --git a/libbuild2/search.cxx b/libbuild2/search.cxx
index dee2ae8..4e855e3 100644
--- a/libbuild2/search.cxx
+++ b/libbuild2/search.cxx
@@ -265,7 +265,24 @@ namespace build2
//
dir_path d;
if (tk.dir->absolute ())
+ {
d = *tk.dir; // Already normalized.
+
+ // Even if out is empty, it may still be (only) in src.
+ //
+ // Note: issue diagnostics consistent with search() after skipping this
+ // function due to non-empty out.
+ //
+ // @@ PERF: we could first check if it's in pk.scope, which feels like
+ // the common case. Though this doesn't seem to affect
+ // performance in any noticeable way.
+ //
+ auto p (ctx.scopes.find (d, false)); // Note: never empty.
+ if (*p.first == nullptr && ++p.first != p.second)
+ {
+ fail << "no existing source file for prerequisite " << pk << endf;
+ }
+ }
else
{
d = pk.scope->out_path ();
@@ -313,7 +330,17 @@ namespace build2
//
dir_path d;
if (tk.dir->absolute ())
+ {
d = *tk.dir; // Already normalized.
+
+ // As above.
+ //
+ auto p (ctx.scopes.find (d, false));
+ if (*p.first == nullptr && ++p.first != p.second)
+ {
+ fail << "no existing source file for prerequisite " << pk << endf;
+ }
+ }
else
{
d = pk.scope->out_path ();
diff --git a/libbuild2/search.hxx b/libbuild2/search.hxx
index e3b1442..198c65f 100644
--- a/libbuild2/search.hxx
+++ b/libbuild2/search.hxx
@@ -37,6 +37,8 @@ namespace build2
// Create a new target in this prerequisite's scope.
//
+ // Fail if the target is in src directory.
+ //
LIBBUILD2_SYMEXPORT const target&
create_new_target (context&, const prerequisite_key&);
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/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)
{