From cce9ea25d243672b9f88b124eb56e4bf37adba4c Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Mon, 3 Jul 2023 21:47:48 +0200 Subject: project.cmake.build -> project.build Accordingly, rename cmake-build to project-build. --- project/build.py | 265 ++++++++++++++++++++++++++++++++++++++++++++++ project/ci/cmake.py | 2 +- project/ci/dirs.py | 2 +- project/cmake/__init__.py | 0 project/cmake/build.py | 265 ---------------------------------------------- 5 files changed, 267 insertions(+), 267 deletions(-) create mode 100644 project/build.py delete mode 100644 project/cmake/__init__.py delete mode 100644 project/cmake/build.py (limited to 'project') diff --git a/project/build.py b/project/build.py new file mode 100644 index 0000000..ff366c3 --- /dev/null +++ b/project/build.py @@ -0,0 +1,265 @@ +# 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. + +R'''Build a CMake project. + +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). + +A simple usage example: + + $ project-build --configuration Release --install path/to/somewhere -- examples/simple build/ + ... + + $ ./path/to/somewhere/bin/foo + foo +''' + +import argparse +from contextlib import contextmanager +import logging +import os +import sys +import tempfile + +from project.configuration import Configuration +from project.platform import Platform +from project.toolset import Toolset, ToolsetVersion +from project.utils import normalize_path, mkdir_parent, run, setup_logging +import project.version + + +DEFAULT_PLATFORM = Platform.AUTO +DEFAULT_CONFIGURATION = Configuration.DEBUG +DEFAULT_TOOLSET_VERSION = ToolsetVersion.default() + + +# This way of basically passing `-j` to make is more universal compared to +# _guessing_ that the build system is make and passing -j explicitly. Plus it +# works with older CMake versions, which don't support the --parallel flag. +cmake_env = os.environ.copy() +cmake_env['CMAKE_BUILD_PARALLEL_LEVEL'] = str(os.cpu_count()) + + +def run_cmake(cmake_args): + return run(['cmake'] + cmake_args, env=cmake_env) + + +class GenerationPhase: + def __init__(self, src_dir, build_dir, install_dir=None, platform=None, + configuration=None, boost_dir=None, cmake_args=None): + src_dir = normalize_path(src_dir) + build_dir = normalize_path(build_dir) + if install_dir is not None: + install_dir = normalize_path(install_dir) + platform = platform or DEFAULT_PLATFORM + configuration = configuration or DEFAULT_CONFIGURATION + if boost_dir is not None: + boost_dir = normalize_path(boost_dir) + cmake_args = cmake_args or [] + + self.src_dir = src_dir + self.build_dir = build_dir + self.install_dir = install_dir + self.platform = platform + self.configuration = configuration + self.boost_dir = boost_dir + self.cmake_args = cmake_args + + def _cmake_args(self, toolset): + result = [] + result += toolset.cmake_args(self.build_dir, self.platform) + result += self.configuration.cmake_args() + result += self._cmake_boost_args() + result += self._cmake_extra_args() + result += self.cmake_args + # Important! -H must come as the last parameter, older CMake versions + # don't like it when it's not. + result += self._cmake_dir_args() + return result + + def _cmake_boost_args(self): + if self.boost_dir is None: + return [] + root = self.boost_dir + librarydir = self.platform.boost_librarydir(self.configuration) + librarydir = os.path.join(self.boost_dir, librarydir) + return [ + f'-DBOOST_ROOT={root}', + f'-DBOOST_LIBRARYDIR={librarydir}', + ] + + @staticmethod + def _cmake_extra_args(): + return ['-DCMAKE_EXPORT_COMPILE_COMMANDS=ON'] + + def _cmake_dir_args(self): + args = [] + if self.install_dir is not None: + args += [f'-DCMAKE_INSTALL_PREFIX={self.install_dir}'] + # Important! -H must come as the last parameter, older CMake versions + # don't like it when it's not. + args += [ + f'-B{self.build_dir}', + f'-H{self.src_dir}' + ] + return args + + def run(self, toolset): + run_cmake(self._cmake_args(toolset)) + + +class BuildPhase: + def __init__(self, build_dir, install_dir=None, configuration=None): + + build_dir = normalize_path(build_dir) + configuration = configuration or DEFAULT_CONFIGURATION + + self.build_dir = build_dir + self.install_dir = install_dir + self.configuration = configuration + + def _cmake_args(self, toolset): + result = ['--build', self.build_dir] + result += ['--config', str(self.configuration)] + if self.install_dir is not None: + result += ['--target', 'install'] + result += ['--'] + toolset.build_system_args() + return result + + def run(self, toolset): + run_cmake(self._cmake_args(toolset)) + + +class BuildParameters: + BUILD_DIR_TMP_PLACEHOLDER = 'TMP' + + def __init__(self, src_dir, build_dir, install_dir=None, + platform=None, configuration=None, boost_dir=None, + toolset_version=None, cmake_args=None): + + src_dir = normalize_path(src_dir) + build_dir = self.normalize_build_dir(build_dir) + if install_dir is not None: + install_dir = normalize_path(install_dir) + platform = platform or DEFAULT_PLATFORM + configuration = configuration or DEFAULT_CONFIGURATION + if boost_dir is not None: + boost_dir = normalize_path(boost_dir) + toolset_version = toolset_version or DEFAULT_TOOLSET_VERSION + cmake_args = cmake_args or [] + + self.src_dir = src_dir + self.build_dir = build_dir + self.install_dir = install_dir + self.platform = platform + self.configuration = configuration + self.boost_dir = boost_dir + self.toolset_version = toolset_version + self.cmake_args = cmake_args + + @staticmethod + def from_args(args): + args = vars(args) + args.pop('help_toolsets', None) + return BuildParameters(**args) + + @staticmethod + def normalize_build_dir(build_dir): + if build_dir == BuildParameters.BUILD_DIR_TMP_PLACEHOLDER: + return build_dir + return normalize_path(build_dir) + + @contextmanager + def create_build_dir(self): + if self.build_dir != BuildParameters.BUILD_DIR_TMP_PLACEHOLDER: + logging.info('Build directory: %s', self.build_dir) + mkdir_parent(self.build_dir) + yield self.build_dir + return + + with tempfile.TemporaryDirectory(dir=os.path.dirname(self.src_dir)) as build_dir: + logging.info('Build directory: %s', build_dir) + try: + yield build_dir + finally: + logging.info('Removing build directory: %s', build_dir) + return + + +def build(params): + with params.create_build_dir() as build_dir: + toolset = Toolset.make(params.toolset_version, params.platform) + + gen_phase = GenerationPhase(params.src_dir, build_dir, + install_dir=params.install_dir, + platform=params.platform, + configuration=params.configuration, + boost_dir=params.boost_dir, + cmake_args=params.cmake_args) + gen_phase.run(toolset) + build_phase = BuildPhase(build_dir, install_dir=params.install_dir, + configuration=params.configuration) + build_phase.run(toolset) + + +def _parse_args(argv=None): + if argv is None: + argv = sys.argv[1:] + + if '--help-toolsets' in argv: + sys.stdout.write(ToolsetVersion.help_toolsets()) + sys.exit(0) + + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + + project.version.add_to_arg_parser(parser) + + parser.add_argument('--install', metavar='DIR', dest='install_dir', + 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('--boost', metavar='DIR', dest='boost_dir', + type=normalize_path, + help='set Boost directory path') + + parser.add_argument('--toolset', metavar='TOOLSET', dest='toolset_version', + type=ToolsetVersion.parse, default=DEFAULT_TOOLSET_VERSION, + help=f'toolset to use ({ToolsetVersion.usage()})') + parser.add_argument('--help-toolsets', action='store_true', + help='show detailed info about supported toolsets') + + parser.add_argument('src_dir', type=normalize_path, + help='source directory') + parser.add_argument('build_dir', type=BuildParameters.normalize_build_dir, + help=f"build directory ('{BuildParameters.BUILD_DIR_TMP_PLACEHOLDER}' to use a temporary directory)") + parser.add_argument('cmake_args', nargs='*', default=[], + help='additional CMake arguments, to be passed verbatim') + + return parser.parse_args(argv) + + +def main(argv=None): + args = _parse_args(argv) + with setup_logging(): + build(BuildParameters.from_args(args)) + + +if __name__ == '__main__': + main() diff --git a/project/ci/cmake.py b/project/ci/cmake.py index 13929f7..24fe34c 100644 --- a/project/ci/cmake.py +++ b/project/ci/cmake.py @@ -8,7 +8,7 @@ import os.path import sys from project.ci.dirs import Dirs -from project.cmake.build import BuildParameters, build +from project.build import BuildParameters, build from project.utils import setup_logging import project.version diff --git a/project/ci/dirs.py b/project/ci/dirs.py index eb4651b..6de9509 100644 --- a/project/ci/dirs.py +++ b/project/ci/dirs.py @@ -108,7 +108,7 @@ The supported CI systems are: {Dirs.join_ci_names()}. def get_cmake_help(): return f'''Build a CMake project during a CI run. -This is similar to running project.cmake.build, but auto-fills some parameters +This is similar to running project.build, but auto-fills some parameters from environment variables. The supported CI systems are: {Dirs.join_ci_names()}. diff --git a/project/cmake/__init__.py b/project/cmake/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/project/cmake/build.py b/project/cmake/build.py deleted file mode 100644 index ef4de1a..0000000 --- a/project/cmake/build.py +++ /dev/null @@ -1,265 +0,0 @@ -# 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. - -R'''Build a CMake project. - -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). - -A simple usage example: - - $ cmake-build --configuration Release --install path/to/somewhere -- examples/simple build/ - ... - - $ ./path/to/somewhere/bin/foo - foo -''' - -import argparse -from contextlib import contextmanager -import logging -import os -import sys -import tempfile - -from project.configuration import Configuration -from project.platform import Platform -from project.toolset import Toolset, ToolsetVersion -from project.utils import normalize_path, mkdir_parent, run, setup_logging -import project.version - - -DEFAULT_PLATFORM = Platform.AUTO -DEFAULT_CONFIGURATION = Configuration.DEBUG -DEFAULT_TOOLSET_VERSION = ToolsetVersion.default() - - -# This way of basically passing `-j` to make is more universal compared to -# _guessing_ that the build system is make and passing -j explicitly. Plus it -# works with older CMake versions, which don't support the --parallel flag. -cmake_env = os.environ.copy() -cmake_env['CMAKE_BUILD_PARALLEL_LEVEL'] = str(os.cpu_count()) - - -def run_cmake(cmake_args): - return run(['cmake'] + cmake_args, env=cmake_env) - - -class GenerationPhase: - def __init__(self, src_dir, build_dir, install_dir=None, platform=None, - configuration=None, boost_dir=None, cmake_args=None): - src_dir = normalize_path(src_dir) - build_dir = normalize_path(build_dir) - if install_dir is not None: - install_dir = normalize_path(install_dir) - platform = platform or DEFAULT_PLATFORM - configuration = configuration or DEFAULT_CONFIGURATION - if boost_dir is not None: - boost_dir = normalize_path(boost_dir) - cmake_args = cmake_args or [] - - self.src_dir = src_dir - self.build_dir = build_dir - self.install_dir = install_dir - self.platform = platform - self.configuration = configuration - self.boost_dir = boost_dir - self.cmake_args = cmake_args - - def _cmake_args(self, toolset): - result = [] - result += toolset.cmake_args(self.build_dir, self.platform) - result += self.configuration.cmake_args() - result += self._cmake_boost_args() - result += self._cmake_extra_args() - result += self.cmake_args - # Important! -H must come as the last parameter, older CMake versions - # don't like it when it's not. - result += self._cmake_dir_args() - return result - - def _cmake_boost_args(self): - if self.boost_dir is None: - return [] - root = self.boost_dir - librarydir = self.platform.boost_librarydir(self.configuration) - librarydir = os.path.join(self.boost_dir, librarydir) - return [ - f'-DBOOST_ROOT={root}', - f'-DBOOST_LIBRARYDIR={librarydir}', - ] - - @staticmethod - def _cmake_extra_args(): - return ['-DCMAKE_EXPORT_COMPILE_COMMANDS=ON'] - - def _cmake_dir_args(self): - args = [] - if self.install_dir is not None: - args += [f'-DCMAKE_INSTALL_PREFIX={self.install_dir}'] - # Important! -H must come as the last parameter, older CMake versions - # don't like it when it's not. - args += [ - f'-B{self.build_dir}', - f'-H{self.src_dir}' - ] - return args - - def run(self, toolset): - run_cmake(self._cmake_args(toolset)) - - -class BuildPhase: - def __init__(self, build_dir, install_dir=None, configuration=None): - - build_dir = normalize_path(build_dir) - configuration = configuration or DEFAULT_CONFIGURATION - - self.build_dir = build_dir - self.install_dir = install_dir - self.configuration = configuration - - def _cmake_args(self, toolset): - result = ['--build', self.build_dir] - result += ['--config', str(self.configuration)] - if self.install_dir is not None: - result += ['--target', 'install'] - result += ['--'] + toolset.build_system_args() - return result - - def run(self, toolset): - run_cmake(self._cmake_args(toolset)) - - -class BuildParameters: - BUILD_DIR_TMP_PLACEHOLDER = 'TMP' - - def __init__(self, src_dir, build_dir, install_dir=None, - platform=None, configuration=None, boost_dir=None, - toolset_version=None, cmake_args=None): - - src_dir = normalize_path(src_dir) - build_dir = self.normalize_build_dir(build_dir) - if install_dir is not None: - install_dir = normalize_path(install_dir) - platform = platform or DEFAULT_PLATFORM - configuration = configuration or DEFAULT_CONFIGURATION - if boost_dir is not None: - boost_dir = normalize_path(boost_dir) - toolset_version = toolset_version or DEFAULT_TOOLSET_VERSION - cmake_args = cmake_args or [] - - self.src_dir = src_dir - self.build_dir = build_dir - self.install_dir = install_dir - self.platform = platform - self.configuration = configuration - self.boost_dir = boost_dir - self.toolset_version = toolset_version - self.cmake_args = cmake_args - - @staticmethod - def from_args(args): - args = vars(args) - args.pop('help_toolsets', None) - return BuildParameters(**args) - - @staticmethod - def normalize_build_dir(build_dir): - if build_dir == BuildParameters.BUILD_DIR_TMP_PLACEHOLDER: - return build_dir - return normalize_path(build_dir) - - @contextmanager - def create_build_dir(self): - if self.build_dir != BuildParameters.BUILD_DIR_TMP_PLACEHOLDER: - logging.info('Build directory: %s', self.build_dir) - mkdir_parent(self.build_dir) - yield self.build_dir - return - - with tempfile.TemporaryDirectory(dir=os.path.dirname(self.src_dir)) as build_dir: - logging.info('Build directory: %s', build_dir) - try: - yield build_dir - finally: - logging.info('Removing build directory: %s', build_dir) - return - - -def build(params): - with params.create_build_dir() as build_dir: - toolset = Toolset.make(params.toolset_version, params.platform) - - gen_phase = GenerationPhase(params.src_dir, build_dir, - install_dir=params.install_dir, - platform=params.platform, - configuration=params.configuration, - boost_dir=params.boost_dir, - cmake_args=params.cmake_args) - gen_phase.run(toolset) - build_phase = BuildPhase(build_dir, install_dir=params.install_dir, - configuration=params.configuration) - build_phase.run(toolset) - - -def _parse_args(argv=None): - if argv is None: - argv = sys.argv[1:] - - if '--help-toolsets' in argv: - sys.stdout.write(ToolsetVersion.help_toolsets()) - sys.exit(0) - - parser = argparse.ArgumentParser( - description=__doc__, - formatter_class=argparse.RawDescriptionHelpFormatter) - - project.version.add_to_arg_parser(parser) - - parser.add_argument('--install', metavar='DIR', dest='install_dir', - 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('--boost', metavar='DIR', dest='boost_dir', - type=normalize_path, - help='set Boost directory path') - - parser.add_argument('--toolset', metavar='TOOLSET', dest='toolset_version', - type=ToolsetVersion.parse, default=DEFAULT_TOOLSET_VERSION, - help=f'toolset to use ({ToolsetVersion.usage()})') - parser.add_argument('--help-toolsets', action='store_true', - help='show detailed info about supported toolsets') - - parser.add_argument('src_dir', type=normalize_path, - help='source directory') - parser.add_argument('build_dir', type=BuildParameters.normalize_build_dir, - help=f"build directory ('{BuildParameters.BUILD_DIR_TMP_PLACEHOLDER}' to use a temporary directory)") - parser.add_argument('cmake_args', nargs='*', default=[], - help='additional CMake arguments, to be passed verbatim') - - return parser.parse_args(argv) - - -def main(argv=None): - args = _parse_args(argv) - with setup_logging(): - build(BuildParameters.from_args(args)) - - -if __name__ == '__main__': - main() -- cgit v1.2.3