Files

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

293 lines
11 KiB
Python
Raw Permalink Normal View History

# /// script
# [tool.ty.environment]
# root = ["."]
# ///
###############################################################################
# #
# This file is part of IfcOpenShell. #
# #
# IfcOpenShell is free software: you can redistribute it and/or modify #
# it under the terms of the Lesser GNU General Public License as published by #
# the Free Software Foundation, either version 3.0 of the License, or #
# (at your option) any later version. #
# #
# IfcOpenShell is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# Lesser GNU General Public License for more details. #
# #
# You should have received a copy of the Lesser GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
###############################################################################
#
import os
import sys
from pathlib import Path
from typing import Literal, NamedTuple
from common import SCRIPT_DIR, logger, run
ArchBits = Literal[32, 64]
# TODO: according to cmake docs, there's no `ARM` platform, only `ARM64`.
# So it can be deleted?
VsPlatform = Literal["Win32", "x64", "ARM", "ARM64"]
"""Platform names as used by Visual Studio (e.g. `cmake -A`), but fine to use for general platform checks too."""
GeneratorType = Literal["EMPTY", "SHORTHAND", "FULL_NAME"]
class CMakeGeneratorInfo(NamedTuple):
name: str
"""E.g. "Visual Studio 16 2019"."""
generator_num: int
"""E.g. 16 for "Visual Studio 16 2019"."""
vs_ver: int
"""E.g. 2019."""
vc_ver: str
"""E.g. "14.2"."""
vs_toolset: str
"""Default toolset for this generator, e.g. "v142".
See `VsCfgResult.vs_toolset_override` for an explicit override.
"""
boost_bootstrap_ver: str
"""E.g. "vc142"."""
CMAKE_GENERATORS = {
"Visual Studio 12 2013": CMakeGeneratorInfo("Visual Studio 12 2013", 12, 2013, "12.0", "v120", "vc120"),
"Visual Studio 14 2015": CMakeGeneratorInfo("Visual Studio 14 2015", 14, 2015, "14.0", "v140", "vc140"),
"Visual Studio 15 2017": CMakeGeneratorInfo("Visual Studio 15 2017", 15, 2017, "14.1", "v141", "vc141"),
"Visual Studio 16 2019": CMakeGeneratorInfo("Visual Studio 16 2019", 16, 2019, "14.2", "v142", "vc142"),
"Visual Studio 17 2022": CMakeGeneratorInfo("Visual Studio 17 2022", 17, 2022, "14.3", "v143", "vc143"),
"Visual Studio 18 2026": CMakeGeneratorInfo("Visual Studio 18 2026", 18, 2026, "14.5", "v145", "vc145"),
}
class VsCfgResult(NamedTuple):
generator: CMakeGeneratorInfo
vs_platform: VsPlatform
"""E.g. "x64"."""
vs_toolset_override: str | None
"""E.g. "v142", or None if not explicitly overridden via generator shorthand."""
arch_bits: ArchBits
boost_toolset: str
"""E.g. "msvc-14.2"."""
gen_shorthand: str
"""E.g. "vs2019-x64"."""
deps_dir: Path
"""E.g. Path("_deps")."""
install_dir: Path
"""E.g. Path("_deps-vs2019-x64-installed")."""
build_dir: str
"""E.g. "_build-vs2019-x64"."""
build_deps_cache_path: Path
"""E.g. Path("BuildDepsCache-x64-v142.txt")."""
PRINT_VARS_SKIP = {"gen_shorthand", "deps_dir", "install_dir", "build_dir", "build_deps_cache_path"}
def print_vars(self) -> None:
fields = [f for f in self._fields if f not in self.PRINT_VARS_SKIP]
name_width = max(len(f) for f in fields)
for field in fields:
logger.info(f"{field.upper():<{name_width}}: [{getattr(self, field)}]")
def is_vs_platform(self, platform: VsPlatform) -> bool:
"""Compare against `vs_platform`, just to prevent typos."""
return self.vs_platform == platform
@property
def vs_toolset(self) -> str:
"""Effective toolset: `vs_toolset_override` if set, else the generator's default."""
return self.vs_toolset_override or self.generator.vs_toolset
VS_TOOLSET_TO_VC_VER = {info.vs_toolset: info.vc_ver for info in CMAKE_GENERATORS.values()}
"""E.g. "v142" -> "14.2"."""
VS_TOOLSET_TO_VS_VER = {info.vs_toolset: info.vs_ver for info in CMAKE_GENERATORS.values()}
"""E.g. "v142" -> 2019."""
class VSArchInfo(NamedTuple):
vs_platform: VsPlatform
arch_bits: ArchBits
VSCMD_ARG_TGT_ARCH_TO_INFO = {
"x86": VSArchInfo("Win32", 32),
"x64": VSArchInfo("x64", 64),
"arm": VSArchInfo("ARM", 32),
"arm64": VSArchInfo("ARM64", 64),
}
VS_PLATFORM_TO_INFO = {info.vs_platform: info for info in VSCMD_ARG_TGT_ARCH_TO_INFO.values()}
VSVar = Literal[
"VSINSTALLDIR",
"VisualStudioVersion",
"VSCMD_ARG_TGT_ARCH",
"UCRTVersion",
]
def get_vs_var(var: VSVar) -> str:
value = os.getenv(var)
if value is None:
logger.error("Visual Studio environment variables not set - cannot proceed.")
sys.exit(1)
return value
class VsCfg:
"""Determines the CMake generator, VS platform/toolset, and derived paths for a build.
Usage: `VsCfg.build(generator, repo_root)`.
"""
@staticmethod
def build(generator: str | None, repo_root: Path) -> VsCfgResult:
generator = generator or ""
vs_platform: VsPlatform | None = None
vs_toolset: str | None = None
boost_toolset_ver: str | None = None
generator_type = VsCfg._determine_generator_type(generator)
if generator_type in ("EMPTY", "SHORTHAND"):
generator, vs_platform, vs_toolset, boost_toolset_ver = VsCfg._parse_generator_shorthand(
generator, generator_type
)
# Just to be double sure.
assert generator in CMAKE_GENERATORS
VsCfg._ensure_cmake_supports_generator(generator)
vs_platform = vs_platform or VsCfg._vs_platform_from_env()
generator_info = CMAKE_GENERATORS[generator]
arch_bits = VS_PLATFORM_TO_INFO[vs_platform].arch_bits
boost_toolset = f"msvc-{boost_toolset_ver or generator_info.vc_ver}"
gen_shorthand = f"vs{generator_info.vs_ver}-{vs_platform}"
if vs_toolset is not None:
gen_shorthand += f"-{vs_toolset}"
# NOTE For IfcOpenShell we can build all of our deps both x86 and x64 using different VS versions in the same
# directories so no need for "-{vs_ver}-{vs_platform}" postfix.
deps_dir = repo_root / "_deps"
install_dir = repo_root / f"_deps-{gen_shorthand}-installed"
# build_dir is a relative build directory used for CMake-based projects.
build_dir = f"_build-{gen_shorthand}"
if vs_toolset is not None:
build_deps_cache_path = SCRIPT_DIR / f"BuildDepsCache-{vs_platform}-{vs_toolset}.txt"
else:
build_deps_cache_path = SCRIPT_DIR / f"BuildDepsCache-{vs_platform}.txt"
result = VsCfgResult(
generator=generator_info,
vs_platform=vs_platform,
vs_toolset_override=vs_toolset,
arch_bits=arch_bits,
boost_toolset=boost_toolset,
gen_shorthand=gen_shorthand,
deps_dir=deps_dir,
install_dir=install_dir,
build_dir=build_dir,
build_deps_cache_path=build_deps_cache_path,
)
result.print_vars()
return result
@staticmethod
def _generator_from_visual_studio_version() -> str:
# E.g. '17.0' -> 17.
vs_version = get_vs_var("VisualStudioVersion")
generator_num = int(vs_version.replace(".0", ""))
for candidate, info in CMAKE_GENERATORS.items():
if info.generator_num == generator_num:
logger.info(
f"Generator not passed, but VisualStudioVersion={vs_version} environment variable detected:"
)
logger.info(f"using '{candidate}' as the generator.")
return candidate
logger.error(
f"Generator is not provided and VisualStudioVersion='{vs_version}' is not supported - cannot proceed."
)
sys.exit(1)
@staticmethod
def _vs_platform_from_env() -> VsPlatform:
# Fall back to `VSCMD_ARG_TGT_ARCH`, the command prompt's target platform, set by vsvarsall.
VSCMD_ARG_TGT_ARCH = get_vs_var("VSCMD_ARG_TGT_ARCH")
return VSCMD_ARG_TGT_ARCH_TO_INFO[VSCMD_ARG_TGT_ARCH].vs_platform
@staticmethod
def _ensure_cmake_supports_generator(generator: str) -> None:
cmake_help = run("cmake", "--help")
if generator not in cmake_help:
logger.error(f"The used CMake version does not support generator '{generator}' - cannot proceed.")
sys.exit(1)
@staticmethod
def _determine_generator_type(generator: str) -> GeneratorType:
match generator:
case "":
return "EMPTY"
case _ if "vs20" in generator:
# E.g. 'vs2022-x64'.
return "SHORTHAND"
case _:
if generator not in CMAKE_GENERATORS:
supported_generators = ", ".join(repr(g) for g in CMAKE_GENERATORS)
logger.error(
f"Invalid or unsupported CMake generator string passed: '{generator}' - cannot proceed."
)
logger.error(f"Supported CMake generator strings: {supported_generators}")
sys.exit(1)
return "FULL_NAME"
@staticmethod
def _parse_generator_shorthand(
generator: str, generator_type: GeneratorType
) -> tuple[str, VsPlatform | None, str | None, str | None]:
# E.g. '2022-x64'.
gen_shorthand = generator.replace("vs", "")
vs_platform = None
vs_toolset = None
boost_toolset_ver = None
# Order matters, e.g. "-arm64" also matches "-arm", so it must come last.
for arch, info in VSCMD_ARG_TGT_ARCH_TO_INFO.items():
if f"-{arch}" in gen_shorthand.lower():
vs_platform = info.vs_platform
for toolset, vc_ver in VS_TOOLSET_TO_VC_VER.items():
if f"-{toolset}" in gen_shorthand:
vs_toolset = toolset
boost_toolset_ver = vc_ver
vs_ver = gen_shorthand[:4]
if generator_type == "SHORTHAND":
for candidate, info in CMAKE_GENERATORS.items():
if info.vs_ver == int(vs_ver):
generator = candidate
break
else:
supported_generators = ", ".join(repr(g) for g in CMAKE_GENERATORS)
logger.error(f"Invalid or unsupported CMake generator string passed: '{generator}' - cannot proceed.")
logger.error(f"Supported CMake generator strings: {supported_generators}")
sys.exit(1)
else:
generator = VsCfg._generator_from_visual_studio_version()
return generator, vs_platform, vs_toolset, boost_toolset_ver
def vs_cfg(generator: str | None, repo_root: Path) -> VsCfgResult:
return VsCfg.build(generator, repo_root)