diff options
-rw-r--r-- | INSTALL | 146 | ||||
-rw-r--r-- | LICENSE | 2 | ||||
-rw-r--r-- | build2/b.cxx | 6 | ||||
-rw-r--r-- | buildfile | 2 | ||||
-rw-r--r-- | libbuild2/adhoc-rule-buildscript.cxx | 26 | ||||
-rw-r--r-- | libbuild2/build/script/parser.cxx | 4 | ||||
-rw-r--r-- | libbuild2/cc/compile-rule.cxx | 27 | ||||
-rw-r--r-- | libbuild2/cc/functions.cxx | 8 | ||||
-rw-r--r-- | libbuild2/cc/init.cxx | 2 | ||||
-rw-r--r-- | libbuild2/config/operation.cxx | 12 | ||||
-rw-r--r-- | libbuild2/context.cxx | 7 | ||||
-rw-r--r-- | libbuild2/context.hxx | 36 | ||||
-rw-r--r-- | libbuild2/dist/operation.cxx | 9 | ||||
-rw-r--r-- | libbuild2/functions-name.cxx | 16 | ||||
-rw-r--r-- | libbuild2/functions-name.hxx | 7 | ||||
-rw-r--r-- | libbuild2/functions-target.cxx | 11 | ||||
-rw-r--r-- | libbuild2/module.cxx | 10 | ||||
-rw-r--r-- | libbuild2/parser.cxx | 52 | ||||
-rw-r--r-- | libbuild2/utility.hxx | 1 | ||||
-rw-r--r-- | repositories.manifest | 7 | ||||
-rw-r--r-- | tests/name/pattern.testscript | 56 |
21 files changed, 391 insertions, 56 deletions
@@ -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 @@ -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 @@ -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; |