Merge pull request #64 from mattyclarkson/nt
MinGW support
diff --git a/.gitignore b/.gitignore
index ac46434..b538747 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,14 @@
*.a
*.so
*.so.?*
+*.dll
+*.exe
*.dylib
*.cmake
!/cmake/*.cmake
*~
+*.pyc
+__pycache__
# cmake files.
/Testing
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..34bdc5f
--- /dev/null
+++ b/appveyor.yml
@@ -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
diff --git a/mingw.py b/mingw.py
new file mode 100644
index 0000000..706ad55
--- /dev/null
+++ b/mingw.py
@@ -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)
+
+ 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)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 40cd9ff..818e6d5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -32,6 +32,11 @@
SOVERSION ${GENERIC_LIB_SOVERSION}
)
+# We need extra libraries on Windows
+if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
+ target_link_libraries(benchmark Shlwapi)
+endif()
+
# Install target (will install the library to specified CMAKE_INSTALL_PREFIX variable)
install(
TARGETS benchmark
diff --git a/src/benchmark.cc b/src/benchmark.cc
index 65f3202..855eb6d 100644
--- a/src/benchmark.cc
+++ b/src/benchmark.cc
@@ -13,9 +13,12 @@
// limitations under the License.
#include "benchmark/benchmark.h"
+#include "internal_macros.h"
#include <sys/time.h>
+#ifndef OS_WINDOWS
#include <sys/resource.h>
+#endif
#include <unistd.h>
#include <cstdlib>
diff --git a/src/colorprint.cc b/src/colorprint.cc
index d240a0c..923bc87 100644
--- a/src/colorprint.cc
+++ b/src/colorprint.cc
@@ -19,6 +19,10 @@
#include "commandlineflags.h"
#include "internal_macros.h"
+#ifdef OS_WINDOWS
+#include <Windows.h>
+#endif
+
DECLARE_bool(color_print);
namespace benchmark {
diff --git a/src/sleep.cc b/src/sleep.cc
index 4dfb4d9..0d78a53 100644
--- a/src/sleep.cc
+++ b/src/sleep.cc
@@ -19,10 +19,14 @@
#include "internal_macros.h"
+#ifdef OS_WINDOWS
+#include <Windows.h>
+#endif
+
namespace benchmark {
#ifdef OS_WINDOWS
-// Window's _sleep takes milliseconds argument.
-void SleepForMilliseconds(int milliseconds) { _sleep(milliseconds); }
+// Window's Sleep takes milliseconds argument.
+void SleepForMilliseconds(int milliseconds) { Sleep(milliseconds); }
void SleepForSeconds(double seconds) {
SleepForMilliseconds(static_cast<int>(kNumMillisPerSecond * seconds));
}
diff --git a/src/sysinfo.cc b/src/sysinfo.cc
index c820c0a..4535586 100644
--- a/src/sysinfo.cc
+++ b/src/sysinfo.cc
@@ -13,13 +13,19 @@
// limitations under the License.
#include "sysinfo.h"
+#include "internal_macros.h"
+#ifdef OS_WINDOWS
+#include <Shlwapi.h>
+#include <Windows.h>
+#else
#include <fcntl.h>
#include <sys/resource.h>
#include <sys/types.h> // this header must be included before 'sys/sysctl.h' to avoid compilation error on FreeBSD
#include <sys/sysctl.h>
#include <sys/time.h>
#include <unistd.h>
+#endif
#include <cerrno>
#include <cstdio>
@@ -227,7 +233,6 @@
// TODO: also figure out cpuinfo_num_cpus
#elif defined OS_WINDOWS
-#pragma comment(lib, "shlwapi.lib") // for SHGetValue()
// In NT, read MHz from the registry. If we fail to do so or we're in win9x
// then make a crude estimate.
OSVERSIONINFO os;
@@ -238,7 +243,7 @@
SHGetValueA(HKEY_LOCAL_MACHINE,
"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0",
"~MHz", nullptr, &data, &data_size)))
- cpuinfo_cycles_per_second = (int64)data * (int64)(1000 * 1000); // was mhz
+ cpuinfo_cycles_per_second = (int64_t)data * (int64_t)(1000 * 1000); // was mhz
else
cpuinfo_cycles_per_second = EstimateCyclesPerSecond();
// TODO: also figure out cpuinfo_num_cpus
@@ -274,9 +279,9 @@
}
} // end namespace
-#ifndef OS_WINDOWS
// getrusage() based implementation of MyCPUUsage
static double MyCPUUsageRUsage() {
+#ifndef OS_WINDOWS
struct rusage ru;
if (getrusage(RUSAGE_SELF, &ru) == 0) {
return (static_cast<double>(ru.ru_utime.tv_sec) +
@@ -286,8 +291,25 @@
} else {
return 0.0;
}
+#else
+ HANDLE proc = GetCurrentProcess();
+ FILETIME creation_time;
+ FILETIME exit_time;
+ FILETIME kernel_time;
+ FILETIME user_time;
+ ULARGE_INTEGER kernel;
+ ULARGE_INTEGER user;
+ GetProcessTimes(proc, &creation_time, &exit_time, &kernel_time, &user_time);
+ kernel.HighPart = kernel_time.dwHighDateTime;
+ kernel.LowPart = kernel_time.dwLowDateTime;
+ user.HighPart = user_time.dwHighDateTime;
+ user.LowPart = user_time.dwLowDateTime;
+ return (static_cast<double>(kernel.QuadPart) +
+ static_cast<double>(user.QuadPart)) / 1.0E-7;
+#endif // OS_WINDOWS
}
+#ifndef OS_WINDOWS
static bool MyCPUUsageCPUTimeNsLocked(double* cputime) {
static int cputime_fd = -1;
if (cputime_fd == -1) {
@@ -313,8 +335,10 @@
*cputime = static_cast<double>(result) / 1e9;
return true;
}
+#endif // OS_WINDOWS
double MyCPUUsage() {
+#ifndef OS_WINDOWS
{
std::lock_guard<std::mutex> l(cputimens_mutex);
static bool use_cputime_ns = true;
@@ -328,10 +352,12 @@
use_cputime_ns = false;
}
}
+#endif // OS_WINDOWS
return MyCPUUsageRUsage();
}
double ChildrenCPUUsage() {
+#ifndef OS_WINDOWS
struct rusage ru;
if (getrusage(RUSAGE_CHILDREN, &ru) == 0) {
return (static_cast<double>(ru.ru_utime.tv_sec) +
@@ -341,8 +367,11 @@
} else {
return 0.0;
}
-}
+#else
+ // TODO: Not sure what this even means on Windows
+ return 0.0;
#endif // OS_WINDOWS
+}
double CyclesPerSecond(void) {
std::call_once(cpuinfo_init, InitializeSystemInfo);