aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--INSTALL146
-rw-r--r--LICENSE2
-rw-r--r--build2/b.cxx6
-rw-r--r--buildfile2
-rw-r--r--libbuild2/adhoc-rule-buildscript.cxx26
-rw-r--r--libbuild2/build/script/parser.cxx4
-rw-r--r--libbuild2/cc/compile-rule.cxx27
-rw-r--r--libbuild2/cc/functions.cxx8
-rw-r--r--libbuild2/cc/init.cxx2
-rw-r--r--libbuild2/config/operation.cxx12
-rw-r--r--libbuild2/context.cxx7
-rw-r--r--libbuild2/context.hxx36
-rw-r--r--libbuild2/dist/operation.cxx9
-rw-r--r--libbuild2/functions-name.cxx16
-rw-r--r--libbuild2/functions-name.hxx7
-rw-r--r--libbuild2/functions-target.cxx11
-rw-r--r--libbuild2/module.cxx10
-rw-r--r--libbuild2/parser.cxx52
-rw-r--r--libbuild2/utility.hxx1
-rw-r--r--repositories.manifest7
-rw-r--r--tests/name/pattern.testscript56
21 files changed, 391 insertions, 56 deletions
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..5d2e225
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,146 @@
+Unless you specifically only need the build2 build system, you should install
+the entire build2 toolchain instead, either using the install script
+(https://build2.org/install.xhtml) or the build2-toolchain distribution.
+
+The instructions outlined below are essentially a summary of the first three
+steps of the manual bootstrap process described in build2-toolchain with a few
+extra examples that would primarily be useful for distribution packaging.
+
+Also, below we only show commands for UNIX-like operating systems. For other
+operating systems and for more details on each step, refer to the
+build2-toolchain installation documentation.
+
+build2 requires a C++14 compiler. GCC 4.9, Clang 3.7, and MSVC 14 (2015) Update
+3 or any later versions of these compilers are known to work. The build system
+is self-hosted, which means that unless you have obtained a pre-built binary
+from somewhere else, you will need to bootstrap it. To accomplish this, we use
+the bootstrap.sh shell script (or equivalent batch files for Windows) found in
+the root directory of the build2 distribution. On UNIX-like operating systems
+as well as on Windows with MinGW or Clang, a GNU make makefile called
+bootstrap.gmake can also be used with the major advanage over the script being
+support for parallel compilation and an out of tree build (see comments inside
+the makefile for more information).
+
+The following is the recommended sequence of steps:
+
+0. Prerequisites
+
+ Get libbutl (normally from the same place where you got build2) and place
+ it inside build2, so that you have:
+
+ build2-X.Y.Z
+ |
+ `-- libbutl-X.Y.Z
+
+1. Bootstrap, Phase 1
+
+ First, we build a minimal build system using bootstrap.sh (run bootstrap.sh
+ -h for options):
+
+ $ cd build2-X.Y.Z
+ $ ./bootstrap.sh g++
+
+ $ build2/b-boot --version
+
+ Alternatively, we can use the bootstrap.gmake makefile:
+
+ $ cd build2-X.Y.Z
+ $ make -f bootstrap.gmake -j 8 CXX=g++
+
+ $ build2/b-boot --version
+
+ If you would prefer to bootstrap out of source tree, this is supported by
+ the makefile (but not the script):
+
+ $ mkdir build2-boot
+ $ make -C build2-boot -f ../build2-X.Y.Z/bootstrap.gmake -j 8 CXX=g++
+
+ $ build2-boot/build2/b-boot --version
+
+2. Bootstrap, Phase 2
+
+ Then, we rebuild the build system with the result of Phase 1 linking
+ libraries statically.
+
+ $ build2/b-boot config.cxx=g++ config.bin.lib=static build2/exe{b}
+ $ mv build2/b build2/b-boot
+
+ $ build2/b-boot --version
+
+ Or, alternatively, for an out of source build:
+
+ $ build2-boot/build2/b-boot config.cxx=g++ config.bin.lib=static \
+ build2-X.Y.Z/build2/@build2-static/build2/exe{b}
+
+ $ build2-static/build2/b --version
+
+3. Build and Install
+
+ Finally, we configure, build, and optionally install the "final" version
+ using shared libraries:
+
+ $ build2/b-boot configure \
+ config.config.hermetic=true \
+ config.cxx=g++ \
+ config.cc.coptions=-O3 \
+ config.bin.rpath=/usr/local/lib \
+ config.install.root=/usr/local \
+ config.install.sudo=sudo
+
+ $ build2/b-boot
+
+ | The config.config.hermetic=true configuration variable in the first
+ | command makes sure the embedded ~host and ~build2 configurations include
+ | the current environment. This is especially important for ~build2 which
+ | is used to dynamically build and load ad hoc recipes and build system
+ | modules and must therefore match the environment that was used to build
+ | the build system itself.
+
+ If you are only interested in installing the result, then you can avoid
+ building tests by specifying the update-for-install operation in the last
+ command:
+
+ $ build2/b-boot update-for-install
+
+ On the other hand, if I you are not planning to install the result, then
+ you can omit the config.install.* values as well as .rpath.
+
+ To install:
+
+ $ build2/b-boot install
+ $ which b
+ $ b --version
+
+ To uninstall:
+
+ $ b uninstall
+ $ which b
+
+ Or, alternatively, for an out of source build:
+
+ $ build2-static/build2/b configure: build2-X.Y.Z/@build2-shared/ \
+ config.config.hermetic=true \
+ config.cxx=g++ \
+ config.cc.coptions=-O3 \
+ config.bin.rpath=/usr/local/lib \
+ config.install.root=/usr/local \
+ config.install.sudo=sudo
+
+ $ build2-static/build2/b update-for-install: build2-shared/
+
+ $ build2-static/build2/b install: build2-shared/
+
+ $ b uninstall: build2-shared/
+
+ For distribution packaging it is often required to install "as if" into the
+ system directory (for example, /usr) but to copy the files somewhere else
+ (for example, /tmp/install/usr; aka the DESTDIR functionality). In build2
+ this can be achieved with the config.install.chroot configuration variable,
+ for example:
+
+ $ build2-static/build2/b configure: build2-X.Y.Z/@build2-shared/ \
+ config.config.hermetic=true \
+ config.cxx=g++ \
+ config.cc.coptions=-O3 \
+ config.install.root=/usr \
+ config.install.chroot=/tmp/install
diff --git a/LICENSE b/LICENSE
index f2f9ac7..42ced0c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2014-2024 the build2 authors (see the AUTHORS file).
+Copyright (c) 2014-2025 the build2 authors (see the AUTHORS file).
Copyright (c) Microsoft Corporation for the libbuild2/cc/msvc-setup.h file.
Permission is hereby granted, free of charge, to any person obtaining a copy
diff --git a/build2/b.cxx b/build2/b.cxx
index 0b4ec3a..f357722 100644
--- a/build2/b.cxx
+++ b/build2/b.cxx
@@ -831,6 +831,12 @@ main (int argc, char* argv[])
if (!oname.empty () && lift ())
break;
+ // Increment load generation for subsequent operations in a batch
+ // since they may load additional buildfiles.
+ //
+ if (mid != 0)
+ ctx.load_generation++;
+
// Next bootstrap projects for all the target so that all the variable
// overrides are set (if we also load/search/match in the same loop
// then we may end up loading a project (via import) before this
diff --git a/buildfile b/buildfile
index e10f1c0..c88c55b 100644
--- a/buildfile
+++ b/buildfile
@@ -1,7 +1,7 @@
# file : buildfile
# license : MIT; see accompanying LICENSE file
-./: {*/ -build/ -config/ -old-tests/} \
+./: {*/ -build/ -config/ -old-tests/ -doc/} \
doc{INSTALL NEWS README} legal{LICENSE AUTHORS} \
file{INSTALL.cli bootstrap* config.guess config.sub} \
manifest
diff --git a/libbuild2/adhoc-rule-buildscript.cxx b/libbuild2/adhoc-rule-buildscript.cxx
index b125ac5..e3ed0a4 100644
--- a/libbuild2/adhoc-rule-buildscript.cxx
+++ b/libbuild2/adhoc-rule-buildscript.cxx
@@ -1207,9 +1207,11 @@ namespace build2
}
// Note that in case of dry run we will have an incomplete (but valid)
- // database which will be updated on the next non-dry run.
+ // database which will be updated on the next non-dry run. Except that
+ // we may still end up performing a non-dry-run update due to update
+ // during match or load.
//
- if (!update || ctx.dry_run_option)
+ if (!update /*|| ctx.dry_run_option*/)
dd.close (false /* mtime_check */);
else
mdb->dd = dd.close_to_reopen ();
@@ -1246,8 +1248,24 @@ namespace build2
md->deferred_failure);
}
- if (update && dd.reading () && !ctx.dry_run_option)
- dd.touch = timestamp_unknown;
+ // Update depdb timestamp if nothing changed. Failed that, we will keep
+ // re-validating the information store in depdb (see similar logic in
+ // cc::compile_rule).
+ //
+ if (update && dd.reading ())
+ {
+ // What will happen if dry_run_option is true but we still end up
+ // performing a non-dry-run update due to update during match or
+ // load? In this case the target will become up-to-date and we will
+ // keep re-validating the cache until the depdb will get touched due
+ // to other reasons, which would be bad. So it feels like the least
+ // bad option is to keep re-touching the database on dry-run.
+ //
+#if 0
+ if (!ctx.dry_run_option)
+#endif
+ dd.touch = timestamp_unknown;
+ }
dd.close (false /* mtime_check */);
diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx
index 3ecf23d..c9193ff 100644
--- a/libbuild2/build/script/parser.cxx
+++ b/libbuild2/build/script/parser.cxx
@@ -2501,6 +2501,10 @@ namespace build2
//
auto fail = [this, what, &ctx] (const auto& f) -> optional<bool>
{
+ // Note that this test will give a false negative if this target
+ // ends up being updated during load or match. At least it's
+ // conservative.
+ //
bool df (!ctx.match_only && !ctx.dry_run_option);
diag_record dr;
diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx
index 29a26b5..c8955bc 100644
--- a/libbuild2/cc/compile-rule.cxx
+++ b/libbuild2/cc/compile-rule.cxx
@@ -1544,8 +1544,20 @@ namespace build2
// to keep re-validating the file on every subsequent dry-run as well
// on the real run).
//
- if (u && dd.reading () && !ctx.dry_run_option)
- dd.touch = timestamp_unknown;
+ if (u && dd.reading ())
+ {
+ // What will happen if dry_run_option is true but we still end up
+ // performing a non-dry-run update due to update during match or
+ // load? In this case the target will become up-to-date and we will
+ // keep re-validating the cache until the depdb will get touched due
+ // to other reasons, which would be bad. So it feels like the least
+ // bad option is to keep re-touching the database on dry-run.
+ //
+#if 0
+ if (!ctx.dry_run_option)
+#endif
+ dd.touch = timestamp_unknown;
+ }
dd.close (false /* mtime_check */);
md.dd = move (dd.path);
@@ -4042,6 +4054,10 @@ namespace build2
//
auto fail = [&ctx] (const auto& h) -> optional<bool>
{
+ // Note that this test will give a false negative if this target
+ // ends up being updated during load or match. At least it's
+ // conservative.
+ //
bool df (!ctx.match_only && !ctx.dry_run_option);
diag_record dr;
@@ -4104,7 +4120,6 @@ namespace build2
this] (path hp, path bp, timestamp mt) -> optional<bool>
{
context& ctx (t.ctx);
- bool df (!ctx.match_only && !ctx.dry_run_option);
const file* ht (
enter_header (a, bs, t, li,
@@ -4113,6 +4128,12 @@ namespace build2
if (ht == nullptr) // hp is still valid.
{
+ // Note that this test will give a false negative if this target
+ // ends up being updated during load or match. At least it's
+ // conservative.
+ //
+ bool df (!ctx.match_only && !ctx.dry_run_option);
+
diag_record dr;
dr << error << "header " << hp << " not found and no rule to "
<< "generate it";
diff --git a/libbuild2/cc/functions.cxx b/libbuild2/cc/functions.cxx
index 9d408af..0adcf5f 100644
--- a/libbuild2/cc/functions.cxx
+++ b/libbuild2/cc/functions.cxx
@@ -76,7 +76,9 @@ namespace build2
for (auto i (ts_ns.begin ()); i != ts_ns.end (); ++i)
{
name& n (*i), o;
- const target& t (to_target (*bs, move (n), move (n.pair ? *++i : o)));
+ const target& t (to_target (*bs,
+ move (n), move (n.pair ? *++i : o),
+ true /* in_recipe */));
if (!t.matched (a))
fail << t << " is not matched" <<
@@ -193,7 +195,9 @@ namespace build2
for (auto i (ts_ns.begin ()); i != ts_ns.end (); ++i)
{
name& n (*i), o;
- const target& t (to_target (*bs, move (n), move (n.pair ? *++i : o)));
+ const target& t (to_target (*bs,
+ move (n), move (n.pair ? *++i : o),
+ true /* in_recipe */));
bool la (false);
if (li
diff --git a/libbuild2/cc/init.cxx b/libbuild2/cc/init.cxx
index d691bc5..64c70f8 100644
--- a/libbuild2/cc/init.cxx
+++ b/libbuild2/cc/init.cxx
@@ -764,7 +764,7 @@ namespace build2
perform_update_id,
context::operation_callback {&compiledb_pre, &compiledb_post});
- if (ctx.load_generation > 1)
+ if (!ctx.phase_mutex.unlocked ()) // Interrupting load.
{
action a (ctx.current_action ());
diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx
index 6e7ef18..77a1294 100644
--- a/libbuild2/config/operation.cxx
+++ b/libbuild2/config/operation.cxx
@@ -1018,8 +1018,20 @@ namespace build2
if (oif->operation_pre != nullptr)
oif->operation_pre (ctx, {}, true /* inner */, location ());
+ // Use perform_match() instead of direct match_sync() to handle
+ // posthoc targets (similar to dist).
+ //
+#if 0
phase_lock pl (ctx, run_phase::match);
match_sync (action (configure_id, id), t);
+#else
+ action a (configure_id, id);
+ action_targets ts {&t};
+ perform_match ({}, a, ts,
+ 1 /* diag (failures only) */,
+ false /* progress */);
+ perform_post_operation_callbacks (ctx, a, ts, false /*failed*/);
+#endif
if (oif->operation_post != nullptr)
oif->operation_post (ctx, {}, true /* inner */);
diff --git a/libbuild2/context.cxx b/libbuild2/context.cxx
index 6e4fd6f..31d218b 100644
--- a/libbuild2/context.cxx
+++ b/libbuild2/context.cxx
@@ -1061,6 +1061,13 @@ namespace build2
return r ? optional<bool> (s) : nullopt;
}
+ bool run_phase_mutex::
+ unlocked () const
+ {
+ mlock l (m_);
+ return lc_ == 0 && mc_ == 0 && ec_ == 0;
+ }
+
// C++17 deprecated uncaught_exception() so use uncaught_exceptions() if
// available.
//
diff --git a/libbuild2/context.hxx b/libbuild2/context.hxx
index 81ac970..12b11d5 100644
--- a/libbuild2/context.hxx
+++ b/libbuild2/context.hxx
@@ -49,6 +49,12 @@ namespace build2
optional<bool>
relock (run_phase unlock, run_phase lock);
+ // Return true if the mutex is unlocked, meaning we are in the initial
+ // load phase.
+ //
+ bool
+ unlocked () const;
+
// Statistics.
//
public:
@@ -78,7 +84,7 @@ namespace build2
//
context& ctx_;
- mutex m_;
+ mutable mutex m_;
bool fail_;
@@ -209,8 +215,12 @@ namespace build2
// hierarchy would "see" a new value from the newly inserted scope.
//
// The special load_generation value 0 indicates initialization before
- // anything has been loaded. Currently, it is changed to 1 at the end
- // of the context constructor.
+ // anything has been loaded. Currently, it is changed to 1 at the end of
+ // the context constructor. Note also that subsequent operations in a
+ // batch may trigger loading of additional buildfiles, in fact, entire new
+ // projects. As a result, load_generation is also incremented after each
+ // operation in a batch. If you need to detect the initial load in each
+ // operation, check that phase_mutex is unlocked.
//
// Note must come (and thus initialized) before the data_ member.
//
@@ -230,6 +240,10 @@ namespace build2
// Match only flag/level (see --{load,match}-only but also dist).
//
+ // See also dry_run, which is in some sense a weaker version of match-
+ // only: the target is executed but nothing is actually being done (unless
+ // executed during match or load, that is).
+ //
optional<match_only_level> match_only;
// Skip booting external modules flag (see --no-external-modules).
@@ -263,11 +277,22 @@ namespace build2
//
// Note also that sometimes it makes sense to do a bit more than
// absolutely necessary or to discard information in order to keep the
- // rule logic sane. And some rules may choose to ignore this flag
+ // rule logic sane. And some rules may choose to ignore this flag
// altogether. In this case, however, the rule should be careful not to
// rely on functions (notably from filesystem) that respect this flag in
// order not to end up with a job half done.
//
+ // Finally, sometimes you may need to know during match whether there will
+ // be a non-dry-run execute and use the dry_run_option for that. This can
+ // be problematic because even when dry_run_option is true, the target may
+ // end up being executed in the non-dry-run mode during load or match. As
+ // a result, any logic that is based on dry_run_option should be capable
+ // of functioning correctly in the non-dry-run execute.
+ //
+ // See also match_only, which is in some sense a stronger version of
+ // dry-run: the target is not executed at all, again, unless during match
+ // or load.
+ //
bool dry_run = false;
bool dry_run_option;
@@ -802,6 +827,9 @@ namespace build2
// Set current meta-operation and operation.
//
+ // Remember to also increment load_generation for subsequent operations in
+ // a batch if additional buildfiles are loaded between them.
+ //
// Note that the context instance is not to be re-used between different
// meta-operations.
//
diff --git a/libbuild2/dist/operation.cxx b/libbuild2/dist/operation.cxx
index 5b00980..0ed02c6 100644
--- a/libbuild2/dist/operation.cxx
+++ b/libbuild2/dist/operation.cxx
@@ -60,8 +60,13 @@ namespace build2
const path& arc, const dir_path& dir, const string& ext);
static operation_id
- dist_operation_pre (context&, const values&, operation_id o)
+ dist_operation_pre (context& ctx, const values&, operation_id o)
{
+ // Note: cannot be --load-only which requires perform(update).
+ //
+ if (ctx.match_only)
+ fail << "--match-only specified for dist meta-operation";
+
if (o != default_id)
fail << "explicit operation specified for dist meta-operation";
@@ -283,6 +288,8 @@ namespace build2
action_targets ts {tgt};
{
+ // Signal to the rules we will not be executing.
+ //
auto mog = make_guard ([&ctx] () {ctx.match_only = nullopt;});
ctx.match_only = match_only_level::all;
diff --git a/libbuild2/functions-name.cxx b/libbuild2/functions-name.cxx
index 456f85b..cb32d09 100644
--- a/libbuild2/functions-name.cxx
+++ b/libbuild2/functions-name.cxx
@@ -49,7 +49,10 @@ namespace build2
}
const target&
- to_target (const scope& s, name&& n, name&& o)
+ to_target (const scope& s,
+ name&& n, name&& o,
+ bool in_recipe,
+ const location& loc)
{
// Note: help the user out and search in both out and src like a
// prerequisite.
@@ -62,13 +65,13 @@ namespace build2
//
bool typed (n.typed ());
- diag_record dr (fail);
+ diag_record dr (fail (loc));
dr << "target "
<< (n.pair ? names {move (n), move (o)} : names {move (n)})
<< " not found";
- if (!typed)
+ if (in_recipe && !typed)
dr << info << "wrap it in ([names] ...) if this is literal target name "
<< "specified inside recipe";
@@ -76,12 +79,15 @@ namespace build2
}
const target&
- to_target (const scope& s, names&& ns)
+ to_target (const scope& s, names&& ns, bool in_recipe, const location& loc)
{
assert (ns.size () == (ns[0].pair ? 2 : 1));
name o;
- return to_target (s, move (ns[0]), move (ns[0].pair ? ns[1] : o));
+ return to_target (s,
+ move (ns[0]), move (ns[0].pair ? ns[1] : o),
+ in_recipe,
+ loc);
}
static bool
diff --git a/libbuild2/functions-name.hxx b/libbuild2/functions-name.hxx
index 34fa4b8..30fc8ad 100644
--- a/libbuild2/functions-name.hxx
+++ b/libbuild2/functions-name.hxx
@@ -18,13 +18,16 @@ namespace build2
// Resolve the name to target issuing diagnostics and failing if not found.
//
LIBBUILD2_SYMEXPORT const target&
- to_target (const scope&, name&&, name&& out);
+ to_target (const scope&,
+ name&&, name&& out,
+ bool in_recipe,
+ const location& = {});
// As above but from the names vector which should contain a single name
// or an out-qualified name pair (asserted).
//
LIBBUILD2_SYMEXPORT const target&
- to_target (const scope&, names&&);
+ to_target (const scope&, names&&, bool in_recipe, const location& = {});
}
#endif // LIBBUILD2_FUNCTIONS_NAME_HXX
diff --git a/libbuild2/functions-target.cxx b/libbuild2/functions-target.cxx
index c7cb50e..55baeda 100644
--- a/libbuild2/functions-target.cxx
+++ b/libbuild2/functions-target.cxx
@@ -35,6 +35,8 @@ namespace build2
if (s == nullptr)
fail << "target.path() called out of scope";
+ context& ctx (s->ctx);
+
// Most of the time we will have a single target so optimize for that.
//
small_vector<path, 1> r;
@@ -42,7 +44,10 @@ namespace build2
for (auto i (ns.begin ()); i != ns.end (); ++i)
{
name& n (*i), o;
- const target& t (to_target (*s, move (n), move (n.pair ? *++i : o)));
+ const target& t (
+ to_target (*s,
+ move (n), move (n.pair ? *++i : o),
+ ctx.phase != run_phase::load /* in_recipe */));
if (const auto* pt = t.is_a<path_target> ())
{
@@ -94,7 +99,9 @@ namespace build2
name o;
const target& t (
- to_target (*s, move (ns[0]), move (ns[0].pair ? ns[1] : o)));
+ to_target (*s,
+ move (ns[0]), move (ns[0].pair ? ns[1] : o),
+ s->ctx.phase != run_phase::load /* in_recipe */));
if (const auto* et = t.is_a<exe> ())
{
diff --git a/libbuild2/module.cxx b/libbuild2/module.cxx
index 36a7ce5..9a12d01 100644
--- a/libbuild2/module.cxx
+++ b/libbuild2/module.cxx
@@ -188,18 +188,24 @@ namespace build2
loc,
tgs);
+ // Note that we suppress all outcome diagnostics, even for failure since
+ // the extra `info: failed to update <target>` is not very useful (we
+ // expect an appropriate diagnostics frame explains what's going on).
+ //
mo_perform.match ({}, /* parameters */
a,
tgs,
- 1, /* diag (failures only) */
+ 0, /* diag (none) */
false /* progress */);
mo_perform.execute ({}, /* parameters */
a,
tgs,
- 1, /* diag (failures only) */
+ 0, /* diag (none) */
false /* progress */);
+ ctx.module_context->load_generation++;
+
assert (tgs.size () == 1);
return tgs[0].as<target> ();
}
diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx
index 53f808c..578d3dd 100644
--- a/libbuild2/parser.cxx
+++ b/libbuild2/parser.cxx
@@ -7258,7 +7258,9 @@ namespace build2
(root_ != nullptr &&
root_->root_extra != nullptr &&
m.to_directory () &&
- exists (d.sp / m / root_->root_extra->buildignore_file)))
+ exists (m.relative ()
+ ? d.sp / m / root_->root_extra->buildignore_file
+ : m / root_->root_extra->buildignore_file)))
return !interm;
// Note that we have to make copies of the extension since there will
@@ -7314,9 +7316,11 @@ namespace build2
return true;
});
+ path pat (move (p));
+
try
{
- path_search (path (move (p)),
+ path_search (pat,
process,
*sp,
path_match_flags::follow_symlinks,
@@ -7324,7 +7328,8 @@ namespace build2
}
catch (const system_error& e)
{
- fail (l) << "unable to scan " << *sp << ": " << e;
+ fail (l) << "unable to scan for '"
+ << (pat.relative () ? *sp / pat : pat) << "': " << e;
}
};
@@ -7569,7 +7574,10 @@ namespace build2
// and look for some wildcards since the pattern can be the result of an
// expansion (or, worse, concatenation). Thus pattern_mode::detect: we
// are going to ask parse_names() to detect for us if the first name is
- // a pattern. And if it is, to refrain from adding pair/dir/type.
+ // a pattern. And if it is, to refrain from adding pair/dir/type (note:
+ // for the pattern inclusions and exclusions the name's type member will
+ // be set to "+" and "-", respectively, and will later be cleared by
+ // expand_name_pattern()).
//
optional<const target_type*> pat_tt (
parse_names (
@@ -8313,16 +8321,42 @@ namespace build2
fail (loc) << "invalid path '" << e.path << "'";
}
- count = parse_names_trailer (
- t, tt, ns, pmode, what, separators, pairn, *pp1, dp1, tp1, cross);
+ // Note that for a pattern inclusion group (see above) we make sure
+ // that the resulting patterns are simple names, passing NULL as the
+ // directory path (the names' type members will still be set to "+"
+ // thought; see the parse_names_trailer::parse() lambda
+ // implementation for details).
+ //
+ assert (!pinc || (tp1 != nullptr && *tp1 == "+"));
- // If empty group or empty name, then this is not a pattern inclusion
- // group (see above).
+ count = parse_names_trailer (
+ t, tt,
+ ns,
+ pmode,
+ what,
+ separators, pairn,
+ *pp1, (!pinc ? dp1 : nullptr), tp1,
+ cross);
+
+ // If empty group, then this is not a pattern inclusion group.
//
if (pinc)
{
- if (count != 0 && (count > 1 || !ns.back ().empty ()))
+ if (count != 0)
+ {
+ // Note that we can never end up with the empty name here. For
+ // example, for the below constructs the above
+ // parse_names_trailer() call would fail with the 'typed empty
+ // name' error, since the empty name's type will be set to "+"
+ // (see above for details):
+ //
+ // foo/{hxx cxx}{+{}}
+ // foo/{+{}}
+ //
+ assert (count > 1 || !ns.back ().empty ());
+
pattern_detected (ttp);
+ }
ppat = pinc = false;
}
diff --git a/libbuild2/utility.hxx b/libbuild2/utility.hxx
index 79652ce..99d6806 100644
--- a/libbuild2/utility.hxx
+++ b/libbuild2/utility.hxx
@@ -84,6 +84,7 @@ namespace build2
using butl::trim;
using butl::next_word;
using butl::sanitize_identifier;
+ using butl::make_sanitized_identifier;
using butl::sanitize_strlit;
using butl::make_guard;
diff --git a/repositories.manifest b/repositories.manifest
index 25be350..e37a9e7 100644
--- a/repositories.manifest
+++ b/repositories.manifest
@@ -3,4 +3,9 @@ summary: build2 build system repository
:
role: prerequisite
-location: ../libbutl.git#HEAD
+location: ../libbutl.git#master
+
+:
+role: prerequisite
+location: https://stage.build2.org/1
+trust: EC:50:13:E2:3D:F7:92:B4:50:0B:BF:2A:1F:7D:31:04:C6:57:6F:BC:BE:04:2E:E0:58:14:FA:66:66:21:1F:14
diff --git a/tests/name/pattern.testscript b/tests/name/pattern.testscript
index c1a4ce4..acad250 100644
--- a/tests/name/pattern.testscript
+++ b/tests/name/pattern.testscript
@@ -105,59 +105,70 @@ EOI
:
{
touch foo.txt;
- $* <'print *.txt' >'foo.txt' : simple-file
+ $* <'print *.txt' >'foo.txt' : simple-file
mkdir foo;
- $* <'print */' >/'foo/' : simple-dir
+ $* <'print */' >/'foo/' : simple-dir
touch foo.txt;
- $* <'print {*.txt}' >'foo.txt' : group
+ $* <'print {*.txt}' >'foo.txt' : group
touch foo.txt;
- $* <'print {+*.txt}' >'foo.txt' : plus-prefixed
+ $* <'print {+*.txt}' >'foo.txt' : plus-prefixed
mkdir dir && touch dir/foo.txt;
- $* <'print dir/{*.txt}' >'dir/foo.txt' : dir
+ $* <'print dir/{*.txt}' >'dir/foo.txt' : dir
touch foo.txt;
- $* <'print file{*.txt}' >'file{foo.txt}' : type
+ $* <'print file{*.txt}' >'file{foo.txt}' : type
touch foo.txt;
- $* <'print x@{*.txt}' >'x@foo.txt' : pair
+ $* <'print x@{*.txt}' >'x@foo.txt' : pair
touch bar.txt;
- $* <'print x@dir/file{f*.txt}' >'' : empty
+ $* <'print x@dir/file{f*.txt}' >'' : empty
mkdir dir && touch dir/foo.txt;
- $* <'print **.txt' >/'dir/foo.txt' : recursive
+ $* <'print **.txt' >/'dir/foo.txt' : recursive
mkdir dir && touch dir/foo.txt;
- $* <'print d*/*.txt' >/'dir/foo.txt' : multi-pattern
+ $* <'print d*/*.txt' >/'dir/foo.txt' : multi-pattern
touch foo.txt bar.txt;
- $* <'print {*.txt -bar.txt}' >'foo.txt' : exclude-non-pattern
+ $* <'print {*.txt -bar.txt}' >'foo.txt' : exclude-non-pattern
mkdir baz;
touch foo.txt bar.txt baz/fox.txt baz/box.txt;
- $* <'print {**.txt -b*.txt -b*/*}' >'foo.txt' : exclude-pattern
+ $* <'print {**.txt -b*.txt -b*/*}' >'foo.txt' : exclude-pattern
touch foo.txt bar.txt baz.txt;
- $* <'print {*.txt -{*z.txt bar.txt}}' >'foo.txt' : exclude-group
+ $* <'print {*.txt -{*z.txt bar.txt}}' >'foo.txt' : exclude-group
touch bar.txt;
- $* <'print {f*.txt +bar.txt}' >'bar.txt' : include-non-wildcard
+ $* <'print {f*.txt +bar.txt}' >'bar.txt' : include-non-wildcard
touch bar.txt;
- $* <'print {f*.txt +b*.txt}' >'bar.txt' : include-pattern
+ $* <'print {f*.txt +b*.txt}' >'bar.txt' : include-pattern
mkdir bar;
- $* <'print {f*/ +{b*/}}' >/'bar/' : include-group
+ $* <'print {f*/ +{b*/}}' >/'bar/' : include-group
+
+ mkdir -p foo/bar;
+ $* <'print $path.canonicalize(foo/{+{b*/}})' >/'foo/bar/' : include-group-first
touch foo.txt fox.txt;
- $* <'print {*.txt -f*.txt +*x.txt}' >'fox.txt' : include-exclude-order
+ $* <'print {*.txt -f*.txt +*x.txt}' >'fox.txt' : include-exclude-order
touch foo.txt;
- $* <'print {+foo.txt} {+bar.txt}' >'foo.txt' : non-wildcard
+ $* <'print {+foo.txt} {+bar.txt}' >'foo.txt' : non-wildcard
+
+ mkdir -p foo/bar;
+ touch foo/bar/baz.txt;
+ $* <'print {$src_base/foo/**}' >/"$~/foo/bar/baz.txt" : abs-path-pattern
+
+ mkdir -p foo/bar;
+ touch foo/bar/baz.txt;
+ $* <'print $src_base/foo/{**}' >/"$~/foo/bar/baz.txt" : abs-start-dir
}
: escaping
@@ -309,6 +320,15 @@ EOI
print {+{$pats}}
EOI
+ : pattern-via-expansion-list-subdir
+ :
+ mkdir baz;
+ touch baz/foo.txt baz/bar.hxx;
+ $* <<EOI >'baz/bar.hxx baz/foo.txt'
+ pats = '*.hxx' '*.txt'
+ print baz/{+{$pats}}
+ EOI
+
: pattern-via-expansion-type
:
touch foo.txt;