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
27 class EmptyLogger(object):
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
55 def repository(urls = urls, log = EmptyLogger()):
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)
125 def download(url, location, log = EmptyLogger()):
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')