From 8a8941b853ba5ccecc65cf55222c6c129e92d675 Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Mon, 30 Mar 2020 00:13:20 +0300 Subject: project: minor-ish refactoring --- project/boost/archive.py | 16 ++---- project/boost/build.py | 4 +- project/boost/toolchain.py | 119 +++++++++++++++++++++------------------------ project/cmake/build.py | 8 ++- project/os.py | 8 +++ project/platform.py | 22 ++++++--- project/utils.py | 35 ++++++++++++- 7 files changed, 122 insertions(+), 90 deletions(-) diff --git a/project/boost/archive.py b/project/boost/archive.py index e4c85e0..5316005 100644 --- a/project/boost/archive.py +++ b/project/boost/archive.py @@ -8,9 +8,9 @@ from contextlib import contextmanager import logging import os.path import shutil -import tempfile from project.boost.directory import BoostDir +from project.utils import temp_file class Archive: @@ -75,15 +75,7 @@ class TemporaryStorage(ArchiveStorage): @contextmanager def write_archive(self, version, contents): - temp = tempfile.NamedTemporaryFile(prefix=f'boost_{version}_', - suffix=version.archive_ext, - dir=self._dir, delete=False) - with temp as dest: - path = dest.name - logging.info('Writing Boost archive: %s', path) - dest.write(contents) - try: + tmp = temp_file(contents, prefix=f'boost_{version}_', + suffix=version.archive_ext, dir=self._dir) + with tmp as path: yield path - finally: - logging.info('Removing temporary Boost archive: %s', path) - os.remove(path) diff --git a/project/boost/build.py b/project/boost/build.py index c9081eb..6d2b897 100644 --- a/project/boost/build.py +++ b/project/boost/build.py @@ -22,7 +22,7 @@ import sys import tempfile from project.boost.directory import BoostDir -from project.boost.toolchain import detect_toolchain +from project.boost.toolchain import Toolchain from project.configuration import Configuration from project.linkage import Linkage from project.platform import Platform @@ -71,7 +71,7 @@ class BuildParameters: def enum_b2_args(self): with self._create_build_dir() as build_dir: for platform in self.platforms: - with detect_toolchain(platform, mingw=self.mingw) as toolchain: + with Toolchain.detect(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, diff --git a/project/boost/toolchain.py b/project/boost/toolchain.py index 9cc452b..cacc2c2 100644 --- a/project/boost/toolchain.py +++ b/project/boost/toolchain.py @@ -17,9 +17,9 @@ parameter. import abc from contextlib import contextmanager import logging -import tempfile import project.os +from project.utils import temp_file class Toolchain(abc.ABC): @@ -30,13 +30,28 @@ class Toolchain(abc.ABC): def get_b2_args(self): pass + @staticmethod + @contextmanager + def detect(platform, mingw=False): + if mingw: + with MinGW.setup(platform) as toolchain: + yield toolchain + else: + yield Native(platform) -class NativeToolchain(Toolchain): + @staticmethod + 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} ;' + + +class Native(Toolchain): def get_b2_args(self): return [f'address-model={self.platform.get_address_model()}'] -class MingwToolchain(Toolchain): +class MinGW(Toolchain): TAG = 'custom' def __init__(self, platform, config_path): @@ -44,65 +59,41 @@ class MingwToolchain(Toolchain): 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) + return [f'--user-config={self.config_path}', f'toolset=gcc-{MinGW.TAG}'] + + @staticmethod + def _get_compiler_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}') + + @staticmethod + def _get_compiler_path(platform): + prefix = MinGW._get_compiler_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 + + @staticmethod + def _format_mingw_user_config(platform): + compiler = MinGW._get_compiler_path(platform) + features = { + 'target-os': 'windows', + 'address-model': platform.get_address_model(), + } + return Toolchain._format_user_config(MinGW.TAG, compiler, **features) + + @staticmethod + @contextmanager + def setup(platform): + config = MinGW._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) + tmp = temp_file(config, mode='w', prefix='mingw_w64_', suffix='.jam') + with tmp as path: + yield MinGW(platform, path) diff --git a/project/cmake/build.py b/project/cmake/build.py index 5355c0b..df7b764 100644 --- a/project/cmake/build.py +++ b/project/cmake/build.py @@ -45,6 +45,10 @@ from project.configuration import Configuration import project.utils +def run_cmake(cmake_args): + return project.utils.run(['cmake'] + cmake_args) + + class GenerationPhase: def __init__(self, build_dir, params): self.build_dir = build_dir @@ -66,7 +70,7 @@ class GenerationPhase: return result def run(self): - project.utils.run_cmake(self._cmake_args()) + run_cmake(self._cmake_args()) class BuildPhase: @@ -87,7 +91,7 @@ class BuildPhase: return result def run(self): - project.utils.run_cmake(self._cmake_args()) + run_cmake(self._cmake_args()) class BuildParameters: diff --git a/project/os.py b/project/os.py index 2f5f088..53b64e1 100644 --- a/project/os.py +++ b/project/os.py @@ -36,6 +36,14 @@ def on_windows_like(): return os is OS.WINDOWS or os is OS.CYGWIN +def on_linux(): + return OS.current() is OS.LINUX + + def on_linux_like(): os = OS.current() return os is OS.LINUX or os is OS.CYGWIN + + +def on_cygwin(): + return OS.current() is OS.CYGWIN diff --git a/project/platform.py b/project/platform.py index 63f6231..249238e 100644 --- a/project/platform.py +++ b/project/platform.py @@ -5,37 +5,43 @@ import argparse from enum import Enum +import platform class Platform(Enum): - '''I only build for x86(-64), so here it goes. - - Win32 is just Visual Studio convention, it's effectively an alias for x86. - ''' + '''I only build for x86(-64), so here it goes.''' X86 = 'x86' X64 = 'x64' - WIN32 = 'Win32' def __str__(self): return self.value + @staticmethod + def native(): + # Source: https://stackoverflow.com/a/12578715/514684 + if platform.machine().endswith('64'): + return Platform.X64 + return Platform.X86 + @staticmethod def all(): - return (Platform.X86, Platform.X64) + return tuple(Platform) @staticmethod def parse(s): try: + if s == 'Win32': + # AppVeyor convention: + return Platform.X86 return Platform(s) except ValueError: raise argparse.ArgumentTypeError(f'invalid platform: {s}') def get_address_model(self): + '''Maps to Boost's address-model.''' if self is Platform.X86: return 32 if self is Platform.X64: return 64 - if self is Platform.WIN32: - return 32 raise NotImplementedError(f'unsupported platform: {self}') diff --git a/project/utils.py b/project/utils.py index 8c57f59..68327af 100644 --- a/project/utils.py +++ b/project/utils.py @@ -7,6 +7,7 @@ from contextlib import contextmanager import logging import os.path import subprocess +import tempfile def normalize_path(s): @@ -41,5 +42,35 @@ def run(cmd_line): return subprocess.run(cmd_line, check=True) -def run_cmake(cmake_args): - return run(['cmake'] + cmake_args) +@contextmanager +def delete_on_error(path): + try: + yield + except: + logging.info('Removing temporary file: %s', path) + os.remove(path) + raise + + +@contextmanager +def delete(path): + try: + yield + finally: + logging.info('Removing temporary file: %s', path) + os.remove(path) + + +@contextmanager +def temp_file(contents, **kwargs): + '''Make NamedTemporaryFile usable on Windows. + + It can't be opened a second time on Windows, hence this silliness. + ''' + tmp = tempfile.NamedTemporaryFile(delete=False, **kwargs) + with tmp as file, delete_on_error(file.name): + path = file.name + logging.info('Writing temporary file: %s', path) + file.write(contents) + with delete(path): + yield path -- cgit v1.2.3