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