diff options
author | Egor Tensin <Egor.Tensin@gmail.com> | 2021-05-07 16:48:07 +0300 |
---|---|---|
committer | Egor Tensin <Egor.Tensin@gmail.com> | 2021-05-07 17:24:30 +0300 |
commit | ce359a1ae006b588b4427ef8784224a36ada4caf (patch) | |
tree | 2cafe6790d1a1e766ef4113c20d2a8c03df4312e /project/toolset.py | |
parent | v2.3 (diff) | |
download | cmake-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/toolset.py')
-rw-r--r-- | project/toolset.py | 244 |
1 files changed, 219 insertions, 25 deletions
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(), ] |