Bug 1270446 - Make it easier to derive compiler results. r=chmanchester
authorMike Hommey <mh+mozilla@glandium.org>
Fri, 22 Apr 2016 16:48:14 +0900
changeset 296362 c3573274d787f8477e3e7ee5f6287fb26da9f06a
parent 296361 cc14a8472950cbc481c9baefac973865e45391b2
child 296363 61bae26a46bc5d4faad4b30cea95ec06f0c1a58f
push id76310
push usermh@glandium.org
push dateFri, 06 May 2016 12:25:15 +0000
treeherdermozilla-inbound@61bae26a46bc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerschmanchester
bugs1270446
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1270446 - Make it easier to derive compiler results. r=chmanchester
python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py
python/mozbuild/mozbuild/test/configure/test_toolchain_helpers.py
python/mozbuild/mozbuild/util.py
--- a/python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py
@@ -10,17 +10,20 @@ import os
 from StringIO import StringIO
 
 from mozunit import main
 
 from common import BaseConfigureTest
 from mozbuild.configure.util import Version
 from mozbuild.util import memoize
 from mozpack import path as mozpath
-from test_toolchain_helpers import FakeCompiler
+from test_toolchain_helpers import (
+    FakeCompiler,
+    CompilerResult,
+)
 
 
 DEFAULT_C99 = {
     '__STDC_VERSION__': '199901L',
 }
 
 DEFAULT_C11 = {
     '__STDC_VERSION__': '201112L',
@@ -152,21 +155,18 @@ class BaseToolchainTest(BaseConfigureTes
     def do_toolchain_test(self, paths, results, args=[], environ={}):
         '''Helper to test the toolchain checks from toolchain.configure.
 
         - `paths` is a dict associating compiler paths to FakeCompiler
           definitions from above.
         - `results` is a dict associating result variable names from
           toolchain.configure (c_compiler, cxx_compiler, host_c_compiler,
           host_cxx_compiler) with a result.
-          The result can either be an error string, or a dict with the
-          following items: flags, version, type, compiler, wrapper. (wrapper
-          can be omitted when it's empty). Those items correspond to the
-          attributes of the object returned by toolchain.configure checks
-          and will be compared to them.
+          The result can either be an error string, or a CompilerResult
+          corresponding to the object returned by toolchain.configure checks.
           When the results for host_c_compiler are identical to c_compiler,
           they can be omitted. Likewise for host_cxx_compiler vs.
           cxx_compiler.
         '''
         environ = dict(environ)
         if 'PATH' not in environ:
             environ['PATH'] = os.pathsep.join(
                 mozpath.abspath(p) for p in ('/bin', '/usr/bin'))
@@ -177,26 +177,22 @@ class BaseToolchainTest(BaseConfigureTes
         for var in ('c_compiler', 'cxx_compiler', 'host_c_compiler',
                     'host_cxx_compiler'):
             if var in results:
                 result = results[var]
             elif var.startswith('host_'):
                 result = results.get(var[5:], {})
             else:
                 result = {}
-            if isinstance(result, dict):
-                result = dict(result)
-                result.setdefault('wrapper', [])
-                result['compiler'] = mozpath.abspath(result['compiler'])
             try:
                 self.out.truncate(0)
                 compiler = sandbox._value_for(sandbox[var])
                 # Add var on both ends to make it clear which of the
                 # variables is failing the test when that happens.
-                self.assertEquals((var, compiler.__dict__), (var, result))
+                self.assertEquals((var, compiler), (var, result))
             except SystemExit:
                 self.assertEquals((var, result),
                                   (var, self.out.getvalue().strip()))
                 return
 
 
 class LinuxToolchainTest(BaseToolchainTest):
     PATHS = {
@@ -211,59 +207,59 @@ class LinuxToolchainTest(BaseToolchainTe
         '/usr/bin/clang-3.6': CLANG_3_6,
         '/usr/bin/clang++-3.6': CLANGXX_3_6,
         '/usr/bin/clang-3.3': CLANG_3_3,
         '/usr/bin/clang++-3.3': CLANGXX_3_3,
     }
     GCC_4_7_RESULT = ('Only GCC 4.8 or newer is supported '
                       '(found version 4.7.3).')
     GXX_4_7_RESULT = GCC_4_7_RESULT
-    GCC_4_9_RESULT = {
-        'flags': ['-std=gnu99'],
-        'version': '4.9.3',
-        'type': 'gcc',
-        'compiler': '/usr/bin/gcc',
-    }
-    GXX_4_9_RESULT = {
-        'flags': ['-std=gnu++11'],
-        'version': '4.9.3',
-        'type': 'gcc',
-        'compiler': '/usr/bin/g++',
-    }
-    GCC_5_RESULT = {
-        'flags': ['-std=gnu99'],
-        'version': '5.2.1',
-        'type': 'gcc',
-        'compiler': '/usr/bin/gcc-5',
-    }
-    GXX_5_RESULT = {
-        'flags': ['-std=gnu++11'],
-        'version': '5.2.1',
-        'type': 'gcc',
-        'compiler': '/usr/bin/g++-5',
-    }
-    CLANG_3_3_RESULT = {
-        'flags': [],
-        'version': '3.3.0',
-        'type': 'clang',
-        'compiler': '/usr/bin/clang-3.3',
-    }
+    GCC_4_9_RESULT = CompilerResult(
+        flags=['-std=gnu99'],
+        version='4.9.3',
+        type='gcc',
+        compiler='/usr/bin/gcc',
+    )
+    GXX_4_9_RESULT = CompilerResult(
+        flags=['-std=gnu++11'],
+        version='4.9.3',
+        type='gcc',
+        compiler='/usr/bin/g++',
+    )
+    GCC_5_RESULT = CompilerResult(
+        flags=['-std=gnu99'],
+        version='5.2.1',
+        type='gcc',
+        compiler='/usr/bin/gcc-5',
+    )
+    GXX_5_RESULT = CompilerResult(
+        flags=['-std=gnu++11'],
+        version='5.2.1',
+        type='gcc',
+        compiler='/usr/bin/g++-5',
+    )
+    CLANG_3_3_RESULT = CompilerResult(
+        flags=[],
+        version='3.3.0',
+        type='clang',
+        compiler='/usr/bin/clang-3.3',
+    )
     CLANGXX_3_3_RESULT = 'Only clang/llvm 3.4 or newer is supported.'
-    CLANG_3_6_RESULT = {
-        'flags': ['-std=gnu99'],
-        'version': '3.6.2',
-        'type': 'clang',
-        'compiler': '/usr/bin/clang',
-    }
-    CLANGXX_3_6_RESULT = {
-        'flags': ['-std=gnu++11'],
-        'version': '3.6.2',
-        'type': 'clang',
-        'compiler': '/usr/bin/clang++',
-    }
+    CLANG_3_6_RESULT = CompilerResult(
+        flags=['-std=gnu99'],
+        version='3.6.2',
+        type='clang',
+        compiler='/usr/bin/clang',
+    )
+    CLANGXX_3_6_RESULT = CompilerResult(
+        flags=['-std=gnu++11'],
+        version='3.6.2',
+        type='clang',
+        compiler='/usr/bin/clang++',
+    )
 
     def test_gcc(self):
         # We'll try gcc and clang, and find gcc first.
         self.do_toolchain_test(self.PATHS, {
             'c_compiler': self.GCC_4_9_RESULT,
             'cxx_compiler': self.GXX_4_9_RESULT,
         })
 
@@ -369,23 +365,23 @@ class LinuxToolchainTest(BaseToolchainTe
         }
         self.do_toolchain_test(paths, {
             'c_compiler': self.CLANG_3_6_RESULT,
             'cxx_compiler': self.CLANGXX_3_6_RESULT,
         })
 
     def test_guess_cxx_clang(self):
         # When CXX is not set, we guess it from CC.
-        clang_3_6_result = dict(self.CLANG_3_6_RESULT)
-        clang_3_6_result['compiler'] = '/usr/bin/clang-3.6'
-        clangxx_3_6_result = dict(self.CLANGXX_3_6_RESULT)
-        clangxx_3_6_result['compiler'] = '/usr/bin/clang++-3.6'
         self.do_toolchain_test(self.PATHS, {
-            'c_compiler': clang_3_6_result,
-            'cxx_compiler': clangxx_3_6_result,
+            'c_compiler': self.CLANG_3_6_RESULT + {
+                'compiler': '/usr/bin/clang-3.6',
+            },
+            'cxx_compiler': self.CLANGXX_3_6_RESULT + {
+                'compiler': '/usr/bin/clang++-3.6',
+            },
         }, environ={
             'CC': 'clang-3.6',
         })
 
     def test_unsupported_clang(self):
         # clang 3.3 C compiler is perfectly fine, but we need more for C++.
         self.do_toolchain_test(self.PATHS, {
             'c_compiler': self.CLANG_3_3_RESULT,
@@ -407,46 +403,46 @@ class LinuxToolchainTest(BaseToolchainTe
         })
 
     def test_absolute_path(self):
         paths = dict(self.PATHS)
         paths.update({
             '/opt/clang/bin/clang': CLANG_3_6,
             '/opt/clang/bin/clang++': CLANGXX_3_6,
         })
-        absolute_clang = dict(self.CLANG_3_6_RESULT)
-        absolute_clang['compiler'] = '/opt/clang/bin/clang'
-        absolute_clangxx = dict(self.CLANGXX_3_6_RESULT)
-        absolute_clangxx['compiler'] = '/opt/clang/bin/clang++'
         result = {
-            'c_compiler': absolute_clang,
-            'cxx_compiler': absolute_clangxx,
+            'c_compiler': self.CLANG_3_6_RESULT + {
+                'compiler': '/opt/clang/bin/clang',
+            },
+            'cxx_compiler': self.CLANGXX_3_6_RESULT + {
+                'compiler': '/opt/clang/bin/clang++'
+            },
         }
         self.do_toolchain_test(paths, result, environ={
             'CC': '/opt/clang/bin/clang',
             'CXX': '/opt/clang/bin/clang++',
         })
         # With CXX guess too.
         self.do_toolchain_test(paths, result, environ={
             'CC': '/opt/clang/bin/clang',
         })
 
     def test_atypical_name(self):
         paths = dict(self.PATHS)
         paths.update({
             '/usr/bin/afl-clang-fast': CLANG_3_6,
             '/usr/bin/afl-clang-fast++': CLANGXX_3_6,
         })
-        afl_clang_result = dict(self.CLANG_3_6_RESULT)
-        afl_clang_result['compiler'] = '/usr/bin/afl-clang-fast'
-        afl_clangxx_result = dict(self.CLANGXX_3_6_RESULT)
-        afl_clangxx_result['compiler'] = '/usr/bin/afl-clang-fast++'
         self.do_toolchain_test(paths, {
-            'c_compiler': afl_clang_result,
-            'cxx_compiler': afl_clangxx_result,
+            'c_compiler': self.CLANG_3_6_RESULT + {
+                'compiler': '/usr/bin/afl-clang-fast',
+            },
+            'cxx_compiler': self.CLANGXX_3_6_RESULT + {
+                'compiler': '/usr/bin/afl-clang-fast++',
+            },
         }, environ={
             'CC': 'afl-clang-fast',
             'CXX': 'afl-clang-fast++',
         })
 
     def test_mixed_compilers(self):
         self.do_toolchain_test(self.PATHS, {
             'c_compiler': self.CLANG_3_6_RESULT,
@@ -541,46 +537,46 @@ class WindowsToolchainTest(BaseToolchain
     }
     PATHS.update(LinuxToolchainTest.PATHS)
 
     VS_2013u2_RESULT = (
         'This version (18.00.30501) of the MSVC compiler is not supported.\n'
         'You must install Visual C++ 2013 Update 3, Visual C++ 2015 Update 1, '
         'or newer in order to build.\n'
         'See https://developer.mozilla.org/en/Windows_Build_Prerequisites')
-    VS_2013u3_RESULT = {
-        'flags': [],
-        'version': '18.00.30723',
-        'type': 'msvc',
-        'compiler': '/opt/VS_2013u3/bin/cl',
-    }
+    VS_2013u3_RESULT = CompilerResult(
+        flags=[],
+        version='18.00.30723',
+        type='msvc',
+        compiler='/opt/VS_2013u3/bin/cl',
+    )
     VS_2015_RESULT = (
         'This version (19.00.23026) of the MSVC compiler is not supported.\n'
         'You must install Visual C++ 2013 Update 3, Visual C++ 2015 Update 1, '
         'or newer in order to build.\n'
         'See https://developer.mozilla.org/en/Windows_Build_Prerequisites')
-    VS_2015u1_RESULT = {
-        'flags': [],
-        'version': '19.00.23506',
-        'type': 'msvc',
-        'compiler': '/usr/bin/cl',
-    }
-    CLANG_CL_3_9_RESULT = {
-        'flags': ['-Xclang', '-std=gnu99',
-                  '-fms-compatibility-version=18.00.30723', '-fallback'],
-        'version': '18.00.30723',
-        'type': 'clang-cl',
-        'compiler': '/usr/bin/clang-cl',
-    }
-    CLANGXX_CL_3_9_RESULT = {
-        'flags': ['-fms-compatibility-version=18.00.30723', '-fallback'],
-        'version': '18.00.30723',
-        'type': 'clang-cl',
-        'compiler': '/usr/bin/clang-cl',
-    }
+    VS_2015u1_RESULT = CompilerResult(
+        flags=[],
+        version='19.00.23506',
+        type='msvc',
+        compiler='/usr/bin/cl',
+    )
+    CLANG_CL_3_9_RESULT = CompilerResult(
+        flags=['-Xclang', '-std=gnu99',
+               '-fms-compatibility-version=18.00.30723', '-fallback'],
+        version='18.00.30723',
+        type='clang-cl',
+        compiler='/usr/bin/clang-cl',
+    )
+    CLANGXX_CL_3_9_RESULT = CompilerResult(
+        flags=['-fms-compatibility-version=18.00.30723', '-fallback'],
+        version='18.00.30723',
+        type='clang-cl',
+        compiler='/usr/bin/clang-cl',
+    )
     CLANG_3_3_RESULT = LinuxToolchainTest.CLANG_3_3_RESULT
     CLANGXX_3_3_RESULT = LinuxToolchainTest.CLANGXX_3_3_RESULT
     CLANG_3_6_RESULT = LinuxToolchainTest.CLANG_3_6_RESULT
     CLANGXX_3_6_RESULT = LinuxToolchainTest.CLANGXX_3_6_RESULT
     GCC_4_7_RESULT = LinuxToolchainTest.GCC_4_7_RESULT
     GCC_4_9_RESULT = LinuxToolchainTest.GCC_4_9_RESULT
     GXX_4_9_RESULT = LinuxToolchainTest.GXX_4_9_RESULT
     GCC_5_RESULT = LinuxToolchainTest.GCC_5_RESULT
@@ -672,34 +668,36 @@ class CrossCompileToolchainTest(BaseTool
         '/usr/bin/arm-linux-gnu-gcc': GCC_4_9,
         '/usr/bin/arm-linux-gnu-g++': GXX_4_9,
         '/usr/bin/arm-linux-gnu-gcc-4.7': GCC_4_7,
         '/usr/bin/arm-linux-gnu-g++-4.7': GXX_4_7,
         '/usr/bin/arm-linux-gnu-gcc-5': GCC_5,
         '/usr/bin/arm-linux-gnu-g++-5': GXX_5,
     }
     PATHS.update(LinuxToolchainTest.PATHS)
-    ARM_GCC_4_9_RESULT = dict(LinuxToolchainTest.GCC_4_9_RESULT)
-    ARM_GCC_4_9_RESULT['compiler'] = '/usr/bin/arm-linux-gnu-gcc'
-    ARM_GXX_4_9_RESULT = dict(LinuxToolchainTest.GXX_4_9_RESULT)
-    ARM_GXX_4_9_RESULT['compiler'] = '/usr/bin/arm-linux-gnu-g++'
     ARM_GCC_4_7_RESULT = LinuxToolchainTest.GXX_4_7_RESULT
-    ARM_GCC_5_RESULT = dict(LinuxToolchainTest.GCC_5_RESULT)
-    ARM_GCC_5_RESULT['compiler'] = '/usr/bin/arm-linux-gnu-gcc-5'
-    ARM_GXX_5_RESULT = dict(LinuxToolchainTest.GXX_5_RESULT)
-    ARM_GXX_5_RESULT['compiler'] = '/usr/bin/arm-linux-gnu-g++-5'
+    ARM_GCC_5_RESULT = LinuxToolchainTest.GCC_5_RESULT + {
+        'compiler': '/usr/bin/arm-linux-gnu-gcc-5',
+    }
+    ARM_GXX_5_RESULT = LinuxToolchainTest.GXX_5_RESULT + {
+        'compiler': '/usr/bin/arm-linux-gnu-g++-5',
+    }
     CLANG_3_6_RESULT = LinuxToolchainTest.CLANG_3_6_RESULT
     CLANGXX_3_6_RESULT = LinuxToolchainTest.CLANGXX_3_6_RESULT
     GCC_4_9_RESULT = LinuxToolchainTest.GCC_4_9_RESULT
     GXX_4_9_RESULT = LinuxToolchainTest.GXX_4_9_RESULT
 
     def test_cross_gcc(self):
         self.do_toolchain_test(self.PATHS, {
-            'c_compiler': self.ARM_GCC_4_9_RESULT,
-            'cxx_compiler': self.ARM_GXX_4_9_RESULT,
+            'c_compiler': self.GCC_4_9_RESULT + {
+                'compiler': '/usr/bin/arm-linux-gnu-gcc',
+            },
+            'cxx_compiler': self.GXX_4_9_RESULT + {
+                'compiler': '/usr/bin/arm-linux-gnu-g++',
+            },
             'host_c_compiler': self.GCC_4_9_RESULT,
             'host_cxx_compiler': self.GXX_4_9_RESULT,
         }, args=['--target=arm-unknown-linux-gnu'])
 
     def test_overridden_cross_gcc(self):
         self.do_toolchain_test(self.PATHS, {
             'c_compiler': self.ARM_GCC_5_RESULT,
             'cxx_compiler': self.ARM_GXX_5_RESULT,
--- a/python/mozbuild/mozbuild/test/configure/test_toolchain_helpers.py
+++ b/python/mozbuild/mozbuild/test/configure/test_toolchain_helpers.py
@@ -1,28 +1,31 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
+import copy
 import re
 import types
 import unittest
 
 from fnmatch import fnmatch
 from StringIO import StringIO
 from textwrap import dedent
 
 from mozunit import (
     main,
     MockedOpen,
 )
 
 from mozbuild.preprocessor import Preprocessor
+from mozbuild.util import ReadOnlyNamespace
+from mozpack import path as mozpath
 
 
 class CompilerPreprocessor(Preprocessor):
     VARSUBST = re.compile('(?P<VAR>\w+)', re.U)
     NON_WHITESPACE = re.compile('\S')
 
     def __init__(self, *args, **kwargs):
         Preprocessor.__init__(self, *args, **kwargs)
@@ -320,10 +323,93 @@ class TestFakeCompiler(unittest.TestCase
                 'D': 5,
             },
             '-bar': {
                 'E': 6,
             },
         })
 
 
+class CompilerResult(ReadOnlyNamespace):
+    '''Helper of convenience to manipulate toolchain results in unit tests
+
+    When adding a dict, the result is a new CompilerResult with the values
+    from the dict replacing those from the CompilerResult, except for `flags`,
+    where the value from the dict extends the `flags` in `self`.
+    '''
+
+    def __init__(self, wrapper=None, compiler='', version='', type='',
+                 flags=None):
+        if flags is None:
+            flags = []
+        if wrapper is None:
+            wrapper = []
+        super(CompilerResult, self).__init__(
+            flags=flags,
+            version=version,
+            type=type,
+            compiler=mozpath.abspath(compiler),
+            wrapper=wrapper,
+        )
+
+    def __add__(self, other):
+        assert isinstance(other, dict)
+        result = copy.deepcopy(self.__dict__)
+        for k, v in other.iteritems():
+            if k == 'flags':
+                result.setdefault(k, []).extend(v)
+            else:
+                result[k] = v
+        return CompilerResult(**result)
+
+
+class TestCompilerResult(unittest.TestCase):
+    def test_compiler_result(self):
+        result = CompilerResult()
+        self.assertEquals(result.__dict__, {
+            'wrapper': [],
+            'compiler': mozpath.abspath(''),
+            'version': '',
+            'type': '',
+            'flags': [],
+        })
+
+        result = CompilerResult(
+            compiler='/usr/bin/gcc',
+            version='4.2.1',
+            type='gcc',
+            flags=['-std=gnu99'],
+        )
+        self.assertEquals(result.__dict__, {
+            'wrapper': [],
+            'compiler': mozpath.abspath('/usr/bin/gcc'),
+            'version': '4.2.1',
+            'type': 'gcc',
+            'flags': ['-std=gnu99'],
+        })
+
+        result2 = result + {'flags': ['-m32']}
+        self.assertEquals(result2.__dict__, {
+            'wrapper': [],
+            'compiler': mozpath.abspath('/usr/bin/gcc'),
+            'version': '4.2.1',
+            'type': 'gcc',
+            'flags': ['-std=gnu99', '-m32'],
+        })
+        # Original flags are untouched.
+        self.assertEquals(result.flags, ['-std=gnu99'])
+
+        result3 = result + {
+            'compiler': '/usr/bin/gcc-4.7',
+            'version': '4.7.3',
+            'flags': ['-m32'],
+        }
+        self.assertEquals(result3.__dict__, {
+            'wrapper': [],
+            'compiler': mozpath.abspath('/usr/bin/gcc-4.7'),
+            'version': '4.7.3',
+            'type': 'gcc',
+            'flags': ['-std=gnu99', '-m32'],
+        })
+
+
 if __name__ == '__main__':
     main()
--- a/python/mozbuild/mozbuild/util.py
+++ b/python/mozbuild/mozbuild/util.py
@@ -101,16 +101,19 @@ class ReadOnlyNamespace(object):
         raise Exception('Object does not support assignment.')
 
     def __ne__(self, other):
         return not (self == other)
 
     def __eq__(self, other):
         return self is other or self.__dict__ == other.__dict__
 
+    def __repr__(self):
+        return '<%s %r>' % (self.__class__.__name__, self.__dict__)
+
 
 class ReadOnlyDict(dict):
     """A read-only dictionary."""
     def __init__(self, *args, **kwargs):
         dict.__init__(self, *args, **kwargs)
 
     def __delitem__(self, key):
         raise Exception('Object does not support deletion.')