Bug 1428461 - Update mozharness' mozinfo from mozbase; r=jmaher
authorGeoff Brown <gbrown@mozilla.com>
Mon, 08 Jan 2018 07:41:09 -0700
changeset 449998 66c7d59eb914550af51b22031d367f4b616360a8
parent 449997 1b4b7cb6959e1d5e760183fe59217299d0a34e85
child 449999 90da22b0bf05b0ca610f890ba848841c3ac3b567
push id8527
push userCallek@gmail.com
push dateThu, 11 Jan 2018 21:05:50 +0000
treeherdermozilla-beta@95342d212a7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjmaher
bugs1428461
milestone59.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 1428461 - Update mozharness' mozinfo from mozbase; r=jmaher Copy testing/mozbase/mozinfo/mozinfo to testing/mozharness/mozinfo, after applying the patch to decouple mozinfo from six.
testing/mozharness/mozinfo/mozinfo.py
testing/mozharness/mozinfo/string_version.py
--- a/testing/mozharness/mozinfo/mozinfo.py
+++ b/testing/mozharness/mozinfo/mozinfo.py
@@ -3,23 +3,24 @@
 # 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/.
 
 # TODO: it might be a good idea of adding a system name (e.g. 'Ubuntu' for
 # linux) to the information; I certainly wouldn't want anyone parsing this
 # information and having behaviour depend on it
 
-import json
+from __future__ import absolute_import, print_function
+
 import os
 import platform
 import re
 import sys
-
-import mozfile
+from .string_version import StringVersion
+from ctypes.util import find_library
 
 # keep a copy of the os module since updating globals overrides this
 _os = os
 
 
 class unknown(object):
     """marker class for unknown information"""
 
@@ -27,56 +28,115 @@ class unknown(object):
         return False
 
     def __str__(self):
         return 'UNKNOWN'
 
 
 unknown = unknown()  # singleton
 
+
+def get_windows_version():
+    import ctypes
+
+    class OSVERSIONINFOEXW(ctypes.Structure):
+        _fields_ = [('dwOSVersionInfoSize', ctypes.c_ulong),
+                    ('dwMajorVersion', ctypes.c_ulong),
+                    ('dwMinorVersion', ctypes.c_ulong),
+                    ('dwBuildNumber', ctypes.c_ulong),
+                    ('dwPlatformId', ctypes.c_ulong),
+                    ('szCSDVersion', ctypes.c_wchar * 128),
+                    ('wServicePackMajor', ctypes.c_ushort),
+                    ('wServicePackMinor', ctypes.c_ushort),
+                    ('wSuiteMask', ctypes.c_ushort),
+                    ('wProductType', ctypes.c_byte),
+                    ('wReserved', ctypes.c_byte)]
+
+    os_version = OSVERSIONINFOEXW()
+    os_version.dwOSVersionInfoSize = ctypes.sizeof(os_version)
+    retcode = ctypes.windll.Ntdll.RtlGetVersion(ctypes.byref(os_version))
+    if retcode != 0:
+        raise OSError
+
+    return os_version.dwMajorVersion, os_version.dwMinorVersion, os_version.dwBuildNumber
+
+
 # get system information
 info = {'os': unknown,
         'processor': unknown,
         'version': unknown,
-        'bits': unknown}
+        'os_version': unknown,
+        'bits': unknown,
+        'has_sandbox': unknown,
+        'webrender': bool(os.environ.get("MOZ_WEBRENDER", False))}
 (system, node, release, version, machine, processor) = platform.uname()
 (bits, linkage) = platform.architecture()
 
 # get os information and related data
 if system in ["Microsoft", "Windows"]:
     info['os'] = 'win'
     # There is a Python bug on Windows to determine platform values
     # http://bugs.python.org/issue7860
     if "PROCESSOR_ARCHITEW6432" in os.environ:
         processor = os.environ.get("PROCESSOR_ARCHITEW6432", processor)
     else:
         processor = os.environ.get('PROCESSOR_ARCHITECTURE', processor)
     system = os.environ.get("OS", system).replace('_', ' ')
-    service_pack = os.sys.getwindowsversion()[4]
+    (major, minor, _, _, service_pack) = os.sys.getwindowsversion()
     info['service_pack'] = service_pack
