aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorEgor Tensin <Egor.Tensin@gmail.com>2021-05-07 01:29:56 +0300
committerEgor Tensin <Egor.Tensin@gmail.com>2021-05-07 02:18:00 +0300
commit01dd0d68be266d59da888483a23abc0c56ed5589 (patch)
tree11b9b0e9ddda7150b86dadf4bc6c8f0b15211ce1
parentproject.cmake.toolset: rework a bit (diff)
downloadcmake-common-01dd0d68be266d59da888483a23abc0c56ed5589.tar.gz
cmake-common-01dd0d68be266d59da888483a23abc0c56ed5589.zip
project.toolset: merge both other modules to this one
-rw-r--r--project/boost/build.py3
-rw-r--r--project/boost/directory.py2
-rw-r--r--project/boost/toolset.py273
-rw-r--r--project/cmake/build.py3
-rw-r--r--project/cmake/toolset.py159
-rw-r--r--project/toolset.py378
6 files changed, 381 insertions, 437 deletions
diff --git a/project/boost/build.py b/project/boost/build.py
index e5c9415..53fdf42 100644
--- a/project/boost/build.py
+++ b/project/boost/build.py
@@ -31,12 +31,11 @@ import sys
import tempfile
from project.boost.directory import BoostDir
-from project.boost.toolset import Toolset
from project.configuration import Configuration
from project.linkage import Linkage
from project.os import on_linux_like
from project.platform import Platform
-from project.toolset import ToolsetHint
+from project.toolset import Toolset, ToolsetHint
from project.utils import normalize_path, setup_logging
diff --git a/project/boost/directory.py b/project/boost/directory.py
index 7cb5af8..fcb6fe6 100644
--- a/project/boost/directory.py
+++ b/project/boost/directory.py
@@ -6,8 +6,8 @@
import logging
import os.path
-from project.boost.toolset import Toolset
from project.os import on_windows
+from project.toolset import Toolset
from project.utils import cd, run
diff --git a/project/boost/toolset.py b/project/boost/toolset.py
deleted file mode 100644
index 7f9be10..0000000
--- a/project/boost/toolset.py
+++ /dev/null
@@ -1,273 +0,0 @@
-# Copyright (c) 2020 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.
-
-# See docs/boost.md for a more thorough description of my pain.
-
-import abc
-from contextlib import contextmanager
-import logging
-import os.path
-import shutil
-
-import project.mingw
-import project.os
-from project.toolset import ToolsetHint
-from project.utils import temp_file
-
-
-def _gcc_or_auto():
- if shutil.which('gcc') is not None:
- return ['gcc']
- return []
-
-
-class Toolset(abc.ABC):
- @contextmanager
- def b2_args(self):
- # Write the config file, etc.
- yield []
-
- @staticmethod
- @abc.abstractmethod
- def get_bootstrap_bat_args():
- pass
-
- @staticmethod
- @abc.abstractmethod
- def get_bootstrap_sh_args():
- pass
-
- @staticmethod
- def detect(hint):
- if hint is ToolsetHint.AUTO:
- return Auto
- if hint is ToolsetHint.MSVC:
- return MSVC
- if hint is ToolsetHint.GCC:
- return GCC
- if hint is ToolsetHint.MINGW:
- return MinGW
- if hint is ToolsetHint.CLANG:
- return Clang
- if hint is ToolsetHint.CLANG_CL:
- return ClangCL
- raise NotImplementedError(f'unrecognized toolset: {hint}')
-
- @staticmethod
- def make(hint, platform):
- # Platform is required here, since some toolsets (MinGW-w64) require
- # it for the compiler path.
- cls = Toolset.detect(hint)
- if cls is MinGW:
- return MinGW(platform)
- return cls()
-
-
-class Auto(Toolset):
- # Let Boost.Build do the detection. Most commonly it means GCC on
- # Linux-likes and MSVC on Windows.
-
- @staticmethod
- def get_bootstrap_bat_args():
- return []
-
- @staticmethod
- def get_bootstrap_sh_args():
- return []
-
-
-class MSVC(Auto):
- @contextmanager
- def b2_args(self):
- yield ['toolset=msvc']
-
- # Note: bootstrap.bat picks up MSVC by default.
-
-
-def _full_exe_name(exe):
- if project.os.on_linux():
- # There's no PATHEXT on Linux.
- return exe
- # b2 on Windows/Cygwin doesn't like it when the executable name doesn't
- # include the extension.
- dir_path = os.path.dirname(exe) or None
- path = shutil.which(exe, path=dir_path)
- if not path:
- raise RuntimeError(f"executable '{exe}' could not be found")
- if project.os.on_cygwin():
- # On Cygwin, shutil.which('gcc') == '/usr/bin/gcc' and shutil.which('gcc.exe')
- # == '/usr/bin/gcc.exe'; we want the latter version. shutil.which('clang++')
- # == '/usr/bin/clang++' is fine though, since it _is_ the complete path
- # (clang++ is a symlink).
- if os.path.exists(path) and os.path.exists(path + '.exe'):
- path += '.exe'
- if dir_path:
- # If it was found in a specific directory, include the directory in the
- # result. shutil.which returns the executable name prefixed with the
- # path argument.
- return path
- # If it was found in PATH, just return the basename (which includes the
- # extension).
- return os.path.basename(path)
-
-
-class Custom(Toolset):
- COMPILER_VERSION = 'custom'
-
- def __init__(self, compiler, path=None, build_options=None):
- if not compiler:
- raise RuntimeError('compiler type is required (like gcc, clang, etc.)')
- self.compiler = compiler
- version = Custom.COMPILER_VERSION
- self.version = version
- path = path or ''
- path = path and _full_exe_name(path)
- self.path = path
- build_options = build_options or []
- self.build_options = build_options
-
- def toolset(self):
- if self.version:
- return f'{self.compiler}-{self.version}'
- return self.compiler
-
- def b2_arg_toolset(self):
- return f'toolset={self.toolset()}'
-
- def _format_build_options(self):
- return ''.join(f'\n <{name}>{val}' for name, val in self.build_options)
-
- def format_config(self):
- version = self.version and f'{self.version} '
- path = self.path and f'{self.path} '
- return f'''using {self.compiler} : {version}: {path}:{self._format_build_options()}
-;'''
-
- @contextmanager
- def b2_args(self):
- config_file = temp_file(prefix='user_config_', suffix='.jam')
- with config_file as config_path:
- config = self.format_config()
- logging.info('Using user config:\n%s', config)
- with open(config_path, mode='w') as fd:
- fd.write(config)
- args = []
- args.append(self.b2_arg_toolset())
- args.append(f'--user-config={config_path}')
- yield args
-
-
-class GCC(Custom):
- # Force GCC. We don't care whether it's a native Linux GCC or a
- # MinGW-flavoured GCC on Windows.
- def __init__(self, path='g++', build_options=None):
- build_options = build_options or self.get_build_options()
- super().__init__('gcc', path, build_options)
-
- @staticmethod
- def get_bootstrap_bat_args():
- return ['gcc']
-
- @staticmethod
- def get_bootstrap_sh_args():
- return ['--with-toolset=gcc']
-
- @staticmethod
- def get_build_options():
- return []
-
-
-class MinGW(GCC):
- # It's important that Boost.Build is actually smart enough to detect the
- # GCC prefix (like "x86_64-w64-mingw32" and prepend it to other tools like
- # "ar").
-
- def __init__(self, platform):
- paths = project.mingw.MinGW(platform)
- super().__init__(paths.gxx())
-
- @staticmethod
- def get_bootstrap_bat_args():
- # On Windows, prefer GCC if it's available.
- return _gcc_or_auto()
-
- @staticmethod
- def get_bootstrap_sh_args():
- return []
-
-
-class Clang(Custom):
- def __init__(self):
- super().__init__('clang', 'clang++', self.get_build_options())
-
- @staticmethod
- def get_bootstrap_bat_args():
- # As of 1.74.0, bootstrap.bat isn't really aware of Clang, so try GCC,
- # then auto-detect.
- return _gcc_or_auto()
-
- @staticmethod
- def get_bootstrap_sh_args():
- # bootstrap.sh, on the other hand, is very much aware of Clang, and
- # it can build b2 using this compiler.
- return ['--with-toolset=clang']
-
- @staticmethod
- def get_build_options():
- options = GCC.get_build_options()
- options += [
- ('cxxflags', '-DBOOST_USE_WINDOWS_H'),
-
- # Even with <warnings>off, the build might sometimes fail with the
- # following error:
- #
- # error: constant expression evaluates to -105 which cannot be narrowed to type 'boost::re_detail::cpp_regex_traits_implementation<char>::char_class_type' (aka 'unsigned int')
- ('cxxflags', '-Wno-c++11-narrowing'),
- ]
- if project.os.on_windows():
- # Prefer LLVM binutils:
- if shutil.which('llvm-ar') is not None:
- options.append(('archiver', 'llvm-ar'))
- if shutil.which('llvm-ranlib') is not None:
- options.append(('ranlib', 'llvm-ranlib'))
- return options
-
- def format_config(self):
- # To make clang.exe/clang++.exe work on Windows, some tweaks are
- # required. I borrowed them from CMake's Windows-Clang.cmake [1].
- # Adding them globally to Boost.Build options is described in [2].
- #
- # [1]: https://github.com/Kitware/CMake/blob/v3.18.4/Modules/Platform/Windows-Clang.cmake
- # [2]: https://stackoverflow.com/questions/2715106/how-to-create-a-new-variant-in-bjam
- return f'''project : requirements
- <target-os>windows:<define>_MT
- <target-os>windows,<variant>debug:<define>_DEBUG
- <target-os>windows,<runtime-link>static,<variant>debug:<cxxflags>"-Xclang -flto-visibility-public-std -Xclang --dependent-lib=libcmtd"
- <target-os>windows,<runtime-link>static,<variant>release:<cxxflags>"-Xclang -flto-visibility-public-std -Xclang --dependent-lib=libcmt"
- <target-os>windows,<runtime-link>shared,<variant>debug:<cxxflags>"-D_DLL -Xclang --dependent-lib=msvcrtd"
- <target-os>windows,<runtime-link>shared,<variant>release:<cxxflags>"-D_DLL -Xclang --dependent-lib=msvcrt"
-;
-{super().format_config()}
-'''
-
-
-class ClangCL(Toolset):
- @contextmanager
- def b2_args(self):
- yield [
- 'toolset=clang-win',
- 'define=BOOST_USE_WINDOWS_H',
- ]
-
- # There's no point in building b2 using clang-cl; clang though, presumably
- # installed alongside clang-cl, should still be used if possible.
-
- @staticmethod
- def get_bootstrap_bat_args():
- return Clang.get_bootstrap_bat_args()
-
- @staticmethod
- def get_bootstrap_sh_args():
- return Clang.get_bootstrap_sh_args()
diff --git a/project/cmake/build.py b/project/cmake/build.py
index 6c5c68c..7513336 100644
--- a/project/cmake/build.py
+++ b/project/cmake/build.py
@@ -26,10 +26,9 @@ import os
import sys
import tempfile
-from project.cmake.toolset import Toolset
from project.configuration import Configuration
from project.platform import Platform
-from project.toolset import ToolsetHint
+from project.toolset import Toolset, ToolsetHint
from project.utils import normalize_path, mkdir_parent, run, setup_logging
diff --git a/project/cmake/toolset.py b/project/cmake/toolset.py
deleted file mode 100644
index f996d6b..0000000
--- a/project/cmake/toolset.py
+++ /dev/null
@@ -1,159 +0,0 @@
-# Copyright (c) 2020 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.
-
-# See docs/cmake.md for a more thorough description of my pain.
-
-import abc
-import os.path
-import shutil
-
-import project.mingw
-from project.os import on_windows
-from project.platform import Platform
-from project.toolset import ToolsetHint
-
-
-class Toolset(abc.ABC):
- def cmake_args(self, build_dir, platform):
- return []
-
- def build_system_args(self):
- return []
-
- @staticmethod
- def detect(hint):
- if hint is ToolsetHint.AUTO:
- return Auto
- if hint is ToolsetHint.MSVC:
- return MSVC
- if hint is ToolsetHint.GCC:
- return GCC
- if hint is ToolsetHint.MINGW:
- return MinGW
- if hint is ToolsetHint.CLANG:
- return Clang
- if hint is ToolsetHint.CLANG_CL:
- return ClangCL
- raise NotImplementedError(f'unrecognized toolset: {hint}')
-
- @staticmethod
- def make(hint, platform):
- cls = Toolset.detect(hint)
- if cls is MinGW:
- return MinGW(platform)
- return cls()
-
-
-class Auto(Toolset):
- def cmake_args(self, build_dir, platform):
- if on_windows():
- # On Windows, 'auto' means 'msvc', and we need to specify the -A
- # parameter. This might break if none of the Visual Studio
- # generators are available, but the NMake one is, although I don't
- # know how this can be possible normally.
- return MSVC().cmake_args(build_dir, platform)
- # On Linux, if the platform wasn't specified, auto-detect everything.
- # There's no need to set -mXX flags, etc.
- if platform is Platform.AUTO:
- return []
- # If a specific platform was requested, we might need to set some
- # CMake/compiler flags, like -m32/-m64.
- return GCC().cmake_args(build_dir, platform)
-
-
-class MSVC(Auto):
- def cmake_args(self, build_dir, platform):
- # This doesn't actually specify the generator of course, but I don't
- # want to implement VS detection logic.
- return ['-A', platform.msvc_arch()]
-
-
-class Makefile(Toolset):
- @staticmethod
- def _get_config_path(build_dir):
- return os.path.join(build_dir, 'custom_toolchain.cmake')
-
- @staticmethod
- def _get_makefile_generator():
- if on_windows():
- if shutil.which('mingw32-make'):
- return 'MinGW Makefiles'
- return 'Unix Makefiles'
- # On Linux/Cygwin, make all the way:
- return 'Unix Makefiles'
-
- @staticmethod
- def _write_config(build_dir, contents):
- path = Makefile._get_config_path(build_dir)
- with open(path, mode='w') as file:
- file.write(contents)
- return path
-
- @abc.abstractmethod
- def format_cmake_toolset_file(self, platform):
- pass
-
- def cmake_args(self, build_dir, platform):
- contents = self.format_cmake_toolset_file(platform)
- config_path = self._write_config(build_dir, contents)
- return [
- '-D', f'CMAKE_TOOLCHAIN_FILE={config_path}',
- # The Visual Studio generator is the default on Windows, override
- # it:
- '-G', self._get_makefile_generator(),
- ]
-
-
-class GCC(Makefile):
- def format_cmake_toolset_file(self, platform):
- return f'''
-set(CMAKE_C_COMPILER gcc)
-set(CMAKE_CXX_COMPILER g++)
-{platform.makefile_toolset_file()}'''
-
-
-class MinGW(Makefile):
- def __init__(self, platform):
- self.paths = project.mingw.MinGW(platform)
-
- def format_cmake_toolset_file(self, platform):
- return f'''
-set(CMAKE_C_COMPILER {self.paths.gcc()})
-set(CMAKE_CXX_COMPILER {self.paths.gxx()})
-set(CMAKE_AR {self.paths.ar()})
-set(CMAKE_RANLIB {self.paths.ranlib()})
-set(CMAKE_RC_COMPILER {self.paths.windres()})
-set(CMAKE_SYSTEM_NAME Windows)
-'''
-
-
-class Clang(Makefile):
- def format_cmake_toolset_file(self, platform):
- return f'''
-if(CMAKE_VERSION VERSION_LESS "3.15" AND WIN32)
- set(CMAKE_C_COMPILER clang-cl)
- set(CMAKE_CXX_COMPILER clang-cl)
-else()
- set(CMAKE_C_COMPILER clang)
- set(CMAKE_CXX_COMPILER clang++)
-endif()
-{platform.makefile_toolset_file()}'''
-
- def _get_makefile_generator(self):
- if on_windows():
- # MinGW utilities like make might be unavailable, but NMake can
- # very much be there.
- if shutil.which('nmake'):
- return 'NMake Makefiles'
- return super()._get_makefile_generator()
-
-
-class ClangCL(Clang):
- def format_cmake_toolset_file(self, platform):
- return f'''
-set(CMAKE_C_COMPILER clang-cl)
-set(CMAKE_CXX_COMPILER clang-cl)
-set(CMAKE_SYSTEM_NAME Windows)
-{platform.makefile_toolset_file()}'''
diff --git a/project/toolset.py b/project/toolset.py
index fc4aa85..6332d3e 100644
--- a/project/toolset.py
+++ b/project/toolset.py
@@ -23,8 +23,20 @@ limited to:
2. Boost 1.69.0 or higher only.
'''
+# See docs/{boost,cmake}.md for a more thorough description of my pain.
+
+import abc
import argparse
+from contextlib import contextmanager
from enum import Enum
+import logging
+import os.path
+import shutil
+
+import project.mingw
+from project.os import on_cygwin, on_linux, on_windows
+from project.platform import Platform
+from project.utils import temp_file
class ToolsetHint(Enum):
@@ -48,3 +60,369 @@ class ToolsetHint(Enum):
return ToolsetHint(s)
except ValueError as e:
raise argparse.ArgumentTypeError(f'invalid toolset: {s}') from e
+
+
+class Toolset(abc.ABC):
+ @contextmanager
+ def b2_args(self):
+ # Write the config file, etc.
+ yield []
+
+ @staticmethod
+ def get_bootstrap_bat_args():
+ return []
+
+ @staticmethod
+ def get_bootstrap_sh_args():
+ return []
+
+ def cmake_args(self, build_dir, platform):
+ return []
+
+ def build_system_args(self):
+ return []
+
+ @staticmethod
+ def detect(hint):
+ if hint is ToolsetHint.AUTO:
+ return Auto
+ if hint is ToolsetHint.MSVC:
+ return MSVC
+ if hint is ToolsetHint.GCC:
+ return GCC
+ if hint is ToolsetHint.MINGW:
+ return MinGW
+ if hint is ToolsetHint.CLANG:
+ return Clang
+ if hint is ToolsetHint.CLANG_CL:
+ return ClangCL
+ raise NotImplementedError(f'unrecognized toolset: {hint}')
+
+ @staticmethod
+ def make(hint, platform):
+ # Platform is required here, since some toolsets (MinGW-w64) require
+ # it for the compiler path.
+ cls = Toolset.detect(hint)
+ if cls is MinGW:
+ return MinGW(platform)
+ return cls()
+
+
+class Auto(Toolset):
+ # Let Boost.Build do the detection. Most commonly it means GCC on
+ # Linux-likes and MSVC on Windows.
+
+ def cmake_args(self, build_dir, platform):
+ if on_windows():
+ # On Windows, 'auto' means 'msvc', and we need to specify the -A
+ # parameter. This might break if none of the Visual Studio
+ # generators are available, but the NMake one is, although I don't
+ # know how this can be possible normally.
+ return MSVC().cmake_args(build_dir, platform)
+ # On Linux, if the platform wasn't specified, auto-detect everything.
+ # There's no need to set -mXX flags, etc.
+ if platform is Platform.AUTO:
+ return []
+ # If a specific platform was requested, we might need to set some
+ # CMake/compiler flags, like -m32/-m64.
+ return GCC().cmake_args(build_dir, platform)
+
+
+class MSVC(Auto):
+ @contextmanager
+ def b2_args(self):
+ yield ['toolset=msvc']
+
+ # Note: bootstrap.bat picks up MSVC by default.
+
+ def cmake_args(self, build_dir, platform):
+ # This doesn't actually specify the generator of course, but I don't
+ # want to implement VS detection logic.
+ return ['-A', platform.msvc_arch()]
+
+
+def _full_exe_name(exe):
+ if on_linux():
+ # There's no PATHEXT on Linux.
+ return exe
+ # b2 on Windows/Cygwin doesn't like it when the executable name doesn't
+ # include the extension.
+ dir_path = os.path.dirname(exe) or None
+ path = shutil.which(exe, path=dir_path)
+ if not path:
+ raise RuntimeError(f"executable '{exe}' could not be found")
+ if on_cygwin():
+ # On Cygwin, shutil.which('gcc') == '/usr/bin/gcc' and shutil.which('gcc.exe')
+ # == '/usr/bin/gcc.exe'; we want the latter version. shutil.which('clang++')
+ # == '/usr/bin/clang++' is fine though, since it _is_ the complete path
+ # (clang++ is a symlink).
+ if os.path.exists(path) and os.path.exists(path + '.exe'):
+ path += '.exe'
+ if dir_path:
+ # If it was found in a specific directory, include the directory in the
+ # result. shutil.which returns the executable name prefixed with the
+ # path argument.
+ return path
+ # If it was found in PATH, just return the basename (which includes the
+ # extension).
+ return os.path.basename(path)
+
+
+class Custom(Toolset):
+ # Boost.Build toolset defined using a config file.
+
+ COMPILER_VERSION = 'custom'
+
+ def __init__(self, compiler, path=None, build_options=None):
+ if not compiler:
+ raise RuntimeError('compiler type is required (like gcc, clang, etc.)')
+ self.compiler = compiler
+ version = Custom.COMPILER_VERSION
+ self.version = version
+ path = path or ''
+ path = path and _full_exe_name(path)
+ self.path = path
+ build_options = build_options or []
+ self.build_options = build_options
+
+ def toolset(self):
+ if self.version:
+ return f'{self.compiler}-{self.version}'
+ return self.compiler
+
+ def b2_arg_toolset(self):
+ return f'toolset={self.toolset()}'
+
+ def _format_build_options(self):
+ return ''.join(f'\n <{name}>{val}' for name, val in self.build_options)
+
+ def format_config(self):
+ version = self.version and f'{self.version} '
+ path = self.path and f'{self.path} '
+ return f'''using {self.compiler} : {version}: {path}:{self._format_build_options()}
+;'''
+
+ @contextmanager
+ def b2_args(self):
+ config_file = temp_file(prefix='user_config_', suffix='.jam')
+ with config_file as config_path:
+ config = self.format_config()
+ logging.info('Using user config:\n%s', config)
+ with open(config_path, mode='w') as fd:
+ fd.write(config)
+ args = []
+ args.append(self.b2_arg_toolset())
+ args.append(f'--user-config={config_path}')
+ yield args
+
+
+class Makefile(Toolset):
+ # One of CMake's "... Makefiles" generator toolsets.
+
+ @staticmethod
+ def _get_config_path(build_dir):
+ return os.path.join(build_dir, 'custom_toolchain.cmake')
+
+ @staticmethod
+ def _get_makefile_generator():
+ if on_windows():
+ if shutil.which('mingw32-make'):
+ return 'MinGW Makefiles'
+ return 'Unix Makefiles'
+ # On Linux/Cygwin, make all the way:
+ return 'Unix Makefiles'
+
+ @staticmethod
+ def _write_config(build_dir, contents):
+ path = Makefile._get_config_path(build_dir)
+ with open(path, mode='w') as file:
+ file.write(contents)
+ return path
+
+ @abc.abstractmethod
+ def format_cmake_toolset_file(self, platform):
+ pass
+
+ def cmake_args(self, build_dir, platform):
+ contents = self.format_cmake_toolset_file(platform)
+ config_path = self._write_config(build_dir, contents)
+ return [
+ '-D', f'CMAKE_TOOLCHAIN_FILE={config_path}',
+ # The Visual Studio generator is the default on Windows, override
+ # it:
+ '-G', self._get_makefile_generator(),
+ ]
+
+
+class GCC(Custom, Makefile):
+ # Force GCC. We don't care whether it's a native Linux GCC or a
+ # MinGW-flavoured GCC on Windows.
+
+ def __init__(self):
+ Custom.__init__(self, 'gcc', 'g++', self.get_build_options())
+ Makefile.__init__(self)
+
+ @staticmethod
+ def get_bootstrap_bat_args():
+ return ['gcc']
+
+ @staticmethod
+ def get_bootstrap_sh_args():
+ return ['--with-toolset=gcc']
+
+ @staticmethod
+ def get_build_options():
+ return []
+
+ def format_cmake_toolset_file(self, platform):
+ return f'''
+set(CMAKE_C_COMPILER gcc)
+set(CMAKE_CXX_COMPILER g++)
+{platform.makefile_toolset_file()}'''
+
+
+def _gcc_or_auto():
+ if shutil.which('gcc') is not None:
+ return ['gcc']
+ return []
+
+
+class MinGW(Custom, Makefile):
+ # It's important that Boost.Build is actually smart enough to detect the
+ # GCC prefix (like "x86_64-w64-mingw32" and prepend it to other tools like
+ # "ar").
+
+ def __init__(self, platform):
+ self.paths = project.mingw.MinGW(platform)
+ Custom.__init__(self, 'gcc', self.paths.gxx(), self.get_build_options())
+ Makefile.__init__(self)
+
+ @staticmethod
+ def get_bootstrap_bat_args():
+ # On Windows, prefer GCC if it's available.
+ return _gcc_or_auto()
+
+ @staticmethod
+ def get_bootstrap_sh_args():
+ return []
+
+ @staticmethod
+ def get_build_options():
+ return GCC.get_build_options()
+
+ def format_cmake_toolset_file(self, platform):
+ return f'''
+set(CMAKE_C_COMPILER {self.paths.gcc()})
+set(CMAKE_CXX_COMPILER {self.paths.gxx()})
+set(CMAKE_AR {self.paths.ar()})
+set(CMAKE_RANLIB {self.paths.ranlib()})
+set(CMAKE_RC_COMPILER {self.paths.windres()})
+set(CMAKE_SYSTEM_NAME Windows)
+'''
+
+
+class Clang(Custom, Makefile):
+ def __init__(self):
+ Custom.__init__(self, 'clang', 'clang++', self.get_build_options())
+ Makefile.__init__(self)
+
+ @staticmethod
+ def get_bootstrap_bat_args():
+ # As of 1.74.0, bootstrap.bat isn't really aware of Clang, so try GCC,
+ # then auto-detect.
+ return _gcc_or_auto()
+
+ @staticmethod
+ def get_bootstrap_sh_args():
+ # bootstrap.sh, on the other hand, is very much aware of Clang, and
+ # it can build b2 using this compiler.
+ return ['--with-toolset=clang']
+
+ @staticmethod
+ def get_build_options():
+ options = GCC.get_build_options()
+ options += [
+ ('cxxflags', '-DBOOST_USE_WINDOWS_H'),
+
+ # Even with <warnings>off, the build might sometimes fail with the
+ # following error:
+ #
+ # error: constant expression evaluates to -105 which cannot be narrowed to type 'boost::re_detail::cpp_regex_traits_implementation<char>::char_class_type' (aka 'unsigned int')
+ ('cxxflags', '-Wno-c++11-narrowing'),
+ ]
+ if on_windows():
+ # Prefer LLVM binutils:
+ if shutil.which('llvm-ar') is not None:
+ options.append(('archiver', 'llvm-ar'))
+ if shutil.which('llvm-ranlib') is not None:
+ options.append(('ranlib', 'llvm-ranlib'))
+ return options
+
+ def format_config(self):
+ # To make clang.exe/clang++.exe work on Windows, some tweaks are
+ # required. I borrowed them from CMake's Windows-Clang.cmake [1].
+ # Adding them globally to Boost.Build options is described in [2].
+ #
+ # [1]: https://github.com/Kitware/CMake/blob/v3.18.4/Modules/Platform/Windows-Clang.cmake
+ # [2]: https://stackoverflow.com/questions/2715106/how-to-create-a-new-variant-in-bjam
+ return f'''project : requirements
+ <target-os>windows:<define>_MT
+ <target-os>windows,<variant>debug:<define>_DEBUG
+ <target-os>windows,<runtime-link>static,<variant>debug:<cxxflags>"-Xclang -flto-visibility-public-std -Xclang --dependent-lib=libcmtd"
+ <target-os>windows,<runtime-link>static,<variant>release:<cxxflags>"-Xclang -flto-visibility-public-std -Xclang --dependent-lib=libcmt"
+ <target-os>windows,<runtime-link>shared,<variant>debug:<cxxflags>"-D_DLL -Xclang --dependent-lib=msvcrtd"
+ <target-os>windows,<runtime-link>shared,<variant>release:<cxxflags>"-D_DLL -Xclang --dependent-lib=msvcrt"
+;
+{Custom.format_config(self)}
+'''
+
+ def format_cmake_toolset_file(self, platform):
+ return f'''
+if(CMAKE_VERSION VERSION_LESS "3.15" AND WIN32)
+ set(CMAKE_C_COMPILER clang-cl)
+ set(CMAKE_CXX_COMPILER clang-cl)
+else()
+ set(CMAKE_C_COMPILER clang)
+ set(CMAKE_CXX_COMPILER clang++)
+endif()
+{platform.makefile_toolset_file()}'''
+
+ @staticmethod
+ def _get_makefile_generator():
+ if on_windows():
+ # MinGW utilities like make might be unavailable, but NMake can
+ # very much be there.
+ if shutil.which('nmake'):
+ return 'NMake Makefiles'
+ return Makefile._get_makefile_generator()
+
+
+class ClangCL(Makefile):
+ @contextmanager
+ def b2_args(self):
+ yield [
+ 'toolset=clang-win',
+ 'define=BOOST_USE_WINDOWS_H',
+ ]
+
+ # There's no point in building b2 using clang-cl; clang though, presumably
+ # installed alongside clang-cl, should still be used if possible.
+
+ @staticmethod
+ def get_bootstrap_bat_args():
+ return Clang.get_bootstrap_bat_args()
+
+ @staticmethod
+ def get_bootstrap_sh_args():
+ return Clang.get_bootstrap_sh_args()
+
+ def format_cmake_toolset_file(self, platform):
+ return f'''
+set(CMAKE_C_COMPILER clang-cl)
+set(CMAKE_CXX_COMPILER clang-cl)
+set(CMAKE_SYSTEM_NAME Windows)
+{platform.makefile_toolset_file()}'''
+
+ @staticmethod
+ def _get_makefile_generator():
+ return Clang._get_makefile_generator()