From b740417add06a6df0dff65b60fbd92a8c8d95aab Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Fri, 7 Dec 2018 15:29:59 +0300 Subject: Add support for build configuration class inheritance --- libbbot/build-config.cxx | 109 +++++++++++++++++++++++++---- libbbot/build-config.hxx | 12 +++- libbbot/manifest.cxx | 4 +- libbbot/manifest.hxx | 2 +- tests/buildtab/driver.cxx | 22 +++--- tests/buildtab/testscript | 171 ++++++++++++++++++++++++++++++++-------------- 6 files changed, 240 insertions(+), 80 deletions(-) diff --git a/libbbot/build-config.cxx b/libbbot/build-config.cxx index de9a71d..2e6dfbc 100644 --- a/libbbot/build-config.cxx +++ b/libbbot/build-config.cxx @@ -4,9 +4,10 @@ #include +#include #include #include // size_t -#include // move() +#include // move(), make_pair() #include // invalid_argument #include @@ -56,6 +57,14 @@ namespace bbot 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 and classes fields are the required ones. // if (i == n) @@ -74,6 +83,7 @@ namespace bbot if (++i == n) bad_line ("no target found"); + if (!placeholder) try { config.target = target_triplet (tl[i].value); @@ -90,21 +100,75 @@ namespace bbot // try { - // We don't expect the class names be quotes as they cannot contain - // spaces. - // using namespace string_parser; - config.classes = parse_quoted (unquote (tl[i].value), - false /* unquote */); - // Validate the class names. - // - for (const string& c: config.classes) + auto validate = [] (const string& c) { + bpkg::build_class_term::validate_name (c); + if (c == "none") throw invalid_argument ("class 'none' is reserved"); + }; - bpkg::build_class_term::validate_name (c); + // 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); + } + } + 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) @@ -112,6 +176,13 @@ namespace bbot 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) @@ -121,11 +192,11 @@ namespace bbot if (v[0] == '~') // Regular expression. { string re (v, 1); - task_manifest::check_regex (re); + task_manifest::validate_regex (re); config.warning_regexes.emplace_back (move (re)); } - else // Configuration variable. - config.vars.emplace_back (move (v)); + else // Configuration option or variable. + config.args.emplace_back (move (v)); } } catch (const invalid_argument& e) @@ -138,6 +209,18 @@ namespace bbot 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; } diff --git a/libbbot/build-config.hxx b/libbbot/build-config.hxx index 3f1c0cc..9396782 100644 --- a/libbbot/build-config.hxx +++ b/libbbot/build-config.hxx @@ -5,6 +5,7 @@ #ifndef LIBBBOT_BUILD_CONFIG_HXX #define LIBBBOT_BUILD_CONFIG_HXX +#include #include #include #include @@ -27,21 +28,26 @@ namespace bbot std::string name; // Configuration name. butl::target_triplet target; std::vector classes; - std::vector vars; // Note: quoting is preserved. + std::vector args; // Note: quoting is preserved. // Warning-detecting regular expressions. Note that quoting is preserved. // std::vector warning_regexes; }; - using build_configs = std::vector; + struct build_configs: std::vector + { + // A map of derived class names to their bases. + // + std::map class_inheritance_map; + }; // Parse buildtab stream or file. Throw tab_parsing on parsing error, // ios::failure on the underlying OS error. // // buildtab consists of lines in the following format: // - // [] [] + // []* []* // using butl::tab_parsing; diff --git a/libbbot/manifest.cxx b/libbbot/manifest.cxx index 2f70b75..f412538 100644 --- a/libbbot/manifest.cxx +++ b/libbbot/manifest.cxx @@ -561,7 +561,7 @@ namespace bbot if (!warning_regex.empty ()) bad_name ("task warning regex redefinition"); - warning_regex = parse_tab (v, check_regex, "warning regex"); + warning_regex = parse_tab (v, validate_regex, "warning regex"); if (warning_regex.empty ()) bad_value ("empty task warning regex"); @@ -673,7 +673,7 @@ namespace bbot } void task_manifest:: - check_regex (const string& s) + validate_regex (const string& s) { try { diff --git a/libbbot/manifest.hxx b/libbbot/manifest.hxx index 163c75e..de93def 100644 --- a/libbbot/manifest.hxx +++ b/libbbot/manifest.hxx @@ -171,7 +171,7 @@ namespace bbot // invalid_argument if that's not the case. // static void - check_regex (const std::string&); + validate_regex (const std::string&); }; class LIBBBOT_EXPORT task_response_manifest diff --git a/tests/buildtab/driver.cxx b/tests/buildtab/driver.cxx index 5242fe4..604aaa9 100644 --- a/tests/buildtab/driver.cxx +++ b/tests/buildtab/driver.cxx @@ -26,26 +26,32 @@ try cin.exceptions (ios::failbit | ios::badbit); cout.exceptions (ios::failbit | ios::badbit); - for (const build_config& c: parse_buildtab (cin, "cin")) + const build_configs& configs (parse_buildtab (cin, "cin")); + + for (const build_config& c: configs) { cout << c.machine_pattern << ' ' << c.name << ' ' << c.target; string classes; - for (const string& cs: c.classes) + for (const string& cls: c.classes) { if (!classes.empty ()) classes += ' '; - classes += cs; + classes += cls; + + auto i (configs.class_inheritance_map.find (cls)); + if (i != configs.class_inheritance_map.end ()) + classes += ':' + i->second; } - if (c.classes.size () > 1) - cout << " \"" << classes << '"'; - else + if (c.classes.size () == 1) cout << ' ' << classes; + else + cout << " \"" << classes << '"'; - for (const string& v: c.vars) - cout << ' ' << v; + for (const string& a: c.args) + cout << ' ' << a; for (const string& r: c.warning_regexes) cout << " ~" << r; diff --git a/tests/buildtab/testscript b/tests/buildtab/testscript index 511d284..e029d47 100644 --- a/tests/buildtab/testscript +++ b/tests/buildtab/testscript @@ -9,10 +9,24 @@ { : all-fileds-combinations : - $* <>EOF - windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 default - windows*-vc_14* windows-vc_14-32-debug i686-microsoft-win32-msvc14.0 default config.cc.coptions=/Z7 config.cc.loptions=/DEBUG ~"warning C4\d{3}: " - EOF + $* <>EOO + windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 all + windows*-vc_14* windows-vc_14-32-debug i686-microsoft-win32-msvc14.0 "" config.cc.coptions=/Z7 config.cc.loptions=/DEBUG ~"warning C4\d{3}: " + + linux_debian_8*-gcc_4.9 linux_debian_8-gcc_4.9 x86_64-linux-gnu "all default linux gcc gcc-4+:gcc-3+" + - - - gcc-5+:gcc-4+ + - - - gcc-6+:gcc-5+ + linux_debian_9*-gcc_7.3 linux_debian_9-gcc_7.3 x86_64-linux-gnu "all default linux gcc gcc-7+:gcc-6+" + linux_debian_9*-gcc_7.3 linux_debian_9-gcc_7.3-O3 x86_64-linux-gnu "all default linux gcc optimized gcc-7+" config.cc.coptions=-O3 + linux_debian_9*-gcc_8.1 linux_debian_9-gcc_8.1 x86_64-linux-gnu "all default linux gcc gcc-8+:gcc-7+" + EOI + windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 all + windows*-vc_14* windows-vc_14-32-debug i686-microsoft-win32-msvc14.0 "" config.cc.coptions=/Z7 config.cc.loptions=/DEBUG ~"warning C4\d{3}: " + linux_debian_8*-gcc_4.9 linux_debian_8-gcc_4.9 x86_64-linux-gnu "all default linux gcc gcc-4+:gcc-3+" + linux_debian_9*-gcc_7.3 linux_debian_9-gcc_7.3 x86_64-linux-gnu "all default linux gcc gcc-7+:gcc-6+" + linux_debian_9*-gcc_7.3 linux_debian_9-gcc_7.3-O3 x86_64-linux-gnu "all default linux gcc optimized gcc-7+:gcc-6+" config.cc.coptions=-O3 + linux_debian_9*-gcc_8.1 linux_debian_9-gcc_8.1 x86_64-linux-gnu "all default linux gcc gcc-8+:gcc-7+" + EOO : empty-lines : @@ -23,71 +37,122 @@ EOI windows*-vc_14* windows-vc_14-32-debug i686-microsoft-win32-msvc14.0 default EOO - - : single-class - : - $* <>EOF - windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 default - EOF - - : multiple-classes - : - $* <>EOF - windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 "all default" - EOF } : parse-errors : { - : no-name + : name : - $* <>EOE == 1 - windows*-vc_14* - EOI - cin:1:16: error: no configuration name found - EOE + { + : none + : + $* <>EOE == 1 + windows*-vc_14* + EOI + cin:1:16: error: no configuration name found + EOE - : no-target - : - $* <>EOE == 1 - windows*-vc_14* windows-vc_14-32 - EOI - cin:1:33: error: no target found - EOE + : dup + : + $* <'cin:2:17: error: duplicate configuration name' == 1 + windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 default + windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 default + EOI + } - : invalid-target + : target : - $* <>EOE == 1 - windows*-vc_14* windows-vc_14-32 microsoft - EOI - cin:1:34: error: missing cpu - EOE + { + : none + : + $* <>EOE == 1 + windows*-vc_14* windows-vc_14-32 + EOI + cin:1:33: error: no target found + EOE - : invalid-class - : - $* <>EOE == 1 - windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 a=b - EOI - cin:1:64: error: class name 'a=b' contains '=' - EOE + : invalid + : + $* <>EOE == 1 + windows*-vc_14* windows-vc_14-32 microsoft + EOI + cin:1:34: error: missing cpu + EOE + } - : invalid-var + : class : { - : unquoted + : invalid + : + $* <>EOE == 1 + windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 a=b + EOI + cin:1:64: error: class name 'a=b' contains '=' + EOE + + : none + : + $* <>EOE == 1 + windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 none + EOI + cin:1:64: error: class 'none' is reserved + EOE + + : self-inheritance + : + $* <>EOE == 1 + windows*-vc_14* windows-vc_14-O i686-microsoft-win32-msvc14.0 "msvc-14+:msvc-14+" + EOI + cin:1:63: error: inheritance cycle in 'msvc-14+' class inheritance + EOE + + : inheritance-cycle + : + $* <>EOE == 1 + windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 msvc-14+:msvc-13+ + windows*-vc_13* windows-vc_13-32 i686-microsoft-win32-msvc13.0 msvc-13+:msvc-14+ + EOI + cin:2:64: error: inheritance cycle in 'msvc-13+' class inheritance + EOE + + : base-mismatch : $* <>EOE == 1 - windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 default config.cc.coptions="/Z7 - EOI - cin:1:95: error: unterminated quoted string - EOE + windows*-vc_12* windows-vc_12 i686-microsoft-win32-msvc12.0 msvc-12+ + windows*-vc_13* windows-vc_13 i686-microsoft-win32-msvc13.0 msvc-13+:msvc-12+ + windows*-vc_14* windows-vc_14 i686-microsoft-win32-msvc14.0 msvc-14+:msvc-13+ + windows*-vc_14* windows-vc_14-O i686-microsoft-win32-msvc14.0 "msvc-14+:msvc-12+ optimized" + EOI + cin:4:63: error: 'msvc-14+' new base 'msvc-12+' does not match existing 'msvc-13+' + EOE + + : base-mismatch-nobase + : + $* <>EOE == 1 + windows*-vc_12* windows-vc_12 i686-microsoft-win32-msvc12.0 msvc-12+ + windows*-vc_13* windows-vc_13 i686-microsoft-win32-msvc13.0 msvc-13+:msvc-12+ + windows*-vc_14* windows-vc_14 i686-microsoft-win32-msvc14.0 msvc-14+ + windows*-vc_14* windows-vc_14-O i686-microsoft-win32-msvc14.0 "msvc-14+:msvc-13+ optimized" + EOI + cin:4:63: error: 'msvc-14+' new base 'msvc-13+' does not match existing '' + EOE } - : dup-config-name + : var : - $* <'cin:2:17: error: duplicate configuration name' == 1 - windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 default - windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 default - EOI + { + : invalid + : + { + : unquoted + : + $* <>EOE == 1 + windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 default config.cc.coptions="/Z7 + EOI + cin:1:95: error: unterminated quoted string + EOE + } + } } -- cgit v1.1