aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2018-12-07 15:29:59 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2018-12-11 14:03:09 +0300
commitb740417add06a6df0dff65b60fbd92a8c8d95aab (patch)
treee07e9a6e7c2f39164ff8bba7b6ee933679bf99aa
parent1d977345398584ff774990cf436289ddd64d2b42 (diff)
Add support for build configuration class inheritance
-rw-r--r--libbbot/build-config.cxx109
-rw-r--r--libbbot/build-config.hxx12
-rw-r--r--libbbot/manifest.cxx4
-rw-r--r--libbbot/manifest.hxx2
-rw-r--r--tests/buildtab/driver.cxx22
-rw-r--r--tests/buildtab/testscript171
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 <libbbot/build-config.hxx>
+#include <map>
#include <string>
#include <cstddef> // size_t
-#include <utility> // move()
+#include <utility> // move(), make_pair()
#include <stdexcept> // invalid_argument
#include <libbutl/path.mxx>
@@ -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 <map>
#include <string>
#include <vector>
#include <iosfwd>
@@ -27,21 +28,26 @@ namespace bbot
std::string name; // Configuration name.
butl::target_triplet target;
std::vector<std::string> classes;
- std::vector<std::string> vars; // Note: quoting is preserved.
+ std::vector<std::string> args; // Note: quoting is preserved.
// Warning-detecting regular expressions. Note that quoting is preserved.
//
std::vector<std::string> warning_regexes;
};
- using build_configs = std::vector<build_config>;
+ struct build_configs: std::vector<build_config>
+ {
+ // A map of derived class names to their bases.
+ //
+ std::map<std::string, std::string> 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:
//
- // <machine-pattern> <config> <target> [<config-vars>] [<warning-regex>]
+ // <machine-pattern> <config> <target> <classes> [<config-arg>]* [<warning-regex>]*
//
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 >>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
+ $* <<EOI >>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 >>EOF
- windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 default
- EOF
-
- : multiple-classes
- :
- $* <<EOF >>EOF
- windows*-vc_14* windows-vc_14-32 i686-microsoft-win32-msvc14.0 "all default"
- EOF
}
: parse-errors
:
{
- : no-name
+ : name
:
- $* <<EOI 2>>EOE == 1
- windows*-vc_14*
- EOI
- cin:1:16: error: no configuration name found
- EOE
+ {
+ : none
+ :
+ $* <<EOI 2>>EOE == 1
+ windows*-vc_14*
+ EOI
+ cin:1:16: error: no configuration name found
+ EOE
- : no-target
- :
- $* <<EOI 2>>EOE == 1
- windows*-vc_14* windows-vc_14-32
- EOI
- cin:1:33: error: no target found
- EOE
+ : dup
+ :
+ $* <<EOI 2>'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
:
- $* <<EOI 2>>EOE == 1
- windows*-vc_14* windows-vc_14-32 microsoft
- EOI
- cin:1:34: error: missing cpu
- EOE
+ {
+ : none
+ :
+ $* <<EOI 2>>EOE == 1
+ windows*-vc_14* windows-vc_14-32
+ EOI
+ cin:1:33: error: no target found
+ EOE
- : invalid-class
- :
- $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>EOE == 1
+ windows*-vc_14* windows-vc_14-32 microsoft
+ EOI
+ cin:1:34: error: missing cpu
+ EOE
+ }
- : invalid-var
+ : class
:
{
- : unquoted
+ : invalid
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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
:
$* <<EOI 2>>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
+ :
+ $* <<EOI 2>>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
:
- $* <<EOI 2>'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
+ :
+ $* <<EOI 2>>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
+ }
+ }
}