From 39902041e7671a94abcf691fa4a769b5cb4fc4fb Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Mon, 30 Mar 2020 02:34:03 +0300 Subject: project.cmake: make it --platform aware --- .ci/Makefile | 10 ++--- project/boost/toolchain.py | 23 +--------- project/ci/appveyor/cmake.py | 3 +- project/ci/travis/cmake.py | 6 +++ project/cmake/build.py | 40 ++++++++++------- project/cmake/toolchain.py | 105 +++++++++++++++++++++++++++++++++++++++++++ project/mingw.py | 37 +++++++++++++++ project/platform.py | 8 ++++ 8 files changed, 189 insertions(+), 43 deletions(-) create mode 100644 project/cmake/toolchain.py create mode 100644 project/mingw.py diff --git a/.ci/Makefile b/.ci/Makefile index 0b768cf..ca41fef 100644 --- a/.ci/Makefile +++ b/.ci/Makefile @@ -95,7 +95,7 @@ echo/%/finished: FORCE .PHONY: simple/build simple/build: echo/simple/build - "$(python)" -m project.cmake.build --install "$(install_prefix)/simple" --configuration Release -- examples/simple $(x64_args) + "$(python)" -m project.cmake.build --install "$(install_prefix)/simple" --platform x64 --configuration Release examples/simple .PHONY: simple/run simple/run: echo/simple/run @@ -112,7 +112,7 @@ simple: simple/build simple/run simple/verify echo/simple/finished .PHONY: static/build static/build: echo/static/build - "$(python)" -m project.cmake.build --install "$(install_prefix)/static" --configuration Debug -- examples/static $(x86_args) + "$(python)" -m project.cmake.build --install "$(install_prefix)/static" --platform x86 --configuration Debug examples/static .PHONY: static/run static/run: echo/static/run @@ -129,7 +129,7 @@ static: static/build static/run static/verify echo/static/finished .PHONY: dynamic/build dynamic/build: echo/dynamic/build - "$(python)" -m project.cmake.build --install "$(install_prefix)/dynamic" --configuration RelWithDebInfo -- examples/dynamic $(x64_args) + "$(python)" -m project.cmake.build --install "$(install_prefix)/dynamic" --platform x64 --configuration RelWithDebInfo examples/dynamic .PHONY: dynamic/run # Windows can pick up DLLs in the same directory, otherwise we need to add them @@ -198,7 +198,7 @@ boost/58/ls: echo/boost/58/ls .PHONY: boost/58/exe/build boost/58/exe/build: echo/boost/58/exe/build - "$(python)" -m project.cmake.build --install "$(install_prefix)/boost_1_58_0" --configuration Debug -- examples/boost $(x86_args) -D "BOOST_ROOT=$(cwd)/boost_1_58_0" -D "BOOST_LIBRARYDIR=$(cwd)/boost_1_58_0/stage/x86/Debug/lib" + "$(python)" -m project.cmake.build --install "$(install_prefix)/boost_1_58_0" --platform x86 --configuration Debug -- examples/boost -D "BOOST_ROOT=$(cwd)/boost_1_58_0" -D "BOOST_LIBRARYDIR=$(cwd)/boost_1_58_0/stage/x86/Debug/lib" .PHONY: boost/58/exe/run # Boost should be linked statically, no need to adjust PATH: @@ -235,7 +235,7 @@ boost/72/ls: echo/boost/72/ls .PHONY: boost/72/exe/build boost/72/exe/build: echo/boost/72/exe/build - "$(python)" -m project.cmake.build --install "$(install_prefix)/boost_1_72_0" --configuration Release -- examples/boost $(x64_args) -D "BOOST_ROOT=$(cwd)/boost_1_72_0" -D "BOOST_LIBRARYDIR=$(cwd)/boost_1_72_0/stage/x64/Release/lib" -D Boost_USE_STATIC_LIBS=OFF + "$(python)" -m project.cmake.build --install "$(install_prefix)/boost_1_72_0" --platform x64 --configuration Release -- examples/boost -D "BOOST_ROOT=$(cwd)/boost_1_72_0" -D "BOOST_LIBRARYDIR=$(cwd)/boost_1_72_0/stage/x64/Release/lib" -D Boost_USE_STATIC_LIBS=OFF .PHONY: boost/72/exe/run # Boost is linked dynamically, we need to adjust PATH: diff --git a/project/boost/toolchain.py b/project/boost/toolchain.py index cacc2c2..a77c073 100644 --- a/project/boost/toolchain.py +++ b/project/boost/toolchain.py @@ -18,7 +18,7 @@ import abc from contextlib import contextmanager import logging -import project.os +import project.mingw from project.utils import temp_file @@ -61,28 +61,9 @@ class MinGW(Toolchain): def get_b2_args(self): 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) + compiler = project.mingw.get_gxx(platform) features = { 'target-os': 'windows', 'address-model': platform.get_address_model(), diff --git a/project/ci/appveyor/cmake.py b/project/ci/appveyor/cmake.py index c3d55d0..c1b851c 100644 --- a/project/ci/appveyor/cmake.py +++ b/project/ci/appveyor/cmake.py @@ -114,12 +114,13 @@ def build_appveyor(argv=None): args = _parse_args(argv) _check_appveyor() - cmake_args = ['-G', str(_get_generator()), '-A', str(_get_platform())] + cmake_args = ['-G', str(_get_generator())] cmake_args += args.cmake_args params = BuildParameters(_get_src_dir(), build_dir=_get_build_dir(), install_dir=args.install_dir, + platform=_get_platform(), configuration=_get_configuration(), cmake_args=cmake_args) build(params) diff --git a/project/ci/travis/cmake.py b/project/ci/travis/cmake.py index 1d6eed4..47177bc 100644 --- a/project/ci/travis/cmake.py +++ b/project/ci/travis/cmake.py @@ -19,6 +19,7 @@ import sys from project.cmake.build import BuildParameters, build from project.configuration import Configuration +from project.platform import Platform from project.utils import setup_logging @@ -41,6 +42,10 @@ def _get_build_dir(): return os.path.join(_env('HOME'), 'build') +def _get_platform(): + return Platform.parse(_env('platform')) + + def _get_configuration(): return Configuration.parse(_env('configuration')) @@ -68,6 +73,7 @@ def build_travis(argv=None): params = BuildParameters(_get_src_dir(), build_dir=_get_build_dir(), install_dir=args.install_dir, + platform=_get_platform(), configuration=_get_configuration(), cmake_args=args.cmake_args) build(params) diff --git a/project/cmake/build.py b/project/cmake/build.py index 101a4ea..5127c3d 100644 --- a/project/cmake/build.py +++ b/project/cmake/build.py @@ -17,18 +17,6 @@ A simple usage example: $ ./path/to/somewhere/bin/foo foo - -Picking the target platform is build system-specific. On Visual Studio, pass -the target platform using the `-A` flag like this: - - > python -m project.cmake.build --install path\to\somewhere -- examples\simple -A Win32 - ... - -Using GCC-like compilers, the best way is to use CMake toolchain files (see -toolchains/cmake in this repository for examples). - - $ python -m project.cmake.build --install path/to/somewhere -- examples/simple -D CMAKE_TOOLCHAIN_FILE="$( pwd )/toolchains/mingw-x86.cmake" - ... ''' import argparse @@ -39,7 +27,9 @@ import os.path import sys import tempfile +from project.cmake.toolchain import Toolchain from project.configuration import Configuration +from project.platform import Platform from project.utils import normalize_path, run, setup_logging @@ -52,7 +42,8 @@ def run_cmake(cmake_args): class GenerationPhase: def __init__(self, src_dir, build_dir, install_dir=None, - configuration=DEFAULT_CONFIGURATION, cmake_args=None): + platform=None, configuration=DEFAULT_CONFIGURATION, + mingw=False, cmake_args=None): src_dir = normalize_path(src_dir) build_dir = normalize_path(build_dir) if install_dir is not None: @@ -62,20 +53,24 @@ class GenerationPhase: self.src_dir = src_dir self.build_dir = build_dir self.install_dir = install_dir + self.platform = platform self.configuration = configuration + self.mingw = mingw self.cmake_args = cmake_args - def _cmake_args(self): + def _cmake_args(self, toolchain): result = [] if self.install_dir is not None: result += ['-D', f'CMAKE_INSTALL_PREFIX={self.install_dir}'] + result += toolchain.get_cmake_args() result += ['-D', f'CMAKE_BUILD_TYPE={self.configuration}'] result += self.cmake_args result += [f'-B{self.build_dir}', f'-H{self.src_dir}'] return result def run(self): - run_cmake(self._cmake_args()) + with Toolchain.detect(self.platform, self.build_dir, mingw=self.mingw) as toolchain: + run_cmake(self._cmake_args(toolchain)) class BuildPhase: @@ -101,7 +96,8 @@ class BuildPhase: class BuildParameters: def __init__(self, src_dir, build_dir=None, install_dir=None, - configuration=DEFAULT_CONFIGURATION, cmake_args=None): + platform=None, configuration=DEFAULT_CONFIGURATION, + mingw=False, cmake_args=None): src_dir = normalize_path(src_dir) if build_dir is not None: @@ -113,7 +109,9 @@ class BuildParameters: self.src_dir = src_dir self.build_dir = build_dir self.install_dir = install_dir + self.platform = platform self.configuration = configuration + self.mingw = mingw self.cmake_args = cmake_args @staticmethod @@ -140,7 +138,9 @@ def build(params): with params.create_build_dir() as build_dir: gen_phase = GenerationPhase(params.src_dir, build_dir, install_dir=params.install_dir, + platform=params.platform, configuration=params.configuration, + mingw=params.mingw, cmake_args=params.cmake_args) gen_phase.run() build_phase = BuildPhase(build_dir, install_dir=params.install_dir, @@ -164,11 +164,19 @@ def _parse_args(argv=None): type=normalize_path, help='install directory') + platform_options = '/'.join(map(str, Platform.all())) configuration_options = '/'.join(map(str, Configuration.all())) + + parser.add_argument('--platform', metavar='PLATFORM', + type=Platform.parse, + help=f'target platform ({platform_options})') parser.add_argument('--configuration', metavar='CONFIG', type=Configuration.parse, default=DEFAULT_CONFIGURATION, help=f'build configuration ({configuration_options})') + parser.add_argument('--mingw', action='store_true', + help='build using MinGW-w64') + parser.add_argument('src_dir', metavar='DIR', type=normalize_path, help='source directory') diff --git a/project/cmake/toolchain.py b/project/cmake/toolchain.py new file mode 100644 index 0000000..073cd1b --- /dev/null +++ b/project/cmake/toolchain.py @@ -0,0 +1,105 @@ +# 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. + +import abc +from contextlib import contextmanager +import os.path + +import project.mingw +from project.platform import Platform +from project.os import on_windows + + +class Toolchain(abc.ABC): + @abc.abstractmethod + def get_cmake_args(self): + pass + + @staticmethod + @contextmanager + def detect(platform, build_dir, mingw=False): + if mingw: + with MinGW.setup(platform, build_dir) as toolchain: + yield toolchain + return + + if on_windows(): + # MSVC is assumed. + if platform is None: + yield Native() + return + yield MSVC(platform) + return + + with GCC.setup(platform, build_dir) as toolchain: + yield toolchain + return + + +class Native(Toolchain): + def get_cmake_args(self): + return [] + + +class MSVC(Toolchain): + def __init__(self, platform): + self.platform = platform + + def get_cmake_args(self): + return ['-A', self.platform.get_cmake_arch()] + + +class File(Toolchain): + def __init__(self, path): + self.path = path + + @staticmethod + def _get_path(build_dir): + return os.path.join(build_dir, 'custom_toolchain.cmake') + + def get_cmake_args(self): + return ['-D', f'CMAKE_TOOLCHAIN_FILE={self.path}'] + + +class GCC(File): + @staticmethod + def _format(platform): + return f''' +set(CMAKE_C_COMPILER gcc) +set(CMAKE_C_FLAGS -m{platform.get_address_model()}) +set(CMAKE_CXX_COMIPLER g++) +set(CMAKE_CXX_FLAGS -m{platform.get_address_model()}) +''' + + @staticmethod + @contextmanager + def setup(platform, build_dir): + if platform is None: + yield Native() + return + path = File._get_path(build_dir) + with open(path, mode='w') as file: + file.write(GCC._format(platform)) + yield GCC(path) + + +class MinGW(File): + @staticmethod + def _format(platform): + return f''' +set(CMAKE_C_COMPILER {project.mingw.get_gcc(platform)}) +set(CMAKE_CXX_COMPILER {project.mingw.get_gxx(platform)}) +set(CMAKE_RC_COMILER {project.mingw.get_windres(platform)}) +set(CMAKE_SYSTEM_NAME Windows) +''' + + @staticmethod + @contextmanager + def setup(platform, build_dir): + platform = platform or Platform.native() + path = File._get_path(build_dir) + with open(path, mode='w') as file: + file.write(MinGW._format(platform)) + yield MinGW(path) diff --git a/project/mingw.py b/project/mingw.py new file mode 100644 index 0000000..1e136cd --- /dev/null +++ b/project/mingw.py @@ -0,0 +1,37 @@ +# 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. + +from project.os import on_windows_like + + +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}') + + +def _get(platform, what): + prefix = _get_compiler_prefix(platform) + ext = '' + if on_windows_like(): + # Boost.Build wants the .exe extension at the end on Cygwin. + ext = '.exe' + path = f'{prefix}-w64-mingw32-{what}{ext}' + return path + + +def get_gcc(platform): + return _get(platform, 'gcc') + + +def get_gxx(platform): + return _get(platform, 'g++') + + +def get_windres(platform): + return _get(platform, 'windres') diff --git a/project/platform.py b/project/platform.py index 249238e..8eb7f3e 100644 --- a/project/platform.py +++ b/project/platform.py @@ -45,3 +45,11 @@ class Platform(Enum): if self is Platform.X64: return 64 raise NotImplementedError(f'unsupported platform: {self}') + + def get_cmake_arch(self): + '''Maps to CMake's -A argument for MSVC.''' + if self is Platform.X86: + return 'Win32' + if self is Platform.X64: + return 'x64' + raise NotImplementedError(f'unsupported platform: {self}') -- cgit v1.2.3