// file : libbbot/build-config.cxx -*- C++ -*- // copyright : Copyright (c) 2014-2019 Code Synthesis Ltd // 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_configs parse_buildtab (istream& is, const string& name) { build_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_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 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 == "-"); // Configuration name, target[/environment] and classes fields are the // required ones. // if (i == n) bad_line ("no configuration name found"); config.name = move (tl[i].value); // Make sure the name is unique. // for (const auto& c: r) { if (c.name == config.name) bad_line ("duplicate configuration name"); } 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 ()); } 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 // Configuration option or variable. config.args.emplace_back (move (v)); } } catch (const invalid_argument& e) { bad_line (e.what ()); } // Save the 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 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_configs parse_buildtab (const path& p) { ifdstream ifs (p); build_configs r (parse_buildtab (ifs, p.string ())); ifs.close (); // Throws on failure. return r; } }