17 import _winreg
as winreg
19 import urllib.request
as request
21 import urllib
as request
23 import urllib.parse
as parse
25 import urlparse
as parse
29 Provides an implementation that performs no logging
45 'http://downloads.sourceforge.net/project/mingw-w64/Toolchains%20'
46 'targetting%20Win32/Personal%20Builds/mingw-builds/installer/'
48 'http://downloads.sourceforge.net/project/mingwbuilds/host-windows/'
52 A list of mingw-build repositories
57 Downloads and parse mingw-build repository files and parses them
59 log.info(
'getting mingw-builds repository')
61 re_sourceforge = re.compile(
r'http://sourceforge.net/projects/([^/]+)/files')
62 re_sub =
r'http://downloads.sourceforge.net/project/\1'
64 log.debug(
' - requesting: %s', url)
65 socket = request.urlopen(url)
67 if not isinstance(repo, str):
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()
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()))
88 Attempts to find an executable in the path
90 if platform.system() ==
'Windows':
93 path = os.environ.get(
'PATH',
'')
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)))
101 Attempts to find 7zip for unpacking the mingw-build archives
103 log.info(
'finding 7zip')
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])
114 def unpack(archive, location, log = EmptyLogger()):
116 Unpacks a mingw-builds archive
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)
127 Downloads and unpacks a mingw-builds archive
129 log.info(
'downloading MinGW')
130 log.debug(
' - url: %s', url)
131 log.debug(
' - location: %s', location)
133 re_content = re.compile(
r'attachment;[ \t]*filename=(")?([^"]*)(")?[\r\n]*')
135 stream = request.urlopen(url)
137 content = stream.getheader(
'Content-Disposition')
or ''
138 except AttributeError:
139 content = stream.headers.getheader(
'Content-Disposition')
or ''
140 matches = re_content.match(content)
142 filename = matches.group(2)
144 parsed = parse.urlparse(stream.geturl())
145 filename = os.path.basename(parsed.path)
148 os.makedirs(location)
150 if e.errno == errno.EEXIST
and os.path.isdir(location):
155 archive = os.path.join(location, filename)
156 with open(archive,
'wb')
as out:
158 buf = stream.read(1024)
162 unpack(archive, location, log = log)
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)
172 def root(location = None, arch = None, version = None, threading = None,
173 exceptions = None, revision = None, log = EmptyLogger()):
175 Returns the root folder of a specific version of the mingw-builds variant
176 of gcc. Will download the compiler if needed
180 if not (arch
and version
and threading
and exceptions
and revision):
184 version = version
or max(versions.keys())
186 arch = platform.machine().lower()
189 elif arch ==
'amd64':
192 keys = versions[version][arch].
keys()
195 elif 'win32' in keys:
200 keys = versions[version][arch][threading].
keys()
208 revision =
max(versions[version][arch][threading][exceptions].
keys())
210 location = os.path.join(tempfile.gettempdir(),
'mingw-builds')
213 url = versions[version][arch][threading][exceptions][revision]
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)
224 slug =
'{version}-{arch}-{threading}-{exceptions}-rev{revision}'
226 version =
'.'.join(
str(v)
for v
in version),
228 threading = threading,
229 exceptions = exceptions,
233 root_dir = os.path.join(location, slug,
'mingw64')
235 root_dir = os.path.join(location, slug,
'mingw32')
237 raise ValueError(
'Unknown MinGW arch: ' + arch)
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))
250 Converts a version string into a tuple
253 version = tuple(
int(v)
for v
in string.split(
'.'))
254 if len(version)
is not 3:
257 raise argparse.ArgumentTypeError(
258 'please provide a three digit version string')
263 Invoked when the script is run directly by the python interpreter
265 parser = argparse.ArgumentParser(
266 description =
'Downloads a specific version of MinGW',
267 formatter_class = argparse.ArgumentDefaultsHelpFormatter
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()
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)
297 logger.setLevel(logging.WARN)
299 logger.setLevel(logging.DEBUG)
302 root_dir =
root(location = args.location, arch = args.arch,
303 version = args.version, threading = args.threading,
304 exceptions = args.exceptions, revision = args.revision,
307 sys.stdout.write(
'%s\n' % os.path.join(root_dir,
'bin'))
309 if __name__ ==
'__main__':
313 sys.stderr.write(
'IO error: %s\n' % e)
316 sys.stderr.write(
'OS error: %s\n' % e)
318 except KeyboardInterrupt
as e:
319 sys.stderr.write(
'Killed\n')