Home | History | Annotate | Download | only in google-benchmark
      1 #! /usr/bin/env python
      2 # encoding: utf-8
      3 
      4 import argparse
      5 import errno
      6 import logging
      7 import os
      8 import platform
      9 import re
     10 import sys
     11 import subprocess
     12 import tempfile
     13 
     14 try:
     15     import winreg
     16 except ImportError:
     17     import _winreg as winreg
     18 try:
     19     import urllib.request as request
     20 except ImportError:
     21     import urllib as request
     22 try:
     23     import urllib.parse as parse
     24 except ImportError:
     25     import urlparse as parse
     26 
     27 class EmptyLogger(object):
     28     '''
     29     Provides an implementation that performs no logging
     30     '''
     31     def debug(self, *k, **kw):
     32         pass
     33     def info(self, *k, **kw):
     34         pass
     35     def warn(self, *k, **kw):
     36         pass
     37     def error(self, *k, **kw):
     38         pass
     39     def critical(self, *k, **kw):
     40         pass
     41     def setLevel(self, *k, **kw):
     42         pass
     43 
     44 urls = (
     45     'http://downloads.sourceforge.net/project/mingw-w64/Toolchains%20'
     46         'targetting%20Win32/Personal%20Builds/mingw-builds/installer/'
     47         'repository.txt',
     48     'http://downloads.sourceforge.net/project/mingwbuilds/host-windows/'
     49         'repository.txt'
     50 )
     51 '''
     52 A list of mingw-build repositories
     53 '''
     54 
     55 def repository(urls = urls, log = EmptyLogger()):
     56     '''
     57     Downloads and parse mingw-build repository files and parses them
     58     '''
     59     log.info('getting mingw-builds repository')
     60     versions = {}
     61     re_sourceforge = re.compile(r'http://sourceforge.net/projects/([^/]+)/files')
     62     re_sub = r'http://downloads.sourceforge.net/project/\1'
     63     for url in urls:
     64         log.debug(' - requesting: %s', url)
     65         socket = request.urlopen(url)
     66         repo = socket.read()
     67         if not isinstance(repo, str):
     68             repo = repo.decode();
     69         socket.close()
     70         for entry in repo.split('\n')[:-1]:
     71             value = entry.split('|')
     72             version = tuple([int(n) for n in value[0].strip().split('.')])
     73             version = versions.setdefault(version, {})
     74             arch = value[1].strip()
     75             if arch == 'x32':
     76                 arch = 'i686'
     77             elif arch == 'x64':
     78                 arch = 'x86_64'
     79             arch = version.setdefault(arch, {})
     80             threading = arch.setdefault(value[2].strip(), {})
     81             exceptions = threading.setdefault(value[3].strip(), {})
     82             revision = exceptions.setdefault(int(value[4].strip()[3:]),
     83                 re_sourceforge.sub(re_sub, value[5].strip()))
     84     return versions
     85 
     86 def find_in_path(file, path=None):
     87     '''
     88     Attempts to find an executable in the path
     89     '''
     90     if platform.system() == 'Windows':
     91         file += '.exe'
     92     if path is None:
     93         path = os.environ.get('PATH', '')
     94     if type(path) is type(''):
     95         path = path.split(os.pathsep)
     96     return list(filter(os.path.exists,
     97         map(lambda dir, file=file: os.path.join(dir, file), path)))
     98 
     99 def find_7zip(log = EmptyLogger()):
    100     '''
    101     Attempts to find 7zip for unpacking the mingw-build archives
    102     '''
    103     log.info('finding 7zip')
    104     path = find_in_path('7z')
    105     if not path:
    106         key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\7-Zip')
    107         path, _ = winreg.QueryValueEx(key, 'Path')
    108         path = [os.path.join(path, '7z.exe')]
    109     log.debug('found \'%s\'', path[0])
    110     return path[0]
    111 
    112 find_7zip()
    113 
    114 def unpack(archive, location, log = EmptyLogger()):
    115     '''
    116     Unpacks a mingw-builds archive
    117     '''
    118     sevenzip = find_7zip(log)
    119     log.info('unpacking %s', os.path.basename(archive))
    120     cmd = [sevenzip, 'x', archive, '-o' + location, '-y']
    121     log.debug(' - %r', cmd)
    122     with open(os.devnull, 'w') as devnull:
    123         subprocess.check_call(cmd, stdout = devnull)
    124 
    125 def download(url, location, log = EmptyLogger()):
    126     '''
    127     Downloads and unpacks a mingw-builds archive
    128     '''
    129     log.info('downloading MinGW')
    130     log.debug(' - url: %s', url)
    131     log.debug(' - location: %s', location)
    132 
    133     re_content = re.compile(r'attachment;[ \t]*filename=(")?([^"]*)(")?[\r\n]*')
    134 
    135     stream = request.urlopen(url)
    136     try:
    137         content = stream.getheader('Content-Disposition') or ''
    138     except AttributeError:
    139         content = stream.headers.getheader('Content-Disposition') or ''
    140     matches = re_content.match(content)
    141     if matches:
    142         filename = matches.group(2)
    143     else:
    144         parsed = parse.urlparse(stream.geturl())
    145         filename = os.path.basename(parsed.path)
    146 
    147     try:
    148         os.makedirs(location)
    149     except OSError as e:
    150         if e.errno == errno.EEXIST and os.path.isdir(location):
    151             pass
    152         else:
    153             raise
    154 
    155     archive = os.path.join(location, filename)
    156     with open(archive, 'wb') as out:
    157         while True:
    158             buf = stream.read(1024)
    159             if not buf:
    160                 break
    161             out.write(buf)
    162     unpack(archive, location, log = log)
    163     os.remove(archive)
    164 
    165     possible = os.path.join(location, 'mingw64')
    166     if not os.path.exists(possible):
    167         possible = os.path.join(location, 'mingw32')
    168         if not os.path.exists(possible):
    169             raise ValueError('Failed to find unpacked MinGW: ' + possible)
    170     return possible
    171 
    172 def root(location = None, arch = None, version = None, threading = None,
    173         exceptions = None, revision = None, log = EmptyLogger()):
    174     '''
    175     Returns the root folder of a specific version of the mingw-builds variant
    176     of gcc. Will download the compiler if needed
    177     '''
    178 
    179     # Get the repository if we don't have all the information
    180     if not (arch and version and threading and exceptions and revision):
    181         versions = repository(log = log)
    182 
    183     # Determine some defaults
    184     version = version or max(versions.keys())
    185     if not arch:
    186         arch = platform.machine().lower()
    187         if arch == 'x86':
    188             arch = 'i686'
    189         elif arch == 'amd64':
    190             arch = 'x86_64'
    191     if not threading:
    192         keys = versions[version][arch].keys()
    193         if 'posix' in keys:
    194             threading = 'posix'
    195         elif 'win32' in keys:
    196             threading = 'win32'
    197         else:
    198             threading = keys[0]
    199     if not exceptions:
    200         keys = versions[version][arch][threading].keys()
    201         if 'seh' in keys:
    202             exceptions = 'seh'
    203         elif 'sjlj' in keys:
    204             exceptions = 'sjlj'
    205         else:
    206             exceptions = keys[0]
    207     if revision == None:
    208         revision = max(versions[version][arch][threading][exceptions].keys())
    209     if not location:
    210         location = os.path.join(tempfile.gettempdir(), 'mingw-builds')
    211 
    212     # Get the download url
    213     url = versions[version][arch][threading][exceptions][revision]
    214 
    215     # Tell the user whatzzup
    216     log.info('finding MinGW %s', '.'.join(str(v) for v in version))
    217     log.debug(' - arch: %s', arch)
    218     log.debug(' - threading: %s', threading)
    219     log.debug(' - exceptions: %s', exceptions)
    220     log.debug(' - revision: %s', revision)
    221     log.debug(' - url: %s', url)
    222 
    223     # Store each specific revision differently
    224     slug = '{version}-{arch}-{threading}-{exceptions}-rev{revision}'
    225     slug = slug.format(
    226         version = '.'.join(str(v) for v in version),
    227         arch = arch,
    228         threading = threading,
    229         exceptions = exceptions,
    230         revision = revision
    231     )
    232     if arch == 'x86_64':
    233         root_dir = os.path.join(location, slug, 'mingw64')
    234     elif arch == 'i686':
    235         root_dir = os.path.join(location, slug, 'mingw32')
    236     else:
    237         raise ValueError('Unknown MinGW arch: ' + arch)
    238 
    239     # Download if needed
    240     if not os.path.exists(root_dir):
    241         downloaded = download(url, os.path.join(location, slug), log = log)
    242         if downloaded != root_dir:
    243             raise ValueError('The location of mingw did not match\n%s\n%s'
    244                 % (downloaded, root_dir))
    245 
    246     return root_dir
    247 
    248 def str2ver(string):
    249     '''
    250     Converts a version string into a tuple
    251     '''
    252     try:
    253         version = tuple(int(v) for v in string.split('.'))
    254         if len(version) is not 3:
    255             raise ValueError()
    256     except ValueError:
    257         raise argparse.ArgumentTypeError(
    258             'please provide a three digit version string')
    259     return version
    260 
    261 def main():
    262     '''
    263     Invoked when the script is run directly by the python interpreter
    264     '''
    265     parser = argparse.ArgumentParser(
    266         description = 'Downloads a specific version of MinGW',
    267         formatter_class = argparse.ArgumentDefaultsHelpFormatter
    268     )
    269     parser.add_argument('--location',
    270         help = 'the location to download the compiler to',
    271         default = os.path.join(tempfile.gettempdir(), 'mingw-builds'))
    272     parser.add_argument('--arch', required = True, choices = ['i686', 'x86_64'],
    273         help = 'the target MinGW architecture string')
    274     parser.add_argument('--version', type = str2ver,
    275         help = 'the version of GCC to download')
    276     parser.add_argument('--threading', choices = ['posix', 'win32'],
    277         help = 'the threading type of the compiler')
    278     parser.add_argument('--exceptions', choices = ['sjlj', 'seh', 'dwarf'],
    279         help = 'the method to throw exceptions')
    280     parser.add_argument('--revision', type=int,
    281         help = 'the revision of the MinGW release')
    282     group = parser.add_mutually_exclusive_group()
    283     group.add_argument('-v', '--verbose', action='store_true',
    284         help='increase the script output verbosity')
    285     group.add_argument('-q', '--quiet', action='store_true',
    286         help='only print errors and warning')
    287     args = parser.parse_args()
    288 
    289     # Create the logger
    290     logger = logging.getLogger('mingw')
    291     handler = logging.StreamHandler()
    292     formatter = logging.Formatter('%(message)s')
    293     handler.setFormatter(formatter)
    294     logger.addHandler(handler)
    295     logger.setLevel(logging.INFO)
    296     if args.quiet:
    297         logger.setLevel(logging.WARN)
    298     if args.verbose:
    299         logger.setLevel(logging.DEBUG)
    300 
    301     # Get MinGW
    302     root_dir = root(location = args.location, arch = args.arch,
    303         version = args.version, threading = args.threading,
    304         exceptions = args.exceptions, revision = args.revision,
    305         log = logger)
    306 
    307     sys.stdout.write('%s\n' % os.path.join(root_dir, 'bin'))
    308 
    309 if __name__ == '__main__':
    310     try:
    311         main()
    312     except IOError as e:
    313         sys.stderr.write('IO error: %s\n' % e)
    314         sys.exit(1)
    315     except OSError as e:
    316         sys.stderr.write('OS error: %s\n' % e)
    317         sys.exit(1)
    318     except KeyboardInterrupt as e:
    319         sys.stderr.write('Killed\n')
    320         sys.exit(1)
    321