aboutsummaryrefslogtreecommitdiff
path: root/libbuild2
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2')
-rw-r--r--libbuild2/adhoc-rule-cxx.cxx42
-rw-r--r--libbuild2/algorithm.cxx13
-rw-r--r--libbuild2/buildfile82
-rw-r--r--libbuild2/config/host-config.cxx.in3
-rw-r--r--libbuild2/config/init.cxx18
-rw-r--r--libbuild2/config/operation.cxx8
-rw-r--r--libbuild2/dist/operation.cxx2
-rw-r--r--libbuild2/dyndep.cxx10
-rw-r--r--libbuild2/file.cxx11
-rw-r--r--libbuild2/functions-regex.cxx2
-rw-r--r--libbuild2/functions-string.cxx132
-rw-r--r--libbuild2/scope.cxx8
-rw-r--r--libbuild2/scope.hxx8
-rw-r--r--libbuild2/script/parser.cxx14
-rw-r--r--libbuild2/script/parser.hxx9
-rw-r--r--libbuild2/script/run.cxx2
-rw-r--r--libbuild2/script/script.cxx5
-rw-r--r--libbuild2/script/script.hxx10
-rw-r--r--libbuild2/search.cxx27
-rw-r--r--libbuild2/search.hxx2
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&);