-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MinGW port #64
MinGW port #64
Changes from all commits
6880244
dd61391
6bc54ba
65a5ebd
05eb1f2
b2a43b4
07de09c
b35b581
60910b3
36d7dc6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,14 @@ | ||
*.a | ||
*.so | ||
*.so.?* | ||
*.dll | ||
*.exe | ||
*.dylib | ||
*.cmake | ||
!/cmake/*.cmake | ||
*~ | ||
*.pyc | ||
__pycache__ | ||
|
||
# cmake files. | ||
/Testing | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
version: '{build}' | ||
|
||
configuration: | ||
- Static Debug | ||
- Static Release | ||
# - Shared Debug | ||
# - Shared Release | ||
|
||
platform: | ||
- x86 | ||
- x64 | ||
|
||
environment: | ||
matrix: | ||
- compiler: gcc-4.9.2-posix | ||
# - compiler: gcc-4.8.4-posix | ||
# - compiler: msvc-12-seh | ||
|
||
install: | ||
# derive some extra information | ||
- for /f "tokens=1-2" %%a in ("%configuration%") do (@set "linkage=%%a") | ||
- for /f "tokens=1-2" %%a in ("%configuration%") do (@set "variant=%%b") | ||
- if "%linkage%"=="Shared" (set shared=YES) else (set shared=NO) | ||
- for /f "tokens=1-3 delims=-" %%a in ("%compiler%") do (@set "compiler_name=%%a") | ||
- for /f "tokens=1-3 delims=-" %%a in ("%compiler%") do (@set "compiler_version=%%b") | ||
- for /f "tokens=1-3 delims=-" %%a in ("%compiler%") do (@set "compiler_threading=%%c") | ||
- if "%platform%"=="x64" (set arch=x86_64) | ||
- if "%platform%"=="x86" (set arch=i686) | ||
# download the specific version of MinGW | ||
- if "%compiler_name%"=="gcc" (for /f %%a in ('python mingw.py --quiet --version "%compiler_version%" --arch "%arch%" --threading "%compiler_threading%" --location "C:\mingw-builds"') do @set "compiler_path=%%a") | ||
|
||
before_build: | ||
# Set up mingw commands | ||
- if "%compiler_name%"=="gcc" (set "generator=MinGW Makefiles") | ||
- if "%compiler_name%"=="gcc" (set "build=mingw32-make -j4") | ||
- if "%compiler_name%"=="gcc" (set "test=mingw32-make CTEST_OUTPUT_ON_FAILURE=1 test") | ||
# msvc specific commands | ||
# TODO :) | ||
# add the compiler path if needed | ||
- if not "%compiler_path%"=="" (set "PATH=%PATH%;%compiler_path%") | ||
# git bash conflicts with MinGW makefiles | ||
- if "%generator%"=="MinGW Makefiles" (set "PATH=%PATH:C:\Program Files (x86)\Git\bin=%") | ||
|
||
build_script: | ||
- cmake -G "%generator%" "-DCMAKE_BUILD_TYPE=%variant%" "-DBENCHMARK_ENABLE_SHARED=%shared%" | ||
- cmd /c "%build%" | ||
|
||
test_script: | ||
- cmd /c "%test%" | ||
|
||
matrix: | ||
fast_finish: true | ||
|
||
cache: | ||
- C:\mingw-builds |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,320 @@ | ||
#! /usr/bin/env python | ||
# encoding: utf-8 | ||
|
||
import argparse | ||
import errno | ||
import logging | ||
import os | ||
import platform | ||
import re | ||
import sys | ||
import subprocess | ||
import tempfile | ||
|
||
try: | ||
import winreg | ||
except ImportError: | ||
import _winreg as winreg | ||
try: | ||
import urllib.request as request | ||
except ImportError: | ||
import urllib as request | ||
try: | ||
import urllib.parse as parse | ||
except ImportError: | ||
import urlparse as parse | ||
|
||
class EmptyLogger(object): | ||
''' | ||
Provides an implementation that performs no logging | ||
''' | ||
def debug(self, *k, **kw): | ||
pass | ||
def info(self, *k, **kw): | ||
pass | ||
def warn(self, *k, **kw): | ||
pass | ||
def error(self, *k, **kw): | ||
pass | ||
def critical(self, *k, **kw): | ||
pass | ||
def setLevel(self, *k, **kw): | ||
pass | ||
|
||
urls = ( | ||
'http://downloads.sourceforge.net/project/mingw-w64/Toolchains%20' | ||
'targetting%20Win32/Personal%20Builds/mingw-builds/installer/' | ||
'repository.txt', | ||
'http://downloads.sourceforge.net/project/mingwbuilds/host-windows/' | ||
'repository.txt' | ||
) | ||
''' | ||
A list of mingw-build repositories | ||
''' | ||
|
||
def repository(urls = urls, log = EmptyLogger()): | ||
''' | ||
Downloads and parse mingw-build repository files and parses them | ||
''' | ||
log.info('getting mingw-builds repository') | ||
versions = {} | ||
re_sourceforge = re.compile(r'http://sourceforge.net/projects/([^/]+)/files') | ||
re_sub = r'http://downloads.sourceforge.net/project/\1' | ||
for url in urls: | ||
log.debug(' - requesting: %s', url) | ||
socket = request.urlopen(url) | ||
repo = socket.read() | ||
if not isinstance(repo, str): | ||
repo = repo.decode(); | ||
socket.close() | ||
for entry in repo.split('\n')[:-1]: | ||
value = entry.split('|') | ||
version = tuple([int(n) for n in value[0].strip().split('.')]) | ||
version = versions.setdefault(version, {}) | ||
arch = value[1].strip() | ||
if arch == 'x32': | ||
arch = 'i686' | ||
elif arch == 'x64': | ||
arch = 'x86_64' | ||
arch = version.setdefault(arch, {}) | ||
threading = arch.setdefault(value[2].strip(), {}) | ||
exceptions = threading.setdefault(value[3].strip(), {}) | ||
revision = exceptions.setdefault(int(value[4].strip()[3:]), | ||
re_sourceforge.sub(re_sub, value[5].strip())) | ||
return versions | ||
|
||
def find_in_path(file, path=None): | ||
''' | ||
Attempts to find an executable in the path | ||
''' | ||
if platform.system() == 'Windows': | ||
file += '.exe' | ||
if path is None: | ||
path = os.environ.get('PATH', '') | ||
if type(path) is type(''): | ||
path = path.split(os.pathsep) | ||
return list(filter(os.path.exists, | ||
map(lambda dir, file=file: os.path.join(dir, file), path))) | ||
|
||
def find_7zip(log = EmptyLogger()): | ||
''' | ||
Attempts to find 7zip for unpacking the mingw-build archives | ||
''' | ||
log.info('finding 7zip') | ||
path = find_in_path('7z') | ||
if not path: | ||
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\7-Zip') | ||
path, _ = winreg.QueryValueEx(key, 'Path') | ||
path = [os.path.join(path, '7z.exe')] | ||
log.debug('found \'%s\'', path[0]) | ||
return path[0] | ||
|
||
find_7zip() | ||
|
||
def unpack(archive, location, log = EmptyLogger()): | ||
''' | ||
Unpacks a mingw-builds archive | ||
''' | ||
sevenzip = find_7zip(log) | ||
log.info('unpacking %s', os.path.basename(archive)) | ||
cmd = [sevenzip, 'x', archive, '-o' + location, '-y'] | ||
log.debug(' - %r', cmd) | ||
with open(os.devnull, 'w') as devnull: | ||
subprocess.check_call(cmd, stdout = devnull) | ||
|
||
def download(url, location, log = EmptyLogger()): | ||
''' | ||
Downloads and unpacks a mingw-builds archive | ||
''' | ||
log.info('downloading MinGW') | ||
log.debug(' - url: %s', url) | ||
log.debug(' - location: %s', location) | ||
|
||
re_content = re.compile(r'attachment;[ \t]*filename=(")?([^"]*)(")?[\r\n]*') | ||
|
||
stream = request.urlopen(url) | ||
try: | ||
content = stream.getheader('Content-Disposition') or '' | ||
except AttributeError: | ||
content = stream.headers.getheader('Content-Disposition') or '' | ||
matches = re_content.match(content) | ||
if matches: | ||
filename = matches.group(2) | ||
else: | ||
parsed = parse.urlparse(stream.geturl()) | ||
filename = os.path.basename(parsed.path) | ||
|
||
try: | ||
os.makedirs(location) | ||
except OSError as e: | ||
if e.errno == errno.EEXIST and os.path.isdir(location): | ||
pass | ||
else: | ||
raise | ||
|
||
archive = os.path.join(location, filename) | ||
with open(archive, 'wb') as out: | ||
while True: | ||
buf = stream.read(1024) | ||
if not buf: | ||
break | ||
out.write(buf) | ||
unpack(archive, location, log = log) | ||
os.remove(archive) | ||
|
||
possible = os.path.join(location, 'mingw64') | ||
if not os.path.exists(possible): | ||
possible = os.path.join(location, 'mingw32') | ||
if not os.path.exists(possible): | ||
raise ValueError('Failed to find unpacked MinGW: ' + possible) | ||
return possible | ||
|
||
def root(location = None, arch = None, version = None, threading = None, | ||
exceptions = None, revision = None, log = EmptyLogger()): | ||
''' | ||
Returns the root folder of a specific version of the mingw-builds variant | ||
of gcc. Will download the compiler if needed | ||
''' | ||
|
||
# Get the repository if we don't have all the information | ||
if not (arch and version and threading and exceptions and revision): | ||
versions = repository(log = log) | ||
|
||
# Determine some defaults | ||
version = version or max(versions.keys()) | ||
if not arch: | ||
arch = platform.machine().lower() | ||
if arch == 'x86': | ||
arch = 'i686' | ||
elif arch == 'amd64': | ||
arch = 'x86_64' | ||
if not threading: | ||
keys = versions[version][arch].keys() | ||
if 'posix' in keys: | ||
threading = 'posix' | ||
elif 'win32' in keys: | ||
threading = 'win32' | ||
else: | ||
threading = keys[0] | ||
if not exceptions: | ||
keys = versions[version][arch][threading].keys() | ||
if 'seh' in keys: | ||
exceptions = 'seh' | ||
elif 'sjlj' in keys: | ||
exceptions = 'sjlj' | ||
else: | ||
exceptions = keys[0] | ||
if revision == None: | ||
revision = max(versions[version][arch][threading][exceptions].keys()) | ||
if not location: | ||
location = os.path.join(tempfile.gettempdir(), 'mingw-builds') | ||
|
||
# Get the download url | ||
url = versions[version][arch][threading][exceptions][revision] | ||
|
||
# Tell the user whatzzup | ||
log.info('finding MinGW %s', '.'.join(str(v) for v in version)) | ||
log.debug(' - arch: %s', arch) | ||
log.debug(' - threading: %s', threading) | ||
log.debug(' - exceptions: %s', exceptions) | ||
log.debug(' - revision: %s', revision) | ||
log.debug(' - url: %s', url) | ||
|
||
# Store each specific revision differently | ||
slug = '{version}-{arch}-{threading}-{exceptions}-rev{revision}' | ||
slug = slug.format( | ||
version = '.'.join(str(v) for v in version), | ||
arch = arch, | ||
threading = threading, | ||
exceptions = exceptions, | ||
revision = revision | ||
) | ||
if arch == 'x86_64': | ||
root_dir = os.path.join(location, slug, 'mingw64') | ||
elif arch == 'i686': | ||
root_dir = os.path.join(location, slug, 'mingw32') | ||
else: | ||
raise ValueError('Unknown MinGW arch: ' + arch) | ||
|
||
# Download if needed | ||
if not os.path.exists(root_dir): | ||
downloaded = download(url, os.path.join(location, slug), log = log) | ||
if downloaded != root_dir: | ||
raise ValueError('The location of mingw did not match\n%s\n%s' | ||
% (downloaded, root_dir)) | ||
|
||
return root_dir | ||
|
||
def str2ver(string): | ||
''' | ||
Converts a version string into a tuple | ||
''' | ||
try: | ||
version = tuple(int(v) for v in string.split('.')) | ||
if len(version) is not 3: | ||
raise ValueError() | ||
except ValueError: | ||
raise argparse.ArgumentTypeError( | ||
'please provide a three digit version string') | ||
return version | ||
|
||
def main(): | ||
''' | ||
Invoked when the script is run directly by the python interpreter | ||
''' | ||
parser = argparse.ArgumentParser( | ||
description = 'Downloads a specific version of MinGW', | ||
formatter_class = argparse.ArgumentDefaultsHelpFormatter | ||
) | ||
parser.add_argument('--location', | ||
help = 'the location to download the compiler to', | ||
default = os.path.join(tempfile.gettempdir(), 'mingw-builds')) | ||
parser.add_argument('--arch', required = True, choices = ['i686', 'x86_64'], | ||
help = 'the target MinGW architecture string') | ||
parser.add_argument('--version', type = str2ver, | ||
help = 'the version of GCC to download') | ||
parser.add_argument('--threading', choices = ['posix', 'win32'], | ||
help = 'the threading type of the compiler') | ||
parser.add_argument('--exceptions', choices = ['sjlj', 'seh', 'dwarf'], | ||
help = 'the method to throw exceptions') | ||
parser.add_argument('--revision', type=int, | ||
help = 'the revision of the MinGW release') | ||
group = parser.add_mutually_exclusive_group() | ||
group.add_argument('-v', '--verbose', action='store_true', | ||
help='increase the script output verbosity') | ||
group.add_argument('-q', '--quiet', action='store_true', | ||
help='only print errors and warning') | ||
args = parser.parse_args() | ||
|
||
# Create the logger | ||
logger = logging.getLogger('mingw') | ||
handler = logging.StreamHandler() | ||
formatter = logging.Formatter('%(message)s') | ||
handler.setFormatter(formatter) | ||
logger.addHandler(handler) | ||
logger.setLevel(logging.INFO) | ||
if args.quiet: | ||
logger.setLevel(logging.WARN) | ||
if args.verbose: | ||
logger.setLevel(logging.DEBUG) | ||
|
||
# Get MinGW | ||
root_dir = root(location = args.location, arch = args.arch, | ||
version = args.version, threading = args.threading, | ||
exceptions = args.exceptions, revision = args.revision, | ||
log = logger) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. given you always pass this, you can kill the EmptyLogger above. honestly, you can probably make the logger global and not even pass it through everywhere :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reasons it's there is so someone can use that python module as a library and turn logging on (or leave it off) when using the methods individually. For example I can then do: import mingw
versions = mingw.repository()
root = mingw.root(version = (4, 9, 2)) And that will give me all the mingw operations without logging anything or I could pass my own logger in. That being said - completely happy to remove all that stuff if you don't want it in there! I tried to make the script as reusable as possible, if people want to use it in other projects for appveyor support or integrate it into python based tool chains. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fair enough. i tend to err on the side of less code, minimal functionality, to avoid any subtle issues. however, this isn't on the critical path so it seems reasonable. |
||
|
||
sys.stdout.write('%s\n' % os.path.join(root_dir, 'bin')) | ||
|
||
if __name__ == '__main__': | ||
try: | ||
main() | ||
except IOError as e: | ||
sys.stderr.write('IO error: %s\n' % e) | ||
sys.exit(1) | ||
except OSError as e: | ||
sys.stderr.write('OS error: %s\n' % e) | ||
sys.exit(1) | ||
except KeyboardInterrupt as e: | ||
sys.stderr.write('Killed\n') | ||
sys.exit(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add some TODO (maybe) to limit the revision to versions we know we support?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well we should always just use the latest revision, which is what the script does. The revision of the mingw-builds is just when niXman rebuilds the compiler because there was a bug in the build. It's actually really hard to limit the revision as each compiler in the matrix has a different number of revisions.
However, I may have misunderstood your question. The
appveyor.yml
limits the compilers we test against.