|
@@ -0,0 +1,320 @@
|
|
|
|
+#! /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)
|