aboutsummaryrefslogtreecommitdiff
path: root/libbuild2
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2019-08-21 12:11:48 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2019-08-21 12:11:48 +0200
commitd64ae97f6865bc25d496485622530e2a090c2eb4 (patch)
treeeef35c80417a9a08c7713368261aa6d1e8632b5d /libbuild2
parentd7b5f03a761714f6ea6c4b1891e8a1f3824d4979 (diff)
Implement dynamic loading of build system modules
Diffstat (limited to 'libbuild2')
-rw-r--r--libbuild2/buildfile11
-rw-r--r--libbuild2/context.cxx5
-rw-r--r--libbuild2/file.cxx2
-rw-r--r--libbuild2/file.hxx2
-rw-r--r--libbuild2/function.hxx14
-rw-r--r--libbuild2/module.cxx307
-rw-r--r--libbuild2/module.hxx49
-rw-r--r--libbuild2/scope.hxx2
-rw-r--r--libbuild2/utility.cxx5
-rw-r--r--libbuild2/utility.hxx6
10 files changed, 337 insertions, 66 deletions
diff --git a/libbuild2/buildfile b/libbuild2/buildfile
index 6539a01..7d91e08 100644
--- a/libbuild2/buildfile
+++ b/libbuild2/buildfile
@@ -2,6 +2,9 @@
# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
# license : MIT; see accompanying LICENSE file
+# NOTE: remember to update bundled_modules in libbuild2/modules.cxx if adding
+# a new module.
+#
./: lib{build2} bash/ in/ version/
import int_libs = libbutl%lib{butl}
@@ -13,6 +16,9 @@ lib{build2}: libul{build2}: {hxx ixx txx cxx}{* -config -version -*.test...} \
# tests loop below). Note that the build system core can still function
# without them or with their alternative implementations.
#
+# NOTE: remember to update import_modules() in libbuild2/modules.cxx if adding
+# a new such module.
+#
for m: config dist install test
libul{build2}: $m/{hxx ixx txx cxx}{** -**-options -**.test...}
@@ -67,8 +73,13 @@ obja{context}: cxx.poptions += -DLIBBUILD2_STATIC_BUILD
objs{context}: cxx.poptions += -DLIBBUILD2_SHARED_BUILD
if ($cxx.target.class != "windows")
+{
cxx.libs += -lpthread
+ if ($cxx.target.class != "bsd")
+ cxx.libs += -ldl
+}
+
# Export options.
#
lib{build2}:
diff --git a/libbuild2/context.cxx b/libbuild2/context.cxx
index e7f9751..2f5e2af 100644
--- a/libbuild2/context.cxx
+++ b/libbuild2/context.cxx
@@ -461,10 +461,7 @@ namespace build2
// Build system interface version. In particular, it is embedded into
// build system modules as load_suffix.
//
- set ("build.version.interface",
- v.pre_release ()
- ? v.string_project_id ()
- : to_string (v.major ()) + '.' + to_string (v.minor ()));
+ set ("build.version.interface", build_version_interface);
// Allow detection (for example, in tests) whether this is a staged
// toolchain.
diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx
index 66f05f7..1da9397 100644
--- a/libbuild2/file.cxx
+++ b/libbuild2/file.cxx
@@ -1170,7 +1170,7 @@ namespace build2
if (scope* rs = root.parent_scope ()->root_scope ())
load_root (*rs);
- // Finish off loading bootstrapped modules.
+ // Finish off initializing bootstrapped modules.
//
for (auto& p: root.root_extra->modules)
{
diff --git a/libbuild2/file.hxx b/libbuild2/file.hxx
index 4d668fe..48d1b63 100644
--- a/libbuild2/file.hxx
+++ b/libbuild2/file.hxx
@@ -163,7 +163,7 @@ namespace build2
LIBBUILD2_SYMEXPORT bool
bootstrapped (scope& root);
- // Execute pre/post-bootstrap hooks. Similar to bootstrap_out/sr(), should
+ // Execute pre/post-bootstrap hooks. Similar to bootstrap_out/src(), should
// only be called once per project bootstrap.
//
LIBBUILD2_SYMEXPORT void
diff --git a/libbuild2/function.hxx b/libbuild2/function.hxx
index 6b2bfe1..51c17c0 100644
--- a/libbuild2/function.hxx
+++ b/libbuild2/function.hxx
@@ -429,10 +429,10 @@ namespace build2
#endif
};
- // Cast data/thunk.
+ // Cast data/thunk for functions.
//
template <typename R, typename... A>
- struct function_cast
+ struct function_cast_func
{
// A pointer to a standard layout struct is a pointer to its first data
// member, which in our case is the cast thunk.
@@ -468,7 +468,7 @@ namespace build2
// argument.
//
template <typename R, typename... A>
- struct function_cast<R, const scope*, A...>
+ struct function_cast_func<R, const scope*, A...>
{
struct data
{
@@ -500,7 +500,7 @@ namespace build2
// Specialization for void return type. In this case we return NULL value.
//
template <typename... A>
- struct function_cast<void, A...>
+ struct function_cast_func<void, A...>
{
struct data
{
@@ -528,7 +528,7 @@ namespace build2
};
template <typename... A>
- struct function_cast<void, const scope*, A...>
+ struct function_cast_func<void, const scope*, A...>
{
struct data
{
@@ -753,7 +753,7 @@ namespace build2
operator= (R (*impl) (A...)) &&
{
using args = function_args<A...>;
- using cast = function_cast<R, A...>;
+ using cast = function_cast_func<R, A...>;
insert (move (name),
function_overload (
@@ -770,7 +770,7 @@ namespace build2
operator= (R (*impl) (const scope*, A...)) &&
{
using args = function_args<A...>;
- using cast = function_cast<R, const scope*, A...>;
+ using cast = function_cast_func<R, const scope*, A...>;
insert (move (name),
function_overload (
diff --git a/libbuild2/module.cxx b/libbuild2/module.cxx
index 50530f2..1feb121 100644
--- a/libbuild2/module.cxx
+++ b/libbuild2/module.cxx
@@ -4,23 +4,259 @@
#include <libbuild2/module.hxx>
+#ifndef _WIN32
+# include <dlfcn.h>
+#else
+# include <libbutl/win32-utility.hxx>
+#endif
+
+#include <libbuild2/file.hxx> // import()
#include <libbuild2/scope.hxx>
#include <libbuild2/variable.hxx>
#include <libbuild2/diagnostics.hxx>
+// Core modules bundled with libbuild2.
+//
+#include <libbuild2/dist/init.hxx>
+#include <libbuild2/test/init.hxx>
+#include <libbuild2/config/init.hxx>
+#include <libbuild2/install/init.hxx>
+
using namespace std;
+using namespace butl;
namespace build2
{
- available_module_map builtin_modules;
+ loaded_module_map loaded_modules;
+
+ // Sorted array of bundled modules (excluding core modules bundled with
+ // libbuild2; see below).
+ //
+ static const char* bundled_modules[] = {
+ "bash",
+ "in",
+ "version"
+ };
+
+ static inline bool
+ bundled_module (const string& mod)
+ {
+ return binary_search (
+ bundled_modules,
+ bundled_modules + sizeof (bundled_modules) / sizeof (*bundled_modules),
+ mod);
+ }
+
+ static module_load_function*
+ import_module (scope& /*bs*/,
+ const string& mod,
+ const location& loc,
+ bool boot,
+ bool opt)
+ {
+ // Take care of core modules that are bundled with libbuild2 in case they
+ // are not pre-loaded by the driver.
+ //
+ if (mod == "config") return &config::build2_config_load;
+ else if (mod == "dist") return &dist::build2_dist_load;
+ else if (mod == "install") return &install::build2_install_load;
+ else if (mod == "test") return &test::build2_test_load;
+
+ bool bundled (bundled_module (mod));
+
+ // Importing external modules during bootstrap is problematic: we haven't
+ // loaded config.build nor entered all the variable overrides so it's not
+ // clear what import() can do except confuse matters. So this requires
+ // more thinking.
+ //
+ if (boot && !bundled)
+ {
+ fail (loc) << "unable to load build system module " << mod <<
+ info << "loading external modules during bootstrap is not yet "
+ << "supported";
+ }
+
+ path lib;
+
+#if 0
+ // See if we can import a target for this module.
+ //
+ // Check if one of the bundled modules, if so, the project name is
+ // build2, otherwise -- libbuild2-<mod>.
+ //
+ // The target we are looking for is <prj>%lib{build2-<mod>}.
+ //
+ name tgt (
+ import (bs,
+ name (bundled ? "build2" : "libbuild2-" + mod,
+ dir_path (),
+ "lib",
+ "build2-" + mod),
+ loc));
+
+ if (!tgt.qualified ())
+ {
+ // Switch the phase and update the target. This will also give us the
+ // shared library path.
+ //
+ // @@ TODO
+ //
+ }
+ else
+#endif
+ {
+ // No luck. Form the shared library name (incorporating build system
+ // core version) and try using system-default search (installed, rpath,
+ // etc).
+
+ // @@ This is unfortunate: it would have been nice to do something
+ // similar to what we've done for exe{}. While libs{} is in the bin
+ // module, we could bring it in (we've done it for exe{}). The
+ // problems are: it is intertwined with its group (lib{}) and we
+ // don't have any mechanisms to deal with prefixes, only extensions.
+ //
+ const char* pfx;
+ const char* sfx;
+#if defined(_WIN32)
+ pfx = "build2-"; sfx = ".dll";
+#elif defined(__APPLE__)
+ pfx = "libbuild2-"; sfx = ".dylib";
+#else
+ pfx = "libbuild2-"; sfx = ".so";
+#endif
+
+ lib = path (pfx + mod + '-' + build_version_interface + sfx);
+ }
+
+ string sym (sanitize_identifier ("build2_" + mod + "_load"));
+
+ // Note that we don't unload our modules since it's not clear what would
+ // the benefit be.
+ //
+ module_load_function* r (nullptr);
+
+#ifndef _WIN32
+ // Use RTLD_NOW instead of RTLD_LAZY to both speed things up (we are going
+ // to use this module now) and to detect any symbol mismatches.
+ //
+ if (void* h = dlopen (lib.string ().c_str (), RTLD_NOW | RTLD_GLOBAL))
+ {
+ r = function_cast<module_load_function*> (dlsym (h, sym.c_str ()));
+
+ // I don't think we should ignore this even if optional.
+ //
+ if (r == nullptr)
+ fail (loc) << "unable to lookup " << sym << " in build system module "
+ << mod << " (" << lib << "): " << dlerror ();
+ }
+ else if (!opt)
+ fail (loc) << "unable to load build system module " << mod
+ << " (" << lib << "): " << dlerror ();
+#else
+ if (HMODULE h = LoadLibrary (lib.string ().c_str ()))
+ {
+ r = function_cast<module_load_function*> (
+ GetProcAddress (h, sym.c_str ()));
+
+ if (r == nullptr)
+ fail (loc) << "unable to lookup " << sym << " in build system module "
+ << mod << " (" << lib << "): " << win32::last_error_msg ();
+ }
+ else if (!opt)
+ fail (loc) << "unable to load build system module " << mod
+ << " (" << lib << "): " << win32::last_error_msg ();
+#endif
+
+ return r;
+ }
+
+ static const module_functions*
+ find_module (scope& bs,
+ const string& smod,
+ const location& loc,
+ bool boot,
+ bool opt)
+ {
+ // Optional modules and submodules sure make this logic convoluted. So we
+ // divide it into two parts: (1) find or insert an entry (for submodule
+ // or, failed that, for the main module, the latter potentially NULL) and
+ // (2) analyze the entry and issue diagnostics.
+ //
+ auto i (loaded_modules.find (smod)), e (loaded_modules.end ());
+
+ if (i == e)
+ {
+ // If this is a submodule, get the main module name.
+ //
+ string mmod (smod, 0, smod.find ('.'));
+
+ if (mmod != smod)
+ i = loaded_modules.find (mmod);
+
+ if (i == e)
+ {
+ module_load_function* f (import_module (bs, mmod, loc, boot, opt));
+
+ if (f != nullptr)
+ {
+ // Enter all the entries noticing which one is our submodule. If
+ // none are, then we notice the main module.
+ //
+ for (const module_functions* j (f ()); j->name != nullptr; ++j)
+ {
+ const string& n (j->name);
+
+ auto p (loaded_modules.emplace (n, j));
+
+ if (!p.second)
+ fail (loc) << "build system submodule name " << n << " of main "
+ << "module " << mmod << " is already in use";
+
+ if (n == smod || (i == e && n == mmod))
+ i = p.first;
+ }
+
+ // We should at least have the main module.
+ //
+ if (i == e)
+ fail (loc) << "invalid function list in build system module "
+ << mmod;
+ }
+ else
+ i = loaded_modules.emplace (move (mmod), nullptr).first;
+ }
+ }
+
+ // Now the iterator points to a submodule or to the main module, the
+ // latter potentially NULL.
+ //
+ if (!opt)
+ {
+ if (i->second == nullptr)
+ {
+ fail (loc) << "unable to load build system module " << i->first;
+ }
+ else if (i->first != smod)
+ {
+ fail (loc) << "build system module " << i->first << " has no "
+ << "submodule " << smod;
+ }
+ }
+
+ // Note that if the main module exists but has no such submodule, we
+ // return NULL rather than fail (think of an older version of a module
+ // that doesn't implement some extra functionality).
+ //
+ return i->second;
+ }
void
- boot_module (scope& rs, const string& name, const location& loc)
+ boot_module (scope& rs, const string& mod, const location& loc)
{
- // First see if this modules has already been loaded for this project.
+ // First see if this modules has already been booted for this project.
//
- loaded_module_map& lm (rs.root_extra->modules);
- auto i (lm.find (name));
+ module_map& lm (rs.root_extra->modules);
+ auto i (lm.find (mod));
if (i != lm.end ())
{
@@ -35,58 +271,48 @@ namespace build2
// Otherwise search for this module.
//
- auto j (builtin_modules.find (name));
-
- if (j == builtin_modules.end ())
- fail (loc) << "unknown module " << name;
-
- const module_functions& mf (j->second);
+ const module_functions& mf (
+ *find_module (rs, mod, loc, true /* boot */, false /* optional */));
if (mf.boot == nullptr)
- fail (loc) << "module " << name << " shouldn't be loaded in bootstrap";
+ fail (loc) << "build system module " << mod << " should not be loaded "
+ << "during bootstrap";
- i = lm.emplace (name,
+ i = lm.emplace (mod,
module_state {true, false, mf.init, nullptr, loc}).first;
i->second.first = mf.boot (rs, loc, i->second.module);
- rs.assign (var_pool.rw (rs).insert (name + ".booted")) = true;
+ rs.assign (var_pool.rw (rs).insert (mod + ".booted")) = true;
}
bool
- load_module (scope& rs,
+ init_module (scope& rs,
scope& bs,
- const string& name,
+ const string& mod,
const location& loc,
bool opt,
const variable_map& hints)
{
- // First see if this modules has already been loaded for this project.
+ // First see if this modules has already been inited for this project.
//
- loaded_module_map& lm (rs.root_extra->modules);
- auto i (lm.find (name));
+ module_map& lm (rs.root_extra->modules);
+ auto i (lm.find (mod));
bool f (i == lm.end ());
if (f)
{
// Otherwise search for this module.
//
- auto j (builtin_modules.find (name));
-
- if (j == builtin_modules.end ())
+ if (const module_functions* mf = find_module (
+ rs, mod, loc, false /* boot */, opt))
{
- if (!opt)
- fail (loc) << "unknown module " << name;
- }
- else
- {
- const module_functions& mf (j->second);
-
- if (mf.boot != nullptr)
- fail (loc) << "module " << name << " should be loaded in bootstrap";
+ if (mf->boot != nullptr)
+ fail (loc) << "build system module " << mod << " should be loaded "
+ << "during bootstrap";
i = lm.emplace (
- name,
- module_state {false, false, mf.init, nullptr, loc}).first;
+ mod,
+ module_state {false, false, mf->init, nullptr, loc}).first;
}
}
else
@@ -103,11 +329,15 @@ namespace build2
// Note: pattern-typed in context.cxx:reset() as project-visibility
// variables of type bool.
//
+ // We call the variable 'loaded' rather than 'inited' because it is
+ // buildfile-visible (where we use the term "load a module"; see the note
+ // on terminology above)
+ //
auto& vp (var_pool.rw (rs));
- value& lv (bs.assign (vp.insert (name + ".loaded")));
- value& cv (bs.assign (vp.insert (name + ".configured")));
+ value& lv (bs.assign (vp.insert (mod + ".loaded")));
+ value& cv (bs.assign (vp.insert (mod + ".configured")));
- bool l; // Loaded.
+ bool l; // Loaded (initialized).
bool c; // Configured.
// Suppress duplicate init() calls for the same module in the same scope.
@@ -122,7 +352,7 @@ namespace build2
if (!opt)
{
if (!l)
- fail (loc) << "unknown module " << name;
+ fail (loc) << "unable to load build system module " << mod;
// We don't have original diagnostics. We could call init() again so
// that it can issue it. But that means optional modules must be
@@ -130,7 +360,8 @@ namespace build2
// simple for now.
//
if (!c)
- fail (loc) << "module " << name << " failed to configure";
+ fail (loc) << "build system module " << mod << " failed to "
+ << "configure";
}
}
else
diff --git a/libbuild2/module.hxx b/libbuild2/module.hxx
index 8343e11..200e52f 100644
--- a/libbuild2/module.hxx
+++ b/libbuild2/module.hxx
@@ -17,6 +17,12 @@
namespace build2
{
+ // A few high-level notes on the terminology: From the user's perspective,
+ // the module is "loaded" (with the `using` directive). From the
+ // implementation's perspectives, the module library is "loaded" and the
+ // module is "bootstrapped" (or "booted" for short) and then "initialized"
+ // (or "inited").
+
class scope;
class location;
@@ -76,7 +82,7 @@ namespace build2
extern "C"
using module_load_function = const module_functions* ();
- // Loaded modules state.
+ // Module state.
//
struct module_state
{
@@ -87,7 +93,7 @@ namespace build2
const location loc; // Boot location.
};
- struct loaded_module_map: std::map<string, module_state>
+ struct module_map: std::map<string, module_state>
{
template <typename T>
T*
@@ -100,15 +106,15 @@ namespace build2
}
};
- // Load and boot the specified module.
+ // Boot the specified module loading its library if necessary.
//
LIBBUILD2_SYMEXPORT void
boot_module (scope& root, const string& name, const location&);
- // Load (if not already loaded) and initialize the specified module. Used
- // by the parser but also by some modules to load prerequisite modules.
- // Return true if the module was both successfully loaded and configured
- // (false can only be returned if optional).
+ // Init the specified module loading its library if necessary. Used by the
+ // parser but also by some modules to init prerequisite modules. Return true
+ // if the module was both successfully loaded and configured (false can only
+ // be returned if optional is true).
//
// The config_hints variable map can be used to pass configuration hints
// from one module to another. For example, the cxx modude may pass the
@@ -117,20 +123,37 @@ namespace build2
// its tools).
//
LIBBUILD2_SYMEXPORT bool
- load_module (scope& root,
+ init_module (scope& root,
scope& base,
const string& name,
const location&,
bool optional = false,
const variable_map& config_hints = variable_map ());
- // Builtin modules.
+ // An alias to use from other modules (we could also distinguish between
+ // boot and init).
+ //
+ // @@ TODO: maybe incorporate the .loaded variable check we have all over
+ // (it's not clear if init_module() already has this semantics)?
+ //
+ inline bool
+ load_module (scope& root,
+ scope& base,
+ const string& name,
+ const location& loc,
+ bool optional = false,
+ const variable_map& config_hints = variable_map ())
+ {
+ return init_module (root, base, name, loc, optional, config_hints);
+ }
+
+ // Loaded modules (as in libraries).
//
- // @@ Maybe this should be renamed to loaded modules?
- // @@ We can also change it to std::map<const char*, const module_functions*>
+ // A NULL entry for the main module indicates that a module library was not
+ // found.
//
- using available_module_map = std::map<string, module_functions>;
- LIBBUILD2_SYMEXPORT extern available_module_map builtin_modules;
+ using loaded_module_map = std::map<string, const module_functions*>;
+ LIBBUILD2_SYMEXPORT extern loaded_module_map loaded_modules;
}
#endif // LIBBUILD2_MODULE_HXX
diff --git a/libbuild2/scope.hxx b/libbuild2/scope.hxx
index 455bcc6..f69e822 100644
--- a/libbuild2/scope.hxx
+++ b/libbuild2/scope.hxx
@@ -298,7 +298,7 @@ namespace build2
// Modules.
//
- loaded_module_map modules;
+ module_map modules;
// Variable override cache (see above).
//
diff --git a/libbuild2/utility.cxx b/libbuild2/utility.cxx
index 79545cc..cacb464 100644
--- a/libbuild2/utility.cxx
+++ b/libbuild2/utility.cxx
@@ -74,6 +74,11 @@ namespace build2
process_path argv0;
const standard_version build_version (LIBBUILD2_VERSION_STR);
+ const string build_version_interface (
+ build_version.pre_release ()
+ ? build_version.string_project_id ()
+ : (to_string (build_version.major ()) + '.' +
+ to_string (build_version.minor ())));
bool dry_run_option;
optional<bool> mtime_check_option;
diff --git a/libbuild2/utility.hxx b/libbuild2/utility.hxx
index c251b64..ed94a08 100644
--- a/libbuild2/utility.hxx
+++ b/libbuild2/utility.hxx
@@ -71,10 +71,13 @@ namespace build2
using butl::trim;
using butl::next_word;
+ using butl::sanitize_identifier;
using butl::make_guard;
using butl::make_exception_guard;
+ using butl::function_cast;
+
using butl::getenv;
using butl::setenv;
using butl::unsetenv;
@@ -136,9 +139,10 @@ namespace build2
//
LIBBUILD2_SYMEXPORT extern process_path argv0;
- // Build system driver version and check.
+ // Build system core version and interface version.
//
LIBBUILD2_SYMEXPORT extern const standard_version build_version;
+ LIBBUILD2_SYMEXPORT extern const string build_version_interface;
LIBBUILD2_SYMEXPORT extern bool dry_run_option; // --dry-run