+    if major >= 6 and minor >= 2:
+        # On windows >= 8.1 the system call that getwindowsversion uses has
+        # been frozen to always return the same values. In this case we call
+        # the RtlGetVersion API directly, which still provides meaningful
+        # values, at least for now.
+        major, minor, build_number = get_windows_version()
+        version = "%d.%d.%d" % (major, minor, build_number)
+
+    os_version = "%d.%d" % (major, minor)
+elif system.startswith(('MINGW', 'MSYS_NT')):
+    # windows/mingw python build (msys)
+    info['os'] = 'win'
+    os_version = version = unknown
 elif system == "Linux":
     if hasattr(platform, "linux_distribution"):
-        (distro, version, codename) = platform.linux_distribution()
+        (distro, os_version, codename) = platform.linux_distribution()
     else:
-        (distro, version, codename) = platform.dist()
-    version = "%s %s" % (distro, version)
+        (distro, os_version, codename) = platform.dist()
     if not processor:
         processor = machine
+    version = "%s %s" % (distro, os_version)
+
+    # Bug in Python 2's `platform` library:
+    # It will return a triple of empty strings if the distribution is not supported.
+    # It works on Python 3. If we don't have an OS version,
+    # the unit tests fail to run.
+    if not distro and not os_version and not codename:
+        distro = 'lfs'
+        version = release
+        os_version = release
+
     info['os'] = 'linux'
+    info['linux_distro'] = distro
 elif system in ['DragonFly', 'FreeBSD', 'NetBSD', 'OpenBSD']:
     info['os'] = 'bsd'
-    version = sys.platform
+    version = os_version = sys.platform
 elif system == "Darwin":
     (release, versioninfo, machine) = platform.mac_ver()
     version = "OS X %s" % release
+    versionNums = release.split('.')[:2]
+    os_version = "%s.%s" % (versionNums[0], versionNums[1])
     info['os'] = 'mac'
 elif sys.platform in ('solaris', 'sunos5'):
     info['os'] = 'unix'
-    version = sys.platform
-info['version'] = version  # os version
+    os_version = version = sys.platform
+else:
+    os_version = version = unknown
+
+info['version'] = version
+info['os_version'] = StringVersion(os_version)
 
 # processor type and bits
 if processor in ["i386", "i686"]:
     if bits == "32bit":
         processor = "x86"
     elif bits == "64bit":
         processor = "x86_64"
 elif processor.upper() == "AMD64":
@@ -84,16 +144,26 @@ elif processor.upper() == "AMD64":
     processor = "x86_64"
 elif processor == "Power Macintosh":
     processor = "ppc"
 bits = re.search('(\d+)bit', bits).group(1)
 info.update({'processor': processor,
              'bits': int(bits),
              })
 
+if info['os'] == 'linux':
+    import ctypes
+    import errno
+    PR_SET_SECCOMP = 22
+    SECCOMP_MODE_FILTER = 2
+    ctypes.CDLL(find_library("c"), use_errno=True).prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, 0)
+    info['has_sandbox'] = ctypes.get_errno() == errno.EFAULT
+else:
+    info['has_sandbox'] = True
+
 # standard value of choices, for easy inspection
 choices = {'os': ['linux', 'bsd', 'win', 'mac', 'unix'],
            'bits': [32, 64],
            'processor': ['x86', 'x86_64', 'ppc']}
 
 
 def sanitize(info):
     """Do some sanitization of input values, primarily
@@ -102,27 +172,36 @@ def sanitize(info):
         # If we're running on OS X 10.6 or newer, assume 64-bit
         if release[:4] >= "10.6":  # Note this is a string comparison
             info["processor"] = "x86_64"
             info["bits"] = 64
         else:
             info["processor"] = "x86"
             info["bits"] = 32
 
+# method for updating information
 
-# method for updating information
+
 def update(new_info):
     """
     Update the info.
 
     :param new_info: Either a dict containing the new info or a path/url
                      to a json file containing the new info.
     """
 
-    if isinstance(new_info, basestring):
+    PY3 = sys.version_info[0] == 3
+    if PY3:
+        string_types = str,
+    else:
+        string_types = basestring,
+    if isinstance(new_info, string_types):
+        # lazy import
+        import mozfile
+        import json
         f = mozfile.load(new_info)
         new_info = json.loads(f.read())
         f.close()
 
     info.update(new_info)
     sanitize(info)
     globals().update(info)
 
@@ -142,79 +221,92 @@ def find_and_update_from_json(*dirs):
     :param dirs: Directories in which to look for the file. They will be
                  searched after first looking in the root of the objdir
                  if the current script is being run from a Mozilla objdir.
 
     Returns the full path to mozinfo.json if it was found, or None otherwise.
     """
     # First, see if we're in an objdir
     try:
