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
31 def debug(self
, *k
, **kw
):
33 def info(self
, *k
, **kw
):
35 def warn(self
, *k
, **kw
):
37 def error(self
, *k
, **kw
):
39 def critical(self
, *k
, **kw
):
41 def setLevel(self
, *k
, **kw
):
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()))
86 def find_in_path(file, path
=None):
88 Attempts to find an executable in the path
90 if platform
.system() == 'Windows':
93 path
= os
.environ
.get('PATH', '')
94 if type(path
) is type(''):
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
)))
99 def find_7zip(log
= EmptyLogger()):
101 Attempts to find 7zip for unpacking the mingw-build archives
103 log
.info('finding 7zip')
104 path
= find_in_path('7z')
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
118 sevenzip
= find_7zip(log
)
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
179 # Get the repository if we don't have all the information
180 if not (arch
and version
and threading
and exceptions
and revision
):
181 versions
= repository(log
= log
)
183 # Determine some defaults
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')
212 # Get the download url
213 url
= versions
[version
][arch
][threading
][exceptions
][revision
]
215 # Tell the user whatzzup
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
)
223 # Store each specific revision differently
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')