aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/project/boost/build.py
diff options
context:
space:
mode:
authorEgor Tensin <Egor.Tensin@gmail.com>2020-03-28 23:01:09 +0000
committerEgor Tensin <Egor.Tensin@gmail.com>2020-03-28 23:54:46 +0000
commitc58a3787eca9c0c4a7f376ba841cd7e39ab95ece (patch)
tree709b5ab8385bbb952912206ae561fbe5e1e752d8 /project/boost/build.py
parentproject.boost: factor out BoostVersion (diff)
downloadcmake-common-c58a3787eca9c0c4a7f376ba841cd7e39ab95ece.tar.gz
cmake-common-c58a3787eca9c0c4a7f376ba841cd7e39ab95ece.zip
project.boost: factor out everything else
I finally snapped. This starts to resemble sensible structure though.
Diffstat (limited to 'project/boost/build.py')
-rw-r--r--project/boost/build.py346
1 files changed, 59 insertions, 287 deletions
diff --git a/project/boost/build.py b/project/boost/build.py
index e9f2ffc..541e2ba 100644
--- a/project/boost/build.py
+++ b/project/boost/build.py
@@ -1,240 +1,59 @@
-#!/usr/bin/env python3
-
# Copyright (c) 2019 Egor Tensin <Egor.Tensin@gmail.com>
# This file is part of the "cmake-common" project.
# For details, see https://github.com/egor-tensin/cmake-common.
# Distributed under the MIT License.
-'''Download & build Boost.
-
-This script downloads and builds the Boost libraries. It's main purpose is to:
-1) provide a cross-platform way to download & unpack the Boost distribution
-archive,
-2) set the correct --stagedir parameter value to avoid name clashes.
+R'''Build Boost.
-Please pick a command below. You can execute `%(prog)s COMMAND --help` to view
-its usage message.
+This script builds the Boost libraries. It's main utility is setting the
+correct --stagedir parameter value to avoid name clashes.
-A simple usage example:
+Usage examples:
- $ %(prog)s download 1.71.0
- ...
- $ %(prog)s build -- boost_1_71_0/ --with-filesystem --with-program_options
+ $ %(prog)s -- boost_1_71_0/ --with-filesystem --with-program_options
...
'''
-import abc
import argparse
-from collections import namedtuple
from contextlib import contextmanager
-from enum import Enum
-from functools import total_ordering
import logging
import os.path
-import platform
-import re
-import shutil
-import subprocess
import sys
import tempfile
-import urllib.request
-from project.boost.version import Version
+from project.boost.directory import BoostDir
from project.configuration import Configuration
from project.linkage import Linkage
from project.platform import Platform
-
-
-@contextmanager
-def _chdir(path):
- cwd = os.getcwd()
- os.chdir(path)
- try:
- yield
- finally:
- os.chdir(cwd)
-
-
-def _setup_logging():
- logging.basicConfig(
- format='%(asctime)s | %(levelname)s | %(message)s',
- level=logging.INFO)
-
-
-def _on_windows():
- return platform.system() == 'Windows'
-
-
-def _on_linux():
- return not _on_windows()
-
-
-def _run_executable(cmd_line):
- logging.info('Running executable: %s', cmd_line)
- return subprocess.run(cmd_line, check=True)
-
-
-class BoostArchive:
- def __init__(self, version, path):
- self.version = version
- self.path = path
-
- @property
- def dir_name(self):
- return self.version.dir_name
-
- def unpack(self, dest_dir):
- path = os.path.join(dest_dir, self.dir_name)
- if os.path.exists(path):
- raise RuntimeError(f'Boost directory already exists: {path}')
- logging.info('Unpacking Boost to: %s', path)
- shutil.unpack_archive(self.path, dest_dir)
- return BoostDir(path)
-
-
-class ArchiveStorage(abc.ABC):
- @contextmanager
- def download(self, version):
- path = self.get_archive(version)
- if path is not None:
- logging.info('Using existing Boost archive: %s', path)
- yield BoostArchive(version, path)
- return
-
- urls = version.get_download_urls()
-
- for url in urls:
- logging.info('Trying URL: %s', url)
- try:
- with urllib.request.urlopen(url, timeout=20) as request:
- with self.write_archive(version, request.read()) as path:
- yield BoostArchive(version, path)
- return
- except urllib.request.URLError as e:
- logging.error("Couldn't download from this mirror, an error occured:")
- logging.exception(e)
-
- raise RuntimeError("Couldn't download Boost from any of the mirrors")
-
- @abc.abstractmethod
- def get_archive(self, version):
- pass
-
- @contextmanager
- @abc.abstractmethod
- def write_archive(self, version, contents):
- pass
-
-
-class CacheStorage(ArchiveStorage):
- def __init__(self, cache_dir):
- self._dir = cache_dir
-
- def _archive_path(self, version):
- return os.path.join(self._dir, version.archive_name)
-
- def get_archive(self, version):
- path = self._archive_path(version)
- if os.path.exists(path):
- return path
- return None
-
- @contextmanager
- def write_archive(self, version, contents):
- path = self._archive_path(version)
- logging.info('Writing Boost archive: %s', path)
- if os.path.exists(path):
- raise RuntimeError(f'cannot download Boost, file already exists: {path}')
- with open(path, mode='w+b') as dest:
- dest.write(contents)
- yield path
-
-
-class TempStorage(ArchiveStorage):
- def __init__(self, temp_dir):
- self._dir = temp_dir
-
- def get_archive(self, version):
- return None
-
- @contextmanager
- def write_archive(self, version, contents):
- with tempfile.NamedTemporaryFile(prefix=f'boost_{version}_', suffix=version.archive_ext, dir=self._dir, delete=False) as dest:
- path = dest.name
- logging.info('Writing Boost archive: %s', path)
- dest.write(contents)
- try:
- yield path
- finally:
- logging.info('Removing temporary Boost archive: %s', path)
- os.remove(path)
-
-
-class BoostDir:
- def __init__(self, path):
- if not os.path.isdir(path):
- raise RuntimeError(f"Boost directory doesn't exist: {path}")
- self.path = path
-
- def _go(self):
- return _chdir(self.path)
-
- def build(self, params):
- with self._go():
- self._bootstrap_if_required()
- self._b2(params)
-
- def _bootstrap_if_required(self):
- if os.path.isfile(self._b2_path()):
- logging.info('Not going to bootstrap, b2 is already there')
- return
- self.bootstrap()
-
- def bootstrap(self):
- with self._go():
- _run_executable(self._bootstrap_path())
-
- def _b2(self, params):
- for b2_params in params.enum_b2_args():
- _run_executable([self._b2_path()] + b2_params)
-
- @staticmethod
- def _bootstrap_path():
- return os.path.join('.', BoostDir._bootstrap_name())
-
- @staticmethod
- def _bootstrap_name():
- ext = '.sh'
- if _on_windows():
- ext = '.bat'
- return f'bootstrap{ext}'
-
- @staticmethod
- def _b2_path():
- return os.path.join('.', BoostDir._b2_name())
-
- @staticmethod
- def _b2_name():
- ext = ''
- if _on_windows():
- ext = '.exe'
- return f'b2{ext}'
+import project.utils
class BuildParameters:
- def __init__(self, args):
- self.platforms = args.platforms or Platform.all()
- self.configurations = args.configurations or Configuration.all()
- self.link = args.link or Linkage.all()
- self.runtime_link = args.runtime_link
-
+ def __init__(self, boost_dir, build_dir=None, platforms=None, configurations=None, link=None,
+ runtime_link=None, b2_args=None):
+
+ 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 Platform.all()
+ configurations = configurations or Configuration.all()
+ link = link or Linkage.all()
+ runtime_link = runtime_link or Linkage.STATIC
+ b2_args = b2_args or []
+
+ 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.build_dir = args.build_dir
- self.boost_dir = args.boost_dir
-
- self.b2_args = args.b2_args
+ @staticmethod
+ def from_args(args):
+ return BuildParameters(**vars(args))
def enum_b2_args(self):
with self._create_build_dir() as build_dir:
@@ -250,7 +69,7 @@ class BuildParameters:
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():
+ elif project.utils.on_linux():
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
@@ -316,8 +135,9 @@ class BuildParameters:
return f'variant={configuration.to_boost_variant()}'
-def _parse_dir(s):
- return os.path.abspath(os.path.normpath(s))
+def build(params):
+ boost_dir = BoostDir(params.boost_dir)
+ boost_dir.build(params)
def _parse_args(argv=None):
@@ -329,93 +149,45 @@ def _parse_args(argv=None):
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
- subparsers = parser.add_subparsers(dest='command')
-
- download = subparsers.add_parser('download', help='download & bootstrap Boost')
-
- download.add_argument('--cache', metavar='DIR', dest='cache_dir',
- type=_parse_dir,
- help='download directory (temporary file unless specified)')
- download.add_argument('--unpack', metavar='DIR', dest='unpack_dir',
- type=_parse_dir, default='.',
- help='directory to unpack the archive to')
- download.add_argument('boost_version', metavar='VERSION',
- type=Version.from_string,
- help='Boost version (in the MAJOR.MINOR.PATCH format)')
-
- build = subparsers.add_parser('build', help='build the libraries')
-
# These are used to put the built libraries into proper stage/
# subdirectories (to avoid name clashes).
- build.add_argument('--platform', metavar='PLATFORM',
- nargs='*', dest='platforms', default=[],
- type=Platform.parse,
- help=f'target platform ({"/".join(map(str, Platform))})')
- build.add_argument('--configuration', metavar='CONFIGURATION',
- nargs='*', dest='configurations', default=[],
- type=Configuration.parse,
- help=f'target configuration ({"/".join(map(str, Configuration))})')
+ parser.add_argument('--platform', metavar='PLATFORM',
+ nargs='*', dest='platforms', default=[],
+ type=Platform.parse,
+ help=f'target platform ({"/".join(map(str, Platform))})')
+ parser.add_argument('--configuration', metavar='CONFIGURATION',
+ nargs='*', dest='configurations', default=[],
+ type=Configuration.parse,
+ help=f'target configuration ({"/".join(map(str, Configuration))})')
# 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.
- build.add_argument('--link', metavar='LINKAGE',
- nargs='*', default=[],
- type=Linkage.parse,
- help=f'how the libraries are linked ({"/".join(map(str, Linkage))})')
+ parser.add_argument('--link', metavar='LINKAGE',
+ nargs='*', default=[],
+ type=Linkage.parse,
+ help=f'how the libraries are linked ({"/".join(map(str, Linkage))})')
# 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.
- build.add_argument('--runtime-link', metavar='LINKAGE',
- type=Linkage.parse, default=Linkage.STATIC,
- help=f'how the libraries link to the runtime ({"/".join(map(str, Linkage))})')
-
- build.add_argument('--build', metavar='DIR', dest='build_dir',
- type=_parse_dir,
- help='Boost build directory (temporary directory unless specified)')
- build.add_argument('boost_dir', metavar='DIR',
- type=_parse_dir,
- help='root Boost directory')
-
- build.add_argument('b2_args', nargs='*', metavar='B2_ARG', default=[],
- help='additional b2 arguments, to be passed verbatim')
-
- args = parser.parse_args(argv)
- if args.command is None:
- parser.error("please specify a command")
- return args
-
-
-def build(args):
- build_params = BuildParameters(args)
- boost_dir = BoostDir(args.boost_dir)
- boost_dir.build(build_params)
-
+ parser.add_argument('--runtime-link', metavar='LINKAGE',
+ type=Linkage.parse, default=Linkage.STATIC,
+ help=f'how the libraries link to the runtime ({"/".join(map(str, Linkage))})')
-def download(args):
- storage = TempStorage(args.unpack_dir)
- if args.cache_dir is not None:
- storage = CacheStorage(args.cache_dir)
- with storage.download(args.boost_version) as archive:
- boost_dir = archive.unpack(args.unpack_dir)
- boost_dir.bootstrap()
+ 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', nargs='*', metavar='B2_ARG', default=[],
+ help='additional b2 arguments, to be passed verbatim')
-def main(argv=None):
- args = _parse_args(argv)
- if args.command == 'download':
- download(args)
- elif args.command == 'build':
- build(args)
- else:
- raise NotImplementedError(f'unsupported command: {args.command}')
+ return parser.parse_args(argv)
def _main(argv=None):
- _setup_logging()
- try:
- main(argv)
- except Exception as e:
- logging.exception(e)
- raise
+ with project.utils.setup_logging():
+ build(BuildParameters.from_args(_parse_args(argv)))
if __name__ == '__main__':