-        from mozbuild.base import MozbuildObject
+        from mozbuild.base import MozbuildObject, BuildEnvironmentNotFoundException
+        from mozbuild.mozconfig import MozconfigFindException
         build = MozbuildObject.from_environment()
         json_path = _os.path.join(build.topobjdir, "mozinfo.json")
         if _os.path.isfile(json_path):
             update(json_path)
             return json_path
     except ImportError:
         pass
+    except (BuildEnvironmentNotFoundException, MozconfigFindException):
+        pass
 
     for d in dirs:
         d = _os.path.abspath(d)
         json_path = _os.path.join(d, "mozinfo.json")
         if _os.path.isfile(json_path):
             update(json_path)
             return json_path
 
     return None
 
 
+def output_to_file(path):
+    import json
+    with open(path, 'w') as f:
+        f.write(json.dumps(info))
+
+
 update({})
 
 # exports
-__all__ = info.keys()
+__all__ = list(info.keys())
 __all__ += ['is' + os_name.title() for os_name in choices['os']]
 __all__ += [
     'info',
     'unknown',
     'main',
     'choices',
     'update',
     'find_and_update_from_json',
-    ]
+    'output_to_file',
+    'StringVersion',
+]
 
 
 def main(args=None):
 
     # parse the command line
     from optparse import OptionParser
     parser = OptionParser(description=__doc__)
     for key in choices:
         parser.add_option('--%s' % key, dest=key,
                           action='store_true', default=False,
                           help="display choices for %s" % key)
     options, args = parser.parse_args()
 
     # args are JSON blobs to override info
     if args:
+        # lazy import
+        import json
         for arg in args:
             if _os.path.exists(arg):
-                string = file(arg).read()
+                string = open(arg).read()
             else:
                 string = arg
             update(json.loads(string))
 
     # print out choices if requested
     flag = False
     for key, value in options.__dict__.items():
         if value is True:
-            print '%s choices: %s' % (key, ' '.join([str(choice)
-                                                     for choice in choices[key]]))
+            print('%s choices: %s' % (key, ' '.join([str(choice)
+                                                     for choice in choices[key]])))
             flag = True
     if flag:
         return
 
     # otherwise, print out all info
     for key, value in info.items():
-        print '%s: %s' % (key, value)
+        print('%s: %s' % (key, value))
 
 
 if __name__ == '__main__':
     main()
new file mode 100644
--- /dev/null
+++ b/testing/mozharness/mozinfo/string_version.py
@@ -0,0 +1,45 @@
+# 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
+
+from distutils.version import LooseVersion
+
+
+class StringVersion(str):
+    """
+    A string version that can be compared with comparison operators.
+    """
+
+    def __init__(self, vstring):
+        str.__init__(self, vstring)
+        self.version = LooseVersion(vstring)
+
+    def __repr__(self):
+        return "StringVersion ('%s')" % self
+
+    def __to_version(self, other):
+        if not isinstance(other, StringVersion):
+            other = StringVersion(other)
+        return other.version
+
+    # rich comparison methods
+
+    def __lt__(self, other):
+        return self.version < self.__to_version(other)
+
+    def __le__(self, other):
+        return self.version <= self.__to_version(other)
+
+    def __eq__(self, other):
+        return self.version == self.__to_version(other)
+
+    def __ne__(self, other):
+        return self.version != self.__to_version(other)
+
+    def __gt__(self, other):
+        return self.version > self.__to_version(other)
+
+    def __ge__(self, other):
+        return self.version >= self.__to_version(other)