aboutsummaryrefslogtreecommitdiff
path: root/libbbot/build-target-config.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbbot/build-target-config.cxx')
-rw-r--r--libbbot/build-target-config.cxx264
1 files changed, 264 insertions, 0 deletions
diff --git a/libbbot/build-target-config.cxx b/libbbot/build-target-config.cxx
new file mode 100644
index 0000000..9e04a17
--- /dev/null
+++ b/libbbot/build-target-config.cxx
@@ -0,0 +1,264 @@
+// file : libbbot/build-target-config.cxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#include <libbbot/build-target-config.hxx>
+
+#include <map>
+#include <string>
+#include <cstddef> // size_t
+#include <utility> // move(), make_pair()
+#include <stdexcept> // invalid_argument
+
+#include <libbutl/path.hxx>
+#include <libbutl/fdstream.hxx>
+#include <libbutl/tab-parser.hxx>
+#include <libbutl/string-parser.hxx>
+
+#include <libbpkg/manifest.hxx> // build_class_term::validate_name()
+
+#include <libbbot/manifest.hxx> // task_manifest::check_config()
+
+using namespace std;
+using namespace butl;
+
+namespace bbot
+{
+ LIBBBOT_EXPORT build_target_configs
+ parse_buildtab (istream& is, const string& name)
+ {
+ build_target_configs r;
+ tab_parser parser (is, name);
+
+ r.classes.push_back ("all");
+ r.classes.push_back ("default");
+
+ tab_fields tl;
+ while (!(tl = parser.next ()).empty ())
+ {
+ size_t n (tl.size ()); // Fields count.
+ size_t i (0); // The field currently being processed.
+
+ // Throw tab_parsing for the field currently being processed. If i == n
+ // then we refer to the end-of-line column (presumably reporting a missed
+ // field).
+ //
+ auto bad_line = [&name, &tl, &i, n] (const string& d)
+ {
+ // Offset beyond the end-of-line is meaningless.
+ //
+ assert (i <= n);
+
+ throw tab_parsing (name,
+ tl.line,
+ i == n
+ ? tl.end_column
+ : tl[i].column,
+ d);
+ };
+
+ build_target_config config;
+ config.machine_pattern = move (tl[i++].value);
+
+ // If the machine pattern is a single dash character, then this is a
+ // placeholder entry. The only thing we are interested about it is the
+ // class inheritance information. Note that while all other information
+ // is discarded, the target configuration name and target must be
+ // present (can also be dashes), so the classes field can be determined
+ // and parsed.
+ //
+ bool placeholder (config.machine_pattern == "-");
+
+ // Target configuration name, target[/environment] and classes fields
+ // are the required ones.
+ //
+ if (i == n)
+ bad_line ("no target configuration name found");
+
+ config.name = move (tl[i].value);
+
+ if (++i == n)
+ bad_line ("no target found");
+
+ if (!placeholder)
+ try
+ {
+ const string& v (tl[i].value);
+
+ // Extract the environment name, if present.
+ //
+ size_t p (v.find ('/'));
+
+ if (p != string::npos)
+ {
+ string env (v, p + 1);
+
+ if (env.empty ())
+ bad_line ("empty environment");
+
+ config.environment = move (env);
+ }
+
+ // Parse the target triplet.
+ //
+ config.target = target_triplet (p != string::npos
+ ? string (v, 0, p)
+ : v);
+ }
+ catch (const invalid_argument& e)
+ {
+ bad_line (e.what ());
+ }
+
+ // Make sure the name/target combination is unique.
+ //
+ for (const auto& c: r)
+ {
+ if (c.name == config.name && c.target == config.target)
+ bad_line ("duplicate target configuration name/target");
+ }
+
+ if (++i == n)
+ bad_line ("no classes found");
+
+ // Parse a potentially quoted class list.
+ //
+ try
+ {
+ using namespace string_parser;
+
+ auto validate = [] (const string& c)
+ {
+ bpkg::build_class_term::validate_name (c);
+
+ if (c == "none")
+ throw invalid_argument ("class 'none' is reserved");
+ };
+
+ // We don't expect the class names be quotes as they cannot contain
+ // spaces.
+ //
+ for (string& c: parse_quoted (unquote (tl[i].value),
+ false /* unquote */))
+ {
+ string base;
+ size_t p (c.find (':'));
+
+ if (p != string::npos)
+ {
+ base = string (c, p + 1);
+ validate (base);
+
+ c.resize (p);
+ }
+
+ validate (c);
+
+ // Add the mapping of the derived class to its base.
+ //
+ // Note that it's not required for a base to also be registered in
+ // the map.
+ //
+ auto i (r.class_inheritance_map.insert (make_pair (c, base)));
+
+ // If the derived-to-base mapping is added, then verify that there
+ // is no inheritance cycle. Otherwise, verify that the base class is
+ // the same as for the existing mapping. Note that once the base
+ // class is specified it can be omitted for subsequent mentions of
+ // the derived class.
+ //
+ auto j (i.first);
+
+ if (i.second) // Added?
+ {
+ // Traverse through the class hierarchy up until a non-registered
+ // base (in particular an empty one) is encountered.
+ //
+ // Note: here we also handle the 'base of itself' case.
+ //
+ while (j != r.class_inheritance_map.end ())
+ {
+ const string& base (j->second);
+
+ if (base == c)
+ throw invalid_argument (
+ "inheritance cycle in '" + c + "' class inheritance");
+
+ j = r.class_inheritance_map.find (base);
+ }
+
+ if (c != "all" && c != "default")
+ r.classes.push_back (c);
+ }
+ else if (j->second != base && !base.empty ())
+ throw invalid_argument ('\'' + c + "' new base '" + base +
+ "' does not match existing '" +
+ j->second + '\'');
+
+ if (!placeholder)
+ config.classes.emplace_back (move (c));
+ }
+ }
+ catch (const invalid_argument& e)
+ {
+ bad_line (e.what ());
+ }
+
+ // We are done if this is a placeholder.
+ //
+ if (placeholder)
+ continue;
+
+ // Parse options, variables, and regexes.
+ //
+ try
+ {
+ for (++i; i < n; ++i)
+ {
+ string& v (tl[i].value);
+
+ if (v[0] == '~') // Regular expression.
+ {
+ string re (v, 1);
+ task_manifest::validate_regex (re);
+ config.warning_regexes.emplace_back (move (re));
+ }
+ else // Target configuration option or variable.
+ config.args.emplace_back (move (v));
+ }
+ }
+ catch (const invalid_argument& e)
+ {
+ bad_line (e.what ());
+ }
+
+ // Save the target configuration.
+ //
+ r.emplace_back (move (config));
+ }
+
+ // Erase entries for baseless classes (we were collecting them to make
+ // sure that the class inheritance is consistent across target
+ // configurations).
+ //
+ for (auto i (r.class_inheritance_map.begin ());
+ i != r.class_inheritance_map.end (); )
+ {
+ if (i->second.empty ())
+ i = r.class_inheritance_map.erase (i);
+ else
+ ++i;
+ }
+
+ return r;
+ }
+
+ build_target_configs
+ parse_buildtab (const path& p)
+ {
+ ifdstream ifs (p);
+ build_target_configs r (parse_buildtab (ifs, p.string ()));
+
+ ifs.close (); // Throws on failure.
+ return r;
+ }
+}