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