aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build2/b.cxx16
-rw-r--r--doc/manual.cli4
-rw-r--r--libbuild2/bin/init.cxx9
-rw-r--r--libbuild2/c/init.cxx10
-rw-r--r--libbuild2/cc/common.cxx124
-rw-r--r--libbuild2/cc/common.hxx26
-rw-r--r--libbuild2/cc/compile-rule.cxx471
-rw-r--r--libbuild2/cc/compile-rule.hxx5
-rw-r--r--libbuild2/cc/guess.cxx176
-rw-r--r--libbuild2/cc/guess.hxx10
-rw-r--r--libbuild2/cc/init.cxx8
-rw-r--r--libbuild2/cc/link-rule.cxx42
-rw-r--r--libbuild2/cc/module.cxx135
-rw-r--r--libbuild2/cc/module.hxx11
-rw-r--r--libbuild2/cc/pkgconfig.cxx220
-rw-r--r--libbuild2/cc/types.cxx189
-rw-r--r--libbuild2/cc/types.hxx76
-rw-r--r--libbuild2/cc/utility.cxx53
-rw-r--r--libbuild2/cc/utility.hxx26
-rw-r--r--libbuild2/cc/windows-rpath.cxx12
-rw-r--r--libbuild2/config/module.hxx4
-rw-r--r--libbuild2/config/operation.cxx4
-rw-r--r--libbuild2/context.cxx4
-rw-r--r--libbuild2/cxx/init.cxx132
-rw-r--r--libbuild2/dist/operation.cxx4
-rw-r--r--libbuild2/dump.cxx17
-rw-r--r--libbuild2/file.cxx48
-rw-r--r--libbuild2/file.hxx2
-rw-r--r--libbuild2/function.hxx3
-rw-r--r--libbuild2/install/utility.hxx4
-rw-r--r--libbuild2/module.hxx4
-rw-r--r--libbuild2/parser.cxx22
-rw-r--r--libbuild2/parser.hxx2
-rw-r--r--libbuild2/rule-map.hxx4
-rw-r--r--libbuild2/scope.cxx59
-rw-r--r--libbuild2/scope.hxx77
-rw-r--r--libbuild2/script/regex.hxx15
-rw-r--r--libbuild2/script/run.cxx1
-rw-r--r--libbuild2/search.cxx2
-rw-r--r--libbuild2/target-key.hxx1
-rw-r--r--libbuild2/target-type.hxx6
-rw-r--r--libbuild2/test/script/parser.hxx1
-rw-r--r--libbuild2/types.hxx3
-rw-r--r--libbuild2/utility.hxx8
-rw-r--r--libbuild2/variable.cxx26
-rw-r--r--libbuild2/variable.hxx161
-rw-r--r--libbuild2/variable.ixx65
-rw-r--r--libbuild2/variable.txx546
-rw-r--r--libbuild2/version/module.hxx4
-rw-r--r--tests/cc/modules/headers.testscript8
-rw-r--r--tests/cc/modules/modules.testscript3
51 files changed, 2171 insertions, 692 deletions
diff --git a/build2/b.cxx b/build2/b.cxx
index 9a924d6..ecf8c7f 100644
--- a/build2/b.cxx
+++ b/build2/b.cxx
@@ -1151,7 +1151,7 @@ main (int argc, char* argv[])
// use to the bootstrap files (other than src-root.build, which,
// BTW, doesn't need to exist if src_root == out_root).
//
- scope& rs (create_root (*ctx, out_root, src_root)->second);
+ scope& rs (*create_root (*ctx, out_root, src_root)->second.scope);
bool bstrapped (bootstrapped (rs));
@@ -1561,9 +1561,10 @@ main (int argc, char* argv[])
// If we have a directory, enter the scope, similar to how we do
// it in the context ctor.
//
- scope& s (o.dir
- ? sm.insert ((out_base / *o.dir).normalize ())->second
- : *rs.weak_scope ());
+ scope& s (
+ o.dir
+ ? *sm.insert ((out_base / *o.dir).normalize ())->second.scope
+ : *rs.weak_scope ());
auto p (s.vars.insert (o.ovr));
@@ -1581,9 +1582,10 @@ main (int argc, char* argv[])
if (o.ovr.visibility == variable_visibility::global)
continue;
- scope& s (o.dir
- ? sm.insert ((out_base / *o.dir).normalize ())->second
- : rs);
+ scope& s (
+ o.dir
+ ? *sm.insert ((out_base / *o.dir).normalize ())->second.scope
+ : rs);
auto p (s.vars.insert (o.ovr));
diff --git a/doc/manual.cli b/doc/manual.cli
index d7cf92f..e09aef8 100644
--- a/doc/manual.cli
+++ b/doc/manual.cli
@@ -6552,8 +6552,8 @@ config.cxx.aoptions
config.cxx.libs
cxx.libs
-config.cxx.translatable_headers
- cxx.translatable_headers
+config.cxx.translate_include
+ cxx.translate_include
\
diff --git a/libbuild2/bin/init.cxx b/libbuild2/bin/init.cxx
index ff00e89..51066cb 100644
--- a/libbuild2/bin/init.cxx
+++ b/libbuild2/bin/init.cxx
@@ -3,8 +3,6 @@
#include <libbuild2/bin/init.hxx>
-#include <map>
-
#include <libbuild2/scope.hxx>
#include <libbuild2/function.hxx>
#include <libbuild2/variable.hxx>
@@ -144,8 +142,11 @@ namespace build2
vp.insert<string> ("bin.lib.load_suffix");
vp.insert<string> ("bin.lib.load_suffix_pattern");
- vp.insert<map<string, string>> ("bin.lib.version");
- vp.insert<string> ("bin.lib.version_pattern");
+ // @@ TMP: update bdep-new generated projects, documentation not to use
+ // @ for platform-independent version.
+ //
+ vp.insert<map<optional<string>, string>> ("bin.lib.version");
+ vp.insert<string> ("bin.lib.version_pattern");
return true;
}
diff --git a/libbuild2/c/init.cxx b/libbuild2/c/init.cxx
index 227afb2..afd8f88 100644
--- a/libbuild2/c/init.cxx
+++ b/libbuild2/c/init.cxx
@@ -175,7 +175,7 @@ namespace build2
vp.insert<strings> ("config.c.loptions"),
vp.insert<strings> ("config.c.aoptions"),
vp.insert<strings> ("config.c.libs"),
- nullptr /* config.c.translatable_headers */,
+ nullptr /* config.c.translate_include */,
vp.insert<process_path_ex> ("c.path"),
vp.insert<strings> ("c.mode"),
@@ -192,7 +192,7 @@ namespace build2
vp.insert<strings> ("c.aoptions"),
vp.insert<strings> ("c.libs"),
- nullptr /* c.translatable_headers */,
+ nullptr /* c.translate_include */,
vp["cc.poptions"],
vp["cc.coptions"],
@@ -220,6 +220,7 @@ namespace build2
vp["cc.type"],
vp["cc.system"],
vp["cc.module_name"],
+ vp["cc.importable"],
vp["cc.reprocess"],
vp.insert<string> ("c.preprocessed"), // See cxx.preprocessed.
@@ -259,7 +260,8 @@ namespace build2
// Alias some cc. variables as c.
//
- vp.insert_alias (d.c_runtime, "c.runtime");
+ vp.insert_alias (d.c_runtime, "c.runtime");
+ vp.insert_alias (d.c_importable, "c.importable");
auto& m (extra.set_module (new config_module (move (d))));
m.guess (rs, loc, extra.hints);
@@ -363,7 +365,7 @@ namespace build2
};
auto& m (extra.set_module (new module (move (d))));
- m.init (rs, loc, extra.hints);
+ m.init (rs, loc, extra.hints, *cm.x_info);
return true;
}
diff --git a/libbuild2/cc/common.cxx b/libbuild2/cc/common.cxx
index 9cac1b0..f307b77 100644
--- a/libbuild2/cc/common.cxx
+++ b/libbuild2/cc/common.cxx
@@ -46,31 +46,42 @@ namespace build2
// The first argument to proc_lib is a pointer to the last element of an
// array that contains the current library dependency chain all the way to
// the library passed to process_libraries(). The first element of this
- // array is NULL.
+ // array is NULL. If this argument is NULL, then this is a library without
+ // a target (e.g., -lpthread) and its name is in the second argument.
+ //
+ // If proc_impl always returns false (that is, we are only interested in
+ // interfaces), then top_li can be absent. This makes process_libraries()
+ // not to pick the liba/libs{} member for installed libraries instead
+ // passing the lib{} group itself. This can be used to match the semantics
+ // of file_rule which, when matching prerequisites, does not pick the
+ // liba/libs{} member (naturally) but just matches the lib{} group.
+ //
+ // Note that if top_li is present, then the target passed to proc_impl,
+ // proc_lib, and proc_opt is always a file.
//
void common::
process_libraries (
action a,
const scope& top_bs,
- linfo top_li,
+ optional<linfo> top_li,
const dir_paths& top_sysd,
- const file& l,
+ const mtime_target& l, // liba/libs{} or lib{}
bool la,
lflags lf,
- const function<bool (const file&,
+ const function<bool (const target&,
bool la)>& proc_impl, // Implementation?
- const function<void (const file* const*, // Can be NULL.
+ const function<void (const target* const*, // Can be NULL.
const string& path, // Library path.
lflags, // Link flags.
bool sys)>& proc_lib, // True if system library.
- const function<void (const file&,
+ const function<void (const target&,
const string& type, // cc.type
bool com, // cc. or x.
bool exp)>& proc_opt, // *.export.
bool self /*= false*/, // Call proc_lib on l?
- small_vector<const file*, 16>* chain) const
+ small_vector<const target*, 16>* chain) const
{
- small_vector<const file*, 16> chain_storage;
+ small_vector<const target*, 16> chain_storage;
if (chain == nullptr)
{
chain = &chain_storage;
@@ -193,7 +204,8 @@ namespace build2
// stub the path to the DLL may not be known and so the path will be
// empty (but proc_lib() will use the import stub).
//
- const path& p (l.path ());
+ const file* f;
+ const path& p ((f = l.is_a<file> ()) ? f->path () : empty_path);
bool s (t != nullptr // If cc library (matched or imported).
? cast_false<bool> (l.vars[c_system])
@@ -203,7 +215,7 @@ namespace build2
}
const scope& bs (t == nullptr || cc ? top_bs : l.base_scope ());
- optional<linfo> li; // Calculate lazily.
+ optional<optional<linfo>> li; // Calculate lazily.
const dir_paths* sysd (nullptr); // Resolve lazily.
// Find system search directories corresponding to this library, i.e.,
@@ -225,7 +237,7 @@ namespace build2
{
li = (t == nullptr || cc)
? top_li
- : link_info (bs, link_type (l).type);
+ : optional<linfo> (link_info (bs, link_type (l).type));
};
// Only go into prerequisites (implementation) if instructed and we are
@@ -234,6 +246,8 @@ namespace build2
//
if (impl && !c_e_libs.defined () && !x_e_libs.defined ())
{
+ assert (top_li); // Must pick a member if implementation (see above).
+
for (const prerequisite_target& pt: l.prerequisite_targets[a])
{
// Note: adhoc prerequisites are not part of the library metadata
@@ -303,7 +317,7 @@ namespace build2
{
// This is something like -lpthread or shell32.lib so should be
// a valid path. But it can also be an absolute library path
- // (e.g., something that may come from our .static/shared.pc
+ // (e.g., something that may come from our .{static/shared}.pc
// files).
//
if (proc_lib)
@@ -316,7 +330,7 @@ namespace build2
if (sysd == nullptr) find_sysd ();
if (!li) find_linfo ();
- const file& t (
+ const mtime_target& t (
resolve_library (a,
bs,
n,
@@ -335,9 +349,22 @@ namespace build2
// on Windows import-installed DLLs may legally have empty
// paths.
//
- if (t.mtime () == timestamp_unknown)
+ const char* w (nullptr);
+ if (t.ctx.phase == run_phase::match)
+ {
+ size_t o (t.state[a].task_count.load (memory_order_consume) -
+ t.ctx.count_base ());
+
+ if (o != target::offset_applied &&
+ o != target::offset_executed)
+ w = "not matched";
+ }
+ else if (t.mtime () == timestamp_unknown)
+ w = "out of date";
+
+ if (w != nullptr)
fail << (impl ? "implementation" : "interface")
- << " dependency " << t << " is out of date" <<
+ << " dependency " << t << " is " << w <<
info << "mentioned in *.export." << (impl ? "imp_" : "")
<< "libs of target " << l <<
info << "is it a prerequisite of " << l << "?";
@@ -436,12 +463,16 @@ namespace build2
// will select exactly the same target as the library's matched rule and
// that's the only way to guarantee it will be up-to-date.
//
- const file& common::
+ // If li is absent, then don't pick the liba/libs{} member, returning the
+ // lib{} target itself. If li is present, then the returned target is
+ // always a file.
+ //
+ const mtime_target& common::
resolve_library (action a,
const scope& s,
const name& cn,
const dir_path& out,
- linfo li,
+ optional<linfo> li,
const dir_paths& sysd,
optional<dir_paths>& usrd) const
{
@@ -478,12 +509,16 @@ namespace build2
fail << "unable to find library " << pk;
}
- // If this is lib{}/libu*{}, pick appropriate member.
+ // If this is lib{}/libu*{}, pick appropriate member unless we were
+ // instructed not to.
//
- if (const libx* l = xt->is_a<libx> ())
- xt = link_member (*l, a, li); // Pick lib*{e,a,s}{}.
+ if (li)
+ {
+ if (const libx* l = xt->is_a<libx> ())
+ xt = link_member (*l, a, *li); // Pick lib*{e,a,s}{}.
+ }
- return xt->as<file> ();
+ return xt->as<mtime_target> ();
}
// Note that pk's scope should not be NULL (even if dir is absolute).
@@ -855,6 +890,24 @@ namespace build2
return l;
};
+ // Mark as a "cc" library (unless already marked) and set the system
+ // flag.
+ //
+ auto mark_cc = [sys, this] (target& t) -> bool
+ {
+ auto p (t.vars.insert (c_type));
+
+ if (p.second)
+ {
+ p.first = string ("cc");
+
+ if (sys)
+ t.vars.assign (c_system) = true;
+ }
+
+ return p.second;
+ };
+
target_lock ll (lock (lt));
// Set lib{} group members to indicate what's available. Note that we
@@ -866,6 +919,11 @@ namespace build2
{
if (s != nullptr) {lt->s = s; mt = s->mtime ();}
if (a != nullptr) {lt->a = a; mt = a->mtime ();}
+
+ // Mark the group since sometimes we use it itself instead of one of
+ // the liba/libs{} members (see process_libraries() for details).
+ //
+ mark_cc (*lt);
}
target_lock al (lock (a));
@@ -877,24 +935,6 @@ namespace build2
if (a != nullptr) a->group = lt;
if (s != nullptr) s->group = lt;
- // Mark as a "cc" library (unless already marked) and set the system
- // flag.
- //
- auto mark_cc = [sys, this] (target& t) -> bool
- {
- auto p (t.vars.insert (c_type));
-
- if (p.second)
- {
- p.first.get () = string ("cc");
-
- if (sys)
- t.vars.assign (c_system) = true;
- }
-
- return p.second;
- };
-
// If the library already has cc.type, then assume it was either
// already imported or was matched by a rule.
//
@@ -938,7 +978,7 @@ namespace build2
strings o;
o.push_back (move (d));
- p.first.get () = move (o);
+ p.first = move (o);
}
}
};
@@ -950,6 +990,10 @@ namespace build2
// idea is that in .pc files that we generate, we copy those macros
// (or custom ones) from *.export.poptions.
//
+ // @@ Should we add .pc files as ad hoc members so pkconfig_save() can
+ // use their names when deriving -l-names (this would be expecially
+ // helpful for binless libraries to get hold of prefix/suffix, etc).
+ //
if (pc.first.empty () && pc.second.empty ())
{
if (!pkgconfig_load (act, *p.scope,
diff --git a/libbuild2/cc/common.hxx b/libbuild2/cc/common.hxx
index f072591..612d081 100644
--- a/libbuild2/cc/common.hxx
+++ b/libbuild2/cc/common.hxx
@@ -64,7 +64,7 @@ namespace build2
const variable& config_x_loptions;
const variable& config_x_aoptions;
const variable& config_x_libs;
- const variable* config_x_translatable_headers;
+ const variable* config_x_translate_include;
const variable& x_path; // Compiler process path.
const variable& x_mode; // Compiler mode options.
@@ -79,7 +79,7 @@ namespace build2
const variable& x_loptions;
const variable& x_aoptions;
const variable& x_libs;
- const variable* x_translatable_headers;
+ const variable* x_translate_include;
const variable& c_poptions; // cc.*
const variable& c_coptions;
@@ -107,6 +107,7 @@ namespace build2
const variable& c_type; // cc.type
const variable& c_system; // cc.system
const variable& c_module_name; // cc.module_name
+ const variable& c_importable; // cc.importable
const variable& c_reprocess; // cc.reprocess
const variable& x_preprocessed; // x.preprocessed
@@ -172,8 +173,7 @@ namespace build2
bool modules; // x.features.modules
bool symexport; // x.features.symexport
- const strings* xlate_hdr; // x.translatable_headers (NULL if
- // unused/empty).
+ build2::cc::importable_headers* importable_headers;
// The order of sys_*_dirs is the mode entries first, followed by the
// compiler built-in entries, and finished off with any extra entries
@@ -252,7 +252,7 @@ namespace build2
ctgt (tgt), tsys (ctgt.system), tclass (ctgt.class_),
modules (fm),
symexport (fs),
- xlate_hdr (nullptr),
+ importable_headers (nullptr),
sys_lib_dirs (sld), sys_inc_dirs (sid), sys_mod_dirs (smd),
sys_lib_dirs_mode (slm), sys_inc_dirs_mode (sim),
sys_mod_dirs_mode (smm),
@@ -272,16 +272,16 @@ namespace build2
process_libraries (
action,
const scope&,
- linfo,
+ optional<linfo>,
const dir_paths&,
- const file&,
+ const mtime_target&,
bool,
lflags,
- const function<bool (const file&, bool)>&,
- const function<void (const file* const*, const string&, lflags, bool)>&,
- const function<void (const file&, const string&, bool, bool)>&,
+ const function<bool (const target&, bool)>&,
+ const function<void (const target* const*, const string&, lflags, bool)>&,
+ const function<void (const target&, const string&, bool, bool)>&,
bool = false,
- small_vector<const file*, 16>* = nullptr) const;
+ small_vector<const target*, 16>* = nullptr) const;
const target*
search_library (action a,
@@ -308,12 +308,12 @@ namespace build2
}
public:
- const file&
+ const mtime_target&
resolve_library (action,
const scope&,
const name&,
const dir_path&,
- linfo,
+ optional<linfo>,
const dir_paths&,
optional<dir_paths>&) const;
diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx
index b96c39d..116f67b 100644
--- a/libbuild2/cc/compile-rule.cxx
+++ b/libbuild2/cc/compile-rule.cxx
@@ -413,11 +413,13 @@ namespace build2
// See through utility libraries.
//
- auto imp = [] (const file& l, bool la) {return la && l.is_a<libux> ();};
+ auto imp = [] (const target& l, bool la) {return la && l.is_a<libux> ();};
- auto opt = [&d, this] (const file& l,
+ auto opt = [&d, this] (const target& lt,
const string& t, bool com, bool exp)
{
+ const file& l (lt.as<file> ());
+
// Note that in our model *.export.poptions are always "interface",
// even if set on liba{}/libs{}, unlike loptions.
//
@@ -450,7 +452,7 @@ namespace build2
};
process_libraries (a, bs, li, sys_lib_dirs,
- l, la, 0, // Hack: lflags unused.
+ l, la, 0, // lflags unused.
imp, nullptr, opt);
}
@@ -504,10 +506,10 @@ namespace build2
target& t,
linfo li) const
{
- auto imp = [] (const file& l, bool la) {return la && l.is_a<libux> ();};
+ auto imp = [] (const target& l, bool la) {return la && l.is_a<libux> ();};
auto opt = [&m, this] (
- const file& l, const string& t, bool com, bool exp)
+ const target& l, const string& t, bool com, bool exp)
{
if (!exp)
return;
@@ -524,8 +526,8 @@ namespace build2
// The same logic as in append_library_options().
//
- const function<bool (const file&, bool)> impf (imp);
- const function<void (const file&, const string&, bool, bool)> optf (opt);
+ const function<bool (const target&, bool)> impf (imp);
+ const function<void (const target&, const string&, bool, bool)> optf (opt);
for (prerequisite_member p: group_prerequisite_members (a, t))
{
@@ -544,7 +546,7 @@ namespace build2
continue;
process_libraries (a, bs, li, sys_lib_dirs,
- pt->as<file> (), la, 0, // Hack: lflags unused.
+ pt->as<file> (), la, 0, // lflags unused.
impf, nullptr, optf);
}
}
@@ -759,8 +761,8 @@ namespace build2
continue;
// A dependency on a library is there so that we can get its
- // *.export.poptions, modules, etc. This is the library metadata
- // protocol. See also append_library_options().
+ // *.export.poptions, modules, importable headers, etc. This is the
+ // library metadata protocol. See also append_library_options().
//
if (pi == include_type::normal &&
(p.is_a<libx> () ||
@@ -773,8 +775,8 @@ namespace build2
// Handle (phase two) imported libraries. We know that for such
// libraries we don't need to do match() in order to get options
// (if any, they would be set by search_library()). But we do need
- // to match it if we may need its modules (see search_modules()
- // for details).
+ // to match it if we may need its modules or importable headers
+ // (see search_modules(), make_header_sidebuild() for details).
//
if (p.proj ())
{
@@ -943,9 +945,30 @@ namespace build2
if (ut == unit_type::module_intf) // Note: still unrefined.
cs.append (&md.symexport, sizeof (md.symexport));
- if (xlate_hdr != nullptr)
- append_options (cs, *xlate_hdr);
-
+ // If we track translate_include then we should probably also track
+ // the cc.importable flag for each header we include, which would be
+ // quite heavy-handed indeed. Or maybe we shouldn't bother with this
+ // at all: after all include translation is an optimization so why
+ // rebuild an otherwise up-to-date target?
+ //
+#if 0
+ if (modules)
+ {
+ // While there is also the companion importable_headers map, it's
+ // unlikely to change in a way that affects us without changes to
+ // other things that we track (e.g., compiler version, etc).
+ //
+ if (const auto* v = cast_null<translatable_headers> (
+ t[x_translate_include]))
+ {
+ for (const auto& p: *v)
+ {
+ cs.append (p.first);
+ cs.append (!p.second || *p.second);
+ }
+ }
+ }
+#endif
if (md.pp != preprocessed::all)
{
append_options (cs, t, x_poptions);
@@ -1813,6 +1836,10 @@ namespace build2
size_t header_units = 0; // Number of header units imported.
module_imports& imports; // Unused (potentially duplicate suppression).
+ // Include translation (looked up lazily).
+ //
+ optional<const build2::cc::translatable_headers*> translatable_headers;
+
small_vector<string, 2> batch; // Reuse buffers.
module_mapper_state (size_t s, module_imports& i)
@@ -2094,21 +2121,84 @@ namespace build2
// Now handle INCLUDE and IMPORT differences.
//
- const string& hp (ht->path ().string ());
+ const path& hp (ht->path ());
+ const string& hs (hp.string ());
// Reduce include translation to the import case.
//
- if (!imp && xlate_hdr != nullptr)
+ if (!imp)
{
- auto i (lower_bound (
- xlate_hdr->begin (), xlate_hdr->end (),
- hp,
- [] (const string& x, const string& y)
- {
- return path_traits::compare (x, y) < 0;
- }));
+ if (!st.translatable_headers)
+ st.translatable_headers =
+ cast_null<translatable_headers> (t[x_translate_include]);
+
+ if (*st.translatable_headers != nullptr)
+ {
+ auto& ths (**st.translatable_headers);
+
+ // First look for the header path in the translatable headers
+ // itself.
+ //
+ auto i (ths.find (hs)), ie (ths.end ());
+
+ // Next look it up in the importable headers and then look up
+ // the associated groups in the translatable headers.
+ //
+ if (i == ie)
+ {
+ slock l (importable_headers->mutex);
+ auto& ihs (importable_headers->header_map);
+
+ auto j (ihs.find (hp)), je (ihs.end ());
+
+ if (j != je)
+ {
+ // The groups are ordered from the most to least specific.
+ //
+ for (const string& g: j->second)
+ if ((i = ths.find (g)) != ie)
+ break;
+ }
+
+ // Finally look for the `all` groups.
+ //
+ if (i == ie)
+ {
+ i = ths.find (header_group_all_importable);
+
+ if (i != ie)
+ {
+ // See if this header is marked as importable.
+ //
+ if (lookup l = (*ht)[c_importable])
+ {
+ if (!cast<bool> (l))
+ i = ie;
+ }
+ else if (j != je)
+ {
+ // See if this is one of ad hoc *-importable groups
+ // (currently only std-importable).
+ //
+ const auto& gs (j->second);
+ if (find (gs.begin (),
+ gs.end (),
+ header_group_std_importable) == gs.end ())
+ i = ie;
+ }
+ else
+ i = ie;
+ }
- imp = (i != xlate_hdr->end () && *i == hp);
+ if (i == ie)
+ i = ths.find (header_group_all);
+ }
+ }
+
+ // Translate if we found an entry and it's not false.
+ //
+ imp = (i != ie && (!i->second || *i->second));
+ }
}
if (imp)
@@ -2118,7 +2208,7 @@ namespace build2
// Synthesize the BMI dependency then update and add the BMI
// target as a prerequisite.
//
- const file& bt (make_header_sidebuild (a, bs, li, *ht));
+ const file& bt (make_header_sidebuild (a, bs, t, li, *ht));
if (!skip)
{
@@ -2151,7 +2241,8 @@ namespace build2
}
catch (const failed&)
{
- r = "ERROR 'unable to update header unit "; r += hp; r += '\'';
+ r = "ERROR 'unable to update header unit for ";
+ r += hs; r += '\'';
continue;
}
}
@@ -2160,7 +2251,7 @@ namespace build2
if (skip)
st.skip--;
else
- dd.expect (hp);
+ dd.expect (hs);
// Confusingly, TRUE means include textually and FALSE means we
// don't know.
@@ -2611,7 +2702,7 @@ namespace build2
// Synthesize the BMI dependency then update and add the BMI
// target as a prerequisite.
//
- const file& bt (make_header_sidebuild (a, bs, li, *ht));
+ const file& bt (make_header_sidebuild (a, bs, t, li, *ht));
if (!skip)
{
@@ -2722,18 +2813,11 @@ namespace build2
if (!e.empty ())
n.resize (n.size () - e.size () - 1); // One for the dot.
- // See if this directory is part of any project out_root hierarchy and
- // if so determine the target type.
+ // See if this directory is part of any project and if so determine
+ // the target type.
//
- // Note that this will miss all the headers that come from src_root
- // (so they will be treated as generic C headers below). Generally, we
- // don't have the ability to determine that some file belongs to
- // src_root of some project. But that's not a problem for our
- // purposes: it is only important for us to accurately determine
- // target types for headers that could be auto-generated.
- //
- // While at it also try to determine if this target is from the src or
- // out tree of said project.
+ // While at it also determine if this target is from the src or out
+ // tree of said project.
//
dir_path out;
@@ -2749,7 +2833,7 @@ namespace build2
{
tts = map_extension (bs, n, e);
- if (bs.out_path () != bs.src_path () && d.sub (bs.src_path ()))
+ if (!bs.out_eq_src () && d.sub (bs.src_path ()))
out = out_src (d, *rs);
}
@@ -2925,82 +3009,12 @@ namespace build2
}
else
{
- // We used to just normalize the path but that could result in an
- // invalid path (e.g., for some system/compiler headers on CentOS 7
- // with Clang 3.4) because of the symlinks (if a directory component
- // is a symlink, then any following `..` are resolved relative to the
- // target; see path::normalize() for background).
- //
- // Initially, to fix this, we realized (i.e., realpath(3)) it instead.
- // But that turned out also not to be quite right since now we have
- // all the symlinks resolved: conceptually it feels correct to keep
- // the original header names since that's how the user chose to
- // arrange things and practically this is how the compilers see/report
- // them (e.g., the GCC module mapper).
- //
- // So now we have a pretty elaborate scheme where we try to use the
- // normalized path if possible and fallback to realized. Normalized
- // paths will work for situations where `..` does not cross symlink
- // boundaries, which is the sane case. And for the insane case we only
- // really care about out-of-project files (i.e., system/compiler
- // headers). In other words, if you have the insane case inside your
- // project, then you are on your own.
- //
- // All of this is unless the path comes from the depdb, in which case
+ // Normalize the path unless it comes from the depdb, in which case
// we've already done that (normally). This is also where we handle
// src-out remap (again, not needed if cached).
//
if (!cache || norm)
- {
- // Interestingly, on most paltforms and with most compilers (Clang
- // on Linux being a notable exception) most system/compiler headers
- // are already normalized.
- //
- path_abnormality a (f.abnormalities ());
- if (a != path_abnormality::none)
- {
- // While we can reasonably expect this path to exit, things do go
- // south from time to time (like compiling under wine with file
- // wlantypes.h included as WlanTypes.h).
- //
- try
- {
- // If we have any parent components, then we have to verify the
- // normalized path matches realized.
- //
- path r;
- if ((a & path_abnormality::parent) == path_abnormality::parent)
- {
- r = f;
- r.realize ();
- }
-
- try
- {
- f.normalize ();
-
- // Note that we might still need to resolve symlinks in the
- // normalized path.
- //
- if (!r.empty () && f != r && path (f).realize () != r)
- f = move (r);
- }
- catch (const invalid_path&)
- {
- assert (!r.empty ()); // Shouldn't have failed if no `..`.
- f = move (r); // Fallback to realize.
- }
- }
- catch (const invalid_path&)
- {
- fail << "invalid header path '" << f.string () << "'";
- }
- catch (const system_error& e)
- {
- fail << "invalid header path '" << f.string () << "': " << e;
- }
- }
- }
+ normalize_header (f);
if (!cache)
{
@@ -3927,7 +3941,7 @@ namespace build2
//
if (inject_header (a, t, *ht, mt, false /* fail */))
{
- const file& bt (make_header_sidebuild (a, bs, li, *ht));
+ const file& bt (make_header_sidebuild (a, bs, t, li, *ht));
// It doesn't look like we need the cache semantics here since given
// the header, we should be able to build its BMI. In other words, a
@@ -5234,7 +5248,7 @@ namespace build2
//
// In the above examples one common theme about all the file names is
// that they contain, in one form or another, the "tail" of the module
- // name ('core'). So what we are going to do is require that, within a
+ // name (`core`). So what we are going to do is require that, within a
// pool (library, executable), the interface file names contain enough
// of the module name tail to unambiguously resolve all the module
// imports. On our side we are going to implement a "fuzzy" module name
@@ -5253,7 +5267,7 @@ namespace build2
// abstract-window.mxx: which one is likely to define this module?
// Clearly the first, but in the above-described scheme they will get
// the same score. More generally, consider these "obvious" (to the
- // human) situations:
+ // human, that is) situations:
//
// window.mxx vs abstract-window.mxx
// details/window.mxx vs abstract-window.mxx
@@ -5262,13 +5276,17 @@ namespace build2
// To handle such cases we are going to combine the above primary score
// with the following secondary scores (in that order):
//
- // a) Strength of separation between matched and unmatched parts:
+ // A) Strength of separation between matched and unmatched parts:
//
// '\0' > directory separator > other separator > unseparated
//
// Here '\0' signifies nothing to separate (unmatched part is empty).
//
- // b) Shortness of the unmatched part.
+ // B) Shortness of the unmatched part.
+ //
+ // Finally, for the fuzzy match we require a complete match of the last
+ // module (or partition) component. Failed that, we will match `format`
+ // to `print` because the last character (`t`) is the same.
//
// For std.* modules we only accept non-fuzzy matches (think std.core vs
// some core.mxx). And if such a module is unresolved, then we assume it
@@ -5285,8 +5303,12 @@ namespace build2
//
// PPPPABBBB
//
- // We use decimal instead of binary packing to make it easier to
- // separate fields in the trace messages, during debugging, etc.
+ // Where PPPP is the primary score, A is the A) score, and BBBB is
+ // the B) scope described above. Zero signifies no match.
+ //
+ // We use decimal instead of binary packing to make it easier for the
+ // human to separate fields in the trace messages, during debugging,
+ // etc.
//
return m.size () * 100000 + 99999; // Maximum match score.
};
@@ -5309,6 +5331,8 @@ namespace build2
(ucase (c1) == c1) != (ucase (c2) == c2));
};
+ auto mod_sep = [] (char c) {return c == '.' || c == ':';};
+
size_t fn (f.size ()), fi (fn);
size_t mn (m.size ()), mi (mn);
@@ -5318,6 +5342,10 @@ namespace build2
bool fsep (false);
bool msep (false);
+ // We require complete match of at least last module component.
+ //
+ bool match (false);
+
// Scan backwards for as long as we match. Keep track of the previous
// character for case change detection.
//
@@ -5343,11 +5371,12 @@ namespace build2
// FOObar
//
bool fs (char_sep (fc));
- bool ms (mc == '_' || mc == '.' || mc == ':');
+ bool ms (mod_sep (mc) || mc == '_');
if (fs && ms)
{
fsep = msep = true;
+ match = match || mod_sep (mc);
continue;
}
@@ -5365,6 +5394,7 @@ namespace build2
if (fa) {++fi; msep = true;}
if (ma) {++mi; fsep = true;}
+ match = match || mod_sep (mc);
continue;
}
}
@@ -5372,6 +5402,14 @@ namespace build2
break; // No match.
}
+ // Deal with edge cases: complete module match and complete file
+ // match.
+ //
+ match = match || mi == 0 || (fi == 0 && mod_sep (m[mi - 1]));
+
+ if (!match)
+ return 0;
+
// "Uncount" real separators.
//
if (fsep) fi++;
@@ -5739,8 +5777,13 @@ namespace build2
//
// But at this stage this doesn't seem worth the trouble.
//
- fail (relative (src)) << "unable to resolve module "
- << imports[i].name;
+ fail (relative (src))
+ << "unable to resolve module " << imports[i].name <<
+ info << "verify module interface is listed as a prerequisite, "
+ << "otherwise" <<
+ info << "consider adjusting module interface file names or" <<
+ info << "consider specifying module name with " << x
+ << ".module_name";
}
}
}
@@ -5859,7 +5902,7 @@ namespace build2
// Find or create a modules sidebuild subproject returning its root
// directory.
//
- dir_path compile_rule::
+ pair<dir_path, const scope&> compile_rule::
find_modules_sidebuild (const scope& rs) const
{
context& ctx (rs.ctx);
@@ -5957,7 +6000,7 @@ namespace build2
assert (m != nullptr && m->modules);
#endif
- return pd;
+ return pair<dir_path, const scope&> (move (pd), *as);
}
// Synthesize a dependency for building a module binary interface on
@@ -5974,7 +6017,7 @@ namespace build2
// Note: see also make_header_sidebuild() below.
- dir_path pd (find_modules_sidebuild (*bs.root_scope ()));
+ dir_path pd (find_modules_sidebuild (*bs.root_scope ()).first);
// We need to come up with a file/target name that will be unique enough
// not to conflict with other modules. If we assume that within an
@@ -6023,14 +6066,6 @@ namespace build2
if (include (a, lt, p) != include_type::normal) // Excluded/ad hoc.
continue;
- // @@ TODO: will probably need revision if using sidebuild for
- // non-installed libraries (e.g., direct BMI dependencies
- // will probably have to be translated to mxx{} or some such).
- // Hm, don't think we want it this way: we want BMIs of binless
- // library to be built in the library rather than on the side
- // (so they can be potentially re-used by multiple independent
- // importers).
- //
if (p.is_a<libx> () ||
p.is_a<liba> () || p.is_a<libs> () || p.is_a<libux> ())
{
@@ -6068,8 +6103,9 @@ namespace build2
// the side.
//
const file& compile_rule::
- make_header_sidebuild (action,
+ make_header_sidebuild (action a,
const scope& bs,
+ const file& t,
linfo li,
const file& ht) const
{
@@ -6077,7 +6113,95 @@ namespace build2
// Note: similar to make_module_sidebuild() above.
- dir_path pd (find_modules_sidebuild (*bs.root_scope ()));
+ auto sb (find_modules_sidebuild (*bs.root_scope ()));
+ dir_path pd (move (sb.first));
+ const scope& as (sb.second);
+
+ // Determine if this header belongs to one of the libraries we depend
+ // on.
+ //
+ // Note that because libraries are not in prerequisite_targets, we have
+ // to go through prerequisites, similar to append_library_options().
+ //
+ const target* lt (nullptr); // Can be lib{}.
+ {
+ // Note that any such library would necessarily be an interface
+ // dependency so we never need to go into implementations.
+ //
+ auto imp = [] (const target&, bool) { return false; };
+
+ // The same logic as in append_libraries().
+ //
+ struct data
+ {
+ action a;
+ const file& ht;
+ const target*& lt;
+ } d {a, ht, lt};
+
+ auto lib = [&d] (const target* const* lc,
+ const string&,
+ lflags,
+ bool)
+ {
+ // It's unfortunate we have no way to bail out.
+ //
+ if (d.lt != nullptr)
+ return;
+
+ const target* l (lc != nullptr ? *lc : nullptr); // Can be lib{}.
+
+ if (l == nullptr)
+ return;
+
+ // Feels like we should only consider non-utility libraries with
+ // utilities being treated as "direct" use.
+ //
+ if (l->is_a<libux> ())
+ return;
+
+ // Since the library is searched and matched, all the headers should
+ // be in prerequisite_targets.
+ //
+ const auto& pts (l->prerequisite_targets[d.a]);
+ if (find (pts.begin (), pts.end (), &d.ht) != pts.end ())
+ d.lt = l;
+ };
+
+ for (prerequisite_member p: group_prerequisite_members (a, t))
+ {
+ if (include (a, t, p) != include_type::normal) // Excluded/ad hoc.
+ continue;
+
+ // Should be already searched and matched for libraries.
+ //
+ if (const target* pt = p.load ())
+ {
+ if (const libx* l = pt->is_a<libx> ())
+ pt = link_member (*l, a, li);
+
+ bool la;
+ const file* f;
+ if ((la = (f = pt->is_a<liba> ())) ||
+ (la = (f = pt->is_a<libux> ())) ||
+ ( (f = pt->is_a<libs> ())))
+ {
+ // Note that we are requesting process_libraries() to not pick
+ // the liba/libs{} member of the installed libraries and return
+ // the lib{} group itself instead. This is because, for the
+ // installed case, the library prerequisites (both headers and
+ // interface dependency libraries) are matched by file_rule
+ // which won't pick the liba/libs{} member (naturally) but will
+ // just match the lib{} group.
+ //
+ process_libraries (
+ a, bs, nullopt, sys_lib_dirs,
+ *f, la, 0, // lflags unused.
+ imp, lib, nullptr, true /* self */);
+ }
+ }
+ }
+ }
// What should we use as a file/target name? On one hand we want it
// unique enough so that <stdio.h> and <custom/stdio.h> don't end up
@@ -6102,7 +6226,14 @@ namespace build2
mf += sha256 (hp.string ()).abbreviated_string (12);
}
- const target_type& tt (compile_types (li.type).hbmi);
+ // If the header comes from the library, use its hbmi?{} type to
+ // maximize reuse.
+ //
+ const target_type& tt (
+ compile_types (
+ lt != nullptr && !lt->is_a<lib> ()
+ ? link_type (*lt).type
+ : li.type).hbmi);
if (const file* bt = bs.ctx.targets.find<file> (
tt,
@@ -6116,6 +6247,48 @@ namespace build2
prerequisites ps;
ps.push_back (prerequisite (ht));
+ // Similar story as for modules: the header may need poptions from its
+ // library (e.g., -I to find other headers that it includes).
+ //
+ if (lt != nullptr)
+ ps.push_back (prerequisite (*lt));
+ else
+ {
+ // If the header does not belong to a library then this is a "direct"
+ // use, for example, by an exe{} target. In this case we need to add
+ // all the prerequisite libraries as well as scope p/coptions (in a
+ // sense, we are trying to approximate how all the sources that would
+ // typically include such a header are build).
+ //
+ // Note that this is also the case when we build the library's own
+ // sources (in a way it would have been cleaner to always build
+ // library's headers with only its "interface" options/prerequisites
+ // but that won't be easy to achieve).
+ //
+ // Note also that at first it might seem like a good idea to
+ // incorporate this information into the hash we use to form the BMI
+ // name. But that would reduce sharing of the BMI. For example, that
+ // would mean we will build the library header twice, once with the
+ // implementation options/prerequisites and once -- with interface.
+ // On the other hand, importable headers are expected to be "modular"
+ // and should probably not depend on any of the implementation
+ // options/prerequisites (though one could conceivably build a
+ // "richer" BMI if it is also to be used to build the library
+ // implementation -- interesting idea).
+ //
+ for (prerequisite_member p: group_prerequisite_members (a, t))
+ {
+ if (include (a, t, p) != include_type::normal) // Excluded/ad hoc.
+ continue;
+
+ if (p.is_a<libx> () ||
+ p.is_a<liba> () || p.is_a<libs> () || p.is_a<libux> ())
+ {
+ ps.push_back (p.as_prerequisite ());
+ }
+ }
+ }
+
auto p (bs.ctx.targets.insert_locked (
tt,
move (pd),
@@ -6130,8 +6303,32 @@ namespace build2
// while we were preparing the prerequisite list.
//
if (p.second)
+ {
bt.prerequisites (move (ps));
+ // Add the p/coptions from our scope in case of a "direct" use. Take
+ // into account hbmi{} target-type/pattern values to allow specifying
+ // hbmi-specific options.
+ //
+ if (lt == nullptr)
+ {
+ auto set = [&bs, &as, &tt, &bt] (const variable& var)
+ {
+ // Avoid duplicating the options if they are from the same
+ // amalgamation as the sidebuild.
+ //
+ lookup l (bs.lookup (var, tt, bt.name, hbmi::static_type, bt.name));
+ if (l.defined () && !l.belongs (as))
+ bt.assign (var) = *l;
+ };
+
+ set (c_poptions);
+ set (x_poptions);
+ set (c_coptions);
+ set (x_coptions);
+ }
+ }
+
return bt;
}
diff --git a/libbuild2/cc/compile-rule.hxx b/libbuild2/cc/compile-rule.hxx
index a5eb8e8..edff1d8 100644
--- a/libbuild2/cc/compile-rule.hxx
+++ b/libbuild2/cc/compile-rule.hxx
@@ -162,7 +162,7 @@ namespace build2
const target_type&,
const file&, module_imports&, sha256&) const;
- dir_path
+ pair<dir_path, const scope&>
find_modules_sidebuild (const scope&) const;
const file&
@@ -170,7 +170,8 @@ namespace build2
const target&, const string&) const;
const file&
- make_header_sidebuild (action, const scope&, linfo, const file&) const;
+ make_header_sidebuild (action, const scope&, const file&,
+ linfo, const file&) const;
void
append_header_options (environment&, cstrings&, small_vector<string, 2>&,
diff --git a/libbuild2/cc/guess.cxx b/libbuild2/cc/guess.cxx
index 9afa29e..1e0c77a 100644
--- a/libbuild2/cc/guess.cxx
+++ b/libbuild2/cc/guess.cxx
@@ -65,7 +65,6 @@
# include <libbuild2/filesystem.hxx>
#endif
-#include <map>
#include <cstring> // strlen(), strchr(), strstr()
#include <libbuild2/diagnostics.hxx>
@@ -3218,5 +3217,180 @@ namespace build2
return r;
}
+
+ // Table 23 [tab:headers.cpp].
+ //
+ // In the future we will probably have to maintain per-standard additions.
+ //
+ static const char* std_importable[] = {
+ "<algorithm>",
+ "<any>",
+ "<array>",
+ "<atomic>",
+ "<barrier>",
+ "<bit>",
+ "<bitset>",
+ "<charconv>",
+ "<chrono>",
+ "<codecvt>",
+ "<compare>",
+ "<complex>",
+ "<concepts>",
+ "<condition_variable>",
+ "<coroutine>",
+ "<deque>",
+ "<exception>",
+ "<execution>",
+ "<filesystem>",
+ "<format>",
+ "<forward_list>",
+ "<fstream>",
+ "<functional>",
+ "<future>",
+ "<initializer_list>",
+ "<iomanip>",
+ "<ios>",
+ "<iosfwd>",
+ "<iostream>",
+ "<istream>",
+ "<iterator>",
+ "<latch>",
+ "<limits>",
+ "<list>",
+ "<locale>",
+ "<map>",
+ "<memory>",
+ "<memory_resource>",
+ "<mutex>",
+ "<new>",
+ "<numbers>",
+ "<numeric>",
+ "<optional>",
+ "<ostream>",
+ "<queue>",
+ "<random>",
+ "<ranges>",
+ "<ratio>",
+ "<regex>",
+ "<scoped_allocator>",
+ "<semaphore>",
+ "<set>",
+ "<shared_mutex>",
+ "<source_location>",
+ "<span>",
+ "<sstream>",
+ "<stack>",
+ "<stdexcept>",
+ "<stop_token>",
+ "<streambuf>",
+ "<string>",
+ "<string_view>",
+ "<strstream>",
+ "<syncstream>",
+ "<system_error>",
+ "<thread>",
+ "<tuple>",
+ "<typeindex>",
+ "<typeinfo>",
+ "<type_traits>",
+ "<unordered_map>",
+ "<unordered_set>",
+ "<utility>",
+ "<valarray>",
+ "<variant>",
+ "<vector>",
+ "<version>"
+ };
+
+ // Table 24 ([tab:headers.cpp.c])
+ //
+ static const char* std_non_importable[] = {
+ "<cassert>",
+ "<cctype>",
+ "<cerrno>",
+ "<cfenv>",
+ "<cfloat>",
+ "<cinttypes>",
+ "<climits>",
+ "<clocale>",
+ "<cmath>",
+ "<csetjmp>",
+ "<csignal>",
+ "<cstdarg>",
+ "<cstddef>",
+ "<cstdint>",
+ "<cstdio>",
+ "<cstdlib>",
+ "<cstring>",
+ "<ctime>",
+ "<cuchar>",
+ "<cwchar>",
+ "<cwctype>"
+ };
+
+ void
+ guess_std_importable_headers (const compiler_info& ci,
+ const dir_paths& sys_inc_dirs,
+ importable_headers& hs)
+ {
+ hs.group_map.emplace (header_group_std, 0);
+ hs.group_map.emplace (header_group_std_importable, 0);
+
+ // For better performance we make compiler-specific assumptions.
+ //
+ // For example, we can assume that all these headers are found in the
+ // same header search directory. This is at least the case for GCC's
+ // libstdc++.
+ //
+ // Note also that some headers could be missing. For example, <format>
+ // is currently not provided by GCC. Though entering missing headers
+ // should be harmless.
+ //
+ pair<const path, importable_headers::groups>* p;
+ auto add_groups = [&p] (bool imp)
+ {
+ if (imp)
+ p->second.push_back (header_group_std_importable); // More specific.
+
+ p->second.push_back (header_group_std);
+ };
+
+ if (ci.id.type != compiler_type::gcc)
+ {
+ for (const char* f: std_importable)
+ if ((p = hs.insert_angle (sys_inc_dirs, f)) != nullptr)
+ add_groups (true);
+
+ for (const char* f: std_non_importable)
+ if ((p = hs.insert_angle (sys_inc_dirs, f)) != nullptr)
+ add_groups (false);
+ }
+ else
+ {
+ p = hs.insert_angle (sys_inc_dirs, std_importable[0]);
+ assert (p != nullptr);
+
+ add_groups (true);
+
+ dir_path d (p->first.directory ());
+
+ auto add_header = [&hs, &d, &p, add_groups] (const char* f, bool imp)
+ {
+ path fp (d);
+ fp.combine (f + 1, strlen (f) - 2, '\0'); // Assuming simple.
+
+ p = &hs.insert_angle (move (fp), f);
+ add_groups (imp);
+ };
+
+ for (size_t i (1);
+ i != sizeof (std_importable) / sizeof (std_importable[0]);
+ ++i)
+ add_header (std_importable[i], true);
+
+ for (const char* f: std_non_importable)
+ add_header (f, false);
+ }
+ }
}
}
diff --git a/libbuild2/cc/guess.hxx b/libbuild2/cc/guess.hxx
index 868e925..a141fa3 100644
--- a/libbuild2/cc/guess.hxx
+++ b/libbuild2/cc/guess.hxx
@@ -265,6 +265,16 @@ namespace build2
const string& cid,
const string& pattern,
const strings& mode);
+
+ // Insert importable/non-importable C++ standard library headers
+ // ([headers]/4).
+ //
+ // Note that the importable_headers instance should be unique-locked.
+ //
+ void
+ guess_std_importable_headers (const compiler_info&,
+ const dir_paths& sys_inc_dirs,
+ importable_headers&);
}
}
diff --git a/libbuild2/cc/init.cxx b/libbuild2/cc/init.cxx
index bb50d07..e8b0e6f 100644
--- a/libbuild2/cc/init.cxx
+++ b/libbuild2/cc/init.cxx
@@ -146,6 +146,14 @@ namespace build2
//
vp.insert<string> ("cc.module_name", v_t);
+ // Importable header marker (normally set via the x.importable alias).
+ //
+ // Note that while at first it might seem like a good idea to allow
+ // setting it on a scope, that will cause translation of inline/template
+ // includes which is something we definitely don't want.
+ //
+ vp.insert<bool> ("cc.importable", v_t);
+
// Ability to disable using preprocessed output for compilation.
//
vp.insert<bool> ("config.cc.reprocess");
diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx
index 9c0b018..2b3f22a 100644
--- a/libbuild2/cc/link-rule.cxx
+++ b/libbuild2/cc/link-rule.cxx
@@ -3,7 +3,6 @@
#include <libbuild2/cc/link-rule.hxx>
-#include <map>
#include <cstdlib> // exit()
#include <cstring> // strlen()
@@ -24,7 +23,6 @@
#include <libbuild2/cc/target.hxx> // c, pc*
#include <libbuild2/cc/utility.hxx>
-using std::map;
using std::exit;
using namespace butl;
@@ -330,7 +328,7 @@ namespace build2
//
string ver;
bool verp (true); // Platform-specific.
- using verion_map = map<string, string>;
+ using verion_map = map<optional<string>, string>;
if (const verion_map* m = cast_null<verion_map> (t["bin.lib.version"]))
{
// First look for the target system.
@@ -347,14 +345,20 @@ namespace build2
// say "all others -- no version".
//
if (i == m->end ())
- i = m->find ("*");
+ i = m->find (string ("*"));
// Finally look for the platform-independent version.
//
if (i == m->end ())
{
verp = false;
- i = m->find ("");
+
+ i = m->find (nullopt);
+
+ // For backwards-compatibility.
+ //
+ if (i == m->end ())
+ i = m->find (string ());
}
// If we didn't find anything, fail. If the bin.lib.version was
@@ -852,7 +856,7 @@ namespace build2
//
else
{
- if (!p.is_a<objx> () && !p.is_a<bmix> ())
+ if (!p.is_a<objx> () && !p.is_a<bmix> () && !x_header (p, true))
{
// @@ Temporary hack until we get the default outer operation
// for update. This allows operations like test and install to
@@ -1549,17 +1553,19 @@ namespace build2
compile_target_types tts;
} d {ls, args, l, a, li, rel, compile_types (li.type)};
- auto imp = [] (const file&, bool la)
+ auto imp = [] (const target&, bool la)
{
return la;
};
- auto lib = [&d, this] (const file* const* lc,
+ auto lib = [&d, this] (const target* const* lc,
const string& p,
lflags f,
bool)
{
- const file* l (lc != nullptr ? *lc : nullptr);
+ // Note: see also make_header_sidebuild().
+
+ const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr);
// Suppress duplicates.
//
@@ -1739,11 +1745,13 @@ namespace build2
al.end = d.args.size (); // Close.
};
- auto opt = [&d, this] (const file& l,
+ auto opt = [&d, this] (const target& lt,
const string& t,
bool com,
bool exp)
{
+ const file& l (lt.as<file> ());
+
// Don't try to pass any loptions when linking a static library.
//
// Note also that we used to pass non-export loptions but that didn't
@@ -1800,17 +1808,17 @@ namespace build2
linfo li;
} d {cs, bs.root_scope ()->out_path (), update, mt, li};
- auto imp = [] (const file&, bool la)
+ auto imp = [] (const target&, bool la)
{
return la;
};
- auto lib = [&d, this] (const file* const* lc,
+ auto lib = [&d, this] (const target* const* lc,
const string& p,
lflags f,
bool)
{
- const file* l (lc != nullptr ? *lc : nullptr);
+ const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr);
if (l == nullptr)
{
@@ -1862,7 +1870,7 @@ namespace build2
}
};
- auto opt = [&d, this] (const file& l,
+ auto opt = [&d, this] (const target& l,
const string& t,
bool com,
bool exp)
@@ -1902,7 +1910,7 @@ namespace build2
return;
}
- auto imp = [link] (const file& l, bool la)
+ auto imp = [link] (const target& l, bool la)
{
// If we are not rpath-link'ing, then we only need to rpath interface
// libraries (they will include rpath's for their implementations)
@@ -1931,12 +1939,12 @@ namespace build2
bool link;
} d {ls, args, link};
- auto lib = [&d, this] (const file* const* lc,
+ auto lib = [&d, this] (const target* const* lc,
const string& f,
lflags,
bool sys)
{
- const file* l (lc != nullptr ? *lc : nullptr);
+ const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr);
// We don't rpath system libraries. Why, you may ask? There are many
// good reasons and I have them written on a napkin somewhere...
diff --git a/libbuild2/cc/module.cxx b/libbuild2/cc/module.cxx
index 8241a01..971f175 100644
--- a/libbuild2/cc/module.cxx
+++ b/libbuild2/cc/module.cxx
@@ -426,20 +426,21 @@ namespace build2
translate_std (xi, tt, rs, mode, v);
}
- // config.x.translatable_header
+ // config.x.translate_include
//
// It's still fuzzy whether specifying (or maybe tweaking) this list in
// the configuration will be a common thing to do so for now we use
- // omitted. It's also probably too early to think whether we should have
- // the cc.* version and what the semantics should be.
+ // omitted.
//
- if (x_translatable_headers != nullptr)
+ if (x_translate_include != nullptr)
{
- lookup l (lookup_config (rs, *config_x_translatable_headers));
-
- // @@ MODHDR: if(modules) ?
- //
- rs.assign (x_translatable_headers) += cast_null<strings> (l);
+ if (lookup l = lookup_config (rs, *config_x_translate_include))
+ {
+ // @@ MODHDR: if(modules) ? Yes.
+ //
+ rs.assign (x_translate_include).prepend (
+ cast<translatable_headers> (l));
+ }
}
// Extract system header/library search paths from the compiler and
@@ -718,8 +719,19 @@ namespace build2
}
}
+ // Global cache of ad hoc importable headers.
+ //
+ // The key is a hash of the system header search directories
+ // (sys_inc_dirs) where we search for the headers.
+ //
+ static map<string, importable_headers> importable_headers_cache;
+ static mutex importable_headers_mutex;
+
void module::
- init (scope& rs, const location& loc, const variable_map&)
+ init (scope& rs,
+ const location& loc,
+ const variable_map&,
+ const compiler_info& xi)
{
tracer trace (x, "init");
@@ -740,71 +752,76 @@ namespace build2
//
load_module (rs, rs, "cc.core", loc);
- // Process, sort, and cache (in this->xlate_hdr) translatable headers.
- // Keep the cache NULL if unused or empty.
+ // Search include translation headers and groups.
//
- // @@ MODHDR TODO: support exclusions entries (e.g., -<stdio.h>)?
- //
- if (modules && x_translatable_headers != nullptr)
+ if (modules)
{
- strings* ih (cast_null<strings> (rs.assign (x_translatable_headers)));
+ {
+ sha256 k;
+ for (const dir_path& d: sys_inc_dirs)
+ k.append (d.string ());
+
+ mlock l (importable_headers_mutex);
+ importable_headers = &importable_headers_cache[k.string ()];
+ }
+
+ auto& hs (*importable_headers);
- if (ih != nullptr && !ih->empty ())
+ ulock ul (hs.mutex);
+
+ if (hs.group_map.find (header_group_std) == hs.group_map.end ())
+ guess_std_importable_headers (xi, sys_inc_dirs, hs);
+
+ // Process x.translate_include.
+ //
+ const variable& var (*x_translate_include);
+ if (auto* v = cast_null<translatable_headers> (rs[var]))
{
- // Translate <>-style header names to absolute paths using the
- // compiler's include search paths. Otherwise complete and normalize
- // since when searching in this list we always use the absolute and
- // normalized header target path.
- //
- for (string& h: *ih)
+ for (const auto& p: *v)
{
- if (h.empty ())
- continue;
+ const string& k (p.first);
- path f;
- if (h.front () == '<' && h.back () == '>')
+ if (k.front () == '<' && k.back () == '>')
{
- h.pop_back ();
- h.erase (0, 1);
+ if (path_pattern (k))
+ {
+ size_t n (hs.insert_angle_pattern (sys_inc_dirs, k));
- for (const dir_path& d: sys_inc_dirs)
+ l5 ([&]{trace << "pattern " << k << " searched to " << n
+ << " headers";});
+ }
+ else
{
- if (file_exists ((f = d, f /= h),
- true /* follow_symlinks */,
- true /* ignore_errors */))
- goto found;
+ // What should we do if not found? While we can fail, this
+ // could be too drastic if, for example, the header is
+ // "optional" and may or may not be present/used. So for now
+ // let's ignore (we could have also removed it from the map as
+ // an indication).
+ //
+ const auto* r (hs.insert_angle (sys_inc_dirs, k));
+
+ l5 ([&]{trace << "header " << k << " searched to "
+ << (r ? r->first.string ().c_str () : "<none>");});
}
-
- // What should we do if not found? While we can fail, this could
- // be too drastic if, for example, the header is "optional" and
- // may or may not be present/used. So for now let's restore the
- // original form to aid debugging (it can't possibly match any
- // absolute path).
+ }
+ else if (path_traits::find_separator (k) == string::npos)
+ {
+ // Group name.
//
- h.insert (0, 1, '<');
- h.push_back ('>');
- continue;
-
- found:
- ; // Fall through.
+ if (k != header_group_all_importable &&
+ k != header_group_std_importable &&
+ k != header_group_all &&
+ k != header_group_std)
+ fail (loc) << "unknown header group '" << k << "' in " << var;
}
else
{
- f = path (move (h));
-
- if (f.relative ())
- f.complete ();
+ // Absolute and normalized header path.
+ //
+ if (!path_traits::absolute (k))
+ fail (loc) << "relative header path '" << k << "' in " << var;
}
-
- // @@ MODHDR: should we use the more elaborate but robust
- // normalize/realize scheme so the we get the same
- // path? Feels right.
- f.normalize ();
- h = move (f).string ();
}
-
- sort (ih->begin (), ih->end ());
- xlate_hdr = ih;
}
}
diff --git a/libbuild2/cc/module.hxx b/libbuild2/cc/module.hxx
index 85b7158..f9d435d 100644
--- a/libbuild2/cc/module.hxx
+++ b/libbuild2/cc/module.hxx
@@ -45,8 +45,10 @@ namespace build2
// Translate the x.std value (if any) to the standard-selecting
// option(s) (if any) and fold them (normally by pre-pending) into the
- // compiler mode options. This function may also check/set x.features.*
- // variables on the root scope.
+ // compiler mode options.
+ //
+ // This function may also check/set [config.]x.features.* variables on
+ // the root scope.
//
virtual void
translate_std (const compiler_info&,
@@ -103,7 +105,10 @@ namespace build2
libux_install_rule (move (d), *this) {}
void
- init (scope&, const location&, const variable_map&);
+ init (scope&,
+ const location&,
+ const variable_map&,
+ const compiler_info&);
};
}
}
diff --git a/libbuild2/cc/pkgconfig.cxx b/libbuild2/cc/pkgconfig.cxx
index 75c7227..d44b0ec 100644
--- a/libbuild2/cc/pkgconfig.cxx
+++ b/libbuild2/cc/pkgconfig.cxx
@@ -739,12 +739,12 @@ namespace build2
// export stub and we shouldn't touch them.
//
if (p.second)
- p.first.get () = move (pops);
+ p.first = move (pops);
}
};
// Parse --libs into loptions/libs (interface and implementation). If
- // ps is not NULL, add each resolves library target as a prerequisite.
+ // ps is not NULL, add each resolved library target as a prerequisite.
//
auto parse_libs = [a, &s, top_sysd, this] (target& t,
bool binless,
@@ -1090,7 +1090,7 @@ namespace build2
auto p (t.vars.insert (c_export_loptions));
if (p.second)
- p.first.get () = move (lops);
+ p.first = move (lops);
}
// Set even if empty (export override).
@@ -1099,7 +1099,7 @@ namespace build2
auto p (t.vars.insert (la ? c_export_imp_libs : c_export_libs));
if (p.second)
- p.first.get () = move (libs);
+ p.first = move (libs);
}
};
@@ -1142,15 +1142,17 @@ namespace build2
return r;
};
- // Parse modules and add them to the prerequisites.
+ // Parse modules, enter them as targets, and add them to the
+ // prerequisites.
//
- auto parse_modules = [&trace, &next, &s, this]
- (const pkgconf& pc, prerequisites& ps)
+ auto parse_modules = [&trace, this,
+ &next, &s, &lt] (const pkgconf& pc,
+ prerequisites& ps)
{
- string mstr (pc.variable ("cxx_modules"));
+ string val (pc.variable ("cxx_modules"));
string m;
- for (size_t b (0), e (0); !(m = next (mstr, b, e)).empty (); )
+ for (size_t b (0), e (0); !(m = next (val, b, e)).empty (); )
{
// The format is <name>=<path> with `..` used as a partition
// separator (see pkgconfig_save() for details).
@@ -1159,7 +1161,7 @@ namespace build2
if (p == string::npos ||
p == 0 || // Empty name.
p == m.size () - 1) // Empty path.
- fail << "invalid module information in '" << mstr << "'" <<
+ fail << "invalid module information in '" << val << "'" <<
info << "while parsing pkg-config --variable=cxx_modules "
<< pc.path;
@@ -1193,12 +1195,12 @@ namespace build2
// If the target already exists, then setting its variables is not
// MT-safe. So currently we only do it if we have the lock (and thus
- // nobody can see this target yet) assuming that this has already
+ // nobody can see this target yet) verifying that this has already
// been done otherwise.
//
// @@ This is not quite correct, though: this target could already
// exist but for a "different purpose" (e.g., it could be used as
- // a header).
+ // a header). Well, maybe it shouldn't.
//
// @@ Could setting it in the rule-specific vars help? (But we
// are not matching a rule for it.) Note that we are setting
@@ -1211,7 +1213,7 @@ namespace build2
// Set module properties. Note that if unspecified we should still
// set them to their default values since the hosting project may
- // have them set to incompatible value.
+ // have them set to incompatible values.
//
{
value& v (mt.vars.assign (x_preprocessed)); // NULL
@@ -1224,11 +1226,72 @@ namespace build2
tl.second.unlock ();
}
+ else
+ {
+ if (!mt.vars[c_module_name])
+ fail << "unexpected metadata for module target " << mt <<
+ info << "module is expected to have assigned name" <<
+ info << "make sure this module is used via " << lt
+ << " prerequisite";
+ }
ps.push_back (prerequisite (mt));
}
};
+ // Parse importable headers, enter them as targets, and add them to
+ // the prerequisites.
+ //
+ auto parse_headers = [&trace, this,
+ &next, &s, &lt] (const pkgconf& pc,
+ const target_type& tt,
+ const char* lang,
+ prerequisites& ps)
+ {
+ string var (string (lang) + "_importable_headers");
+ string val (pc.variable (var));
+
+ string h;
+ for (size_t b (0), e (0); !(h = next (val, b, e)).empty (); )
+ {
+ path hp (move (h));
+ path hf (hp.leaf ());
+
+ auto tl (
+ s.ctx.targets.insert_locked (
+ tt,
+ hp.directory (),
+ dir_path (),
+ hf.base ().string (),
+ hf.extension (),
+ target_decl::implied,
+ trace));
+
+ target& ht (tl.first);
+
+ // If the target already exists, then setting its variables is not
+ // MT-safe. So currently we only do it if we have the lock (and thus
+ // nobody can see this target yet) verifying that this has already
+ // been done otherwise.
+ //
+ if (tl.second.owns_lock ())
+ {
+ ht.vars.assign (c_importable) = true;
+ tl.second.unlock ();
+ }
+ else
+ {
+ if (!cast_false<bool> (ht.vars[c_importable]))
+ fail << "unexpected metadata for existing header target " << ht <<
+ info << "header is expected to be marked importable" <<
+ info << "make sure this header is used via " << lt
+ << " prerequisite";
+ }
+
+ ps.push_back (prerequisite (ht));
+ }
+ };
+
// For now we only populate prerequisites for lib{}. To do it for
// liba{} would require weeding out duplicates that are already in
// lib{}.
@@ -1291,13 +1354,21 @@ namespace build2
parse_cflags (*st, spc, false);
// For now we assume static and shared variants export the same set of
- // modules. While technically possible, having a different set will most
- // likely lead to all sorts of complications (at least for installed
- // libraries) and life is short.
+ // modules/importable headers. While technically possible, having
+ // different sets will most likely lead to all sorts of complications
+ // (at least for installed libraries) and life is short.
//
if (modules)
+ {
parse_modules (ipc, prs);
+ // We treat headers outside of any project as C headers (see
+ // enter_header() for details).
+ //
+ parse_headers (ipc, h::static_type /* **x_hdr */, x, prs);
+ parse_headers (ipc, h::static_type, "c", prs);
+ }
+
assert (!lt.has_prerequisites ());
if (!prs.empty ())
lt.prerequisites (move (prs));
@@ -1479,13 +1550,25 @@ namespace build2
}
else
{
- // Derive -l-name from the file name in a fuzzy, platform-specific
- // manner.
- //
- n = l.path ().leaf ().base ().string ();
+ const path& p (l.path ());
- if (cclass != compiler_class::msvc)
- strip_lib ();
+ if (p.empty ()) // Binless.
+ {
+ // For a binless library the target name is all it can possibly
+ // be.
+ //
+ n = l.name;
+ }
+ else
+ {
+ // Derive -l-name from the file name in a fuzzy, platform-
+ // specific manner.
+ //
+ n = p.leaf ().base ().string ();
+
+ if (cclass != compiler_class::msvc)
+ strip_lib ();
+ }
}
os << " -l" << n;
@@ -1550,15 +1633,15 @@ namespace build2
// pretty similar to link_rule::append_libraries()).
//
bool priv (false);
- auto imp = [&priv] (const file&, bool la) {return priv && la;};
+ auto imp = [&priv] (const target&, bool la) {return priv && la;};
auto lib = [&save_library_target,
- &save_library_name] (const file* const* c,
+ &save_library_name] (const target* const* lc,
const string& p,
lflags,
bool)
{
- const file* l (c != nullptr ? *c : nullptr);
+ const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr);
if (l != nullptr)
{
@@ -1569,9 +1652,7 @@ namespace build2
save_library_name (p); // Something "system'y", save as is.
};
- auto opt = [] (const file&,
- const string&,
- bool, bool)
+ auto opt = [] (const target&, const string&, bool, bool)
{
//@@ TODO: should we filter -L similar to -I?
//@@ TODO: how will the Libs/Libs.private work?
@@ -1602,32 +1683,46 @@ namespace build2
}
}
- // If we have modules, list them in the modules variable. We also save
- // some extra info about them (yes, the rabbit hole runs deep). This
- // code is pretty similar to compiler::search_modules().
+ // If we have modules and/or importable headers, list them in the
+ // respective variables. We also save some extra info about modules
+ // (yes, the rabbit hole runs deep). This code is pretty similar to
+ // compiler::search_modules().
+ //
+ // Note that we want to convey the importable headers information even
+ // if modules are not enabled.
//
- if (modules)
{
struct module
{
string name;
path file;
- string pp;
+ string preprocessed;
bool symexport;
};
- vector<module> modules;
+ vector<module> mods;
+
+ // If we were to ever support another C-based language (e.g.,
+ // Objective-C) and libraries that can use a mix of languages (e.g.,
+ // C++ and Objective-C), then we would need to somehow reverse-
+ // lookup header target type to language. Let's hope we don't.
+ //
+ vector<path> x_hdrs;
+ vector<path> c_hdrs;
// Note that the prerequisite targets are in the member, not the
- // group (for now we don't support different sets of modules for
- // static/shared library; see load above for details).
+ // group (for now we don't support different sets of modules/headers
+ // for static/shared library; see load above for details).
//
for (const target* pt: l.prerequisite_targets[a])
{
+ if (pt == nullptr)
+ continue;
+
// @@ UTL: we need to (recursively) see through libu*{} (and
// also in search_modules()).
//
- if (pt != nullptr && pt->is_a<bmix> ())
+ if (modules && pt->is_a<bmix> ())
{
// What we have is a binary module interface. What we need is
// a module interface source it was built from. We assume it's
@@ -1654,7 +1749,7 @@ namespace build2
if (const string* v = cast_null<string> ((*mt)[x_preprocessed]))
pp = *v;
- modules.push_back (
+ mods.push_back (
module {
cast<string> (pt->state[a].vars[c_module_name]),
move (p),
@@ -1662,9 +1757,21 @@ namespace build2
symexport
});
}
+ else if (pt->is_a (**x_hdr) || pt->is_a<h> ())
+ {
+ if (cast_false<bool> ((*pt)[c_importable]))
+ {
+ path p (install::resolve_file (pt->as<file> ()));
+
+ if (p.empty ()) // Not installed.
+ continue;
+
+ (pt->is_a<h> () ? c_hdrs : x_hdrs).push_back (move (p));
+ }
+ }
}
- if (!modules.empty ())
+ if (size_t n = mods.size ())
{
os << endl
<< "cxx_modules =";
@@ -1676,7 +1783,7 @@ namespace build2
// for example hello.print..impl. While in the variable values we
// can use `:`, for consistency we use `..` there as well.
//
- for (module& m: modules)
+ for (module& m: mods)
{
size_t p (m.name.find (':'));
if (p != string::npos)
@@ -1684,7 +1791,8 @@ namespace build2
// Module names shouldn't require escaping.
//
- os << ' ' << m.name << '=' << escape (m.file.string ());
+ os << (n != 1 ? " \\\n" : " ")
+ << m.name << '=' << escape (m.file.string ());
}
os << endl;
@@ -1693,16 +1801,38 @@ namespace build2
//
// <lang>_module_<property>.<module> = <value>
//
- for (const module& m: modules)
+ for (const module& m: mods)
{
- if (!m.pp.empty ())
- os << "cxx_module_preprocessed." << m.name << " = " << m.pp
- << endl;
+ if (!m.preprocessed.empty ())
+ os << "cxx_module_preprocessed." << m.name << " = "
+ << m.preprocessed << endl;
if (m.symexport)
os << "cxx_module_symexport." << m.name << " = true" << endl;
}
}
+
+ if (size_t n = c_hdrs.size ())
+ {
+ os << endl
+ << "c_importable_headers =";
+
+ for (const path& h: c_hdrs)
+ os << (n != 1 ? " \\\n" : " ") << escape (h.string ());
+
+ os << endl;
+ }
+
+ if (size_t n = x_hdrs.size ())
+ {
+ os << endl
+ << x << "_importable_headers =";
+
+ for (const path& h: x_hdrs)
+ os << (n != 1 ? " \\\n" : " ") << escape (h.string ());
+
+ os << endl;
+ }
}
os.close ();
diff --git a/libbuild2/cc/types.cxx b/libbuild2/cc/types.cxx
new file mode 100644
index 0000000..666b048
--- /dev/null
+++ b/libbuild2/cc/types.cxx
@@ -0,0 +1,189 @@
+// file : libbuild2/cc/types.cxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/cc/types.hxx>
+
+#include <libbuild2/cc/utility.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ namespace cc
+ {
+ const string header_group_all ("all");
+ const string header_group_all_importable ("all-importable");
+ const string header_group_std ("std");
+ const string header_group_std_importable ("std-importable");
+
+ // Find the position where the group should be inserted unless the group
+ // is already there.
+ //
+ using groups = importable_headers::groups;
+
+ static inline optional<groups::const_iterator>
+ find_angle (const groups& gs, const string& g)
+ {
+ for (auto i (gs.begin ()); i != gs.end (); ++i)
+ {
+ // After last angle-bracket file.
+ //
+ if (i->front () != '<' || i->back () != '>' || path_pattern (*i))
+ return i;
+
+ if (*i == g)
+ return nullopt;
+ }
+
+ return gs.begin ();
+ }
+
+ static inline optional<groups::const_iterator>
+ find_angle_pattern (const groups& gs, const string& g)
+ {
+ for (auto i (gs.begin ()); i != gs.end (); ++i)
+ {
+ // After last angle-bracket file pattern.
+ //
+ if (i->front () != '<' || i->back () != '>')
+ return i;
+
+ if (*i == g)
+ return nullopt;
+ }
+
+ return gs.begin ();
+ }
+
+ auto importable_headers::
+ insert_angle (const dir_paths& sys_inc_dirs,
+ const string& s) -> pair<const path, groups>*
+ {
+ assert (s.front () == '<' && s.back () == '>');
+
+ // First see if it has already been inserted.
+ //
+ auto i (group_map.find (s));
+ if (i == group_map.end ())
+ {
+ path f (s, 1, s.size () - 2);
+
+ path p;
+ for (const dir_path& d: sys_inc_dirs)
+ {
+ if (file_exists ((p = d, p /= f),
+ true /* follow_symlinks */,
+ true /* ignore_errors */))
+ goto found;
+ }
+
+ return nullptr;
+
+ found:
+ normalize_header (p);
+
+ // Note that it's possible this header has already been entered as
+ // part of a different group.
+ //
+ auto j (header_map.emplace (move (p), groups {}).first);
+
+ if (auto p = find_angle (j->second, s))
+ j->second.insert (*p, s);
+
+ i = group_map.emplace (s, reinterpret_cast<uintptr_t> (&*j)).first;
+ }
+
+ return reinterpret_cast<pair<const path, groups>*> (i->second);
+ }
+
+ auto importable_headers::
+ insert_angle (path p, const string& s) -> pair<const path, groups>&
+ {
+ assert (s.front () == '<' && s.back () == '>');
+
+ // First see if it has already been inserted.
+ //
+ auto i (group_map.find (s));
+ if (i == group_map.end ())
+ {
+ // Note that it's possible this header has already been entered as
+ // part of a different group.
+ //
+ auto j (header_map.emplace (move (p), groups {}).first);
+
+ if (auto p = find_angle (j->second, s))
+ j->second.insert (*p, s);
+
+ i = group_map.emplace (s, reinterpret_cast<uintptr_t> (&*j)).first;
+ }
+
+ return *reinterpret_cast<pair<const path, groups>*> (i->second);
+ }
+
+ size_t importable_headers::
+ insert_angle_pattern (const dir_paths& sys_inc_dirs, const string& pat)
+ {
+ assert (pat.front () == '<' && pat.back () == '>' && path_pattern (pat));
+
+ // First see if it has already been inserted.
+ //
+ auto i (group_map.find (pat));
+ if (i == group_map.end ())
+ {
+ path f (pat, 1, pat.size () - 2);
+
+ struct data
+ {
+ uintptr_t n;
+ const string& pat;
+ const dir_path* dir;
+ } d {0, pat, nullptr};
+
+ auto process = [&d, this] (path&& pe, const string&, bool interm)
+ {
+ if (interm)
+ return true;
+
+ path p (*d.dir / pe);
+ normalize_header (p);
+
+ string s (move (pe).string ());
+ s.insert (0, 1, '<');
+ s.push_back ('>');
+
+ // Note that it's possible this header has already been entered as
+ // part of a different group.
+ //
+ auto j (header_map.emplace (move (p), groups {}).first);
+
+ if (auto p = find_angle (j->second, s))
+ j->second.insert (*p, move (s));
+
+ if (auto p = find_angle_pattern (j->second, d.pat))
+ j->second.insert (*p, d.pat);
+
+ d.n++;
+ return true;
+ };
+
+ for (const dir_path& dir: sys_inc_dirs)
+ {
+ d.dir = &dir;
+
+ try
+ {
+ path_search (f, process, dir);
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to scan " << dir << ": " << e;
+ }
+ }
+
+ i = group_map.emplace (pat, d.n).first;
+ }
+
+ return static_cast<size_t> (i->second);
+ }
+ }
+}
diff --git a/libbuild2/cc/types.hxx b/libbuild2/cc/types.hxx
index 20b67c2..1297b7b 100644
--- a/libbuild2/cc/types.hxx
+++ b/libbuild2/cc/types.hxx
@@ -4,6 +4,8 @@
#ifndef LIBBUILD2_CC_TYPES_HXX
#define LIBBUILD2_CC_TYPES_HXX
+#include <unordered_map>
+
#include <libbuild2/types.hxx>
#include <libbuild2/utility.hxx>
@@ -81,6 +83,80 @@ namespace build2
build2::cc::module_info module_info;
};
+ // Ad hoc (as opposed to marked with x.importable) importable headers.
+ //
+ // Note that these are only searched for in the system header search
+ // directories (sys_inc_dirs).
+ //
+ struct importable_headers
+ {
+ mutable shared_mutex mutex;
+
+ using groups = small_vector<string, 3>;
+
+ // Map of groups (e.g., std, <vector>, <boost/*.hpp>) that have already
+ // been inserted.
+ //
+ // For angle-bracket file groups (e.g., <vector>), the value is a
+ // pointer to the corresponding header_map element. For angle-bracket
+ // file pattern groups (e.g., <boost/**.hpp>), the value is the number
+ // of files in the group.
+ //
+ // Note that while the case-sensitivity of header names in #include
+ // directives is implementation-defined, our group names are case-
+ // sensitive (playing loose with the case will lead to portability
+ // issues sooner or later so we don't bother with any more elborate
+ // solutions).
+ //
+ std::unordered_map<string, uintptr_t> group_map;
+
+ // Map of absolute and normalized header paths to groups (e.g., std,
+ // <vector>, <boost/**.hpp>) to which they belong. The groups are
+ // ordered from the most to least specific (e.g., <vector> then std).
+ //
+ std::unordered_map<path, groups> header_map;
+
+ // Note that all these functions assume the instance is unique-locked.
+ //
+
+ // Search for and insert an angle-bracket file, for example <vector>,
+ // making it belong to the angle-bracket file group itself. Return the
+ // pointer to the corresponding header_map element if the file has been
+ // found and NULL otherwise (so can be used as bool).
+ //
+ pair<const path, groups>*
+ insert_angle (const dir_paths& sys_inc_dirs, const string& file);
+
+ // As above but for a manually-searched absolute and normalized path.
+ //
+ pair<const path, groups>&
+ insert_angle (path, const string& file);
+
+ // Search for and insert an angle-bracket file pattern, for example
+ // <boost/**.hpp>, making each header belong to the angle-bracket file
+ // group (e.g., <boost/any.hpp>) and the angle-bracket file pattern
+ // group itself. Return the number of files found that match the
+ // pattern.
+ //
+ size_t
+ insert_angle_pattern (const dir_paths& sys_inc_dirs, const string& pat);
+ };
+
+ // Headers and header groups whose inclusion should or should not be
+ // translated to the corresponding header unit imports.
+ //
+ // The key is either an absolute and normalized header path or a reference
+ // to an importable_headers group (e.g., <vector>, std).
+ //
+ using translatable_headers = map<string, optional<bool>>;
+
+ // Special translatable header groups.
+ //
+ extern const string header_group_all;
+ extern const string header_group_all_importable;
+ extern const string header_group_std;
+ extern const string header_group_std_importable;
+
// Compiler language.
//
enum class lang {c, cxx};
diff --git a/libbuild2/cc/utility.cxx b/libbuild2/cc/utility.cxx
index 283e1b4..ffe3e03 100644
--- a/libbuild2/cc/utility.cxx
+++ b/libbuild2/cc/utility.cxx
@@ -17,5 +17,58 @@ namespace build2
const dir_path module_build_dir (dir_path (module_dir) /= "build");
const dir_path module_build_modules_dir (
dir_path (module_build_dir) /= "modules");
+
+ void
+ normalize_header (path& f)
+ {
+ // Interestingly, on most paltforms and with most compilers (Clang on
+ // Linux being a notable exception) most system/compiler headers are
+ // already normalized.
+ //
+ path_abnormality a (f.abnormalities ());
+ if (a != path_abnormality::none)
+ {
+ // While we can reasonably expect this path to exit, things do go
+ // south from time to time (like compiling under wine with file
+ // wlantypes.h included as WlanTypes.h).
+ //
+ try
+ {
+ // If we have any parent components, then we have to verify the
+ // normalized path matches realized.
+ //
+ path r;
+ if ((a & path_abnormality::parent) == path_abnormality::parent)
+ {
+ r = f;
+ r.realize ();
+ }
+
+ try
+ {
+ f.normalize ();
+
+ // Note that we might still need to resolve symlinks in the
+ // normalized path.
+ //
+ if (!r.empty () && f != r && path (f).realize () != r)
+ f = move (r);
+ }
+ catch (const invalid_path&)
+ {
+ assert (!r.empty ()); // Shouldn't have failed if no `..`.
+ f = move (r); // Fallback to realize.
+ }
+ }
+ catch (const invalid_path&)
+ {
+ fail << "invalid header path '" << f.string () << "'";
+ }
+ catch (const system_error& e)
+ {
+ fail << "invalid header path '" << f.string () << "': " << e;
+ }
+ }
+ }
}
}
diff --git a/libbuild2/cc/utility.hxx b/libbuild2/cc/utility.hxx
index a856fd0..42e53e3 100644
--- a/libbuild2/cc/utility.hxx
+++ b/libbuild2/cc/utility.hxx
@@ -48,6 +48,32 @@ namespace build2
compile_target_types
compile_types (otype);
+
+ // Normalize an absolute path to an existing header.
+ //
+ // We used to just normalize the path but that could result in an invalid
+ // path (e.g., for some system/compiler headers on CentOS 7 with Clang
+ // 3.4) because of the symlinks (if a directory component is a symlink,
+ // then any following `..` are resolved relative to the target; see
+ // path::normalize() for background).
+ //
+ // Initially, to fix this, we realized (i.e., realpath(3)) it instead.
+ // But that turned out also not to be quite right since now we have all
+ // the symlinks resolved: conceptually it feels correct to keep the
+ // original header names since that's how the user chose to arrange things
+ // and practically this is how the compilers see/report them (e.g., the
+ // GCC module mapper).
+ //
+ // So now we have a pretty elaborate scheme where we try to use the
+ // normalized path if possible and fallback to realized. Normalized paths
+ // will work for situations where `..` does not cross symlink boundaries,
+ // which is the sane case. And for the insane case we only really care
+ // about out-of-project files (i.e., system/compiler headers). In other
+ // words, if you have the insane case inside your project, then you are on
+ // your own.
+ //
+ void
+ normalize_header (path&);
}
}
diff --git a/libbuild2/cc/windows-rpath.cxx b/libbuild2/cc/windows-rpath.cxx
index eddb9c4..70eef73 100644
--- a/libbuild2/cc/windows-rpath.cxx
+++ b/libbuild2/cc/windows-rpath.cxx
@@ -56,14 +56,14 @@ namespace build2
// We need to collect all the DLLs, so go into implementation of both
// shared and static (in case they depend on shared).
//
- auto imp = [] (const file&, bool) {return true;};
+ auto imp = [] (const target&, bool) {return true;};
- auto lib = [&r] (const file* const* lc,
+ auto lib = [&r] (const target* const* lc,
const string& f,
lflags,
bool sys)
{
- const file* l (lc != nullptr ? *lc : nullptr);
+ const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr);
// We don't rpath system libraries.
//
@@ -137,14 +137,14 @@ namespace build2
{
windows_dlls r;
- auto imp = [] (const file&, bool) {return true;};
+ auto imp = [] (const target&, bool) {return true;};
- auto lib = [&r, &bs] (const file* const* lc,
+ auto lib = [&r, &bs] (const target* const* lc,
const string& f,
lflags,
bool sys)
{
- const file* l (lc != nullptr ? *lc : nullptr);
+ const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr);
if (sys)
return;
diff --git a/libbuild2/config/module.hxx b/libbuild2/config/module.hxx
index 857a30c..3afe721 100644
--- a/libbuild2/config/module.hxx
+++ b/libbuild2/config/module.hxx
@@ -4,8 +4,6 @@
#ifndef LIBBUILD2_CONFIG_MODULE_HXX
#define LIBBUILD2_CONFIG_MODULE_HXX
-#include <map>
-
#include <libbutl/prefix-map.mxx>
#include <libbuild2/types.hxx>
@@ -55,7 +53,7 @@ namespace build2
// Priority order with INT32_MIN being the highest. Modules with the
// same priority are saved in the order inserted.
//
- std::multimap<std::int32_t, const_iterator> order;
+ multimap<std::int32_t, const_iterator> order;
pair<iterator, bool>
insert (string name, int prio = 0)
diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx
index b9856be..7490bc3 100644
--- a/libbuild2/config/operation.cxx
+++ b/libbuild2/config/operation.cxx
@@ -82,8 +82,6 @@ namespace build2
}
}
- using project_set = set<const scope*>; // Use pointers to get comparison.
-
// Return (first) whether an unused/inherited variable should be saved
// according to the config.config.persist value and (second) whether the
// user should be warned about it.
@@ -722,7 +720,7 @@ namespace build2
//
create_bootstrap_inner (rs);
- if (rs.out_path () == rs.src_path ())
+ if (rs.out_eq_src ())
fail (l) << "forwarding to source directory " << rs.src_path ();
}
else
diff --git a/libbuild2/context.cxx b/libbuild2/context.cxx
index 3224dc1..df06aa8 100644
--- a/libbuild2/context.cxx
+++ b/libbuild2/context.cxx
@@ -35,7 +35,7 @@ namespace build2
create_global_scope (scope_map& m)
{
auto i (m.insert (dir_path ()));
- scope& r (i->second);
+ scope& r (*i->second.scope);
r.out_path_ = &i->first;
return r;
};
@@ -488,7 +488,7 @@ namespace build2
//
if (c == '!' || (dir && dir->absolute ()))
{
- scope& s (c == '!' ? gs : sm.insert (*dir)->second);
+ scope& s (c == '!' ? gs : *sm.insert (*dir)->second.scope);
auto p (s.vars.insert (*o));
assert (p.second); // Variable name is unique.
diff --git a/libbuild2/cxx/init.cxx b/libbuild2/cxx/init.cxx
index e2d7343..077e3fd 100644
--- a/libbuild2/cxx/init.cxx
+++ b/libbuild2/cxx/init.cxx
@@ -6,6 +6,8 @@
#include <libbuild2/scope.hxx>
#include <libbuild2/diagnostics.hxx>
+#include <libbuild2/config/utility.hxx>
+
#include <libbuild2/cc/guess.hxx>
#include <libbuild2/cc/module.hxx>
@@ -93,11 +95,57 @@ namespace build2
//
auto& vp (rs.var_pool ());
- //bool concepts (false);
- //auto& v_c (vp.insert<bool> ("cxx.features.concepts"));
+ // Similar to config.cxx.std, config.cxx.features.* overrides
+ // cxx.features.*.
+ //
+ struct feature
+ {
+ optional<bool> value; // cxx.features.* value.
+ optional<bool> c_value; // config.cxx.features.* value.
+ bool result; // Calculated result value.
+
+ feature& operator= (bool r) {result = r; return *this;}
+
+ build2::value& value_; // cxx.features.* variable value.
+ const char* name_; // Feature name.
+ };
+
+ auto get_feature = [&rs, &vp] (const char* name) -> feature
+ {
+ auto& var (vp.insert<bool> (string ("cxx.features.") + name));
+ auto& c_var (vp.insert<bool> (string ("config.cxx.features.") + name));
+
+ pair<value&, bool> val (rs.vars.insert (var));
+ lookup l (config::lookup_config (rs, c_var));
+
+ optional<bool> v, c_v;
+ if (l.defined ())
+ v = c_v = cast_false<bool> (*l);
+ else if (!val.second)
+ v = cast_false<bool> (val.first);
+
+ return feature {v, c_v, false, val.first, name};
+ };
+
+ auto set_feature = [&rs, &ci, v] (const feature& f)
+ {
+ if (f.c_value && *f.c_value != f.result)
+ {
+ fail << f.name_ << " cannot be "
+ << (*f.c_value ? "enabled" : "disabled") << " for "
+ << project (rs) << '@' << rs <<
+ info << "C++ language standard is "
+ << (v != nullptr ? v->c_str () : "compiler-default") <<
+ info << "C++ compiler is " << ci.signature <<
+ info << f.name_ << " state requested with config.cxx.features."
+ << f.name_;
+ }
+
+ f.value_ = f.result;
+ };
- bool modules (false);
- auto& v_m (vp.insert<bool> ("cxx.features.modules"));
+ feature modules (get_feature ("modules"));
+ //feature concepts (get_feature ("concepts"));
// NOTE: see also module sidebuild subproject if changing anything about
// modules here.
@@ -301,8 +349,7 @@ namespace build2
// Unless disabled by the user, try to enable C++ modules.
//
- lookup l;
- if (!(l = rs[v_m]) || cast<bool> (l))
+ if (!modules.value || *modules.value)
{
switch (ct)
{
@@ -315,7 +362,7 @@ namespace build2
// M;` syntax. And 16.4 (19.24) supports the global module
// fragment.
//
- if (mj > 19 || (mj == 19 && mi >= (l ? 10 : 12)))
+ if (mj > 19 || (mj == 19 && mi >= (modules.value ? 10 : 12)))
{
prepend (
mj > 19 || mi >= 24 ?
@@ -331,27 +378,19 @@ namespace build2
}
case compiler_type::gcc:
{
- // We now use extended/experimental module mapper support which
- // is currently only available in our c++-modules-ex branch.
- // But let's allow forcing it to plain c++-modules in case
- // things got merged or the user feels adventurous.
+ // We use the module mapper support which is only available
+ // since GCC 11. And since we are not yet capable of supporting
+ // generated headers via the mapper, we require the user to
+ // explicitly request modules.
//
- // @@ TMP: revise: for now must be forced (also in modules
- // tests).
- //
- if (mj >= 11 &&
- l &&
- ci.version.build.find ("c++-modules")
- /*
- ci.version.build.find (l
- ? "c++-modules"
- : "c++-modules-ex")*/ != string::npos)
+ if (mj >= 11 && modules.value)
{
// Defines __cpp_modules=201907. @@ TMP: confirm.
//
prepend ("-fmodules-ts");
modules = true;
}
+
break;
}
case compiler_type::clang:
@@ -369,12 +408,13 @@ namespace build2
//
// Also see Clang modules support hack in cc::compile.
//
- if (l)
+ if (modules.value)
{
prepend ("-D__cpp_modules=201704"); // p0629r0
mode.push_back ("-fmodules-ts"); // For the hack to work.
modules = true;
}
+
break;
}
case compiler_type::icc:
@@ -383,8 +423,8 @@ namespace build2
}
}
- rs.assign (v_m) = modules;
- //rs.assign (v_c) = concepts;
+ set_feature (modules);
+ //set_feature (concepts);
}
static const char* const hinters[] = {"c", nullptr};
@@ -440,16 +480,42 @@ namespace build2
vp.insert<strings> ("config.cxx.aoptions"),
vp.insert<strings> ("config.cxx.libs"),
- // List of translatable headers. Inclusions of such headers are
+ // Headers and header groups whose inclusion should or should not be
// translated to the corresponding header unit imports.
//
// A header can be specified either as an absolute and normalized path
- // or as a <>-style include name. The latter kind is automatically
- // translated to the absolute form based on the compiler's system (as
- // opposed to -I) header search paths. Note also that all entries must
- // be specified before loading the cxx module.
+ // or as a <>-style include file or file pattern (for example,
+ // <vector>, <boost/**.hpp>). The latter kind is automatically
+ // resolved to the absolute form based on the compiler's system (as
+ // opposed to project's) header search paths.
+ //
+ // Currently recognized header groups are:
+ //
+ // std-importable -- translate importable standard library headers
+ // std -- translate all standard library headers
+ // all-importable -- translate all importable headers
+ // all -- translate all headers
+ //
+ // Note that a header may belong to multiple groups which are looked
+ // up from the most to least specific, for example: <vector>,
+ // std-importable, std, all-importable, all.
+ //
+ // A header or group can also be excluded from being translated, for
+ // example:
+ //
+ // std-importable <vector>@false
+ //
+ // The config.cxx.translate_include value is prepended (merged with
+ // override) into cxx.translate_include while loading the cxx.config
+ // module. The headers and header groups in cxx.translate_include are
+ // resolved while loading the cxx module. For example:
+ //
+ // cxx.translate_include = <map>@false # Can be overriden.
+ // using cxx.config
+ // cxx.translate_include =+ <set>@false # Cannot be overriden.
+ // using cxx
//
- &vp.insert<strings> ("config.cxx.translatable_headers"),
+ &vp.insert<cc::translatable_headers> ("config.cxx.translate_include"),
vp.insert<process_path_ex> ("cxx.path"),
vp.insert<strings> ("cxx.mode"),
@@ -466,7 +532,7 @@ namespace build2
vp.insert<strings> ("cxx.aoptions"),
vp.insert<strings> ("cxx.libs"),
- &vp.insert<strings> ("cxx.translatable_headers"),
+ &vp.insert<cc::translatable_headers> ("cxx.translate_include"),
vp["cc.poptions"],
vp["cc.coptions"],
@@ -494,6 +560,7 @@ namespace build2
vp["cc.type"],
vp["cc.system"],
vp["cc.module_name"],
+ vp["cc.importable"],
vp["cc.reprocess"],
// Ability to signal that source is already (partially) preprocessed.
@@ -548,6 +615,7 @@ namespace build2
//
vp.insert_alias (d.c_runtime, "cxx.runtime");
vp.insert_alias (d.c_module_name, "cxx.module_name");
+ vp.insert_alias (d.c_importable, "cxx.importable");
auto& m (extra.set_module (new config_module (move (d))));
m.guess (rs, loc, extra.hints);
@@ -671,7 +739,7 @@ namespace build2
};
auto& m (extra.set_module (new module (move (d))));
- m.init (rs, loc, extra.hints);
+ m.init (rs, loc, extra.hints, *cm.x_info);
return true;
}
diff --git a/libbuild2/dist/operation.cxx b/libbuild2/dist/operation.cxx
index 2fa953d..ac76a62 100644
--- a/libbuild2/dist/operation.cxx
+++ b/libbuild2/dist/operation.cxx
@@ -6,8 +6,6 @@
#include <libbutl/sha1.mxx>
#include <libbutl/sha256.mxx>
-#include <libbutl/path-pattern.mxx>
-
#include <libbuild2/file.hxx>
#include <libbuild2/dump.hxx>
#include <libbuild2/scope.hxx>
@@ -599,7 +597,7 @@ namespace build2
(rs->out_path () != t.dir && rs->src_path () != t.dir))
fail << "dist meta-operation target must be project root directory";
- if (rs->out_path () == rs->src_path ())
+ if (rs->out_eq_src ())
fail << "in-tree distribution of target " << t <<
info << "distribution requires out-of-tree build";
diff --git a/libbuild2/dump.cxx b/libbuild2/dump.cxx
index 95c7cae..7cd95dd 100644
--- a/libbuild2/dump.cxx
+++ b/libbuild2/dump.cxx
@@ -438,7 +438,7 @@ namespace build2
scope_map::const_iterator& i,
bool rel)
{
- const scope& p (i->second);
+ const scope& p (*i->second.scope);
const dir_path& d (i->first);
++i;
@@ -484,7 +484,8 @@ namespace build2
vb = true;
}
- // Nested scopes of which we are an immediate parent.
+ // Nested scopes of which we are an immediate parent. Only consider the
+ // out hierarchy.
//
// Note that because we use the logical (rather than physical) parent, we
// will be printing the logical scope hierarchy (i.e., a project with
@@ -492,7 +493,7 @@ namespace build2
// scope).
//
for (auto e (p.ctx.scopes.end ());
- i != e && i->second.parent_scope () == &p; )
+ i != e && i->second.out && i->second.scope->parent_scope () == &p; )
{
if (vb)
{
@@ -541,8 +542,8 @@ namespace build2
void
dump (const context& c, optional<action> a)
{
- auto i (c.scopes.cbegin ());
- assert (&i->second == &c.global_scope);
+ auto i (c.scopes.begin ());
+ assert (i->second.scope == &c.global_scope);
// We don't lock diag_stream here as dump() is supposed to be called from
// the main thread prior/after to any other threads being spawned.
@@ -556,9 +557,9 @@ namespace build2
void
dump (const scope& s, const char* cind)
{
- const scope_map_base& m (s.ctx.scopes); // Iterator interface.
- auto i (m.find (s.out_path ()));
- assert (i != m.end () && &i->second == &s);
+ const scope_map& m (s.ctx.scopes);
+ auto i (m.find_exact (s.out_path ()));
+ assert (i != m.end () && i->second.scope == &s);
string ind (cind);
ostream& os (*diag_stream);
diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx
index 5d1487f..87e21a6 100644
--- a/libbuild2/file.cxx
+++ b/libbuild2/file.cxx
@@ -399,7 +399,7 @@ namespace build2
const dir_path& src_root)
{
auto i (ctx.scopes.rw ().insert (out_root, true /* root */));
- scope& rs (i->second);
+ scope& rs (*i->second.scope);
// Set out_path. Note that src_path is set in setup_root() below.
//
@@ -457,9 +457,17 @@ namespace build2
const dir_path& d (cast<dir_path> (v));
if (s.src_path_ == nullptr)
- s.src_path_ = &d;
+ {
+ if (*s.out_path_ != d)
+ {
+ auto i (ctx.scopes.rw (s).insert (s, d));
+ s.src_path_ = &i->first;
+ }
+ else
+ s.src_path_ = s.out_path_;
+ }
else
- assert (s.src_path_ == &d);
+ assert (*s.src_path_ == d);
s.assign (ctx.var_forwarded) = forwarded;
}
@@ -469,7 +477,7 @@ namespace build2
const dir_path& out_base,
const dir_path& src_base)
{
- scope& s (i->second);
+ scope& s (*i->second.scope);
context& ctx (s.ctx);
// Set src/out_base variables.
@@ -496,7 +504,15 @@ namespace build2
assert (*s.out_path_ == out_base);
if (s.src_path_ == nullptr)
- s.src_path_ = &cast<dir_path> (sv);
+ {
+ if (out_base != src_base)
+ {
+ auto i (ctx.scopes.rw (s).insert (s, src_base));
+ s.src_path_ = &i->first;
+ }
+ else
+ s.src_path_ = s.out_path_;
+ }
else
assert (*s.src_path_ == src_base);
@@ -504,21 +520,22 @@ namespace build2
}
pair<scope&, scope*>
- switch_scope (scope& root, const dir_path& p, bool proj)
+ switch_scope (scope& root, const dir_path& out_base, bool proj)
{
// First, enter the scope into the map and see if it is in any project. If
// it is not, then there is nothing else to do.
//
- auto i (root.ctx.scopes.rw (root).insert (p));
- scope& base (i->second);
+ auto i (root.ctx.scopes.rw (root).insert (out_base));
+ scope& base (*i->second.scope);
scope* rs (nullptr);
if (proj && (rs = base.root_scope ()) != nullptr)
{
- // Path p can be src_base or out_base. Figure out which one it is.
+ // The path must be in the out (since we've inserted it as out into the
+ // scope map).
//
- dir_path out_base (p.sub (rs->out_path ()) ? p : out_src (p, *rs));
+ assert (out_base.sub (rs->out_path ()));
// Create and bootstrap root scope(s) of subproject(s) that this scope
// may belong to. If any were created, load them. Note that we need to
@@ -534,8 +551,7 @@ namespace build2
// Now we can figure out src_base and finish setting the scope.
//
- dir_path src_base (src_out (out_base, *rs));
- setup_base (i, move (out_base), move (src_base));
+ setup_base (i, out_base, src_out (out_base, *rs));
}
return pair<scope&, scope*> (base, rs);
@@ -1316,7 +1332,7 @@ namespace build2
// probably be tried first since that src_root was explicitly configured
// by the user. After that, #2 followed by #1 seems reasonable.
//
- scope& rs (create_root (ctx, out_root, dir_path ())->second);
+ scope& rs (*create_root (ctx, out_root, dir_path ())->second.scope);
bool bstrapped (bootstrapped (rs));
@@ -1383,7 +1399,7 @@ namespace build2
// The same logic to src_root as in create_bootstrap_outer().
//
- scope& rs (create_root (ctx, out_root, dir_path ())->second);
+ scope& rs (*create_root (ctx, out_root, dir_path ())->second.scope);
optional<bool> altn;
if (!bootstrapped (rs))
@@ -1618,7 +1634,7 @@ namespace build2
assert (!forwarded || out_root != src_root);
auto i (create_root (ctx, out_root, src_root));
- scope& rs (i->second);
+ scope& rs (*i->second.scope);
if (!bootstrapped (rs))
{
@@ -2327,7 +2343,7 @@ namespace build2
{
bool top (proot == nullptr);
- root = &create_root (ctx, out_root, src_root)->second;
+ root = create_root (ctx, out_root, src_root)->second.scope;
bool bstrapped (bootstrapped (*root));
diff --git a/libbuild2/file.hxx b/libbuild2/file.hxx
index e0291fe..0c4ad62 100644
--- a/libbuild2/file.hxx
+++ b/libbuild2/file.hxx
@@ -146,7 +146,7 @@ namespace build2
// second.
//
LIBBUILD2_SYMEXPORT pair<scope&, scope*>
- switch_scope (scope& root, const dir_path&, bool project = true);
+ switch_scope (scope& root, const dir_path& out_base, bool project = true);
// Bootstrap and optionally load an ad hoc (sub)project (i.e., the kind that
// is not discovered and loaded automatically by bootstrap/load functions
diff --git a/libbuild2/function.hxx b/libbuild2/function.hxx
index 8fdf8f4..6654257 100644
--- a/libbuild2/function.hxx
+++ b/libbuild2/function.hxx
@@ -4,7 +4,6 @@
#ifndef LIBBUILD2_FUNCTION_HXX
#define LIBBUILD2_FUNCTION_HXX
-#include <map>
#include <utility> // index_sequence
#include <type_traits> // aligned_storage
@@ -196,7 +195,7 @@ namespace build2
class LIBBUILD2_SYMEXPORT function_map
{
public:
- using map_type = std::map<string, function_overloads>;
+ using map_type = map<string, function_overloads>;
using iterator = map_type::iterator;
using const_iterator = map_type::const_iterator;
diff --git a/libbuild2/install/utility.hxx b/libbuild2/install/utility.hxx
index ee78e17..2c0ca56 100644
--- a/libbuild2/install/utility.hxx
+++ b/libbuild2/install/utility.hxx
@@ -28,7 +28,7 @@ namespace build2
*s.var_pool ().find ("install")));
if (r.second) // Already set by the user?
- r.first.get () = path_cast<path> (move (d));
+ r.first = path_cast<path> (move (d));
}
template <typename T>
@@ -46,7 +46,7 @@ namespace build2
*s.var_pool ().find ("install.mode")));
if (r.second) // Already set by the user?
- r.first.get () = move (m);
+ r.first = move (m);
}
template <typename T>
diff --git a/libbuild2/module.hxx b/libbuild2/module.hxx
index 770e694..8223bae 100644
--- a/libbuild2/module.hxx
+++ b/libbuild2/module.hxx
@@ -4,8 +4,6 @@
#ifndef LIBBUILD2_MODULE_HXX
#define LIBBUILD2_MODULE_HXX
-#include <map>
-
#include <libbuild2/types.hxx>
#include <libbuild2/forward.hxx>
#include <libbuild2/utility.hxx>
@@ -275,7 +273,7 @@ namespace build2
// A NULL entry for the main module indicates that a module library was not
// found.
//
- using loaded_module_map = std::map<string, const module_functions*>;
+ using loaded_module_map = map<string, const module_functions*>;
// The loaded_modules map is locked per top-level (as opposed to nested)
// context (see context.hxx for details).
diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx
index ab4fa48..def4654 100644
--- a/libbuild2/parser.cxx
+++ b/libbuild2/parser.cxx
@@ -7,7 +7,6 @@
#include <iostream> // cout
#include <libbutl/filesystem.mxx> // path_search
-#include <libbutl/path-pattern.mxx>
#include <libbuild2/rule.hxx>
#include <libbuild2/dump.hxx>
@@ -4754,21 +4753,26 @@ namespace build2
//
bool unique (r.empty () && path_pattern_recursive (path (p)) <= 1);
- function<void (string&&, optional<string>&&)> appf;
+ struct data
+ {
+ const optional<string>& e;
+ const dir_path& sp;
+ function<void (string&&, optional<string>&&)> appf;
+
+ } d {e, *sp, nullptr};
+
if (unique)
- appf = [a, &append] (string&& v, optional<string>&& e)
+ d.appf = [a, &append] (string&& v, optional<string>&& e)
{
append (move (v), move (e), a);
};
else
- appf = [a, &include_match] (string&& v, optional<string>&& e)
+ d.appf = [a, &include_match] (string&& v, optional<string>&& e)
{
include_match (move (v), move (e), a);
};
- auto process = [this, &e, &appf, sp] (path&& m,
- const string& p,
- bool interm)
+ auto process = [&d, this] (path&& m, const string& p, bool interm)
{
// Ignore entries that start with a dot unless the pattern that
// matched them also starts with a dot. Also ignore directories
@@ -4780,14 +4784,14 @@ namespace build2
(root_ != nullptr &&
root_->root_extra != nullptr &&
m.to_directory () &&
- exists (*sp / m / root_->root_extra->buildignore_file)))
+ exists (d.sp / m / root_->root_extra->buildignore_file)))
return !interm;
// Note that we have to make copies of the extension since there will
// multiple entries for each pattern.
//
if (!interm)
- appf (move (m).representation (), optional<string> (e));
+ d.appf (move (m).representation (), optional<string> (d.e));
return true;
};
diff --git a/libbuild2/parser.hxx b/libbuild2/parser.hxx
index 1e924f0..3efb94a 100644
--- a/libbuild2/parser.hxx
+++ b/libbuild2/parser.hxx
@@ -531,7 +531,7 @@ namespace build2
// project. So both must be saved and restored.
//
void
- switch_scope (const dir_path&);
+ switch_scope (const dir_path& out_base);
void
process_default_target (token&);
diff --git a/libbuild2/rule-map.hxx b/libbuild2/rule-map.hxx
index df59548..8014d02 100644
--- a/libbuild2/rule-map.hxx
+++ b/libbuild2/rule-map.hxx
@@ -4,8 +4,6 @@
#ifndef LIBBUILD2_RULE_MAP_HXX
#define LIBBUILD2_RULE_MAP_HXX
-#include <map>
-
#include <libbutl/prefix-map.mxx>
#include <libbuild2/types.hxx>
@@ -19,7 +17,7 @@ namespace build2
using hint_rule_map =
butl::prefix_map<string, reference_wrapper<const rule>, '.'>;
- using target_type_rule_map = std::map<const target_type*, hint_rule_map>;
+ using target_type_rule_map = map<const target_type*, hint_rule_map>;
// This is an "indexed map" with operation_id being the index. Entry
// with id 0 is a wildcard.
diff --git a/libbuild2/scope.cxx b/libbuild2/scope.cxx
index 53c859f..29e0c55 100644
--- a/libbuild2/scope.cxx
+++ b/libbuild2/scope.cxx
@@ -944,10 +944,21 @@ namespace build2
auto scope_map::
insert (const dir_path& k, bool root) -> iterator
{
- scope_map_base& m (*this);
+ auto er (map_.emplace (k, true /* out */));
- auto er (m.emplace (k, scope (ctx, true /* global */)));
- scope& s (er.first->second);
+ if (er.second)
+ {
+ er.first->second.scope = new scope (ctx, true /* global */);
+ }
+ else if (!er.first->second.out)
+ {
+ // This can potentially be triggered if we use the same directory as one
+ // project's out and another's src.
+ //
+ fail << "directory " << k << " is used as both src and out scope";
+ }
+
+ scope& s (*er.first->second.scope);
// If this is a new scope, update the parent chain.
//
@@ -958,14 +969,14 @@ namespace build2
// Update scopes of which we are a new parent/root (unless this is the
// global scope). Also find our parent while at it.
//
- if (m.size () > 1)
+ if (map_.size () > 1)
{
// The first entry is ourselves.
//
- auto r (m.find_sub (k));
+ auto r (map_.find_sub (k));
for (++r.first; r.first != r.second; ++r.first)
{
- scope& c (r.first->second);
+ scope& c (*r.first->second.scope);
// The first scope of which we are a parent is the least (shortest)
// one which means there is no other scope between it and our
@@ -995,10 +1006,10 @@ namespace build2
{
// Upgrade to root scope.
//
- auto r (m.find_sub (k));
+ auto r (map_.find_sub (k));
for (++r.first; r.first != r.second; ++r.first)
{
- scope& c (r.first->second);
+ scope& c (*r.first->second.scope);
if (c.root_ == s.root_) // No intermediate root.
c.root_ = &s;
@@ -1010,14 +1021,36 @@ namespace build2
return er.first;
}
+ auto scope_map::
+ insert (scope& s, const dir_path& k) -> iterator
+ {
+ auto er (map_.emplace (k, false /* out */));
+
+ if (er.second)
+ {
+ er.first->second.scope = &s;
+ }
+ else if (!er.first->second.out)
+ {
+ assert (er.first->second.scope == &s);
+ }
+ else
+ {
+ // This can be triggered, for example, by specifying a variable override
+ // with src instead of out directory.
+ //
+ fail << "directory " << k << " is used as both src and out scope";
+ }
+
+ return er.first;
+ }
+
scope& scope_map::
find (const dir_path& k)
{
assert (k.normalized (false)); // Allow non-canonical dir separators.
-
- scope_map_base& m (*this);
- auto i (m.find_sup (k));
- assert (i != m.end ()); // Should have global scope.
- return i->second;
+ auto i (map_.find_sup (k));
+ assert (i != map_.end ()); // Should have global scope.
+ return *i->second.scope;
}
}
diff --git a/libbuild2/scope.hxx b/libbuild2/scope.hxx
index d441267..11fdfe4 100644
--- a/libbuild2/scope.hxx
+++ b/libbuild2/scope.hxx
@@ -4,7 +4,6 @@
#ifndef LIBBUILD2_SCOPE_HXX
#define LIBBUILD2_SCOPE_HXX
-#include <map>
#include <unordered_set>
#include <libbuild2/types.hxx>
@@ -27,7 +26,7 @@ namespace build2
{
class dir;
- using subprojects = std::map<project_name, dir_path>;
+ using subprojects = map<project_name, dir_path>;
LIBBUILD2_SYMEXPORT ostream&
operator<< (ostream&, const subprojects&); // Print as name@dir sequence.
@@ -44,8 +43,10 @@ namespace build2
const dir_path& out_path () const {return *out_path_;}
const dir_path& src_path () const {return *src_path_;}
- // The first is a pointer to the key in scope_map. The second is a pointer
- // to the src_root/base variable value, if any (i.e., it can be NULL).
+ bool out_eq_src () const {return out_path_ == src_path_;}
+
+ // These are pointers to the keys in scope_map. The second can be NULL
+ // during bootstrap until initialized.
//
const dir_path* out_path_ = nullptr;
const dir_path* src_path_ = nullptr;
@@ -140,12 +141,20 @@ namespace build2
return lookup (var, &tt, &tn).first;
}
+ lookup_type
+ lookup (const variable& var,
+ const target_type& tt, const string& tn,
+ const target_type& gt, const string& gn) const
+ {
+ return lookup (var, &tt, &tn, &gt, &gn).first;
+ }
+
pair<lookup_type, size_t>
lookup (const variable& var,
- const target_type* tt = nullptr,
- const string* tn = nullptr) const
+ const target_type* tt = nullptr, const string* tn = nullptr,
+ const target_type* gt = nullptr, const string* gn = nullptr) const
{
- auto p (lookup_original (var, tt, tn));
+ auto p (lookup_original (var, tt, tn, gt, gn));
return var.overrides == nullptr ? p : lookup_override (var, move (p));
}
@@ -416,8 +425,7 @@ namespace build2
function<callback> post;
};
- using operation_callback_map = std::multimap<action_id,
- operation_callback>;
+ using operation_callback_map = multimap<action_id, operation_callback>;
operation_callback_map operation_callbacks;
@@ -621,21 +629,53 @@ namespace build2
}
};
- // Scope map.
+ // Scope map. Protected by the phase mutex.
//
- // Protected by the phase mutex. Note that the scope map is only for paths
- // from the out tree.
+ // While it contains both out and src paths, the latter is not available
+ // during bootstrap (see setup_root() and setup_base() for details).
//
- using scope_map_base = dir_path_map<scope>;
-
- class scope_map: public scope_map_base
+ class scope_map
{
public:
+ struct scope_ptr
+ {
+ using scope_type = build2::scope;
+
+ scope_type* scope;
+ bool out;
+
+ scope_ptr (bool o): scope (nullptr), out (o) {}
+ ~scope_ptr () {if (out) delete scope;}
+
+ scope_ptr (scope_ptr&& x) // For GCC 4.9
+ : scope (x.scope), out (x.out)
+ {
+ x.scope = nullptr;
+ }
+
+ scope_ptr& operator= (scope_ptr&&) = delete;
+
+ scope_ptr (const scope_ptr&) = delete;
+ scope_ptr& operator= (const scope_ptr&) = delete;
+ };
+
+ using map_type = dir_path_map<scope_ptr>;
+
+ using iterator = map_type::iterator;
+ using const_iterator = map_type::const_iterator;
+
+ // Insert a scope given its out path.
+ //
// Note that we assume the first insertion into the map is always the
// global scope with empty key.
//
LIBBUILD2_SYMEXPORT iterator
- insert (const dir_path&, bool root = false);
+ insert (const dir_path& our_path, bool root = false);
+
+ // Insert a shallow reference to the scope for its src path.
+ //
+ LIBBUILD2_SYMEXPORT iterator
+ insert (scope&, const dir_path& src_path);
// Find the most qualified scope that encompasses this path.
//
@@ -661,6 +701,10 @@ namespace build2
return find (path_cast<dir_path> (p));
}
+ const_iterator begin () const {return map_.begin ();}
+ const_iterator end () const {return map_.end ();}
+ const_iterator find_exact (const dir_path& d) const {return map_.find (d);}
+
// RW access.
//
public:
@@ -685,6 +729,7 @@ namespace build2
private:
context& ctx;
+ map_type map_;
};
}
diff --git a/libbuild2/script/regex.hxx b/libbuild2/script/regex.hxx
index 6d2c5c6..e043c99 100644
--- a/libbuild2/script/regex.hxx
+++ b/libbuild2/script/regex.hxx
@@ -25,10 +25,10 @@ namespace build2
enum class char_flags: uint16_t
{
icase = 0x1, // Case-insensitive match.
- idot = 0x2, // Invert '.' escaping.
+ idot = 0x2, // Invert '.' escaping.
- none = 0
- };
+ none = 0
+ };
// Restricts valid standard flags to just {icase}, extends with custom
// flags {idot}.
@@ -66,9 +66,9 @@ namespace build2
enum class line_type
{
special,
- literal,
- regex
- };
+ literal,
+ regex
+ };
struct line_char
{
@@ -625,7 +625,8 @@ namespace std
// specialize the class template to behave as the __match_any<line_char>
// instantiation does (that luckily has all the functions in place).
//
-#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION <= 10000
+//#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION <= 11000
+#ifdef _LIBCPP_VERSION
template <>
class __match_any_but_newline<build2::script::regex::line_char>
: public __match_any<build2::script::regex::line_char>
diff --git a/libbuild2/script/run.cxx b/libbuild2/script/run.cxx
index 58ba23d..0b49dea 100644
--- a/libbuild2/script/run.cxx
+++ b/libbuild2/script/run.cxx
@@ -15,7 +15,6 @@
#include <libbutl/builtin.mxx>
#include <libbutl/fdstream.mxx> // fdopen_mode, fddup()
#include <libbutl/filesystem.mxx> // path_search()
-#include <libbutl/path-pattern.mxx>
#include <libbuild2/filesystem.hxx>
#include <libbuild2/diagnostics.hxx>
diff --git a/libbuild2/search.cxx b/libbuild2/search.cxx
index 25a4199..fca19ea 100644
--- a/libbuild2/search.cxx
+++ b/libbuild2/search.cxx
@@ -175,7 +175,7 @@ namespace build2
if (tk.out->empty ())
{
- if (s->out_path () != s->src_path ())
+ if (!s->out_eq_src ())
out = out_src (d, *s->root_scope ());
}
else
diff --git a/libbuild2/target-key.hxx b/libbuild2/target-key.hxx
index 62bcc25..2cc58f9 100644
--- a/libbuild2/target-key.hxx
+++ b/libbuild2/target-key.hxx
@@ -4,7 +4,6 @@
#ifndef LIBBUILD2_TARGET_KEY_HXX
#define LIBBUILD2_TARGET_KEY_HXX
-#include <map>
#include <cstring> // strcmp()
#include <libbuild2/types.hxx>
diff --git a/libbuild2/target-type.hxx b/libbuild2/target-type.hxx
index 88171f5..913432e 100644
--- a/libbuild2/target-type.hxx
+++ b/libbuild2/target-type.hxx
@@ -4,8 +4,6 @@
#ifndef LIBBUILD2_TARGET_TYPE_HXX
#define LIBBUILD2_TARGET_TYPE_HXX
-#include <map>
-
#include <libbuild2/types.hxx>
#include <libbuild2/forward.hxx>
#include <libbuild2/utility.hxx>
@@ -203,8 +201,8 @@ namespace build2
bool d_;
};
- std::map<string, target_type_ref> type_map_;
- std::map<string, reference_wrapper<const target_type>> file_map_;
+ map<string, target_type_ref> type_map_;
+ map<string, reference_wrapper<const target_type>> file_map_;
};
}
diff --git a/libbuild2/test/script/parser.hxx b/libbuild2/test/script/parser.hxx
index f8c3f21..f118ad3 100644
--- a/libbuild2/test/script/parser.hxx
+++ b/libbuild2/test/script/parser.hxx
@@ -4,6 +4,7 @@
#ifndef LIBBUILD2_TEST_SCRIPT_PARSER_HXX
#define LIBBUILD2_TEST_SCRIPT_PARSER_HXX
+#include <set>
#include <unordered_map>
#include <libbuild2/types.hxx>
diff --git a/libbuild2/types.hxx b/libbuild2/types.hxx
index 60101b3..6877f92 100644
--- a/libbuild2/types.hxx
+++ b/libbuild2/types.hxx
@@ -13,6 +13,7 @@
# include <libbuild2/config.hxx>
#endif
+#include <map>
#include <array>
#include <tuple>
#include <vector>
@@ -92,6 +93,8 @@ namespace build2
using std::shared_ptr;
using std::weak_ptr;
+ using std::map;
+ using std::multimap;
using std::array;
using std::vector;
using butl::vector_view; // <libbutl/vector-view.mxx>
diff --git a/libbuild2/utility.hxx b/libbuild2/utility.hxx
index a07d928..4cfdf15 100644
--- a/libbuild2/utility.hxx
+++ b/libbuild2/utility.hxx
@@ -4,7 +4,6 @@
#ifndef LIBBUILD2_UTILITY_HXX
#define LIBBUILD2_UTILITY_HXX
-#include <map>
#include <tuple> // make_tuple()
#include <memory> // make_shared()
#include <string> // to_string()
@@ -18,6 +17,7 @@
#include <libbutl/utility.mxx> // combine_hash(), reverse_iterate(), etc
#include <libbutl/fdstream.mxx>
+#include <libbutl/path-pattern.mxx>
#include <libbuild2/types.hxx>
#include <libbuild2/forward.hxx>
@@ -94,6 +94,10 @@ namespace build2
using butl::open_file_or_stdin;
using butl::open_file_or_stdout;
+ // <libbutl/path-pattern.mxx>
+ //
+ using butl::path_pattern;
+
// Diagnostics state (verbosity level, etc; see <libbuild2/diagnostics.hxx>).
//
// Note on naming of values (here and in the global state below) that come
@@ -561,7 +565,7 @@ namespace build2
}
private:
- std::map<string, T> cache_;
+ map<string, T> cache_;
mutable mutex mutex_;
};
diff --git a/libbuild2/variable.cxx b/libbuild2/variable.cxx
index f5476b5..917b9e7 100644
--- a/libbuild2/variable.cxx
+++ b/libbuild2/variable.cxx
@@ -1736,7 +1736,7 @@ namespace build2
return pair<value_data*, const variable&> (r, p.second);
}
- pair<reference_wrapper<value>, bool> variable_map::
+ pair<value&, bool> variable_map::
insert (const variable& var, bool typed)
{
assert (!global_ || ctx->phase == run_phase::load);
@@ -1756,7 +1756,7 @@ namespace build2
r.version++;
- return make_pair (reference_wrapper<value> (r), p.second);
+ return pair<value&, bool> (r, p.second);
}
// variable_type_map
@@ -1832,8 +1832,26 @@ namespace build2
value_traits<vector<pair<string, string>>>;
template struct LIBBUILD2_DEFEXPORT
- value_traits<std::map<string, string>>;
+ value_traits<vector<pair<string, optional<string>>>>;
template struct LIBBUILD2_DEFEXPORT
- value_traits<std::map<project_name, dir_path>>;
+ value_traits<vector<pair<optional<string>, string>>>;
+
+ template struct LIBBUILD2_DEFEXPORT
+ value_traits<vector<pair<string, optional<bool>>>>;
+
+ template struct LIBBUILD2_DEFEXPORT
+ value_traits<map<string, string>>;
+
+ template struct LIBBUILD2_DEFEXPORT
+ value_traits<map<string, optional<string>>>;
+
+ template struct LIBBUILD2_DEFEXPORT
+ value_traits<map<optional<string>, string>>;
+
+ template struct LIBBUILD2_DEFEXPORT
+ value_traits<map<string, optional<bool>>>;
+
+ template struct LIBBUILD2_DEFEXPORT
+ value_traits<map<project_name, dir_path>>;
}
diff --git a/libbuild2/variable.hxx b/libbuild2/variable.hxx
index a671978..898648c 100644
--- a/libbuild2/variable.hxx
+++ b/libbuild2/variable.hxx
@@ -4,7 +4,6 @@
#ifndef LIBBUILD2_VARIABLE_HXX
#define LIBBUILD2_VARIABLE_HXX
-#include <map>
#include <set>
#include <type_traits> // aligned_storage
#include <unordered_map>
@@ -339,15 +338,17 @@ namespace build2
// Assign/Append/Prepend.
//
public:
- // Assign/append a typed value. For assign, LHS should be either of the
- // same type or untyped. For append, LHS should be either of the same type
- // or untyped and NULL.
+ // Assign/append/prepend a typed value. For assign, LHS should be either
+ // of the same type or untyped. For append, LHS should be either of the
+ // same type or untyped and NULL.
//
template <typename T> value& operator= (T);
template <typename T> value& operator+= (T);
+ template <typename T> value& prepend (T);
value& operator= (names);
value& operator+= (names);
+ //value& prepend (names); // See below.
template <typename T> value& operator= (T* v) {
return v != nullptr ? *this = *v : *this = nullptr;}
@@ -355,8 +356,12 @@ namespace build2
template <typename T> value& operator+= (T* v) {
return v != nullptr ? *this += *v : *this;}
+ template <typename T> value& prepend (T* v) {
+ return v != nullptr ? prepend (*v) : *this;}
+
value& operator= (const char* v) {return *this = string (v);}
value& operator+= (const char* v) {return *this += string (v);}
+ value& prepend (const char* v) {return prepend (string (v));}
// Assign/append/prepend raw data. Variable is optional and is only used
// for diagnostics.
@@ -610,6 +615,9 @@ namespace build2
// static const build2::value_type value_type;
// };
+ template <typename T>
+ struct value_traits<const T>: value_traits<T> {};
+
// Convert name to a simple value. Throw invalid_argument (with a message)
// if the name is not a valid representation of value (in which case the
// name remains unchanged for diagnostics). The second version is called for
@@ -897,6 +905,9 @@ namespace build2
// half of a pair). If both are empty then this is an empty value (and not a
// pair of two empties).
//
+ // @@ Maybe we should redo this with optional<> to signify which half can
+ // be missing?
+ //
template <>
struct LIBBUILD2_SYMEXPORT value_traits<name_pair>
{
@@ -1004,9 +1015,67 @@ namespace build2
static const build2::value_type value_type;
};
+ // optional<T>
+ //
+ // This is an incomplete implementation meant to provide enough support only
+ // to be usable as elements of containers.
+ //
+ template <typename T>
+ struct value_traits<optional<T>>
+ {
+ static int compare (const optional<T>&, const optional<T>&);
+ };
+
+ // pair<F, S>
+ //
+ // Either F or S can be optional<T> making the corresponding half of the
+ // pair optional.
+ //
+ // This is an incomplete implementation meant to provide enough support only
+ // to be usable as elements of containers.
+ //
+ template <typename F, typename S>
+ struct pair_value_traits
+ {
+ static pair<F, S>
+ convert (name&&, name*, const char*, const char*, const variable*);
+
+ static void
+ reverse (const F&, const S&, names&);
+ };
+
+ template <typename F, typename S>
+ struct pair_value_traits<F, optional<S>>
+ {
+ static pair<F, optional<S>>
+ convert (name&&, name*, const char*, const char*, const variable*);
+
+ static void
+ reverse (const F&, const optional<S>&, names&);
+ };
+
+ template <typename F, typename S>
+ struct pair_value_traits<optional<F>, S>
+ {
+ static pair<optional<F>, S>
+ convert (name&&, name*, const char*, const char*, const variable*);
+
+ static void
+ reverse (const optional<F>&, const S&, names&);
+ };
+
+ template <typename F, typename S>
+ struct value_traits<pair<F, S>>: pair_value_traits<F, S>
+ {
+ static int compare (const pair<F, S>&, const pair<F, S>&);
+ };
+
// vector<T>
//
template <typename T>
+ struct vector_value_type;
+
+ template <typename T>
struct value_traits<vector<T>>
{
static_assert (sizeof (vector<T>) <= value::size_, "insufficient space");
@@ -1018,20 +1087,17 @@ namespace build2
static bool empty (const vector<T>& x) {return x.empty ();}
static const vector<T> empty_instance;
-
- // Make sure these are static-initialized together. Failed that VC will
- // make sure it's done in the wrong order.
- //
- struct value_type_ex: build2::value_type
- {
- string type_name;
- value_type_ex (value_type&&);
- };
- static const value_type_ex value_type;
+ static const vector_value_type<T> value_type;
};
// vector<pair<K, V>>
//
+ // Either K or V can be optional<T> making the corresponding half of the
+ // pair optional.
+ //
+ template <typename K, typename V>
+ struct pair_vector_value_type;
+
template <typename K, typename V>
struct value_traits<vector<pair<K, V>>>
{
@@ -1045,44 +1111,33 @@ namespace build2
static bool empty (const vector<pair<K, V>>& x) {return x.empty ();}
static const vector<pair<K, V>> empty_instance;
-
- // Make sure these are static-initialized together. Failed that VC will
- // make sure it's done in the wrong order.
- //
- struct value_type_ex: build2::value_type
- {
- string type_name;
- value_type_ex (value_type&&);
- };
- static const value_type_ex value_type;
+ static const pair_vector_value_type<K, V> value_type;
};
// map<K, V>
//
+ // Either K or V can be optional<T> making the key or value optional.
+ //
+ // Note that append/+= is non-overriding (like insert()) while prepend/=+
+ // is (like insert_or_assign()).
+ //
template <typename K, typename V>
- struct value_traits<std::map<K, V>>
+ struct map_value_type;
+
+ template <typename K, typename V>
+ struct value_traits<map<K, V>>
{
- template <typename K1, typename V1> using map = std::map<K1, V1>;
+ template <typename K1, typename V1> using map = map<K1, V1>;
static_assert (sizeof (map<K, V>) <= value::size_, "insufficient space");
static void assign (value&, map<K, V>&&);
static void append (value&, map<K, V>&&);
- static void prepend (value& v, map<K, V>&& x) {
- return append (v, move (x));}
+ static void prepend (value&, map<K, V>&&);
static bool empty (const map<K, V>& x) {return x.empty ();}
static const map<K, V> empty_instance;
-
- // Make sure these are static-initialized together. Failed that VC will
- // make sure it's done in the wrong order.
- //
- struct value_type_ex: build2::value_type
- {
- string type_name;
- value_type_ex (value_type&&);
- };
- static const value_type_ex value_type;
+ static const map_value_type<K, V> value_type;
};
// Explicitly pre-instantiate and export value_traits templates for
@@ -1102,10 +1157,28 @@ namespace build2
value_traits<vector<pair<string, string>>>;
extern template struct LIBBUILD2_DECEXPORT
- value_traits<std::map<string, string>>;
+ value_traits<vector<pair<string, optional<string>>>>;
+
+ extern template struct LIBBUILD2_DECEXPORT
+ value_traits<vector<pair<optional<string>, string>>>;
+
+ extern template struct LIBBUILD2_DECEXPORT
+ value_traits<vector<pair<string, optional<bool>>>>;
+
+ extern template struct LIBBUILD2_DECEXPORT
+ value_traits<map<string, string>>;
+
+ extern template struct LIBBUILD2_DECEXPORT
+ value_traits<map<string, optional<string>>>;
+
+ extern template struct LIBBUILD2_DECEXPORT
+ value_traits<map<optional<string>, string>>;
+
+ extern template struct LIBBUILD2_DECEXPORT
+ value_traits<map<string, optional<bool>>>;
extern template struct LIBBUILD2_DECEXPORT
- value_traits<std::map<project_name, dir_path>>;
+ value_traits<map<project_name, dir_path>>; // var_subprojects
// Project-wide (as opposed to global) variable overrides (see context ctor
// for details).
@@ -1523,7 +1596,7 @@ namespace build2
// will be NULL) was actually inserted. Similar to find(), if typed is
// false, leave the value untyped even if the variable is.
//
- pair<reference_wrapper<value>, bool>
+ pair<value&, bool>
insert (const variable&, bool typed = true);
pair<const_iterator, const_iterator>
@@ -1638,7 +1711,7 @@ namespace build2
stem_version (sver) {}
};
- using map_type = std::map<K, entry_type>;
+ using map_type = map<K, entry_type>;
map_type m_;
};
@@ -1660,7 +1733,7 @@ namespace build2
class variable_pattern_map
{
public:
- using map_type = std::map<string, variable_map>;
+ using map_type = map<string, variable_map>;
using const_iterator = map_type::const_iterator;
using const_reverse_iterator = map_type::const_reverse_iterator;
@@ -1688,7 +1761,7 @@ namespace build2
class LIBBUILD2_SYMEXPORT variable_type_map
{
public:
- using map_type = std::map<reference_wrapper<const target_type>,
+ using map_type = map<reference_wrapper<const target_type>,
variable_pattern_map>;
using const_iterator = map_type::const_iterator;
diff --git a/libbuild2/variable.ixx b/libbuild2/variable.ixx
index c8f9541..a84c012 100644
--- a/libbuild2/variable.ixx
+++ b/libbuild2/variable.ixx
@@ -114,6 +114,22 @@ namespace build2
return *this;
}
+ template <typename T>
+ inline value& value::
+ prepend (T v)
+ {
+ assert (type == &value_traits<T>::value_type || (type == nullptr && null));
+
+ // Prepare the receiving value.
+ //
+ if (type == nullptr)
+ type = &value_traits<T>::value_type;
+
+ value_traits<T>::prepend (*this, move (v));
+ null = false;
+ return *this;
+ }
+
inline value& value::
operator= (names v)
{
@@ -728,6 +744,31 @@ namespace build2
: s);
}
+ // optional<T>
+ //
+ template <typename T>
+ inline int value_traits<optional<T>>::
+ compare (const optional<T>& l, const optional<T>& r)
+ {
+ return l
+ ? (r ? value_traits<T>::compare (*l, *r) : 1)
+ : (r ? -1 : 0);
+ }
+
+ // pair<F, S> value
+ //
+ template <typename F, typename S>
+ inline int value_traits<pair<F, S>>::
+ compare (const pair<F, S>& l, const pair<F, S>& r)
+ {
+ int i (value_traits<F>::compare (l.first, r.first));
+
+ if (i == 0)
+ i = value_traits<S>::compare (l.second, r.second);
+
+ return i;
+ }
+
// vector<T> value
//
template <typename T>
@@ -812,7 +853,7 @@ namespace build2
// map<K, V> value
//
template <typename K, typename V>
- inline void value_traits<std::map<K, V>>::
+ inline void value_traits<map<K, V>>::
assign (value& v, map<K, V>&& x)
{
if (v)
@@ -822,7 +863,7 @@ namespace build2
}
template <typename K, typename V>
- inline void value_traits<std::map<K, V>>::
+ inline void value_traits<map<K, V>>::
append (value& v, map<K, V>&& x)
{
if (v)
@@ -842,6 +883,26 @@ namespace build2
new (&v.data_) map<K, V> (move (x));
}
+ template <typename K, typename V>
+ inline void value_traits<map<K, V>>::
+ prepend (value& v, map<K, V>&& x)
+ {
+ if (v)
+ {
+ map<K, V>& m (v.as<map<K, V>> ());
+
+ m.swap (x);
+
+ // Note that this will only move values. Keys (being const) are still
+ // copied.
+ //
+ m.insert (make_move_iterator (x.begin ()),
+ make_move_iterator (x.end ()));
+ }
+ else
+ new (&v.data_) map<K, V> (move (x));
+ }
+
// variable_pool
//
inline const variable& variable_pool::
diff --git a/libbuild2/variable.txx b/libbuild2/variable.txx
index 8fa1d7c..3e4a9f3 100644
--- a/libbuild2/variable.txx
+++ b/libbuild2/variable.txx
@@ -241,9 +241,228 @@ namespace build2
return value_traits<T>::compare (l.as<T> (), r.as<T> ());
}
- // vector<T> value
+ // pair<F, S> value
//
+ template <typename F, typename S>
+ pair<F, S> pair_value_traits<F, S>::
+ convert (name&& l, name* r,
+ const char* type, const char* what, const variable* var)
+ {
+ if (!l.pair)
+ {
+ diag_record dr (fail);
+
+ dr << type << ' ' << what << (*what != '\0' ? " " : "")
+ << "pair expected instead of '" << l << "'";
+
+ if (var != nullptr)
+ dr << " in variable " << var->name;
+ }
+
+ if (l.pair != '@')
+ {
+ diag_record dr (fail);
+
+ dr << "unexpected pair style for "
+ << type << ' ' << what << (*what != '\0' ? " " : "")
+ << "key-value pair '"
+ << l << "'" << l.pair << "'" << *r << "'";
+
+ if (var != nullptr)
+ dr << " in variable " << var->name;
+ }
+
+ try
+ {
+ F f (value_traits<F>::convert (move (l), nullptr));
+
+ try
+ {
+ S s (value_traits<S>::convert (move (*r), nullptr));
+
+ return pair<F, S> (move (f), move (s));
+ }
+ catch (const invalid_argument&)
+ {
+ diag_record dr (fail);
+
+ dr << "invalid " << value_traits<S>::value_type.name
+ << " second have of pair '" << *r << "'";
+
+ if (var != nullptr)
+ dr << " in variable " << var->name;
+
+ dr << endf;
+ }
+ }
+ catch (const invalid_argument&)
+ {
+ diag_record dr (fail);
+
+ dr << "invalid " << value_traits<F>::value_type.name
+ << " first have of pair '" << l << "'";
+
+ if (var != nullptr)
+ dr << " in variable " << var->name;
+
+ dr << endf;
+ }
+ }
+
+ template <typename F, typename S>
+ pair<F, optional<S>> pair_value_traits<F, optional<S>>::
+ convert (name&& l, name* r,
+ const char* type, const char* what, const variable* var)
+ {
+ if (l.pair && l.pair != '@')
+ {
+ diag_record dr (fail);
+
+ dr << "unexpected pair style for "
+ << type << ' ' << what << (*what != '\0' ? " " : "")
+ << "key-value pair '"
+ << l << "'" << l.pair << "'" << *r << "'";
+
+ if (var != nullptr)
+ dr << " in variable " << var->name;
+ }
+
+ try
+ {
+ F f (value_traits<F>::convert (move (l), nullptr));
+
+ try
+ {
+ optional<S> s;
+
+ if (l.pair)
+ s = value_traits<S>::convert (move (*r), nullptr);
+
+ return pair<F, optional<S>> (move (f), move (s));
+ }
+ catch (const invalid_argument&)
+ {
+ diag_record dr (fail);
+
+ dr << "invalid " << value_traits<S>::value_type.name
+ << " second have of pair '" << *r << "'";
+
+ if (var != nullptr)
+ dr << " in variable " << var->name;
+
+ dr << endf;
+ }
+ }
+ catch (const invalid_argument&)
+ {
+ diag_record dr (fail);
+
+ dr << "invalid " << value_traits<F>::value_type.name
+ << " first have of pair '" << l << "'";
+
+ if (var != nullptr)
+ dr << " in variable " << var->name;
+
+ dr << endf;
+ }
+ }
+ template <typename F, typename S>
+ pair<optional<F>, S> pair_value_traits<optional<F>, S>::
+ convert (name&& l, name* r,
+ const char* type, const char* what, const variable* var)
+ {
+ if (l.pair && l.pair != '@')
+ {
+ diag_record dr (fail);
+
+ dr << "unexpected pair style for "
+ << type << ' ' << what << (*what != '\0' ? " " : "")
+ << "key-value pair '"
+ << l << "'" << l.pair << "'" << *r << "'";
+
+ if (var != nullptr)
+ dr << " in variable " << var->name;
+ }
+
+ try
+ {
+ optional<F> f;
+
+ if (l.pair)
+ {
+ f = value_traits<F>::convert (move (l), nullptr);
+ l = move (*r); // Shift.
+ }
+
+ try
+ {
+ S s (value_traits<S>::convert (move (l), nullptr));
+
+ return pair<optional<F>, S> (move (f), move (s));
+ }
+ catch (const invalid_argument&)
+ {
+ diag_record dr (fail);
+
+ dr << "invalid " << value_traits<S>::value_type.name
+ << " second have of pair '" << l << "'";
+
+ if (var != nullptr)
+ dr << " in variable " << var->name;
+
+ dr << endf;
+ }
+ }
+ catch (const invalid_argument&)
+ {
+ diag_record dr (fail);
+
+ dr << "invalid " << value_traits<F>::value_type.name
+ << " first have of pair '" << l << "'";
+
+ if (var != nullptr)
+ dr << " in variable " << var->name;
+
+ dr << endf;
+ }
+ }
+
+ template <typename F, typename S>
+ void pair_value_traits<F, S>::
+ reverse (const F& f, const S& s, names& ns)
+ {
+ ns.push_back (value_traits<F>::reverse (f));
+ ns.back ().pair = '@';
+ ns.push_back (value_traits<S>::reverse (s));
+ }
+
+ template <typename F, typename S>
+ void pair_value_traits<F, optional<S>>::
+ reverse (const F& f, const optional<S>& s, names& ns)
+ {
+ ns.push_back (value_traits<F>::reverse (f));
+ if (s)
+ {
+ ns.back ().pair = '@';
+ ns.push_back (value_traits<S>::reverse (*s));
+ }
+ }
+
+ template <typename F, typename S>
+ void pair_value_traits<optional<F>, S>::
+ reverse (const optional<F>& f, const S& s, names& ns)
+ {
+ if (f)
+ {
+ ns.push_back (value_traits<F>::reverse (*f));
+ ns.back ().pair = '@';
+ }
+ ns.push_back (value_traits<S>::reverse (s));
+ }
+
+ // vector<T> value
+ //
template <typename T>
vector<T> value_traits<vector<T>>::
convert (names&& ns)
@@ -396,21 +615,28 @@ namespace build2
return 0;
}
+ // Make sure these are static-initialized together. Failed that VC will make
+ // sure it's done in the wrong order.
+ //
template <typename T>
- value_traits<vector<T>>::value_type_ex::
- value_type_ex (value_type&& v)
- : value_type (move (v))
+ struct vector_value_type: value_type
{
- type_name = value_traits<T>::type_name;
- type_name += 's';
- name = type_name.c_str ();
- }
+ string type_name;
+
+ vector_value_type (value_type&& v)
+ : value_type (move (v))
+ {
+ type_name = value_traits<T>::type_name;
+ type_name += 's';
+ name = type_name.c_str ();
+ }
+ };
template <typename T>
const vector<T> value_traits<vector<T>>::empty_instance;
template <typename T>
- const typename value_traits<vector<T>>::value_type_ex
+ const vector_value_type<T>
value_traits<vector<T>>::value_type = build2::value_type // VC14 wants =.
{
nullptr, // Patched above.
@@ -444,63 +670,14 @@ namespace build2
for (auto i (ns.begin ()); i != ns.end (); ++i)
{
name& l (*i);
+ name* r (l.pair ? &*++i : nullptr);
- if (!l.pair)
- {
- diag_record dr (fail);
-
- dr << value_traits<vector<pair<K, V>>>::value_type.name
- << " key-value pair expected instead of '" << l << "'";
+ p.push_back (value_traits<pair<K, V>>::convert (
+ move (l), r,
+ value_traits<vector<pair<K, V>>>::value_type.name,
+ "element",
+ var));
- if (var != nullptr)
- dr << " in variable " << var->name;
- }
-
- name& r (*++i); // Got to have the second half of the pair.
-
- if (l.pair != '@')
- {
- diag_record dr (fail);
-
- dr << "unexpected pair style for "
- << value_traits<vector<pair<K, V>>>::value_type.name
- << " key-value '" << l << "'" << l.pair << "'" << r << "'";
-
- if (var != nullptr)
- dr << " in variable " << var->name;
- }
-
- try
- {
- K k (value_traits<K>::convert (move (l), nullptr));
-
- try
- {
- V v (value_traits<V>::convert (move (r), nullptr));
-
- p.emplace_back (move (k), move (v));
- }
- catch (const invalid_argument&)
- {
- diag_record dr (fail);
-
- dr << "invalid " << value_traits<V>::value_type.name
- << " element value '" << r << "'";
-
- if (var != nullptr)
- dr << " in variable " << var->name;
- }
- }
- catch (const invalid_argument&)
- {
- diag_record dr (fail);
-
- dr << "invalid " << value_traits<K>::value_type.name
- << " element key '" << l << "'";
-
- if (var != nullptr)
- dr << " in variable " << var->name;
- }
}
}
@@ -522,11 +699,7 @@ namespace build2
s.reserve (2 * vv.size ());
for (const auto& p: vv)
- {
- s.push_back (value_traits<K>::reverse (p.first));
- s.back ().pair = '@';
- s.push_back (value_traits<V>::reverse (p.second));
- }
+ value_traits<pair<K, V>>::reverse (p.first, p.second, s);
return s;
}
@@ -543,9 +716,7 @@ namespace build2
for (; li != le && ri != re; ++li, ++ri)
{
- int r;
- if ((r = value_traits<K>::compare (li->first, ri->first)) != 0 ||
- (r = value_traits<V>::compare (li->second, ri->second)) != 0)
+ if (int r = value_traits<pair<K, V>>::compare (*li, *ri))
return r;
}
@@ -558,23 +729,66 @@ namespace build2
return 0;
}
+ // Make sure these are static-initialized together. Failed that VC will make
+ // sure it's done in the wrong order.
+ //
template <typename K, typename V>
- value_traits<vector<pair<K, V>>>::value_type_ex::
- value_type_ex (value_type&& v)
- : value_type (move (v))
+ struct pair_vector_value_type: value_type
{
- type_name = value_traits<K>::type_name;
- type_name += '_';
- type_name += value_traits<V>::type_name;
- type_name += "_pair_vector";
- name = type_name.c_str ();
- }
+ string type_name;
+
+ pair_vector_value_type (value_type&& v)
+ : value_type (move (v))
+ {
+ type_name = value_traits<K>::type_name;
+ type_name += '_';
+ type_name += value_traits<V>::type_name;
+ type_name += "_pair_vector";
+ name = type_name.c_str ();
+ }
+ };
+
+ // This is beyond our static initialization order control skills, so we hack
+ // it up for now.
+ //
+ template <typename K, typename V>
+ struct pair_vector_value_type<K, optional<V>>: value_type
+ {
+ string type_name;
+
+ pair_vector_value_type (value_type&& v)
+ : value_type (move (v))
+ {
+ type_name = value_traits<K>::type_name;
+ type_name += "_optional_";
+ type_name += value_traits<V>::type_name;
+ type_name += "_pair_vector";
+ name = type_name.c_str ();
+ }
+ };
+
+ template <typename K, typename V>
+ struct pair_vector_value_type<optional<K>, V>: value_type
+ {
+ string type_name;
+
+ pair_vector_value_type (value_type&& v)
+ : value_type (move (v))
+ {
+ type_name = "optional_";
+ type_name += value_traits<K>::type_name;
+ type_name += '_';
+ type_name += value_traits<V>::type_name;
+ type_name += "_pair_vector";
+ name = type_name.c_str ();
+ }
+ };
template <typename K, typename V>
const vector<pair<K, V>> value_traits<vector<pair<K, V>>>::empty_instance;
template <typename K, typename V>
- const typename value_traits<std::vector<pair<K, V>>>::value_type_ex
+ const pair_vector_value_type<K, V>
value_traits<vector<pair<K, V>>>::value_type = build2::value_type // VC14 wants =
{
nullptr, // Patched above.
@@ -599,8 +813,6 @@ namespace build2
void
map_append (value& v, names&& ns, const variable* var)
{
- using std::map;
-
map<K, V>& p (v
? v.as<map<K, V>> ()
: *new (&v.data_) map<K, V> ());
@@ -610,63 +822,42 @@ namespace build2
for (auto i (ns.begin ()); i != ns.end (); ++i)
{
name& l (*i);
+ name* r (l.pair ? &*++i : nullptr);
- if (!l.pair)
- {
- diag_record dr (fail);
-
- dr << value_traits<map<K, V>>::value_type.name << " key-value "
- << "pair expected instead of '" << l << "'";
-
- if (var != nullptr)
- dr << " in variable " << var->name;
- }
-
- name& r (*++i); // Got to have the second half of the pair.
-
- if (l.pair != '@')
- {
- diag_record dr (fail);
-
- dr << "unexpected pair style for "
- << value_traits<map<K, V>>::value_type.name << " key-value "
- << "'" << l << "'" << l.pair << "'" << r << "'";
-
- if (var != nullptr)
- dr << " in variable " << var->name;
- }
-
- try
- {
- K k (value_traits<K>::convert (move (l), nullptr));
-
- try
- {
- V v (value_traits<V>::convert (move (r), nullptr));
+ pair<K, V> v (value_traits<pair<K, V>>::convert (
+ move (l), r,
+ value_traits<map<K, V>>::value_type.name,
+ "element",
+ var));
- p.emplace (move (k), move (v));
- }
- catch (const invalid_argument&)
- {
- diag_record dr (fail);
+ p.emplace (move (v.first), move (v.second));
+ }
+ }
- dr << "invalid " << value_traits<V>::value_type.name
- << " element value '" << r << "'";
+ template <typename K, typename V>
+ void
+ map_prepend (value& v, names&& ns, const variable* var)
+ {
+ map<K, V>& p (v
+ ? v.as<map<K, V>> ()
+ : *new (&v.data_) map<K, V> ());
- if (var != nullptr)
- dr << " in variable " << var->name;
- }
- }
- catch (const invalid_argument&)
- {
- diag_record dr (fail);
+ // Verify we have a sequence of pairs and convert each lhs/rhs to K/V.
+ //
+ for (auto i (ns.begin ()); i != ns.end (); ++i)
+ {
+ name& l (*i);
+ name* r (l.pair ? &*++i : nullptr);
- dr << "invalid " << value_traits<K>::value_type.name
- << " element key '" << l << "'";
+ pair<K, V> v (value_traits<pair<K, V>>::convert (
+ move (l), r,
+ value_traits<map<K, V>>::value_type.name,
+ "element",
+ var));
- if (var != nullptr)
- dr << " in variable " << var->name;
- }
+ // Poor man's emplace_or_assign().
+ //
+ p.emplace (move (v.first), V ()).first->second = move (v.second);
}
}
@@ -674,8 +865,6 @@ namespace build2
void
map_assign (value& v, names&& ns, const variable* var)
{
- using std::map;
-
if (v)
v.as<map<K, V>> ().clear ();
@@ -686,17 +875,11 @@ namespace build2
static names_view
map_reverse (const value& v, names& s)
{
- using std::map;
-
auto& vm (v.as<map<K, V>> ());
s.reserve (2 * vm.size ());
for (const auto& p: vm)
- {
- s.push_back (value_traits<K>::reverse (p.first));
- s.back ().pair = '@';
- s.push_back (value_traits<V>::reverse (p.second));
- }
+ value_traits<pair<K, V>>::reverse (p.first, p.second, s);
return s;
}
@@ -705,8 +888,6 @@ namespace build2
static int
map_compare (const value& l, const value& r)
{
- using std::map;
-
auto& lm (l.as<map<K, V>> ());
auto& rm (r.as<map<K, V>> ());
@@ -715,9 +896,7 @@ namespace build2
for (; li != le && ri != re; ++li, ++ri)
{
- int r;
- if ((r = value_traits<K>::compare (li->first, ri->first)) != 0 ||
- (r = value_traits<V>::compare (li->second, ri->second)) != 0)
+ if (int r = value_traits<pair<const K, V>>::compare (*li, *ri))
return r;
}
@@ -730,24 +909,67 @@ namespace build2
return 0;
}
+ // Make sure these are static-initialized together. Failed that VC will make
+ // sure it's done in the wrong order.
+ //
template <typename K, typename V>
- value_traits<std::map<K, V>>::value_type_ex::
- value_type_ex (value_type&& v)
- : value_type (move (v))
+ struct map_value_type: value_type
{
- type_name = value_traits<K>::type_name;
- type_name += '_';
- type_name += value_traits<V>::type_name;
- type_name += "_map";
- name = type_name.c_str ();
- }
+ string type_name;
+
+ map_value_type (value_type&& v)
+ : value_type (move (v))
+ {
+ type_name = value_traits<K>::type_name;
+ type_name += '_';
+ type_name += value_traits<V>::type_name;
+ type_name += "_map";
+ name = type_name.c_str ();
+ }
+ };
+
+ // This is beyond our static initialization order control skills, so we hack
+ // it up for now.
+ //
+ template <typename K, typename V>
+ struct map_value_type<K, optional<V>>: value_type
+ {
+ string type_name;
+
+ map_value_type (value_type&& v)
+ : value_type (move (v))
+ {
+ type_name = value_traits<K>::type_name;
+ type_name += "_optional_";
+ type_name += value_traits<V>::type_name;
+ type_name += "_map";
+ name = type_name.c_str ();
+ }
+ };
+
+ template <typename K, typename V>
+ struct map_value_type<optional<K>, V>: value_type
+ {
+ string type_name;
+
+ map_value_type (value_type&& v)
+ : value_type (move (v))
+ {
+ type_name = "optional_";
+ type_name += value_traits<K>::type_name;
+ type_name += '_';
+ type_name += value_traits<V>::type_name;
+ type_name += "_map";
+ name = type_name.c_str ();
+ }
+ };
template <typename K, typename V>
- const std::map<K, V> value_traits<std::map<K, V>>::empty_instance;
+ const map<K, V> value_traits<map<K, V>>::empty_instance;
template <typename K, typename V>
- const typename value_traits<std::map<K, V>>::value_type_ex
- value_traits<std::map<K, V>>::value_type = build2::value_type // VC14 wants =
+ const map_value_type<K, V>
+ value_traits<map<K, V>>::value_type = build2::value_type // VC14 wants =
{
nullptr, // Patched above.
sizeof (map<K, V>),
@@ -758,7 +980,7 @@ namespace build2
&default_copy_assign<map<K, V>>,
&map_assign<K, V>,
&map_append<K, V>,
- &map_append<K, V>, // Prepend is the same as append.
+ &map_prepend<K, V>,
&map_reverse<K, V>,
nullptr, // No cast (cast data_ directly).
&map_compare<K, V>,
diff --git a/libbuild2/version/module.hxx b/libbuild2/version/module.hxx
index 26bd48a..e80870e 100644
--- a/libbuild2/version/module.hxx
+++ b/libbuild2/version/module.hxx
@@ -4,8 +4,6 @@
#ifndef LIBBUILD2_VERSION_MODULE_HXX
#define LIBBUILD2_VERSION_MODULE_HXX
-#include <map>
-
#include <libbuild2/types.hxx>
#include <libbuild2/utility.hxx>
@@ -26,7 +24,7 @@ namespace build2
string constraint;
};
- using dependencies = std::map<string, dependency>;
+ using dependencies = map<string, dependency>;
struct module: build2::module
{
diff --git a/tests/cc/modules/headers.testscript b/tests/cc/modules/headers.testscript
index 6fc2ba7..20f2a5f 100644
--- a/tests/cc/modules/headers.testscript
+++ b/tests/cc/modules/headers.testscript
@@ -69,7 +69,7 @@ cat <<EOI >=driver-inc.cxx;
#endif
int main () {return f () - CORE_OUT;}
EOI
-$* test clean config.cxx.translatable_headers="$~/core.hxx" <<EOI
+$* test clean config.cxx.translate_include="$~/core.hxx" <<EOI
./: exe{test-imp}: cxx{driver-imp} hxx{core}
./: exe{test-inc}: cxx{driver-inc} hxx{core}
EOI
@@ -104,7 +104,7 @@ $* test clean config.cxx.translatable_headers="$~/core.hxx" <<EOI
int main () {return g ();}
EOI
$* test clean config.cxx.poptions=-DBASE_INCLUDE \
- config.cxx.translatable_headers="$~/core.hxx" <<EOI
+ config.cxx.translate_include="$~/core.hxx" <<EOI
exe{test}: cxx{driver} hxx{core} mxx{base}
EOI
#\
@@ -135,7 +135,7 @@ cat <<EOI >=driver-inc.cxx;
#include <generated/core.hxx>
int main () {return f ();}
EOI
-$* test clean config.cxx.translatable_headers="$~/core.hxx" <<EOI
+$* test clean config.cxx.translate_include="$~/core.hxx" <<EOI
./: exe{test-imp}: cxx{driver-imp} hxx{core}
./: exe{test-inc}: cxx{driver-inc} hxx{core}
hxx{core}: in{core}
@@ -161,7 +161,7 @@ cat <<EOI >=driver-inc.cxx;
#
out = ../../headers-remapped-out;
$* 'test:' ./@$out/remapped/ \
- config.cxx.translatable_headers=$out/remapped/core.hxx <<EOI;
+ config.cxx.translate_include=$out/remapped/core.hxx <<EOI;
./: exe{test-imp}: cxx{driver-imp} hxx{core}
./: exe{test-inc}: cxx{driver-inc} hxx{core}
hxx{core}: in{core}
diff --git a/tests/cc/modules/modules.testscript b/tests/cc/modules/modules.testscript
index a43cc57..eb4c122 100644
--- a/tests/cc/modules/modules.testscript
+++ b/tests/cc/modules/modules.testscript
@@ -173,6 +173,9 @@ $* test clean <<EOI
ln -s ../driver.cxx ./;
$* test &*.d <'exe{test}: cxx{driver}' 2>>EOE != 0
driver.cxx: error: unable to resolve module foo.core
+ info: verify module interface is listed as a prerequisite, otherwise
+ info: consider adjusting module interface file names or
+ info: consider specifying module name with cxx.module_name
EOE
: misguessed