aboutsummaryrefslogtreecommitdiff
path: root/libbuild2
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2023-06-08 13:31:08 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2023-06-08 13:48:37 +0200
commit1c30f9e9c4fca846f05e881638920a9beb082fd1 (patch)
tree21c14ea34baca0a25e7fb245e92d2cd19b4ac7e7 /libbuild2
parent957e150b49fce148c51a13bca0aa9f754ac2c4cb (diff)
Add support for buildfile importation
Diffstat (limited to 'libbuild2')
-rw-r--r--libbuild2/algorithm.cxx4
-rw-r--r--libbuild2/buildfile35
-rw-r--r--libbuild2/file.cxx242
-rw-r--r--libbuild2/file.hxx52
-rw-r--r--libbuild2/file.ixx20
-rw-r--r--libbuild2/install/init.cxx4
-rw-r--r--libbuild2/operation.cxx3
-rw-r--r--libbuild2/parser.cxx482
-rw-r--r--libbuild2/parser.hxx18
-rw-r--r--libbuild2/utility-installed.cxx4
-rw-r--r--libbuild2/utility-uninstalled.cxx6
-rw-r--r--libbuild2/utility.hxx6
12 files changed, 701 insertions, 175 deletions
diff --git a/libbuild2/algorithm.cxx b/libbuild2/algorithm.cxx
index a2e1b57..3c0da3b 100644
--- a/libbuild2/algorithm.cxx
+++ b/libbuild2/algorithm.cxx
@@ -57,10 +57,10 @@ namespace build2
assert (t.ctx.phase == run_phase::match);
// If this is a project-qualified prerequisite, then this is import's
- // business.
+ // business (phase 2).
//
if (pk.proj)
- return import (t.ctx, pk);
+ return import2 (t.ctx, pk);
if (const target* pt = pk.tk.type->search (t, pk))
return *pt;
diff --git a/libbuild2/buildfile b/libbuild2/buildfile
index b3a9c52..6d7c597 100644
--- a/libbuild2/buildfile
+++ b/libbuild2/buildfile
@@ -191,14 +191,37 @@ if! $cross
{
{obja objs}{context}: cxx.poptions += \
-DBUILD2_IMPORT_PATH=\"$regex.replace($out_root, '\\', '\\\\')\"
+}
+
+# Note that while the -installed object file should only be linked when we
+# are installing, it will be compiled even in the uninstalled case.
+#
+if ($install.root != [null])
+{
+ # Only if installed.
+ #
+ {obja objs}{utility-installed}: cxx.poptions += \
+ -DBUILD2_INSTALL_LIB=\"$regex.replace(\
+ $install.resolve($install.lib), '\\', '\\\\')\"
- # While this object file should only be linked when we are installing, it
- # will be compiled even in the uninstalled case.
+ # Only if configured.
+ #
+ # Note: strip the last directory component (<project>).
#
- if ($install.root != [null])
- {obja objs}{utility-installed}: cxx.poptions += \
- -DBUILD2_INSTALL_LIB=\"$regex.replace(\
- $install.resolve($install.lib), '\\', '\\\\')\"
+ # @@ TMP drop after 0.16.0 release.
+ #
+ install_buildfile = ($install.buildfile != [null] \
+ ? $directory($install.resolve($install.buildfile)) \
+ :)
+ {obja objs}{utility-installed utility-uninstalled}: cxx.poptions += \
+ -DBUILD2_INSTALL_BUILDFILE=\"$regex.replace($install_buildfile, '\\', '\\\\')\"
+
+ #\
+ {obja objs}{utility-installed utility-uninstalled}: cxx.poptions += \
+ -DBUILD2_INSTALL_BUILDFILE=\"$regex.replace(\
+ $directory($install.resolve($install.buildfile)), '\\', '\\\\')\"
+ #\
+
}
if ($cxx.target.class != 'windows')
diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx
index 7a48b2e..64a92a4 100644
--- a/libbuild2/file.cxx
+++ b/libbuild2/file.cxx
@@ -29,6 +29,8 @@ namespace build2
{
// Standard and alternative build file/directory naming schemes.
//
+ extern const dir_path std_export_dir;
+ extern const dir_path alt_export_dir;
// build:
@@ -36,6 +38,7 @@ namespace build2
const dir_path std_root_dir (dir_path (std_build_dir) /= "root");
const dir_path std_bootstrap_dir (dir_path (std_build_dir) /= "bootstrap");
const dir_path std_build_build_dir (dir_path (std_build_dir) /= "build");
+ const dir_path std_export_dir (dir_path (std_build_dir) /= "export");
const path std_root_file (std_build_dir / "root.build");
const path std_bootstrap_file (std_build_dir / "bootstrap.build");
@@ -53,6 +56,7 @@ namespace build2
const dir_path alt_root_dir (dir_path (alt_build_dir) /= "root");
const dir_path alt_bootstrap_dir (dir_path (alt_build_dir) /= "bootstrap");
const dir_path alt_build_build_dir (dir_path (alt_build_dir) /= "build");
+ const dir_path alt_export_dir (dir_path (alt_build_dir) /= "export");
const path alt_root_file (alt_build_dir / "root.build2");
const path alt_bootstrap_file (alt_build_dir / "bootstrap.build2");
@@ -2043,7 +2047,7 @@ namespace build2
static void
import_suggest (const diag_record& dr,
const project_name& pn,
- const target_type& tt,
+ const target_type* tt,
const string& tn,
bool rule_hint,
const char* qual = nullptr)
@@ -2058,11 +2062,11 @@ namespace build2
// Suggest ad hoc import but only if it's a path-based target (doing it
// for lib{} is very confusing).
//
- if (tt.is_a<path_target> ())
+ if (tt != nullptr && tt->is_a<path_target> ())
{
- string v (tt.is_a<exe> () && (pv == tn || pn == tn)
+ string v (tt->is_a<exe> () && (pv == tn || pn == tn)
? "config." + pv
- : "config.import." + pv + '.' + tn + '.' + tt.name);
+ : "config.import." + pv + '.' + tn + '.' + tt->name);
dr << info << "or use " << v << " configuration variable to specify "
<< "its " << (qual != nullptr ? qual : "") << "path";
@@ -2119,6 +2123,9 @@ namespace build2
//
// 4. Normal import.
//
+ // @@ PERF: in quite a few places (local, subproject) we could have
+ // returned the scope and save on bootstrap in import_load().
+ //
if (tgt.unqualified ())
{
if (tgt.directory () && tgt.relative ())
@@ -2126,6 +2133,8 @@ namespace build2
if (tgt.absolute ())
{
+ // Ad hoc import.
+ //
// Actualize the directory to be analogous to the config.import.<proj>
// case (which is of abs_dir_path type).
//
@@ -2142,7 +2151,7 @@ namespace build2
fail (loc) << "project-local importation of target " << tgt
<< " from an unnamed project";
- tgt.proj = pn;
+ tgt.proj = pn; // Reduce to normal import.
return make_pair (move (tgt), optional<dir_path> (iroot.out_path ()));
}
@@ -2347,7 +2356,7 @@ namespace build2
[&proj, tt, &on] (const diag_record& dr)
{
import_suggest (
- dr, proj, *tt, on, false, "alternative ");
+ dr, proj, tt, on, false, "alternative ");
});
md = extract_metadata (e->process_path (),
@@ -2670,6 +2679,65 @@ namespace build2
fail (loc) << out_root << " is not out_root for " << *proj;
}
+ // Buildfile importation is quite different so handle it separately.
+ //
+ // Note that we don't need to load the project in this case.
+ //
+ // @@ For now we don't out-qualify the resulting target to be able to
+ // re-import it ad hoc (there is currently no support for out-qualified
+ // ad hoc import). Feels like this should be harmless since it's just a
+ // glorified path to a static file that nobody is actually going to use
+ // as a target (e.g., to depend upon).
+ //
+ if (tgt.type == "buildfile")
+ {
+ auto add_ext = [&altn] (string& n)
+ {
+ if (path_traits::find_extension (n) == string::npos)
+ {
+ if (n != (*altn ? alt_buildfile_file : std_buildfile_file).string ())
+ {
+ n += ".";
+ n += *altn ? alt_build_ext : std_build_ext;
+ }
+ }
+ };
+
+ if (proj)
+ {
+ name n;
+
+ n.dir = move (src_root);
+ n.dir /= *altn ? alt_export_dir : std_export_dir;
+ if (!tgt.dir.empty ())
+ {
+ n.dir /= tgt.dir;
+ n.dir.normalize ();
+ }
+
+ n.type = tgt.type;
+ n.value = tgt.value;
+ add_ext (n.value);
+
+ pair<names, const scope&> r (names {move (n)}, *root);
+
+ // Cache.
+ //
+ if (cache_out_root.empty ())
+ cache_out_root = move (out_root);
+
+ ctx.import_cache.emplace (
+ import_key {move (cache_out_root), move (tgt), metav}, r);
+
+ return r;
+ }
+ else
+ {
+ add_ext (tgt.value);
+ return pair<names, const scope&> (names {move (tgt)}, *root);
+ }
+ }
+
// Load the imported root scope.
//
if (!root->root_extra->loaded)
@@ -2806,6 +2874,9 @@ namespace build2
}
}
+ static names
+ import2_buildfile (context&, names&&, bool, const location&);
+
pair<names, import_kind>
import (scope& base,
name tgt,
@@ -2867,18 +2938,23 @@ namespace build2
//
if (ns.back ().qualified ())
{
- if (ph2)
+ if (ns.back ().type == "buildfile")
+ {
+ assert (ph2);
+ ns = import2_buildfile (ctx, move (ns), opt && !r.second, loc);
+ }
+ else 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),
- *ph2,
- opt && !r.second /* optional */,
- nullopt /* metadata */,
- false /* existing */,
- loc))
+ if (const target* t = import2 (ctx,
+ base.find_prerequisite_key (ns, loc),
+ *ph2,
+ opt && !r.second /* optional */,
+ nullopt /* metadata */,
+ false /* existing */,
+ loc))
{
// Note that here r.first was still project-qualified and we
// have no choice but to call as_name(). This shouldn't cause
@@ -2909,15 +2985,15 @@ namespace build2
}
const target*
- import (context& ctx,
- const prerequisite_key& pk,
- const string& hint,
- bool opt,
- const optional<string>& meta,
- bool exist,
- const location& loc)
+ import2 (context& ctx,
+ const prerequisite_key& pk,
+ const string& hint,
+ bool opt,
+ const optional<string>& meta,
+ bool exist,
+ const location& loc)
{
- tracer trace ("import");
+ tracer trace ("import2");
// Neither hint nor metadata can be requested for existing.
//
@@ -3037,7 +3113,7 @@ namespace build2
auto df = make_diag_frame (
[&proj, &tt, &tk] (const diag_record& dr)
{
- import_suggest (dr, proj, tt, *tk.name, false, "alternative ");
+ import_suggest (dr, proj, &tt, *tk.name, false, "alternative ");
});
if (!(md = extract_metadata (pp, *meta, opt, loc)))
@@ -3082,7 +3158,84 @@ namespace build2
else
// Use metadata as proxy for immediate import.
//
- import_suggest (dr, proj, tt, *tk.name, meta && hint.empty ());
+ import_suggest (dr, proj, &tt, *tk.name, meta && hint.empty ());
+
+ dr << endf;
+ }
+
+ static names
+ import2_buildfile (context&, names&& ns, bool opt, const location& loc)
+ {
+ tracer trace ("import2_buildfile");
+
+ assert (ns.size () == 1);
+ name n (move (ns.front ()));
+
+ // Our approach doesn't work for targets without a project so let's fail
+ // hard, even if optional.
+ //
+ if (!n.proj || n.proj->empty ())
+ fail (loc) << "unable to import target " << n << " without project name";
+
+ while (!build_install_buildfile.empty ()) // Breakout loop.
+ {
+ path f (build_install_buildfile /
+ dir_path (n.proj->string ()) /
+ n.dir /
+ n.value);
+
+ // See if we need to try with extensions.
+ //
+ bool ext (path_traits::find_extension (n.value) == string::npos &&
+ n.value != std_buildfile_file.string () &&
+ n.value != alt_buildfile_file.string ());
+
+ if (ext)
+ {
+ f += '.';
+ f += std_build_ext;
+ }
+
+ if (!exists (f))
+ {
+ l6 ([&]{trace << "tried " << f;});
+
+ if (ext)
+ {
+ f.make_base ();
+ f += '.';
+ f += alt_build_ext;
+
+ if (!exists (f))
+ {
+ l6 ([&]{trace << "tried " << f;});
+ break;
+ }
+ }
+ else
+ break;
+ }
+
+ // Split the path into the target.
+ //
+ ns = {name (f.directory (), move (n.type), f.leaf ().string ())};
+ return ns;
+ }
+
+ if (opt)
+ return names {};
+
+ diag_record dr;
+ dr << fail (loc) << "unable to import target " << n;
+
+ import_suggest (dr, *n.proj, nullptr /* tt */, n.value, false);
+
+ if (build_install_buildfile.empty ())
+ dr << info << "no exported buildfile installation location is "
+ << "configured in build2";
+ else
+ dr << info << "exported buildfile installation location is "
+ << build_install_buildfile;
dr << endf;
}
@@ -3104,7 +3257,7 @@ namespace build2
l5 ([&]{trace << tgt << " from " << base << " for " << what;});
- assert ((!opt || ph2) && (!metadata || ph2));
+ assert ((!opt || ph2) && (!metadata || ph2) && tgt.type != "buildfile");
context& ctx (base.ctx);
assert (ctx.phase == run_phase::load);
@@ -3147,13 +3300,13 @@ namespace build2
// This is tricky: we only want the optional semantics for the
// fallback case.
//
- pt = import (ctx,
- base.find_prerequisite_key (ns, loc),
- *ph2,
- opt && !r.second,
- meta,
- false /* existing */,
- loc);
+ pt = import2 (ctx,
+ base.find_prerequisite_key (ns, loc),
+ *ph2,
+ opt && !r.second,
+ meta,
+ false /* existing */,
+ loc);
}
if (pt == nullptr)
@@ -3274,6 +3427,31 @@ namespace build2
return import_result<target> {pt, move (rns), k};
}
+ path
+ import_buildfile (scope& bs, name n, bool opt, const location& loc)
+ {
+ names r (import (bs,
+ move (n),
+ string () /* phase2 */,
+ opt,
+ false /* metadata */,
+ loc).first);
+
+ path p;
+ if (!r.empty ()) // Optional not found.
+ {
+ // Note: see also parse_import().
+ //
+ assert (r.size () == 1); // See import_load() for details.
+ name& n (r.front ());
+ p = n.dir / n.value; // Should already include extension.
+ }
+ else
+ assert (opt);
+
+ return p;
+ }
+
ostream&
operator<< (ostream& o, const import_result<exe>& r)
{
diff --git a/libbuild2/file.hxx b/libbuild2/file.hxx
index 5847498..6c5097d 100644
--- a/libbuild2/file.hxx
+++ b/libbuild2/file.hxx
@@ -19,6 +19,28 @@ namespace build2
class lexer;
class parser;
+ // The following filesystem entries in the build/ subdirectory are reserved
+ // by the build2 core:
+ //
+ // build/ -- build2 core-internal build state (e.g., recipes)
+ // bootstrap/ -- bootstrap state and hooks
+ // bootstrap.build -- bootstrap buildfile
+ // root/ -- root load hooks
+ // root.build -- root buildfile
+ // export.build -- export stub
+ // export/ -- exported buildfiles
+ //
+ // The build/, bootstrap/, root/, and config.build entries are in .gitignore
+ // as generated by bdep-new.
+ //
+ // The rest of the filesystem entries are shared between the project and the
+ // modules that it loads. In particular, if a project loads module named
+ // <mod>, then the <mod>.build, <mod>/, *.<mod> entries (spelled in any
+ // case) are reserved to this module and should not be used by the project
+ // unless explicitly allowed by the module. By convention, <mod>/build/ is
+ // for module-internal build state (e.g., C++ modules side-build) and is
+ // .gitignore'ed.
+ //
LIBBUILD2_SYMEXPORT extern const dir_path std_build_dir; // build/
// build/root.build
@@ -339,7 +361,9 @@ namespace build2
// 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.
+ // since we don't restrict what users can import/export. If name has
+ // buildfile type, then the result is an absolute buildfile target to be
+ // included (once) at the point of importation.
//
// Finally, note that import is (and should be kept) idempotent or, more
// precisely, "accumulatively idempotent" in that additional steps may be
@@ -358,7 +382,7 @@ namespace build2
// Import phase 2.
//
const target&
- import (context&, const prerequisite_key&);
+ import2 (context&, const prerequisite_key&);
// 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,
@@ -383,6 +407,9 @@ namespace build2
// target::as_name() for details) as well as the kind of import that was
// performed.
//
+ // Note: cannot be used to import buildfile targets (use import_buildfile()
+ // instead).
+ //
template <typename T>
struct import_result
{
@@ -391,6 +418,13 @@ namespace build2
import_kind kind;
};
+ // 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 import_result<exe>&);
+
import_result<target>
import_direct (scope& base,
name,
@@ -433,12 +467,16 @@ namespace build2
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.
+ // The import_direct() equivalent for importing buildfile targets. Return
+ // empty name if optional and not found. Note that the returned file path is
+ // not necessarily checked for existence so sourcing it may still fail.
//
- LIBBUILD2_SYMEXPORT ostream&
- operator<< (ostream&, const import_result<exe>&);
+ // Note also that this function can be used for an ad hoc import by passing
+ // an absolute target name as would be returned by the normal import (can be
+ // useful for importing own buildfiles).
+ //
+ LIBBUILD2_SYMEXPORT path
+ import_buildfile (scope& base, name, bool optional, const location&);
// As import phase 2 but only imports as an already existing target. But
// unlike it, this function can be called during the load and execute
diff --git a/libbuild2/file.ixx b/libbuild2/file.ixx
index 43c46c9..dc39bcb 100644
--- a/libbuild2/file.ixx
+++ b/libbuild2/file.ixx
@@ -22,16 +22,16 @@ namespace build2
}
LIBBUILD2_SYMEXPORT const target*
- import (context&,
- const prerequisite_key&,
- const string& hint,
- bool optional_,
- const optional<string>& metadata, // False or metadata key.
- bool existing,
- const location&);
+ import2 (context&,
+ const prerequisite_key&,
+ const string& hint,
+ bool optional_,
+ const optional<string>& metadata, // False or metadata key.
+ bool existing,
+ const location&);
inline const target&
- import (context& ctx, const prerequisite_key& pk)
+ import2 (context& ctx, const prerequisite_key& pk)
{
assert (ctx.phase == run_phase::match);
@@ -40,7 +40,7 @@ namespace build2
// 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, string (), false, nullopt, false, location ());
+ return *import2 (ctx, pk, string (), false, nullopt, false, location ());
}
inline import_result<target>
@@ -98,6 +98,6 @@ namespace build2
inline const target*
import_existing (context& ctx, const prerequisite_key& pk)
{
- return import (ctx, pk, string (), false, nullopt, true, location ());
+ return import2 (ctx, pk, string (), false, nullopt, true, location ());
}
}
diff --git a/libbuild2/install/init.cxx b/libbuild2/install/init.cxx
index dfd78df..0b33475 100644
--- a/libbuild2/install/init.cxx
+++ b/libbuild2/install/init.cxx
@@ -260,7 +260,7 @@ namespace build2
// way we distinguish between the two is via the presence/absence of
// the trailing directory separator.
//
- // Plus it can have the special true/false values when acting as a
+ // Plus it can have the special true/false values when acting as an
// operation variable.
//
auto& ovar (rs.var_pool ().insert<path> ("install",
@@ -299,6 +299,7 @@ namespace build2
DIR (include_arch, dir_path ("include"));
DIR (share, dir_path ("data_root") /= "share");
DIR (data, (dir_path ("share") /= "<private>") /= "<project>");
+ DIR (buildfile, ((dir_path ("share") /= "build2") /= "export") /= "<project>");
DIR (doc, ((dir_path ("share") /= "doc") /= "<private>") /= "<project>");
DIR (legal, dir_path ("doc"));
@@ -701,6 +702,7 @@ namespace build2
set_dir (s, p, rs, "include_arch", dir_include_arch);
set_dir (s, p, rs, "share", dir_share);
set_dir (s, p, rs, "data", dir_data);
+ set_dir (s, p, rs, "buildfile", dir_buildfile);
set_dir (s, p, rs, "doc", dir_doc);
set_dir (s, p, rs, "legal", dir_legal);
diff --git a/libbuild2/operation.cxx b/libbuild2/operation.cxx
index e9b9211..bafc263 100644
--- a/libbuild2/operation.cxx
+++ b/libbuild2/operation.cxx
@@ -85,7 +85,8 @@ namespace build2
{
// Load project's root.build.
//
- load_root (root);
+ if (!root.root_extra->loaded)
+ load_root (root);
// Create the base scope. Note that its existence doesn't mean it was
// already setup as a base scope; it can be the same as root.
diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx
index b79b65c..6214648 100644
--- a/libbuild2/parser.cxx
+++ b/libbuild2/parser.cxx
@@ -302,9 +302,9 @@ namespace build2
? auto_project_env (*root_)
: auto_project_env ());
- if (enter && path_->path != nullptr)
- enter_buildfile (*path_->path);
-
+ const buildfile* bf (enter && path_->path != nullptr
+ ? &enter_buildfile (*path_->path)
+ : nullptr);
token t;
type tt;
next (t, tt);
@@ -316,7 +316,7 @@ namespace build2
else
{
parse_clause (t, tt);
- process_default_target (t);
+ process_default_target (t, bf);
}
if (tt != type::eos)
@@ -1601,7 +1601,8 @@ namespace build2
if (!start_names (tt))
fail (t) << "unexpected " << t;
- // @@ PAT: currently we pattern-expand target-specific var names.
+ // @@ PAT: currently we pattern-expand target-specific var names (see
+ // also parse_import()).
//
const location ploc (get_location (t));
names pns (parse_names (t, tt, pattern_mode::expand));
@@ -3140,14 +3141,18 @@ namespace build2
}
void parser::
- source (istream& is, const path_name& in, const location& loc, bool deft)
+ source_buildfile (istream& is,
+ const path_name& in,
+ const location& loc,
+ bool deft)
{
- tracer trace ("parser::source", &path_);
+ tracer trace ("parser::source_buildfile", &path_);
l5 ([&]{trace (loc) << "entering " << in;});
- if (in.path != nullptr)
- enter_buildfile (*in.path);
+ const buildfile* bf (in.path != nullptr
+ ? &enter_buildfile (*in.path)
+ : nullptr);
const path_name* op (path_);
path_ = &in;
@@ -3173,7 +3178,7 @@ namespace build2
if (deft)
{
- process_default_target (t);
+ process_default_target (t, bf);
default_target_ = odt;
}
@@ -3217,10 +3222,10 @@ namespace build2
try
{
ifdstream ifs (p);
- source (ifs,
- path_name (p),
- get_location (t),
- false /* default_target */);
+ source_buildfile (ifs,
+ path_name (p),
+ get_location (t),
+ false /* default_target */);
}
catch (const io_error& e)
{
@@ -3350,6 +3355,8 @@ namespace build2
continue;
}
+ // Note: see a variant of this in parse_import().
+ //
// Clear/restore if/switch location.
//
// We do it here but not in parse_source since the included buildfile is
@@ -3365,10 +3372,10 @@ namespace build2
try
{
ifdstream ifs (p);
- source (ifs,
- path_name (p),
- get_location (t),
- true /* default_target */);
+ source_buildfile (ifs,
+ path_name (p),
+ get_location (t),
+ true /* default_target */);
}
catch (const io_error& e)
{
@@ -3451,10 +3458,10 @@ namespace build2
dr << info (l) << "while parsing " << args[0] << " output";
});
- source (is,
- path_name ("<stdout>"),
- l,
- false /* default_target */);
+ source_buildfile (is,
+ path_name ("<stdout>"),
+ l,
+ false /* default_target */);
}
is.close (); // Detect errors.
@@ -3867,10 +3874,14 @@ namespace build2
if (stage_ == stage::boot)
fail (t) << "import during bootstrap";
- // General import format:
+ // General import form:
//
// import[?!] [<attrs>] <var> = [<attrs>] (<target>|<project>%<target>])+
//
+ // Special form for importing buildfiles:
+ //
+ // import[?!] [<attrs>] (<target>|<project>%<target>])+
+ //
bool opt (t.value.back () == '?');
optional<string> ph2 (opt || t.value.back () == '!'
? optional<string> (string ())
@@ -3880,13 +3891,15 @@ namespace build2
//
next_with_attributes (t, tt);
- // Get variable attributes, if any, and deal with the special metadata and
- // rule_hint attributes. Since currently they can only appear in the
- // import directive, we handle them in an ad hoc manner.
+ // Get variable (or value, in the second form) attributes, if any, and
+ // deal with the special metadata and rule_hint attributes. Since
+ // currently they can only appear in the import directive, we handle them
+ // in an ad hoc manner.
//
attributes_push (t, tt);
- bool meta (false);
+ bool meta (false); // Import with metadata.
+ bool once (false); // Import buildfile once.
{
attributes& as (attributes_top ());
const location& l (as.loc);
@@ -3904,6 +3917,10 @@ namespace build2
meta = true;
}
+ else if (n == "once")
+ {
+ once = true;
+ }
else if (n == "rule_hint")
{
if (!ph2)
@@ -3934,80 +3951,197 @@ namespace build2
}
}
- if (tt != type::word)
- fail (t) << "expected variable name instead of " << t;
-
- const variable& var (
- parse_variable_name (move (t.value), get_location (t)));
- apply_variable_attributes (var);
+ // Note that before supporting the second form (without <var>) we used to
+ // parse the value after assignment in the value mode. However, we don't
+ // really need to since what we should have is a bunch of target names.
+ // In other words, whatever the value mode does not treat as special
+ // compared to the normal mode (like `:`) would be illegal here.
+ //
+ // Note that we expant patterns for the ad hoc import case:
+ //
+ // import sub = */
+ //
+ // @@ PAT: the only issue here is that we currently pattern-expand var
+ // name (same assue as with target-specific var names).
+ //
+ if (!start_names (tt))
+ fail (t) << "expected variable name or buildfile target instead of " << t;
- if (var.visibility > variable_visibility::scope)
- {
- fail (t) << "variable " << var << " has " << var.visibility
- << " visibility but is assigned in import";
- }
+ location loc (get_location (t));
+ names ns (parse_names (t, tt, pattern_mode::expand));
- // Next should come the assignment operator. Note that we don't support
+ // Next could come the assignment operator. Note that we don't support
// default assignment (?=) yet (could make sense when attempting to import
// alternatives or some such).
//
- next (t, tt);
+ type atype;
+ const variable* var (nullptr);
+ if (tt == type::assign || tt == type::append || tt == type::prepend)
+ {
+ var = &parse_variable_name (move (ns), loc);
+ apply_variable_attributes (*var);
- if (tt != type::assign && tt != type::append && tt != type::prepend)
- fail (t) << "expected variable assignment instead of " << t;
+ if (var->visibility > variable_visibility::scope)
+ {
+ fail (loc) << "variable " << *var << " has " << var->visibility
+ << " visibility but is assigned in import";
+ }
- type atype (tt);
- value& val (atype == type::assign
- ? scope_->assign (var)
- : scope_->append (var));
+ atype = tt;
+ next_with_attributes (t, tt);
+ attributes_push (t, tt, true /* standalone */);
- // The rest should be a list of targets. Parse them similar to a value on
- // the RHS of an assignment (attributes, etc).
- //
- // Note that we expant patterns for the ad hoc import case:
- //
- // import sub = */
+ if (!start_names (tt))
+ fail (t) << "expected target to import instead of " << t;
+
+ loc = get_location (t);
+ ns = parse_names (t, tt, pattern_mode::expand);
+ }
+ else if (tt == type::default_assign)
+ fail (t) << "default assignment not yet supported";
+
+
+ // If there are any value attributes, roundtrip the names through the
+ // value applying the attributes.
//
- mode (lexer_mode::value, '@');
- next_with_attributes (t, tt);
+ if (!attributes_top ().empty ())
+ {
+ value lhs, rhs (move (ns));
+ apply_value_attributes (nullptr, lhs, move (rhs), type::assign);
- if (tt == type::newline || tt == type::eos)
- fail (t) << "expected target to import instead of " << t;
+ if (!lhs)
+ fail (loc) << "expected target to import instead of null value";
- const location loc (get_location (t));
+ untypify (lhs, true /* reduce */);
+ ns = move (lhs.as<names> ());
+ }
+ else
+ attributes_pop ();
- if (value v = parse_value_with_attributes (t, tt, pattern_mode::expand))
+ value* val (var != nullptr ?
+ &(atype == type::assign
+ ? scope_->assign (*var)
+ : scope_->append (*var))
+ : nullptr);
+
+ for (name& n: ns)
{
- names storage;
- for (name& n: reverse (v, storage, true /* reduce */))
+ // @@ Could this be an out-qualified ad hoc import? Yes, see comment
+ // about buildfile import in import_load().
+ //
+ if (n.pair)
+ fail (loc) << "unexpected pair in import";
+
+ // See if we are importing a buildfile target. Such an import is always
+ // immediate.
+ //
+ bool bf (n.type == "buildfile");
+ if (bf)
{
- // @@ Could this be an out-qualified ad hoc import?
- //
- if (n.pair)
- fail (loc) << "unexpected pair in import";
+ if (meta)
+ fail (loc) << "metadata requested for buildfile target " << n;
- // import() will check the name, if required.
- //
- names r (import (*scope_, move (n), ph2, opt, meta, loc).first);
+ if (once && var != nullptr)
+ fail (loc) << "once importation requested with variable assignment";
+ if (ph2 && !ph2->empty ())
+ fail (loc) << "rule hint specified for buildfile target " << n;
+ }
+ else
+ {
+ if (once)
+ fail (loc) << "once importation requested for target " << n;
+
+ if (var == nullptr)
+ fail (loc) << "variable assignment required to import target " << n;
+ }
+
+ // import() will check the name, if required.
+ //
+ names r (import (*scope_,
+ move (n),
+ ph2 ? ph2 : bf ? optional<string> (string ()) : nullopt,
+ opt,
+ meta,
+ loc).first);
+
+ if (val != nullptr)
+ {
if (r.empty ()) // Optional not found.
{
if (atype == type::assign)
- val = nullptr;
+ *val = nullptr;
}
else
{
- 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) 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.
}
+ else
+ {
+ assert (bf);
+
+ if (r.empty ()) // Optional not found.
+ {
+ assert (opt);
+ continue;
+ }
+
+ // Note: see also import_buildfile().
+ //
+ assert (r.size () == 1); // See import_load() for details.
+ name& n (r.front ());
+ path p (n.dir / n.value); // Should already include extension.
+
+ // Note: similar to parse_include().
+ //
+ // Nuance: we insert this buildfile even with once=false in case it
+ // gets imported with once=true from another place.
+ //
+ if (!root_->root_extra->insert_buildfile (p) && once)
+ {
+ l5 ([&]{trace (loc) << "skipping already imported " << p;});
+ continue;
+ }
+
+ // Clear/restore if/switch location.
+ //
+ auto g = make_guard ([this, old = condition_] () mutable
+ {
+ condition_ = old;
+ });
+ condition_ = nullopt;
+
+ try
+ {
+ ifdstream ifs (p);
+
+ auto df = make_diag_frame (
+ [this, &loc] (const diag_record& dr)
+ {
+ dr << info (loc) << "imported from here";
+ });
+
+ // @@ Do we want to enter this buildfile? What's the harm (one
+ // benefit is that it will be in dump). But, we currently don't
+ // out-qualify them, though feels like there is nothing fatal
+ // in that, just inaccurate.
+ //
+ source_buildfile (ifs,
+ path_name (p),
+ loc,
+ false /* default_target */);
+ }
+ catch (const io_error& e)
+ {
+ fail (loc) << "unable to read imported buildfile " << p << ": " << e;
+ }
+ }
}
next_after_newline (t, tt);
@@ -8910,23 +9044,27 @@ namespace build2
return r;
}
+ // file.cxx
+ //
+ extern const dir_path std_export_dir;
+ extern const dir_path alt_export_dir;
+
void parser::
- process_default_target (token& t)
+ process_default_target (token& t, const buildfile* bf)
{
tracer trace ("parser::process_default_target", &path_);
// The logic is as follows: if we have an explicit current directory
- // target, then that's the default target. Otherwise, we take the
- // first target and use it as a prerequisite to create an implicit
- // current directory target, effectively making it the default
- // target via an alias. If there are no targets in this buildfile,
- // then we don't do anything.
+ // target, then that's the default target. Otherwise, we take the first
+ // target and use it as a prerequisite to create an implicit current
+ // directory target, effectively making it the default target via an
+ // alias. If this is a project root buildfile, then also add exported
+ // buildfiles. And if there are no targets in this buildfile, then we
+ // don't do anything (reasonably assuming it's not root).
//
if (default_target_ == nullptr) // No targets in this buildfile.
return;
- target& dt (*default_target_);
-
target* ct (
const_cast<target*> ( // Ok (serial execution).
ctx->targets.find (dir::static_type, // Explicit current dir target.
@@ -8936,35 +9074,169 @@ namespace build2
nullopt,
trace)));
- if (ct == nullptr)
+ if (ct != nullptr && ct->decl == target_decl::real)
+ ; // Existing and not implied.
+ else
{
- l5 ([&]{trace (t) << "creating current directory alias for " << dt;});
+ target& dt (*default_target_);
- // While this target is not explicitly mentioned in the buildfile, we
- // say that we behave as if it were. Thus not implied.
- //
- ct = &ctx->targets.insert (dir::static_type,
- scope_->out_path (),
- dir_path (),
- string (),
- nullopt,
- target_decl::real,
- trace).first;
- // Fall through.
+ if (ct == nullptr)
+ {
+ l5 ([&]{trace (t) << "creating current directory alias for " << dt;});
+
+ // While this target is not explicitly mentioned in the buildfile, we
+ // say that we behave as if it were. Thus not implied.
+ //
+ ct = &ctx->targets.insert (dir::static_type,
+ scope_->out_path (),
+ dir_path (),
+ string (),
+ nullopt,
+ target_decl::real,
+ trace).first;
+ }
+ else
+ ct->decl = target_decl::real;
+
+ ct->prerequisites_state_.store (2, memory_order_relaxed);
+ ct->prerequisites_.push_back (prerequisite (dt));
}
- else if (ct->decl != target_decl::real)
+
+ // See if this is a root buildfile and not in a simple project.
+ //
+ if (bf != nullptr &&
+ root_ != nullptr &&
+ root_->root_extra != nullptr &&
+ root_->root_extra->loaded &&
+ *root_->root_extra->project != nullptr &&
+ bf->dir == root_->src_path () &&
+ bf->name == root_->root_extra->buildfile_file.string ())
{
- ct->decl = target_decl::real;
- // Fall through.
- }
- else
- return; // Existing and not implied.
+ // See if we have any exported buildfiles.
+ //
+ const dir_path& export_dir (
+ root_->root_extra->altn ? alt_export_dir : std_export_dir);
+
+ dir_path d (root_->src_path () / export_dir);
+ if (exists (d))
+ {
+ // Make sure prerequisites are set.
+ //
+ ct->prerequisites_state_.store (2, memory_order_relaxed);
+
+ const string& build_ext (root_->root_extra->build_ext);
+
+ // Return true if entered any exported buildfiles.
+ //
+ // Note: recursive lambda.
+ //
+ auto iterate = [this, &trace,
+ ct, &build_ext] (const dir_path& d,
+ const auto& iterate) -> bool
+ {
+ bool r (false);
+
+ try
+ {
+ for (const dir_entry& e:
+ dir_iterator (d, dir_iterator::detect_dangling))
+ {
+ switch (e.type ())
+ {
+ case entry_type::directory:
+ {
+ r = iterate (d / path_cast<dir_path> (e.path ()), iterate) || r;
+ break;
+ }
+ case entry_type::regular:
+ {
+ const path& n (e.path ());
+
+ if (n.extension () == build_ext)
+ {
+ // Similar to above, enter as real.
+ //
+ // Note that these targets may already be entered (for
+ // example, if already imported).
+ //
+ const target& bf (
+ ctx->targets.insert (buildfile::static_type,
+ d,
+ (root_->out_eq_src ()
+ ? dir_path ()
+ : out_src (d, *root_)),
+ n.base ().string (),
+ build_ext,
+ target_decl::real,
+ trace).first);
+
+ ct->prerequisites_.push_back (prerequisite (bf));
+ r = true;
+ }
+
+ break;
+ }
+ case entry_type::unknown:
+ {
+ bool sl (e.ltype () == entry_type::symlink);
- ct->prerequisites_state_.store (2, memory_order_relaxed);
- ct->prerequisites_.emplace_back (prerequisite (dt));
+ fail << (sl ? "dangling symlink" : "inaccessible entry")
+ << ' ' << d / e.path ();
+
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ catch (const system_error& e)
+ {
+ fail << "unable to iterate over " << d << ": " << e;
+ }
+
+ return r;
+ };
+
+ if (iterate (d, iterate))
+ {
+ // Arrange for the exported buildfiles to be installed, recreating
+ // subdirectories inside export/. Essentially, we are arranging for
+ // this:
+ //
+ // build/export/buildfile{*}:
+ // {
+ // install = buildfile/
+ // install.subdirs = true
+ // }
+ //
+ if (cast_false<bool> (root_->vars["install.loaded"]))
+ {
+ enter_scope es (*this, dir_path (export_dir));
+ auto& vars (scope_->target_vars[buildfile::static_type]["*"]);
+
+ // @@ TODO: get cached variables from the module once we have one.
+ //
+ {
+ auto r (vars.insert (*root_->var_pool ().find ("install")));
+
+ if (r.second) // Already set by the user?
+ r.first = path_cast<path> (dir_path ("buildfile"));
+ }
+
+ {
+ auto r (vars.insert (
+ *root_->var_pool (true).find ("install.subdirs")));
+ if (r.second)
+ r.first = true;
+ }
+ }
+ }
+ }
+ }
}
- void parser::
+ const buildfile& parser::
enter_buildfile (const path& p, optional<dir_path> out)
{
tracer trace ("parser::enter_buildfile", &path_);
@@ -8984,7 +9256,7 @@ namespace build2
o = out_src (d, *root_);
}
- ctx->targets.insert<buildfile> (
+ return ctx->targets.insert<buildfile> (
move (d),
move (o),
p.leaf ().base ().string (),
diff --git a/libbuild2/parser.hxx b/libbuild2/parser.hxx
index d97cd7b..54735d5 100644
--- a/libbuild2/parser.hxx
+++ b/libbuild2/parser.hxx
@@ -379,15 +379,15 @@ namespace build2
attributes&
attributes_top () {return attributes_.back ();}
- // 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.
+ // Source a buildfile as a stream optionally 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 default_target);
+ source_buildfile (istream&,
+ const path_name&,
+ const location&,
+ bool default_target);
// The what argument is used in diagnostics (e.g., "expected <what>
// instead of ...".
@@ -604,11 +604,11 @@ namespace build2
switch_scope (const dir_path& out_base);
void
- process_default_target (token&);
+ process_default_target (token&, const buildfile*);
// Enter buildfile as a target.
//
- void
+ const buildfile&
enter_buildfile (const path&, optional<dir_path> out = nullopt);
// Lexer.
diff --git a/libbuild2/utility-installed.cxx b/libbuild2/utility-installed.cxx
index 441e31b..decc71d 100644
--- a/libbuild2/utility-installed.cxx
+++ b/libbuild2/utility-installed.cxx
@@ -14,6 +14,10 @@ namespace build2
#ifdef BUILD2_INSTALL_LIB
const dir_path build_install_lib (BUILD2_INSTALL_LIB);
#endif
+
+#ifdef BUILD2_INSTALL_BUILDFILE
+ const dir_path build_install_buildfile (BUILD2_INSTALL_BUILDFILE);
+#endif
}
#endif
diff --git a/libbuild2/utility-uninstalled.cxx b/libbuild2/utility-uninstalled.cxx
index a6bad55..69908f9 100644
--- a/libbuild2/utility-uninstalled.cxx
+++ b/libbuild2/utility-uninstalled.cxx
@@ -7,4 +7,10 @@ namespace build2
{
const bool build_installed = false;
const dir_path build_install_lib; // Empty.
+
+#ifdef BUILD2_INSTALL_BUILDFILE
+ const dir_path build_install_buildfile (BUILD2_INSTALL_BUILDFILE);
+#else
+ const dir_path build_install_buildfile; // Empty.
+#endif
}
diff --git a/libbuild2/utility.hxx b/libbuild2/utility.hxx
index 1e22e7e..43cb904 100644
--- a/libbuild2/utility.hxx
+++ b/libbuild2/utility.hxx
@@ -186,11 +186,13 @@ namespace build2
LIBBUILD2_SYMEXPORT extern const standard_version build_version;
LIBBUILD2_SYMEXPORT extern const string build_version_interface;
- // Whether running installed build and, if so, the library installation
- // directory (empty otherwise).
+ // Whether running installed build as well as the library installation
+ // directory (only if installed, empty otherwise) and the exported buildfile
+ // installation directory (only if configured, empty otherwise).
//
LIBBUILD2_SYMEXPORT extern const bool build_installed;
LIBBUILD2_SYMEXPORT extern const dir_path build_install_lib; // $install.lib
+ LIBBUILD2_SYMEXPORT extern const dir_path build_install_buildfile; // $install.buildfile
// --[no-]mtime-check
//