Improve compiler version detection
This patch adds a matcher for compiler versions in tool/util.py, to be
used in SConstruct. We now have a new `utils.CompilerInformation` class,
that can be created from the path of the compiler. It can then be
compared with a string describing the requirements:
compiler = util.CompilerInformat("/path/to/c++")
# Check the compiler is clang.
if compiler == 'clang':
# Check its version.
if compiler == 'clang-3.8':
# Valid only using clang version 3.8 exactly
if compiler >= 'clang-3.6':
# Add options that weren't supported in clang before 3.6
# Check the compiler is at least gcc 5:
if compiler >= 'gcc-5'
# Add new options
if compiler == 'gcc-4.8':
# Workaround gcc 4.8
Finally, this patch uses this to make sure warnings about the `override`
keyword are supported.
Change-Id: I5c779ba88d6b476b12b66a86b966efe280ae2f8d
diff --git a/tools/util.py b/tools/util.py
index e869b40..2c11213 100644
--- a/tools/util.py
+++ b/tools/util.py
@@ -24,7 +24,9 @@
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+from distutils.version import LooseVersion
import glob
+import operator
import os
import re
import shlex
@@ -72,30 +74,115 @@
def relrealpath(path, start=os.getcwd()):
return os.path.relpath(os.path.realpath(path), start)
-# Query the target architecture of the compiler. The 'target' architecture of
-# the compiler used to build VIXL is considered to be the 'host' architecture of
-# VIXL itself.
-def GetHostArch(cxx):
+# Query the compiler about its preprocessor directives and return all of them as
+# a dictionnary.
+def GetCompilerDirectives(cxx):
# Instruct the compiler to dump all its preprocessor macros.
dump = subprocess.Popen([cxx, '-E', '-dM', '-'], stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
out, _ = dump.communicate()
- macro_list = [
- # Extract the macro name.
- match.group(1)
+ return {
+ # Extract the macro name as key and value as element.
+ match.group(1): match.group(2)
for match in [
# Capture macro name.
- re.search('#define (.*?) 1', macro)
+ re.search('^#define (\S+?) (.+)$', macro)
for macro in out.split('\n')
]
# Filter out non-matches.
if match
- ]
- if "__x86_64__" in macro_list:
+ }
+
+# Query the target architecture of the compiler. The 'target' architecture of
+# the compiler used to build VIXL is considered to be the 'host' architecture of
+# VIXL itself.
+def GetHostArch(cxx):
+ directives = GetCompilerDirectives(cxx)
+ if "__x86_64__" in directives:
return "x86_64"
- elif "__arm__" in macro_list:
+ elif "__arm__" in directives:
return "aarch32"
- elif "__aarch64__" in macro_list:
+ elif "__aarch64__" in directives:
return "aarch64"
else:
raise Exception("Unsupported archtecture")
+
+# Class representing the compiler toolchain and version.
+class CompilerInformation(object):
+ def __init__(self, cxx):
+ directives = GetCompilerDirectives(cxx)
+ if '__llvm__' in directives:
+ major = int(directives['__clang_major__'])
+ minor = int(directives['__clang_minor__'])
+ self.compiler = 'clang'
+ self.version = '{}.{}'.format(major, minor)
+ elif '__GNUC__' in directives:
+ major = int(directives['__GNUC__'])
+ minor = int(directives['__GNUC_MINOR__'])
+ self.compiler = 'gcc'
+ self.version = '{}.{}'.format(major, minor)
+ else:
+ # Allow other compilers to be used for building VIXL. However, one would
+ # need to teach this class how to extract version information in order to
+ # make use of it.
+ self.compiler = None
+ self.version = None
+
+ def GetDescription(self):
+ return "{}-{}".format(self.compiler, self.version)
+
+ def __str__(self):
+ return self.GetDescription()
+
+ # Compare string descriptions with our object. The semantics are:
+ #
+ # - Equality
+ #
+ # If the description does not have a version number, then we compare the
+ # compiler names. For instance, "clang-3.6" is still equal to "clang" but of
+ # course is not to "gcc".
+ #
+ # - Ordering
+ #
+ # Asking whether a compiler is lower than another depends on the version
+ # number. What we are really asking here when using these operator is
+ # "Is my compiler in the allowed range?". So with this in mind, comparing
+ # two different compilers will always return false. If the compilers are the
+ # same, then the version numbers are compared. Of course, we cannot use
+ # ordering operators if no version number is provided.
+
+ def __eq__(self, description):
+ if description == self.GetDescription():
+ return True
+ else:
+ # The user may not have provided a version, let's see if it matches the
+ # compiler.
+ return self.compiler == description
+
+ def __ne__(self, description):
+ return not self.__eq__(description)
+
+ def __lt__(self, description):
+ return self.CompareVersion(operator.lt, description)
+
+ def __le__(self, description):
+ return self.CompareVersion(operator.le, description)
+
+ def __ge__(self, description):
+ return self.CompareVersion(operator.ge, description)
+
+ def __gt__(self, description):
+ return self.CompareVersion(operator.gt, description)
+
+ # Comparing the provided `description` string, in the form of
+ # "{compiler}-{major}.{minor}". The comparison is done using the provided
+ # `operator` argument.
+ def CompareVersion(self, operator, description):
+ match = re.search('^(\S+)-(.*?)$', description)
+ if not match:
+ raise Exception("A version number is required when comparing compilers")
+ compiler, version = match.group(1), match.group(2)
+ # The result is false if the compilers are different, otherwise compare the
+ # version numbers.
+ return self.compiler == compiler and \
+ operator(LooseVersion(self.version), LooseVersion(version))