aboutsummaryrefslogtreecommitdiff
path: root/libbuild2
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2')
-rw-r--r--libbuild2/bin/init.cxx2
-rw-r--r--libbuild2/c/init.cxx6
-rw-r--r--libbuild2/cc/common.cxx16
-rw-r--r--libbuild2/cc/compile-rule.cxx6
-rw-r--r--libbuild2/cc/init.cxx2
-rw-r--r--libbuild2/cc/utility.hxx8
-rw-r--r--libbuild2/config/utility.hxx14
-rw-r--r--libbuild2/context.cxx5
-rw-r--r--libbuild2/context.hxx5
-rw-r--r--libbuild2/cxx/init.cxx6
-rw-r--r--libbuild2/file.cxx846
-rw-r--r--libbuild2/file.hxx182
-rw-r--r--libbuild2/file.ixx62
-rw-r--r--libbuild2/forward.hxx5
-rw-r--r--libbuild2/function.test.cxx2
-rw-r--r--libbuild2/module.cxx46
-rw-r--r--libbuild2/module.hxx13
-rw-r--r--libbuild2/name.hxx4
-rw-r--r--libbuild2/parser.cxx162
-rw-r--r--libbuild2/parser.hxx26
-rw-r--r--libbuild2/prerequisite-key.hxx44
-rw-r--r--libbuild2/prerequisite.hxx26
-rw-r--r--libbuild2/scope.cxx52
-rw-r--r--libbuild2/scope.hxx21
-rw-r--r--libbuild2/scope.ixx23
-rw-r--r--libbuild2/search.cxx2
-rw-r--r--libbuild2/target-key.hxx7
-rw-r--r--libbuild2/target.cxx21
-rw-r--r--libbuild2/target.hxx63
-rw-r--r--libbuild2/target.txx1
-rw-r--r--libbuild2/utility.hxx4
31 files changed, 1380 insertions, 302 deletions
diff --git a/libbuild2/bin/init.cxx b/libbuild2/bin/init.cxx
index ab4b686..34bfcd7 100644
--- a/libbuild2/bin/init.cxx
+++ b/libbuild2/bin/init.cxx
@@ -150,7 +150,7 @@ namespace build2
// We only support root loading (which means there can only be one).
//
- if (&rs != &bs)
+ if (rs != bs)
fail (loc) << "bin.config module must be loaded in project root";
// Load bin.vars.
diff --git a/libbuild2/c/init.cxx b/libbuild2/c/init.cxx
index e6e28aa..5eea8de 100644
--- a/libbuild2/c/init.cxx
+++ b/libbuild2/c/init.cxx
@@ -142,7 +142,7 @@ namespace build2
// We only support root loading (which means there can only be one).
//
- if (&rs != &bs)
+ if (rs != bs)
fail (loc) << "c.guess module must be loaded in project root";
// Load cc.core.vars so that we can cache all the cc.* variables.
@@ -276,7 +276,7 @@ namespace build2
// We only support root loading (which means there can only be one).
//
- if (&rs != &bs)
+ if (rs != bs)
fail (loc) << "c.config module must be loaded in project root";
// Load c.guess and share its module instance as ours.
@@ -313,7 +313,7 @@ namespace build2
// We only support root loading (which means there can only be one).
//
- if (&rs != &bs)
+ if (rs != bs)
fail (loc) << "c module must be loaded in project root";
// Load c.config.
diff --git a/libbuild2/cc/common.cxx b/libbuild2/cc/common.cxx
index 99de66e..cf6d546 100644
--- a/libbuild2/cc/common.cxx
+++ b/libbuild2/cc/common.cxx
@@ -465,20 +465,8 @@ namespace build2
{
// This is import.
//
- name n (cn);
- auto rp (s.find_target_type (n, location ())); // Note: changes name.
- const target_type* tt (rp.first);
- optional<string>& ext (rp.second);
-
- if (tt == nullptr)
- fail << "unknown target type '" << n.type << "' in library " << n;
-
- // @@ OUT: for now we assume out is undetermined, just like in
- // search (name, scope).
- //
- dir_path out;
-
- prerequisite_key pk {n.proj, {tt, &n.dir, &out, &n.value, ext}, &s};
+ name n (cn), o; // Note: find_prerequisite_key() changes name.
+ prerequisite_key pk (s.find_prerequisite_key (n, o, location ()));
xt = search_library_existing (a, sysd, usrd, pk);
if (xt == nullptr)
diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx
index a8916cf..8b082cc 100644
--- a/libbuild2/cc/compile-rule.cxx
+++ b/libbuild2/cc/compile-rule.cxx
@@ -696,8 +696,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
- // meta-information protocol. See also append_lib_options().
+ // *.export.poptions, modules, etc. This is the library metadata
+ // protocol. See also append_lib_options().
//
if (pi == include_type::normal &&
(p.is_a<libx> () ||
@@ -4871,7 +4871,7 @@ namespace build2
//
// For (direct) library prerequisites, check their prerequisite bmi{}s
// (which should be searched and matched with module names discovered;
- // see the library meta-information protocol for details).
+ // see the library metadata protocol for details).
//
// For our own bmi{} prerequisites, checking if each (better) matches
// any of the imports.
diff --git a/libbuild2/cc/init.cxx b/libbuild2/cc/init.cxx
index 8d66376..2a0dbd2 100644
--- a/libbuild2/cc/init.cxx
+++ b/libbuild2/cc/init.cxx
@@ -399,7 +399,7 @@ namespace build2
// We only support root loading (which means there can only be one).
//
- if (&rs != &bs)
+ if (rs != bs)
fail (loc) << m << " module must be loaded in project root";
// We want to order the loading to match what user specified on the
diff --git a/libbuild2/cc/utility.hxx b/libbuild2/cc/utility.hxx
index fa9f165..017765b 100644
--- a/libbuild2/cc/utility.hxx
+++ b/libbuild2/cc/utility.hxx
@@ -41,10 +41,10 @@ namespace build2
//
// The reason we pass scope and not the target is because this function is
// called not only for exe/lib but also for obj as part of the library
- // meta-information protocol implementation. Normally the bin.*.lib values
- // will be project-wide. With this scheme they can be customized on the
- // per-directory basis but not per-target which means all exe/lib in the
- // same directory have to have the same link order.
+ // metadata protocol implementation. Normally the bin.*.lib values will be
+ // project-wide. With this scheme they can be customized on the per-
+ // directory basis but not per-target which means all exe/lib in the same
+ // directory have to have the same link order.
//
lorder
link_order (const scope& base, otype);
diff --git a/libbuild2/config/utility.hxx b/libbuild2/config/utility.hxx
index 614791f..fb3023b 100644
--- a/libbuild2/config/utility.hxx
+++ b/libbuild2/config/utility.hxx
@@ -311,6 +311,20 @@ namespace build2
// be used to "remember" that the module is left unconfigured in order to
// avoid re-running the tests, etc.
//
+ // @@ This functionality is WIP/unused and still has a number of issues:
+ //
+ // - This seems to be a subset of a bigger problem of caching discovered
+ // configuration results. In fact, what we do in the configured case,
+ // for example in the cc module (multiple path extraction runs, etc), is
+ // a lot more expensive.
+ //
+ // - The current semantics does not work well for the case where, say, the
+ // missing tool has appeared in PATH and can now be used via the default
+ // configuration. In fact, even reconfiguring will not help without a
+ // "nudge" (e.g., config.<tool>=<tool>). So maybe this value should be
+ // ignored during configuration? See the "Tool importation: unconfigured
+ // state" page for more notes.
+ //
LIBBUILD2_SYMEXPORT bool
unconfigured (scope& rs, const string& var);
diff --git a/libbuild2/context.cxx b/libbuild2/context.cxx
index 06cdc6d..0be0046 100644
--- a/libbuild2/context.cxx
+++ b/libbuild2/context.cxx
@@ -544,7 +544,10 @@ namespace build2
var_project_url = &vp.insert<string> ("project.url");
var_project_summary = &vp.insert<string> ("project.summary");
- var_import_target = &vp.insert<name> ("import.target");
+ var_import_target = &vp.insert<name> ("import.target");
+ var_import_metadata = &vp.insert<uint64_t> ("import.metadata");
+
+ var_export_metadata = &vp.insert<uint64_t> ("export.metadata", v_t);
var_extension = &vp.insert<string> ("extension", v_t);
var_clean = &vp.insert<bool> ("clean", v_t);
diff --git a/libbuild2/context.hxx b/libbuild2/context.hxx
index ea2c017..573b8d1 100644
--- a/libbuild2/context.hxx
+++ b/libbuild2/context.hxx
@@ -352,6 +352,11 @@ namespace build2
//
const variable* var_import_build2;
const variable* var_import_target;
+ const variable* var_import_metadata;
+
+ // export.*
+ //
+ const variable* var_export_metadata;
// [string] target visibility
//
diff --git a/libbuild2/cxx/init.cxx b/libbuild2/cxx/init.cxx
index 1d8421c..265bbc0 100644
--- a/libbuild2/cxx/init.cxx
+++ b/libbuild2/cxx/init.cxx
@@ -382,7 +382,7 @@ namespace build2
// We only support root loading (which means there can only be one).
//
- if (&rs != &bs)
+ if (rs != bs)
fail (loc) << "cxx.guess module must be loaded in project root";
// Load cc.core.vars so that we can cache all the cc.* variables.
@@ -540,7 +540,7 @@ namespace build2
// We only support root loading (which means there can only be one).
//
- if (&rs != &bs)
+ if (rs != bs)
fail (loc) << "cxx.config module must be loaded in project root";
// Load cxx.guess and share its module instance as ours.
@@ -585,7 +585,7 @@ namespace build2
// We only support root loading (which means there can only be one).
//
- if (&rs != &bs)
+ if (rs != bs)
fail (loc) << "cxx module must be loaded in project root";
// Load cxx.config.
diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx
index 0bf3fd4..fb24160 100644
--- a/libbuild2/file.cxx
+++ b/libbuild2/file.cxx
@@ -4,19 +4,20 @@
#include <libbuild2/file.hxx>
#include <iomanip> // left, setw()
+#include <sstream>
#include <libbuild2/scope.hxx>
#include <libbuild2/target.hxx>
#include <libbuild2/context.hxx>
#include <libbuild2/filesystem.hxx>
-#include <libbuild2/prerequisite.hxx>
#include <libbuild2/diagnostics.hxx>
+#include <libbuild2/prerequisite-key.hxx>
#include <libbuild2/token.hxx>
#include <libbuild2/lexer.hxx>
#include <libbuild2/parser.hxx>
-#include <libbuild2/config/utility.hxx> // save_variable()
+#include <libbuild2/config/utility.hxx> // lookup_config()
using namespace std;
using namespace butl;
@@ -25,6 +26,9 @@ namespace build2
{
// Standard and alternative build file/directory naming schemes.
//
+
+ // build:
+
const dir_path std_build_dir ("build");
const dir_path std_root_dir (dir_path (std_build_dir) /= "root");
const dir_path std_bootstrap_dir (dir_path (std_build_dir) /= "bootstrap");
@@ -39,7 +43,7 @@ namespace build2
const path std_buildfile_file ("buildfile");
const path std_buildignore_file (".buildignore");
- //
+ // build2:
const dir_path alt_build_dir ("build2");
const dir_path alt_root_dir (dir_path (alt_build_dir) /= "root");
@@ -171,7 +175,7 @@ namespace build2
try
{
l5 ([&]{trace << "sourcing " << fn;});
- p.parse_buildfile (l, root, base);
+ p.parse_buildfile (l, &root, base);
}
catch (const io_error& e)
{
@@ -1417,8 +1421,221 @@ namespace build2
return rs;
}
- pair<name, dir_path>
- import_search (scope& ibase, name target, const location& loc, bool subp)
+ // Find or insert a target based on the file path.
+ //
+ static const target*
+ find_target (tracer& trace, context& ctx,
+ const target_type& tt, const path& p)
+ {
+ const target* t (
+ ctx.targets.find (tt,
+ p.directory (),
+ dir_path (),
+ p.leaf ().base ().string (),
+ p.extension (),
+ trace));
+
+ if (t != nullptr)
+ {
+ if (const file* f = t->is_a<file> ())
+ assert (f->path () == p);
+ }
+
+ return t;
+ }
+
+ static pair<target&, ulock>
+ insert_target (tracer& trace, context& ctx,
+ const target_type& tt, path p)
+ {
+ auto r (
+ ctx.targets.insert_locked (tt,
+ p.directory (),
+ dir_path (), // No out (not in project).
+ p.leaf ().base ().string (),
+ p.extension (), // Always specified.
+ true /* implied */,
+ trace));
+
+ if (const file* f = r.first.is_a<file> ())
+ f->path (move (p));
+
+ return r;
+ }
+
+ // Extract metadata for an executable target by executing it with the
+ // --build2-metadata option. In case of an error, issue diagnostics and fail
+ // if opt is false and return nullopt if it's true.
+ //
+ // Note that loading of the metadata is split into two steps, extraction and
+ // parsing, because extraction also serves as validation that the executable
+ // is runnable, what we expected, etc. In other words, we sometimes do the
+ // extraction without parsing. In this light it would have been more
+ // efficient for extract to return the running process with a pipe rather
+ // than the extracted data. But this would complicate the code quite a bit
+ // plus we don't expect the data to be large, typically.
+ //
+ // Also note that we do not check the export.metadata here leaving it to
+ // the caller to do for both this case and export stub.
+ //
+ static optional<string>
+ extract_metadata (const process_path& pp,
+ const string& key,
+ bool opt,
+ const location& loc)
+ {
+ // Note: to ease handling (think patching third-party code) we will always
+ // specify the --build2-metadata option in this single-argument form.
+ //
+ const char* args[] {pp.recall_string (), "--build2-metadata=1", nullptr};
+
+ // @@ TODO This needs some more thinking/clarification. Specifically, what
+ // does it mean "x not found/not ours"? Is it just not found in PATH?
+ // That plus was not able to execute (e.g., some shared libraries
+ // missing)? That plus abnormal termination? That plus x that we found
+ // is something else?
+ //
+ // Specifically, at least on Linux, when a shared library is not found,
+ // it appears exec() issues the diagnostics and calls exit(127) (that
+ // is, exec() does not return). So this is a normal termination with a
+ // peculiar exit code.
+ //
+ // Overall, it feels like we should only silently ignore the "not
+ // found" and "not ours" cases since for all others the result is
+ // ambigous: it could be "ours" but just broken and the user expects
+ // us to use it but we silently ignored it. But then the same can be
+ // said about the "not ours" case: the user expected us to find "ours"
+ // but we didn't and silently ignored it.
+ //
+ try
+ {
+ // Note: not using run_*() functions since need to be able to suppress
+ // all errors, including inability to exec.
+ //
+ if (verb >= 3)
+ print_process (args);
+
+ process pr (pp,
+ args,
+ -2 /* stdin to /dev/null */,
+ -1 /* stdout to pipe */,
+ opt ? -2 : 2 /* stderr to /dev/null or pass-through */);
+
+ try
+ {
+ ifdstream is (move (pr.in_ofd), fdstream_mode::skip);
+ string r;
+ getline (is, r, '\0'); // Will fail if there is no data.
+ is.close (); // Detect errors.
+
+ if (pr.wait ())
+ {
+ // Check the signature line. It should be in the following form:
+ //
+ // # build2 buildfile <key>
+ //
+ // This makes sure we don't treat bogus output as metadata and also
+ // will allow us to support other formats (say, JSON) in the future.
+ // Note that we won't be able to add more options since trying them
+ // will be expensive.
+ //
+ if (r.compare (0,
+ 20 + key.size (),
+ "# build2 buildfile " + key + '\n') == 0)
+ return r;
+
+ if (!opt)
+ error (loc) << "invalid metadata signature in " << args[0]
+ << " output";
+
+ goto fail;
+ }
+
+ // Process error, fall through.
+ }
+ catch (const io_error& e)
+ {
+ // IO error (or process error), fall through.
+ }
+
+ // Deal with process or IO error.
+ //
+ if (pr.wait ())
+ {
+ if (!opt)
+ error (loc) << "unable to read metadata from " << args[0];
+ }
+ else
+ {
+ // The child process presumably issued diagnostics but if it didn't,
+ // the result will be very confusing. So let's issue something
+ // generic for good measure.
+ //
+ if (!opt)
+ error (loc) << "unable to extract metadata from " << args[0];
+ }
+
+ goto fail;
+ }
+ catch (const process_error& e)
+ {
+ if (!opt)
+ error (loc) << "unable to execute " << args[0] << ": " << e;
+
+ if (e.child)
+ exit (1);
+
+ goto fail;
+ }
+
+ fail:
+
+ if (opt)
+ return nullopt;
+ else
+ throw failed ();
+ }
+
+ static void
+ parse_metadata (target& t, const string& md, const location& loc)
+ {
+ istringstream is (md);
+ path_name in ("<metadata>");
+
+ auto df = make_diag_frame (
+ [&t, &loc] (const diag_record& dr)
+ {
+ dr << info (loc) << "while loading metadata for " << t;
+ });
+
+ parser p (t.ctx);
+ p.parse_buildfile (is, in,
+ nullptr /* root */,
+ t.base_scope ().rw (), // Load phase.
+ &t);
+ }
+
+ // Return the processed target name as well as the project directory, if
+ // any.
+ //
+ // Absent project directory means nothing importable for this target was
+ // found (and the returned target name is the same as the original). Empty
+ // project directory means the target was found in an ad hoc manner, outside
+ // of any project (in which case it may still be qualified; see
+ // config.import.<proj>.<name>[.<type>]).
+ //
+ // Return empty name if an ad hoc import resulted in a NULL target (only
+ // allowed if optional is true).
+ //
+ pair<name, optional<dir_path>>
+ import_search (bool& new_value,
+ scope& ibase,
+ name tgt,
+ bool opt,
+ const optional<string>& meta,
+ bool subp,
+ const location& loc,
+ const char* what)
{
tracer trace ("import_search");
@@ -1426,24 +1643,24 @@ namespace build2
// short and sweet: we simply return it as empty-project-qualified and
// let someone else (e.g., a rule) take a stab at it.
//
- if (target.unqualified ())
+ if (tgt.unqualified ())
{
- target.proj = project_name ();
- return make_pair (move (target), dir_path ());
+ tgt.proj = project_name ();
+ return make_pair (move (tgt), optional<dir_path> ());
}
context& ctx (ibase.ctx);
// Otherwise, get the project name and convert the target to unqualified.
//
- project_name proj (move (*target.proj));
- target.proj = nullopt;
+ project_name proj (move (*tgt.proj));
+ tgt.proj = nullopt;
scope& iroot (*ibase.root_scope ());
// Figure out the imported project's out_root.
//
- dir_path out_root;
+ optional<dir_path> out_root;
// First try the config.import.* mechanism. The idea is that if the user
// explicitly told us the project's location, then we should prefer that
@@ -1452,95 +1669,208 @@ namespace build2
//
auto& vp (iroot.var_pool ());
+ using config::lookup_config;
+
for (;;) // Break-out loop.
{
- string n ("config.import." + proj.variable ());
+ string projv (proj.variable ());
+ string n ("config.import." + projv);
- auto skip = [&target, &proj, &trace] ()
+ // Skip import phase 1.
+ //
+ auto skip = [&tgt, &proj, &trace] ()
{
- target.proj = move (proj);
- l5 ([&]{trace << "skipping " << target;});
- return make_pair (move (target), dir_path ());
+ tgt.proj = move (proj);
+ l5 ([&]{trace << "skipping " << tgt;});
+ return make_pair (move (tgt), optional<dir_path> ());
};
- // config.import.<proj>
+ // Add hoc import.
//
- {
- // Note: pattern-typed in context ctor as an overridable variable of
- // type abs_dir_path (path auto-completion).
- //
- const variable& var (vp.insert (n));
-
- if (auto l = iroot[var])
- {
- out_root = cast<dir_path> (l); // Normalized and actualized.
-
- // Mark as part of config.
- //
- config::save_variable (iroot, var);
-
- // Empty config.import.* value means don't look in subprojects or
- // amalgamations and go straight to the rule-specific import (e.g.,
- // to use system-installed).
- //
- if (out_root.empty ())
- return skip ();
-
- break;
- }
- }
-
// config.import.<proj>.<name>.<type>
// config.import.<proj>.<name>
//
// For example: config.import.build2.b.exe=/opt/build2/bin/b
//
- if (!target.value.empty ())
+ // If <type> is exe and <proj> and <name> are the same, then we also
+ // recognize the special config.<proj> (tool importation; we could
+ // also handle the case where <proj> is not the same as <name> via
+ // the config.<proj>.<name> variable). For backwards-compatibility
+ // reasons, it takes precedence over config.import.
+ //
+ // Note: see import phase 2 diagnostics if changing anything here.
+ //
+ // @@ How will this work for snake-case targets, say libs{build2-foo}?
+ // As well as for dot-separated target types, say, cli.cxx{}?
+ //
+ // @@ This duality has a nasty side-effect: if we have config.<proj>
+ // configured, then specifying config.<proj>.import has no effect
+ // (see also a note below on priority just among these options).
+ //
+ // Some ideas on how to resolve this include: using lookup depth,
+ // using override info, and using the "new value" status. All of
+ // these undoubtfully will complicate this logic (i.e., we will have
+ // to lookup all of them and then decide which one "wins").
+ //
+ if (!tgt.value.empty ())
{
- auto lookup = [&iroot, &vp, &loc] (string name) -> path
+ // Return NULL if not found and empty path if NULL. For executable
+ // targets (exe is true), also treat the special `false` value as
+ // NULL.
+ //
+ auto lookup = [&new_value, &iroot, opt, &loc, what] (
+ const variable& var, bool exe) -> const path*
{
- // Note: pattern-typed in context ctor as an overridable variable of
- // type path.
- //
- const variable& var (vp.insert (move (name)));
+ auto l (lookup_config (new_value, iroot, var));
- path r;
- if (auto l = iroot[var])
+ if (l.defined ())
{
- r = cast<path> (l);
+ const path* p (cast_null<path> (l));
+
+ if (p != nullptr)
+ {
+ if (p->empty ())
+ fail (loc) << "empty path in " << var;
- if (r.empty ())
- fail (loc) << "empty path in " << var.name;
+ if (!exe || p->to_directory () || p->string () != "false")
+ return p;
+ }
+
+ if (!opt)
+ fail (loc) << (p == nullptr ? "null" : "false") << " in "
+ << var << " for non-optional " << what;
- config::save_variable (iroot, var);
+ return &empty_path;
}
- return r;
+ return nullptr;
};
- // First try .<name>.<type>, then just .<name>.
+ // First try config.<proj>, then import.<name>.<type>, and finally
+ // just import.<name>.
//
- path p;
- if (target.typed ())
- p = lookup (n + '.' + target.value + '.' + target.type);
-
- if (p.empty ())
- p = lookup (n + '.' + target.value);
+ // @@ What should we do if several of them are specified? For example,
+ // one is inherited from amalgamation while the other is specified
+ // on the project's root? We could pick the one with the least
+ // lookup depth. On the other hand, we expect people to stick with
+ // the config.<proj> notation for tools (since it's a lot easier to
+ // type) so let's not complicate things for the time being.
+ //
+ // Another alternative would be to see which one is new.
+ //
+ const path* p (nullptr);
- if (!p.empty ())
+ if (tgt.typed ())
{
- // If the path is relative, then keep it project-qualified assuming
- // import phase 2 knows what to do with it. Think:
- //
- // config.import.build2.b=b-boot
+ bool e (tgt.type == "exe");
+
+ // The config.import.* vars are pattern-typed in context ctor as an
+ // overridable variable of type path. The config.<proj> we have to
+ // type manually.
//
- if (p.relative ())
- target.proj = move (proj);
+ if (e && (projv == tgt.value || proj == tgt.value))
+ p = lookup (vp.insert<path> ("config." + projv), e);
+
+ if (p == nullptr)
+ p = lookup (vp.insert (n + '.' + tgt.value + '.' + tgt.type), e);
+ }
+
+ if (p == nullptr)
+ p = lookup (vp.insert (n + '.' + tgt.value), false);
+
+ if (p != nullptr)
+ {
+ if (p->empty ())
+ tgt = name (); // NULL
+ else
+ {
+ tgt.dir = p->directory ();
+ tgt.value = p->leaf ().string ();
+
+ // If the path is relative, then keep it project-qualified
+ // assuming import phase 2 knows what to do with it. Think:
+ //
+ // config.import.build2.b=b-boot
+ //
+ // @@ Maybe we should still complete it if it's not simple? After
+ // all, this is a path, do we want interpretations other than
+ // relative to CWD? Maybe we do, who knows. Doesn't seem to
+ // harm anything at the moment.
+ //
+ // Why not call import phase 2 directly here? Well, one good
+ // reason would be to allow for rule-specific import resolution.
+ //
+ if (p->relative ())
+ tgt.proj = move (proj);
+ else
+ {
+ // Enter the target and assign its path (this will most commonly
+ // be some out of project file).
+ //
+ // @@ Should we check that the file actually exists (and cache
+ // the extracted timestamp)? Or just let things take their
+ // natural course?
+ //
+ name n (tgt);
+ auto r (ibase.find_target_type (n, loc));
- target.dir = p.directory ();
- target.value = p.leaf ().string ();
+ if (r.first == nullptr)
+ fail (loc) << "unknown target type " << n.type << " in " << n;
+
+ // Note: not using the extension extracted by find_target_type()
+ // to be consistent with import phase 2.
+ //
+ target& t (insert_target (trace, ctx, *r.first, *p).first);
- return make_pair (move (target), dir_path ());
+ // Load the metadata, similar to import phase 2.
+ //
+ if (meta)
+ {
+ if (exe* e = t.is_a<exe> ())
+ {
+ if (!e->vars[ctx.var_export_metadata].defined ())
+ {
+ parse_metadata (*e,
+ *extract_metadata (e->process_path (),
+ *meta,
+ false /* optional */,
+ loc),
+ loc);
+ }
+ }
+ }
+ }
+ }
+
+ return make_pair (move (tgt), optional<dir_path> (dir_path ()));
+ }
+ }
+
+ // Normal import.
+ //
+ // config.import.<proj>
+ //
+ // Note: see import phase 2 diagnostics if changing anything here.
+ //
+ {
+ // Note: pattern-typed in context ctor as an overridable variable of
+ // type abs_dir_path (path auto-completion).
+ //
+ auto l (lookup_config (new_value, iroot, vp.insert (n)));
+
+ if (l.defined ())
+ {
+ const dir_path* d (cast_null<dir_path> (l));
+
+ // Empty/NULL config.import.* value means don't look in subprojects
+ // or amalgamations and go straight to the rule-specific import
+ // (e.g., to use system-installed).
+ //
+ if (d == nullptr || d->empty ())
+ return skip ();
+
+ out_root = *d; // Normalized and actualized.
+ break;
}
}
@@ -1560,7 +1890,7 @@ namespace build2
{
out_root = cast<dir_path> (l);
- if (out_root.empty ())
+ if (out_root->empty ())
return skip ();
break;
@@ -1609,22 +1939,27 @@ namespace build2
// Add the qualification back to the target (import_load() will remove it
// again).
//
- target.proj = move (proj);
+ tgt.proj = move (proj);
- return make_pair (move (target), move (out_root));
+ return make_pair (move (tgt), move (out_root));
}
pair<names, const scope&>
- import_load (context& ctx, pair<name, dir_path> x, const location& loc)
+ import_load (context& ctx,
+ pair<name, optional<dir_path>> x,
+ bool meta,
+ const location& loc)
{
tracer trace ("import_load");
- name target (move (x.first));
- dir_path out_root (move (x.second));
+ assert (x.second);
- assert (target.proj);
- project_name proj (move (*target.proj));
- target.proj = nullopt;
+ name tgt (move (x.first));
+ dir_path out_root (move (*x.second));
+
+ assert (tgt.proj);
+ project_name proj (move (*tgt.proj));
+ tgt.proj = nullopt;
// Bootstrap the imported root scope. This is pretty similar to what we do
// in main() except that here we don't try to guess src_root.
@@ -1746,15 +2081,27 @@ namespace build2
ts.assign (ctx.var_out_root) = move (out_root);
ts.assign (ctx.var_src_root) = move (src_root);
- // Also pass the target being imported in the import.target variable.
+ // Pass the target being imported in import.target.
//
{
value& v (ts.assign (ctx.var_import_target));
- if (!target.empty ()) // Otherwise leave NULL.
- v = target; // Can't move (need for diagnostics below).
+ if (!tgt.empty ()) // Otherwise leave NULL.
+ v = tgt; // Can't move (need for diagnostics below).
}
+ // Pass the metadata compatibility version in import.metadata.
+ //
+ // This serves both as an indication that the metadata is required (can be
+ // useful, for example, in cases where it is expensive to calculate) as
+ // well as the maximum version we recognize. The exporter may return it in
+ // any version up to and including this maximum. And it may return it even
+ // if not requested (but only in version 1). The exporter should also set
+ // the returned version as the target-specific export.metadata variable.
+ //
+ if (meta)
+ ts.assign (ctx.var_import_metadata) = uint64_t (1);
+
// Load the export stub. Note that it is loaded in the context
// of the importing project, not the imported one. The export
// stub will normally switch to the imported root scope at some
@@ -1778,8 +2125,8 @@ namespace build2
// If there were no export directive executed in an export stub, assume
// the target is not exported.
//
- if (v.empty () && !target.empty ())
- fail (loc) << "target " << target << " is not exported by project "
+ if (v.empty () && !tgt.empty ())
+ fail (loc) << "target " << tgt << " is not exported by project "
<< proj;
return pair<names, const scope&> (move (v), *root);
@@ -1790,32 +2137,104 @@ namespace build2
}
}
- names
- import (scope& base, name target, const location& loc)
+ pair<names, import_kind>
+ import (scope& base,
+ name tgt,
+ bool ph2,
+ bool opt,
+ bool metadata,
+ const location& loc)
{
tracer trace ("import");
- l5 ([&]{trace << target << " from " << base;});
+ l5 ([&]{trace << tgt << " from " << base;});
+
+ assert ((!opt || ph2) && (!metadata || ph2));
+
+ context& ctx (base.ctx);
+ assert (ctx.phase == run_phase::load);
+
+ // If metadata is requested, delegate to import_direct() which will lookup
+ // the target and verify the metadata was loaded.
+ //
+ if (metadata)
+ {
+ pair<const target*, import_kind> r (
+ import_direct (base, move (tgt), ph2, opt, metadata, loc));
+
+ return make_pair (r.first != nullptr ? r.first->as_name () : names {},
+ r.second);
+ }
+
+ // Save the original target name as metadata key.
+ //
+ auto meta (metadata ? optional<string> (tgt.value) : nullopt);
- pair<name, dir_path> r (import_search (base, move (target), loc));
+ pair<name, optional<dir_path>> r (
+ import_search (base, move (tgt), opt, meta, true /* subpproj */, loc));
- // If we couldn't find the project, return to let someone else (e.g., a
- // rule) take a stab at it.
+ // If there is no project, we are either done or go straight to phase 2.
//
- if (r.second.empty ())
+ if (!r.second || r.second->empty ())
{
- l5 ([&]{trace << "postponing " << r.first;});
- return names {move (r.first)};
+ names ns;
+
+ if (r.first.empty ())
+ {
+ assert (opt); // NULL
+ }
+ else
+ {
+ ns.push_back (move (r.first));
+
+ // If the target is still qualified, it is either phase 2 now or we
+ // return it as is to let someone else (e.g., a rule, import phase 2)
+ // take a stab at it later.
+ //
+ if (ns.back ().qualified ())
+ {
+ if (ph2)
+ {
+ // This is tricky: we only want the optional semantics for the
+ // fallback case.
+ //
+ if (const target* t = import (ctx,
+ base.find_prerequisite_key (ns, loc),
+ opt && !r.second /* optional */,
+ meta,
+ false /* existing */,
+ loc))
+ ns = t->as_name ();
+ else
+ ns.clear (); // NULL
+ }
+ else
+ l5 ([&]{trace << "postponing " << r.first;});
+ }
+ }
+
+ return make_pair (
+ move (ns),
+ r.second.has_value () ? import_kind::adhoc : import_kind::fallback);
}
- return import_load (base.ctx, move (r), loc).first;
+ return make_pair (
+ import_load (base.ctx, move (r), metadata, loc).first,
+ import_kind::normal);
}
const target*
- import (context& ctx, const prerequisite_key& pk, bool existing)
+ import (context& ctx,
+ const prerequisite_key& pk,
+ bool opt,
+ const optional<string>& meta,
+ bool exist,
+ const location& loc)
{
tracer trace ("import");
+ assert (!meta || !exist);
+
assert (pk.proj);
const project_name& proj (*pk.proj);
@@ -1826,7 +2245,7 @@ namespace build2
// Try to find the executable in PATH (or CWD if relative).
//
- if (tt.is_a<exe> ())
+ for (; tt.is_a<exe> (); ) // Breakout loop.
{
path n (*tk.dir);
n /= *tk.name;
@@ -1836,63 +2255,218 @@ namespace build2
n += *tk.ext;
}
- // Only search in PATH (or CWD).
+ // Only search in PATH (or CWD if not simple).
//
- process_path pp (process::try_path_search (n, true, dir_path (), true));
+ process_path pp (
+ process::try_path_search (n,
+ false /* init */,
+ dir_path () /* fallback */,
+ true /* path_only */));
+ if (pp.empty ())
+ break;
- if (!pp.empty ())
- {
- path& p (pp.effect);
- assert (!p.empty ()); // We searched for a simple name.
-
- const exe* t (
- !existing
- ? &ctx.targets.insert<exe> (tt,
- p.directory (),
- dir_path (), // No out (not in project).
- p.leaf ().base ().string (),
- p.extension (), // Always specified.
- trace)
- : ctx.targets.find<exe> (tt,
- p.directory (),
- dir_path (),
- p.leaf ().base ().string (),
- p.extension (),
- trace));
-
- if (t != nullptr)
- {
- if (!existing)
- t->path (move (p));
- else
- assert (t->path () == p);
+ const path& p (pp.effect);
+ assert (!p.empty ()); // We searched for a relative path.
+ if (exist) // Note: then meta is false.
+ {
+ if (const target* t = find_target (trace, ctx, tt, p))
return t;
+
+ break;
+ }
+
+ // Try hard to avoid re-extracting the metadata (think of a tool that is
+ // used by multiple projects in an amalgamation).
+ //
+ optional<string> md;
+ optional<const target*> t;
+ if (meta)
+ {
+ t = find_target (trace, ctx, tt, p);
+
+ if (*t != nullptr && (*t)->vars[ctx.var_export_metadata].defined ())
+ return *t; // We've got all we need.
+
+ if (!(md = extract_metadata (pp, *meta, opt, loc)))
+ break;
+ }
+
+ if (!t || *t == nullptr)
+ {
+ pair<target&, ulock> r (insert_target (trace, ctx, tt, p));
+ t = &r.first;
+
+ // Cache the process path if we've created the target (it's possible
+ // that the same target will be imported via different paths, e.g., as
+ // a simple name via PATH search and as an absolute path in which case
+ // the first import will determine the path).
+ //
+ if (r.second.owns_lock ())
+ {
+ r.first.as<exe> ().process_path (move (pp));
+ r.second.unlock ();
}
}
+
+ // Save the metadata. Note that this happens during the load phase and
+ // so MT-safe.
+ //
+ if (meta)
+ parse_metadata ((*t)->rw (), *md, loc);
+
+ return *t;
}
- if (existing)
+ if (opt || exist)
return nullptr;
- // @@ We no longer have location. This is especially bad for the
- // empty case, i.e., where do I need to specify the project
- // name)? Looks like the only way to do this is to keep location
- // in name and then in prerequisite. Perhaps one day...
- //
diag_record dr;
- dr << fail << "unable to import target " << pk;
+ dr << fail (loc) << "unable to import target " << pk;
if (proj.empty ())
dr << info << "consider adding its installation location" <<
info << "or explicitly specify its project name";
else
- dr << info << "use config.import." << proj.variable ()
- << " command line variable to specify its project out_root";
+ {
+ string projv (proj.variable ());
+
+ // Suggest normal import.
+ //
+ dr << info << "use config.import." << projv << " configuration variable "
+ << "to specify its project out_root";
+
+ // Suggest ad hoc import.
+ //
+ string v (tt.is_a<exe> () && (projv == *tk.name || proj == *tk.name)
+ ? "config." + projv
+ : "config.import." + projv + '.' + *tk.name + '.' + tt.name);
+
+ dr << info << "or use " << v << " configuration variable to specify its "
+ << "path";
+ }
dr << endf;
}
+ pair<const target*, import_kind>
+ import_direct (bool& new_value,
+ scope& base,
+ name tgt,
+ bool ph2,
+ bool opt,
+ bool metadata,
+ const location& loc,
+ const char* what)
+ {
+ // This is like normal import() except we return the target rather than
+ // its name.
+ //
+ tracer trace ("import_direct");
+
+ l5 ([&]{trace << tgt << " from " << base << " for " << what;});
+
+ assert ((!opt || ph2) && (!metadata || ph2));
+
+ context& ctx (base.ctx);
+ assert (ctx.phase == run_phase::load);
+
+ auto meta (metadata ? optional<string> (tgt.value) : nullopt);
+
+ names ns;
+ import_kind k;
+ const target* t (nullptr);
+
+ pair<name, optional<dir_path>> r (
+ import_search (new_value,
+ base,
+ move (tgt),
+ opt,
+ meta,
+ true /* subpproj */,
+ loc,
+ what));
+
+ // If there is no project, we are either done or go straight to phase 2.
+ //
+ if (!r.second || r.second->empty ())
+ {
+ k = r.second.has_value () ? import_kind::adhoc : import_kind::fallback;
+
+ if (r.first.empty ())
+ {
+ assert (opt);
+ return make_pair (t, k); // NULL
+ }
+ else if (r.first.qualified ())
+ {
+ if (ph2)
+ {
+ names ns {move (r.first)};
+
+ // This is tricky: we only want the optional semantics for the
+ // fallback case.
+ //
+ t = import (ctx,
+ base.find_prerequisite_key (ns, loc),
+ opt && !r.second,
+ meta,
+ false /* existing */,
+ loc);
+ }
+
+ if (t == nullptr)
+ return make_pair (t, k); // NULL
+
+ // Otherwise fall through.
+ }
+ else
+ ns.push_back (move (r.first)); // And fall through.
+ }
+ else
+ {
+ k = import_kind::normal;
+ ns = import_load (base.ctx, move (r), metadata, loc).first;
+ }
+
+ if (t == nullptr)
+ {
+ // Similar logic to perform's search().
+ //
+ target_key tk (base.find_target_key (ns, loc));
+ t = ctx.targets.find (tk, trace);
+ if (t == nullptr)
+ fail (loc) << "unknown imported target " << tk;
+ }
+
+ if (meta)
+ {
+ if (auto* v = cast_null<uint64_t> (t->vars[ctx.var_export_metadata]))
+ {
+ if (*v != 1)
+ fail (loc) << "unexpected metadata version " << *v
+ << " in imported target " << *t;
+ }
+ else
+ fail (loc) << "no metadata for imported target " << *t;
+ }
+
+ return make_pair (t, k);
+ }
+
+ ostream&
+ operator<< (ostream& o, const pair<const exe*, import_kind>& p)
+ {
+ assert (p.first != nullptr);
+
+ if (p.second == import_kind::normal)
+ o << *p.first;
+ else
+ o << p.first->process_path ();
+
+ return o;
+ }
+
void
create_project (const dir_path& d,
const optional<dir_path>& amal,
diff --git a/libbuild2/file.hxx b/libbuild2/file.hxx
index b44efb6..231ea73 100644
--- a/libbuild2/file.hxx
+++ b/libbuild2/file.hxx
@@ -11,6 +11,7 @@
#include <libbuild2/utility.hxx>
#include <libbuild2/scope.hxx>
+#include <libbuild2/target.hxx>
#include <libbuild2/variable.hxx> // list_value
#include <libbuild2/export.hxx>
@@ -241,37 +242,190 @@ namespace build2
// that (or failed to find anything usable), it calls the standard
// prerequisite search() function which sees this is a project-qualified
// prerequisite and goes straight to the second phase of import. Here,
- // currently, we simply fail but in the future this will be the place where
- // we can call custom "last resort" import hooks. For example, we can hook a
- // package manager that will say, "Hey, dude, I see you are trying to import
- // foo and I see there is a package foo available in repository bar. Wanna,
- // like, download and use it or something?"
+ // currently, we only have special handling of exe{} targets (search in
+ // PATH) simply failing for the rest. But in the future this coud be the
+ // place where we could call custom "last resort" import hooks. For example,
+ // we can hook a package manager that will say, "Hey, dude, I see you are
+ // trying to import foo and I see there is a package foo available in
+ // repository bar. Wanna, like, download and use it or something?" Though
+ // the latest thoughts indicate this is probably a bad idea (implicitness,
+ // complexity, etc).
+ //
+ // More specifically, we have the following kinds of import (tried in this
+ // order):
+ //
+ // ad hoc
+ //
+ // The target is imported by specifying its path directly with
+ // config.import.<proj>.<name>[.<type>]. For example, this can be
+ // used to import an installed target.
+ //
+ //
+ // normal
+ //
+ // The target is imported from a project that was either specified with
+ // config.import.<proj> or was found via the subproject search. This also
+ // loads the target's dependency information.
+ //
+ //
+ // rule-specific
+ //
+ // The target was imported in a rule-specific manner (e.g., a library was
+ // found in the compiler's search paths).
+ //
+ //
+ // fallback/default
+ //
+ // The target was found by the second phase of import (e.g., an executable
+ // was found in PATH).
+
+ // Import phase 1. Return the imported target(s) as well as the kind of
+ // import that was performed with `fallback` indicating it was not found.
+ //
+ // If second is `fallback`, then first contains the original, project-
+ // qualified target. If second is `adhoc`, first may still contain a
+ // project-qualified target (which may or may not be the same as the
+ // original; see the config.import.<proj>.<name>[.<type>] logic for details)
+ // in which case it should still be passed to import phase 2.
+ //
+ // If phase2 is true then the phase 2 is performed right away (we call it
+ // immediate import). Note that if optional is true, phase2 must be true as
+ // well (and thus there is no rule-specific logic for optional imports). In
+ // case of optional, empty names value is retuned if nothing was found.
+ //
+ // If metadata is true, then load the target metadata. In this case phase2
+ // must be true as well.
//
// Note also that we return names rather than a single name: while normally
// it will be a single target name, it can be an out-qualified pair (if
// someone wants to return a source target) but it can also be a non-target
// since we don't restrict what users can import/export.
//
- LIBBUILD2_SYMEXPORT names
- import (scope& base, name, const location&);
-
- LIBBUILD2_SYMEXPORT pair<name, dir_path>
- import_search (scope& base, name, const location&, bool subproj = true);
+ // Finally, note that import is (and should be kept) idempotent or, more
+ // precisely, "accumulatively idempotent" in that additional steps may be
+ // performed (phase 2, loading of the metadata) unless already done.
+ //
+ enum class import_kind {adhoc, normal, fallback};
- LIBBUILD2_SYMEXPORT pair<names, const scope&>
- import_load (context&, pair<name, dir_path>, const location&);
+ LIBBUILD2_SYMEXPORT pair<names, import_kind>
+ import (scope& base,
+ name,
+ bool phase2,
+ bool optional,
+ bool metadata,
+ const location&);
+ // Import phase 2.
+ //
const target&
import (context&, const prerequisite_key&);
- // As above but only imports as an already existing target. Unlike the above
- // version, this one can be called during the execute phase.
+ // As above but import the target "here and now" without waiting for phase 2
+ // (and thus omitting any rule-specific logic). This version of import is,
+ // for example, used by build system modules to perform an implicit import
+ // of the corresponding tool.
+ //
+ // If phase2 is false, then the second phase's fallback/default logic is
+ // only invoked if the import was ad hoc (i.e., a relative path was
+ // specified via config.import.<proj>.<name>[.<type>]) with NULL returned
+ // otherwise.
+ //
+ // If phase2 is true and optional is true, then NULL is returned instead of
+ // failing if phase 2 could not find anything.
+ //
+ // If metadata is true, then load the target metadata. In this case phase2
+ // must be true as well.
+ //
+ // The what argument specifies what triggered the import (for example,
+ // "module load") and is used in diagnostics.
+ //
+ // This function also returns the kind of import that was performed.
+ //
+ pair<const target*, import_kind>
+ import_direct (scope& base,
+ name,
+ bool phase2,
+ bool optional,
+ bool metadata,
+ const location&,
+ const char* what = "import");
+
+ // As above but also return (in new_value) an indication of whether this
+ // import is based on a new config.* value. See config::lookup_config() for
+ // details. Note that a phase 2 fallback/default logic is not considered new
+ // (though this can be easily adjusted based on import kind).
+ //
+ LIBBUILD2_SYMEXPORT pair<const target*, import_kind>
+ import_direct (bool& new_value,
+ scope& base,
+ name,
+ bool phase2,
+ bool optional,
+ bool metadata,
+ const location&,
+ const char* what = "import");
+
+
+ template <typename T>
+ pair<const T*, import_kind>
+ import_direct (scope&,
+ name, bool, bool, bool,
+ const location&, const char* = "import");
+
+ template <typename T>
+ pair<const T*, import_kind>
+ import_direct (bool&,
+ scope&,
+ name,
+ bool, bool, bool,
+ const location&, const char* = "import");
+
+ // Print import_direct<exe>() result either as a target for a normal import
+ // or as a process path for ad hoc and fallback imports. Normally used in
+ // build system modules to print the configuration report.
+ //
+ LIBBUILD2_SYMEXPORT ostream&
+ operator<< (ostream&, const pair<const exe*, import_kind>&);
+
+ // As import phase 2 but only imports as an already existing target. But
+ // unlike it, this function can be called during the execute phase.
//
// Note: similar to search_existing().
//
const target*
import_existing (context&, const prerequisite_key&);
+ // Lower-level components of phase 1 (see implementation for details).
+ //
+ pair<name, optional<dir_path>>
+ import_search (scope& base,
+ name,
+ bool optional_,
+ const optional<string>& metadata, // False or metadata key.
+ bool subprojects,
+ const location&,
+ const char* what = "import");
+
+ // As above but also return (in new_value) an indication of whether this
+ // import is based on a new config.* value. See config::lookup_config()
+ // for details.
+ //
+ LIBBUILD2_SYMEXPORT pair<name, optional<dir_path>>
+ import_search (bool& new_value,
+ scope& base,
+ name,
+ bool optional_,
+ const optional<string>& metadata,
+ bool subprojects,
+ const location&,
+ const char* what = "import");
+
+ LIBBUILD2_SYMEXPORT pair<names, const scope&>
+ import_load (context&,
+ pair<name, optional<dir_path>>,
+ bool metadata,
+ const location&);
+
// Create a build system project in the specified directory.
//
LIBBUILD2_SYMEXPORT void
diff --git a/libbuild2/file.ixx b/libbuild2/file.ixx
index e940eb3..7c09d3d 100644
--- a/libbuild2/file.ixx
+++ b/libbuild2/file.ixx
@@ -11,20 +11,76 @@ namespace build2
return source_once (root, base, bf, base);
}
+ inline pair<name, optional<dir_path>>
+ import_search (scope& base,
+ name tgt,
+ bool opt, const optional<string>& md, bool sp,
+ const location& loc, const char* w)
+ {
+ bool dummy (false);
+ return import_search (dummy, base, move (tgt), opt, md, sp, loc, w);
+ }
+
LIBBUILD2_SYMEXPORT const target*
- import (context&, const prerequisite_key&, bool existing);
+ import (context&,
+ const prerequisite_key&,
+ bool optional_,
+ const optional<string>& metadata, // False or metadata key.
+ bool existing,
+ const location&);
inline const target&
import (context& ctx, const prerequisite_key& pk)
{
assert (ctx.phase == run_phase::match);
- return *import (ctx, pk, false);
+
+ // @@ We no longer have location. This is especially bad for the empty
+ // project case (i.e., where do I need to specify the project name)?
+ // Looks like the only way to do this is to keep location in name and
+ // then in prerequisite. Perhaps one day...
+ //
+ return *import (ctx, pk, false, nullopt, false, location ());
+ }
+
+ inline pair<const target*, import_kind>
+ import_direct (scope& base,
+ name tgt,
+ bool ph2, bool opt, bool md,
+ const location& loc, const char* w)
+ {
+ bool dummy (false);
+ return import_direct (dummy, base, move (tgt), ph2, opt, md, loc, w);
+ }
+
+ template <typename T>
+ inline pair<const T*, import_kind>
+ import_direct (scope& base,
+ name tgt,
+ bool ph2, bool opt, bool md,
+ const location& loc, const char* w)
+ {
+ auto r (import_direct (base, move (tgt), ph2, opt, md, loc, w));
+ return make_pair (r.first != nullptr ? &r.first->as<const T> () : nullptr,
+ r.second);
+ }
+
+ template <typename T>
+ inline pair<const T*, import_kind>
+ import_direct (bool& nv,
+ scope& base,
+ name tgt,
+ bool ph2, bool opt, bool md,
+ const location& loc, const char* w)
+ {
+ auto r (import_direct (nv, base, move (tgt), ph2, opt, md, loc, w));
+ return make_pair (r.first != nullptr ? &r.first->as<const T> () : nullptr,
+ r.second);
}
inline const target*
import_existing (context& ctx, const prerequisite_key& pk)
{
assert (ctx.phase == run_phase::match || ctx.phase == run_phase::execute);
- return import (ctx, pk, true);
+ return import (ctx, pk, false, nullopt, true, location ());
}
}
diff --git a/libbuild2/forward.hxx b/libbuild2/forward.hxx
index b96aa31..1679b4a 100644
--- a/libbuild2/forward.hxx
+++ b/libbuild2/forward.hxx
@@ -55,10 +55,13 @@ namespace build2
class include_type;
struct prerequisite_member;
+ // <libbuild2/prerequisite-key.hxx>
+ //
+ class prerequisite_key;
+
// <libbuild2/prerequisite.hxx>
//
class prerequisite;
- class prerequisite_key;
// <libbuild2/rule.hxx>
//
diff --git a/libbuild2/function.test.cxx b/libbuild2/function.test.cxx
index e059619..514ac1e 100644
--- a/libbuild2/function.test.cxx
+++ b/libbuild2/function.test.cxx
@@ -124,7 +124,7 @@ namespace build2
scope& s (ctx.global_scope.rw ());
parser p (ctx);
- p.parse_buildfile (cin, path_name ("buildfile"), s, s);
+ p.parse_buildfile (cin, path_name ("buildfile"), &s, s);
}
catch (const failed&)
{
diff --git a/libbuild2/module.cxx b/libbuild2/module.cxx
index 28d2468..3abb102 100644
--- a/libbuild2/module.cxx
+++ b/libbuild2/module.cxx
@@ -147,18 +147,32 @@ namespace build2
// (remember, if it's top-level, then it must be in an isolated
// configuration).
//
- pair<name, dir_path> ir (
+ pair<name, optional<dir_path>> ir (
import_search (bs,
name (proj, dir_path (), "libs", "build2-" + mod),
- loc,
- nested /* subprojects */));
+ opt,
+ nullopt /* metadata */,
+ nested /* subprojects */,
+ loc));
- if (!ir.second.empty ())
+ if (ir.first.empty ())
{
+ assert (opt);
+ return nullptr;
+ }
+
+ if (ir.second)
+ {
+ // What if a module is specified with config.import.<mod>.<lib>.libs?
+ // Note that this could still be a project-qualified target.
+ //
+ if (ir.second->empty ())
+ fail (loc) << "direct module target importation not yet supported";
+
// We found the module as a target in a project. Now we need to update
// the target (which will also give us the shared library path).
//
- l5 ([&]{trace << "found " << ir.first << " in " << ir.second;});
+ l5 ([&]{trace << "found " << ir.first << " in " << *ir.second;});
// Create the build context if necessary.
//
@@ -215,7 +229,8 @@ namespace build2
// Load the imported project in the module context.
//
- pair<names, const scope&> lr (import_load (ctx, move (ir), loc));
+ pair<names, const scope&> lr (
+ import_load (ctx, move (ir), false /* metadata */, loc));
l5 ([&]{trace << "loaded " << lr.first;});
@@ -638,7 +653,7 @@ namespace build2
// Note that it would have been nice to keep these inline but we need the
// definition of scope for the variable lookup.
//
- bool
+ const shared_ptr<module>*
load_module (scope& rs,
scope& bs,
const string& name,
@@ -646,8 +661,18 @@ namespace build2
bool opt,
const variable_map& hints)
{
- return cast_false<bool> (bs[name + ".loaded"]) ||
- init_module (rs, bs, name, loc, opt, hints);
+ if (cast_false<bool> (bs[name + ".loaded"]))
+ {
+ if (cast_false<bool> (bs[name + ".configured"]))
+ return &rs.root_extra->modules.find (name)->second.module;
+ }
+ else
+ {
+ if (module_state* ms = init_module (rs, bs, name, loc, opt, hints))
+ return &ms->module;
+ }
+
+ return nullptr;
}
const shared_ptr<module>&
@@ -657,6 +682,9 @@ namespace build2
const location& loc,
const variable_map& hints)
{
+ //@@ TODO: shouldn't we also check for configured? What if the previous
+ // attempt to load it was optional?
+
return cast_false<bool> (bs[name + ".loaded"])
? rs.root_extra->modules.find (name)->second.module
: init_module (rs, bs, name, loc, false /* optional */, hints)->module;
diff --git a/libbuild2/module.hxx b/libbuild2/module.hxx
index f5c726e..b2b42e4 100644
--- a/libbuild2/module.hxx
+++ b/libbuild2/module.hxx
@@ -170,9 +170,18 @@ namespace build2
// A wrapper over init_module() to use from other modules that incorporates
// the <name>.loaded variable check (use init_module() directly to sidestep
- // this check).
+ // this check). Return a pointer to the pointer to the module instance if it
+ // was both successfully loaded and configured and NULL otherwise (so can be
+ // used as bool).
//
- LIBBUILD2_SYMEXPORT bool
+ // Note also that NULL can be returned even of optional is false which
+ // happens if the requested module has already been loaded but failed to
+ // configure. The function could have issued diagnostics but the caller can
+ // normally provide additional information.
+ //
+ // Note: for non-optional modules use the versions below.
+ //
+ LIBBUILD2_SYMEXPORT const shared_ptr<module>*
load_module (scope& root,
scope& base,
const string& name,
diff --git a/libbuild2/name.hxx b/libbuild2/name.hxx
index f623110..d0e8d85 100644
--- a/libbuild2/name.hxx
+++ b/libbuild2/name.hxx
@@ -50,6 +50,10 @@ namespace build2
name (dir_path d, string t, string v)
: dir (move (d)), type (move (t)), value (move (v)) {}
+ name (string p, dir_path d, string t, string v)
+ : proj (project_name (move (p))), dir (move (d)), type (move (t)),
+ value (move (v)) {}
+
name (optional<project_name> p, dir_path d, string t, string v)
: proj (move (p)), dir (move (d)), type (move (t)), value (move (v)) {}
diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx
index cd6fe5e..a5c34d9 100644
--- a/libbuild2/parser.cxx
+++ b/libbuild2/parser.cxx
@@ -222,37 +222,53 @@ namespace build2
}
void parser::
- parse_buildfile (istream& is, const path_name& in, scope& root, scope& base)
+ parse_buildfile (istream& is,
+ const path_name& in,
+ scope* root,
+ scope& base,
+ target* tgt,
+ prerequisite* prq)
{
lexer l (is, in);
- parse_buildfile (l, root, base);
+ parse_buildfile (l, root, base, tgt, prq);
}
void parser::
- parse_buildfile (lexer& l, scope& root, scope& base)
+ parse_buildfile (lexer& l,
+ scope* root,
+ scope& base,
+ target* tgt,
+ prerequisite* prq)
{
path_ = &l.name ();
lexer_ = &l;
- root_ = &root;
+ root_ = root;
scope_ = &base;
- target_ = nullptr;
- prerequisite_ = nullptr;
+ target_ = tgt;
+ prerequisite_ = prq;
pbase_ = scope_->src_path_;
- enter_buildfile (*path_); // Needs scope_.
+ if (path_->path != nullptr)
+ enter_buildfile (*path_->path); // Note: needs scope_.
token t;
type tt;
next (t, tt);
- parse_clause (t, tt);
+ if (target_ != nullptr || prerequisite_ != nullptr)
+ {
+ parse_variable_block (t, tt);
+ }
+ else
+ {
+ parse_clause (t, tt);
+ process_default_target (t);
+ }
if (tt != type::eos)
fail (t) << "unexpected " << t;
-
- process_default_target (t);
}
token parser::
@@ -385,7 +401,9 @@ namespace build2
{
f = &parser::parse_run;
}
- else if (n == "import")
+ else if (n == "import" ||
+ n == "import?" ||
+ n == "import!")
{
f = &parser::parse_import;
}
@@ -892,7 +910,7 @@ namespace build2
// NULL, then this is a target type/pattern-specific block.
//
// enter: first token of first line in the block (normal lexer mode)
- // leave: rcbrace
+ // leave: rcbrace or eos
//
// This is a more restricted variant of parse_clause() that only allows
// variable assignments.
@@ -1098,6 +1116,8 @@ namespace build2
//
name n (tt != type::colon ? move (pn) : pn);
+ // See also scope::find_prerequisite_key().
+ //
auto rp (scope_->find_target_type (n, ploc));
const target_type* tt (rp.first);
optional<string>& e (rp.second);
@@ -1210,7 +1230,7 @@ namespace build2
{
next (t, tt); // First token inside the block.
- parse_variable_block (t, tt, nullptr, string ());
+ parse_variable_block (t, tt);
if (tt != type::rcbrace)
fail (t) << "expected '}' instead of " << t;
@@ -1318,7 +1338,7 @@ namespace build2
{
next (t, tt); // First token inside the block.
- parse_variable_block (t, tt, nullptr, string ());
+ parse_variable_block (t, tt);
if (tt != type::rcbrace)
fail (t) << "expected '}' instead of " << t;
@@ -1333,18 +1353,14 @@ namespace build2
}
void parser::
- source (istream& is,
- const path_name& in,
- const location& loc,
- bool enter,
- bool deft)
+ source (istream& is, const path_name& in, const location& loc, bool deft)
{
tracer trace ("parser::source", &path_);
l5 ([&]{trace (loc) << "entering " << in;});
- if (enter)
- enter_buildfile (in);
+ if (in.path != nullptr)
+ enter_buildfile (*in.path);
const path_name* op (path_);
path_ = &in;
@@ -1417,7 +1433,6 @@ namespace build2
source (ifs,
path_name (p),
get_location (t),
- true /* enter */,
false /* default_target */);
}
catch (const io_error& e)
@@ -1558,8 +1573,7 @@ namespace build2
source (ifs,
path_name (p),
get_location (t),
- true /* enter */,
- true /* default_target */);
+ true /* default_target */);
}
catch (const io_error& e)
{
@@ -1642,7 +1656,6 @@ namespace build2
source (is,
path_name ("<stdout>"),
l,
- false /* enter */,
false /* default_target */);
}
@@ -1689,13 +1702,10 @@ namespace build2
// the config[.**].<project>.** pattern where <project> is the innermost
// named project.
//
- // Note that we currently don't allow just the config.<project> name even
- // though this is used quite liberally in build system modules. Allowing
- // this will complicate the logic (and documentation) a bit and there are
- // no obvious use-cases. On the other hand, for tools that could be used
- // during the build (say yacc), such a variable would most likely be used
- // to specify its location (say config.yacc) . So let's "reserve" it for
- // now.
+ // Note that we also allow just the config.<project> name which can be
+ // used by tools (such as source code generators) that use themselves in
+ // their own build. This is a bit of an advanced/experimental setup so
+ // we leave this undocumented for now.
//
// What should we do if there is no named project? We used to fail but
// there are valid cases where this can happen, for example, a standalone
@@ -1788,7 +1798,7 @@ namespace build2
name.compare (0, 7, "config.") != 0)
{
if (!as.empty ())
- fail (t) << "unexpected attributes for report-only variable";
+ fail (as.loc) << "unexpected attributes for report-only variable";
attributes_pop ();
@@ -1822,9 +1832,15 @@ namespace build2
if (!proj.empty ())
{
- if (name.find ('.' + proj + '.') == string::npos)
+ size_t p (name.find ('.' + proj));
+
+ if (p == string::npos ||
+ ((p += proj.size () + 1) != name.size () && // config.<proj>
+ name[p] != '.')) // config.<proj>.
+ {
dr << fail (t) << "configuration variable '" << name
<< "' does not include project name";
+ }
}
if (!dr.empty ())
@@ -1937,8 +1953,11 @@ namespace build2
// General import format:
//
- // import [<var>=](<project>|<project>/<target>])+
+ // import[?!] [<attrs>] [<var>=](<project>|<project>%<target>])+
//
+ bool opt (t.value.back () == '?');
+ bool ph2 (opt || t.value.back () == '!');
+
type atype; // Assignment type.
value* val (nullptr);
const variable* var (nullptr);
@@ -1950,13 +1969,38 @@ namespace build2
// switch to the value mode, get the first token, and then re-parse it
// manually looking for =/=+/+=.
//
+ // Note that if we ever wanted to support value attributes, that would be
+ // non-trivial.
+ //
mode (lexer_mode::value, '@');
next_with_attributes (t, tt);
- // Get variable attributes, if any (note that here we will go into a
- // nested value mode with a different pair character).
+ // Get variable (or value) attributes, if any, and deal with the special
+ // metadata attribute. Since currently it can only appear in the import
+ // directive, we handle it in an ad hoc manner.
//
- auto at (attributes_push (t, tt));
+ attributes_push (t, tt);
+ attributes& as (attributes_top ());
+
+ bool meta (false);
+ for (auto i (as.begin ()); i != as.end (); )
+ {
+ if (i->name == "metadata")
+ {
+ if (!ph2)
+ fail (as.loc) << "loading metadata requires immediate import" <<
+ info << "consider using the import! directive instead";
+
+ meta = true;
+ }
+ else
+ {
+ ++i;
+ continue;
+ }
+
+ i = as.erase (i);
+ }
const location vloc (get_location (t));
@@ -1964,8 +2008,9 @@ namespace build2
{
// Split the token into the variable name and value at position (p) of
// '=', taking into account leading/trailing '+'. The variable name is
- // returned while the token is set to value. If the resulting token
- // value is empty, get the next token. Also set assignment type (at).
+ // returned while the token is set to the value part. If the resulting
+ // token value is empty, get the next token. Also set assignment type
+ // (at).
//
auto split = [&atype, &t, &tt, this] (size_t p) -> string
{
@@ -2042,10 +2087,10 @@ namespace build2
}
else
{
- if (at.first)
- fail (at.second) << "attributes without variable";
- else
- attributes_pop ();
+ if (!as.empty ())
+ fail (as.loc) << "attributes without variable";
+
+ attributes_pop ();
}
// The rest should be a list of projects and/or targets. Parse them as
@@ -2064,19 +2109,27 @@ namespace build2
// import() will check the name, if required.
//
- names r (import (*scope_, move (n), l));
+ names r (import (*scope_, move (n), ph2, opt, meta, l).first);
if (val != nullptr)
{
- if (atype == type::assign)
+ if (r.empty ()) // Optional not found.
{
- val->assign (move (r), var);
- atype = type::append; // Append subsequent values.
+ if (atype == type::assign)
+ *val = nullptr;
}
- else if (atype == type::prepend)
- val->prepend (move (r), var);
else
- val->append (move (r), var);
+ {
+ if (atype == type::assign)
+ val->assign (move (r), var);
+ else if (atype == type::prepend)
+ val->prepend (move (r), var);
+ else
+ val->append (move (r), var);
+ }
+
+ if (atype == type::assign)
+ atype = type::append; // Append subsequent values.
}
}
@@ -6170,11 +6223,10 @@ namespace build2
}
void parser::
- enter_buildfile (const path_name& pn)
+ enter_buildfile (const path& p)
{
tracer trace ("parser::enter_buildfile", &path_);
- const path& p (pn.path != nullptr ? *pn.path : path ());
dir_path d (p.directory ()); // Empty for a path name with the NULL path.
// Figure out if we need out.
@@ -6190,8 +6242,8 @@ namespace build2
ctx.targets.insert<buildfile> (
move (d),
move (out),
- pn.name ? *pn.name : p.leaf ().base ().string (),
- p.extension (), // Always specified.
+ p.leaf ().base ().string (),
+ p.extension (), // Always specified.
trace);
}
diff --git a/libbuild2/parser.hxx b/libbuild2/parser.hxx
index 6552114..c55e14f 100644
--- a/libbuild2/parser.hxx
+++ b/libbuild2/parser.hxx
@@ -33,11 +33,17 @@ namespace build2
void
parse_buildfile (istream&,
const path_name&,
- scope& root,
- scope& base);
+ scope* root,
+ scope& base,
+ target* = nullptr,
+ prerequisite* = nullptr);
void
- parse_buildfile (lexer&, scope& root, scope& base);
+ parse_buildfile (lexer&,
+ scope* root,
+ scope& base,
+ target* = nullptr,
+ prerequisite* = nullptr);
buildspec
parse_buildspec (istream&, const path_name&);
@@ -51,7 +57,7 @@ namespace build2
names
parse_export_stub (istream& is, const path_name& name, scope& r, scope& b)
{
- parse_buildfile (is, name, r, b);
+ parse_buildfile (is, name, &r, b);
return move (export_value);
}
@@ -99,7 +105,9 @@ namespace build2
parse_clause (token&, token_type&, bool one = false);
void
- parse_variable_block (token&, token_type&, const target_type*, string);
+ parse_variable_block (token&, token_type&,
+ const target_type* = nullptr,
+ string = string ());
// Ad hoc target names inside < ... >.
//
@@ -270,14 +278,14 @@ namespace build2
attributes&
attributes_top () {return attributes_.back ();}
- // Source a stream optionnaly entering it as a buildfile and performing
- // the default target processing.
+ // Source a stream optionnaly performing the default target processing.
+ // If the specified path name has a real path, then also enter it as a
+ // buildfile.
//
void
source (istream&,
const path_name&,
const location&,
- bool enter,
bool default_target);
// The what argument is used in diagnostics (e.g., "expected <what>
@@ -494,7 +502,7 @@ namespace build2
// Enter buildfile as a target.
//
void
- enter_buildfile (const path_name&);
+ enter_buildfile (const path&);
// Lexer.
//
diff --git a/libbuild2/prerequisite-key.hxx b/libbuild2/prerequisite-key.hxx
new file mode 100644
index 0000000..12b94a8
--- /dev/null
+++ b/libbuild2/prerequisite-key.hxx
@@ -0,0 +1,44 @@
+// file : libbuild2/prerequisite-key.hxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_PREREQUISITE_KEY_HXX
+#define LIBBUILD2_PREREQUISITE_KEY_HXX
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/forward.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/target-key.hxx>
+#include <libbuild2/target-type.hxx>
+
+#include <libbuild2/export.hxx>
+
+namespace build2
+{
+ // Light-weight (by being shallow-pointing) prerequisite key, similar
+ // to (and based on) target key.
+ //
+ // Note that unlike prerequisite, the key is not (necessarily) owned by a
+ // target. So for the key we instead have the base scope of the target that
+ // (would) own it. Note that we assume keys to be ephemeral enough for the
+ // base scope to remain unchanged.
+ //
+ class prerequisite_key
+ {
+ public:
+ using scope_type = build2::scope;
+
+ const optional<project_name>& proj;
+ target_key tk; // The .dir and .out members can be relative.
+ const scope_type* scope; // Can be NULL if tk.dir is absolute.
+
+ template <typename T>
+ bool is_a () const {return tk.is_a<T> ();}
+ bool is_a (const target_type& tt) const {return tk.is_a (tt);}
+ };
+
+ LIBBUILD2_SYMEXPORT ostream&
+ operator<< (ostream&, const prerequisite_key&);
+}
+
+#endif // LIBBUILD2_PREREQUISITE_KEY_HXX
diff --git a/libbuild2/prerequisite.hxx b/libbuild2/prerequisite.hxx
index 9448fbc..476ed9d 100644
--- a/libbuild2/prerequisite.hxx
+++ b/libbuild2/prerequisite.hxx
@@ -13,36 +13,12 @@
#include <libbuild2/variable.hxx>
#include <libbuild2/target-key.hxx>
#include <libbuild2/diagnostics.hxx>
+#include <libbuild2/prerequisite-key.hxx>
#include <libbuild2/export.hxx>
namespace build2
{
- // Light-weight (by being shallow-pointing) prerequisite key, similar
- // to (and based on) target key.
- //
- // Note that unlike prerequisite, the key is not (necessarily) owned by a
- // target. So for the key we instead have the base scope of the target that
- // (would) own it. Note that we assume keys to be ephemeral enough for the
- // base scope to remain unchanged.
- //
- class prerequisite_key
- {
- public:
- using scope_type = build2::scope;
-
- const optional<project_name>& proj;
- target_key tk; // The .dir and .out members can be relative.
- const scope_type* scope; // Can be NULL if tk.dir is absolute.
-
- template <typename T>
- bool is_a () const {return tk.is_a<T> ();}
- bool is_a (const target_type& tt) const {return tk.is_a (tt);}
- };
-
- LIBBUILD2_SYMEXPORT ostream&
- operator<< (ostream&, const prerequisite_key&);
-
// Note that every data member except for the target is immutable (const).
//
class LIBBUILD2_SYMEXPORT prerequisite
diff --git a/libbuild2/scope.cxx b/libbuild2/scope.cxx
index 842b839..92fc12c 100644
--- a/libbuild2/scope.cxx
+++ b/libbuild2/scope.cxx
@@ -738,7 +738,7 @@ namespace build2
auto r (find_target_type (n, loc));
if (r.first == nullptr)
- fail (loc) << "unknown target type " << n.type;
+ fail (loc) << "unknown target type " << n.type << " in " << n;
bool src (n.pair); // If out-qualified, then it is from src.
if (src)
@@ -784,20 +784,54 @@ namespace build2
if (n == (ns[0].pair ? 2 : 1))
{
name dummy;
- target_key r (find_target_key (ns[0], n == 1 ? dummy : ns[1], loc));
-
- return target_key {
- r.type,
- r.dir,
- n == 1 ? &empty_dir_path : r.out,
- r.name,
- move (r.ext)};
+ return find_target_key (ns[0], n == 1 ? dummy : ns[1], loc);
}
}
fail (loc) << "invalid target name: " << ns << endf;
}
+ pair<const target_type&, optional<string>> scope::
+ find_prerequisite_type (name& n, name& o, const location& loc) const
+ {
+ auto r (find_target_type (n, loc));
+
+ if (r.first == nullptr)
+ fail (loc) << "unknown target type " << n.type << " in " << n;
+
+ if (n.pair) // If out-qualified, then it is from src.
+ {
+ assert (n.pair == '@');
+
+ if (!o.directory ())
+ fail (loc) << "expected directory after '@'";
+ }
+
+ if (!n.dir.empty ())
+ n.dir.normalize (false, true); // Current dir collapses to an empty one.
+
+ if (!o.dir.empty ())
+ o.dir.normalize (false, true); // Ditto.
+
+ return pair<const target_type&, optional<string>> (
+ *r.first, move (r.second));
+ }
+
+ prerequisite_key scope::
+ find_prerequisite_key (names& ns, const location& loc) const
+ {
+ if (size_t n = ns.size ())
+ {
+ if (n == (ns[0].pair ? 2 : 1))
+ {
+ name dummy;
+ return find_prerequisite_key (ns[0], n == 1 ? dummy : ns[1], loc);
+ }
+ }
+
+ fail (loc) << "invalid prerequisite name: " << ns << endf;
+ }
+
static target*
derived_tt_factory (context& c,
const target_type& t, dir_path d, dir_path o, string n)
diff --git a/libbuild2/scope.hxx b/libbuild2/scope.hxx
index e1cdf78..e7c2db7 100644
--- a/libbuild2/scope.hxx
+++ b/libbuild2/scope.hxx
@@ -19,6 +19,7 @@
#include <libbuild2/target-key.hxx>
#include <libbuild2/target-type.hxx>
#include <libbuild2/target-state.hxx>
+#include <libbuild2/prerequisite-key.hxx>
#include <libbuild2/export.hxx>
@@ -330,6 +331,20 @@ namespace build2
target_key
find_target_key (names&, const location&) const;
+ // Similar to the find_target_type() but does not complete relative
+ // directories.
+ //
+ pair<const target_type&, optional<string>>
+ find_prerequisite_type (name&, name&, const location&) const;
+
+ // As above, but return a prerequisite key.
+ //
+ prerequisite_key
+ find_prerequisite_key (name&, name&, const location&) const;
+
+ prerequisite_key
+ find_prerequisite_key (names&, const location&) const;
+
// Dynamically derive a new target type from an existing one. Return the
// reference to the target type and an indicator of whether it was
// actually created.
@@ -497,6 +512,12 @@ namespace build2
// NULL means no strong amalgamtion.
};
+ inline bool
+ operator== (const scope& x, const scope& y) { return &x == &y; }
+
+ inline bool
+ operator!= (const scope& x, const scope& y) { return !(x == y); }
+
inline ostream&
operator<< (ostream& os, const scope& s)
{
diff --git a/libbuild2/scope.ixx b/libbuild2/scope.ixx
index f13aa70..9aecd48 100644
--- a/libbuild2/scope.ixx
+++ b/libbuild2/scope.ixx
@@ -55,7 +55,28 @@ namespace build2
find_target_key (name& n, name& o, const location& loc) const
{
auto p (find_target_type (n, o, loc));
- return target_key {&p.first, &n.dir, &o.dir, &n.value, move (p.second)};
+ return target_key {
+ &p.first,
+ &n.dir,
+ o.dir.empty () ? &empty_dir_path : &o.dir,
+ &n.value,
+ move (p.second)};
+ }
+
+ inline prerequisite_key scope::
+ find_prerequisite_key (name& n, name& o, const location& loc) const
+ {
+ auto p (find_prerequisite_type (n, o, loc));
+ return prerequisite_key {
+ n.proj,
+ {
+ &p.first,
+ &n.dir,
+ o.dir.empty () ? &empty_dir_path : &o.dir,
+ &n.value,
+ move (p.second)
+ },
+ this};
}
inline dir_path
diff --git a/libbuild2/search.cxx b/libbuild2/search.cxx
index 0ff49ac..5887138 100644
--- a/libbuild2/search.cxx
+++ b/libbuild2/search.cxx
@@ -6,8 +6,8 @@
#include <libbuild2/scope.hxx>
#include <libbuild2/target.hxx>
#include <libbuild2/filesystem.hxx> // mtime()
-#include <libbuild2/prerequisite.hxx>
#include <libbuild2/diagnostics.hxx>
+#include <libbuild2/prerequisite-key.hxx>
using namespace std;
using namespace butl;
diff --git a/libbuild2/target-key.hxx b/libbuild2/target-key.hxx
index bd9b8c7..0096d46 100644
--- a/libbuild2/target-key.hxx
+++ b/libbuild2/target-key.hxx
@@ -19,7 +19,7 @@ namespace build2
{
// Light-weight (by being shallow-pointing) target key.
//
- class target_key
+ class LIBBUILD2_SYMEXPORT target_key
{
public:
const target_type* const type;
@@ -31,6 +31,11 @@ namespace build2
template <typename T>
bool is_a () const {return type->is_a<T> ();}
bool is_a (const target_type& tt) const {return type->is_a (tt);}
+
+ // Return the target name or a pair of names if out-qualified.
+ //
+ names
+ as_name () const;
};
inline bool
diff --git a/libbuild2/target.cxx b/libbuild2/target.cxx
index b49071a..d313b05 100644
--- a/libbuild2/target.cxx
+++ b/libbuild2/target.cxx
@@ -42,6 +42,27 @@ namespace build2
return false;
}
+ // target_key
+ //
+ names target_key::
+ as_name () const
+ {
+ names r;
+
+ string v (*name);
+ target::combine_name (v, ext, false /* @@ TODO: what to do? */);
+
+ r.push_back (build2::name (*dir, type->name, move (v)));
+
+ if (!out->empty ())
+ {
+ r.front ().pair = '@';
+ r.push_back (build2::name (*out));
+ }
+
+ return r;
+ }
+
// target_state
//
static const char* const target_state_[] =
diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx
index ea72925..6edfe5c 100644
--- a/libbuild2/target.hxx
+++ b/libbuild2/target.hxx
@@ -138,7 +138,7 @@ namespace build2
class LIBBUILD2_SYMEXPORT target
{
public:
- // Context this scope belongs to.
+ // Context this target belongs to.
//
context& ctx;
@@ -305,6 +305,12 @@ namespace build2
target_key
key () const;
+ names
+ as_name () const
+ {
+ return key ().as_name ();
+ }
+
// Scoping.
//
public:
@@ -764,6 +770,15 @@ namespace build2
virtual const target_type& dynamic_type () const = 0;
static const target_type static_type;
+ // RW access.
+ //
+ target&
+ rw () const
+ {
+ assert (ctx.phase == run_phase::load);
+ return const_cast<target&> (*this);
+ }
+
public:
// Split the name leaf into target name (in place) and extension
// (returned).
@@ -1529,8 +1544,15 @@ namespace build2
//
// An empty path may signify special unknown/undetermined/unreal location
// (for example, a binless library or an installed import library -- we
- // know the DLL is there, just not exactly where). In this case you would
- // also normally set its mtime.
+ // know the DLL is there, just not exactly where). In this case you could
+ // also set its mtime to timestamp_unreal (but don't have to, if a real
+ // timestamp can be derived, for example, the from the import library in
+ // the DLL case above).
+ //
+ // Note, however, that a target with timestamp_unreal does not have to
+ // have an empty path. One consequence of this arrangement (assigned path
+ // with unreal timestamp) is that the timestamp of such a target when used
+ // as a prerequisite won't affect the dependent's target out-of-date-ness.
//
// We used to return a pointer to properly distinguish between not set and
// empty but that proved too tedious to work with. So now we return empty
@@ -1705,9 +1727,44 @@ namespace build2
public:
using file::file;
+ using process_path_type = build2::process_path;
+
+ // Return the process path of this executable target. Normally it will be
+ // the absolute path returned by path() but can also be something custom
+ // if, for example, the target was found via a PATH search (see import for
+ // details). The idea is to use this path if we need to execute the target
+ // in which case, for the above example, we will see a short (recall) path
+ // instead of the absolute one in diagnostics.
+ //
+ process_path_type
+ process_path () const
+ {
+ // It's unfortunate we have to return by value but hopefully the
+ // compiler will see through it. Note also that returning empty
+ // process path if path is empty.
+ //
+ return process_path_.empty ()
+ ? process_path_type (path ().string ().c_str (),
+ path_type (),
+ path_type ())
+ : process_path_type (process_path_, false /* init */);
+ }
+
+ // Note that setting the custom process path is not MT-safe and must be
+ // done while holding the insertion lock.
+ //
+ void
+ process_path (process_path_type p)
+ {
+ process_path_ = move (p);
+ }
+
public:
static const target_type static_type;
virtual const target_type& dynamic_type () const {return static_type;}
+
+ private:
+ process_path_type process_path_;
};
class LIBBUILD2_SYMEXPORT buildfile: public file
diff --git a/libbuild2/target.txx b/libbuild2/target.txx
index 0e4d9bf..b482d64 100644
--- a/libbuild2/target.txx
+++ b/libbuild2/target.txx
@@ -5,7 +5,6 @@
#include <libbuild2/scope.hxx>
#include <libbuild2/diagnostics.hxx>
-#include <libbuild2/prerequisite.hxx>
namespace build2
{
diff --git a/libbuild2/utility.hxx b/libbuild2/utility.hxx
index 3a5e708..9800d6c 100644
--- a/libbuild2/utility.hxx
+++ b/libbuild2/utility.hxx
@@ -579,7 +579,7 @@ namespace build2
void
append_options (sha256&, T&, const char*);
- // As above but from the strings value directly.
+ // As above but from the lookup directly.
//
LIBBUILD2_SYMEXPORT void
append_options (cstrings&, const lookup&, const char* excl = nullptr);
@@ -590,6 +590,8 @@ namespace build2
LIBBUILD2_SYMEXPORT void
append_options (sha256&, const lookup&);
+ // As above but from the strings value directly.
+ //
void
append_options (cstrings&, const strings&, const char* excl = nullptr);