From 70da99e4f70845da37ae368c4788ecc18546792d Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Sat, 28 Mar 2020 17:19:43 +0000 Subject: WIP: restructure A stupid attempt to reduce code duplication led me to believe that all the scripts could use _a bit_ of refactoring. This is going to be a major pain (factoring out all the things), which I'll take gladly. All the links and usage examples are broken right now, but nobody cares, so whatevs. --- project/cmake/build.py | 190 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 project/cmake/build.py (limited to 'project/cmake/build.py') diff --git a/project/cmake/build.py b/project/cmake/build.py new file mode 100644 index 0000000..695489b --- /dev/null +++ b/project/cmake/build.py @@ -0,0 +1,190 @@ +#!/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. + +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: + + $ %(prog)s --configuration Release --install path/to/somewhere -- ../examples/simple + ... + + $ ./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: + + > %(prog)s --install path\to\somewhere -- ..\examples\simple -A Win32 + ... + +Using GCC-like compilers, the best way is to use CMake toolchain files (see +cmake/toolchains in this repository for examples). + + $ %(prog)s --install path/to/somewhere -- ../examples/simple -D CMAKE_TOOLCHAIN_FILE="$( pwd )/../toolchains/mingw-x86.cmake" + ... +''' + +import argparse +from contextlib import contextmanager +import logging +from enum import Enum +import os +import os.path +import subprocess +import sys +import tempfile + + +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' + MINSIZEREL = 'MinSizeRel' + RELWITHDEBINFO = 'RelWithDebInfo' + 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}') + + +@contextmanager +def _create_build_dir(args): + if args.build_dir is not None: + logging.info('Build directory: %s', args.build_dir) + yield args.build_dir + return + + with tempfile.TemporaryDirectory(dir=os.path.dirname(args.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 + + +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}', 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] + 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()) + + +def _parse_dir(s): + return os.path.abspath(os.path.normpath(s)) + + +def _parse_args(argv=None): + if argv is None: + argv = sys.argv[1:] + logging.info('Command line arguments: %s', argv) + + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + + parser.add_argument('--build', metavar='DIR', dest='build_dir', + type=_parse_dir, + help='build directory (temporary directory unless specified)') + parser.add_argument('--install', metavar='DIR', dest='install_dir', + type=_parse_dir, + help='install directory') + parser.add_argument('--configuration', metavar='CONFIG', + type=_parse_configuration, default=Configuration.DEBUG, + help=f'build configuration ({"/".join(map(str, Configuration))})') + parser.add_argument('src_dir', metavar='DIR', + type=_parse_dir, + help='source directory') + 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 _create_build_dir(args) as build_dir: + gen_phase = GenerationPhase(build_dir, args) + gen_phase.run() + build_phase = BuildPhase(build_dir, args) + build_phase.run() + + +def main(argv=None): + _setup_logging() + try: + build(argv) + except Exception as e: + logging.exception(e) + raise + + +if __name__ == '__main__': + main() -- cgit v1.2.3