2023-05-19 10:32:20 +02:00
#!/usr/bin/python
###############################################################################
# #
# 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/>. #
# #
###############################################################################
2025-07-25 17:31:08 +05:00
"""
Example usage:
# Build all targets by default.
python build-all.py
# Build just the provided targets.
python build - all . py IfcParse IfcOpenShell - Python
Available arguments :
` ` - py - 313 ` ` - build for specific Python version
( building for all supported Python version by default ) .
` ` - wasm ` ` - compile for wasm
2025-10-10 13:52:52 +05:00
` ` - without - xxx ` ` - do not build dependency ` ` xxx ` ` ( e . g . ` ` - - without - swig ` ` )
2025-09-24 11:43:45 +05:00
` ` - mac - cross - compile - intel ` ` - cross compile for Intel Mac on Apple Silicon host
2025-07-25 17:31:08 +05:00
` ` - shared ` ` - build shared libraries . By default will build static .
2025-07-28 17:03:18 +05:00
` ` - diskcleanup ` ` - clean up build directories after finishing building dependencies
2025-07-30 19:35:39 +05:00
` ` - v ` ` - enable verbose logs
2025-07-25 17:31:08 +05:00
Used environment variables :
- ` ` CXXFLAGS ` ` , ` ` CPPFLAGS ` ` , ` ` CFLAGS ` ` , ` ` LDFLAGS ` `
- ` ` BUILD_DIR ` ` - build directory . By default will use " build " folder in IfcOpenShell repository .
- ` ` DEPS_DIR ` ` - dependencies directory . By default will create automatic folder in build directory .
- ` ` BUILD_CFG ` ` - build configuration , ' RelWithDebInfo ' by default .
2025-08-27 10:22:52 +02:00
- ` ` USE_CURRENT_PYTHON_VERSION ` ` - use current python config instead of compile from source
- ` ` IFCOS_NUM_BUILD_PROCS ` ` - number of concurrent processes defaults to available cores + 1
2025-09-18 09:44:36 +05:00
- ` ` NO_CLEAN ` ` - do not clean ` ifcopenshell ` build directories but continue working on current build
( installed dependencies are never cleared ) .
By default option is disabled , to enable pass any value from ` 1 ` , ` on ` , ` true ` .
- ` ` IFCOS_SCHEMAS ` ` - schemas to be built ; defaults to cmake default ( IFC2X3 ; IFC4 ; IFC4X3_ADD2 ) - to be supplied as ` 2 x3 ; 4 `
- ` ` USE_OCCT ` ` - whether to use official Open CASCADE instead of Community Edition
( ` true ` by default , any other value is considered ` false ` )
2025-10-16 11:03:29 +05:00
- ` ` WASM_PYTHON_PATH ` ` - path to WASM Python installation ,
2025-10-23 12:21:11 +05:00
used to deduce ` PYVERSION ` ( e . g . ' 3.13.2 ' ) , ` PYTHONINCLUDE ` ,
2025-10-16 11:03:29 +05:00
` SIDE_MODULE_CFLAGS ` , ` SIDE_MODULE_LDFLAGS ` .
Allows to build wasm without pyodide build environment , which can be useful for debugging build issues .
Example value : ' pyodide/cpython/installs/python-3.13.2 '
- ` ` WASM_TOOLCHAIN_FILE ` ` - path to emscripten toolchain file from pyodide ( ' Emscripten.cmake ' )
needed only if ` ` WASM_PYTHON_PATH ` ` is provided .
2025-09-24 13:26:32 +05:00
- ` ` ADD_COMMIT_SHA ` ` - if defined with any non - empty value then
` ADD_COMMIT_SHA ` and ` VERSION_OVERRIDE ` will be set to ` ON ` while configuring IfcOpenShell
2025-07-25 17:31:08 +05:00
2023-05-19 10:32:20 +02:00
# This script builds IfcOpenShell and its dependencies #
# #
# Prerequisites for this script to function correctly: #
# * cmake * git * bzip2 * tar * c(++) compilers * autoconf #
# #
# if building with USE_OCCT additionally: #
# * glx.h #
# #
# if building with OCCT 7.4.0 additionally: #
# * libfontconfig1-dev #
# #
# if building with -shared #
# * libgl1-mesa-dev libxext-dev libxmu-dev libxmu-headers libxi-dev #
# #
# for python37 to install correctly additionally: #
# * libffi(-dev[el]) #
2025-09-17 12:15:34 +05:00
# for Python build we also needs ssl #
# (since we do `pip install numpy` at the end) #
# * libssl-dev #
2023-05-19 10:32:20 +02:00
# #
# on debian 7.8 these can be obtained with: #
# $ apt-get install git gcc g++ autoconf bison bzip2 cmake #
# mesa-common-dev libffi-dev libfontconfig1-dev #
2025-09-22 12:51:55 +05:00
# libssl-dev xz #
2023-05-19 10:32:20 +02:00
# #
# on ubuntu 14.04: #
# $ apt-get install git gcc g++ autoconf bison make cmake #
# mesa-common-dev libffi-dev libfontconfig1-dev #
2025-09-22 12:51:55 +05:00
# libssl-dev xz-utils #
2023-05-19 10:32:20 +02:00
# #
# on OS X El Capitan with homebrew: #
# $ brew install git bison autoconf automake libffi cmake #
# #
2024-10-26 17:11:23 +02:00
# on RHEL-related distros: #
# $ yum install git gcc gcc-c++ autoconf bison make cmake #
# mesa-libGL-devel libffi-devel fontconfig-devel bzip2 #
2025-09-22 12:51:55 +05:00
# automake patch byacc xz #
2025-08-27 10:22:52 +02:00
"""
2023-05-19 10:32:20 +02:00
import logging
import os
2024-05-12 20:32:09 +00:00
import re
2023-05-19 10:32:20 +02:00
import sys
2024-07-08 08:48:53 +02:00
import glob
2023-05-19 10:32:20 +02:00
import subprocess as sp
import shutil
import tarfile
import multiprocessing
import platform
2025-09-17 11:22:06 +05:00
import threading
2023-05-19 10:32:20 +02:00
import sysconfig
2025-09-17 11:22:06 +05:00
from datetime import datetime
2023-05-19 10:32:20 +02:00
# @todo temporary for expired mpfr.org certificate on 2023-04-08
import ssl
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
ssl._create_default_https_context = ssl._create_unverified_context
2025-06-13 18:15:23 +02:00
import time
2023-05-19 10:32:20 +02:00
from urllib.request import urlretrieve
2025-07-25 17:31:08 +05:00
from collections.abc import Generator, Sequence
from pathlib import Path
2025-07-28 11:45:12 +05:00
2025-07-28 13:17:19 +02:00
try:
from typing import Union, Literal
except:
# python 3.6 compatibility for rocky 8
from typing import Union
from typing_extensions import Literal
2023-05-19 10:32:20 +02:00
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
ch = logging.StreamHandler()
logger.addHandler(ch)
PROJECT_NAME = " IfcOpenShell "
USE_CURRENT_PYTHON_VERSION = os.getenv( " USE_CURRENT_PYTHON_VERSION " )
ADD_COMMIT_SHA = os.getenv( " ADD_COMMIT_SHA " )
2025-10-08 15:59:42 +05:00
PYTHON_VERSIONS = [ " 3.9.11 " , " 3.10.3 " , " 3.11.8 " , " 3.12.1 " , " 3.13.6 " , " 3.14.0 " ]
2025-09-23 16:16:23 +05:00
JSON_VERSION = " 3.11.3 "
2023-05-19 10:32:20 +02:00
OCE_VERSION = " 0.18.3 "
2024-09-20 20:49:22 +02:00
OCCT_VERSION = " 7.8.1 "
2024-11-21 15:25:47 +01:00
BOOST_VERSION = " 1.86.0 "
2025-09-23 16:50:50 +05:00
EIGEN_VERSION = " 3.4.0 "
2023-05-19 10:32:20 +02:00
PCRE_VERSION = " 8.41 "
2025-09-16 17:05:34 +05:00
PCRE2_VERSION = " 10.32 "
2025-06-16 21:17:00 +02:00
LIBXML2_VERSION = " 2.13.8 "
2025-08-29 15:35:25 +05:00
SWIG_VERSION = " 4.1.0 "
2023-05-19 10:32:20 +02:00
OPENCOLLADA_VERSION = " v1.6.68 "
2025-09-16 10:59:59 +05:00
HDF5_VERSION = " 1.13.1 "
2023-05-19 10:32:20 +02:00
2025-10-21 12:18:48 +02:00
GMP_VERSION = " 6.3.0 "
2025-07-25 17:19:16 +05:00
MPFR_VERSION = " 3.1.6 " # latest is 4.1.0
2025-09-23 13:59:46 +05:00
CGAL_VERSION = " v5.6.3 "
2023-05-19 10:32:20 +02:00
USD_VERSION = " 23.05 "
TBB_VERSION = " 2021.9.0 "
2025-08-27 10:22:52 +02:00
ROCKSDB_VERSION = " 9.11.2 "
2025-09-10 10:05:43 +02:00
ZSTD_VERSION = " 1.5.7 "
2023-05-19 10:32:20 +02:00
# binaries
cp = " cp "
bash = " bash "
git = " git "
bunzip2 = " bunzip2 "
tar = " tar "
cc = " cc "
cplusplus = " c++ "
autoconf = " autoconf "
automake = " automake "
make = " make "
date = " date "
curl = " curl "
wget = " wget "
strip = " strip "
2025-09-22 12:51:55 +05:00
xz = " xz " # Used implicitly for `tar -xf *.tar.xz`.
2025-09-26 19:34:32 +05:00
brew = " brew "
2023-05-19 10:32:20 +02:00
explicit_targets = [s for s in sys.argv[1:] if not s.startswith( " - " )]
2025-07-25 17:31:08 +05:00
""" Targets provided by CLI . """
2025-07-25 17:19:16 +05:00
flags = set(s.lstrip( " - " ) for s in sys.argv[1:] if s.startswith( " - " ))
2025-07-25 17:31:08 +05:00
""" CLI flags . """
2023-05-19 10:32:20 +02:00
# Helper function for coloured printing
2025-07-25 17:19:16 +05:00
NO_COLOR = (
" \033 [0m " # <ref>http://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux</ref>
)
2023-05-19 10:32:20 +02:00
BLACK_ON_WHITE = " \033 [0;30;107m "
RED = " \033 [31m "
GREEN = " \033 [32m "
YELLOW = " \033 [33m "
MAGENTA = " \033 [35m "
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
def cecho(message, color=NO_COLOR):
""" Logs message ` message ` in color ` color ` . """
logger.info(f " {color} {message} \033 [0m " )
2025-07-25 17:19:16 +05:00
2025-09-24 11:43:45 +05:00
# Flags.
2025-10-10 17:48:22 +05:00
APPLE = platform.system() == " Darwin "
2025-09-24 11:43:45 +05:00
MAC_CROSS_COMPILE_INTEL = " mac-cross-compile-intel " in flags
assert platform.system() == " Darwin " or not MAC_CROSS_COMPILE_INTEL
2025-10-10 20:45:47 +05:00
2025-09-24 11:43:45 +05:00
WASM = " wasm " in flags
2025-10-16 11:03:29 +05:00
""" Build WASM outside pyodide build environment . """
2025-10-27 18:31:54 +05:00
WASM_CMAKE_IS_USING_INIT_VARS = False
2025-10-10 20:45:47 +05:00
if WASM:
2025-10-28 12:53:59 +05:00
def get_pyodide_config_var(var_name: str) -> str:
output = sp.check_output([ " pyodide " , " config " , " get " , var_name], encoding= " utf-8 " ).strip()
return output
if " PYODIDE_ROOT " not in os.environ:
cecho( " WARNING. Couldn ' t find ' PYODIDE_ROOT ' in environment variables. " , YELLOW)
cecho( " Assuming building wasm outside pyodide build environment and resetting necessary variables. " , YELLOW)
os.environ[ " SIDE_MODULE_CFLAGS " ] = get_pyodide_config_var( " cflags " )
os.environ[ " SIDE_MODULE_LDFLAGS " ] = get_pyodide_config_var( " ldflags " )
# Override cmake toolchain for all `emcmake` calls,
# needed for shared libraries (resulting .so wrapper)
# and to ensure compilation is pyodide compatible (e.g. `-fwasm-exceptions` is used in compilation flags).
os.environ[ " CMAKE_TOOLCHAIN_FILE " ] = get_pyodide_config_var( " cmake_toolchain_file " )
2025-10-10 20:45:47 +05:00
required_vars = (
" SIDE_MODULE_CFLAGS " ,
" SIDE_MODULE_LDFLAGS " ,
2025-10-28 12:53:59 +05:00
" CMAKE_TOOLCHAIN_FILE " ,
2025-10-10 20:45:47 +05:00
)
missing_vars = [v for v in required_vars if v not in os.environ]
assert not missing_vars, f " Some variables required for WASM compilation are missing: { ' , ' .join(missing_vars)} "
2025-09-24 11:43:45 +05:00
2025-10-27 18:31:54 +05:00
def get_pyodide_build_version() -> " tuple[int, ...] " :
pyodide_build_suffix = " pyodide-build version: "
output = sp.check_output([ " pyodide " , " --version " ], encoding= " utf-8 " ).strip()
assert pyodide_build_suffix in output, output
version_line = next(l for l in output.splitlines() if l.startswith(pyodide_build_suffix))
version = version_line.partition( " : " )[2].strip()
return tuple(map(int, version.split( " . " )))
# Pyodide still in transition from `FLAGS` to `FLAGS_INIT`.
# `FLAGS_INIT` allow us to provide flags using environment variables
# and providing `FLAGS` directly would break pyodide toolchain.
WASM_CMAKE_IS_USING_INIT_VARS = get_pyodide_build_version() >= (0, 30, 8)
2025-10-27 18:18:49 +05:00
# pyodide provide empty `CXXFLAGS`, leading to issues using C++ files compiled with `-fexceptions`
# which is used by OCCT.
# https://github.com/pyodide/pyodide-build/issues/251
side_module_cxx_flags = os.environ.get( " SIDE_MODULE_CXXFLAGS " , " " )
if side_module_cxx_flags.strip():
print( " SIDE_MODULE_CXXFLAGS are already passed from pyodide build ( ' {side_module_cxx_flags} ' ). " )
print( " Maybe it ' s time to stop overriding them in the script? " )
os.environ[ " SIDE_MODULE_CXXFLAGS " ] = os.environ[ " SIDE_MODULE_CFLAGS " ]
2023-05-19 10:32:20 +02:00
# Set defaults for missing empty environment variables
USE_OCCT = os.environ.get( " USE_OCCT " , " true " ).lower() == " true "
TOOLSET = None
if platform.system() == " Darwin " :
# C++11 features used in OCCT 7+ need a more recent stdlib
2025-08-29 10:02:15 +02:00
# TOOLSET = " 10.9 " if USE_OCCT else " 10.6 "
2025-08-27 13:23:43 +02:00
# /Users/runner/work/IfcOpenShell/IfcOpenShell/build/Darwin/arm64/10.9/build/rocksdb/cache/clock_cache.cc:732:14: error: aligned allocation function of type ' void *(std::size_t, std::align_val_t) ' is only available on macOS 10.13 or newer
2025-08-28 17:06:12 +02:00
# /Users/runner/work/IfcOpenShell/IfcOpenShell/src/ifcparse/IfcFile.cpp:539:14: error: ' exists ' is unavailable: introduced in macOS 10.15
TOOLSET = " 10.15 "
2023-05-19 10:32:20 +02:00
IFCOS_NUM_BUILD_PROCS = os.getenv( " IFCOS_NUM_BUILD_PROCS " , multiprocessing.cpu_count() + 1)
2025-10-20 11:45:49 +05:00
SCRIPT_PATH = Path(__file__).parent
REPO_PATH = SCRIPT_PATH.parent
CMAKE_DIR = (REPO_PATH / " cmake " ).resolve().__str__()
2023-05-19 10:32:20 +02:00
2025-10-21 12:43:09 +05:00
BUILD_DIR = os.environ.get( " BUILD_DIR " , (REPO_PATH / " build " ).__str__())
2023-05-19 10:32:20 +02:00
2025-09-24 11:43:45 +05:00
if WASM:
arch = " wasm "
elif MAC_CROSS_COMPILE_INTEL:
arch = " x86_64 "
else:
arch = platform.machine()
2025-10-21 12:43:09 +05:00
DEFAULT_DEPS_DIR = Path(BUILD_DIR) / platform.system() / arch
2025-09-24 11:43:45 +05:00
2023-05-19 10:32:20 +02:00
if TOOLSET:
2025-07-25 17:31:08 +05:00
DEFAULT_DEPS_DIR = DEFAULT_DEPS_DIR / TOOLSET
DEFAULT_DEPS_DIR = os.path.realpath(DEFAULT_DEPS_DIR)
2023-05-19 10:32:20 +02:00
DEPS_DIR = os.getenv( " DEPS_DIR " , DEFAULT_DEPS_DIR)
if not os.path.exists(DEPS_DIR):
os.makedirs(DEPS_DIR)
BUILD_CFG = os.getenv( " BUILD_CFG " , " RelWithDebInfo " )
# Print build configuration information
2025-07-25 17:19:16 +05:00
cecho(
f """ This script fetches and builds { PROJECT_NAME } and its dependencies
""" ,
BLACK_ON_WHITE,
)
cecho(
""" Script configuration :
""" ,
GREEN,
)
2023-05-19 10:32:20 +02:00
cecho(f """ * USE_OCCT = { USE_OCCT } """ , MAGENTA)
if USE_OCCT:
cecho( " - Compiling against official Open Cascade " )
else:
cecho( " - Compiling against Open Cascade Community Edition " )
2025-10-21 12:43:09 +05:00
cecho(f " * Build Directory = {BUILD_DIR} " , MAGENTA)
2023-05-19 10:32:20 +02:00
cecho(f " * Dependency Directory = {DEPS_DIR} " , MAGENTA)
cecho(f " - The directory where {PROJECT_NAME} dependencies are installed. " )
cecho(f " * Build Config Type = {BUILD_CFG} " , MAGENTA)
2025-07-25 17:19:16 +05:00
cecho(
""" - The used build configuration type for the dependencies .
Defaults to RelWithDebInfo if not specified . """
)
2023-05-19 10:32:20 +02:00
if BUILD_CFG == " MinSizeRel " :
cecho( " WARNING: MinSizeRel build can suffer from a significant performance loss. " , RED)
cecho(f " * IFCOS_NUM_BUILD_PROCS = {IFCOS_NUM_BUILD_PROCS} " , MAGENTA)
2025-07-25 17:19:16 +05:00
cecho(
""" - How many compiler processes may be run in parallel .
"""
)
2025-10-10 20:45:58 +05:00
cecho(f " * IFCOS_SCHEMAS = ' { os.environ.get( ' IFCOS_SCHEMAS ' )} ' " , MAGENTA)
cecho(
""" - IFC Schemas to compile . If not provided , fallback to default provided in cmake .
"""
)
2023-05-19 10:32:20 +02:00
2025-07-28 11:45:12 +05:00
dependency_tree: " dict[str, tuple[str, ...]] " = {
2025-08-26 19:38:42 +02:00
" IfcParse " : ( " boost " , " libxml2 " , " hdf5 " , " rocksdb " ),
2025-09-23 14:59:06 +05:00
" IfcGeom " : ( " IfcParse " , " occ " , " json " , " cgal " , " eigen " , " OpenCOLLADA " ),
2025-07-25 17:19:16 +05:00
" IfcConvert " : ( " IfcGeom " ,),
" OpenCOLLADA " : ( " libxml2 " , " pcre " ),
" IfcGeomServer " : ( " IfcGeom " ,),
" IfcOpenShell-Python " : ( " python " , " swig " , " IfcGeom " ),
2025-09-16 17:05:34 +05:00
" swig " : ( " pcre2 " ,),
2025-07-25 17:19:16 +05:00
" boost " : (),
" libxml2 " : (),
" python " : (),
" occ " : ( " freetype " ,),
" pcre " : (),
2025-09-16 17:05:34 +05:00
" pcre2 " : (),
2025-07-25 17:19:16 +05:00
" json " : (),
" hdf5 " : (),
" cgal " : (),
" eigen " : (),
" freetype " : (),
2025-09-10 10:37:05 +02:00
" rocksdb " : ( " zstd " ,),
2025-09-10 10:05:43 +02:00
" zstd " : (),
2024-06-10 20:08:13 +02:00
# ' usd ' : ( ' boost ' , ' oneTBB ' )
2023-05-19 10:32:20 +02:00
}
2025-07-25 17:19:16 +05:00
2025-07-28 11:45:12 +05:00
def gather_dependencies(dep: str) -> " Generator[str] " :
2025-07-25 17:19:16 +05:00
yield dep
for d in dependency_tree[dep]:
2025-09-12 16:10:19 +02:00
if f " without- { d.lower()} " not in flags:
for x in gather_dependencies(d):
yield x
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
if " v " in flags:
logger.setLevel(logging.DEBUG)
2025-07-30 11:51:15 +05:00
formatter = logging.Formatter( " %(asctime)s - %(levelname)s - %(message)s " )
ch.setFormatter(formatter)
2023-05-19 10:32:20 +02:00
else:
logger.setLevel(logging.INFO)
2025-09-24 11:43:45 +05:00
if MAC_CROSS_COMPILE_INTEL:
MAC_CROSS_COMPILE_INTEL_ARGS = [ " -DCMAKE_OSX_ARCHITECTURES=x86_64 " ]
2025-09-25 08:07:14 +02:00
MAC_CROSS_COMPILE_INTEL_BJAM_ARGS = [ " architecture=x86 " ]
MAC_CROSS_COMPILE_INTEL_CXX = " clang++ -arch x86_64 "
MAC_CROSS_COMPILE_INTEL_CC = " clang -arch x86_64 "
2025-10-29 14:38:16 +05:00
MAC_CROSS_COMPILE_INTEL_AUTOCONF_HOST_ARGS = [ " --host=x86_64-apple-darwin " ]
2025-09-24 11:43:45 +05:00
else:
MAC_CROSS_COMPILE_INTEL_ARGS = []
2025-09-25 08:07:14 +02:00
MAC_CROSS_COMPILE_INTEL_BJAM_ARGS = []
MAC_CROSS_COMPILE_INTEL_CXX = " "
MAC_CROSS_COMPILE_INTEL_CC = " "
2025-10-29 14:38:16 +05:00
MAC_CROSS_COMPILE_INTEL_AUTOCONF_HOST_ARGS = []
2025-09-25 08:07:14 +02:00
2024-10-10 09:15:11 +00:00
OFF_ON = [ " OFF " , " ON " ]
2023-05-19 10:32:20 +02:00
BUILD_STATIC = " shared " not in flags
ENABLE_FLAG = " --enable-static " if BUILD_STATIC else " --enable-shared "
DISABLE_FLAG = " --disable-shared " if BUILD_STATIC else " --disable-static "
LINK_TYPE = " static " if BUILD_STATIC else " shared "
2025-07-25 17:31:08 +05:00
LINK_TYPE_UCFIRST = LINK_TYPE.capitalize()
2023-05-19 10:32:20 +02:00
LIBRARY_EXT = " a " if BUILD_STATIC else " so "
PIC = " -fPIC " if BUILD_STATIC else " "
if any(f.startswith( " py- " ) for f in flags):
2025-07-25 17:31:08 +05:00
PYTHON_VERSIONS = [pyv for pyv in PYTHON_VERSIONS if f " py- { ' ' .join(pyv.split( ' . ' )[:2])} " in flags]
2023-05-19 10:32:20 +02:00
2025-10-03 18:05:39 +02:00
if any(f.startswith( " occt- " ) for f in flags):
2025-10-04 15:59:42 +02:00
OCCT_VERSION = next(f.split( " - " , 1)[1] for f in flags if f.startswith( " occt- " ))
2025-10-03 18:05:39 +02:00
print(OCCT_VERSION)
2025-07-25 17:31:08 +05:00
if explicit_targets:
targets = { dep for target in explicit_targets for dep in gather_dependencies(target)}
2023-05-19 10:32:20 +02:00
else:
targets = set(dependency_tree.keys())
2025-07-25 17:19:16 +05:00
targets = set(t for t in targets if " without- %s " % t.lower() not in flags)
2025-10-13 17:46:32 +05:00
if WASM:
SKIP_TARGETS_FOR_WASM = {
" hdf5 " ,
" rocksdb " ,
" opencollada " ,
" swig " ,
" pcre " ,
" pcre2 " ,
" IfcGeom " ,
" IfcConvert " ,
" IfcGeomServer " ,
}
SKIP_TARGETS_FOR_WASM = { t.lower() for t in SKIP_TARGETS_FOR_WASM}
skip_targets = { t for t in targets if t.lower() in SKIP_TARGETS_FOR_WASM}
if skip_targets:
cecho(f " Skipping targets for wasm build: { ' , ' .join(sorted(skip_targets))} " , YELLOW)
targets.difference_update(skip_targets)
2023-05-19 10:32:20 +02:00
2025-07-25 17:31:08 +05:00
print( " Building: " , *sorted(targets, key=lambda t: len(list(gather_dependencies(t)))))
2023-05-19 10:32:20 +02:00
# Check that required tools are in PATH
2025-09-22 12:18:46 +05:00
yacc = " yacc " # Used during swig building process, installed with `bison` on Debian / `byacc` on Red Hat.
2025-09-22 12:34:35 +05:00
missing_commands: " list[str] " = []
2025-09-28 13:51:38 +01:00
required_commands = [git, bunzip2, tar, cc, cplusplus, autoconf, automake, make, " patch " , " cmake " , yacc, xz]
if " wasm " in flags:
2025-10-13 17:46:32 +05:00
# Skip swig build for WASM.
required_commands.append( " swig " )
2025-10-27 18:31:54 +05:00
required_commands.append( " pyodide " )
2025-10-13 17:46:32 +05:00
required_commands.remove(yacc)
2025-09-28 13:51:38 +01:00
for cmd in required_commands:
2025-10-22 17:09:13 +05:00
if shutil.which(cmd) is None:
2025-09-22 12:34:35 +05:00
missing_commands.append(cmd)
if missing_commands:
raise ValueError(f " Required tools not installed or not added to PATH: { ' , ' .join(missing_commands)} " )
2023-05-19 10:32:20 +02:00
2025-10-08 15:59:42 +05:00
MAC_INTEL_BIN_PATH = " /usr/local/bin "
2025-09-25 08:08:21 +02:00
if MAC_CROSS_COMPILE_INTEL:
2025-10-08 15:59:42 +05:00
brew = f " {MAC_INTEL_BIN_PATH} /brew "
2025-09-26 19:34:32 +05:00
assert os.path.exists(brew), f " For intel cross compilation the brew path is expected to be ' {brew} ' . "
2025-09-25 08:08:21 +02:00
2023-05-19 10:32:20 +02:00
# identifiers for the download tool (could be less memory consuming as ints, but are more verbose as strings)
download_tool_default = download_tool_py = " py "
download_tool_git = " git "
# Create log directory and file
log_dir = os.path.join(DEPS_DIR, " logs " )
if not os.path.exists(log_dir):
os.makedirs(log_dir)
LOG_FILE = os.path.join(log_dir, sp.check_output([date, " + % Y % m %d " ], encoding= " utf-8 " ).strip()) + " .log "
if not os.path.exists(LOG_FILE):
open(LOG_FILE, " w " ).close()
logger.info(f " using command log file ' {LOG_FILE} ' " )
2024-03-20 06:22:33 -04:00
# Causing havoc in python 3.11 build
try:
2025-07-25 17:19:16 +05:00
del os.environ[ " __PYVENV_LAUNCHER__ " ]
except:
pass
2024-03-20 06:22:33 -04:00
2023-05-19 10:32:20 +02:00
2025-10-10 17:48:22 +05:00
def restore_env(var_name: str, old_value: Union[str, None]) -> None:
if old_value is None:
del os.environ[var_name]
else:
os.environ[var_name] = old_value
2025-07-28 11:45:12 +05:00
def run(cmds: " Sequence[str] " , cwd: " Union[str, None] " = None, can_fail: bool = False) -> str:
2023-05-19 10:32:20 +02:00
"""
Wraps ` subprocess . Popen . communicate ( ) ` and logs the command being executed ,
sets up logging ` stderr ` to ` LOG_FILE ` ( in append mode ) and returns stdout
with leading and trailing whitespace removed .
"""
2025-09-17 11:22:06 +05:00
def timestamp() -> str:
return datetime.now().strftime( " % Y- % m- %d % H: % M: % S, %f " )[:-3] # same format as logging
2025-09-22 12:36:18 +05:00
def stream_reader(pipe, collector: " list[str] " , log_file) -> None:
2025-09-17 11:22:06 +05:00
for line in iter(pipe.readline, " " ):
log_file.write(f " { timestamp()} {line} " )
log_file.flush()
collector.append(line)
pipe.close()
2023-05-19 10:32:20 +02:00
logger.debug(f " running command { ' ' .join(cmds)} in directory {cwd} " )
2025-09-17 11:22:06 +05:00
stdout: list[str] = []
stderr: list[str] = []
# Ensure both live logs available in the log file
# and the putput.
with open(LOG_FILE, " a " , encoding= " utf-8 " ) as log_file_handle:
proc = sp.Popen(cmds, cwd=cwd, stdout=sp.PIPE, stderr=sp.PIPE, encoding= " utf-8 " )
assert proc.stdout and proc.stderr
t_out = threading.Thread(target=stream_reader, args=(proc.stdout, stdout, log_file_handle))
t_err = threading.Thread(target=stream_reader, args=(proc.stderr, stderr, log_file_handle))
t_out.start()
t_err.start()
t_out.join()
t_err.join()
proc.wait()
2023-05-19 10:32:20 +02:00
logger.debug(f " command returned {proc.returncode} " )
if proc.returncode != 0 and not can_fail:
print( " - " * 70)
2025-09-17 11:22:06 +05:00
print( " " .join(stderr))
2023-05-19 10:32:20 +02:00
print( " - " * 70)
raise RuntimeError(f " Command ` { ' ' .join(cmds)}` returned exit code {proc.returncode} " )
2025-09-17 11:22:06 +05:00
return " " .join(stdout).strip()
2023-05-19 10:32:20 +02:00
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
if platform.system() == " Darwin " :
if run([ " sw_vers " , " -productVersion " ]) >= " 11. " :
# Apparently not supported
PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split( " . " ))) >= (3, 7)]
if run([ " sw_vers " , " -productVersion " ]) < " 10.16 " :
2024-03-20 06:22:33 -04:00
# This is now solved with the ' __PYVENV_LAUNCHER__ ' hack
# PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split( " . " ))) < (3, 11)]
pass
2023-05-19 10:32:20 +02:00
BOOST_VERSION_UNDERSCORE = BOOST_VERSION.replace( " . " , " _ " )
OCE_LOCATION = f " https://github.com/tpaviot/oce/archive/OCE- {OCE_VERSION} .tar.gz "
2025-01-06 10:05:59 +01:00
BOOST_LOCATION = f " https://github.com/boostorg/boost/releases/download/boost- {BOOST_VERSION} / "
2023-05-19 10:32:20 +02:00
# Helper functions
2025-07-28 11:48:20 +05:00
def run_autoconf(arg1: str, configure_args: " list[str] " , cwd: str) -> None:
2023-05-19 10:32:20 +02:00
configure_path = os.path.realpath(os.path.join(cwd, " .. " , " configure " ))
if not os.path.exists(configure_path):
2025-07-25 17:19:16 +05:00
run(
[bash, " ./autogen.sh " ], cwd=os.path.realpath(os.path.join(cwd, " .. " ))
) # only run autogen.sh in the directory it is located and use cwd to achieve that in order to not mess up things
# Using `sh` over `bash` fixes issues with building swig
2023-05-19 10:32:20 +02:00
prefix = os.path.realpath(f " {DEPS_DIR} /install/ {arg1} " )
wasm = []
if " wasm " in flags:
wasm.append( " emconfigure " )
2025-07-25 17:19:16 +05:00
run(
[
*wasm,
" /bin/sh " ,
" ../configure " ,
*([ " --host=wasm32 " ] if " wasm " in flags and not any(s.startswith( " --host " ) for s in configure_args) else []),
*configure_args,
f " --prefix= {prefix} " ,
],
cwd=cwd,
)
2023-05-19 10:32:20 +02:00
2025-07-28 11:48:20 +05:00
def run_cmake(arg1, cmake_args: " list[str] " , cmake_dir: Union[str, None] = None, cwd: Union[str, None] = None):
2023-05-19 10:32:20 +02:00
if cmake_dir is None:
P = " .. "
else:
P = cmake_dir
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
wasm = []
if " wasm " in flags:
wasm.append( " emcmake " )
2025-07-25 17:19:16 +05:00
2025-10-28 17:56:44 +05:00
cmake_flags: list[str] = []
2025-10-27 18:31:54 +05:00
if not WASM or not WASM_CMAKE_IS_USING_INIT_VARS:
# For WASM we provide flags using just environment variables.
# If we provide them using cmake vars, it will override emscripten toolchain flags.
# Unsure if we need this in general even for non-WASM builds.
cmake_flags.extend(
[
f " -DCMAKE_CXX_FLAGS= ' {os.environ['CXXFLAGS']} ' " ,
f " -DCMAKE_C_FLAGS= ' {os.environ['CFLAGS']} ' " ,
]
)
2025-07-25 17:19:16 +05:00
run(
[
*wasm,
" cmake " ,
P,
2025-10-27 18:31:54 +05:00
*cmake_flags,
2025-07-25 17:19:16 +05:00
*cmake_args,
f " -DCMAKE_BUILD_TYPE= {BUILD_CFG} " ,
f " -DBUILD_SHARED_LIBS= {OFF_ON[not BUILD_STATIC]} " ,
f " -DCMAKE_SHARED_LINKER_FLAGS= {os.environ['LDFLAGS']} " ,
],
cwd=cwd,
)
2023-05-19 10:32:20 +02:00
2025-07-25 17:31:08 +05:00
def git_clone_or_pull_repository(clone_url: str, target_dir: str, revision: Union[str, None] = None) -> None:
2023-05-19 10:32:20 +02:00
""" Lazily clones the ` git ` repository denoted by ` clone_url ` into
the ` target_dir ` or pulls latest changes if the ` target_dir ` exists ( naively assumes
that a working clone exists there ) and optionally checks out a revision
` revision ` after cloning or in the existing clone if ` revision ` is not
` None ` . """
if not os.path.exists(target_dir):
logger.info(f " cloning ' {clone_url} ' into ' {target_dir} ' " )
run([git, " clone " , " --recursive " , clone_url, target_dir])
else:
logger.info(f " directory ' {target_dir} ' already cloned. Pulling latest changes. " )
2025-02-24 14:01:48 +01:00
run([git, " -C " , target_dir, " fetch " , " --all " , " --tags " , " --force " ])
2023-05-19 10:32:20 +02:00
# detect whether we are on a branch and pull
if run([git, " rev-parse " , " --abbrev-ref " , " HEAD " ], cwd=target_dir) != " HEAD " :
run([git, " pull " , clone_url], cwd=target_dir)
if revision != None:
2024-09-23 09:16:24 +02:00
run([git, " reset " , " --hard " ], cwd=target_dir)
2024-09-23 06:52:49 +02:00
run([git, " fetch " , " --all " ], cwd=target_dir)
2023-05-19 10:32:20 +02:00
run([git, " checkout " , revision], cwd=target_dir)
2025-07-25 17:19:16 +05:00
def build_dependency(
2025-07-25 17:31:08 +05:00
name: str,
mode: Literal[
" cmake " ,
" autoconf " ,
" ctest " ,
" bjam " ,
],
2025-07-28 11:45:12 +05:00
build_tool_args: " list[str] " ,
2025-07-25 17:31:08 +05:00
download_url: str,
download_name: str,
download_tool: Literal[ " py " , " git " ] = download_tool_default,
2025-07-28 11:45:12 +05:00
revision: " Union[str, None] " = None,
patch: " Union[str, list[str], None] " = None,
2025-07-25 17:19:16 +05:00
shell=None,
2025-07-28 11:45:12 +05:00
pre_compile_subs: " Sequence[tuple[str, str, str]] " = (),
additional_files: " Union[dict[str, str], None] " = None,
2025-07-25 17:19:16 +05:00
no_append_name=False,
2025-09-10 11:14:16 +02:00
cmake_dir=None,
2025-07-25 17:19:16 +05:00
**kwargs,
2025-07-25 17:31:08 +05:00
) -> None:
2023-05-19 10:32:20 +02:00
""" Handles building of dependencies with different tools ( which are
distinguished with the ` mode ` argument . ` build_tool_args ` is expected to be
a list which is necessary in order to not mess up quoting of compiler and
2025-07-25 17:31:08 +05:00
linker flags .
: param pre_compile_subs : A sequence of ` ` ( fn , before , after ) ` `
: param additional_files : Mapping path - > url .
: param kwargs : Additional ` ` mode ` ` related kwargs .
"""
2023-05-19 10:32:20 +02:00
check_dir = os.path.join(DEPS_DIR, " install " , name)
if os.path.exists(check_dir):
logger.info(f " Found existing {name} , skipping " )
return
build_dir = os.path.join(DEPS_DIR, " build " )
if not os.path.exists(build_dir):
os.makedirs(build_dir)
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
logger.info(f " \r Fetching {name} ... " )
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
if download_tool == download_tool_py:
if no_append_name:
url = download_url
else:
url = os.path.join(download_url, download_name)
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
download_path = os.path.join(build_dir, download_name)
if not os.path.exists(download_path):
2025-06-13 18:15:23 +02:00
for _ in range(3):
try:
urlretrieve(url, os.path.join(build_dir, download_path))
break
except ConnectionError as e:
print(e, " ... retrying... " )
2025-07-25 17:19:16 +05:00
time.sleep(30.0)
2025-06-13 18:15:23 +02:00
continue
2023-05-19 10:32:20 +02:00
else:
2025-07-25 17:19:16 +05:00
logger.info(
f " Download ' {download_path} ' already exists, assuming it ' s an undamaged download and that it has been extracted if possible, skipping "
)
2023-05-19 10:32:20 +02:00
elif download_tool == download_tool_git:
logger.info(f " \r Checking {name} ... " )
git_clone_or_pull_repository(download_url, target_dir=os.path.join(build_dir, download_name), revision=revision)
else:
raise ValueError(f " download tool ' {download_tool} ' is not supported " )
download_dir = os.path.join(build_dir, download_name)
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
if os.path.isdir(download_dir):
extract_dir_name = download_name
extract_dir = os.path.join(build_dir, extract_dir_name)
else:
download_tarfile_path = os.path.join(build_dir, download_name)
if download_name.endswith( " .tar.gz " ) or download_name.endswith( " .tgz " ):
compr = " gz "
elif download_name.endswith( " .tar.bz2 " ):
compr = " bz2 "
2025-06-16 21:17:00 +02:00
elif download_name.endswith( " .tar.xz " ):
compr = " xz "
2023-05-19 10:32:20 +02:00
else:
raise RuntimeError( " fix source for new download type " )
download_tarfile = tarfile.open(name=download_tarfile_path, mode=f " r: {compr} " )
# tarfile seriously doesn ' t have a function to retrieve the root directory more easily
extract_dir_name = os.path.commonprefix([x for x in download_tarfile.getnames() if x != " . " ])
2025-07-25 17:19:16 +05:00
# run([tar, " --exclude= \" */* \" " , " -tf " , download_name], cwd=build_dir).strip() no longer works
2023-05-19 10:32:20 +02:00
if extract_dir_name is None:
2025-07-25 17:19:16 +05:00
extract_dir_name = run(
[bash, " -c " , f " tar -tf {download_name} 2> /dev/null | head -n 1 | cut -f1 -d / " ], cwd=build_dir
)
2023-05-19 10:32:20 +02:00
extract_dir = os.path.join(build_dir, extract_dir_name)
if not os.path.exists(extract_dir):
run([tar, " -xf " , download_name], cwd=build_dir)
2025-07-25 17:19:16 +05:00
2025-07-25 17:31:08 +05:00
if additional_files:
for path, url in additional_files.items():
if not os.path.exists(path):
urlretrieve(url, os.path.join(extract_dir, path))
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
if patch is not None:
if isinstance(patch, str):
patch = [patch]
for p in patch:
2025-10-20 11:45:49 +05:00
patch_abs = (SCRIPT_PATH / p).absolute().__str__()
2023-05-19 10:32:20 +02:00
if os.path.exists(patch_abs):
2025-07-25 17:19:16 +05:00
try:
run([ " patch " , " -p1 " , " --batch " , " --forward " , " -i " , patch_abs], cwd=extract_dir)
2023-05-19 10:32:20 +02:00
except Exception as e:
# Assert that the patch has already been applied
run([ " patch " , " -p1 " , " --batch " , " --reverse " , " --dry-run " , " -i " , patch_abs], cwd=extract_dir)
2025-06-16 21:17:00 +02:00
if shell is not None:
sp.run(shell, shell=True, check=True, cwd=extract_dir)
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
if mode == " ctest " :
2025-08-28 15:40:38 +02:00
try:
run(
[ " ctest " , " -S " , " HDF5config.cmake,BUILD_GENERATOR=Unix " , " -C " , BUILD_CFG, " -V " , " -O " , " hdf5.log " ],
cwd=extract_dir,
)
except Exception as e:
print( " - " * 70)
2025-09-12 16:44:58 +05:00
print(open(os.path.join(extract_dir, " hdf5.log " )))
2025-08-28 15:40:38 +02:00
print( " - " * 70)
raise e
2025-07-25 17:19:16 +05:00
run([tar, " -xf " , kwargs[ " ctest_result " ] + " .tar.gz " ], cwd=os.path.join(extract_dir, " build " ))
2023-05-19 10:32:20 +02:00
shutil.copytree(
os.path.join(extract_dir, " build " , kwargs[ " ctest_result " ], kwargs[ " ctest_result_path " ]),
2025-07-25 17:19:16 +05:00
os.path.join(DEPS_DIR, " install " , name),
2023-05-19 10:32:20 +02:00
)
elif mode != " bjam " :
2025-09-10 11:14:16 +02:00
extract_build_dir = os.path.join(extract_dir, *([cmake_dir] if cmake_dir else []), " build " )
2023-05-19 10:32:20 +02:00
if os.path.exists(extract_build_dir):
shutil.rmtree(extract_build_dir)
os.makedirs(extract_build_dir)
logger.info(f " \r Configuring {name} ... " )
if mode == " autoconf " :
run_autoconf(name, build_tool_args, cwd=extract_build_dir)
elif mode == " cmake " :
run_cmake(name, build_tool_args, cwd=extract_build_dir)
else:
raise ValueError()
2025-06-16 21:17:00 +02:00
for fn, before, after in pre_compile_subs:
2025-07-25 17:19:16 +05:00
with open(os.path.join(extract_dir, fn), " r " ) as f:
2025-06-16 21:17:00 +02:00
s = f.read()
s = s.replace(before, after)
2025-07-25 17:19:16 +05:00
with open(os.path.join(extract_dir, fn), " w " ) as f:
2025-06-16 21:17:00 +02:00
f.write(s)
2023-05-19 10:32:20 +02:00
logger.info(f " \r Building {name} ... " )
run([make, f " -j {IFCOS_NUM_BUILD_PROCS} " , " VERBOSE=1 " ], cwd=extract_build_dir)
logger.info(f " \r Installing {name} ... " )
run([make, " install " ], cwd=extract_build_dir)
logger.info(f " \r Installed {name} \n " )
2025-07-28 17:03:18 +05:00
else: # bjam
2023-05-19 10:32:20 +02:00
logger.info(f " \r Configuring {name} ... " )
run([bash, " ./bootstrap.sh " ], cwd=extract_dir)
logger.info(f " \r Building {name} ... " )
run([ " ./b2 " , f " -j {IFCOS_NUM_BUILD_PROCS} " ] + build_tool_args, cwd=extract_dir, can_fail= " wasm " in flags)
logger.info(f " \r Installing {name} ... " )
2025-07-25 17:19:16 +05:00
shutil.copytree(
os.path.join(extract_dir, " boost " ), os.path.join(DEPS_DIR, " install " , f " boost- {BOOST_VERSION} " , " boost " )
)
2023-05-19 10:32:20 +02:00
logger.info(f " \r Installed {name} \n " )
2024-11-21 15:25:47 +01:00
if " diskcleanup " in flags:
shutil.rmtree(build_dir, ignore_errors=True)
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
cecho( " Collecting dependencies: " , GREEN)
# Set compiler flags for 32bit builds on 64bit system
# TODO: This is untested
ADDITIONAL_ARGS = []
if platform.system() == " Darwin " :
ADDITIONAL_ARGS = [f " -mmacosx-version-min= {TOOLSET} " ] + ADDITIONAL_ARGS
# If the linker supports GC sections, set it up to reduce binary file size
# -fPIC is required for the shared libraries to work
2024-05-12 20:32:09 +00:00
compiler_flags = " CFLAGS " , " CXXFLAGS " , " LDFLAGS "
2023-05-19 10:32:20 +02:00
CXXFLAGS = os.environ.get( " CXXFLAGS " , " " )
CFLAGS = os.environ.get( " CFLAGS " , " " )
LDFLAGS = os.environ.get( " LDFLAGS " , " " )
ADDITIONAL_ARGS_STR = " " .join(ADDITIONAL_ARGS)
2025-06-16 21:17:00 +02:00
2025-10-16 11:08:15 +05:00
CXXFLAGS_MINIMAL = f " {CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR} "
CFLAGS_MINIMAL = f " {CFLAGS} {PIC} {ADDITIONAL_ARGS_STR} "
2025-06-16 21:17:00 +02:00
if " wasm " in flags:
2025-10-16 11:08:15 +05:00
# WASM `SIDE_MODULE_` are absorbed by `emcmake` automatically.
CXXFLAGS = CXXFLAGS_MINIMAL
CFLAGS = CFLAGS_MINIMAL
2025-06-16 21:17:00 +02:00
elif sp.call([bash, " -c " , " ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev/null " ]) != 0:
2023-05-19 10:32:20 +02:00
if BUILD_STATIC:
CXXFLAGS = f " {CXXFLAGS} {PIC} -fdata-sections -ffunction-sections -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR} "
CFLAGS = f " {CFLAGS} {PIC} -fdata-sections -ffunction-sections -fvisibility=hidden {ADDITIONAL_ARGS_STR} "
else:
CXXFLAGS = CXXFLAGS_MINIMAL
CFLAGS = CFLAGS_MINIMAL
LDFLAGS = f " {LDFLAGS} -Wl,--gc-sections {ADDITIONAL_ARGS_STR} "
else:
if BUILD_STATIC:
CXXFLAGS = f " {CXXFLAGS} {PIC} -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR} "
CFLAGS = f " {CFLAGS} {PIC} -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR} "
else:
2025-07-25 17:19:16 +05:00
CXXFLAGS = CXXFLAGS_MINIMAL
CFLAGS = CFLAGS_MINIMAL
2023-05-19 10:32:20 +02:00
LDFLAGS = f " {LDFLAGS} {ADDITIONAL_ARGS_STR} "
2024-05-12 20:32:09 +00:00
if " lto " in flags:
for f in compiler_flags:
locals()[f] += f " -flto= {IFCOS_NUM_BUILD_PROCS} "
2023-05-19 10:32:20 +02:00
os.environ[ " CXXFLAGS " ] = CXXFLAGS
2025-06-16 21:17:00 +02:00
os.environ[ " CPPFLAGS " ] = CXXFLAGS
2023-05-19 10:32:20 +02:00
os.environ[ " CFLAGS " ] = CFLAGS
os.environ[ " LDFLAGS " ] = LDFLAGS
# Some dependencies need a more recent CMake version than most distros provide
# @tfk: this is no longer needed
# build_dependency(name= " cmake- %s " % (CMAKE_VERSION,), mode= " autoconf " , build_tool_args=[], download_url= " https://cmake.org/files/v %s " % (CMAKE_VERSION_2,), download_name= " cmake- %s .tar.gz " % (CMAKE_VERSION,))
2025-07-25 17:19:16 +05:00
if " hdf5 " in targets:
2024-05-12 20:32:09 +00:00
# not supported
orig = [os.environ[f] for f in compiler_flags]
for f in compiler_flags:
2024-11-21 15:25:47 +01:00
os.environ[f] = re.sub(r " -flto(= \ w+)? " , " " , os.environ[f])
2024-05-12 20:32:09 +00:00
2025-09-16 12:13:53 +05:00
HDF5_UNDERSCORE = " _ " .join(HDF5_VERSION.split( " . " ))
2023-05-19 10:32:20 +02:00
HDF5_MAJOR = " . " .join(HDF5_VERSION.split( " . " )[:-1])
2025-09-16 12:13:53 +05:00
dependency_name = f " hdf5- {HDF5_VERSION} "
2023-05-19 10:32:20 +02:00
build_dependency(
2025-09-16 12:13:53 +05:00
name=dependency_name,
mode= " cmake " ,
build_tool_args=[
f " -DCMAKE_INSTALL_PREFIX= {DEPS_DIR} /install/ {dependency_name} " ,
" -DHDF5_ENABLE_Z_LIB_SUPPORT=OFF " ,
" -DBUILD_TESTING=OFF " ,
" -DHDF5_BUILD_TOOLS=OFF " ,
" -DHDF5_BUILD_EXAMPLES=OFF " ,
" -DBUILD_SHARED_LIBS=OFF " ,
" -DHDF5_BUILD_UTILS=OFF " ,
" -DHDF5_BUILD_CPP_LIB=ON " ,
2025-09-24 11:43:45 +05:00
*MAC_CROSS_COMPILE_INTEL_ARGS,
2025-09-16 12:13:53 +05:00
],
download_url=f " https://github.com/HDFGroup/hdf5/archive/refs/tags/ " ,
download_name=f " hdf5- {HDF5_UNDERSCORE} .tar.gz " ,
2023-05-19 10:32:20 +02:00
)
2025-07-25 17:19:16 +05:00
2024-05-12 20:32:09 +00:00
for f, o in zip(compiler_flags, orig):
os.environ[f] = o
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
if " json " in targets:
2025-09-18 14:03:35 +05:00
dependency_name = f " json- {JSON_VERSION} "
build_dependency(
name=dependency_name,
mode= " cmake " ,
build_tool_args=[
f " -DCMAKE_INSTALL_PREFIX= {DEPS_DIR} /install/ {dependency_name} " ,
" -DJSON_BuildTests=OFF " ,
],
download_url=f " https://github.com/nlohmann/json/releases/download/v {JSON_VERSION} " ,
download_name= " json.tar.xz " ,
)
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
if " eigen " in targets:
2025-09-18 12:35:30 +05:00
dependency_name = f " eigen-install- {EIGEN_VERSION} "
build_dependency(
name=f " {dependency_name} " ,
mode= " cmake " ,
# We add ' -install- ' in the middle, so it won ' t be confused with git repo we used previously.
build_tool_args=[
f " -DCMAKE_INSTALL_PREFIX= {DEPS_DIR} /install/ {dependency_name} " ,
],
download_url=f " https://gitlab.com/libeigen/eigen/-/archive/ {EIGEN_VERSION} / " ,
download_name=f " eigen- {EIGEN_VERSION} .tar.gz " ,
2025-07-25 17:19:16 +05:00
)
2023-05-19 10:32:20 +02:00
if " pcre " in targets:
2025-10-10 17:48:22 +05:00
OLD_CC, OLD_CXX = None, None
2025-09-25 08:07:14 +02:00
if MAC_CROSS_COMPILE_INTEL:
2025-10-10 17:48:22 +05:00
OLD_CC, OLD_CXX = os.environ.get( " CC " ), os.environ.get( " CXX " )
2025-09-25 08:07:14 +02:00
os.environ[ " CC " ] = MAC_CROSS_COMPILE_INTEL_CC
os.environ[ " CXX " ] = MAC_CROSS_COMPILE_INTEL_CXX
# Keep it autoconf as OpenCOLLADA is pretty old and might break
# if we update it ' s dependencies for mmore modern cmake.
2023-05-19 10:32:20 +02:00
build_dependency(
name=f " pcre- {PCRE_VERSION} " ,
mode= " autoconf " ,
build_tool_args=[DISABLE_FLAG],
download_url=f " https://downloads.sourceforge.net/project/pcre/pcre/ {PCRE_VERSION} / " ,
2025-07-25 17:19:16 +05:00
download_name=f " pcre- {PCRE_VERSION} .tar.bz2 " ,
2023-05-19 10:32:20 +02:00
)
2025-09-25 08:07:14 +02:00
if MAC_CROSS_COMPILE_INTEL:
2025-10-10 17:48:22 +05:00
restore_env( " CC " , OLD_CC)
restore_env( " CXX " , OLD_CXX)
2023-05-19 10:32:20 +02:00
2025-09-16 17:05:34 +05:00
if " pcre2 " in targets:
build_dependency(
name=f " pcre2- {PCRE2_VERSION} " ,
mode= " autoconf " ,
build_tool_args=[DISABLE_FLAG],
download_url=f " https://downloads.sourceforge.net/project/pcre/pcre2/ {PCRE2_VERSION} / " ,
download_name=f " pcre2- {PCRE2_VERSION} .tar.bz2 " ,
)
2023-05-19 10:32:20 +02:00
# An issue exists with swig-1.3 and python >= 3.2
# Therefore, build a recent copy from source
if " swig " in targets:
build_dependency(
name= " swig " ,
mode= " autoconf " ,
2025-09-16 17:05:34 +05:00
build_tool_args=[ " --disable-ccache " , f " --with-pcre2-prefix= {DEPS_DIR} /install/pcre2- {PCRE2_VERSION} " ],
2023-05-19 10:32:20 +02:00
download_url= " https://github.com/swig/swig.git " ,
download_name= " swig " ,
download_tool=download_tool_git,
2025-09-16 12:30:34 +05:00
revision=f " v {SWIG_VERSION} " ,
2023-05-19 10:32:20 +02:00
)
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
if " freetype " in targets:
build_dependency(
name=f " freetype " ,
mode= " cmake " ,
2025-07-25 17:19:16 +05:00
build_tool_args=[f " -DCMAKE_INSTALL_PREFIX= {DEPS_DIR} /install/freetype " ],
download_url= " https://github.com/freetype/freetype " ,
download_name= " freetype2 " ,
2023-05-19 10:32:20 +02:00
download_tool=download_tool_git,
2025-10-10 13:53:12 +05:00
revision= " VER-2-14-0 " ,
2023-05-19 10:32:20 +02:00
)
if USE_OCCT and " occ " in targets:
patches = []
if OCCT_VERSION < " 7.4 " :
patches.append( " ./patches/occt/enable-exception-handling.patch " )
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
if OCCT_VERSION == " 7.7.1 " :
patches.append( " ./patches/occt/no_ExpToCasExe.patch " )
2025-07-25 17:19:16 +05:00
2024-09-03 08:52:22 +00:00
if OCCT_VERSION == " 7.7.2 " :
patches.append( " ./patches/occt/no_ExpToCasExe_7_7_2.patch " )
2024-09-20 20:49:22 +02:00
if OCCT_VERSION == " 7.8.1 " :
patches.append( " ./patches/occt/no_ExpToCasExe_7_8_1.patch " )
2025-07-25 17:19:16 +05:00
2025-10-03 18:08:37 +02:00
if OCCT_VERSION == " 7.9.1 " :
patches.append( " ./patches/occt/no_ExpToCasExe_7_9_1.patch " )
2023-05-19 10:32:20 +02:00
if " wasm " in flags:
patches.append( " ./patches/occt/no_em_js.patch " )
build_dependency(
name=f " occt- {OCCT_VERSION} " ,
mode= " cmake " ,
build_tool_args=[
f " -DINSTALL_DIR= {DEPS_DIR} /install/occt- {OCCT_VERSION} " ,
f " -DBUILD_LIBRARY_TYPE= {LINK_TYPE_UCFIRST} " ,
2025-08-26 19:38:42 +02:00
f " -DBUILD_MODULE_Draw=0 " ,
f " -DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off " ,
2025-09-16 18:09:44 +05:00
# Disable xlib explicitly, as it tries to use it on Desktop Ubuntu, adding unnecessary dependency.
f " -DUSE_XLIB=OFF " ,
2025-09-16 18:30:44 +05:00
# Avoid building 3D Viewer.
f " -DUSE_FREETYPE=OFF " ,
f " -DUSE_OPENGL=OFF " ,
f " -DUSE_GLES2=OFF " ,
2025-07-25 17:19:16 +05:00
f " -D3RDPARTY_FREETYPE_DIR= {DEPS_DIR} /install/freetype " ,
2025-09-25 08:07:14 +02:00
f " -DCMAKE_POLICY_VERSION_MINIMUM=3.5 " ,
*MAC_CROSS_COMPILE_INTEL_ARGS,
2023-05-19 10:32:20 +02:00
],
2025-07-25 17:19:16 +05:00
download_url= " https://github.com/Open-Cascade-SAS/OCCT " ,
download_name= " occt " ,
2023-05-19 10:32:20 +02:00
download_tool=download_tool_git,
patch=patches,
2025-07-25 17:19:16 +05:00
revision= " V " + OCCT_VERSION.replace( " . " , " _ " ),
2023-05-19 10:32:20 +02:00
)
elif " occ " in targets:
build_dependency(
name=f " oce- {OCE_VERSION} " ,
mode= " cmake " ,
build_tool_args=[
2025-08-26 19:38:42 +02:00
f " -DOCE_DISABLE_TKSERVICE_FONT=ON " ,
f " -DOCE_TESTING=OFF " ,
f " -DOCE_BUILD_SHARED_LIB=OFF " ,
f " -DOCE_DISABLE_X11=ON " ,
f " -DOCE_VISUALISATION=OFF " ,
f " -DOCE_OCAF=OFF " ,
2025-07-25 17:19:16 +05:00
f " -DOCE_INSTALL_PREFIX= {DEPS_DIR} /install/oce- {OCE_VERSION} " ,
2023-05-19 10:32:20 +02:00
],
download_url= " https://github.com/tpaviot/oce/archive/ " ,
2025-07-25 17:19:16 +05:00
download_name=f " OCE- {OCE_VERSION} .tar.gz " ,
2023-05-19 10:32:20 +02:00
)
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
if " libxml2 " in targets:
2025-09-25 08:07:14 +02:00
OLD_CC = " "
if MAC_CROSS_COMPILE_INTEL:
OLD_CC = os.environ.get( " CC " )
os.environ[ " CC " ] = MAC_CROSS_COMPILE_INTEL_CC
2025-09-29 09:58:16 +05:00
build_tool_args = [
2025-09-29 00:49:12 +01:00
" --without-python " ,
ENABLE_FLAG,
DISABLE_FLAG,
" --without-zlib " ,
" --without-iconv " ,
" --without-lzma " ,
]
if " wasm " in flags:
build_tool_args.append( " --without-threads " )
2023-05-19 10:32:20 +02:00
build_dependency(
f " libxml2- {LIBXML2_VERSION} " ,
" autoconf " ,
2025-09-29 00:49:12 +01:00
build_tool_args=build_tool_args,
2025-06-16 21:17:00 +02:00
download_url=f " https://download.gnome.org/sources/libxml2/ { ' . ' .join(LIBXML2_VERSION.split( ' . ' )[0:2])}/ " ,
2025-07-25 17:19:16 +05:00
download_name=f " libxml2- {LIBXML2_VERSION} .tar.xz " ,
2023-05-19 10:32:20 +02:00
)
2025-09-25 08:07:14 +02:00
if MAC_CROSS_COMPILE_INTEL:
if OLD_CC is None:
del os.environ[ " CC " ]
else:
os.environ[ " CC " ] = OLD_CC
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
if " OpenCOLLADA " in targets:
patches = [ " ./patches/opencollada/pr622_and_disable_subdirs.patch " ]
2025-09-23 15:34:09 +05:00
# This patch allows static libraries config on Unix,
# because the config is weird and doesn ' t allow non-shared libraries on Unix.
patches.append( " ./patches/opencollada/allow_static_libraries_config_on_unix.patch " )
2023-05-19 10:32:20 +02:00
if " wasm " in flags:
# This is necessary for the WASM build, because recent versions of
# clang don ' t have the tr1:: namespace anymore. However, it breaks
# some versions of gcc (9.4.0 at least) due to specializing std::hash
# outside of the std:: namespace.
patches.append( " ./patches/opencollada/remove_tr1.patch " )
build_dependency(
" OpenCOLLADA " ,
" cmake " ,
build_tool_args=[
f " -DLIBXML2_INCLUDE_DIR= {DEPS_DIR} /install/libxml2- {LIBXML2_VERSION} /include/libxml2 " ,
f " -DLIBXML2_LIBRARIES= {DEPS_DIR} /install/libxml2- {LIBXML2_VERSION} /lib/libxml2. {LIBRARY_EXT} " ,
f " -DPCRE_INCLUDE_DIR= {DEPS_DIR} /install/pcre- {PCRE_VERSION} /include " ,
f " -DPCRE_PCREPOSIX_LIBRARY= {DEPS_DIR} /install/pcre- {PCRE_VERSION} /lib/libpcreposix. {LIBRARY_EXT} " ,
f " -DPCRE_PCRE_LIBRARY= {DEPS_DIR} /install/pcre- {PCRE_VERSION} /lib/libpcre. {LIBRARY_EXT} " ,
2025-07-25 17:19:16 +05:00
f " -DCMAKE_INSTALL_PREFIX= {DEPS_DIR} /install/OpenCOLLADA/ " ,
2025-09-23 23:43:56 +05:00
# OpenCOLLADA is ancient at this point and allows cmake 2.6+, which results in error in cmake 4.
f " -DCMAKE_POLICY_VERSION_MINIMUM=3.5 " ,
2025-09-24 11:43:45 +05:00
*MAC_CROSS_COMPILE_INTEL_ARGS,
2023-05-19 10:32:20 +02:00
],
download_url= " https://github.com/KhronosGroup/OpenCOLLADA.git " ,
download_name= " OpenCOLLADA " ,
download_tool=download_tool_git,
patch=patches,
2025-07-25 17:19:16 +05:00
revision=OPENCOLLADA_VERSION,
2023-05-19 10:32:20 +02:00
)
if " python " in targets and not USE_CURRENT_PYTHON_VERSION and " wasm " not in flags:
# Python should not be built with -fvisibility=hidden, from experience that introduces segfaults
2025-06-16 21:17:00 +02:00
OLD_CPP_FLAGS = os.environ[ " CPPFLAGS " ]
2023-05-19 10:32:20 +02:00
OLD_CXX_FLAGS = os.environ[ " CXXFLAGS " ]
OLD_C_FLAGS = os.environ[ " CFLAGS " ]
os.environ[ " CXXFLAGS " ] = CXXFLAGS_MINIMAL
2025-06-16 21:17:00 +02:00
os.environ[ " CPPFLAGS " ] = CXXFLAGS_MINIMAL
2023-05-19 10:32:20 +02:00
os.environ[ " CFLAGS " ] = CFLAGS_MINIMAL
# On OSX a dynamic python library is built or it would not be compatible
# with the system python because of some threading initialization
2025-07-28 11:45:12 +05:00
PYTHON_CONFIGURE_ARGS: " list[str] " = []
2025-10-08 15:59:42 +05:00
original_path = " "
2023-05-19 10:32:20 +02:00
if platform.system() == " Darwin " :
PYTHON_CONFIGURE_ARGS = [ " --enable-shared " ]
2025-09-26 19:34:32 +05:00
open_ssl_prefix = run([brew, " --prefix " , " openssl@3 " ]).strip()
2025-09-25 08:07:14 +02:00
# I ' m not sure why, but if I do ` " {open_ssl_prefix} " ` (keep the quotes),
# autconf fails to find ssl.
2025-09-26 19:34:32 +05:00
PYTHON_CONFIGURE_ARGS.append(f " --with-openssl= {open_ssl_prefix} " )
2025-09-25 08:07:14 +02:00
if MAC_CROSS_COMPILE_INTEL:
2025-10-08 15:59:42 +05:00
original_path = os.environ[ " PATH " ]
# Need to ensure python will pick up intel ' s `pkg-config`,
# otherwise it might attempt to use ARM libraries (e.g. `zstd`) and fail.
os.environ[ " PATH " ] = f " {MAC_INTEL_BIN_PATH} {os.pathsep} {original_path} "
2025-09-25 08:07:14 +02:00
PYTHON_CONFIGURE_ARGS.extend([ " --with-universal-archs=intel-64 " , " --enable-universalsdk " ])
2023-05-19 10:32:20 +02:00
for PYTHON_VERSION in PYTHON_VERSIONS:
try:
build_dependency(
f " python- {PYTHON_VERSION} " ,
" autoconf " ,
PYTHON_CONFIGURE_ARGS,
f " http://www.python.org/ftp/python/ {PYTHON_VERSION} / " ,
2025-07-25 17:19:16 +05:00
f " Python- {PYTHON_VERSION} .tgz " ,
2023-05-19 10:32:20 +02:00
)
except RuntimeError as e:
# Sometimes setting up modules such as pip/lzma can cause
# the python installer script to return a non zero exit
# code where actually the headers and dynamic libraries
# are installed correctly. This is all we need so we catch
# the exception and only reraise if a partially successful
# install is not detected.
2025-07-25 17:19:16 +05:00
if not os.path.exists(os.path.join(DEPS_DIR, " install " , f " python- {PYTHON_VERSION} " )):
2023-05-19 10:32:20 +02:00
raise e
2025-10-08 15:59:42 +05:00
if MAC_CROSS_COMPILE_INTEL:
assert original_path
os.environ[ " PATH " ] = original_path
2025-06-16 21:17:00 +02:00
os.environ[ " CPPFLAGS " ] = OLD_CPP_FLAGS
2023-05-19 10:32:20 +02:00
os.environ[ " CXXFLAGS " ] = OLD_CXX_FLAGS
os.environ[ " CFLAGS " ] = OLD_C_FLAGS
if " boost " in targets:
str_concat = lambda prefix: lambda postfix: " " if postfix.strip() == " " else " = " .join((prefix, postfix.strip()))
toolset = []
if " wasm " in flags:
toolset.append( " toolset=emscripten " )
build_dependency(
f " boost- {BOOST_VERSION} " ,
mode= " bjam " ,
build_tool_args=[
f " --stagedir= {DEPS_DIR} /install/boost- {BOOST_VERSION} " ,
" --with-system " ,
" --with-program_options " ,
" --with-regex " ,
" --with-thread " ,
" --with-date_time " ,
" --with-iostreams " ,
2024-11-22 09:31:23 +01:00
" --with-filesystem " ,
2023-05-19 10:32:20 +02:00
f " link= {LINK_TYPE} " ,
*toolset,
2025-07-25 17:19:16 +05:00
*map(str_concat( " cxxflags " ), CXXFLAGS.strip().split( " " )),
*map(str_concat( " linkflags " ), LDFLAGS.strip().split( " " )),
" stage " ,
" -s " ,
" NO_BZIP2=1 " ,
2025-09-25 08:07:14 +02:00
*MAC_CROSS_COMPILE_INTEL_BJAM_ARGS,
2025-07-25 17:19:16 +05:00
],
2023-05-19 10:32:20 +02:00
download_url=BOOST_LOCATION,
2024-11-21 15:25:47 +01:00
# don ' t remember what this is, but fail on 1.86
# patch= " ./patches/boost/boostorg_regex_62.patch " ,
2025-07-25 17:19:16 +05:00
download_name=f " boost- {BOOST_VERSION} -b2-nodocs.tar.gz " ,
2023-05-19 10:32:20 +02:00
)
if " wasm " in flags:
# only supported on nix for now
2025-07-25 17:19:16 +05:00
run(
( " find " , " . " , " -name " , " *.bc " , " -exec " , " bash " , " -c " , " emar q $ { 1 % .bc}.a $1 " , " bash " , " {} " , " ; " ),
cwd=f " {DEPS_DIR} /install/boost- {BOOST_VERSION} /lib " ,
)
2023-05-19 10:32:20 +02:00
if " cgal " in targets:
2025-07-28 11:45:12 +05:00
gmp_args: " list[str] " = []
mpfr_args: " list[str] " = []
2025-10-10 17:48:22 +05:00
OLD_HOST_CC = None
if WASM:
if APPLE:
# Override `HOST_CC`, otherwise `emcc` will try to use it ' s own `clang` which can only build
# wasm executables and build will fail.
os.environ[ " HOST_CC " ] = " clang "
2025-10-10 16:42:27 +05:00
# Disable assembly, otherwise `emcc -c conftest.s` will crash due to assembly mismatch.
gmp_args.extend(( " --disable-assembly " , " --enable-cxx " ))
2023-05-19 10:32:20 +02:00
mpfr_args.extend(( " --host " , " none " ))
2025-09-25 08:07:14 +02:00
OLD_CC = None
if MAC_CROSS_COMPILE_INTEL:
OLD_CC = os.environ.get( " CC " )
2025-10-29 14:38:16 +05:00
# Otherwise it ' s using arm64 `gcc` and fails to build gmp.
2025-09-25 08:07:14 +02:00
os.environ[ " CC " ] = MAC_CROSS_COMPILE_INTEL_CC
2025-10-29 14:38:16 +05:00
gmp_args.extend(MAC_CROSS_COMPILE_INTEL_AUTOCONF_HOST_ARGS)
2025-09-25 08:07:14 +02:00
2023-05-19 10:32:20 +02:00
build_dependency(
name=f " gmp- {GMP_VERSION} " ,
mode= " autoconf " ,
build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, " --with-pic " , *gmp_args],
2025-07-25 17:19:16 +05:00
pre_compile_subs=(
[( " build/config.h " , " HAVE_OBSTACK_VPRINTF 1 " , " HAVE_OBSTACK_VPRINTF 0 " )] if " wasm " in flags else []
),
2025-10-16 19:27:37 +05:00
# Sometimes ftp.gnu.org is very slow, use ftpmirror.gnu.org as a workaround.
2025-10-21 20:43:17 +05:00
download_url= " https://ftpmirror.gnu.org/gnu/gmp/ " ,
2025-07-25 17:19:16 +05:00
download_name=f " gmp- {GMP_VERSION} .tar.bz2 " ,
2023-05-19 10:32:20 +02:00
)
2025-07-25 17:19:16 +05:00
2025-10-10 17:48:22 +05:00
if WASM and APPLE:
restore_env( " HOST_CC " , OLD_HOST_CC)
2023-05-19 10:32:20 +02:00
build_dependency(
name=f " mpfr- {MPFR_VERSION} " ,
mode= " autoconf " ,
build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, *mpfr_args, f " --with-gmp= {DEPS_DIR} /install/gmp- {GMP_VERSION} " ],
download_url=f " http://www.mpfr.org/mpfr- {MPFR_VERSION} / " ,
2025-07-25 17:19:16 +05:00
download_name=f " mpfr- {MPFR_VERSION} .tar.bz2 " ,
2023-05-19 10:32:20 +02:00
)
2025-07-25 17:19:16 +05:00
2025-09-25 08:07:14 +02:00
if MAC_CROSS_COMPILE_INTEL:
2025-10-10 17:48:22 +05:00
restore_env( " CC " , OLD_CC)
2025-09-25 08:07:14 +02:00
2023-05-19 10:32:20 +02:00
build_dependency(
name=f " cgal- {CGAL_VERSION} " ,
mode= " cmake " ,
build_tool_args=[
f " -DGMP_LIBRARIES= {DEPS_DIR} /install/gmp- {GMP_VERSION} /lib/libgmp. {LIBRARY_EXT} " ,
f " -DGMP_INCLUDE_DIR= {DEPS_DIR} /install/gmp- {GMP_VERSION} /include " ,
2025-07-25 17:19:16 +05:00
f " -DMPFR_LIBRARIES= {DEPS_DIR} /install/mpfr- {MPFR_VERSION} /lib/libmpfr. {LIBRARY_EXT} " ,
2023-05-19 10:32:20 +02:00
f " -DMPFR_INCLUDE_DIR= {DEPS_DIR} /install/mpfr- {MPFR_VERSION} /include " ,
f " -DBoost_INCLUDE_DIR= {DEPS_DIR} /install/boost- {BOOST_VERSION} " ,
f " -DCMAKE_INSTALL_PREFIX= {DEPS_DIR} /install/cgal- {CGAL_VERSION} / " ,
2025-08-26 19:38:42 +02:00
f " -DCGAL_HEADER_ONLY=On " ,
f " -DBUILD_SHARED_LIBS=Off " ,
2023-05-19 10:32:20 +02:00
],
download_url= " https://github.com/CGAL/cgal.git " ,
download_name= " cgal " ,
download_tool=download_tool_git,
2025-07-25 17:19:16 +05:00
revision=CGAL_VERSION,
2023-05-19 10:32:20 +02:00
)
2023-05-19 10:32:20 +02:00
if " usd " in targets:
build_dependency(
name=f " oneTBB- {TBB_VERSION} " ,
mode= " cmake " ,
2025-07-25 17:19:16 +05:00
build_tool_args=[f " -DCMAKE_INSTALL_PREFIX= {DEPS_DIR} /install/tbb- {TBB_VERSION} " , f " -DTBB_TEST=OFF " ],
2023-05-19 10:32:20 +02:00
download_url= " https://github.com/oneapi-src/oneTBB " ,
download_name= " oneTBB " ,
download_tool=download_tool_git,
2025-07-25 17:19:16 +05:00
revision=f " v {TBB_VERSION} " ,
2023-05-19 10:32:20 +02:00
)
build_dependency(
name=f " usd- {USD_VERSION} " ,
mode= " cmake " ,
build_tool_args=[
f " -DCMAKE_INSTALL_PREFIX= {DEPS_DIR} /install/usd- {USD_VERSION} " ,
f " -DBOOST_ROOT= {DEPS_DIR} /install/boost- {BOOST_VERSION} " ,
f " -DTBB_ROOT_DIR= {DEPS_DIR} /install/tbb- {TBB_VERSION} " ,
f " -DPXR_ENABLE_PYTHON_SUPPORT=FALSE " ,
f " -DPXR_ENABLE_GL_SUPPORT=FALSE " ,
f " -DPXR_BUILD_IMAGING=FALSE " ,
f " -DPXR_BUILD_TUTORIALS=FALSE " ,
f " -DPXR_BUILD_EXAMPLES=FALSE " ,
f " -DPXR_BUILD_USD_TOOLS=FALSE " ,
2025-07-25 17:19:16 +05:00
f " -DPXR_BUILD_TESTS=FALSE " ,
2023-05-19 10:32:20 +02:00
],
download_url= " https://github.com/PixarAnimationStudios/USD " ,
download_name= " USD " ,
download_tool=download_tool_git,
2025-07-25 17:19:16 +05:00
revision=f " v {USD_VERSION} " ,
2023-05-19 10:32:20 +02:00
)
2025-09-10 10:05:43 +02:00
if " zstd " in targets:
build_dependency(
name=f " zstd- {ZSTD_VERSION} " ,
mode= " cmake " ,
build_tool_args=[
f " -DCMAKE_INSTALL_PREFIX= {DEPS_DIR} /install/zstd- {ZSTD_VERSION} " ,
f " -DZSTD_BUILD_STATIC=ON " ,
f " -DZSTD_BUILD_SHARED=OFF " ,
2025-09-11 09:36:42 +02:00
f " -DCMAKE_INSTALL_LIBDIR=lib " ,
2025-09-25 08:07:14 +02:00
*MAC_CROSS_COMPILE_INTEL_ARGS,
2025-09-10 10:05:43 +02:00
],
2025-09-10 11:14:16 +02:00
cmake_dir= " build/cmake/ " ,
2025-09-10 10:05:43 +02:00
download_url= " https://github.com/facebook/zstd " ,
download_name= " zstd " ,
download_tool=download_tool_git,
revision=f " v {ZSTD_VERSION} " ,
)
2025-08-26 19:38:42 +02:00
if " rocksdb " in targets:
build_dependency(
name=f " rocksdb- {ROCKSDB_VERSION} " ,
mode= " cmake " ,
build_tool_args=[
2025-08-28 11:28:23 +02:00
f " -DCMAKE_INSTALL_PREFIX= {DEPS_DIR} /install/rocksdb- {ROCKSDB_VERSION} " ,
2025-08-26 19:38:42 +02:00
f " -DFAIL_ON_WARNINGS=Off " ,
f " -DWITH_TESTS=OFF " ,
2025-08-27 10:22:52 +02:00
f " -DWITH_TOOLS=OFF " ,
f " -DWITH_GFLAGS=OFF " ,
f " -DWITH_BENCHMARK_TOOLS=OFF " ,
f " -DWITH_CORE_TOOLS=OFF " ,
f " -DROCKSDB_BUILD_SHARED=Off " ,
2025-08-27 13:25:13 +02:00
f " -DCMAKE_POSITION_INDEPENDENT_CODE=On " ,
2025-08-28 15:28:28 +02:00
f " -DUSE_RTTI=On " ,
2025-09-10 10:05:43 +02:00
f " -DWITH_ZSTD=On " ,
2025-09-11 09:22:54 +02:00
f " -DPORTABLE=1 " ,
2025-09-12 16:10:30 +05:00
f " -DCMAKE_PREFIX_PATH= {DEPS_DIR} /install/zstd- {ZSTD_VERSION} " ,
2025-09-25 08:07:14 +02:00
*MAC_CROSS_COMPILE_INTEL_ARGS,
2025-08-26 19:38:42 +02:00
],
download_url= " https://github.com/facebook/rocksdb " ,
download_name= " rocksdb " ,
download_tool=download_tool_git,
revision=f " v {ROCKSDB_VERSION} " ,
)
2023-05-19 10:32:20 +02:00
cecho( " Building IfcOpenShell: " , GREEN)
IFCOS_DIR = os.path.join(DEPS_DIR, " build " , " ifcopenshell " )
if os.environ.get( " NO_CLEAN " , " " ).lower() not in { " 1 " , " on " , " true " }:
if os.path.exists(IFCOS_DIR):
shutil.rmtree(IFCOS_DIR)
os.makedirs(IFCOS_DIR, exist_ok=True)
executables_dir = os.path.join(IFCOS_DIR, " executables " )
os.makedirs(executables_dir, exist_ok=True)
cmake_args = [
2025-07-30 19:35:39 +05:00
" -DCMAKE_CXX_STANDARD=17 " ,
" -DUSE_MMAP=OFF " ,
" -DBUILD_EXAMPLES=OFF " ,
2025-07-25 17:19:16 +05:00
" -DBUILD_SHARED_LIBS= " + OFF_ON[not BUILD_STATIC],
2025-07-30 19:35:39 +05:00
" -DGLTF_SUPPORT=ON " ,
" -DBoost_NO_BOOST_CMAKE=On " ,
2025-07-25 17:19:16 +05:00
" -DADD_COMMIT_SHA= " + ( " On " if ADD_COMMIT_SHA else " Off " ),
" -DVERSION_OVERRIDE= " + ( " On " if ADD_COMMIT_SHA else " Off " ),
2025-09-24 11:43:45 +05:00
*MAC_CROSS_COMPILE_INTEL_ARGS,
2023-05-19 10:32:20 +02:00
]
2025-07-30 19:35:39 +05:00
""" Default CMake args to use for all CMake configs . """
2025-09-22 12:36:18 +05:00
cmake_args_prefix_path: " list[str] " = [
2025-09-18 11:32:56 +05:00
f " {DEPS_DIR} /install/boost- {BOOST_VERSION} " ,
2025-09-18 12:35:30 +05:00
f " {DEPS_DIR} /install/eigen-install- {EIGEN_VERSION} " ,
2025-09-18 14:03:35 +05:00
f " {DEPS_DIR} /install/json- {JSON_VERSION} " ,
2025-09-18 11:32:56 +05:00
]
2025-09-12 16:10:30 +05:00
2025-09-22 12:36:18 +05:00
def get_cmake_args_prefix_path(additional_paths: " Sequence[str] " = ()) -> " list[str] " :
2025-09-18 09:13:14 +05:00
args_prefix_path = cmake_args_prefix_path.copy()
args_prefix_path.extend(additional_paths)
prefix_path = " ; " .join(args_prefix_path)
2025-10-16 10:53:51 +05:00
if WASM:
# `emcmake` is disabling search in PATH, so we provide root paths instead.
# Provide ' / ' to PATH, so it will be combined with provided root paths,
# otherwise, depending on environment, it might not search the root path itself.
return [f " -DCMAKE_FIND_ROOT_PATH= {prefix_path} " , " -DCMAKE_PREFIX_PATH=// " ]
else:
return [f " -DCMAKE_PREFIX_PATH= {prefix_path} " ]
2025-09-12 16:10:30 +05:00
2023-05-19 10:32:20 +02:00
if " wasm " in flags:
# Boost is built by the build script so should not be found
# inside of the sysroot set by the emscriptem toolchain
cmake_args.append( " -DWASM_BUILD=On " )
2025-08-27 11:49:05 +02:00
schemas = os.environ.get( " IFCOS_SCHEMAS " )
if schemas:
2025-08-27 10:22:52 +02:00
cmake_args.append(f " -DSCHEMA_VERSIONS= {schemas} " )
2023-05-19 10:32:20 +02:00
if " cgal " in targets:
2025-09-18 14:47:18 +05:00
cmake_args_prefix_path.append(f " {DEPS_DIR} /install/cgal- {CGAL_VERSION} " )
cmake_args_prefix_path.append(f " {DEPS_DIR} /install/gmp- {GMP_VERSION} " )
cmake_args_prefix_path.append(f " {DEPS_DIR} /install/mpfr- {MPFR_VERSION} " )
2023-05-19 10:32:20 +02:00
if " occ " in targets and USE_OCCT:
2025-09-18 10:33:52 +05:00
cmake_args_prefix_path.append(f " {DEPS_DIR} /install/occt- {OCCT_VERSION} " )
2023-05-19 10:32:20 +02:00
elif " occ " in targets:
2025-09-18 10:33:52 +05:00
# We don ' t support find_package for OCE.
2025-07-25 17:19:16 +05:00
occ_include_dir = f " {DEPS_DIR} /install/oce- {OCE_VERSION} /include/oce "
occ_library_dir = f " {DEPS_DIR} /install/oce- {OCE_VERSION} /lib "
cmake_args.extend([ " -DOCC_INCLUDE_DIR= " + occ_include_dir, " -DOCC_LIBRARY_DIR= " + occ_library_dir])
2023-05-19 10:32:20 +02:00
if " OpenCOLLADA " in targets:
2025-09-18 09:34:53 +05:00
# pcre is a dependency of OpenCOLLADA, but since we `find_package`,
# we don ' t need to add it explicitly here as cmake will find it from the config.
cmake_args_prefix_path.append(f " {DEPS_DIR} /install/OpenCOLLADA " )
2023-05-19 10:32:20 +02:00
else:
2025-07-25 17:19:16 +05:00
cmake_args.extend(
[
2025-08-26 19:38:42 +02:00
f " -DCOLLADA_SUPPORT=Off " ,
2025-07-25 17:19:16 +05:00
]
)
2023-05-19 10:32:20 +02:00
if " libxml2 " in targets:
2025-09-18 09:18:03 +05:00
cmake_args_prefix_path.append(f " {DEPS_DIR} /install/libxml2- {LIBXML2_VERSION} " )
2023-05-19 10:32:20 +02:00
if " hdf5 " in targets:
2025-09-16 12:16:22 +05:00
cmake_args_prefix_path.append(f " {DEPS_DIR} /install/hdf5- {HDF5_VERSION} " )
2023-05-19 10:32:20 +02:00
else:
cmake_args.append( " -DHDF5_SUPPORT=Off " )
2023-05-19 10:32:20 +02:00
if " usd " in targets:
2025-07-25 17:19:16 +05:00
cmake_args.extend(
[
2025-08-26 19:38:42 +02:00
f " -DUSD_SUPPORT= " " On " ,
f " -DUSD_INCLUDE_DIR= {DEPS_DIR} /install/usd- {USD_VERSION} /include " ,
f " -DUSD_LIBRARY_DIR= {DEPS_DIR} /install/usd- {USD_VERSION} /lib " ,
]
)
if " rocksdb " in targets:
cmake_args.extend(
[
f " -DWITH_ROCKSDB=On " ,
2025-09-11 13:53:44 +02:00
f " -DWITH_ZSTD=On " ,
2025-09-12 16:10:30 +05:00
]
)
cmake_args_prefix_path.extend(
[
f " {DEPS_DIR} /install/rocksdb- {ROCKSDB_VERSION} " ,
f " {DEPS_DIR} /install/zstd- {ZSTD_VERSION} " ,
2025-07-25 17:19:16 +05:00
]
)
2023-05-19 10:32:20 +02:00
2025-10-13 17:46:32 +05:00
if not WASM and (not explicit_targets or { " IfcGeom " , " IfcConvert " , " IfcGeomServer " } & set(explicit_targets)):
2023-05-19 10:32:20 +02:00
logger.info( " \r Configuring executables... " )
exec_args = [
2025-08-29 14:12:39 +02:00
f " -DBUILD_IFCGEOM= {OFF_ON['IfcGeom' in targets]} " ,
f " -DBUILD_GEOMSERVER= {OFF_ON['IfcGeomServer' in targets]} " ,
f " -DBUILD_CONVERT= {OFF_ON['IfcConvert' in targets]} " ,
f " -DBUILD_IFCPYTHON=OFF " ,
f " -DCMAKE_INSTALL_PREFIX= {DEPS_DIR} /install/ifcopenshell " ,
2023-05-19 10:32:20 +02:00
]
2025-07-25 17:19:16 +05:00
2025-09-12 16:10:30 +05:00
run_cmake( " " , exec_args + cmake_args + get_cmake_args_prefix_path(), cmake_dir=CMAKE_DIR, cwd=executables_dir)
2023-05-19 10:32:20 +02:00
logger.info( " \r Building executables... " )
2025-10-31 13:31:32 +05:00
run([make, f " -j {IFCOS_NUM_BUILD_PROCS} " , " VERBOSE=1 " ], cwd=executables_dir)
2023-05-19 10:32:20 +02:00
run([make, " install/strip " if BUILD_CFG == " Release " else " install " ], cwd=executables_dir)
if " IfcOpenShell-Python " in targets:
# On OSX the actual Python library is not linked against.
ADDITIONAL_ARGS = " "
if platform.system() == " Darwin " :
2025-10-30 19:44:43 +05:00
ADDITIONAL_ARGS = " -Wl,-undefined,dynamic_lookup "
2025-07-25 17:19:16 +05:00
2025-10-03 13:12:32 +05:00
# NOTE: We don ' t use `CXXFLAGS` for wrappers, so wrapper is compiled with different flags
# (e.g. ` -fdata-sections` is missing, which is set by default for executables)
# So cache doesn ' t match and running build-all.py builds most of ifcopenshell libraries twice.
2025-06-16 21:17:00 +02:00
os.environ[ " CPPFLAGS " ] = f " {CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS} "
2023-05-19 10:32:20 +02:00
os.environ[ " CXXFLAGS " ] = f " {CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS} "
os.environ[ " CFLAGS " ] = f " {CFLAGS_MINIMAL} {ADDITIONAL_ARGS} "
os.environ[ " LDFLAGS " ] = f " {LDFLAGS} {ADDITIONAL_ARGS} "
python_dir = os.path.join(IFCOS_DIR, " pythonwrapper " )
os.makedirs(python_dir, exist_ok=True)
2025-07-25 17:31:08 +05:00
def compile_python_wrapper(
2025-10-06 19:44:32 +05:00
python_version: str,
python_include: Union[str, None] = None,
python_executable: Union[str, None] = None,
python_path: Union[Path, None] = None,
2025-07-25 17:31:08 +05:00
) -> Union[str, None]:
"""
: return : Path to module dir if ` ` python_executable ` ` was provided , otherwise ` ` None ` ` .
"""
2025-10-23 12:21:11 +05:00
assert bool(python_path) ^ bool(python_include)
2025-10-06 19:44:32 +05:00
2023-05-19 10:32:20 +02:00
logger.info(f " \r Configuring python {python_version} wrapper... " )
cache_path = os.path.join(python_dir, " CMakeCache.txt " )
if os.path.exists(cache_path):
os.remove(cache_path)
2025-10-06 19:44:32 +05:00
prefix_paths: list[str] = []
2023-05-19 10:32:20 +02:00
if " swig " in targets:
2025-10-06 19:44:32 +05:00
prefix_paths.append(f " {DEPS_DIR} /install/swig " )
if python_path:
# We couldn ' t just prefix PATH and have to provide all variables explicitly,
2025-10-23 12:21:11 +05:00
# see ifcwrap/cmake for the details.
2025-10-06 19:44:32 +05:00
python_executable = (Path(python_path) / " bin " / " python3 " ).__str__()
python_include = run(
[
python_executable,
" -c " ,
" import sysconfig; print(sysconfig.get_config_var( ' INCLUDEPY ' )) " ,
]
)
2023-05-19 10:32:20 +02:00
2025-10-23 12:21:11 +05:00
assert python_include
2025-07-25 17:19:16 +05:00
run_cmake(
" " ,
cmake_args
2025-10-06 19:44:32 +05:00
+ get_cmake_args_prefix_path(prefix_paths)
2025-07-25 17:19:16 +05:00
+ [
2023-05-19 10:32:20 +02:00
*([f " -DPYTHON_EXECUTABLE= {python_executable} " ] if python_executable else []),
2025-10-20 11:45:49 +05:00
# Needed because pyodide is expecting setup.py to be in the root.
*([f " -DPYTHON_MODULE_INSTALL_DIR= {REPO_PATH} " ] * WASM),
2025-10-06 19:44:32 +05:00
f " -DPYTHON_INCLUDE_DIR= {python_include} " ,
2025-08-26 19:38:42 +02:00
f " -DCMAKE_INSTALL_PREFIX= {DEPS_DIR} /install/ifcopenshell/tmp " ,
2025-07-25 17:19:16 +05:00
" -DUSERSPACE_PYTHON_PREFIX= "
+ [ " Off " , " On " ][os.environ.get( " PYTHON_USER_SITE " , " " ).lower() in { " 1 " , " on " , " true " }],
],
cmake_dir=CMAKE_DIR,
cwd=python_dir,
)
2023-05-19 10:32:20 +02:00
logger.info(f " \r Building python {python_version} wrapper... " )
2025-10-29 16:06:44 +05:00
run([make, f " -j {IFCOS_NUM_BUILD_PROCS} " , " ifcopenshell_wrapper " , " VERBOSE=1 " ], cwd=python_dir)
2023-05-19 10:32:20 +02:00
run([make, " install/local " ], cwd=os.path.join(python_dir, " ifcwrap " ))
if python_executable:
2025-09-11 18:34:54 +02:00
run([python_executable, " -m " , " ensurepip " ])
run([python_executable, " -m " , " pip " , " install " , " --user " , " numpy " , " typing_extensions " ])
2025-09-17 17:13:15 +05:00
module_dir = run(
[python_executable, " -c " , " import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell)) " ]
2025-07-25 17:19:16 +05:00
)
2025-09-17 17:13:15 +05:00
# Use just the last line is used,
# because output might contain warning like `No stream support: No module named ' lark ' `.
module_dir = module_dir.strip().splitlines()[-1]
module_dir = os.path.dirname(module_dir)
2023-05-19 10:32:20 +02:00
if platform.system() != " Darwin " :
2023-12-17 13:18:34 +01:00
if BUILD_CFG == " Release " :
# TODO: This symbol name depends on the Python version?
2024-12-30 16:47:59 +01:00
so = glob.glob(os.path.join(module_dir, " _ifcopenshell_wrapper*.so " ))[0]
if " wasm " in flags:
2025-07-25 17:19:16 +05:00
run([ " wasm-strip " , so, " -k " , " dylink.0 " ])
2024-12-30 16:47:59 +01:00
else:
run([strip, " -s " , " -K " , " PyInit__ifcopenshell_wrapper " , so], cwd=module_dir)
2023-05-19 10:32:20 +02:00
return module_dir
if " wasm " in flags:
2025-10-28 12:53:59 +05:00
compile_python_wrapper(
run([ " pyodide " , " config " , " get " , " python_version " ]),
run([ " pyodide " , " config " , " get " , " python_include_dir " ]),
)
2025-10-20 11:45:49 +05:00
# Copy setup.py where pyodide build system expects it.
shutil.copy(REPO_PATH / " pyodide " / " setup.py " , REPO_PATH)
2025-07-25 17:19:16 +05:00
2023-05-19 10:32:20 +02:00
elif USE_CURRENT_PYTHON_VERSION:
python_info = sysconfig.get_paths()
2025-10-23 12:21:11 +05:00
compile_python_wrapper(platform.python_version(), python_info[ " include " ], sys.executable)
2023-05-19 10:32:20 +02:00
else:
for python_version in PYTHON_VERSIONS:
2025-10-06 19:44:32 +05:00
python_path = Path(DEPS_DIR) / " install " / f " python- {python_version} "
module_dir = compile_python_wrapper(python_version, python_path=python_path)
2025-07-25 17:31:08 +05:00
assert module_dir
2025-08-29 10:10:28 +02:00
# Not sure why, but added after reading this in the logs
# cp: /Users/runner/work/IfcOpenShell/IfcOpenShell/build/Darwin/x86_64/10.15/install/ifcopenshell/python-3.9.11: No such file or directory
2025-08-29 14:12:39 +02:00
# D ' oh this was just due to a missing f-string f but doesn ' t hurt to keep it in.
2025-08-29 10:10:28 +02:00
run([ " mkdir " , " -p " , os.path.join(DEPS_DIR, " install " , " ifcopenshell " )])
2023-05-19 10:32:20 +02:00
run([cp, " -R " , module_dir, os.path.join(DEPS_DIR, " install " , " ifcopenshell " , f " python- {python_version} " )])
logger.info( " \r Built IfcOpenShell... \n \n " )