321 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			321 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
| #! /usr/bin/env python
 | |
| # encoding: utf-8
 | |
| 
 | |
| import argparse
 | |
| import errno
 | |
| import logging
 | |
| import os
 | |
| import platform
 | |
| import re
 | |
| import sys
 | |
| import subprocess
 | |
| import tempfile
 | |
| 
 | |
| try:
 | |
|     import winreg
 | |
| except ImportError:
 | |
|     import _winreg as winreg
 | |
| try:
 | |
|     import urllib.request as request
 | |
| except ImportError:
 | |
|     import urllib as request
 | |
| try:
 | |
|     import urllib.parse as parse
 | |
| except ImportError:
 | |
|     import urlparse as parse
 | |
| 
 | |
| class EmptyLogger(object):
 | |
|     '''
 | |
|     Provides an implementation that performs no logging
 | |
|     '''
 | |
|     def debug(self, *k, **kw):
 | |
|         pass
 | |
|     def info(self, *k, **kw):
 | |
|         pass
 | |
|     def warn(self, *k, **kw):
 | |
|         pass
 | |
|     def error(self, *k, **kw):
 | |
|         pass
 | |
|     def critical(self, *k, **kw):
 | |
|         pass
 | |
|     def setLevel(self, *k, **kw):
 | |
|         pass
 | |
| 
 | |
| urls = (
 | |
|     'http://downloads.sourceforge.net/project/mingw-w64/Toolchains%20'
 | |
|         'targetting%20Win32/Personal%20Builds/mingw-builds/installer/'
 | |
|         'repository.txt',
 | |
|     'http://downloads.sourceforge.net/project/mingwbuilds/host-windows/'
 | |
|         'repository.txt'
 | |
| )
 | |
| '''
 | |
| A list of mingw-build repositories
 | |
| '''
 | |
| 
 | |
| def repository(urls = urls, log = EmptyLogger()):
 | |
|     '''
 | |
|     Downloads and parse mingw-build repository files and parses them
 | |
|     '''
 | |
|     log.info('getting mingw-builds repository')
 | |
|     versions = {}
 | |
|     re_sourceforge = re.compile(r'http://sourceforge.net/projects/([^/]+)/files')
 | |
|     re_sub = r'http://downloads.sourceforge.net/project/\1'
 | |
|     for url in urls:
 | |
|         log.debug(' - requesting: %s', url)
 | |
|         socket = request.urlopen(url)
 | |
|         repo = socket.read()
 | |
|         if not isinstance(repo, str):
 | |
|             repo = repo.decode();
 | |
|         socket.close()
 | |
|         for entry in repo.split('\n')[:-1]:
 | |
|             value = entry.split('|')
 | |
|             version = tuple([int(n) for n in value[0].strip().split('.')])
 | |
|             version = versions.setdefault(version, {})
 | |
|             arch = value[1].strip()
 | |
|             if arch == 'x32':
 | |
|                 arch = 'i686'
 | |
|             elif arch == 'x64':
 | |
|                 arch = 'x86_64'
 | |
|             arch = version.setdefault(arch, {})
 | |
|             threading = arch.setdefault(value[2].strip(), {})
 | |
|             exceptions = threading.setdefault(value[3].strip(), {})
 | |
|             revision = exceptions.setdefault(int(value[4].strip()[3:]),
 | |
|                 re_sourceforge.sub(re_sub, value[5].strip()))
 | |
|     return versions
 | |
| 
 | |
| def find_in_path(file, path=None):
 | |
|     '''
 | |
|     Attempts to find an executable in the path
 | |
|     '''
 | |
|     if platform.system() == 'Windows':
 | |
|         file += '.exe'
 | |
|     if path is None:
 | |
|         path = os.environ.get('PATH', '')
 | |
|     if type(path) is type(''):
 | |
|         path = path.split(os.pathsep)
 | |
|     return list(filter(os.path.exists,
 | |
|         map(lambda dir, file=file: os.path.join(dir, file), path)))
 | |
| 
 | |
| def find_7zip(log = EmptyLogger()):
 | |
|     '''
 | |
|     Attempts to find 7zip for unpacking the mingw-build archives
 | |
|     '''
 | |
|     log.info('finding 7zip')
 | |
|     path = find_in_path('7z')
 | |
|     if not path:
 | |
|         key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\7-Zip')
 | |
|         path, _ = winreg.QueryValueEx(key, 'Path')
 | |
|         path = [os.path.join(path, '7z.exe')]
 | |
|     log.debug('found \'%s\'', path[0])
 | |
|     return path[0]
 | |
| 
 | |
| find_7zip()
 | |
| 
 | |
| def unpack(archive, location, log = EmptyLogger()):
 | |
