From 1106f51640ee3cec107a8ac904a818767c21a9aa Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Sun, 29 Mar 2020 20:34:08 +0300 Subject: project.boost: first-class MinGW-w64 support --- project/boost/build.py | 32 +++++++------- project/boost/toolchain.py | 108 +++++++++++++++++++++++++++++++++++++++++++++ project/os.py | 5 +++ 3 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 project/boost/toolchain.py (limited to 'project') diff --git a/project/boost/build.py b/project/boost/build.py index a33a538..c9081eb 100644 --- a/project/boost/build.py +++ b/project/boost/build.py @@ -22,6 +22,7 @@ import sys import tempfile from project.boost.directory import BoostDir +from project.boost.toolchain import detect_toolchain from project.configuration import Configuration from project.linkage import Linkage from project.platform import Platform @@ -39,7 +40,7 @@ DEFAULT_B2_ARGS = ['-d0'] class BuildParameters: def __init__(self, boost_dir, build_dir=None, platforms=None, configurations=None, link=None, runtime_link=None, - b2_args=None): + b2_args=None, mingw=False): boost_dir = project.utils.normalize_path(boost_dir) if build_dir is not None: @@ -61,6 +62,7 @@ class BuildParameters: self.link = link self.runtime_link = runtime_link self.b2_args = b2_args + self.mingw = mingw @staticmethod def from_args(args): @@ -69,11 +71,12 @@ class BuildParameters: def enum_b2_args(self): with self._create_build_dir() as build_dir: for platform in self.platforms: - for configuration in self.configurations: - for link, runtime_link in self._linkage_options(): - yield self._build_params(build_dir, platform, - configuration, link, - runtime_link) + with detect_toolchain(platform, mingw=self.mingw) as toolchain: + for configuration in self.configurations: + for link, runtime_link in self._linkage_options(): + yield self._build_params(build_dir, toolchain, + configuration, link, + runtime_link) def _linkage_options(self): for link in self.link: @@ -102,13 +105,13 @@ class BuildParameters: logging.info('Removing build directory: %s', build_dir) return - def _build_params(self, build_dir, platform, configuration, link, runtime_link): + def _build_params(self, build_dir, toolchain, configuration, link, runtime_link): params = [] params.append(self._build_dir(build_dir)) - params.append(self._stagedir(platform, configuration)) + params.append(self._stagedir(toolchain, configuration)) + params += toolchain.get_b2_args() params.append(self._link(link)) params.append(self._runtime_link(runtime_link)) - params.append(self._address_model(platform)) params.append(self._variant(configuration)) params += self.b2_args return params @@ -117,7 +120,7 @@ class BuildParameters: def _build_dir(build_dir): return f'--build-dir={build_dir}' - def _stagedir(self, platform, configuration): + def _stagedir(self, toolchain, configuration): # Having different --stagedir values for every configuration/platform # combination is unnecessary on Windows. Even for older Boost versions # (when the binaries weren't tagged with their target platform) only a @@ -125,7 +128,7 @@ class BuildParameters: # versions, just a single --stagedir would do, as the binaries are # tagged with the target platform, as well as their configuration # (a.k.a. "variant" in Boost's terminology). Still, uniformity helps. - platform = str(platform) + platform = str(toolchain.platform) configuration = str(configuration) return f'--stagedir={os.path.join(self.stage_dir, platform, configuration)}' @@ -137,10 +140,6 @@ class BuildParameters: def _runtime_link(runtime_link): return f'runtime-link={runtime_link}' - @staticmethod - def _address_model(platform): - return f'address-model={platform.get_address_model()}' - @staticmethod def _variant(configuration): return f'variant={configuration.to_boost_variant()}' @@ -195,6 +194,9 @@ def _parse_args(argv=None): nargs='*', default=[], help='additional b2 arguments, to be passed verbatim') + parser.add_argument('--mingw', action='store_true', + help='build using MinGW-w64') + return parser.parse_args(argv) diff --git a/project/boost/toolchain.py b/project/boost/toolchain.py new file mode 100644 index 0000000..9cc452b --- /dev/null +++ b/project/boost/toolchain.py @@ -0,0 +1,108 @@ +# Copyright (c) 2020 Egor Tensin +# This file is part of the "cmake-common" project. +# For details, see https://github.com/egor-tensin/cmake-common. +# Distributed under the MIT License. + +R'''Compiler detection. + +It is assumed that Boost.Build is good enough to detect both GCC on Linux and +MSVC on Windows. From that point on, it's just a matter of setting the correct +address-model= value. + +But I also frequently use MinGW-w64, and the most convinient way to use it that +I know is making a "user config" and passing it to b2 using the --user-config +parameter. +''' + +import abc +from contextlib import contextmanager +import logging +import tempfile + +import project.os + + +class Toolchain(abc.ABC): + def __init__(self, platform): + self.platform = platform + + @abc.abstractmethod + def get_b2_args(self): + pass + + +class NativeToolchain(Toolchain): + def get_b2_args(self): + return [f'address-model={self.platform.get_address_model()}'] + + +class MingwToolchain(Toolchain): + TAG = 'custom' + + def __init__(self, platform, config_path): + super().__init__(platform) + self.config_path = config_path + + def get_b2_args(self): + return [f'--user-config={self.config_path}', f'toolset=gcc-{MingwToolchain.TAG}'] + + +def _native_toolchain(platform): + return NativeToolchain(platform) + + +def _get_mingw_prefix(platform): + target_arch = platform.get_address_model() + if target_arch == 32: + return 'i686' + if target_arch == 64: + return 'x86_64' + raise RuntimeError(f'unexpected address model: {target_arch}') + + +def _get_mingw_path(platform): + prefix = _get_mingw_prefix(platform) + ext = '' + if project.os.on_windows_like(): + # Boost.Build wants the .exe extension at the end on Cygwin. + ext = '.exe' + path = f'{prefix}-w64-mingw32-g++{ext}' + return path + + +def _format_user_config(tag, compiler, **kwargs): + features = (f'<{k}>{v}' for k, v in kwargs.items()) + features = ' '.join(features) + return f'using gcc : {tag} : {compiler} : {features} ;' + + +def _format_mingw_user_config(platform): + compiler = _get_mingw_path(platform) + features = { + 'target-os': 'windows', + 'address-model': platform.get_address_model(), + } + return _format_user_config(MingwToolchain.TAG, compiler, **features) + + +@contextmanager +def _mingw_toolchain(platform): + tmp = tempfile.NamedTemporaryFile(mode='w', prefix='mingw_w64_', suffix='.jam') + with tmp as file: + config = _format_mingw_user_config(platform) + logging.info('Using user config:\n%s', config) + file.write(config) + file.flush() + try: + yield MingwToolchain(platform, file.name) + finally: + logging.info('Removing temporary user config file') + + +@contextmanager +def detect_toolchain(platform, mingw=False): + if mingw: + with _mingw_toolchain(platform) as toolchain: + yield toolchain + else: + yield _native_toolchain(platform) diff --git a/project/os.py b/project/os.py index 86ccaad..2f5f088 100644 --- a/project/os.py +++ b/project/os.py @@ -31,6 +31,11 @@ def on_windows(): return OS.current() is OS.WINDOWS +def on_windows_like(): + os = OS.current() + return os is OS.WINDOWS or os is OS.CYGWIN + + def on_linux_like(): os = OS.current() return os is OS.LINUX or os is OS.CYGWIN -- cgit v1.2.3