# 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 Boost. This script builds the Boost libraries. It's main utility is setting the correct --stagedir parameter value to avoid name clashes. Usage example: $ python -m project.boost.build -- boost_1_71_0/ --with-filesystem --with-program_options ... ''' import argparse from contextlib import contextmanager import logging import os.path import sys import tempfile from project.boost.directory import BoostDir from project.boost.toolchain import detect_toolchain from project.configuration import Configuration from project.linkage import Linkage from project.platform import Platform from project.os import on_linux_like import project.utils DEFAULT_PLATFORMS = Platform.all() DEFAULT_CONFIGURATIONS = Configuration.all() DEFAULT_LINK = Linkage.all() DEFAULT_RUNTIME_LINK = Linkage.STATIC DEFAULT_B2_ARGS = ['-d0'] class BuildParameters: def __init__(self, boost_dir, build_dir=None, platforms=None, configurations=None, link=None, runtime_link=None, b2_args=None, mingw=False): boost_dir = project.utils.normalize_path(boost_dir) if build_dir is not None: build_dir = project.utils.normalize_path(build_dir) platforms = platforms or DEFAULT_PLATFORMS configurations = configurations or DEFAULT_CONFIGURATIONS link = link or DEFAULT_LINK runtime_link = runtime_link or DEFAULT_RUNTIME_LINK if b2_args: b2_args = DEFAULT_B2_ARGS + b2_args else: b2_args = DEFAULT_B2_ARGS self.boost_dir = boost_dir self.stage_dir = 'stage' self.build_dir = build_dir self.platforms = platforms self.configurations = configurations self.link = link self.runtime_link = runtime_link self.b2_args = b2_args self.mingw = mingw @staticmethod def from_args(args): return BuildParameters(**vars(args)) 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: for configuration in self.configurations: for link, runtime_link in self._linkage_options(): yield self._build_params(build_dir, toolchain, configuration, link, runtime_link) def _linkage_options(self): for link in self.link: runtime_link = self.runtime_link if runtime_link is Linkage.STATIC: if link is Linkage.SHARED: logging.warning("Cannot link the runtime statically to a dynamic library, going to link dynamically") runtime_link = Linkage.SHARED elif on_linux_like(): logging.warning("Cannot link to the GNU C Library (which is assumed) statically, going to link dynamically") runtime_link = Linkage.SHARED yield link, runtime_link @contextmanager def _create_build_dir(self): if self.build_dir is not None: logging.info('Build directory: %s', self.build_dir) yield self.build_dir return with tempfile.TemporaryDirectory(dir=os.path.dirname(self.boost_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(self, build_dir, toolchain, configuration, link, runtime_link): params = [] params.append(self._build_dir(build_dir)) params.append(self._stagedir(toolchain, configuration)) params += toolchain.get_b2_args() params.append(self._link(link)) params.append(self._runtime_link(runtime_link)) params.append(self._variant(configuration)) params += self.b2_args return params @staticmethod def _build_dir(build_dir): return f'--build-dir={build_dir}' def _stagedir(self, toolchain, configuration): # Having different --stagedir values for every configuration/platform # combination is unnecessary on Windows. Even for older Boost versions # (when the binaries weren't tagged with their target platform) only a # single --stagedir for every platform would suffice. For newer # versions, just a single --stagedir would do, as the binaries are # tagged with the target platform, as well as their configuration # (a.k.a. "variant" in Boost's terminology). Still, uniformity helps. platform = str(toolchain.platform) configuration = str(configuration) return f'--stagedir={os.path.join(self.stage_dir, platform, configuration)}' @staticmethod def _link(link): return f'link={link}' @staticmethod def _runtime_link(runtime_link): return f'runtime-link={runtime_link}' @staticmethod def _variant(configuration): return f'variant={configuration.to_boost_variant()}' def build(params): boost_dir = BoostDir(params.boost_dir) boost_dir.build(params) 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) platform_options = '/'.join(map(str, Platform.all())) configuration_options = '/'.join(map(str, Configuration.all())) # These are used to put the built libraries into proper stage/ # subdirectories (to avoid name clashes). parser.add_argument('--platform', metavar='PLATFORM', dest='platforms', nargs='*', type=Platform.parse, default=[], help=f'target platform ({platform_options})') parser.add_argument('--configuration', metavar='CONFIGURATION', dest='configurations', nargs='*', type=Configuration.parse, default=[], help=f'target configuration ({configuration_options})') linkage_options = '/'.join(map(str, Linkage.all())) # This is needed because the default behaviour on Linux and Windows is # different: static & dynamic libs are built on Linux, but only static libs # are built on Windows by default. parser.add_argument('--link', metavar='LINKAGE', nargs='*', type=Linkage.parse, default=[], help=f'how the libraries are linked ({linkage_options})') # This is used to omit runtime-link=static I'd have to otherwise use a lot, # plus the script validates the link= and runtime-link= combinations. parser.add_argument('--runtime-link', metavar='LINKAGE', type=Linkage.parse, default=DEFAULT_RUNTIME_LINK, help=f'how the libraries link to the runtime ({linkage_options})') parser.add_argument('--build', metavar='DIR', dest='build_dir', type=project.utils.normalize_path, help='Boost build directory (temporary directory unless specified)') parser.add_argument('boost_dir', metavar='DIR', type=project.utils.normalize_path, help='root Boost directory') parser.add_argument('b2_args', metavar='B2_ARG', nargs='*', default=[], help='additional b2 arguments, to be passed verbatim') parser.add_argument('--mingw', action='store_true', help='build using MinGW-w64') return parser.parse_args(argv) def _main(argv=None): with project.utils.setup_logging(): build(BuildParameters.from_args(_parse_args(argv))) if __name__ == '__main__': _main()