aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2020-12-11 07:20:18 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2020-12-11 07:20:18 +0200
commit4cf87fa84a6938e262fd6122e654e5a483a78abe (patch)
tree9d6a7802d156dc287562f77184396e0cc7b62d23
parent3074c5e1409ad49c0793db6384ecbc6ac4ed33a9 (diff)
Add support for module interface-only libraries
Also suppress generation of the object file in cases where we don't need it.
-rw-r--r--libbuild2/bin/init.cxx21
-rw-r--r--libbuild2/bin/utility.cxx16
-rw-r--r--libbuild2/bin/utility.hxx2
-rw-r--r--libbuild2/c/init.cxx2
-rw-r--r--libbuild2/cc/common.cxx4
-rw-r--r--libbuild2/cc/common.hxx12
-rw-r--r--libbuild2/cc/compile-rule.cxx97
-rw-r--r--libbuild2/cc/compile-rule.hxx2
-rw-r--r--libbuild2/cc/link-rule.cxx176
-rw-r--r--libbuild2/cc/link-rule.hxx8
-rw-r--r--libbuild2/cc/pkgconfig.cxx2
-rw-r--r--libbuild2/cxx/init.cxx2
12 files changed, 269 insertions, 75 deletions
diff --git a/libbuild2/bin/init.cxx b/libbuild2/bin/init.cxx
index 9c16432..49ba518 100644
--- a/libbuild2/bin/init.cxx
+++ b/libbuild2/bin/init.cxx
@@ -116,12 +116,23 @@ namespace build2
//
// If unspecified, defaults to false for liba{} and to true for libu*{}.
//
- vp.insert<bool> ("bin.whole", variable_visibility::target);
+ vp.insert<bool> ("bin.whole", variable_visibility::target);
- vp.insert<string> ("bin.exe.prefix");
- vp.insert<string> ("bin.exe.suffix");
- vp.insert<string> ("bin.lib.prefix");
- vp.insert<string> ("bin.lib.suffix");
+ // Mark library as binless.
+ //
+ // For example, the user can mark a C++ library consisting of only
+ // module interfaces as binless so it becomes a modules equivalent to
+ // header-only library (which we will call a module interface-only
+ // library).
+ //
+ vp.insert<bool> ("bin.binless", variable_visibility::target);
+
+ // Executable and library name prefixes and suffixes.
+ //
+ vp.insert<string> ("bin.exe.prefix");
+ vp.insert<string> ("bin.exe.suffix");
+ vp.insert<string> ("bin.lib.prefix");
+ vp.insert<string> ("bin.lib.suffix");
// The optional custom clean patterns should be just the pattern stem,
// without the library prefix/name or extension. For example, `-[A-Z]`
diff --git a/libbuild2/bin/utility.cxx b/libbuild2/bin/utility.cxx
index 6b0c4de..11230cd 100644
--- a/libbuild2/bin/utility.cxx
+++ b/libbuild2/bin/utility.cxx
@@ -47,9 +47,11 @@ namespace build2
return lmembers {a, s};
}
- const target*
+ const file*
link_member (const libx& x, action a, linfo li, bool exist)
{
+ const target* r;
+
if (x.is_a<libul> ())
{
// For libul{} that is linked to an executable the member choice
@@ -72,7 +74,7 @@ namespace build2
// Called by the compile rule during execute.
//
- return x.ctx.phase == run_phase::match && !exist
+ r = x.ctx.phase == run_phase::match && !exist
? &search (x, tt, x.dir, x.out, x.name)
: search_existing (x.ctx, tt, x.dir, x.out, x.name);
}
@@ -87,15 +89,17 @@ namespace build2
group_view gv (resolve_members (a, l));
assert (gv.members != nullptr);
- pair<otype, bool> r (
+ pair<otype, bool> p (
link_member (lmembers {l.a != nullptr, l.s != nullptr}, li.order));
- if (!r.second)
- fail << (r.first == otype::s ? "shared" : "static")
+ if (!p.second)
+ fail << (p.first == otype::s ? "shared" : "static")
<< " variant of " << l << " is not available";
- return r.first == otype::s ? static_cast<const target*> (l.s) : l.a;
+ r = p.first == otype::s ? static_cast<const target*> (l.s) : l.a;
}
+
+ return static_cast<const file*> (r);
}
pattern_paths
diff --git a/libbuild2/bin/utility.hxx b/libbuild2/bin/utility.hxx
index 5d7eed4..e12bb5f 100644
--- a/libbuild2/bin/utility.hxx
+++ b/libbuild2/bin/utility.hxx
@@ -59,7 +59,7 @@ namespace build2
// If existing is true, then only return the member target if it exists
// (currently only used and supported for utility libraries).
//
- LIBBUILD2_BIN_SYMEXPORT const target*
+ LIBBUILD2_BIN_SYMEXPORT const file*
link_member (const libx&, action, linfo, bool existing = false);
// As above but return otype::a or otype::s as well as an indication if
diff --git a/libbuild2/c/init.cxx b/libbuild2/c/init.cxx
index 923fbcb..227afb2 100644
--- a/libbuild2/c/init.cxx
+++ b/libbuild2/c/init.cxx
@@ -161,6 +161,8 @@ namespace build2
hinters,
+ vp["bin.binless"],
+
// NOTE: remember to update documentation if changing anything here.
//
vp.insert<strings> ("config.c"),
diff --git a/libbuild2/cc/common.cxx b/libbuild2/cc/common.cxx
index 62f0ee0..9cac1b0 100644
--- a/libbuild2/cc/common.cxx
+++ b/libbuild2/cc/common.cxx
@@ -721,7 +721,7 @@ namespace build2
}
// Look for binary-less libraries via pkg-config .pc files. Note that
- // it is possible we have already found one of them as binfull but the
+ // it is possible we have already found one of them as binful but the
// other is binless.
//
{
@@ -731,7 +731,7 @@ namespace build2
if (na || ns)
{
// Only consider the common .pc file if we can be sure there
- // is no binfull variant.
+ // is no binful variant.
//
pair<path, path> r (
pkgconfig_search (d, p.proj, name, na && ns /* common */));
diff --git a/libbuild2/cc/common.hxx b/libbuild2/cc/common.hxx
index bf971ab..f072591 100644
--- a/libbuild2/cc/common.hxx
+++ b/libbuild2/cc/common.hxx
@@ -42,6 +42,18 @@ namespace build2
//
const char* const* x_hinters;
+ // We set this variable on the bmi*{} target to indicate whether it
+ // belongs to a binless library. More specifically, it controls both
+ // the production and consumption (linking) of the object file with
+ // the following possible states:
+ //
+ // produce consume
+ // true y y (binless normal or sidebuild)
+ // false n n (binful sidebuild)
+ // absent y n (binful normal)
+ //
+ const variable& b_binless; // bin.binless
+
const variable& config_x;
const variable& config_x_id; // <type>[-<variant>]
const variable& config_x_version;
diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx
index 7cea9d6..a02b3c4 100644
--- a/libbuild2/cc/compile-rule.cxx
+++ b/libbuild2/cc/compile-rule.cxx
@@ -704,12 +704,16 @@ namespace build2
}
}
- // If we are compiling a BMI-producing module TU, then the obj*{} is
- // an ad hoc member of bmi*{}. For now neither GCC nor Clang produce
- // an object file for a header unit (but something tells me this is
- // going to change).
+ // If we are compiling a BMI-producing module TU, then add obj*{} an
+ // ad hoc member of bmi*{} unless we only need the BMI (see
+ // config_data::b_binless for details).
//
- if (ut == unit_type::module_intf) // Note: still unrefined.
+ // For now neither GCC nor Clang produce an object file for a header
+ // unit (but something tells me this might change).
+ //
+ // Note: ut is still unrefined.
+ //
+ if (ut == unit_type::module_intf && cast_true<bool> (t[b_binless]))
{
// The module interface unit can be the same as an implementation
// (e.g., foo.mxx and foo.cxx) which means obj*{} targets could
@@ -5404,8 +5408,8 @@ namespace build2
// implicit import, if you will). Do you see where it's going? Nowever
// good, that's right. This shallow reference means that the compiler
// should be able to find BMIs for all the re-exported modules,
- // recursive. The good news is we are actually in a pretty good shape to
- // handle this: after match all our prerequisite BMIs will have their
+ // recursively. The good news is we are actually in a pretty good shape
+ // to handle this: after match all our prerequisite BMIs will have their
// prerequisite BMIs known, recursively. The only bit that is missing is
// the re-export flag of some sorts. As well as deciding where to handle
// it: here or in append_module_options(). After some meditation it
@@ -5423,6 +5427,8 @@ namespace build2
// bmi{}s have been matched by the same rule. But let's not kid
// ourselves, there will be no other rule that matches bmi{}s.
//
+ // @@ I think now we could use prerequisite_targets::data for this?
+ //
// 2. Once we have matched all the bmi{}s we are importing directly
// (with all the re-exported by us at the back), we will go over them
// and copy all of their re-exported bmi{}s (using the position we
@@ -5553,12 +5559,12 @@ namespace build2
if (pt != nullptr)
{
- const target* lt (nullptr);
+ const file* lt (nullptr);
if (const libx* l = pt->is_a<libx> ())
lt = link_member (*l, a, li);
else if (pt->is_a<liba> () || pt->is_a<libs> () || pt->is_a<libux> ())
- lt = pt;
+ lt = &pt->as<file> ();
// If this is a library, check its bmi{}s and mxx{}s.
//
@@ -5951,7 +5957,7 @@ namespace build2
const file& compile_rule::
make_module_sidebuild (action a,
const scope& bs,
- const target& lt,
+ const file& lt,
const target& mt,
const string& mn) const
{
@@ -5965,17 +5971,16 @@ namespace build2
// not to conflict with other modules. If we assume that within an
// amalgamation there is only one "version" of each module, then the
// module name itself seems like a good fit. We just replace '.' with
- // '-'.
+ // '-' and ':' with '+'.
//
string mf;
transform (mn.begin (), mn.end (),
back_inserter (mf),
- [] (char c) {return c == '.' ? '-' : c;});
+ [] (char c) {return c == '.' ? '-' : c == ':' ? '+' : c;});
// It seems natural to build a BMI type that corresponds to the library
// type. After all, this is where the object file part of the BMI is
- // going to come from (though things will probably be different for
- // module-only libraries).
+ // going to come from (unless it's a module interface-only library).
//
const target_type& tt (compile_types (link_type (lt).type).bmi);
@@ -6012,6 +6017,10 @@ namespace build2
// @@ 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> ())
@@ -6034,8 +6043,15 @@ namespace build2
// while we were preparing the prerequisite list.
//
if (p.second.owns_lock ())
+ {
bt.prerequisites (move (ps));
+ // Unless this is a binless library, we don't need the object file
+ // (see config_data::b_binless for details).
+ //
+ bt.vars.assign (b_binless) = (lt.mtime () == timestamp_unreal);
+ }
+
return bt;
}
@@ -6410,17 +6426,28 @@ namespace build2
cstrings args {cpath.recall_string ()};
// If we are building a module interface or partition, then the target
- // is bmi*{} and its ad hoc member is obj*{}. For header units there is
- // no obj*{}.
+ // is bmi*{} and it may have an ad hoc obj*{} member. For header units
+ // there is no obj*{} (see the corresponding add_adhoc_member() call in
+ // apply()).
//
path relm;
- path relo (ut == unit_type::module_header
- ? path ()
- : relative (ut == unit_type::module_intf ||
- ut == unit_type::module_intf_part ||
- ut == unit_type::module_impl_part
- ? find_adhoc_member<file> (t, tts.obj)->path ()
- : tp));
+ path relo;
+ switch (ut)
+ {
+ case unit_type::module_header:
+ break;
+ case unit_type::module_intf:
+ case unit_type::module_intf_part:
+ case unit_type::module_impl_part:
+ {
+ if (const file* o = find_adhoc_member<file> (t, tts.obj))
+ relo = relative (o->path ());
+
+ break;
+ }
+ default:
+ relo = relative (tp);
+ }
// Build the command line.
//
@@ -6526,6 +6553,8 @@ namespace build2
// Note also that what we are doing here appears to be incompatible
// with PCH (/Y* options) and /Gm (minimal rebuild).
//
+ // @@ MOD: TODO deal with absent relo.
+ //
if (find_options ({"/Zi", "/ZI"}, args))
{
if (fc)
@@ -6699,16 +6728,34 @@ namespace build2
// Output module file is specified in the mapping file, the
// same as input.
//
- if (ut != unit_type::module_header) // No object file.
+ if (ut == unit_type::module_header) // No obj, -c implied.
+ break;
+
+ if (!relo.empty ())
{
args.push_back ("-o");
args.push_back (relo.string ().c_str ());
- args.push_back ("-c");
}
+ else if (ut != unit_type::module_header)
+ {
+ // Should this be specified in append_lang_options() like
+ // -fmodule-header (which, BTW, implies -fmodule-only)?
+ // While it's plausible that -fmodule-header has some
+ // semantic differences that should be in effect during
+ // preprocessing, -fmodule-only seems to only mean "don't
+ // write the object file" so for now we specify it only
+ // here.
+ //
+ args.push_back ("-fmodule-only");
+ }
+
+ args.push_back ("-c");
break;
}
case compiler_type::clang:
{
+ // @@ MOD TODO: deal with absent relo.
+
relm = relative (tp);
args.push_back ("-o");
diff --git a/libbuild2/cc/compile-rule.hxx b/libbuild2/cc/compile-rule.hxx
index 2d80d18..a716b4c 100644
--- a/libbuild2/cc/compile-rule.hxx
+++ b/libbuild2/cc/compile-rule.hxx
@@ -166,7 +166,7 @@ namespace build2
find_modules_sidebuild (const scope&) const;
const file&
- make_module_sidebuild (action, const scope&, const target&,
+ make_module_sidebuild (action, const scope&, const file&,
const target&, const string&) const;
const file&
diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx
index dea5879..3f6824e 100644
--- a/libbuild2/cc/link-rule.cxx
+++ b/libbuild2/cc/link-rule.cxx
@@ -583,37 +583,37 @@ namespace build2
move (cp_l), move (cp_v)};
}
- // Look for binary-full utility library recursively until we hit a
- // non-utility "barier".
+ // Look for binful utility library recursively until we hit a non-utility
+ // "barier".
//
- static bool
- find_binfull (action a, const target& t, linfo li)
+ static const libux*
+ find_binful (action a, const target& t, linfo li)
{
for (const target* pt: t.prerequisite_targets[a])
{
if (pt == nullptr || unmark (pt) != 0) // Called after pass 1 below.
continue;
- const file* pf;
+ const libux* ux;
// If this is the libu*{} group, then pick the appropriate member.
//
if (const libul* ul = pt->is_a<libul> ())
{
- pf = &link_member (*ul, a, li)->as<file> ();
+ ux = &link_member (*ul, a, li)->as<libux> ();
}
- else if ((pf = pt->is_a<libue> ()) ||
- (pf = pt->is_a<libus> ()) ||
- (pf = pt->is_a<libua> ()))
+ else if ((ux = pt->is_a<libue> ()) ||
+ (ux = pt->is_a<libus> ()) ||
+ (ux = pt->is_a<libua> ()))
;
else
continue;
- if (!pf->path ().empty () || find_binfull (a, *pf, li))
- return true;
+ if (!ux->path ().empty () || (ux = find_binful (a, *ux, li)))
+ return ux;
}
- return false;
+ return nullptr;
};
recipe link_rule::
@@ -642,6 +642,7 @@ namespace build2
t.state[a].assign (c_type) = string (x);
bool binless (lt.library ()); // Binary-less until proven otherwise.
+ bool user_binless (lt.library () && cast_false<bool> (t[b_binless]));
// Inject dependency on the output directory. Note that we do it even
// for binless libraries since there could be other output (e.g., .pc
@@ -655,7 +656,7 @@ namespace build2
//
// Also clear the binless flag if we see any source or object files.
// Note that if we don't see any this still doesn't mean the library is
- // binless since it can depend on a binfull utility library. This we
+ // binless since it can depend on a binful utility library. This we
// check below, after matching the libraries.
//
// We do libraries first in order to indicate that we will execute these
@@ -680,9 +681,9 @@ namespace build2
optional<dir_paths> usr_lib_dirs; // Extract lazily.
compile_target_types tts (compile_types (ot));
- auto skip = [&a, &rs] (const target* pt) -> bool
+ auto skip = [&a, &rs] (const target& pt) -> bool
{
- return a.operation () == clean_id && !pt->dir.sub (rs.out_path ());
+ return a.operation () == clean_id && !pt.dir.sub (rs.out_path ());
};
auto& pts (t.prerequisite_targets[a]);
@@ -713,15 +714,13 @@ namespace build2
if (mod || p.is_a (x_src) || p.is_a<c> ())
{
- binless = binless && false;
+ binless = binless && (mod ? user_binless : false);
// Rule chaining, part 1.
//
-
// Which scope shall we use to resolve the root? Unlikely, but
// possible, the prerequisite is from a different project
// altogether. So we are going to use the target's project.
- //
// If the source came from the lib{} group, then create the obj{}
// group and add the source as a prerequisite of the obj{} group,
@@ -763,19 +762,44 @@ namespace build2
// obj/bmi{} is always in the out tree. Note that currently it could
// be the group -- we will pick a member in part 2 below.
//
- pt = &search (t, rtt, d, dir_path (), *cp.tk.name, nullptr, cp.scope);
+ pair<target&, ulock> r (
+ search_locked (
+ t, rtt, d, dir_path (), *cp.tk.name, nullptr, cp.scope));
// If we shouldn't clean obj{}, then it is fair to assume we
// shouldn't clean the source either (generated source will be in
// the same directory as obj{} and if not, well, go find yourself
// another build system ;-)).
//
- if (skip (pt))
+ if (skip (r.first))
{
pt = nullptr;
continue;
}
+ // Either set of verify the bin.binless value on this bmi*{} target
+ // (see config_data::b_binless for semantics).
+ //
+ if (mod)
+ {
+ if (r.second.owns_lock ())
+ {
+ if (user_binless)
+ r.first.assign (b_binless) = true;
+ }
+ else
+ {
+ lookup l (r.first[b_binless]);
+
+ if (user_binless ? !cast_false<bool> (l) : l.defined ())
+ fail << "synthesized dependency for prerequisite " << p
+ << " would be incompatible with existing target "
+ << r.first <<
+ info << "incompatible bin.binless value";
+ }
+ }
+
+ pt = &r.first;
m = mod ? 2 : 1;
}
else if (p.is_a<libx> () ||
@@ -797,7 +821,7 @@ namespace build2
if (pt == nullptr)
pt = &p.search (t);
- if (skip (pt))
+ if (skip (*pt))
m = 3; // Mark so it is not matched.
// If this is the lib{}/libu{} group, then pick the appropriate
@@ -844,19 +868,28 @@ namespace build2
pt = &p.search (t);
}
- if (skip (pt))
+ if (skip (*pt))
{
pt = nullptr;
continue;
}
- // @@ MODHDR: hbmix{} has no objx{}
+ // Header BMIs have no object file. Module BMI must be explicitly
+ // marked with bin.binless by the user to be usable in a binless
+ // library.
//
- binless = binless && !(pt->is_a<objx> () || pt->is_a<bmix> ());
+ binless = binless && !(
+ pt->is_a<objx> () ||
+ (pt->is_a<bmix> () &&
+ !pt->is_a<hbmix> () &&
+ cast_false<bool> ((*pt)[b_binless])));
m = 3;
}
+ if (user_binless && !binless)
+ fail << t << " cannot be binless due to " << p << " prerequisite";
+
mark (pt, m);
}
@@ -864,9 +897,19 @@ namespace build2
//
match_members (a, t, pts, start);
- // Check if we have any binfull utility libraries.
+ // Check if we have any binful utility libraries.
//
- binless = binless && !find_binfull (a, t, li);
+ if (binless)
+ {
+ if (const libux* l = find_binful (a, t, li))
+ {
+ binless = false;
+
+ if (user_binless)
+ fail << t << " cannot be binless due to binful " << *l
+ << " prerequisite";
+ }
+ }
// Now that we know for sure whether we are binless, derive file name(s)
// and add ad hoc group members. Note that for binless we still need the
@@ -1176,10 +1219,11 @@ namespace build2
? (group ? bmi::static_type : tts.bmi)
: (group ? obj::static_type : tts.obj));
- // If this obj*{} already has prerequisites, then verify they are
- // "compatible" with what we are doing here. Otherwise, synthesize
- // the dependency. Note that we may also end up synthesizing with
- // someone beating us to it. In this case also verify.
+ // If this obj*/bmi*{} already has prerequisites, then verify they
+ // are "compatible" with what we are doing here. Otherwise,
+ // synthesize the dependency. Note that we may also end up
+ // synthesizing with someone beating us to it. In this case also
+ // verify.
//
bool verify (true);
@@ -1397,7 +1441,7 @@ namespace build2
// If this is a library not to be cleaned, we can finally blank it
// out.
//
- if (skip (pt))
+ if (skip (*pt))
{
pt = nullptr;
continue;
@@ -2003,6 +2047,50 @@ namespace build2
}
}
+ // Append object files of bmi{} prerequisites that belong to binless
+ // libraries.
+ //
+ void link_rule::
+ append_binless_modules (strings& args,
+ const scope& bs, action a, const file& t) const
+ {
+ // Note that here we don't need to hoist anything on duplicate detection
+ // since the order in which we link object files is not important.
+ //
+ for (const target* pt: t.prerequisite_targets[a])
+ {
+ if (pt != nullptr &&
+ pt->is_a<bmix> () &&
+ cast_false<bool> ((*pt)[b_binless]))
+ {
+ const objx& o (*find_adhoc_member<objx> (*pt)); // Must be there.
+ string p (relative (o.path ()).string ());
+ if (find (args.begin (), args.end (), p) == args.end ())
+ {
+ args.push_back (move (p));
+ append_binless_modules (args, bs, a, o);
+ }
+ }
+ }
+ }
+
+ void link_rule::
+ append_binless_modules (sha256& cs,
+ const scope& bs, action a, const file& t) const
+ {
+ for (const target* pt: t.prerequisite_targets[a])
+ {
+ if (pt != nullptr &&
+ pt->is_a<bmix> () &&
+ cast_false<bool> ((*pt)[b_binless]))
+ {
+ const objx& o (*find_adhoc_member<objx> (*pt));
+ hash_path (cs, o.path (), bs.root_scope ()->out_path ());
+ append_binless_modules (cs, bs, a, o);
+ }
+ }
+ }
+
// Filter link.exe noise (msvc.cxx).
//
void
@@ -2635,8 +2723,13 @@ namespace build2
//
if (modules)
{
- if (pt->is_a<bmix> ()) // @@ MODHDR: hbmix{} has no objx{}
+ if (pt->is_a<bmix> ())
+ {
pt = find_adhoc_member (*pt, tts.obj);
+
+ if (pt == nullptr) // Header BMIs have no object file.
+ continue;
+ }
}
const file* f;
@@ -2669,7 +2762,12 @@ namespace build2
f = nullptr; // Timestamp checked by hash_libraries().
}
else
+ {
hash_path (cs, f->path (), rs.out_path ());
+
+ if (modules)
+ append_binless_modules (cs, rs, a, *f);
+ }
}
else if ((f = pt->is_a<bin::def> ()))
{
@@ -2963,8 +3061,13 @@ namespace build2
if (modules)
{
- if (pt->is_a<bmix> ()) // @@ MODHDR: hbmix{} has no objx{}
+ if (pt->is_a<bmix> ())
+ {
pt = find_adhoc_member (*pt, tts.obj);
+
+ if (pt == nullptr) // Header BMIs have no object file.
+ continue;
+ }
}
const file* f;
@@ -2985,7 +3088,12 @@ namespace build2
// files might satisfy symbols in the preceding libraries.
//
als.clear ();
- sargs.push_back (relative (f->path ()).string ()); // string()&&
+
+ sargs.push_back (relative (f->path ()).string ());
+
+ if (modules)
+ append_binless_modules (sargs, bs, a, *f);
+
seen_obj = true;
}
}
diff --git a/libbuild2/cc/link-rule.hxx b/libbuild2/cc/link-rule.hxx
index 6d0649c..baccf8d 100644
--- a/libbuild2/cc/link-rule.hxx
+++ b/libbuild2/cc/link-rule.hxx
@@ -131,6 +131,14 @@ namespace build2
const scope&, action,
const target&, linfo, bool) const;
+ void
+ append_binless_modules (strings&,
+ const scope&, action, const file&) const;
+
+ void
+ append_binless_modules (sha256&,
+ const scope&, action, const file&) const;
+
protected:
static void
functions (function_family&, const char*); // functions.cxx
diff --git a/libbuild2/cc/pkgconfig.cxx b/libbuild2/cc/pkgconfig.cxx
index f0a5e0c..6c0f1fc 100644
--- a/libbuild2/cc/pkgconfig.cxx
+++ b/libbuild2/cc/pkgconfig.cxx
@@ -1524,7 +1524,7 @@ namespace build2
os << "Libs:";
// While we don't need it for a binless library itselt, it may be
- // necessary to resolve its binfull dependencies.
+ // necessary to resolve its binful dependencies.
//
os << " -L" << escape (ldir.string ());
diff --git a/libbuild2/cxx/init.cxx b/libbuild2/cxx/init.cxx
index 21acedc..ad0dd05 100644
--- a/libbuild2/cxx/init.cxx
+++ b/libbuild2/cxx/init.cxx
@@ -414,6 +414,8 @@ namespace build2
hinters,
+ vp["bin.binless"],
+
// NOTE: remember to update documentation if changing anything here.
//
vp.insert<strings> ("config.cxx"),