aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/build/script
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2021-11-23 11:20:03 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2021-11-23 11:20:03 +0200
commitf5c7b962c4d3512f32df8c3bbd3370f846239b02 (patch)
tree2aff4e52f277ecac62ce1cb8bf302ffd0884666a /libbuild2/build/script
parentb5083221dad8084deb4a7949cb9fc487aa09e080 (diff)
WIP: completedyndep
Diffstat (limited to 'libbuild2/build/script')
-rw-r--r--libbuild2/build/script/builtin-options.cxx122
-rw-r--r--libbuild2/build/script/builtin-options.hxx128
-rw-r--r--libbuild2/build/script/builtin-options.ixx96
-rw-r--r--libbuild2/build/script/builtin.cli11
-rw-r--r--libbuild2/build/script/parser.cxx773
-rw-r--r--libbuild2/build/script/parser.hxx49
-rw-r--r--libbuild2/build/script/script.hxx2
-rw-r--r--libbuild2/build/script/types-parsers.cxx56
-rw-r--r--libbuild2/build/script/types-parsers.hxx49
9 files changed, 1013 insertions, 273 deletions
diff --git a/libbuild2/build/script/builtin-options.cxx b/libbuild2/build/script/builtin-options.cxx
index dfb8e62..cf99b12 100644
--- a/libbuild2/build/script/builtin-options.cxx
+++ b/libbuild2/build/script/builtin-options.cxx
@@ -6,6 +6,7 @@
// Begin prologue.
//
+#include <libbuild2/build/script/types-parsers.hxx>
//
// End prologue.
@@ -404,86 +405,77 @@ namespace build2
: file_ (),
file_specified_ (false),
format_ (),
- format_specified_ (false)
+ format_specified_ (false),
+ what_ (),
+ what_specified_ (false),
+ include_path_ (),
+ include_path_specified_ (false),
+ default_prereq_type_ (),
+ default_prereq_type_specified_ (false)
{
}
- depdb_dep_options::
- depdb_dep_options (int& argc,
- char** argv,
- bool erase,
- ::build2::build::script::cli::unknown_mode opt,
- ::build2::build::script::cli::unknown_mode arg)
- : file_ (),
- file_specified_ (false),
- format_ (),
- format_specified_ (false)
+ bool depdb_dep_options::
+ parse (int& argc,
+ char** argv,
+ bool erase,
+ ::build2::build::script::cli::unknown_mode opt,
+ ::build2::build::script::cli::unknown_mode arg)
{
::build2::build::script::cli::argv_scanner s (argc, argv, erase);
- _parse (s, opt, arg);
+ bool r = _parse (s, opt, arg);
+ return r;
}
- depdb_dep_options::
- depdb_dep_options (int start,
- int& argc,
- char** argv,
- bool erase,
- ::build2::build::script::cli::unknown_mode opt,
- ::build2::build::script::cli::unknown_mode arg)
- : file_ (),
- file_specified_ (false),
- format_ (),
- format_specified_ (false)
+ bool depdb_dep_options::
+ parse (int start,
+ int& argc,
+ char** argv,
+ bool erase,
+ ::build2::build::script::cli::unknown_mode opt,
+ ::build2::build::script::cli::unknown_mode arg)
{
::build2::build::script::cli::argv_scanner s (start, argc, argv, erase);
- _parse (s, opt, arg);
+ bool r = _parse (s, opt, arg);
+ return r;
}
- depdb_dep_options::
- depdb_dep_options (int& argc,
- char** argv,
- int& end,
- bool erase,
- ::build2::build::script::cli::unknown_mode opt,
- ::build2::build::script::cli::unknown_mode arg)
- : file_ (),
- file_specified_ (false),
- format_ (),
- format_specified_ (false)
+ bool depdb_dep_options::
+ parse (int& argc,
+ char** argv,
+ int& end,
+ bool erase,
+ ::build2::build::script::cli::unknown_mode opt,
+ ::build2::build::script::cli::unknown_mode arg)
{
::build2::build::script::cli::argv_scanner s (argc, argv, erase);
- _parse (s, opt, arg);
+ bool r = _parse (s, opt, arg);
end = s.end ();
+ return r;
}
- depdb_dep_options::
- depdb_dep_options (int start,
- int& argc,
- char** argv,
- int& end,
- bool erase,
- ::build2::build::script::cli::unknown_mode opt,
- ::build2::build::script::cli::unknown_mode arg)
- : file_ (),
- file_specified_ (false),
- format_ (),
- format_specified_ (false)
+ bool depdb_dep_options::
+ parse (int start,
+ int& argc,
+ char** argv,
+ int& end,
+ bool erase,
+ ::build2::build::script::cli::unknown_mode opt,
+ ::build2::build::script::cli::unknown_mode arg)
{
::build2::build::script::cli::argv_scanner s (start, argc, argv, erase);
- _parse (s, opt, arg);
+ bool r = _parse (s, opt, arg);
end = s.end ();
+ return r;
}
- depdb_dep_options::
- depdb_dep_options (::build2::build::script::cli::scanner& s,
- ::build2::build::script::cli::unknown_mode opt,
- ::build2::build::script::cli::unknown_mode arg)
- : file_ (),
- file_specified_ (false),
- format_ (),
- format_specified_ (false)
+ bool depdb_dep_options::
+ parse (::build2::build::script::cli::scanner& s,
+ ::build2::build::script::cli::unknown_mode opt,
+ ::build2::build::script::cli::unknown_mode arg)
{
- _parse (s, opt, arg);
+ bool r = _parse (s, opt, arg);
+ return r;
}
typedef
@@ -497,11 +489,23 @@ namespace build2
_cli_depdb_dep_options_map_init ()
{
_cli_depdb_dep_options_map_["--file"] =
- &::build2::build::script::cli::thunk< depdb_dep_options, string, &depdb_dep_options::file_,
+ &::build2::build::script::cli::thunk< depdb_dep_options, path, &depdb_dep_options::file_,
&depdb_dep_options::file_specified_ >;
_cli_depdb_dep_options_map_["--format"] =
&::build2::build::script::cli::thunk< depdb_dep_options, string, &depdb_dep_options::format_,
&depdb_dep_options::format_specified_ >;
+ _cli_depdb_dep_options_map_["--what"] =
+ &::build2::build::script::cli::thunk< depdb_dep_options, string, &depdb_dep_options::what_,
+ &depdb_dep_options::what_specified_ >;
+ _cli_depdb_dep_options_map_["--include-path"] =
+ &::build2::build::script::cli::thunk< depdb_dep_options, dir_paths, &depdb_dep_options::include_path_,
+ &depdb_dep_options::include_path_specified_ >;
+ _cli_depdb_dep_options_map_["-I"] =
+ &::build2::build::script::cli::thunk< depdb_dep_options, dir_paths, &depdb_dep_options::include_path_,
+ &depdb_dep_options::include_path_specified_ >;
+ _cli_depdb_dep_options_map_["--default-prereq-type"] =
+ &::build2::build::script::cli::thunk< depdb_dep_options, string, &depdb_dep_options::default_prereq_type_,
+ &depdb_dep_options::default_prereq_type_specified_ >;
}
};
diff --git a/libbuild2/build/script/builtin-options.hxx b/libbuild2/build/script/builtin-options.hxx
index ccfd54b..85d67b9 100644
--- a/libbuild2/build/script/builtin-options.hxx
+++ b/libbuild2/build/script/builtin-options.hxx
@@ -302,48 +302,55 @@ namespace build2
public:
depdb_dep_options ();
- depdb_dep_options (int& argc,
- char** argv,
- bool erase = false,
- ::build2::build::script::cli::unknown_mode option = ::build2::build::script::cli::unknown_mode::fail,
- ::build2::build::script::cli::unknown_mode argument = ::build2::build::script::cli::unknown_mode::stop);
-
- depdb_dep_options (int start,
- int& argc,
- char** argv,
- bool erase = false,
- ::build2::build::script::cli::unknown_mode option = ::build2::build::script::cli::unknown_mode::fail,
- ::build2::build::script::cli::unknown_mode argument = ::build2::build::script::cli::unknown_mode::stop);
-
- depdb_dep_options (int& argc,
- char** argv,
- int& end,
- bool erase = false,
- ::build2::build::script::cli::unknown_mode option = ::build2::build::script::cli::unknown_mode::fail,
- ::build2::build::script::cli::unknown_mode argument = ::build2::build::script::cli::unknown_mode::stop);
-
- depdb_dep_options (int start,
- int& argc,
- char** argv,
- int& end,
- bool erase = false,
- ::build2::build::script::cli::unknown_mode option = ::build2::build::script::cli::unknown_mode::fail,
- ::build2::build::script::cli::unknown_mode argument = ::build2::build::script::cli::unknown_mode::stop);
-
- depdb_dep_options (::build2::build::script::cli::scanner&,
- ::build2::build::script::cli::unknown_mode option = ::build2::build::script::cli::unknown_mode::fail,
- ::build2::build::script::cli::unknown_mode argument = ::build2::build::script::cli::unknown_mode::stop);
+ // Return true if anything has been parsed.
+ //
+ bool
+ parse (int& argc,
+ char** argv,
+ bool erase = false,
+ ::build2::build::script::cli::unknown_mode option = ::build2::build::script::cli::unknown_mode::fail,
+ ::build2::build::script::cli::unknown_mode argument = ::build2::build::script::cli::unknown_mode::stop);
+
+ bool
+ parse (int start,
+ int& argc,
+ char** argv,
+ bool erase = false,
+ ::build2::build::script::cli::unknown_mode option = ::build2::build::script::cli::unknown_mode::fail,
+ ::build2::build::script::cli::unknown_mode argument = ::build2::build::script::cli::unknown_mode::stop);
+
+ bool
+ parse (int& argc,
+ char** argv,
+ int& end,
+ bool erase = false,
+ ::build2::build::script::cli::unknown_mode option = ::build2::build::script::cli::unknown_mode::fail,
+ ::build2::build::script::cli::unknown_mode argument = ::build2::build::script::cli::unknown_mode::stop);
+
+ bool
+ parse (int start,
+ int& argc,
+ char** argv,
+ int& end,
+ bool erase = false,
+ ::build2::build::script::cli::unknown_mode option = ::build2::build::script::cli::unknown_mode::fail,
+ ::build2::build::script::cli::unknown_mode argument = ::build2::build::script::cli::unknown_mode::stop);
+
+ bool
+ parse (::build2::build::script::cli::scanner&,
+ ::build2::build::script::cli::unknown_mode option = ::build2::build::script::cli::unknown_mode::fail,
+ ::build2::build::script::cli::unknown_mode argument = ::build2::build::script::cli::unknown_mode::stop);
// Option accessors and modifiers.
//
- const string&
+ const path&
file () const;
- string&
+ path&
file ();
void
- file (const string&);
+ file (const path&);
bool
file_specified () const;
@@ -366,6 +373,51 @@ namespace build2
void
format_specified (bool);
+ const string&
+ what () const;
+
+ string&
+ what ();
+
+ void
+ what (const string&);
+
+ bool
+ what_specified () const;
+
+ void
+ what_specified (bool);
+
+ const dir_paths&
+ include_path () const;
+
+ dir_paths&
+ include_path ();
+
+ void
+ include_path (const dir_paths&);
+
+ bool
+ include_path_specified () const;
+
+ void
+ include_path_specified (bool);
+
+ const string&
+ default_prereq_type () const;
+
+ string&
+ default_prereq_type ();
+
+ void
+ default_prereq_type (const string&);
+
+ bool
+ default_prereq_type_specified () const;
+
+ void
+ default_prereq_type_specified (bool);
+
// Implementation details.
//
protected:
@@ -379,10 +431,16 @@ namespace build2
::build2::build::script::cli::unknown_mode argument);
public:
- string file_;
+ path file_;
bool file_specified_;
string format_;
bool format_specified_;
+ string what_;
+ bool what_specified_;
+ dir_paths include_path_;
+ bool include_path_specified_;
+ string default_prereq_type_;
+ bool default_prereq_type_specified_;
};
}
}
diff --git a/libbuild2/build/script/builtin-options.ixx b/libbuild2/build/script/builtin-options.ixx
index 3263803..06575c8 100644
--- a/libbuild2/build/script/builtin-options.ixx
+++ b/libbuild2/build/script/builtin-options.ixx
@@ -179,20 +179,20 @@ namespace build2
// depdb_dep_options
//
- inline const string& depdb_dep_options::
+ inline const path& depdb_dep_options::
file () const
{
return this->file_;
}
- inline string& depdb_dep_options::
+ inline path& depdb_dep_options::
file ()
{
return this->file_;
}
inline void depdb_dep_options::
- file (const string& x)
+ file (const path& x)
{
this->file_ = x;
}
@@ -238,6 +238,96 @@ namespace build2
{
this->format_specified_ = x;
}
+
+ inline const string& depdb_dep_options::
+ what () const
+ {
+ return this->what_;
+ }
+
+ inline string& depdb_dep_options::
+ what ()
+ {
+ return this->what_;
+ }
+
+ inline void depdb_dep_options::
+ what (const string& x)
+ {
+ this->what_ = x;
+ }
+
+ inline bool depdb_dep_options::
+ what_specified () const
+ {
+ return this->what_specified_;
+ }
+
+ inline void depdb_dep_options::
+ what_specified (bool x)
+ {
+ this->what_specified_ = x;
+ }
+
+ inline const dir_paths& depdb_dep_options::
+ include_path () const
+ {
+ return this->include_path_;
+ }
+
+ inline dir_paths& depdb_dep_options::
+ include_path ()
+ {
+ return this->include_path_;
+ }
+
+ inline void depdb_dep_options::
+ include_path (const dir_paths& x)
+ {
+ this->include_path_ = x;
+ }
+
+ inline bool depdb_dep_options::
+ include_path_specified () const
+ {
+ return this->include_path_specified_;
+ }
+
+ inline void depdb_dep_options::
+ include_path_specified (bool x)
+ {
+ this->include_path_specified_ = x;
+ }
+
+ inline const string& depdb_dep_options::
+ default_prereq_type () const
+ {
+ return this->default_prereq_type_;
+ }
+
+ inline string& depdb_dep_options::
+ default_prereq_type ()
+ {
+ return this->default_prereq_type_;
+ }
+
+ inline void depdb_dep_options::
+ default_prereq_type (const string& x)
+ {
+ this->default_prereq_type_ = x;
+ }
+
+ inline bool depdb_dep_options::
+ default_prereq_type_specified () const
+ {
+ return this->default_prereq_type_specified_;
+ }
+
+ inline void depdb_dep_options::
+ default_prereq_type_specified (bool x)
+ {
+ this->default_prereq_type_specified_ = x;
+ }
}
}
}
diff --git a/libbuild2/build/script/builtin.cli b/libbuild2/build/script/builtin.cli
index 6e66d67..3ed3659 100644
--- a/libbuild2/build/script/builtin.cli
+++ b/libbuild2/build/script/builtin.cli
@@ -17,8 +17,15 @@ namespace build2
//
class depdb_dep_options
{
- string --file; // @@ TMP path
- string --format;
+ // Note that --byproduct, if any, must be the first option and is
+ // handled ad hoc, kind of as a sub-command.
+ //
+ path --file; // Read from file rather than stdin.
+ string --format; // Dependency format: make (default).
+ string --what; // Dependency kind, e.g., "header".
+ dir_paths --include-path|-I; // Search paths for generated files.
+ string --default-prereq-type; // Default prerequisite type to use
+ // if none could be derived from ext.
};
}
}
diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx
index c6d7d49..67dbf69 100644
--- a/libbuild2/build/script/parser.cxx
+++ b/libbuild2/build/script/parser.cxx
@@ -3,11 +3,16 @@
#include <libbuild2/build/script/parser.hxx>
+#include <cstring> // strcmp()
+#include <sstream>
+
#include <libbutl/builtin.hxx>
#include <libbuild2/depdb.hxx>
+#include <libbuild2/dyndep.hxx>
#include <libbuild2/function.hxx>
#include <libbuild2/algorithm.hxx>
+#include <libbuild2/make-parser.hxx>
#include <libbuild2/script/run.hxx>
@@ -129,8 +134,8 @@ namespace build2
// Save the custom dependency change tracking lines, if present.
//
s.depdb_clear = depdb_clear_.has_value ();
- if (depdb_pre_dynamic_)
- s.depdb_pre_dynamic = depdb_pre_dynamic_->second;
+ if (depdb_dyndep_)
+ s.depdb_dyndep = depdb_dyndep_->second;
s.depdb_preamble = move (depdb_preamble_);
return s;
@@ -497,7 +502,7 @@ namespace build2
v != "hash" &&
v != "string" &&
v != "env" &&
- v != "pre-dynamic"))
+ v != "dyndep"))
{
fail (get_location (t))
<< "expected 'depdb' builtin command instead of " << t;
@@ -543,28 +548,31 @@ namespace build2
}
else
{
- // Verify depdb-pre-dynamic is last.
+ // Verify depdb-dyndep is last.
//
- if (v == "pre-dynamic")
+ if (v == "dyndep")
{
- // Note that for now we do not allow multiple pre-dynamic
- // calls. But we may wan to relax this later (though
- // alternating targets with prerequisites may be tricky --
- // maybe still only allow additional targets in the first
- // call).
+ // Note that for now we do not allow multiple dyndep calls.
+ // But we may wan to relax this later (though alternating
+ // targets with prerequisites in depdb may be tricky -- maybe
+ // still only allow additional targets in the first call).
//
- if (!depdb_pre_dynamic_)
- depdb_pre_dynamic_ = make_pair (l, depdb_preamble_.size ());
+ if (!depdb_dyndep_)
+ depdb_dyndep_ = make_pair (l, depdb_preamble_.size ());
else
- fail (l) << "multiple 'depdb pre-dynamic' calls" <<
- info (depdb_pre_dynamic_->first) << "previous call is here";
+ fail (l) << "multiple 'depdb dyndep' calls" <<
+ info (depdb_dyndep_->first) << "previous call is here";
+
+#if 0
+ if (peek () == type::word && peeked ().value == "--byproduct")
+ ;
+#endif
}
else
{
- if (depdb_pre_dynamic_)
- fail (l) << "'depdb " << v << "' after 'depdb pre-dynamic'" <<
- info (depdb_pre_dynamic_->first)
- << "'depdb pre-dynamic' call is here";
+ if (depdb_dyndep_)
+ fail (l) << "'depdb " << v << "' after 'depdb dyndep'" <<
+ info (depdb_dyndep_->first) << "'depdb dyndep' call is here";
}
// Move the script body to the end of the depdb preamble.
@@ -885,11 +893,11 @@ namespace build2
}
void parser::
- execute_body (const scope& bs,
+ execute_body (const scope& rs, const scope& bs,
environment& e, const script& s, runner& r,
bool enter, bool leave)
{
- pre_exec (*bs.root_scope (), bs, e, &s, &r);
+ pre_exec (rs, bs, e, &s, &r);
if (enter)
runner_->enter (e, s.start_loc);
@@ -919,10 +927,13 @@ namespace build2
}
void parser::
- exec_depdb_preamble (const scope& bs,
+ exec_depdb_preamble (action a, const scope& bs, const file& t,
environment& e, const script& s, runner& r,
lines_iterator begin, lines_iterator end,
- depdb& dd, bool* update, optional<timestamp> mt)
+ depdb& dd,
+ bool* update,
+ bool* deferred_failure,
+ optional<timestamp> mt)
{
tracer trace ("exec_depdb_preamble");
@@ -938,12 +949,20 @@ namespace build2
struct
{
tracer& trace;
+
+ action a;
+ const scope& bs;
+ const file& t;
+
environment& env;
const script& scr;
+
depdb& dd;
bool* update;
+ bool* deferred_failure;
optional<timestamp> mt;
- } data {trace, e, s, dd, update, mt};
+
+ } data {trace, a, bs, t, e, s, dd, update, deferred_failure, mt};
auto exec_cmd = [this, &data] (token& t,
build2::script::token_type& tt,
@@ -965,14 +984,17 @@ namespace build2
string cmd (move (t.value));
- if (cmd == "pre-dynamic")
+ if (cmd == "dyndep")
{
- exec_depdb_pre_dynamic (t, tt,
- li, ll,
- data.env.target,
- data.dd,
- *data.update,
- *data.mt);
+ // Note: cast is safe since this is always executed in apply().
+ //
+ exec_depdb_dyndep (t, tt,
+ li, ll,
+ data.a, data.bs, const_cast<file&> (data.t),
+ data.dd,
+ *data.update,
+ *data.deferred_failure,
+ *data.mt);
}
else
{
@@ -988,7 +1010,7 @@ namespace build2
l4 ([&] {
data.trace (ll)
<< "'depdb hash' argument change forcing update of "
- << data.env.target;});
+ << data.t;});
}
else if (cmd == "string")
{
@@ -1006,7 +1028,7 @@ namespace build2
l4 ([&] {
data.trace (ll)
<< "'depdb string' argument change forcing update of "
- << data.env.target;});
+ << data.t;});
}
else if (cmd == "env")
{
@@ -1031,7 +1053,7 @@ namespace build2
l4 ([&] {
data.trace (ll)
<< "'depdb env' environment change forcing update of "
- << data.env.target;});
+ << data.t;});
}
else
assert (false);
@@ -1157,15 +1179,12 @@ namespace build2
}
names parser::
- execute_special (const scope& bs,
+ execute_special (const scope& rs, const scope& bs,
environment& e,
const line& ln,
bool omit_builtin)
{
- pre_exec (*bs.root_scope (), bs,
- e,
- nullptr /* script */,
- nullptr /* runner */);
+ pre_exec (rs, bs, e, nullptr /* script */, nullptr /* runner */);
// Copy the tokens and start playing.
//
@@ -1182,113 +1201,415 @@ namespace build2
}
void parser::
- exec_depdb_pre_dynamic (token& t, build2::script::token_type& tt,
- size_t li, const location& ll,
- const target& tgt,
- depdb& dd,
- bool& update,
- timestamp /*mt*/)
+ exec_depdb_dyndep (token& lt, build2::script::token_type& ltt,
+ size_t li, const location& ll,
+ action a, const scope& bs, file& t,
+ depdb& dd,
+ bool& update,
+ bool& deferred_failure,
+ timestamp mt)
{
+ tracer trace ("exec_depdb_dyndep");
+
+ context& ctx (t.ctx);
+
// Similar approach to parse_env_builtin().
//
- next (t, tt); // Skip 'dep' command.
-
- // Note that an option name and value can belong to different name
- // chunks. That's why we parse the arguments in the chunking mode into
- // the list up to the `--` separator and parse this list into options
- // afterwards. Note that the `--` separator should be omitted if there
- // is no program (i.e., additional dependency info is being read from
- // one of the prerequisites).
- //
- strings args;
+ depdb_dep_options ops;
bool prog (false);
-
- names ns; // Reuse to reduce allocations.
- while (tt != type::newline && tt != type::eos)
{
- if (tt == type::word && t.value == "--")
- {
- prog = true;
- break;
- }
-
- location l (get_location (t));
+ auto& t (lt);
+ auto& tt (ltt);
- if (!start_names (tt))
- fail (l) << "depdb pre-dynamic: expected option or '--' separator "
- << "instead of " << t;
+ next (t, tt); // Skip 'dep' command.
- parse_names (t, tt,
- ns,
- pattern_mode::ignore,
- true /* chunk */,
- "depdb pre-dynamic builtin argument",
- nullptr);
+ // Note that an option name and value can belong to different name
+ // chunks. That's why we parse the arguments in the chunking mode
+ // into the list up to the `--` separator and parse this list into
+ // options afterwards. Note that the `--` separator should be
+ // omitted if there is no program (i.e., additional dependency info
+ // is being read from one of the prerequisites).
+ //
+ strings args;
- for (name& n: ns)
+ names ns; // Reuse to reduce allocations.
+ while (tt != type::newline && tt != type::eos)
{
- try
+ if (tt == type::word && t.value == "--")
{
- args.push_back (convert<string> (move (n)));
+ prog = true;
+ break;
}
- catch (const invalid_argument&)
+
+ location l (get_location (t));
+
+ if (!start_names (tt))
+ fail (l) << "depdb dyndep: expected option or '--' separator "
+ << "instead of " << t;
+
+ parse_names (t, tt,
+ ns,
+ pattern_mode::ignore,
+ true /* chunk */,
+ "depdb dyndep builtin argument",
+ nullptr);
+
+ for (name& n: ns)
{
- diag_record dr (fail (l));
- dr << "invalid string value ";
- to_stream (dr.os, n, true /* quote */);
+ try
+ {
+ args.push_back (convert<string> (move (n)));
+ }
+ catch (const invalid_argument&)
+ {
+ diag_record dr (fail (l));
+ dr << "invalid string value ";
+ to_stream (dr.os, n, true /* quote */);
+ }
}
+
+ ns.clear ();
}
- ns.clear ();
- }
+ if (prog)
+ {
+ next (t, tt); // Skip '--'.
- if (prog)
- {
- next (t, tt); // Skip '--'.
+ if (tt == type::newline || tt == type::eos)
+ fail (t) << "depdb dyndep: expected program name instead of "
+ << t;
+ }
- if (tt == type::newline || tt == type::eos)
- fail (t) << "depdb pre-dynamic: expected program name instead of "
- << t;
+ // Parse the options.
+ //
+ // We would like to support both -I <dir> as well as -I<dir> forms
+ // for better compatibility. The latter requires manual parsing.
+ //
+ try
+ {
+ for (cli::vector_scanner scan (args); scan.more (); )
+ {
+ if (ops.parse (scan, cli::unknown_mode::stop) && !scan.more ())
+ break;
+
+ const char* a (scan.peek ());
+
+ // Handle -I<dir>
+ //
+ if (a[0] == '-' && a[1] == 'I')
+ {
+ try
+ {
+ ops.include_path ().push_back (dir_path (a + 2));
+ }
+ catch (const invalid_path&)
+ {
+ throw cli::invalid_value ("-I", a + 2);
+ }
+
+ scan.next ();
+ continue;
+ }
+
+#if 0
+ // Handle --byproduct in the wrong place.
+ //
+ if (strcmp (a, "--byproduct") == 0)
+ fail (ll) << "depdb dyndep: --byproduct must be first option";
+#endif
+
+ // Handle unknown option.
+ //
+ if (a[0] == '-')
+ throw cli::unknown_option (a);
+
+ // Handle unexpected argument.
+ //
+ fail (ll) << "depdb dyndep: unexpected argument '" << a << "'";
+ }
+ }
+ catch (const cli::exception& e)
+ {
+ fail (ll) << "depdb dyndep: " << e;
+ }
}
- // Parse the options.
+ // Get the default prerequisite type falling back to file{} if not
+ // specified.
//
- depdb_dep_options ops;
- try
+ // The reason one would want to specify it is to make sure different
+ // rules "resolve" the same dynamic prerequisites to the same targets.
+ // For example, a rule that implements custom C compilation for some
+ // translation unit would want to make sure it resolves extracted
+ // system headers to h{} targets analogous to the c module's rule.
+ //
+ const target_type* def_pt;
+ if (ops.default_prereq_type_specified ())
{
- cli::vector_scanner scan (args);
- ops = depdb_dep_options (scan);
+ const string& t (ops.default_prereq_type ());
- if (scan.more ())
- fail (ll) << "depdb pre-dynamic: unexpected argument '"
- << scan.next () << "'";
- }
- catch (const cli::exception& e)
- {
- fail (ll) << "depdb pre-dynamic: " << e;
+ def_pt = bs.find_target_type (t);
+ if (def_pt == nullptr)
+ fail (ll) << "unknown target type '" << t << "'";
}
+ else
+ def_pt = &file::static_type;
// This code is based on the prior work in the cc module (specifically
// extract_headers()) where you can often find more detailed rationale
// for some of the steps performed.
+ using dyndep = dyndep_rule;
+
+ // Build the maps lazily, only if/when needed.
+ //
+ using prefix_map = dyndep::prefix_map;
+ using srcout_map = dyndep::srcout_map;
+
+ function<dyndep::map_extension_func> map_ext (
+ [] (const scope& bs, const string& n, const string& e)
+ {
+ // @@ TODO: allow specifying base target types.
+ //
+ // Feels like the only reason one would want to specify base types
+ // is to tighten things up (as opposed to making some setup work)
+ // since it essentially restricts the set of registered target
+ // types that we will consider.
+ //
+ // Note also that these would be this project's target types while
+ // the file could be from another project.
+ //
+ return dyndep::map_extension (bs, n, e, nullptr);
+
+ // @@ TODO: should we return something as fallback (file{},
+ // def_pt)? Note: not the same semantics as enter_file()'s
+ // fallback. Feels like it could conceivably be different
+ // (e.g., h{} for fallback and hxx{} for some "unmappable" gen
+ // header). It looks like the "best" way currently is to define
+ // a custom target types for it (see moc{} in libQt5Core).
+ //
+ // Note also that we should only do this if bs is in our
+ // project.
+ });
+
+ // Don't we want to insert a "local"/prefixless mapping in case the
+ // user did not specify any -I's? But then will also need src-out
+ // remapping. So it will be equivalent to -I$out_base -I$src_base? But
+ // then it's not hard to add explicitly...
+ //
+ function<dyndep::prefix_map_func> pfx_map;
+
+ struct
+ {
+ tracer& trace;
+ const location& ll;
+ const depdb_dep_options& ops;
+ optional<prefix_map> map;
+ } pfx_data {trace, ll, ops, nullopt};
+
+ if (!ops.include_path ().empty ())
+ {
+ pfx_map = [this, &pfx_data] (action,
+ const scope& bs,
+ const target& t) -> const prefix_map&
+ {
+ if (!pfx_data.map)
+ {
+ pfx_data.map = prefix_map ();
+
+ const scope& rs (*bs.root_scope ());
+
+ for (dir_path d: pfx_data.ops.include_path ())
+ {
+ if (d.relative ())
+ fail (pfx_data.ll) << "depdb dyndep: relative include "
+ << "search path " << d;
+
+ if (!d.normalized (false /* canonical dir seperators */))
+ d.normalize ();
+
+ // If we are not inside our project root, then ignore.
+ //
+ if (d.sub (rs.out_path ()))
+ dyndep::append_prefix (
+ pfx_data.trace, *pfx_data.map, t, move (d));
+ }
+ }
+
+ return *pfx_data.map;
+ };
+ }
+
+ optional<path> file;
+ enum class format {make} fmt (format::make);
+ command_expr cmd;
+ srcout_map so_map;
+
+ // Parse the remainder of the command line as a program (which can be
+ // a pipe). If file is absent, then we save the command's stdout to a
+ // pipe. Otherwise, assume the command writes to file and add it to
+ // the cleanups.
+ //
+ // Note that MSVC /showInclude sends its output to stderr (and so
+ // could do other broken tools). However, the user can always merge
+ // stderr to stdout (2>&1).
+ //
+ auto init_run = [this, &ctx,
+ &lt, &ltt, &ll,
+ &ops, prog, &file, &cmd, &so_map] ()
+ {
+ // --format
+ //
+ if (ops.format_specified ())
+ {
+ const string& f (ops.format ());
+
+ if (f != "make")
+ fail (ll) << "depdb dyndep: invalid --format option value '"
+ << f << "'";
+ }
+
+ // --file
+ //
+ if (ops.file_specified ())
+ {
+ file = move (ops.file ());
+
+ if (file->relative ())
+ fail (ll) << "depdb dyndep: relative path specified with --file";
+ }
+
+ // Populate the srcout map with the -I$out_base -I$src_base pairs.
+ //
+ {
+ dyndep::srcout_builder builder (ctx, so_map);
+
+ for (dir_path d: ops.include_path ())
+ builder.next (move (d));
+ }
+
+ if (prog)
+ {
+ cmd = parse_command_line (lt, static_cast<token_type&> (ltt));
+
+ // If the output goes to stdout, then this should be a single
+ // pipeline without any logical operators (&& or ||).
+ //
+ if (!file && cmd.size () != 1)
+ fail (ll) << "depdb dyndep: command with stdout output cannot "
+ << "contain logical operators";
+
+ // Note that we may need to run this command multiple times. The
+ // two potential issues here are the re-registration of the
+ // clenups and re-use of the special files (stdin, stdout, etc;
+ // they include the line index in their names to avoid clashes
+ // between lines).
+ //
+ // Cleanups are not an issue, they will simply replaced. And
+ // overriding the contents of the special files seems harmless and
+ // consistent with what would happen if the command redirects its
+ // output to a non-special file.
+ //
+ if (file)
+ environment_->clean (
+ {build2::script::cleanup_type::always, *file},
+ true /* implicit */);
+ }
+ else
+ {
+ // Assume file is one of the prerequisites.
+ //
+ if (!file)
+ fail (ll) << "depdb dyndep: program or --file expected";
+ }
+ };
+
+ // Enter as a target, update, and add to the list of prerequisite
+ // targets a file.
+ //
+ const char* what (ops.what_specified ()
+ ? ops.what ().c_str ()
+ : "file");
+
+ size_t skip_count (0);
+ auto add = [this, &trace, what,
+ a, &bs, &t,
+ &map_ext, def_pt, &pfx_map, &so_map,
+ &dd, &skip_count] (path fp,
+ bool cache,
+ timestamp mt) -> optional<bool>
+ {
+ context& ctx (t.ctx);
+
+ // We can only defer the failure if we will be running the recipe
+ // body.
+ //
+ auto fail = [this, what, &ctx] (const auto& f) -> optional<bool>
+ {
+ bool df (!ctx.match_only && !ctx.dry_run_option);
+
+ diag_record dr;
+ dr << error << what << ' ' << f << " not found and no rule to "
+ << "generate it";
+
+ if (df)
+ dr << info << "failure deferred to recipe body diagnostics";
+
+ if (verb < 4)
+ dr << info << "re-run with --verbose=4 for more information";
+
+ if (df)
+ return nullopt;
+ else
+ dr << endf;
+ };
+
+ if (const build2::file* ft = dyndep::enter_file (
+ trace, what,
+ a, bs, t,
+ move (fp), cache, false /* normalize */,
+ map_ext, *def_pt, pfx_map, so_map).first)
+ {
+ if (optional<bool> u = dyndep::inject_file (
+ trace, what,
+ a, t,
+ *ft, mt, false /* fail */))
+ {
+ if (!cache)
+ dd.expect (ft->path ());
+
+ skip_count++;
+ return *u;
+ }
+ else if (cache)
+ {
+ dd.write (); // Invalidate this line.
+ return true;
+ }
+ else
+ return fail (*ft);
+ }
+ else
+ return fail (fp);
+ };
+
// If things go wrong (and they often do in this area), give the user
// a bit extra context.
//
auto df = make_diag_frame (
- [this, &tgt](const diag_record& dr)
+ [this, &ll, &t] (const diag_record& dr)
{
if (verb != 0)
- dr << info << "while extracting dynamic dependencies for " << tgt;
+ dr << info (ll) << "while extracting dynamic dependencies for "
+ << t;
});
// If nothing so far has invalidated the dependency database, then try
// the cached data before running the program.
//
bool cache (!update);
- size_t skip_count (0);
- for (bool restart (true); restart; cache = false)
+ for (bool restart (true), first_run (true); restart; cache = false)
{
restart = false;
@@ -1314,62 +1635,212 @@ namespace build2
if (l->empty ()) // Done, nothing changed.
return;
+
+ if (optional<bool> r = add (path (move (*l)), true /*cache*/, mt))
+ {
+ restart = *r;
+
+ if (restart)
+ {
+ update = true;
+ l6 ([&]{trace << "restarting (cache)";});
+ break;
+ }
+ }
+ else
+ {
+ // Trigger rebuild and mark as expected to fail.
+ //
+ update = true;
+ deferred_failure = true;
+ return;
+ }
}
}
else
{
- }
- }
+ if (first_run)
+ {
+ init_run ();
+ first_run = false;
+ }
+ else if (!prog)
+ {
+ fail (ll) << "generated " << what << " without program to retry";
+ }
- optional<path> file;
- if (ops.file_specified ())
- {
- file = path (ops.file ()); // @@ TMP path
+ // Save the timestamp just before we run the command. If we depend
+ // on any file that has been updated since, then we should assume
+ // we have "seen" the old copy and restart.
+ //
+ timestamp rmt (prog ? system_clock::now () : mt);
- // @@ TODO: file must be absolute.
- }
+ // Run the command if any and reduce outputs to common istream.
+ //
+ // Note that the resulting stream should tolerate partial read.
+ //
+ // While reading the entire stdout into a string is not the most
+ // efficient way to do it, this does simplify things quite a bit,
+ // not least of which is not having to parse the output before
+ // knowing the program exist status.
+ //
+ istringstream iss;
+ if (prog)
+ {
+ string s;
+ build2::script::run (*environment_,
+ cmd,
+ li,
+ ll,
+ !file ? &s : nullptr);
+
+ if (!file)
+ {
+ iss.str (move (s));
+ iss.exceptions (istream::badbit);
+ }
+ }
- if (prog)
- {
- // Run the remainder of the command line as a program (which can be
- // a pipe). If file is absent, then redirect the command's stdout to
- // a pipe. Otherwise, assume the command writes to a file and add it
- // to the cleanups. @@ TODO: reword
- //
- // Note that MSVC /showInclude sends its output to stderr (and so
- // could do other broken tools). However, the user can always merge
- // stderr to stdout (2>&1).
- //
- command_expr ce (
- parse_command_line (t, static_cast<token_type&> (tt)));
+ ifdstream ifs (ifdstream::badbit);
+ if (file)
+ try
+ {
+ ifs.open (*file);
+ }
+ catch (const io_error& e)
+ {
+ fail (ll) << "unable to open file " << *file << ": " << e;
+ }
- // If the output goes to stdout, then this should be a single
- // pipeline without any logical operators (&& or ||).
- //
- // @@ TODO: improve diagnostics.
- //
- if (!file && ce.size () != 1)
- fail (ll) << "depdb pre-dynamic: command cannot contain "
- << "logical operators";
-
- string s;
- build2::script::run (*environment_,
- ce,
- li,
- ll,
- !file ? &s : nullptr);
-
- if (file)
- environment_->clean ({build2::script::cleanup_type::always, *file},
- true /* implicit */);
- }
- else
- {
- // Assume file is one of the prerequisites.
- //
- if (!file)
- fail (ll) << "depdb pre-dynamic: program or --file expected";
+ istream& is (file
+ ? static_cast<istream&> (ifs)
+ : static_cast<istream&> (iss));
+
+ const path_name& in (file
+ ? path_name (*file)
+ : path_name ("<stdin>"));
+
+ location il (in, 1);
+
+ // The way we parse things is format-specific.
+ //
+ size_t skip (skip_count);
+
+ switch (fmt)
+ {
+ case format::make:
+ {
+ using make_state = make_parser;
+ using make_type = make_parser::type;
+
+ make_parser make;
+
+ for (string l; !restart; ++il.line) // Reuse the buffer.
+ {
+ if (eof (getline (is, l)))
+ {
+ if (make.state != make_state::end)
+ fail (il) << "incomplete make dependency declaration";
+
+ break;
+ }
+
+ size_t pos (0);
+ do
+ {
+ pair<make_type, string> r;
+ {
+ auto df = make_diag_frame (
+ [this, &l] (const diag_record& dr)
+ {
+ if (verb != 0)
+ dr << info << "while parsing make dependency "
+ << "declaration line '" << l << "'";
+ });
+
+ r = make.next (l, pos, il, false /* strict */);
+ }
+
+ if (r.second.empty ())
+ continue;
+
+ // @@ TODO: what should we do about targets?
+ //
+ // Note that if we take GCC as an example, things are
+ // quite messed up: by default it ignores -o and just
+ // takes the source file name and replaces the extension
+ // with a platform-appropriate object file extension. One
+ // can specify a custom target (or even multiple targets)
+ // with -MT or with -MQ (quoting). Though MinGW GCC still
+ // does not quote `:` with -MQ. So in this case it's
+ // definitely easier for the user to ignore the targets
+ // and just specify everything in the buildfile.
+ //
+ // On the other hand, other tools are likely to produce
+ // more sensible output (except perhaps for quoting).
+ //
+ // @@ Maybe in the lax mode we should only recognize `:`
+ // if it's separated on at least one side?
+ //
+ // Alternatively, we could detect Windows drives in
+ // paths and "handle" them (I believe this is what GNU
+ // make does). Maybe we should have three formats:
+ // make-lax, make, make-strict?
+ //
+ if (r.first == make_type::target)
+ continue;
+
+ // Skip until where we left off.
+ //
+ if (skip != 0)
+ {
+ skip--;
+ continue;
+ }
+
+ if (optional<bool> u = add (path (move (r.second)),
+ false /* cache */,
+ rmt))
+ {
+ restart = *u;
+
+ if (restart)
+ {
+ update = true;
+ l6 ([&]{trace << "restarting";});
+ break;
+ }
+ }
+ else
+ {
+ // Trigger recompilation, mark as expected to fail, and
+ // bail out.
+ //
+ update = true;
+ deferred_failure = true;
+ break;
+ }
+ }
+ while (pos != l.size ());
+
+ if (make.state == make_state::end || deferred_failure)
+ break;
+ }
+
+ break;
+ }
+ }
+
+ // Bail out early if we have deferred a failure.
+ //
+ if (deferred_failure)
+ return;
+ }
}
+
+ // Add the terminating blank line (we are updating depdb).
+ //
+ dd.expect ("");
}
// When add a special variable don't forget to update lexer::word().
diff --git a/libbuild2/build/script/parser.hxx b/libbuild2/build/script/parser.hxx
index f957f82..da15509 100644
--- a/libbuild2/build/script/parser.hxx
+++ b/libbuild2/build/script/parser.hxx
@@ -81,13 +81,16 @@ namespace build2
// initialize/clean up the environment before/after the script
// execution.
//
+ // Note: having both root and base scopes for testing (where we pass
+ // global scope for both).
+ //
void
- execute_body (const scope& base,
+ execute_body (const scope& root, const scope& base,
environment&, const script&, runner&,
bool enter = true, bool leave = true);
- // Execute the first or the second (pre-dynamic) half of the depdb
+ // Execute the first or the second (dyndep) half of the depdb
// preamble.
//
// Note that it's the caller's responsibility to make sure that the
@@ -95,33 +98,33 @@ namespace build2
// command execution and leave() -- after the last command.
//
void
- execute_depdb_preamble (const scope& base,
+ execute_depdb_preamble (action a, const scope& base, const file& t,
environment& e, const script& s, runner& r,
depdb& dd)
{
auto b (s.depdb_preamble.begin ());
exec_depdb_preamble (
- base,
+ a, base, t,
e, s, r,
b,
- (s.depdb_pre_dynamic
- ? b + *s.depdb_pre_dynamic
+ (s.depdb_dyndep
+ ? b + *s.depdb_dyndep
: s.depdb_preamble.end ()),
dd);
}
void
- execute_depdb_preamble_dynamic (
- const scope& base,
+ execute_depdb_preamble_dyndep (
+ action a, const scope& base, file& t,
environment& e, const script& s, runner& r,
- depdb& dd, bool& update, timestamp mt)
+ depdb& dd, bool& update, bool& deferred_failure, timestamp mt)
{
exec_depdb_preamble (
- base,
+ a, base, t,
e, s, r,
- s.depdb_preamble.begin () + *s.depdb_pre_dynamic,
+ s.depdb_preamble.begin () + *s.depdb_dyndep,
s.depdb_preamble.end (),
- dd, &update, mt);
+ dd, &update, &deferred_failure, mt);
}
// Parse a special builtin line into names, performing the variable
@@ -129,7 +132,7 @@ namespace build2
// builtin name from the result.
//
names
- execute_special (const scope& base,
+ execute_special (const scope& root, const scope& base,
environment&,
const line&,
bool omit_builtin = true);
@@ -157,20 +160,22 @@ namespace build2
exec_special (token&, build2::script::token_type&, bool skip_first);
void
- exec_depdb_preamble (const scope& base,
+ exec_depdb_preamble (action, const scope& base, const file&,
environment&, const script&, runner&,
lines_iterator begin, lines_iterator end,
depdb&,
bool* update = nullptr,
+ bool* deferred_failure = nullptr,
optional<timestamp> mt = nullopt);
void
- exec_depdb_pre_dynamic (token&, build2::script::token_type&,
- size_t line_index, const location&,
- const target&,
- depdb&,
- bool& update,
- timestamp);
+ exec_depdb_dyndep (token&, build2::script::token_type&,
+ size_t line_index, const location&,
+ action, const scope& base, file&,
+ depdb&,
+ bool& update,
+ bool& deferred_failure,
+ timestamp);
// Helpers.
//
@@ -269,13 +274,13 @@ namespace build2
// depdb env <var-names> - Track the environment variables change as a
// hash.
//
- // depdb pre-dynamic ... - Extract dynamic dependency information.
+ // depdb dyndep ... - Extract dynamic dependency information.
// Can only be the last depdb builtin call
// in the preamble.
//
optional<location> depdb_clear_; // depdb-clear location.
optional<pair<location, size_t>>
- depdb_pre_dynamic_; // depdb-pre-dynamic location.
+ depdb_dyndep_; // depdb-dyndep location/position.
lines depdb_preamble_; // Note: excluding depdb-clear.
// If present, the first impure function called in the body of the
diff --git a/libbuild2/build/script/script.hxx b/libbuild2/build/script/script.hxx
index bdfbe51..9d7567c 100644
--- a/libbuild2/build/script/script.hxx
+++ b/libbuild2/build/script/script.hxx
@@ -75,7 +75,7 @@ namespace build2
// script parser for details).
//
bool depdb_clear;
- optional<size_t> depdb_pre_dynamic; // Position of first pre-dynamic.
+ optional<size_t> depdb_dyndep; // Position of first depdb-dyndep.
lines_type depdb_preamble;
bool depdb_preamble_temp_dir = false; // True if refs $~.
diff --git a/libbuild2/build/script/types-parsers.cxx b/libbuild2/build/script/types-parsers.cxx
new file mode 100644
index 0000000..9ecfa13
--- /dev/null
+++ b/libbuild2/build/script/types-parsers.cxx
@@ -0,0 +1,56 @@
+// file : libbuild2/build/script/types-parsers.cxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/build/script/types-parsers.hxx>
+
+#include <libbuild2/build/script/builtin-options.hxx> // cli namespace
+
+namespace build2
+{
+ namespace build
+ {
+ namespace script
+ {
+ namespace cli
+ {
+ template <typename T>
+ static void
+ parse_path (T& x, scanner& s)
+ {
+ const char* o (s.next ());
+
+ if (!s.more ())
+ throw missing_value (o);
+
+ const char* v (s.next ());
+
+ try
+ {
+ x = T (v);
+
+ if (x.empty ())
+ throw invalid_value (o, v);
+ }
+ catch (const invalid_path&)
+ {
+ throw invalid_value (o, v);
+ }
+ }
+
+ void parser<path>::
+ parse (path& x, bool& xs, scanner& s)
+ {
+ xs = true;
+ parse_path (x, s);
+ }
+
+ void parser<dir_path>::
+ parse (dir_path& x, bool& xs, scanner& s)
+ {
+ xs = true;
+ parse_path (x, s);
+ }
+ }
+ }
+ }
+}
diff --git a/libbuild2/build/script/types-parsers.hxx b/libbuild2/build/script/types-parsers.hxx
new file mode 100644
index 0000000..a42dab7
--- /dev/null
+++ b/libbuild2/build/script/types-parsers.hxx
@@ -0,0 +1,49 @@
+// file : libbuild2/build/script/types-parsers.hxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+// CLI parsers, included into the generated source files.
+//
+
+#ifndef LIBBUILD2_BUILD_SCRIPT_TYPES_PARSERS_HXX
+#define LIBBUILD2_BUILD_SCRIPT_TYPES_PARSERS_HXX
+
+#include <libbuild2/types.hxx>
+
+namespace build2
+{
+ namespace build
+ {
+ namespace script
+ {
+ namespace cli
+ {
+ class scanner;
+
+ template <typename T>
+ struct parser;
+
+ template <>
+ struct parser<path>
+ {
+ static void
+ parse (path&, bool&, scanner&);
+
+ static void
+ merge (path& b, const path& a) {b = a;}
+ };
+
+ template <>
+ struct parser<dir_path>
+ {
+ static void
+ parse (dir_path&, bool&, scanner&);
+
+ static void
+ merge (dir_path& b, const dir_path& a) {b = a;}
+ };
+ }
+ }
+ }
+}
+
+#endif // LIBBUILD2_BUILD_SCRIPT_TYPES_PARSERS_HXX