2026-09-11 16:07:36 +05:00
|
|
|
# /// 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 argparse
|
|
|
|
|
import os
|
2026-09-15 10:04:55 +05:00
|
|
|
import shutil
|
2026-09-11 16:07:36 +05:00
|
|
|
import sys
|
|
|
|
|
from itertools import chain
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import Literal, NamedTuple, NoReturn
|
|
|
|
|
|
|
|
|
|
from common import (
|
2026-09-16 10:12:15 +05:00
|
|
|
ADD_COMMIT_SHA_DEFAULT,
|
2026-09-11 16:07:36 +05:00
|
|
|
OFF_ON,
|
|
|
|
|
PROJECT_NAME,
|
|
|
|
|
REPO_ROOT,
|
|
|
|
|
C,
|
|
|
|
|
HelpStrings,
|
|
|
|
|
colorize,
|
|
|
|
|
ensure_script_dir,
|
|
|
|
|
logger,
|
2026-09-14 15:15:36 +05:00
|
|
|
resolve_cli_or_env,
|
2026-09-11 16:07:36 +05:00
|
|
|
run,
|
|
|
|
|
run_streamed,
|
|
|
|
|
)
|
2026-09-16 09:53:30 +05:00
|
|
|
from common_win import BuildDepsCache, resolve_generator
|
2026-09-11 16:07:36 +05:00
|
|
|
from vs_cfg import VsCfgResult, vs_cfg
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Dep(NamedTuple):
|
|
|
|
|
env_var: str
|
|
|
|
|
rel_path: Path | None
|
|
|
|
|
cmake_prefix: bool = True
|
|
|
|
|
"""Whether the dep's dir should be added to CMAKE_PREFIX_PATH."""
|
2026-09-14 15:32:16 +05:00
|
|
|
pass_as_cmake_arg: bool = False
|
|
|
|
|
"""Whether `env_var` should be passed to cmake as a `-D` cache variable."""
|
2026-09-11 16:07:36 +05:00
|
|
|
required: bool = True
|
|
|
|
|
"""Whether to error out if the dep's value can't be resolved."""
|
|
|
|
|
base: Literal["DEPS", "INSTALL"] = "INSTALL"
|
|
|
|
|
"""Which base dir `rel_path` is relative to."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Deps:
|
|
|
|
|
DEPS: dict[str, Dep] = {
|
|
|
|
|
"boost": Dep("BOOST_INSTALL_DIR", None),
|
|
|
|
|
"occ": Dep("OCC_INSTALL_DIR", None),
|
|
|
|
|
"opencollada": Dep("OPENCOLLADA_INSTALL_DIR", Path("OpenCOLLADA")),
|
|
|
|
|
# We don't install Eigen currently,
|
|
|
|
|
# so there's no Eigen3config.cmake and therefore we provide path explicitly.
|
2026-09-14 15:32:16 +05:00
|
|
|
"eigen": Dep("EIGEN_DIR", Path("Eigen"), cmake_prefix=False, pass_as_cmake_arg=True),
|
2026-09-11 16:07:36 +05:00
|
|
|
"cgal": Dep("CGAL_INSTALL_DIR", Path("cgal")),
|
|
|
|
|
"gmp": Dep("GMP_INSTALL_DIR", Path("mpir")),
|
|
|
|
|
"mpfr": Dep("MPFR_INSTALL_DIR", Path("mpfr")),
|
|
|
|
|
# CCACHE_INSTALL_DIR is only set when ccache wasn't found on PATH.
|
|
|
|
|
"ccache": Dep("CCACHE_INSTALL_DIR", None, required=False),
|
|
|
|
|
"zstd": Dep("ZSTD_INSTALL_DIR", Path("zstd")),
|
|
|
|
|
"swig": Dep("SWIG_INSTALL_DIR", None),
|
|
|
|
|
"rocksdb": Dep("ROCKSDB_INSTALL_DIR", Path("rocksdb")),
|
2026-09-14 15:32:16 +05:00
|
|
|
"json": Dep("JSON_INCLUDE_DIR", Path("json"), cmake_prefix=False, pass_as_cmake_arg=True),
|
2026-09-11 16:07:36 +05:00
|
|
|
"libxml2_libraries": Dep(
|
2026-09-14 15:32:16 +05:00
|
|
|
"LIBXML2_LIBRARIES",
|
|
|
|
|
Path("OpenCOLLADA/lib/opencollada/xml.lib"),
|
|
|
|
|
cmake_prefix=False,
|
|
|
|
|
pass_as_cmake_arg=True,
|
2026-09-11 16:07:36 +05:00
|
|
|
),
|
|
|
|
|
"libxml2_include_dir": Dep(
|
|
|
|
|
"LIBXML2_INCLUDE_DIR",
|
|
|
|
|
Path("OpenCOLLADA/Externals/LibXML/include"),
|
|
|
|
|
cmake_prefix=False,
|
2026-09-14 15:32:16 +05:00
|
|
|
pass_as_cmake_arg=True,
|
2026-09-11 16:07:36 +05:00
|
|
|
base="DEPS",
|
|
|
|
|
),
|
|
|
|
|
# TODO: drop this TRANSITION check once everyone has re-run build-deps.py with manifold support.
|
|
|
|
|
"manifold": Dep("MANIFOLD_INSTALL_PATH", None, required=False),
|
|
|
|
|
"pythonhome": Dep("PYTHONHOME", None, cmake_prefix=False),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_values: dict[str, Path | None] | None = None
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def values(cls) -> dict[str, Path | None]:
|
|
|
|
|
if cls._values is None:
|
|
|
|
|
raise RuntimeError("Deps.values() accessed before Deps.init_values() was called.")
|
|
|
|
|
return cls._values
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def init_values(cls, vs_cfg_vars: VsCfgResult, deps_cache: dict[str, str]) -> None:
|
|
|
|
|
cls._values = {}
|
|
|
|
|
has_errors = False
|
|
|
|
|
for name, dep in cls.DEPS.items():
|
|
|
|
|
if dep.rel_path is not None:
|
|
|
|
|
base_dir = vs_cfg_vars.install_dir if dep.base == "INSTALL" else vs_cfg_vars.deps_dir
|
|
|
|
|
value = base_dir / dep.rel_path
|
|
|
|
|
else:
|
|
|
|
|
value = get_var(deps_cache, dep.env_var, deps_cache_only=True)
|
|
|
|
|
if isinstance(value, str):
|
|
|
|
|
value = Path(value)
|
|
|
|
|
if dep.required:
|
|
|
|
|
if value is None:
|
|
|
|
|
logger.error(f"{dep.env_var} is required but could not be resolved.")
|
|
|
|
|
has_errors = True
|
|
|
|
|
elif not value.exists():
|
|
|
|
|
logger.error(f"{dep.env_var} does not exist: {value}")
|
|
|
|
|
has_errors = True
|
|
|
|
|
cls._values[name] = value
|
|
|
|
|
if has_errors:
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def cmake_prefix_paths(cls) -> list[Path]:
|
|
|
|
|
return [
|
|
|
|
|
value for name, dep in cls.DEPS.items() if dep.cmake_prefix and (value := cls.values()[name]) is not None
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2026-09-14 15:32:16 +05:00
|
|
|
def cmake_args(cls) -> list[str]:
|
|
|
|
|
return [f"-D{dep.env_var}={cls.values()[name]}" for name, dep in cls.DEPS.items() if dep.pass_as_cmake_arg]
|
2026-09-11 16:07:36 +05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_var(deps_cache: dict[str, str], key: str, *, deps_cache_only: bool = False) -> str | None:
|
|
|
|
|
# TODO: just rely on `deps_cache`?
|
|
|
|
|
if deps_cache_only:
|
|
|
|
|
return deps_cache.get(key)
|
|
|
|
|
return deps_cache.get(key) or os.environ.get(key)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Args(NamedTuple):
|
|
|
|
|
generator: str | None
|
2026-09-14 15:15:36 +05:00
|
|
|
add_commit_sha: bool
|
|
|
|
|
use_ninja: bool
|
2026-09-15 10:04:55 +05:00
|
|
|
clean: bool
|
2026-09-11 16:07:36 +05:00
|
|
|
extra_args: list[str]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ArgumentParser(argparse.ArgumentParser):
|
|
|
|
|
def error(self, message: str) -> NoReturn:
|
|
|
|
|
# TODO: same as in build-ifcopenshell.py, can be removed later.
|
|
|
|
|
if message.startswith("unrecognized arguments"):
|
|
|
|
|
message += (
|
|
|
|
|
"\nHint: put args meant for CMake after '--', e.g. `run-cmake.py vs2022-x64 -- -DGLTF_SUPPORT=ON`."
|
|
|
|
|
)
|
|
|
|
|
super().error(message)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_args() -> Args:
|
|
|
|
|
parser = ArgumentParser(
|
|
|
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
|
|
|
epilog="Arguments after '--' are passed through as-is to CMake, e.g. -DGLTF_SUPPORT=ON.",
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"generator",
|
|
|
|
|
nargs="?",
|
|
|
|
|
default=None,
|
|
|
|
|
help=HelpStrings.generator("GEN_SHORTHAND from the most recently modified BuildDepsCache-*.txt is used"),
|
|
|
|
|
)
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--generator",
|
|
|
|
|
dest="generator_flag",
|
|
|
|
|
default=None,
|
|
|
|
|
help=HelpStrings.GENERATOR_FLAG,
|
|
|
|
|
)
|
2026-09-14 15:15:36 +05:00
|
|
|
parser.add_argument(
|
|
|
|
|
"--add-commit-sha",
|
|
|
|
|
dest="add_commit_sha",
|
|
|
|
|
action=argparse.BooleanOptionalAction,
|
|
|
|
|
default=argparse.SUPPRESS,
|
2026-09-16 10:12:15 +05:00
|
|
|
help=HelpStrings.ADD_COMMIT_SHA,
|
2026-09-14 15:15:36 +05:00
|
|
|
)
|
|
|
|
|
USE_NINJA_DEFAULT = False
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"--use-ninja",
|
|
|
|
|
dest="use_ninja",
|
|
|
|
|
action=argparse.BooleanOptionalAction,
|
|
|
|
|
default=argparse.SUPPRESS,
|
|
|
|
|
help=(
|
|
|
|
|
"Use the Ninja generator instead of the MSVC generator/platform. "
|
|
|
|
|
"Also can be specified by using USE_NINJA env variable. "
|
|
|
|
|
f"(default: {USE_NINJA_DEFAULT})"
|
|
|
|
|
),
|
|
|
|
|
)
|
2026-09-15 10:04:55 +05:00
|
|
|
parser.add_argument(
|
|
|
|
|
"--clean",
|
|
|
|
|
dest="clean",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Remove the existing build directory before running CMake.",
|
|
|
|
|
)
|
2026-09-11 16:07:36 +05:00
|
|
|
argv = sys.argv[1:]
|
|
|
|
|
if "--" in argv:
|
|
|
|
|
separator_idx = argv.index("--")
|
|
|
|
|
own_argv, extra_args = argv[:separator_idx], argv[separator_idx + 1 :]
|
|
|
|
|
else:
|
|
|
|
|
own_argv, extra_args = argv, []
|
|
|
|
|
|
|
|
|
|
args = parser.parse_args(own_argv)
|
|
|
|
|
|
|
|
|
|
if args.generator is not None and args.generator_flag is not None:
|
|
|
|
|
parser.error("generator was specified both as a positional argument and as --generator.")
|
|
|
|
|
generator = args.generator or args.generator_flag
|
|
|
|
|
|
2026-09-14 15:15:36 +05:00
|
|
|
add_commit_sha = resolve_cli_or_env(
|
|
|
|
|
getattr(args, "add_commit_sha", None), "ADD_COMMIT_SHA", ADD_COMMIT_SHA_DEFAULT, arg_type="bool"
|
|
|
|
|
)
|
|
|
|
|
use_ninja = resolve_cli_or_env(getattr(args, "use_ninja", None), "USE_NINJA", USE_NINJA_DEFAULT, arg_type="bool")
|
|
|
|
|
|
|
|
|
|
return Args(
|
|
|
|
|
generator=generator,
|
|
|
|
|
add_commit_sha=add_commit_sha,
|
|
|
|
|
use_ninja=use_ninja,
|
2026-09-15 10:04:55 +05:00
|
|
|
clean=args.clean,
|
2026-09-14 15:15:36 +05:00
|
|
|
extra_args=extra_args,
|
|
|
|
|
)
|
2026-09-11 16:07:36 +05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> None:
|
|
|
|
|
ARGS = parse_args()
|
|
|
|
|
|
|
|
|
|
ensure_script_dir()
|
|
|
|
|
|
|
|
|
|
explicit_generator = ARGS.generator is not None
|
|
|
|
|
generator = resolve_generator(ARGS.generator)
|
|
|
|
|
|
|
|
|
|
vs_cfg_vars = vs_cfg(generator, REPO_ROOT)
|
|
|
|
|
|
|
|
|
|
deps_cache = BuildDepsCache.parse(vs_cfg_vars.build_deps_cache_path)
|
|
|
|
|
|
|
|
|
|
Deps.init_values(vs_cfg_vars, deps_cache)
|
|
|
|
|
|
|
|
|
|
pythonhome = Deps.values()["pythonhome"]
|
|
|
|
|
assert pythonhome is not None
|
|
|
|
|
python_executable = f"{pythonhome}\\python.exe"
|
|
|
|
|
py_ver_major_minor = run(
|
|
|
|
|
python_executable, "-c", "import sys; print(f'{sys.version_info[0]}{sys.version_info[1]}')"
|
|
|
|
|
).strip()
|
|
|
|
|
python_include_dir = f"{pythonhome}\\include"
|
|
|
|
|
python_library = f"{pythonhome}\\libs\\python{py_ver_major_minor}.lib"
|
|
|
|
|
|
2026-09-14 15:15:36 +05:00
|
|
|
ADD_COMMIT_SHA = OFF_ON[ARGS.add_commit_sha]
|
2026-09-11 16:07:36 +05:00
|
|
|
VERSION_OVERRIDE = ADD_COMMIT_SHA
|
|
|
|
|
|
|
|
|
|
qt_dir = get_var(deps_cache, "QT_DIR") or get_var(deps_cache, "QT6_INSTALL_DIR")
|
|
|
|
|
qt_host_path = get_var(deps_cache, "QT_HOST_PATH") or get_var(deps_cache, "QT6_HOST_INSTALL_DIR")
|
|
|
|
|
|
|
|
|
|
cmake_install_prefix = REPO_ROOT / f"_installed-{vs_cfg_vars.gen_shorthand}"
|
|
|
|
|
|
|
|
|
|
logger.info("")
|
|
|
|
|
logger.info(colorize("Script configuration:", C.PURPLE))
|
|
|
|
|
logger.info(f" Generator = {generator}")
|
|
|
|
|
logger.info(f" Architecture = {vs_cfg_vars.vs_platform}")
|
|
|
|
|
logger.info(f" Toolset = {vs_cfg_vars.vs_toolset_override}")
|
|
|
|
|
logger.info(f" Arguments = {ARGS.extra_args}")
|
|
|
|
|
logger.info("")
|
|
|
|
|
|
|
|
|
|
# Some deps are a bit less trivial to get, so we calculate them outside `Deps`.
|
|
|
|
|
extra_vars: dict[str, object] = {
|
|
|
|
|
"PYTHON_INCLUDE_DIR": python_include_dir,
|
|
|
|
|
"PYTHON_LIBRARY": python_library,
|
|
|
|
|
"PYTHON_EXECUTABLE": python_executable,
|
|
|
|
|
"QT_DIR": qt_dir,
|
|
|
|
|
"QT_HOST_PATH": qt_host_path,
|
|
|
|
|
"CMAKE_INSTALL_PREFIX": cmake_install_prefix,
|
|
|
|
|
}
|
|
|
|
|
dep_vars = ((dep.env_var, Deps.values()[name]) for name, dep in Deps.DEPS.items())
|
|
|
|
|
logger.info(colorize(f"Dependency Environment Variables for {PROJECT_NAME}:", C.PURPLE))
|
|
|
|
|
for env_var, value in chain(dep_vars, extra_vars.items()):
|
|
|
|
|
logger.info(f" {env_var:<23} = {value}")
|
|
|
|
|
logger.info("")
|
|
|
|
|
|
|
|
|
|
build_dir = REPO_ROOT / vs_cfg_vars.build_dir
|
2026-09-15 10:04:55 +05:00
|
|
|
if ARGS.clean and build_dir.exists():
|
|
|
|
|
logger.info(f"Removing existing build directory: {build_dir}")
|
|
|
|
|
shutil.rmtree(build_dir)
|
2026-09-11 16:07:36 +05:00
|
|
|
build_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
cmakelists_dir = REPO_ROOT / "cmake"
|
|
|
|
|
if explicit_generator:
|
|
|
|
|
cmake_cache_path = build_dir / "CMakeCache.txt"
|
|
|
|
|
if cmake_cache_path.exists():
|
|
|
|
|
cmake_cache_path.unlink()
|
|
|
|
|
logger.info(f'"Running CMake for {PROJECT_NAME}."')
|
|
|
|
|
|
|
|
|
|
cmake_prefix_path_parts = Deps.cmake_prefix_paths()
|
|
|
|
|
if qt_dir:
|
2026-09-14 16:38:12 +05:00
|
|
|
cmake_prefix_path_parts.append(Path(qt_dir))
|
2026-09-11 16:07:36 +05:00
|
|
|
cmake_prefix_path = ";".join(str(part) for part in cmake_prefix_path_parts)
|
|
|
|
|
|
2026-09-14 15:15:36 +05:00
|
|
|
if ARGS.use_ninja:
|
2026-09-11 16:07:36 +05:00
|
|
|
cmake_generator = "Ninja"
|
|
|
|
|
arch_option = ()
|
|
|
|
|
else:
|
|
|
|
|
cmake_generator = vs_cfg_vars.generator.name
|
|
|
|
|
arch_option = ("-A", vs_cfg_vars.vs_platform)
|
|
|
|
|
|
|
|
|
|
cmake_args = [
|
|
|
|
|
str(cmakelists_dir),
|
|
|
|
|
"-G",
|
|
|
|
|
cmake_generator,
|
|
|
|
|
*arch_option,
|
|
|
|
|
f"-DCMAKE_INSTALL_PREFIX={cmake_install_prefix}",
|
|
|
|
|
"-DWITH_ROCKSDB=ON",
|
|
|
|
|
"-DWITH_ZSTD=ON",
|
|
|
|
|
f"-DCMAKE_PREFIX_PATH={cmake_prefix_path}",
|
|
|
|
|
f"-DADD_COMMIT_SHA={ADD_COMMIT_SHA}",
|
|
|
|
|
f"-DVERSION_OVERRIDE={VERSION_OVERRIDE}",
|
2026-09-11 18:47:42 +05:00
|
|
|
f"-DPYTHON_EXECUTABLE={python_executable}",
|
|
|
|
|
f"-DPYTHON_INCLUDE_DIR={python_include_dir}",
|
|
|
|
|
f"-DPYTHON_LIBRARY={python_library}",
|
2026-09-11 16:07:36 +05:00
|
|
|
]
|
|
|
|
|
if qt_dir:
|
|
|
|
|
cmake_args.append(f"-DQT_DIR={qt_dir}")
|
|
|
|
|
if qt_host_path:
|
|
|
|
|
cmake_args.append(f"-DQT_HOST_PATH={qt_host_path}")
|
|
|
|
|
if Deps.values()["manifold"]:
|
|
|
|
|
cmake_args.append("-DWITH_MANIFOLD=ON")
|
2026-09-14 15:32:16 +05:00
|
|
|
cmake_args += Deps.cmake_args()
|
2026-09-11 16:07:36 +05:00
|
|
|
cmake_args += ARGS.extra_args
|
|
|
|
|
|
|
|
|
|
run_streamed(
|
|
|
|
|
"cmake",
|
|
|
|
|
*cmake_args,
|
|
|
|
|
cwd=build_dir,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
logger.info("")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|