|     '''
 | |
|     Unpacks a mingw-builds archive
 | |
|     '''
 | |
|     sevenzip = find_7zip(log)
 | |
|     log.info('unpacking %s', os.path.basename(archive))
 | |
|     cmd = [sevenzip, 'x', archive, '-o' + location, '-y']
 | |
|     log.debug(' - %r', cmd)
 | |
|     with open(os.devnull, 'w') as devnull:
 | |
|         subprocess.check_call(cmd, stdout = devnull)
 | |
| 
 | |
| def download(url, location, log = EmptyLogger()):
 | |
|     '''
 | |
|     Downloads and unpacks a mingw-builds archive
 | |
|     '''
 | |
|     log.info('downloading MinGW')
 | |
|     log.debug(' - url: %s', url)
 | |
|     log.debug(' - location: %s', location)
 | |
| 
 | |
|     re_content = re.compile(r'attachment;[ \t]*filename=(")?([^"]*)(")?[\r\n]*')
 | |
| 
 | |
|     stream = request.urlopen(url)
 | |
|     try:
 | |
|         content = stream.getheader('Content-Disposition') or ''
 | |
|     except AttributeError:
 | |
|         content = stream.headers.getheader('Content-Disposition') or ''
 | |
|     matches = re_content.match(content)
 | |
|     if matches:
 | |
|         filename = matches.group(2)
 | |
|     else:
 | |
|         parsed = parse.urlparse(stream.geturl())
 | |
|         filename = os.path.basename(parsed.path)
 | |
| 
 | |
|     try:
 | |
|         os.makedirs(location)
 | |
|     except OSError as e:
 | |
|         if e.errno == errno.EEXIST and os.path.isdir(location):
 | |
|             pass
 | |
|         else:
 | |
|             raise
 | |
| 
 | |
|     archive = os.path.join(location, filename)
 | |
|     with open(archive, 'wb') as out:
 | |
|         while True:
 | |
|             buf = stream.read(1024)
 | |
|             if not buf:
 | |
|                 break
 | |
|             out.write(buf)
 | |
|     unpack(archive, location, log = log)
 | |
|     os.remove(archive)
 | |
| 
 | |
|     possible = os.path.join(location, 'mingw64')
 | |
|     if not os.path.exists(possible):
 | |
|         possible = os.path.join(location, 'mingw32')
 | |
|         if not os.path.exists(possible):
 | |
|             raise ValueError('Failed to find unpacked MinGW: ' + possible)
 | |
|     return possible
 | |
| 
 | |
| def root(location = None, arch = None, version = None, threading = None,
 | |
|         exceptions = None, revision = None, log = EmptyLogger()):
 | |
|     '''
 | |
|     Returns the root folder of a specific version of the mingw-builds variant
 | |
|     of gcc. Will download the compiler if needed
 | |
|     '''
 | |
| 
 | |
|     # Get the repository if we don't have all the information
 | |
|     if not (arch and version and threading and exceptions and revision):
 | |
|         versions = repository(log = log)
 | |
| 
 | |
|     # Determine some defaults
 | |
|     version = version or max(versions.keys())
 | |
|     if not arch:
 | |
|         arch = platform.machine().lower()
 | |
|         if arch == 'x86':
 | |
|             arch = 'i686'
 | |
|         elif arch == 'amd64':
 | |
|             arch = 'x86_64'
 | |
|     if not threading:
 | |
|         keys = versions[version][arch].keys()
 | |
|         if 'posix' in keys:
 | |
|             threading = 'posix'
 | |
|         elif 'win32' in keys:
 | |
|             threading = 'win32'
 | |
|         else:
 | |
|             threading = keys[0]
 | |
|     if not exceptions:
 | |
|         keys = versions[version][arch][threading].keys()
 | |
|         if 'seh' in keys:
 | |
|             exceptions = 'seh'
 | |
|         elif 'sjlj' in keys:
 | |
|             exceptions = 'sjlj'
 | |
|         else:
 | |
|             exceptions = keys[0]
 | |
|     if revision == None:
 | |
|         revision = max(versions[version][arch][threading][exceptions].keys())
 | |
|     if not location:
 | |
|         location = os.path.join(tempfile.gettempdir(), 'mingw-builds')
 | |
| 
 | |
|     # Get the download url
 | |
|     url = versions[version][arch][threading][exceptions][revision]
 | |
| 
 | |
|     # Tell the user whatzzup
 | |
|     log.info('finding MinGW %s', '.'.join(str(v) for v in version))
 | |
|     log.debug(' - arch: %s', arch)
 | |
|     log.debug(' - threading: %s', threading)
 | |
|     log.debug(' - exceptions: %s', exceptions)
 | |
