diff options
Diffstat (limited to 'libbuild2')
-rw-r--r-- | libbuild2/adhoc-rule-cxx.cxx | 42 | ||||
-rw-r--r-- | libbuild2/algorithm.cxx | 13 | ||||
-rw-r--r-- | libbuild2/buildfile | 82 | ||||
-rw-r--r-- | libbuild2/config/host-config.cxx.in | 3 | ||||
-rw-r--r-- | libbuild2/config/init.cxx | 18 | ||||
-rw-r--r-- | libbuild2/config/operation.cxx | 8 | ||||
-rw-r--r-- | libbuild2/dist/operation.cxx | 2 | ||||
-rw-r--r-- | libbuild2/dyndep.cxx | 10 | ||||
-rw-r--r-- | libbuild2/file.cxx | 11 | ||||
-rw-r--r-- | libbuild2/functions-regex.cxx | 2 | ||||
-rw-r--r-- | libbuild2/functions-string.cxx | 132 | ||||
-rw-r--r-- | libbuild2/scope.cxx | 8 | ||||
-rw-r--r-- | libbuild2/scope.hxx | 8 | ||||
-rw-r--r-- | libbuild2/script/parser.cxx | 14 | ||||
-rw-r--r-- | libbuild2/script/parser.hxx | 9 | ||||
-rw-r--r-- | libbuild2/script/run.cxx | 2 | ||||
-rw-r--r-- | libbuild2/script/script.cxx | 5 | ||||
-rw-r--r-- | libbuild2/script/script.hxx | 10 | ||||
-rw-r--r-- | libbuild2/search.cxx | 27 | ||||
-rw-r--r-- | libbuild2/search.hxx | 2 |
20 files changed, 366 insertions, 42 deletions
diff --git a/libbuild2/adhoc-rule-cxx.cxx b/libbuild2/adhoc-rule-cxx.cxx index 2ac97eb..8a91809 100644 --- a/libbuild2/adhoc-rule-cxx.cxx +++ b/libbuild2/adhoc-rule-cxx.cxx @@ -358,6 +358,46 @@ namespace build2 // This way the configuration will be always in sync with ~build2 // and we can update the recipe manually (e.g., for debugging). // + // Should we use ~build2 or ~build2-no-warnings? This case is similar + // to private host/module configurations in that the user doesn't have + // any control over the options used, etc. So it would be natural to + // use the no-warnings variant. However, unlike with tools/modules + // which can be configured in a user-created configuration (and which + // will normally be the case during development), for recipes it's + // always this automatically-create configuration. It feels like the + // best we can do is use ~build2-no-warnings by default but switch to + // ~build2 if the project is configured for development + // (config.<project>.develop). + // + string cfg; + { + const project_name& pn (named_project (rs)); + + if (!pn.empty ()) + { + string var ("config." + pn.variable () + ".develop"); + + if (lookup l = rs[var]) + { + // The value could be untyped if the project didn't declare this + // variable. Let's handle that case gracefully. + // + try + { + if (convert<bool> (*l)) + cfg = "~build2"; + } + catch (const invalid_argument& e) + { + fail << "invalid " << var << " value: " << e; + } + } + } + + if (cfg.empty ()) + cfg = "~build2-no-warnings"; + } + create_project ( pd, dir_path (), /* amalgamation */ @@ -366,7 +406,7 @@ namespace build2 {"cxx."}, /* root_modules */ "", /* root_post */ string ("config"), /* config_module */ - string ("config.config.load = ~build2"), /* config_file */ + "config.config.load = " + cfg, /* config_file */ false, /* buildfile */ "build2 core", /* who */ verbosity); /* verbosity */ diff --git a/libbuild2/algorithm.cxx b/libbuild2/algorithm.cxx index 62c500d..16f1503 100644 --- a/libbuild2/algorithm.cxx +++ b/libbuild2/algorithm.cxx @@ -1282,6 +1282,19 @@ namespace build2 // has no own prerequisites and the group's ones will be matched // by the group. } + else + { + // Similar to catch(failed) below. + // + s.state = target_state::failed; + l.offset = target::offset_applied; + + // Make sure we don't relock a failed target. + // + match_extra& me (s.match_extra); + me.cur_options = match_extra::all_options; + me.cur_options_.store (me.cur_options, memory_order_relaxed); + } } else l.offset = target::offset_tried; diff --git a/libbuild2/buildfile b/libbuild2/buildfile index 5ef0006..3518d93 100644 --- a/libbuild2/buildfile +++ b/libbuild2/buildfile @@ -73,6 +73,12 @@ libul{build2}: config/{hxx ixx txx cxx}{** -host-config -**.test...} \ # options which could cause spurious rebuilds when we filter out entire # groups. # +# For ~host also filter out config.bin.lib/config.bin.*.lib (static/shared +# library build/link preferences). In particular, we don't want to force +# config.bin.lib=shared since that will cause static libraries to link shared +# versions of their prerequisites (see mysql-client for a case where this can +# make a difference). +# # For ~build2 also filter out config.install.chroot -- we definitely don't # want it carried through. Also filter out variables that control tests # execution. @@ -82,41 +88,91 @@ libul{build2}: config/{hxx ixx txx cxx}{** -host-config -**.test...} \ # on the users of ~host/~build2; they can decide for themselves if they # want it). # -build2_config_lines = [strings] +# The *_no_warnings variants are with the suppressed C/C++ compiler warnings +# (in particular, used for private host configuration in bpkg). +# +# host_config_lines = [strings] +build2_config_lines = [strings] + +host_config_no_warnings_lines = [strings] +build2_config_no_warnings_lines = [strings] for l: $regex.replace_lines( \ $config.save(), \ '^( *(#|(config\.(test[. ]|dist\.|install\.chroot|config\.hermetic))).*|)$', \ [null]) { - build2_config_lines += $l - # Note: also preserve config.version. # + h = [null] if $regex.match( \ $l, \ ' *config\.(c[. ]|cxx[. ]|cc[.]|bin[.]|config.environment |version ).*') { - # Filter out sanitizer options in ~host. We run the toolchain with various - # sanitizers on CI but sanitizers cause issues in some packages. Note that - # we can have both -fsanitize and -fno-sanitize forms. For example: - # - # -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all - # - if $regex.match($l, ' *config\.(c|cxx|cc)\.(coptions|loptions)[ =].*') + if! ($regex.match(\ + $l, \ + ' *config\.bin\.(lib|exe\.lib|liba\.lib|libs\.lib)[ =].*')) { - l = $regex.replace($l, ' ?-f(no-)?sanitize[=-][^ ]+', '') + # Filter out sanitizer options in ~host. We run the toolchain with + # various sanitizers on CI but sanitizers cause issues in some packages. + # Note that we can have both -fsanitize and -fno-sanitize forms. For + # example: + # + # -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all + # + if $regex.match($l, ' *config\.(c|cxx|cc)\.(coptions|loptions)[ =].*') + { + h = $regex.replace($l, ' ?-f(no-)?sanitize[=-][^ ]+', '') + } + else + h = $l } + } + + if ($h != [null]) + host_config_lines += $h + + build2_config_lines += $l - host_config_lines += $l + # Append the warning suppressing option to config.{c,cxx}.coptions rather + # than config.cc.coptions since the former could re-enable them. + # + if ($regex.match($l, ' *config\.(c|cxx)\.coptions[ =].*')) + { + # Note that in MSVC overriding one warning option (say /W3) with another + # (say /w) triggers a warning. However, our compile_rule sanitizes the + # command line to resolve such overrides (see msvc_sanitize_cl()). + # + o = ($cxx.class == 'gcc' ? -w : $cxx.class == 'msvc' ? /w : ) + + if ($regex.match($l, '[^=]+= *\[null\] *')) + { + l = $regex.replace($l, '= *\[null\] *$', "= $o") + h = $regex.replace($h, '= *\[null\] *$', "= $o") + } + else + { + l = $regex.replace($l, '=(.*)$', "=\\1 $o") + h = $regex.replace($h, '=(.*)$', "=\\1 $o") + } } + + if ($h != [null]) + host_config_no_warnings_lines += $h + + build2_config_no_warnings_lines += $l } config/cxx{host-config}: config/in{host-config} { - build2_config = $regex.merge($build2_config_lines, '(.+)', '\1\n') host_config = $regex.merge($host_config_lines, '(.+)', '\1\n') + build2_config = $regex.merge($build2_config_lines, '(.+)', '\1\n') + + host_config_no_warnings = $regex.merge($host_config_no_warnings_lines, \ + '(.+)', '\1\n') + build2_config_no_warnings = $regex.merge($build2_config_no_warnings_lines, \ + '(.+)', '\1\n') } libul{build2}: dist/{hxx ixx txx cxx}{** -**.test...} diff --git a/libbuild2/config/host-config.cxx.in b/libbuild2/config/host-config.cxx.in index 9e3e0c2..6b1ce77 100644 --- a/libbuild2/config/host-config.cxx.in +++ b/libbuild2/config/host-config.cxx.in @@ -9,5 +9,8 @@ namespace build2 // extern const char host_config[] = R"###($host_config$)###"; extern const char build2_config[] = R"###($build2_config$)###"; + + extern const char host_config_no_warnings[] = R"###($host_config_no_warnings$)###"; + extern const char build2_config_no_warnings[] = R"###($build2_config_no_warnings$)###"; } } diff --git a/libbuild2/config/init.cxx b/libbuild2/config/init.cxx index 38590ae..2f134c4 100644 --- a/libbuild2/config/init.cxx +++ b/libbuild2/config/init.cxx @@ -210,6 +210,9 @@ namespace build2 #ifndef BUILD2_BOOTSTRAP extern const char host_config[]; extern const char build2_config[]; + + extern const char host_config_no_warnings[]; + extern const char build2_config_no_warnings[]; #endif bool @@ -484,14 +487,23 @@ namespace build2 const string& s (f.string ()); - if (s[0] != '~') + if (s.empty ()) + fail << "empty path in config.config.load"; + else if (s[0] != '~') load_config_file (f, l); - else if (s == "~host" || s == "~build2") + else if (s == "~host" || s == "~host-no-warnings" || + s == "~build2" || s == "~build2-no-warnings") { #ifdef BUILD2_BOOTSTRAP assert (false); #else - istringstream is (s[1] == 'h' ? host_config : build2_config); + istringstream is (s[1] == 'h' + ? (s.size () == 5 + ? host_config + : host_config_no_warnings) + : (s.size () == 7 + ? build2_config + : build2_config_no_warnings)); load_config (is, path_name (s), l); #endif } diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx index 5983e4b..150bf1a 100644 --- a/libbuild2/config/operation.cxx +++ b/libbuild2/config/operation.cxx @@ -759,6 +759,11 @@ namespace build2 lookup l (rs[*c_s]); if (l && (l.belongs (rs) || l.belongs (ctx.global_scope))) { + const path& f (cast<path> (l)); + + if (f.empty ()) + fail << "empty path in " << *c_s; + // While writing the complete configuration seems like a natural // default, there might be a desire to take inheritance into // account (if, say, we are exporting at multiple levels). One can @@ -766,8 +771,7 @@ namespace build2 // still want to support this mode somehow in the future (it seems // like an override of config.config.persist should do the trick). // - save_config ( - rs, cast<path> (l), false /* inherit */, mod, projects); + save_config (rs, f, false /* inherit */, mod, projects); } } } diff --git a/libbuild2/dist/operation.cxx b/libbuild2/dist/operation.cxx index cd88eac..cfc90cf 100644 --- a/libbuild2/dist/operation.cxx +++ b/libbuild2/dist/operation.cxx @@ -156,7 +156,7 @@ namespace build2 { const path& n (e.path ()); - if (n.string ()[0] != '.') + if (!n.empty () && n.string ().front () != '.') try { if (e.type () == entry_type::directory) // Can throw. diff --git a/libbuild2/dyndep.cxx b/libbuild2/dyndep.cxx index 68260fb..dbeb47e 100644 --- a/libbuild2/dyndep.cxx +++ b/libbuild2/dyndep.cxx @@ -442,7 +442,7 @@ namespace build2 // which case return the target that would have been inserted. // // The directory is only moved from if insert is true. Note that it must - // be normalized. + // be absolute and normalized. // auto find = [&trace, what, &bs, &t, &map_extension, @@ -632,7 +632,7 @@ namespace build2 // // While it may seem like there is not much difference, the caller may // actually do more than just issue more specific diagnostics. For - // example, if may defer the failure to the tool diagnostics. + // example, it may defer the failure to the tool diagnostics. // #if 0 r = &search (t, *tts[0], d, out, n, &e, s); @@ -642,7 +642,11 @@ namespace build2 r = pk.tk.type->search (ctx, &t, pk); if (r == nullptr && pk.tk.out->empty ()) - r = &create_new_target (ctx, pk); + { + auto p (ctx.scopes.find (d, false)); + if (*p.first != nullptr || ++p.first == p.second) + r = &create_new_target (ctx, pk); + } #endif } diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx index c0957ad..18147a2 100644 --- a/libbuild2/file.cxx +++ b/libbuild2/file.cxx @@ -859,6 +859,13 @@ namespace build2 for (const dir_entry& de: dir_iterator (d, dir_iterator::detect_dangling)) { + const path& n (de.path ()); + + // Skip hidden entries. + // + if (n.empty () || n.string ().front () == '.') + continue; + if (de.type () != entry_type::directory) { if (de.type () == entry_type::unknown) @@ -867,13 +874,13 @@ namespace build2 warn << "skipping " << (sl ? "dangling symlink" : "inaccessible entry") << ' ' - << d / de.path (); + << d / n; } continue; } - dir_path sd (d / path_cast<dir_path> (de.path ())); + dir_path sd (d / path_cast<dir_path> (n)); bool src (false); optional<bool> altn; diff --git a/libbuild2/functions-regex.cxx b/libbuild2/functions-regex.cxx index a7fcf55..cf3ffd0 100644 --- a/libbuild2/functions-regex.cxx +++ b/libbuild2/functions-regex.cxx @@ -775,6 +775,8 @@ namespace build2 // If both `format_first_only` and `format_no_copy` flags are specified // then the result will only contain the replacement of the first match. // + // See also `$string.replace()`. + // f[".replace"] += [](value v, string re, string fmt, optional<names> flags) { return replace (move (v), re, fmt, move (flags)); diff --git a/libbuild2/functions-string.cxx b/libbuild2/functions-string.cxx index 367923f..b7e0a17 100644 --- a/libbuild2/functions-string.cxx +++ b/libbuild2/functions-string.cxx @@ -8,6 +8,103 @@ using namespace std; namespace build2 { + static string + replace (string&& s, value&& fv, value&& tv, optional<names>&& fs) + { + bool ic (false), fo (false), lo (false); + if (fs) + { + for (name& f: *fs) + { + string s (convert<string> (move (f))); + + if (s == "icase") + ic = true; + else if (s == "first_only") + fo = true; + else if (s == "last_only") + lo = true; + else + throw invalid_argument ("invalid flag '" + s + '\''); + } + } + + string f (convert<string> (move (fv))); + string t (convert<string> (move (tv))); + + if (f.empty ()) + throw invalid_argument ("empty <from> substring"); + + if (!s.empty ()) + { + // Note that we don't cache s.size () since the string size will be + // changing as we are replacing. In fact, we may end up with an empty + // string after a replacement. + + size_t fn (f.size ()); + + // Look for the substring forward in the [p, n) range. + // + auto find = [&s, &f, fn, ic] (size_t p) -> size_t + { + for (size_t n (s.size ()); p != n; ++p) + { + if (n - p >= fn && + (ic + ? icasecmp (f, s.c_str () + p, fn) + : s.compare (p, fn, f)) == 0) + return p; + } + + return string::npos; + }; + + // Look for the substring backard in the [0, n) range. + // + auto rfind = [&s, &f, fn, ic] (size_t n) -> size_t + { + if (n >= fn) + { + n -= fn; // Don't consider characters out of range. + + for (size_t p (n);; ) + { + if ((ic + ? icasecmp (f, s.c_str () + p, fn) + : s.compare (p, fn, f)) == 0) + return p; + + if (--p == 0) + break; + } + } + + return string::npos; + }; + + if (fo || lo) + { + size_t p (lo ? rfind (s.size ()) : find (0)); + + if (fo && lo && p != string::npos) + { + if (p != find (0)) + p = string::npos; + } + + if (p != string::npos) + s.replace (p, fn, t); + } + else + { + for (size_t p (0); (p = find (0)) != string::npos; p += fn) + s.replace (p, fn, t); + } + } + + return move (s); + } + static size_t find_index (const strings& vs, value&& v, optional<names>&& fs) { @@ -32,7 +129,7 @@ namespace build2 })); return i != vs.end () ? i - vs.begin () : vs.size (); - }; + } void string_functions (function_map& m) @@ -76,6 +173,39 @@ namespace build2 convert<string> (move (y))) == 0; }; + // $string.replace(<untyped>, <from>, <to> [, <flags>]) + // $replace(<string>, <from>, <to> [, <flags>]) + // + // Replace occurences of substring <from> with <to> in a string. The + // <from> substring must not be empty. + // + // The following flags are supported: + // + // icase - compare ignoring case + // + // first_only - only replace the first match + // + // last_only - only replace the last match + // + // + // If both `first_only` and `last_only` flags are specified, then <from> + // is replaced only if it occurs in the string once. + // + // See also `$regex.replace()`. + // + f["replace"] += [](string s, value f, value t, optional<names> fs) + { + return replace (move (s), move (f), move (t), move (fs)); + }; + + f[".replace"] += [](names s, value f, value t, optional<names> fs) + { + return names { + name ( + replace ( + convert<string> (move (s)), move (f), move (t), move (fs)))}; + }; + // $string.trim(<untyped>) // $trim(<string>) // diff --git a/libbuild2/scope.cxx b/libbuild2/scope.cxx index 6ed7bab..23781a8 100644 --- a/libbuild2/scope.cxx +++ b/libbuild2/scope.cxx @@ -1205,8 +1205,8 @@ namespace build2 } auto scope_map:: - find (const dir_path& k) const -> pair<scopes::const_iterator, - scopes::const_iterator> + find (const dir_path& k, bool sno) const -> pair<scopes::const_iterator, + scopes::const_iterator> { assert (k.normalized (false)); auto i (map_.find_sup (k)); @@ -1215,9 +1215,9 @@ namespace build2 auto b (i->second.begin ()); auto e (i->second.end ()); - // Skip NULL first element. + // Skip NULL first element if requested. // - if (*b == nullptr) + if (sno && *b == nullptr) ++b; assert (b != e); diff --git a/libbuild2/scope.hxx b/libbuild2/scope.hxx index 968727b..09d61e9 100644 --- a/libbuild2/scope.hxx +++ b/libbuild2/scope.hxx @@ -793,6 +793,8 @@ namespace build2 // The first element, if not NULL, is for the "owning" out path. The rest // of the elements are for the src path shallow references. // + // Note that the global scope is in the first element. + // struct scopes: small_vector<scope*, 3> { scopes () = default; @@ -832,6 +834,10 @@ namespace build2 // Find all the scopes that encompass this path (out or src). // + // If skip_null_out is false, then the first element always corresponds to + // the out scope and is NULL if there is none (see struct scopes above for + // details). + // // Note that the returned range will never be empty (there is always the // global scope). // @@ -864,7 +870,7 @@ namespace build2 // "island append" restriction we have on loading additional buildfile. // LIBBUILD2_SYMEXPORT pair<scopes::const_iterator, scopes::const_iterator> - find (const dir_path&) const; + find (const dir_path&, bool skip_null_out = true) const; const_iterator begin () const {return map_.begin ();} const_iterator end () const {return map_.end ();} diff --git a/libbuild2/script/parser.cxx b/libbuild2/script/parser.cxx index ae6da76..84d2afc 100644 --- a/libbuild2/script/parser.cxx +++ b/libbuild2/script/parser.cxx @@ -1134,9 +1134,10 @@ namespace build2 if (t.value == "env") { parsed_env r (parse_env_builtin (t, tt)); - c.cwd = move (r.cwd); - c.variables = move (r.variables); - c.timeout = r.timeout; + c.cwd = move (r.cwd); + c.variables = move (r.variables); + c.timeout = r.timeout; + c.timeout_success = r.timeout_success; env = true; } else if (t.value == "for") @@ -1601,6 +1602,10 @@ namespace build2 { r.timeout = chrono::seconds (*v); } + else if (o == "-s" || o == "--timeout-success") + { + r.timeout_success = true; + } else if (optional<dir_path> v = dir ("--cwd", "-c")) { r.cwd = move (*v); @@ -1615,6 +1620,9 @@ namespace build2 break; } + if (r.timeout_success && !r.timeout) + fail (l) << "env: -s|--timeout-success specified without -t|--timeout"; + // Parse arguments (variable sets). // for (; i != e; ++i) diff --git a/libbuild2/script/parser.hxx b/libbuild2/script/parser.hxx index cadf909..795ce4e 100644 --- a/libbuild2/script/parser.hxx +++ b/libbuild2/script/parser.hxx @@ -163,14 +163,15 @@ namespace build2 pre_parse_line_start (token&, token_type&, lexer_mode); // Parse the env pseudo-builtin arguments up to the program name. Return - // the program execution timeout, CWD, the list of the variables that - // should be unset ("name") and/or set ("name=value") in the command - // environment, and the token/type that starts the program name. Note - // that the variable unsets come first, if present. + // the program execution timeout and its success flag, CWD, the list of + // the variables that should be unset ("name") and/or set ("name=value") + // in the command environment, and the token/type that starts the + // program name. Note that the variable unsets come first, if present. // struct parsed_env { optional<duration> timeout; + bool timeout_success = false; optional<dir_path> cwd; environment_vars variables; }; diff --git a/libbuild2/script/run.cxx b/libbuild2/script/run.cxx index 6d73a7e..f8f98c1 100644 --- a/libbuild2/script/run.cxx +++ b/libbuild2/script/run.cxx @@ -2069,7 +2069,7 @@ namespace build2 if (c.timeout) { - deadline d (system_clock::now () + *c.timeout, false /* success */); + deadline d (system_clock::now () + *c.timeout, c.timeout_success); if (!dl || d < *dl) dl = d; } diff --git a/libbuild2/script/script.cxx b/libbuild2/script/script.cxx index 4a6ca33..b53fc23 100644 --- a/libbuild2/script/script.cxx +++ b/libbuild2/script/script.cxx @@ -425,9 +425,14 @@ namespace build2 // Timeout. // if (c.timeout) + { o << " -t " << chrono::duration_cast<chrono::seconds> (*c.timeout).count (); + if (c.timeout_success) + o << " -s"; + } + // CWD. // if (c.cwd) diff --git a/libbuild2/script/script.hxx b/libbuild2/script/script.hxx index cccad98..f5bd69a 100644 --- a/libbuild2/script/script.hxx +++ b/libbuild2/script/script.hxx @@ -331,9 +331,13 @@ namespace build2 process_path program; strings arguments; - optional<dir_path> cwd; // From env builtin. - environment_vars variables; // From env builtin. - optional<duration> timeout; // From env builtin. + + // These come from the env builtin. + // + optional<dir_path> cwd; + environment_vars variables; + optional<duration> timeout; + bool timeout_success = false; optional<redirect> in; optional<redirect> out; diff --git a/libbuild2/search.cxx b/libbuild2/search.cxx index dee2ae8..4e855e3 100644 --- a/libbuild2/search.cxx +++ b/libbuild2/search.cxx @@ -265,7 +265,24 @@ namespace build2 // dir_path d; if (tk.dir->absolute ()) + { d = *tk.dir; // Already normalized. + + // Even if out is empty, it may still be (only) in src. + // + // Note: issue diagnostics consistent with search() after skipping this + // function due to non-empty out. + // + // @@ PERF: we could first check if it's in pk.scope, which feels like + // the common case. Though this doesn't seem to affect + // performance in any noticeable way. + // + auto p (ctx.scopes.find (d, false)); // Note: never empty. + if (*p.first == nullptr && ++p.first != p.second) + { + fail << "no existing source file for prerequisite " << pk << endf; + } + } else { d = pk.scope->out_path (); @@ -313,7 +330,17 @@ namespace build2 // dir_path d; if (tk.dir->absolute ()) + { d = *tk.dir; // Already normalized. + + // As above. + // + auto p (ctx.scopes.find (d, false)); + if (*p.first == nullptr && ++p.first != p.second) + { + fail << "no existing source file for prerequisite " << pk << endf; + } + } else { d = pk.scope->out_path (); diff --git a/libbuild2/search.hxx b/libbuild2/search.hxx index e3b1442..198c65f 100644 --- a/libbuild2/search.hxx +++ b/libbuild2/search.hxx @@ -37,6 +37,8 @@ namespace build2 // Create a new target in this prerequisite's scope. // + // Fail if the target is in src directory. + // LIBBUILD2_SYMEXPORT const target& create_new_target (context&, const prerequisite_key&); |