aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/project
diff options
context:
space:
mode:
authorEgor Tensin <Egor.Tensin@gmail.com>2021-05-07 16:48:07 +0300
committerEgor Tensin <Egor.Tensin@gmail.com>2021-05-07 17:24:30 +0300
commitce359a1ae006b588b4427ef8784224a36ada4caf (patch)
tree2cafe6790d1a1e766ef4113c20d2a8c03df4312e /project
parentv2.3 (diff)
downloadcmake-common-ce359a1ae006b588b4427ef8784224a36ada4caf.tar.gz
cmake-common-ce359a1ae006b588b4427ef8784224a36ada4caf.zip
project.toolset: support versioned MSVC toolsets
You can now use something like msvc-141, vs-2017, etc.
Diffstat (limited to 'project')
-rw-r--r--project/boost/build.py18
-rw-r--r--project/boost/directory.py6
-rw-r--r--project/ci/boost.py2
-rw-r--r--project/ci/cmake.py2
-rw-r--r--project/ci/dirs.py4
-rw-r--r--project/cmake/build.py19
-rw-r--r--project/toolset.py244
7 files changed, 244 insertions, 51 deletions
diff --git a/project/boost/build.py b/project/boost/build.py
index 53fdf42..cb3688c 100644
--- a/project/boost/build.py
+++ b/project/boost/build.py
@@ -35,7 +35,7 @@ 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 Toolset, ToolsetHint
+from project.toolset import Toolset, ToolsetVersion
from project.utils import normalize_path, setup_logging
@@ -45,6 +45,7 @@ DEFAULT_CONFIGURATIONS = (Configuration.DEBUG, Configuration.RELEASE,)
# binaries from a CI, etc. and run them everywhere):
DEFAULT_LINK = (Linkage.STATIC,)
DEFAULT_RUNTIME_LINK = Linkage.STATIC
+DEFAULT_TOOLSET_VERSION = ToolsetVersion.default()
B2_QUIET = ['warnings=off', '-d0']
B2_VERBOSE = ['warnings=all', '-d2', '--debug-configuration']
@@ -52,7 +53,7 @@ B2_VERBOSE = ['warnings=all', '-d2', '--debug-configuration']
class BuildParameters:
def __init__(self, boost_dir, build_dir=None, platforms=None,
configurations=None, link=None, runtime_link=None,
- toolset_hint=None, verbose=False, b2_args=None):
+ toolset_version=None, verbose=False, b2_args=None):
boost_dir = normalize_path(boost_dir)
if build_dir is not None:
@@ -61,7 +62,7 @@ class BuildParameters:
configurations = configurations or DEFAULT_CONFIGURATIONS
link = link or DEFAULT_LINK
runtime_link = runtime_link or DEFAULT_RUNTIME_LINK
- toolset_hint = toolset_hint or ToolsetHint.AUTO
+ toolset_version = toolset_version or DEFAULT_TOOLSET_VERSION
verbosity = B2_VERBOSE if verbose else B2_QUIET
if b2_args:
b2_args = verbosity + b2_args
@@ -74,7 +75,7 @@ class BuildParameters:
self.configurations = configurations
self.link = link
self.runtime_link = runtime_link
- self.toolset_hint = toolset_hint
+ self.toolset_version = toolset_version
self.b2_args = b2_args
@staticmethod
@@ -84,7 +85,7 @@ class BuildParameters:
def enum_b2_args(self):
with self._create_build_dir() as build_dir:
for platform in self.platforms:
- toolset = Toolset.make(self.toolset_hint, platform)
+ toolset = Toolset.make(self.toolset_version, platform)
for configuration in self.configurations:
for link, runtime_link in self._enum_linkage_options():
with self._b2_args(build_dir, toolset, platform, configuration, link, runtime_link) as args:
@@ -167,10 +168,9 @@ def _parse_args(argv=None):
type=Linkage.parse, default=DEFAULT_RUNTIME_LINK,
help=f'how the libraries link to the runtime ({linkage_options})')
- toolset_options = '/'.join(map(str, ToolsetHint.all()))
- parser.add_argument('--toolset', metavar='TOOLSET', dest='toolset_hint',
- type=ToolsetHint.parse, default=ToolsetHint.AUTO,
- help=f'toolset to use ({toolset_options})')
+ 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('--build', metavar='DIR', dest='build_dir',
type=normalize_path,
diff --git a/project/boost/directory.py b/project/boost/directory.py
index 4da58a7..be633fe 100644
--- a/project/boost/directory.py
+++ b/project/boost/directory.py
@@ -33,7 +33,7 @@ class BoostDir:
def bootstrap(self, params):
with self._go():
- run([self._bootstrap_path()] + self._bootstrap_args(params.toolset_hint))
+ run([self._bootstrap_path()] + self._bootstrap_args(params.toolset_version))
def _b2(self, params):
for b2_params in params.enum_b2_args():
@@ -51,8 +51,8 @@ class BoostDir:
return f'bootstrap{ext}'
@staticmethod
- def _bootstrap_args(hint):
- toolset = Toolset.detect(hint)
+ def _bootstrap_args(toolset_version):
+ toolset = Toolset.detect(toolset_version)
if on_windows():
return toolset.bootstrap_bat_args()
return toolset.bootstrap_sh_args()
diff --git a/project/ci/boost.py b/project/ci/boost.py
index 8784e19..0d89b75 100644
--- a/project/ci/boost.py
+++ b/project/ci/boost.py
@@ -59,7 +59,7 @@ def build_ci(dirs, argv=None):
configurations=(dirs.get_configuration(),),
link=args.link,
runtime_link=args.runtime_link,
- toolset_hint=dirs.get_toolset(),
+ toolset_version=dirs.get_toolset(),
b2_args=args.b2_args)
build(params)
diff --git a/project/ci/cmake.py b/project/ci/cmake.py
index 9234a39..1f49f78 100644
--- a/project/ci/cmake.py
+++ b/project/ci/cmake.py
@@ -63,7 +63,7 @@ def build_ci(dirs, argv=None):
platform=dirs.get_platform(),
configuration=dirs.get_configuration(),
boost_dir=boost_dir,
- toolset_hint=dirs.get_toolset(),
+ toolset_version=dirs.get_toolset(),
cmake_args=dirs.get_cmake_args() + args.cmake_args)
build(params)
diff --git a/project/ci/dirs.py b/project/ci/dirs.py
index 7e3ce03..eb4651b 100644
--- a/project/ci/dirs.py
+++ b/project/ci/dirs.py
@@ -11,7 +11,7 @@ from project.boost.version import Version
from project.ci.appveyor.generator import Generator, Image
from project.configuration import Configuration
from project.platform import Platform
-from project.toolset import ToolsetHint
+from project.toolset import ToolsetVersion
from project.utils import env
@@ -47,7 +47,7 @@ class Dirs(abc.ABC):
@staticmethod
def get_toolset():
if 'TOOLSET' in os.environ:
- return ToolsetHint.parse(os.environ['TOOLSET'])
+ return ToolsetVersion.parse(os.environ['TOOLSET'])
return None
@staticmethod
diff --git a/project/cmake/build.py b/project/cmake/build.py
index 7513336..c9e798b 100644
--- a/project/cmake/build.py
+++ b/project/cmake/build.py
@@ -28,13 +28,13 @@ import tempfile
from project.configuration import Configuration
from project.platform import Platform
-from project.toolset import Toolset, ToolsetHint
+from project.toolset import Toolset, ToolsetVersion
from project.utils import normalize_path, mkdir_parent, run, setup_logging
DEFAULT_PLATFORM = Platform.AUTO
DEFAULT_CONFIGURATION = Configuration.DEBUG
-DEFAULT_TOOLSET_HINT = ToolsetHint.AUTO
+DEFAULT_TOOLSET_VERSION = ToolsetVersion.default()
# This way of basically passing `-j` to make is more universal compared to
@@ -132,7 +132,7 @@ class BuildPhase:
class BuildParameters:
def __init__(self, src_dir, build_dir=None, install_dir=None,
platform=None, configuration=None, boost_dir=None,
- toolset_hint=None, cmake_args=None):
+ toolset_version=None, cmake_args=None):
src_dir = normalize_path(src_dir)
if build_dir is not None:
@@ -143,7 +143,7 @@ class BuildParameters:
configuration = configuration or DEFAULT_CONFIGURATION
if boost_dir is not None:
boost_dir = normalize_path(boost_dir)
- toolset_hint = toolset_hint or DEFAULT_TOOLSET_HINT
+ toolset_version = toolset_version or DEFAULT_TOOLSET_VERSION
cmake_args = cmake_args or []
self.src_dir = src_dir
@@ -152,7 +152,7 @@ class BuildParameters:
self.platform = platform
self.configuration = configuration
self.boost_dir = boost_dir
- self.toolset_hint = toolset_hint
+ self.toolset_version = toolset_version
self.cmake_args = cmake_args
@staticmethod
@@ -178,7 +178,7 @@ class BuildParameters:
def build(params):
with params.create_build_dir() as build_dir:
- toolset = Toolset.make(params.toolset_hint, params.platform)
+ toolset = Toolset.make(params.toolset_version, params.platform)
gen_phase = GenerationPhase(params.src_dir, build_dir,
install_dir=params.install_dir,
@@ -221,10 +221,9 @@ def _parse_args(argv=None):
type=normalize_path,
help='set Boost directory path')
- toolset_options = '/'.join(map(str, ToolsetHint.all()))
- parser.add_argument('--toolset', metavar='TOOLSET', dest='toolset_hint',
- type=ToolsetHint.parse, default=ToolsetHint.AUTO,
- help=f'toolset to use ({toolset_options})')
+ 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('src_dir', metavar='DIR',
type=normalize_path,
diff --git a/project/toolset.py b/project/toolset.py
index d1a2d29..b9cb6c3 100644
--- a/project/toolset.py
+++ b/project/toolset.py
@@ -28,6 +28,7 @@ limited to:
import abc
import argparse
from contextlib import contextmanager
+from decimal import Decimal
from enum import Enum
import logging
import os.path
@@ -39,9 +40,119 @@ from project.platform import Platform
from project.utils import temp_file
-class ToolsetHint(Enum):
+class MSVCVersion(Enum):
+ # It's the "toolset" version, or whatever that is.
+ # Source: https://cmake.org/cmake/help/v3.20/variable/MSVC_TOOLSET_VERSION.html#variable:MSVC_TOOLSET_VERSION
+
+ VS2005 = '80'
+ VS2008 = '90'
+ VS2010 = '100'
+ VS2012 = '110'
+ VS2013 = '120'
+ VS2015 = '140'
+ VS2017 = '141'
+ VS2019 = '142'
+
+ def __str__(self):
+ return str(self.value)
+
+ @staticmethod
+ def parse(s):
+ try:
+ return MSVCVersion(s)
+ except ValueError as e:
+ raise argparse.ArgumentTypeError(f'invalid MSVC version: {s}') from e
+
+ @staticmethod
+ def all():
+ return tuple(MSVCVersion)
+
+ def to_msvc_version(self):
+ return self
+
+ def to_visual_studio_version(self):
+ if MSVCVersion.VS2005:
+ return VisualStudioVersion.VS2005
+ if MSVCVersion.VS2008:
+ return VisualStudioVersion.VS2008
+ if MSVCVersion.VS2010:
+ return VisualStudioVersion.VS2010
+ if MSVCVersion.VS2012:
+ return VisualStudioVersion.VS2012
+ if MSVCVersion.VS2013:
+ return VisualStudioVersion.VS2013
+ if MSVCVersion.VS2015:
+ return VisualStudioVersion.VS2015
+ if MSVCVersion.VS2017:
+ return VisualStudioVersion.VS2017
+ if MSVCVersion.VS2019:
+ return VisualStudioVersion.VS2019
+ raise NotImplementedError(f'unsupported MSVC version: {self}')
+
+ def to_boost_msvc_version(self):
+ try:
+ numeric = int(self.value)
+ except ValueError:
+ raise RuntimeError(f'what? MSVC versions are supposed to be integers: {self.value}')
+ numeric = Decimal(numeric) / 10
+ numeric = numeric.quantize(Decimal('1.0'))
+ return str(numeric)
+
+ def to_cmake_toolset(self):
+ return f'v{self}'
+
+
+class VisualStudioVersion(Enum):
+ VS2005 = '2005'
+ VS2008 = '2008'
+ VS2010 = '2010'
+ VS2012 = '2012'
+ VS2013 = '2013'
+ VS2015 = '2015'
+ VS2017 = '2017'
+ VS2019 = '2019'
+
+ def __str__(self):
+ return str(self.value)
+
+ @staticmethod
+ def parse(s):
+ try:
+ return VisualStudioVersion(s)
+ except ValueError as e:
+ raise argparse.ArgumentTypeError(f'invalid Visual Studio version: {s}') from e
+
+ @staticmethod
+ def all():
+ return tuple(VisualStudioVersion)
+
+ def to_msvc_version(self):
+ if self is VisualStudioVersion.VS2005:
+ return MSVCVersion.VS2005
+ if self is VisualStudioVersion.VS2008:
+ return MSVCVersion.VS2008
+ if self is VisualStudioVersion.VS2010:
+ return MSVCVersion.VS2010
+ if self is VisualStudioVersion.VS2012:
+ return MSVCVersion.VS2012
+ if self is VisualStudioVersion.VS2013:
+ return MSVCVersion.VS2013
+ if self is VisualStudioVersion.VS2015:
+ return MSVCVersion.VS2015
+ if self is VisualStudioVersion.VS2017:
+ return MSVCVersion.VS2017
+ if self is VisualStudioVersion.VS2019:
+ return MSVCVersion.VS2019
+ raise NotImplementedError(f'unsupported Visual Studio version: {self}')
+
+ def to_visual_studio_version(self):
+ return self
+
+
+class ToolsetType(Enum):
AUTO = 'auto' # This most commonly means GCC on Linux and MSVC on Windows.
MSVC = 'msvc' # Force MSVC.
+ VISUAL_STUDIO = 'visual-studio' # Same as 'msvc'.
GCC = 'gcc' # Force GCC.
MINGW = 'mingw' # As in MinGW-w64; GCC with the PLATFORM-w64-mingw32 prefix.
CLANG = 'clang'
@@ -51,20 +162,83 @@ class ToolsetHint(Enum):
return str(self.value)
@staticmethod
+ def parse(s):
+ try:
+ return ToolsetType(s)
+ except ValueError as e:
+ raise argparse.ArgumentTypeError(f'invalid toolset: {s}') from e
+
+ @property
+ def supports_version(self):
+ if self is ToolsetType.MSVC or self is ToolsetType.VISUAL_STUDIO:
+ return True
+ return False
+
+ def parse_version(self, s):
+ if self is ToolsetType.MSVC:
+ return MSVCVersion.parse(s)
+ if self is ToolsetType.VISUAL_STUDIO:
+ return VisualStudioVersion.parse(s)
+ raise RuntimeError(f"this toolset doesn't support versions: {self}")
+
+ @staticmethod
def all():
- return tuple(ToolsetHint)
+ return tuple(ToolsetType)
+
+ @staticmethod
+ def versioned():
+ return (t for t in ToolsetType.all() if t.supports_version)
+
+ @staticmethod
+ def non_versioned():
+ return (t for t in ToolsetType.all() if not t.supports_version)
+
+
+class ToolsetVersion:
+ _VERSION_SEP = '-'
+
+ def __init__(self, hint, version):
+ self.hint = hint
+ self.version = version
+
+ def __str__(self):
+ if self.version is None:
+ return str(self.hint)
+ return f'{self.hint}{ToolsetVersion._VERSION_SEP}{self.version}'
+
+ @staticmethod
+ def default():
+ return ToolsetVersion(ToolsetType.AUTO, None)
+
+ @staticmethod
+ def all_usage_placeholders():
+ for hint in ToolsetType.all():
+ if hint.supports_version:
+ yield f'{hint}[{ToolsetVersion._VERSION_SEP}VERSION]'
+ else:
+ yield str(hint)
+
+ @staticmethod
+ def usage():
+ return '/'.join(ToolsetVersion.all_usage_placeholders())
@staticmethod
def parse(s):
try:
- return ToolsetHint(s)
- except ValueError as e:
- raise argparse.ArgumentTypeError(f'invalid toolset: {s}') from e
+ return ToolsetVersion(ToolsetType(s), None)
+ except ValueError:
+ pass
+ for hint in ToolsetType.versioned():
+ prefix = f'{hint}{ToolsetVersion._VERSION_SEP}'
+ if s.startswith(prefix):
+ return ToolsetVersion(hint, hint.parse_version(s[len(prefix):]))
+ raise argparse.ArgumentTypeError(f'invalid toolset: {s}')
class Toolset(abc.ABC):
+ @staticmethod
@contextmanager
- def b2_args(self):
+ def b2_args():
# Write the config file, etc.
yield []
@@ -77,36 +251,45 @@ class Toolset(abc.ABC):
return []
@staticmethod
- def cmake_args(build_dir, platform):
- return []
+ def cmake_generator():
+ return None
+
+ def cmake_args(self, build_dir, platform):
+ args = []
+ generator = self.cmake_generator()
+ if generator is not None:
+ args += ['-G', generator]
+ return args
@staticmethod
def build_system_args():
return []
@staticmethod
- def detect(hint):
- if hint is ToolsetHint.AUTO:
+ def detect(version):
+ if version.hint is ToolsetType.AUTO:
return Auto
- if hint is ToolsetHint.MSVC:
+ if version.hint is ToolsetType.MSVC or version.hint is ToolsetType.VISUAL_STUDIO:
return MSVC
- if hint is ToolsetHint.GCC:
+ if version.hint is ToolsetType.GCC:
return GCC
- if hint is ToolsetHint.MINGW:
+ if version.hint is ToolsetType.MINGW:
return MinGW
- if hint is ToolsetHint.CLANG:
+ if version.hint is ToolsetType.CLANG:
return Clang
- if hint is ToolsetHint.CLANG_CL:
+ if version.hint is ToolsetType.CLANG_CL:
return ClangCL
- raise NotImplementedError(f'unrecognized toolset: {hint}')
+ raise NotImplementedError(f'unrecognized toolset: {version}')
@staticmethod
- def make(hint, platform):
+ def make(version, platform):
# Platform is required here, since some toolsets (MinGW-w64) require
# it for the compiler path.
- cls = Toolset.detect(hint)
+ cls = Toolset.detect(version)
if cls is MinGW:
return MinGW(platform)
+ if version.hint.supports_version:
+ return cls(version.version)
return cls()
@@ -124,23 +307,35 @@ class Auto(Toolset):
# 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 []
+ return super().cmake_args(build_dir, platform)
# 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(Toolset):
+ def __init__(self, version=None):
+ self.version = version
+
+ def b2_toolset(self):
+ if self.version is not None:
+ return f'msvc-{self.version.to_msvc_version().to_boost_msvc_version()}'
+ return 'msvc'
+
@contextmanager
def b2_args(self):
- yield ['toolset=msvc']
+ yield [f'toolset={self.b2_toolset()}']
# 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()]
+ args = super().cmake_args(build_dir, platform)
+ args += ['-A', platform.msvc_arch()]
+ if self.version is not None:
+ args += ['-T', self.version.to_msvc_version().to_cmake_toolset()]
+ return args
def _full_exe_name(exe):
@@ -224,6 +419,7 @@ class BoostCustom(Toolset):
class CMakeCustom(Toolset):
@staticmethod
def cmake_generator():
+ # The Visual Studio generator is the default on Windows, override it:
return CMakeCustom.makefiles()
@staticmethod
@@ -258,11 +454,9 @@ class CMakeCustom(Toolset):
def cmake_args(self, build_dir, platform):
contents = self.cmake_format_config(platform)
config_path = self._cmake_write_config(build_dir, contents)
- return [
+
+ return super().cmake_args(build_dir, platform) + [
'-D', f'CMAKE_TOOLCHAIN_FILE={config_path}',
- # The Visual Studio generator is the default on Windows, override
- # it:
- '-G', self.cmake_generator(),
]