|     log.debug(' - revision: %s', revision)
 | |
|     log.debug(' - url: %s', url)
 | |
| 
 | |
|     # Store each specific revision differently
 | |
|     slug = '{version}-{arch}-{threading}-{exceptions}-rev{revision}'
 | |
|     slug = slug.format(
 | |
|         version = '.'.join(str(v) for v in version),
 | |
|         arch = arch,
 | |
|         threading = threading,
 | |
|         exceptions = exceptions,
 | |
|         revision = revision
 | |
|     )
 | |
|     if arch == 'x86_64':
 | |
|         root_dir = os.path.join(location, slug, 'mingw64')
 | |
|     elif arch == 'i686':
 | |
|         root_dir = os.path.join(location, slug, 'mingw32')
 | |
|     else:
 | |
|         raise ValueError('Unknown MinGW arch: ' + arch)
 | |
| 
 | |
|     # Download if needed
 | |
|     if not os.path.exists(root_dir):
 | |
|         downloaded = download(url, os.path.join(location, slug), log = log)
 | |
|         if downloaded != root_dir:
 | |
|             raise ValueError('The location of mingw did not match\n%s\n%s'
 | |
|                 % (downloaded, root_dir))
 | |
| 
 | |
|     return root_dir
 | |
| 
 | |
| def str2ver(string):
 | |
|     '''
 | |
|     Converts a version string into a tuple
 | |
|     '''
 | |
|     try:
 | |
|         version = tuple(int(v) for v in string.split('.'))
 | |
|         if len(version) is not 3:
 | |
|             raise ValueError()
 | |
|     except ValueError:
 | |
|         raise argparse.ArgumentTypeError(
 | |
|             'please provide a three digit version string')
 | |
|     return version
 | |
| 
 | |
| def main():
 | |
|     '''
 | |
|     Invoked when the script is run directly by the python interpreter
 | |
|     '''
 | |
|     parser = argparse.ArgumentParser(
 | |
|         description = 'Downloads a specific version of MinGW',
 | |
|         formatter_class = argparse.ArgumentDefaultsHelpFormatter
 | |
|     )
 | |
|     parser.add_argument('--location',
 | |
|         help = 'the location to download the compiler to',
 | |
|         default = os.path.join(tempfile.gettempdir(), 'mingw-builds'))
 | |
|     parser.add_argument('--arch', required = True, choices = ['i686', 'x86_64'],
 | |
|         help = 'the target MinGW architecture string')
 | |
|     parser.add_argument('--version', type = str2ver,
 | |
|         help = 'the version of GCC to download')
 | |
|     parser.add_argument('--threading', choices = ['posix', 'win32'],
 | |
|         help = 'the threading type of the compiler')
 | |
|     parser.add_argument('--exceptions', choices = ['sjlj', 'seh', 'dwarf'],
 | |
|         help = 'the method to throw exceptions')
 | |
|     parser.add_argument('--revision', type=int,
 | |
|         help = 'the revision of the MinGW release')
 | |
|     group = parser.add_mutually_exclusive_group()
 | |
|     group.add_argument('-v', '--verbose', action='store_true',
 | |
|         help='increase the script output verbosity')
 | |
|     group.add_argument('-q', '--quiet', action='store_true',
 | |
|         help='only print errors and warning')
 | |
|     args = parser.parse_args()
 | |
| 
 | |
|     # Create the logger
 | |
|     logger = logging.getLogger('mingw')
 | |
|     handler = logging.StreamHandler()
 | |
|     formatter = logging.Formatter('%(message)s')
 | |
|     handler.setFormatter(formatter)
 | |
|     logger.addHandler(handler)
 | |
|     logger.setLevel(logging.INFO)
 | |
|     if args.quiet:
 | |
|         logger.setLevel(logging.WARN)
 | |
|     if args.verbose:
 | |
|         logger.setLevel(logging.DEBUG)
 | |
| 
 | |
|     # Get MinGW
 | |
|     root_dir = root(location = args.location, arch = args.arch,
 | |
|         version = args.version, threading = args.threading,
 | |
|         exceptions = args.exceptions, revision = args.revision,
 | |
|         log = logger)
 | |
| 
 | |
|     sys.stdout.write('%s\n' % os.path.join(root_dir, 'bin'))
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     try:
 | |
|         main()
 | |
|     except IOError as e:
 | |
|         sys.stderr.write('IO error: %s\n' % e)
 | |
|         sys.exit(1)
 | |
|     except OSError as e:
 | |
|         sys.stderr.write('OS error: %s\n' % e)
 | |
|         sys.exit(1)
 | |
|     except KeyboardInterrupt as e:
 | |
|         sys.stderr.write('Killed\n')
 | |
|         sys.exit(1)
 |