From 40e4d161fa319a443c2598ddbc74b8ad31220c68 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Wed, 16 Nov 2022 22:14:53 +0300 Subject: Add support for package-config task manifest value --- libbbot/build-target-config.cxx | 264 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 libbbot/build-target-config.cxx (limited to 'libbbot/build-target-config.cxx') 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 + +#include +#include +#include // size_t +#include // move(), make_pair() +#include // invalid_argument + +#include +#include +#include +#include + +#include // build_class_term::validate_name() + +#include // 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; + } +} -- cgit v1.1