Add install-/build-ifcopenshell.py, deprecate corresponding .bat files

Mainly drop-in replacement for `build-ifcopenshell.bat` with `--help`, kw args and args validation, but there's a small caveat.

Previously it was possible to pass args to the underlying build tool as simple positionals - e.g. `build-ifcopenshell vs2022-x64 Release /p:Foo=bar`.
This behaviour is disabled now, because it doesn't allow validating provided args - it's impossible to tell whether `--config Release` is meant to be passed to msbuild or was meant as `--build-cfg Release` for `build-ifcopenshell`.

But it's still possible to pass args to msbuild by using `--` - `python build-ifcopenshell.py vs2022-x64 Release -- /p:Foo=bar`

`install-ifcopenshell.py` is now just a small wrapper passing `--target INSTALL` arg.
This commit is contained in:
Andrej730
2026-09-11 15:20:12 +05:00
parent 322de0b56b
commit 51a86cdd86
9 changed files with 307 additions and 29 deletions
+1 -1
View File
@@ -236,7 +236,7 @@ def build() -> None:
]
)
restore_env(*OLD_ADD_COMMIT_SHA)
run([str(REPO_WIN / "install-ifcopenshell.bat"), build_generator(), "Release"])
run([sys.executable, str(REPO_WIN / "install-ifcopenshell.py"), build_generator(), "Release"])
def archive_executables() -> None:
+8 -19
View File
@@ -28,7 +28,6 @@ import os
import shutil
import sys
from datetime import datetime
from pathlib import Path
from typing import NamedTuple
from common import (
@@ -43,7 +42,9 @@ from common import (
BuildDepsCache,
BuildType,
C,
HelpStrings,
colorize,
ensure_script_dir,
is_on_off,
logger,
require_command,
@@ -144,19 +145,13 @@ def parse_args() -> Args:
"generator",
nargs="?",
default=None,
help=(
"CMake generator to use. Accepts 3 forms: "
"(1) omitted - deduced from the active Visual Studio environment; "
"(2) shorthand, e.g. 'vs2022', 'vs2022-x64', 'vs2019-x86-v141' - optionally provide platform/toolset "
"using the suffix; "
"(3) full CMake generator name, e.g. 'Visual Studio 17 2022'."
),
help=HelpStrings.generator("deduced from the active Visual Studio environment"),
)
parser.add_argument(
"--generator",
dest="generator_flag",
default=None,
help="Alternative way to specify the generator, instead of the positional argument. See above for accepted forms.",
help=HelpStrings.GENERATOR_FLAG,
)
# SUPPRESS avoids a misleading "(default: None)" in `--help`,
# though then arg might not be set and we use `getattr` to get it.
@@ -165,14 +160,14 @@ def parse_args() -> Args:
nargs="?",
default=argparse.SUPPRESS,
choices=BUILD_CFGS,
help=f"Build configuration type. (default: {BUILD_CFG_DEFAULT})",
help=HelpStrings.BUILD_CFG,
)
parser.add_argument(
"--build-cfg",
dest="build_cfg_flag",
default=BUILD_CFG_DEFAULT,
choices=BUILD_CFGS,
help="Alternative way to specify the build configuration type, instead of the positional argument.",
help=HelpStrings.BUILD_CFG_FLAG,
)
parser.add_argument(
"build_type",
@@ -208,11 +203,7 @@ def parse_args() -> Args:
dest="num_build_procs",
type=int,
default=argparse.SUPPRESS,
help=(
"How many build processes may be run in parallel. "
"Also can be specified by using IFCOS_NUM_BUILD_PROCS env variable. "
"(default: NUMBER_OF_PROCESSORS)"
),
help=HelpStrings.NUM_BUILD_PROCS,
)
parser.add_argument(
"--install-python",
@@ -274,9 +265,7 @@ def main() -> None:
logger.info(f"This script fetches and builds all {PROJECT_NAME} dependencies\n")
if Path.cwd() != SCRIPT_DIR:
logger.error(f"This script must be run from '{SCRIPT_DIR}'.")
sys.exit(1)
ensure_script_dir()
# Make sure vcvarsall.bat is called and dev env set is up.
get_vs_var("VSINSTALLDIR")
+5
View File
@@ -58,6 +58,11 @@ if not defined IFCOS_NUM_BUILD_PROCS set IFCOS_NUM_BUILD_PROCS=%NUMBER_OF_PROCES
call cecho.cmd 0 13 "* IFCOS_NUM_BUILD_PROCS`t= %IFCOS_NUM_BUILD_PROCS%"
echo.
call cecho.cmd 0 12 "WARNING: build-ifcopenshell.bat is deprecated since 11 Sep 2026 and will be removed very shortly."
call cecho.cmd 0 12 "Use `python build-ifcopenshell.py` instead. It's intended to be a drop-in replacement, so exactly the same args apply,"
call cecho.cmd 0 12 "except MSBuild args now need to be passed after `"--`", e.g. `python build-ifcopenshell.py vs2022-x64 -- /p:Foo=bar`."
echo.
call cecho.cmd 0 13 "Building %VS_PLATFORM% %BUILD_CFG% %PROJECT_NAME%"
set MSBUILD_MULTIPROC=/m /p:CL_MPCount=%IFCOS_NUM_BUILD_PROCS% /p:UseMultiToolTask=true /p:EnforceProcessCountAcrossBuilds=true
cmake --build ..\%BUILD_DIR% -- /nologo %MSBUILD_MULTIPROC% /p:Platform=%VS_PLATFORM% /p:Configuration=%BUILD_CFG% ^
+196
View File
@@ -0,0 +1,196 @@
# /// 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 multiprocessing
import os
import sys
from typing import NamedTuple, NoReturn
from common import (
BUILD_CFG_DEFAULT,
BUILD_CFGS,
PROJECT_NAME,
REPO_ROOT,
BuildCfg,
C,
HelpStrings,
colorize,
ensure_script_dir,
find_cached_gen_shorthand,
logger,
run_streamed,
)
from vs_cfg import vs_cfg
class Args(NamedTuple):
generator: str | None
build_cfg: BuildCfg
num_build_procs: int
target: str | None
extra_args: list[str]
class ArgumentParser(argparse.ArgumentParser):
def error(self, message: str) -> NoReturn:
# TODO: this hint can be removed later, it's just for anyone transitioning from build-ifcopenshell.bat,
# which allowed providing additional args as positionals. We disallow it here (they can be passed after
# '--') to ensure we can validate the provided args.
if message.startswith("unrecognized arguments"):
message += (
"\nHint: put args meant for the underlying build tool (e.g. MSBuild) after '--', "
"e.g. `build-ifcopenshell.py -- /p:Foo=bar`."
)
super().error(message)
def parse_args() -> Args:
parser = ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
epilog="Arguments after '--' are passed through as-is to the underlying build tool (e.g. MSBuild).",
)
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,
)
parser.add_argument(
"build_cfg",
nargs="?",
default=argparse.SUPPRESS,
choices=BUILD_CFGS,
help=HelpStrings.BUILD_CFG,
)
parser.add_argument(
"--build-cfg",
dest="build_cfg_flag",
default=BUILD_CFG_DEFAULT,
choices=BUILD_CFGS,
help=HelpStrings.BUILD_CFG_FLAG,
)
parser.add_argument(
"--num-build-procs",
dest="num_build_procs",
type=int,
default=argparse.SUPPRESS,
help=HelpStrings.NUM_BUILD_PROCS,
)
parser.add_argument(
"--target",
dest="target",
default=None,
help=(
"cmake --build target, passed as-is. E.g. 'INSTALL' to also install after building. "
"By default no target is passed, which builds the whole solution."
),
)
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
build_cfg = getattr(args, "build_cfg", None) or args.build_cfg_flag
num_build_procs = getattr(args, "num_build_procs", None) or int(
os.getenv("IFCOS_NUM_BUILD_PROCS") or multiprocessing.cpu_count()
)
return Args(
generator=generator,
build_cfg=build_cfg,
num_build_procs=num_build_procs,
target=args.target,
extra_args=extra_args,
)
def main() -> None:
ARGS = parse_args()
ensure_script_dir()
generator = ARGS.generator
if generator is None:
generator = find_cached_gen_shorthand()
if generator is None:
logger.error(
"BuildDepsCache file does not exist and/or GEN_SHORTHAND missing from it. Run build-deps.py to create it."
)
sys.exit(1)
vs_cfg_vars = vs_cfg(generator, REPO_ROOT)
logger.info("")
logger.info(colorize(f"* IFCOS_NUM_BUILD_PROCS\t= {ARGS.num_build_procs}", C.PURPLE))
logger.info("")
if ARGS.target:
target_suffix = f" (target: {ARGS.target})"
target_args = ("--target", ARGS.target)
else:
target_suffix = ""
target_args = ()
logger.info(
colorize(f"Building {vs_cfg_vars.vs_platform} {ARGS.build_cfg} {PROJECT_NAME}{target_suffix}", C.PURPLE)
)
MSBUILD_MULTIPROC = (
"/m",
f"/p:CL_MPCount={ARGS.num_build_procs}",
"/p:UseMultiToolTask=true",
"/p:EnforceProcessCountAcrossBuilds=true",
)
run_streamed(
"cmake",
"--build",
str(REPO_ROOT / vs_cfg_vars.build_dir),
*target_args,
"--",
"/nologo",
*MSBUILD_MULTIPROC,
f"/p:Platform={vs_cfg_vars.vs_platform}",
f"/p:Configuration={ARGS.build_cfg}",
*ARGS.extra_args,
)
logger.info("")
logger.info(colorize(f"{vs_cfg_vars.vs_platform} {ARGS.build_cfg} {PROJECT_NAME} build finished.", C.GREEN))
if __name__ == "__main__":
main()
+49
View File
@@ -111,10 +111,41 @@ BUILD_TYPES = get_args(BuildType)
BUILD_TYPE_DEFAULT: BuildType = "Build"
class HelpStrings:
NUM_BUILD_PROCS = (
"How many build processes may be run in parallel. "
"Also can be specified by using IFCOS_NUM_BUILD_PROCS env variable. "
"(default: NUMBER_OF_PROCESSORS)"
)
GENERATOR_FLAG = (
"Alternative way to specify the generator, instead of the positional argument. See above for accepted forms."
)
BUILD_CFG = f"Build configuration type. (default: {BUILD_CFG_DEFAULT})"
BUILD_CFG_FLAG = "Alternative way to specify the build configuration type, instead of the positional argument."
@staticmethod
def generator(omitted_behavior: str) -> str:
return (
"CMake generator to use. Accepts 3 forms: "
f"(1) omitted - {omitted_behavior}; "
"(2) shorthand, e.g. 'vs2022', 'vs2022-x64', 'vs2019-x86-v141' - optionally provide platform/toolset "
"using the suffix; "
"(3) full CMake generator name, e.g. 'Visual Studio 17 2022'."
)
def debug_or_release(build_cfg: BuildCfg) -> DebugOrRelease:
return "Debug" if build_cfg == "Debug" else "Release"
def ensure_script_dir() -> None:
if Path.cwd() != SCRIPT_DIR:
logger.error(f"This script must be run from '{SCRIPT_DIR}'.")
sys.exit(1)
def require_command(command: str) -> str:
path = shutil.which(command)
if not path:
@@ -149,3 +180,21 @@ class BuildDepsCache:
def add_entry(self, key: str, value: str) -> None:
with self.path.open("a") as f:
f.write(f"{key}={value}\n")
@staticmethod
def parse(path: Path) -> dict[str, str]:
entries: dict[str, str] = {}
for line in path.read_text().splitlines():
key, _, value = line.partition("=")
entries[key] = value
return entries
def find_cached_gen_shorthand() -> str | None:
"""Read GEN_SHORTHAND from the most recently modified BuildDepsCache-*.txt, if any."""
cache_files = sorted(SCRIPT_DIR.glob("BuildDepsCache-*.txt"), key=lambda p: p.stat().st_mtime, reverse=True)
if not cache_files:
return None
cache_file = cache_files[0]
logger.info(f"Found {cache_file.name}, reading GEN_SHORTHAND from it.")
return BuildDepsCache.parse(cache_file).get("GEN_SHORTHAND")
+5
View File
@@ -57,6 +57,11 @@ IF "%IFCOS_NUM_BUILD_PROCS%"=="" set IFCOS_NUM_BUILD_PROCS=%NUMBER_OF_PROCESSORS
call cecho.cmd 0 13 "* IFCOS_NUM_BUILD_PROCS`t= %IFCOS_NUM_BUILD_PROCS%"
echo.
call cecho.cmd 0 12 "WARNING: install-ifcopenshell.bat is deprecated since 11 Sep 2026 and will be removed very shortly."
call cecho.cmd 0 12 "Use `python install-ifcopenshell.py` instead. It's intended to be a drop-in replacement, so exactly the same args apply,"
call cecho.cmd 0 12 "except MSBuild args now need to be passed after `"--`", e.g. `python install-ifcopenshell.py vs2022-x64 -- /p:Foo=bar`."
echo.
call cecho.cmd 0 13 "Installing %VS_PLATFORM% %BUILD_CFG% %PROJECT_NAME%"
set MSBUILD_MULTIPROC=/m /p:CL_MPCount=%IFCOS_NUM_BUILD_PROCS% /p:UseMultiToolTask=true /p:EnforceProcessCountAcrossBuilds=true
cmake --build ..\%BUILD_DIR% --target INSTALL -- /nologo %MSBUILD_MULTIPROC% /p:Platform=%VS_PLATFORM% ^
+34
View File
@@ -0,0 +1,34 @@
# /// 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 sys
from common import SCRIPT_DIR, run_streamed
def main() -> None:
run_streamed(sys.executable, str(SCRIPT_DIR / "build-ifcopenshell.py"), "--target", "INSTALL", *sys.argv[1:])
if __name__ == "__main__":
main()
+7 -7
View File
@@ -39,11 +39,11 @@ After the dependencies are built, execute `run-cmake.bat`. The batch file expect
Note that building IfcOpenShell as 64-bit is recommended as many of real life IFC files has been observed to take easily more than 2 GBs of RAM while converting.
After this, one can build the project using the `IfcOpenShell.sln` file in the build folder. Build the `INSTALL` project
if wanted. Convenience batch files `build-ifcopenshell.bat` and `install-ifcopenshell.bat` can also be used. The batch
files expect `%1` and `%2` in same fashion as above and possible extra parameters are passed for the `MSBuild` call.
`run-cmake.bat`, `build-ifcopenshell.bat`, and `install-ifcopenshell.bat` can also be directly invoked from Filer Explorer
or regular Command Prompt if BuildDepsCache file exists (the last modified version is used). Running the scripts without extra
parameters reads the build options from an existing CMakeCache.txt.
if wanted. Convenience scripts `python build-ifcopenshell.py` and `python install-ifcopenshell.py` can also be used. The
scripts expect the generator and build configuration type in the same fashion as `build-deps.py` and possible extra
parameters are passed for the `MSBuild` call after `--`. `run-cmake.bat` can also be directly invoked from File
Explorer or regular Command Prompt if BuildDepsCache file exists (the last modified version is used). Running the
scripts without extra parameters reads the build options from an existing CMakeCache.txt.
The project will be installed to `_installed-vs<VERSION>-<ARCHITECTURE>\` folder in the project's root folder and the
required IfcOpenShell-Python parts are deployed to the `<PYTHONHOME>\Lib\site-packages\` folder. The 3ds Max plug-in,
@@ -80,9 +80,9 @@ Directory Structure
| build-all.cmd - Runs all of the build scripts for IFCOS and it dependencies in a row without pauses
| build-deps.py - Fetches and builds all needed dependencies for IFCOS using MSVC
| BuildDepsCache-<ARCH>.txt - Cache file created by build-deps.py
| build-ifcopenshell.bat - Builds IFCOS using MSVC
| build-ifcopenshell.py - Builds IFCOS using MSVC
| build-type-cfg.cmd - Utility file used by the build scripts
| install-ifcopenshell.bat - Installs/deploys IFCOS using MSVC.
| install-ifcopenshell.py - Installs/deploys IFCOS using MSVC.
| readme.md - This file
| run-cmake.bat - Sets environment variables for the dependencies and runs CMake for IFCOS using MSVC
| vs-cfg.cmd - Utility file used by the build scripts