From d7bd2e3d50d5b5b51f5af28343a6f03ed2efbcfc Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Fri, 3 Jan 2020 18:00:24 +0300 Subject: rearrange all files completely --- cmake/build/build.py | 199 ++++++++++++++++++++++++++++++++ cmake/build/ci/appveyor.py | 157 ++++++++++++++++++++++++++ cmake/build/ci/travis.py | 90 +++++++++++++++ cmake/common.cmake | 212 +++++++++++++++++++++++++++++++++++ cmake/toolchains/README.md | 3 + cmake/toolchains/gcc-x64.cmake | 4 + cmake/toolchains/gcc-x86.cmake | 4 + cmake/toolchains/mingw-w64-x64.cmake | 3 + cmake/toolchains/mingw-w64-x86.cmake | 3 + 9 files changed, 675 insertions(+) create mode 100755 cmake/build/build.py create mode 100755 cmake/build/ci/appveyor.py create mode 100755 cmake/build/ci/travis.py create mode 100644 cmake/common.cmake create mode 100644 cmake/toolchains/README.md create mode 100644 cmake/toolchains/gcc-x64.cmake create mode 100644 cmake/toolchains/gcc-x86.cmake create mode 100644 cmake/toolchains/mingw-w64-x64.cmake create mode 100644 cmake/toolchains/mingw-w64-x86.cmake (limited to 'cmake') diff --git a/cmake/build/build.py b/cmake/build/build.py new file mode 100755 index 0000000..0e352c3 --- /dev/null +++ b/cmake/build/build.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 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. + +# This script is used basically to invoke the CMake executable in a +# cross-platform way (provided the platform has Python 3, of course). +# The motivation was to merge my Travis and AppVeyor build scripts (largely +# similar, but written in bash and PowerShell, respectively). + +import argparse +import logging +from enum import Enum +import os +import os.path +import shutil +import subprocess +import sys +import tempfile + + +def _make_tmp_dir(**kwargs): + path = tempfile.mkdtemp(**kwargs) + logging.info('Created temporary directory: %s', path) + return path + + +def _log_rmtree_error(function, path, exc_info): + logging.error("Couldn't remove path '%s': %s", path, exc_info) + + +def _remove_dir(path): + logging.info('Removing directory: %s', path) + shutil.rmtree(path, onerror=_log_rmtree_error) + + +def _run_executable(cmd_line): + logging.info('Running executable: %s', cmd_line) + return subprocess.run(cmd_line, check=True) + + +def _run_cmake(cmake_args): + _run_executable(['cmake'] + cmake_args) + + +class Configuration(Enum): + DEBUG = 'Debug' + RELEASE = 'Release' + + def __str__(self): + return self.value + + +def _parse_configuration(s): + try: + return Configuration(s) + except ValueError: + raise argparse.ArgumentTypeError(f'invalid configuration: {s}') + + +class BuildDir: + def __init__(self, args): + self.path = args.build_dir + self.clean = args.clean_build_dir + self.tmp_dir = self.path is None + if self.tmp_dir: + self.path = self._make_build_dir() + + @staticmethod + def _make_build_dir(): + return _make_tmp_dir(prefix='build_') + + def __enter__(self): + return self + + def __exit__(self, *args): + if self.tmp_dir and self.clean: + _remove_dir(self.path) + + +class GenerationPhase: + def __init__(self, build_dir, args): + self.build_dir = build_dir + self.args = args + + def _cmake_args(self): + return self._to_cmake_args(self.build_dir, self.args) + + @staticmethod + def _to_cmake_args(build_dir, args): + result = [] + if args.install_dir is not None: + result += ['-D', f'CMAKE_INSTALL_PREFIX={args.install_dir}'] + if args.configuration is not None: + result += ['-D', f'CMAKE_BUILD_TYPE={args.configuration}'] + if args.cmake_args is not None: + result += args.cmake_args + result += [f'-B{build_dir.path}', f'-H{args.src_dir}'] + return result + + def run(self): + _run_cmake(self._cmake_args()) + + +class BuildPhase: + def __init__(self, build_dir, args): + self.build_dir = build_dir + self.args = args + + def _cmake_args(self): + return self._to_cmake_args(self.build_dir, self.args) + + @staticmethod + def _to_cmake_args(build_dir, args): + result = ['--build', build_dir.path] + if args.configuration is not None: + result += ['--config', str(args.configuration)] + if args.install_dir is not None: + result += ['--target', 'install'] + return result + + def run(self): + _run_cmake(self._cmake_args()) + + +class CleanPhase: + def __init__(self, build_dir, args): + self.build_dir = build_dir + self.args = args + + def _cmake_args(self): + return self._to_cmake_args(self.build_dir, self.args) + + @staticmethod + def _to_cmake_args(build_dir, args): + result = ['--build', build_dir.path] + if args.configuration is not None: + result += ['--config', str(args.configuration)] + result += ['--target', 'clean'] + return result + + def run(self): + if self.args.clean_build_dir: + _run_cmake(self._cmake_args()) + + +def _parse_args(argv=None): + if argv is None: + argv = sys.argv[1:] + logging.info('Command line arguments: %s', argv) + + parser = argparse.ArgumentParser(description='Build a CMake project') + parser.add_argument('--src', required=True, metavar='DIR', dest='src_dir', + help='source directory') + parser.add_argument('--build', metavar='DIR', dest='build_dir', + help='build directory (temporary directory if not specified)') + parser.add_argument('--install', metavar='DIR', dest='install_dir', + help='install directory') + parser.add_argument('--clean', action='store_true', dest='clean_build_dir', + help='clean the build directory (temporary directory will be removed)') + parser.add_argument('--configuration', metavar='CONFIG', + type=_parse_configuration, default=Configuration.DEBUG, + help='build configuration (i.e. Debug/Release)') + parser.add_argument('cmake_args', nargs='*', metavar='CMAKE_ARG', + help='additional CMake arguments, to be passed verbatim') + args = parser.parse_args(argv) + return args + + +def _setup_logging(): + logging.basicConfig( + format='%(asctime)s | %(levelname)s | %(message)s', + level=logging.INFO) + + +def build(argv=None): + args = _parse_args(argv) + with BuildDir(args) as build_dir: + gen_phase = GenerationPhase(build_dir, args) + gen_phase.run() + build_phase = BuildPhase(build_dir, args) + build_phase.run() + clean_phase = CleanPhase(build_dir, args) + clean_phase.run() + + +def main(argv=None): + _setup_logging() + try: + build(argv) + except Exception as e: + logging.exception(e) + raise + + +if __name__ == '__main__': + main() diff --git a/cmake/build/ci/appveyor.py b/cmake/build/ci/appveyor.py new file mode 100755 index 0000000..38d0d7e --- /dev/null +++ b/cmake/build/ci/appveyor.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 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. + +# This is similar to build.py, but auto-fills some parameters for build.py from +# the AppVeyor-defined environment variables. +# The project is built in C:\Projects\build. + +import argparse +from enum import Enum +import logging +import os +import sys + +from build import build + + +class Image(Enum): + VS_2013 = 'Visual Studio 2013' + VS_2015 = 'Visual Studio 2015' + VS_2017 = 'Visual Studio 2017' + VS_2019 = 'Visual Studio 2019' + + def __str__(self): + return self.value + + +def _parse_image(s): + try: + return Image(s) + except ValueError as e: + raise ValueError(f'unsupported AppVeyor image: {s}') from e + + +class Generator(Enum): + VS_2013 = 'Visual Studio 12 2013' + VS_2015 = 'Visual Studio 14 2015' + VS_2017 = 'Visual Studio 15 2017' + VS_2019 = 'Visual Studio 16 2019' + + def __str__(self): + return self.value + + @staticmethod + def from_image(image): + if image is Image.VS_2013: + return Generator.VS_2013 + if image is Image.VS_2015: + return Generator.VS_2015 + if image is Image.VS_2017: + return Generator.VS_2017 + if image is Image.VS_2019: + return Generator.VS_2019 + raise RuntimeError(f"don't know which generator to use for image: {image}") + + +class Platform(Enum): + x86 = 'Win32' + X64 = 'x64' + + def __str__(self): + return self.value + + +def _parse_platform(s): + try: + return Platform(s) + except ValueError as e: + raise ValueError(f'unsupported AppVeyor platform: {s}') from e + + +def _env(name): + if name not in os.environ: + raise RuntimeError(f'undefined environment variable: {name}') + return os.environ[name] + + +def _check_appveyor(): + if 'APPVEYOR' not in os.environ: + raise RuntimeError('not running on AppVeyor') + + +def _get_src_dir(): + return _env('APPVEYOR_BUILD_FOLDER') + + +def _get_build_dir(): + return R'C:\Projects\build' + + +def _get_generator(): + image = _parse_image(_env('APPVEYOR_BUILD_WORKER_IMAGE')) + return str(Generator.from_image(image)) + + +def _get_platform(): + return str(_parse_platform(_env('PLATFORM'))) + + +def _get_configuration(): + return _env('CONFIGURATION') + + +def _setup_logging(): + logging.basicConfig( + format='%(asctime)s | %(levelname)s | %(message)s', + level=logging.INFO) + + +def _parse_args(argv=None): + if argv is None: + argv = sys.argv[1:] + logging.info('Command line arguments: %s', argv) + + parser = argparse.ArgumentParser(description='Build a CMake project on AppVeyor') + parser.add_argument('--install', metavar='DIR', dest='install_dir', + help='install directory') + parser.add_argument('cmake_args', nargs='*', metavar='CMAKE_ARG', default=[], + help='additional CMake arguments, to be passed verbatim') + return parser.parse_args(argv) + + +def build_appveyor(argv=None): + args = _parse_args(argv) + _check_appveyor() + + appveyor_argv = [ + '--src', _get_src_dir(), + '--build', _get_build_dir(), + '--configuration', _get_configuration(), + ] + if args.install_dir is not None: + appveyor_argv += [ + '--install', args.install_dir, + ] + appveyor_argv += [ + '--', + '-G', _get_generator(), + '-A', _get_platform(), + ] + build(appveyor_argv + args.cmake_args) + + +def main(argv=None): + _setup_logging() + try: + build_appveyor(argv) + except Exception as e: + logging.exception(e) + raise + + +if __name__ == '__main__': + main() diff --git a/cmake/build/ci/travis.py b/cmake/build/ci/travis.py new file mode 100755 index 0000000..25dbe70 --- /dev/null +++ b/cmake/build/ci/travis.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 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. + +# This is similar to build.py, but auto-fills some parameters for build.py from +# the Travis-defined environment variables. +# The project is built in $HOME/build. + +import argparse +import logging +import os +import os.path +import sys + +from build import build + + +def _env(name): + if name not in os.environ: + raise RuntimeError(f'undefined environment variable: {name}') + return os.environ[name] + + +def _check_travis(): + if 'TRAVIS' not in os.environ: + raise RuntimeError('not running on Travis') + + +def _get_src_dir(): + return _env('TRAVIS_BUILD_DIR') + + +def _get_build_dir(): + return os.path.join(_env('HOME'), 'build') + + +def _get_configuration(): + return _env('configuration') + + +def _setup_logging(): + logging.basicConfig( + format='%(asctime)s | %(levelname)s | %(message)s', + level=logging.INFO) + + +def _parse_args(argv=None): + if argv is None: + argv = sys.argv[1:] + logging.info('Command line arguments: %s', argv) + + parser = argparse.ArgumentParser(description='Build a CMake project on Travis') + parser.add_argument('--install', metavar='DIR', dest='install_dir', + help='install directory') + parser.add_argument('cmake_args', nargs='*', metavar='CMAKE_ARG', default=[], + help='additional CMake arguments, to be passed verbatim') + return parser.parse_args(argv) + + +def build_travis(argv=None): + args = _parse_args(argv) + _check_travis() + + travis_argv = [ + '--src', _get_src_dir(), + '--build', _get_build_dir(), + '--configuration', _get_configuration(), + ] + if args.install_dir is not None: + travis_argv += [ + '--install', args.install_dir, + ] + travis_argv.append('--') + build(travis_argv + args.cmake_args) + + +def main(argv=None): + _setup_logging() + try: + build_travis(argv) + except Exception as e: + logging.exception(e) + raise + + +if __name__ == '__main__': + main() diff --git a/cmake/common.cmake b/cmake/common.cmake new file mode 100644 index 0000000..447f340 --- /dev/null +++ b/cmake/common.cmake @@ -0,0 +1,212 @@ +# Copyright (c) 2017 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. + +# It's a CMake code snippet I use in all of my CMake projects. +# It makes targets link the runtime statically by default, strips debug symbols +# in release builds and sets a couple of useful compilation options. + +# Add this to the top-level CMakeLists.txt (unless a higher version has already +# been specified): +# +# cmake_minimum_required(VERSION 3.1) + +# Without this policy set, this line: +# +# if(toolset STREQUAL "MSVC") +# +# evaluates to false even when using Visual Studio (since MSVC is a predefined +# variable; it's completely bonkers). +if(NOT POLICY CMP0054) + message(FATAL_ERROR "common.cmake uses CMP0054, which is unsupported by this CMake version") +endif() +cmake_policy(SET CMP0054 NEW) + +# Toolset identification: + +if(CMAKE_C_COMPILER_ID) + set(toolset "${CMAKE_C_COMPILER_ID}") +elseif(CMAKE_CXX_COMPILER_ID) + set(toolset "${CMAKE_CXX_COMPILER_ID}") +else() + set(toolset "unknown") +endif() + +if(toolset STREQUAL "GNU") + set(is_gcc ON) +elseif(toolset STREQUAL "MSVC") + set(is_msvc ON) +else() + message(WARNING "Unrecognized toolset: ${toolset}") +endif() + +# User-defined switches: + +set(default_value ON) +get_directory_property(parent_dir PARENT_DIRECTORY) +if(parent_dir) + set(default_value OFF) +endif() + +if(NOT DEFINED CC_CXX_STANDARD) + set(CC_CXX_STANDARD "14" CACHE STRING "C++ standard version") +endif() +if(NOT DEFINED CC_BEST_PRACTICES) + option(CC_BEST_PRACTICES "Set common compiler options" "${default_value}") +endif() +if(NOT DEFINED CC_WINDOWS_DEF) + option(CC_WINDOWS_DEF "Define useful Windows macros" "${default_value}") +endif() +if(NOT DEFINED CC_STATIC_RUNTIME) + set(static_runtime_default_value "${default_value}") + if(UNIX) + set(static_runtime_default_value OFF) + endif() + option(CC_STATIC_RUNTIME "Link the runtime statically" "${static_runtime_default_value}") +endif() +if(NOT DEFINED CC_STRIP_SYMBOLS) + option(CC_STRIP_SYMBOLS "Strip debug symbols" "${default_value}") +endif() + +option(Boost_USE_STATIC_LIBS "Use the static Boost libraries" "${default_value}") +option(Boost_USE_STATIC_RUNTIME "Use Boost libraries linked to the runtime statically" "${CC_STATIC_RUNTIME}") + +message(STATUS "Toolset: ${toolset}") +message(STATUS "C++ standard: ${CC_CXX_STANDARD}") +message(STATUS "Set common compiler options: ${CC_BEST_PRACTICES}") +message(STATUS "Define useful Windows macros: ${CC_WINDOWS_DEF}") +message(STATUS "Use the static Boost libraries: ${Boost_USE_STATIC_LIBS}") +message(STATUS "Link the runtime statically: ${CC_STATIC_RUNTIME}") +message(STATUS "Strip symbols: ${CC_STRIP_SYMBOLS}") + +# C++ standard version: + +set(CMAKE_CXX_STANDARD "${CC_CXX_STANDARD}") +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Common compiler options routines: + +function(_cc_best_practices_msvc target) + set(compile_options /MP /W4) + get_target_property(target_type "${target}" TYPE) + if(NOT target_type STREQUAL "INTERFACE_LIBRARY") + target_compile_options("${target}" PRIVATE ${compile_options}) + endif() +endfunction() + +function(_cc_best_practices_gcc target) + set(compile_options -Wall -Wextra) + get_target_property(target_type "${target}" TYPE) + if(NOT target_type STREQUAL "INTERFACE_LIBRARY") + target_compile_options("${target}" PRIVATE ${compile_options}) + endif() +endfunction() + +function(_cc_best_practices target) + if(is_msvc) + _cc_best_practices_msvc("${target}") + elseif(is_gcc) + _cc_best_practices_gcc("${target}") + endif() +endfunction() + +# Useful Windows macros routines: + +function(_cc_common_windows_definitions target) + set(compile_definitions WIN32_LEAN_AND_MEAN NOMINMAX) + get_target_property(target_type "${target}" TYPE) + if(target_type STREQUAL "INTERFACE_LIBRARY") + target_compile_definitions("${target}" INTERFACE ${compile_definitions}) + else() + target_compile_definitions("${target}" PRIVATE ${compile_definitions}) + endif() +endfunction() + +# Static runtime routines: + +function(_cc_static_runtime_msvc target) + get_target_property(target_type "${target}" TYPE) + if(NOT target_type STREQUAL "INTERFACE_LIBRARY") + target_compile_options("${target}" PRIVATE + $<$:/MTd> + $<$>:/MT>) + endif() +endfunction() + +function(_cc_static_runtime_gcc target) + get_target_property(target_type "${target}" TYPE) + if(target_type STREQUAL "EXECUTABLE") + # This causes issues with mixing keyword- and plain- versions of + # target_link_libraries: + #target_link_libraries("${target}" PRIVATE -static) + + set_property(TARGET "${target}" APPEND_STRING PROPERTY LINK_FLAGS " -static") + + # Or (haven't tested this), if CMake 3.13+ is used: + #target_link_options("${target}" PRIVATE -static) + endif() +endfunction() + +function(_cc_static_runtime target) + if(is_msvc) + _cc_static_runtime_msvc("${target}") + elseif(is_gcc) + _cc_static_runtime_gcc("${target}") + endif() +endfunction() + +# Symbol stripping routines: + +function(_cc_strip_symbols_gcc target) + get_target_property(target_type "${target}" TYPE) + if(NOT target_type STREQUAL "INTERFACE_LIBRARY") + set(release_build $,$>) + if(release_build) + # This causes issues with mixing keyword- and plain- versions of + # target_link_libraries: + #target_link_libraries("${target}" PRIVATE -s) + + set_property(TARGET "${target}" APPEND_STRING PROPERTY LINK_FLAGS " -s") + endif() + endif() +endfunction() + +function(_cc_strip_symbols target) + if(is_gcc) + _cc_strip_symbols_gcc("${target}") + endif() +endfunction() + +# Main macros: + +function(_cc_apply_settings target) + if(TARGET "${target}") + get_target_property(target_imported "${target}" IMPORTED) + if(NOT target_imported) + if(CC_BEST_PRACTICES) + _cc_best_practices("${target}") + endif() + if(CC_WINDOWS_DEF) + _cc_common_windows_definitions("${target}") + endif() + if(CC_STRIP_SYMBOLS) + _cc_strip_symbols("${target}") + endif() + if(CC_STATIC_RUNTIME) + _cc_static_runtime("${target}") + endif() + endif() + endif() +endfunction() + +macro(add_executable target) + _add_executable(${ARGV}) + _cc_apply_settings("${target}") +endmacro() + +macro(add_library target) + _add_library(${ARGV}) + _cc_apply_settings("${target}") +endmacro() diff --git a/cmake/toolchains/README.md b/cmake/toolchains/README.md new file mode 100644 index 0000000..0e46391 --- /dev/null +++ b/cmake/toolchains/README.md @@ -0,0 +1,3 @@ +Use the toolchain files by passing something like +`-DCMAKE_TOOLCHAIN_FILE=path/to/cmake-common/toolchains/toolchain.cmake` to +`cmake`. diff --git a/cmake/toolchains/gcc-x64.cmake b/cmake/toolchains/gcc-x64.cmake new file mode 100644 index 0000000..2f7019e --- /dev/null +++ b/cmake/toolchains/gcc-x64.cmake @@ -0,0 +1,4 @@ +set(CMAKE_C_COMPILER gcc) +set(CMAKE_C_FLAGS -m64) +set(CMAKE_CXX_COMIPLER g++) +set(CMAKE_CXX_FLAGS -m64) diff --git a/cmake/toolchains/gcc-x86.cmake b/cmake/toolchains/gcc-x86.cmake new file mode 100644 index 0000000..e2e84be --- /dev/null +++ b/cmake/toolchains/gcc-x86.cmake @@ -0,0 +1,4 @@ +set(CMAKE_C_COMPILER gcc) +set(CMAKE_C_FLAGS -m32) +set(CMAKE_CXX_COMIPLER g++) +set(CMAKE_CXX_FLAGS -m32) diff --git a/cmake/toolchains/mingw-w64-x64.cmake b/cmake/toolchains/mingw-w64-x64.cmake new file mode 100644 index 0000000..6009edf --- /dev/null +++ b/cmake/toolchains/mingw-w64-x64.cmake @@ -0,0 +1,3 @@ +set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) +set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) +set(CMAKE_RC_COMILER x86_64-w64-mingw32-windres) diff --git a/cmake/toolchains/mingw-w64-x86.cmake b/cmake/toolchains/mingw-w64-x86.cmake new file mode 100644 index 0000000..b0c717f --- /dev/null +++ b/cmake/toolchains/mingw-w64-x86.cmake @@ -0,0 +1,3 @@ +set(CMAKE_C_COMPILER i686-w64-mingw32-gcc) +set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) +set(CMAKE_RC_COMILER i686-w64-mingw32-windres) -- cgit v1.2.3