diff --git a/nix/build-all.py b/nix/build-all.py
index 286c12f865..949f54a3a1 100644
--- a/nix/build-all.py
+++ b/nix/build-all.py
@@ -1,939 +1,984 @@
-#!/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 . #
-# #
-###############################################################################
-
-###############################################################################
-# #
-# 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]) #
-# #
-# 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 #
-# #
-# on ubuntu 14.04: #
-# $ apt-get install git gcc g++ autoconf bison make cmake #
-# mesa-common-dev libffi-dev libfontconfig1-dev #
-# #
-# on OS X El Capitan with homebrew: #
-# $ brew install git bison autoconf automake libffi cmake #
-# #
-###############################################################################
-import logging
-import os
-import sys
-import subprocess as sp
-import shutil
-import tarfile
-import multiprocessing
-import platform
-import sysconfig
-
-# @todo temporary for expired mpfr.org certificate on 2023-04-08
-import ssl
-ssl._create_default_https_context = ssl._create_unverified_context
-
-from urllib.request import urlretrieve
-
-
-logger = logging.getLogger(__name__)
-logger.setLevel(logging.INFO)
-ch = logging.StreamHandler()
-ch.setLevel(logging.INFO)
-logger.addHandler(ch)
-
-PROJECT_NAME = "IfcOpenShell"
-USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
-ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
-
-PYTHON_VERSIONS = ["3.7.16", "3.8.16", "3.9.16", "3.10.10", "3.11.2"]
-JSON_VERSION = "v3.6.1"
-OCE_VERSION = "0.18.3"
-OCCT_VERSION = "7.7.1"
-BOOST_VERSION = "1.80.0"
-PCRE_VERSION = "8.41"
-LIBXML2_VERSION = "2.9.11"
-SWIG_VERSION = "4.0.2"
-OPENCOLLADA_VERSION = "v1.6.68"
-HDF5_VERSION = "1.12.1"
-
-GMP_VERSION = "6.2.1"
-MPFR_VERSION = "3.1.6" # latest is 4.1.0
-CGAL_VERSION = "5.3"
-
-# 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"
-
-explicit_targets = [s for s in sys.argv[1:] if not s.startswith("-")]
-flags = set(s.lstrip('-') for s in sys.argv[1:] if s.startswith("-"))
-
-# Helper function for coloured printing
-
-NO_COLOR = "\033[0m" # [http://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux]
-BLACK_ON_WHITE = "\033[0;30;107m"
-RED = "\033[31m"
-GREEN = "\033[32m"
-YELLOW = "\033[33m"
-MAGENTA = "\033[35m"
-
-def cecho(message, color=NO_COLOR):
- """Logs message `message` in color `color`."""
- logger.info(f"{color}{message}\033[0m")
-
-def which(cmd):
- for path in os.getenv("PATH").split(":"):
- if os.path.exists(path) and cmd in os.listdir(path):
- return cmd
- return None
-
-
-# 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
- TOOLSET = "10.9" if USE_OCCT else "10.6"
-
-
-IFCOS_NUM_BUILD_PROCS = os.getenv("IFCOS_NUM_BUILD_PROCS", multiprocessing.cpu_count() + 1)
-
-CMAKE_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "cmake"))
-
-build_dir = os.environ.get("BUILD_DIR", os.path.join(os.path.dirname(__file__), "..", "build"))
-
-path = [build_dir, platform.system(), "wasm" if "wasm" in flags else platform.machine()]
-if TOOLSET:
- path.append(TOOLSET)
-DEFAULT_DEPS_DIR = os.path.realpath(os.path.join(*path))
-
-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
-
-cecho (f"""This script fetches and builds {PROJECT_NAME} and its dependencies
-""", BLACK_ON_WHITE)
-cecho("""Script configuration:
-
-""", GREEN)
-cecho(f"""* USE_OCCT = {USE_OCCT}""", MAGENTA)
-if USE_OCCT:
- cecho(" - Compiling against official Open Cascade")
-else:
- cecho(" - Compiling against Open Cascade Community Edition")
-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)
-cecho(""" - The used build configuration type for the dependencies.
- Defaults to RelWithDebInfo if not specified.""")
-
-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)
-cecho(""" - How many compiler processes may be run in parallel.
-""")
-
-dependency_tree = {
- 'IfcParse': ('boost', 'libxml2', 'hdf5'),
- 'IfcGeom': ('IfcParse', 'occ', 'json', 'cgal', 'eigen'),
- 'IfcConvert': ('IfcGeom',),
- 'OpenCOLLADA': ('libxml2', 'pcre'),
- 'IfcGeomServer': ('IfcGeom',),
- 'IfcOpenShell-Python': ('python', 'swig', 'IfcGeom'),
- 'swig': ('pcre',),
- 'boost': (),
- 'libxml2': (),
- 'python': (),
- 'occ': ('freetype',),
- 'pcre': (),
- 'json': (),
- 'hdf5': (),
- 'cgal': (),
- 'eigen': (),
- 'freetype': (),
-}
-
-def v(dep):
- yield dep
- for d in dependency_tree[dep]:
- for x in v(d):
- yield x
-
-if "v" in flags:
- logger.setLevel(logging.DEBUG)
-else:
- logger.setLevel(logging.INFO)
-
-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"
-LINK_TYPE_UCFIRST = LINK_TYPE[0].upper() + LINK_TYPE[1:]
-LIBRARY_EXT = "a" if BUILD_STATIC else "so"
-PIC = "-fPIC" if BUILD_STATIC else ""
-
-if any(f.startswith("py-") for f in flags):
- PYTHON_VERSIONS = [pyv for pyv in PYTHON_VERSIONS if "py-%s" % "".join(pyv.split('.')[0:2]) in flags]
-
-if len(explicit_targets):
- targets = set(sum((list(v(target)) for target in explicit_targets), []))
-else:
- targets = set(dependency_tree.keys())
-
-targets = set(t for t in targets if 'without-%s' % t.lower() not in flags)
-
-print("Building:", *sorted(targets, key=lambda t: len(list(v(t)))))
-
-# Check that required tools are in PATH
-
-for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, make, "patch", "cmake"]:
- if which(cmd) is None:
- raise ValueError(f"Required tool '{cmd}' not installed or not added to PATH")
-
-# 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}'")
-
-def run(cmds, cwd=None, can_fail=False):
-
- """
- 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.
- """
-
- logger.debug(f"running command {' '.join(cmds)} in directory {cwd}")
- log_file_handle = open(LOG_FILE, "a")
- proc = sp.Popen(cmds, cwd=cwd, stdout=sp.PIPE, stderr=sp.PIPE, encoding="utf-8")
- stdout, stderr = proc.communicate()
- log_file_handle.write(stdout)
- log_file_handle.write(stderr)
- log_file_handle.close()
- logger.debug(f"command returned {proc.returncode}")
-
- if proc.returncode != 0 and not can_fail:
- print("-" * 70)
- print(stderr)
- print("-" * 70)
- raise RuntimeError(f"Command `{' '.join(cmds)}` returned exit code {proc.returncode}")
-
- return stdout.strip()
-
-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":
- # Apparently not supported
- PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) < (3, 11)]
-
-BOOST_VERSION_UNDERSCORE = BOOST_VERSION.replace(".", "_")
-
-OCE_LOCATION = f"https://github.com/tpaviot/oce/archive/OCE-{OCE_VERSION}.tar.gz"
-BOOST_LOCATION = f"https://boostorg.jfrog.io/artifactory/main/release/{BOOST_VERSION}/source/"
-
-# Helper functions
-
-
-def run_autoconf(arg1, configure_args, cwd):
- configure_path = os.path.realpath(os.path.join(cwd, "..", "configure"))
- if not os.path.exists(configure_path):
- 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
- prefix = os.path.realpath(f"{DEPS_DIR}/install/{arg1}")
-
- wasm = []
- if "wasm" in flags:
- wasm.append("emconfigure")
-
- run([*wasm, "/bin/sh", "../configure"] + configure_args + [f"--prefix={prefix}"], cwd=cwd)
-
-
-def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
- if cmake_dir is None:
- P = ".."
- else:
- P = cmake_dir
-
- wasm = []
- if "wasm" in flags:
- wasm.append("emcmake")
-
- run([*wasm, "cmake", P, *cmake_args, f"-DCMAKE_BUILD_TYPE={BUILD_CFG}"], cwd=cwd)
-
-
-def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
- """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.")
-
- # 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:
- run([git, "checkout", revision], cwd=target_dir)
-
-
-def build_dependency(name, mode, build_tool_args, download_url, download_name, download_tool=download_tool_default, revision=None, patch=None, additional_files={}, no_append_name=False, **kwargs):
- """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
- linker flags."""
- 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)
-
- logger.info(f"\rFetching {name}... ")
-
- if download_tool == download_tool_py:
- if no_append_name:
- url = download_url
- else:
- url = os.path.join(download_url, download_name)
-
- download_path = os.path.join(build_dir, download_name)
- if not os.path.exists(download_path):
- urlretrieve(url, os.path.join(build_dir, download_path))
- else:
- logger.info(f"Download '{download_path}' already exists, assuming it's an undamaged download and that it has been extracted if possible, skipping")
- elif download_tool == download_tool_git:
- logger.info(f"\rChecking {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)
-
- 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"
- 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 != "."])
- #run([tar, "--exclude=\"*/*\"", "-tf", download_name], cwd=build_dir).strip() no longer works
- if extract_dir_name is None:
- extract_dir_name = run([bash, "-c", f"tar -tf {download_name} 2> /dev/null | head -n 1 | cut -f1 -d /"], cwd=build_dir)
- 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)
-
- for path, url in additional_files.items():
- if not os.path.exists(path):
- urlretrieve(url, os.path.join(extract_dir, path))
-
- if patch is not None:
- if isinstance(patch, str):
- patch = [patch]
- for p in patch:
- patch_abs = os.path.abspath(os.path.join(os.path.dirname(__file__), p))
- if os.path.exists(patch_abs):
- try: run(["patch", "-p1", "--batch", "--forward", "-i", patch_abs], cwd=extract_dir)
- 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)
-
- if mode == "ctest":
- run(["ctest", "-S", "HDF5config.cmake,BUILD_GENERATOR=Unix", "-C", BUILD_CFG, "-V", "-O", "hdf5.log"], cwd=extract_dir)
- run([tar, "-xf", kwargs["ctest_result"] + ".tar.gz"], cwd=os.path.join(extract_dir, 'build'))
- shutil.copytree(
- os.path.join(extract_dir, "build", kwargs["ctest_result"], kwargs["ctest_result_path"]),
- os.path.join(DEPS_DIR, "install", name)
- )
- elif mode != "bjam":
- extract_build_dir = os.path.join(extract_dir, "build")
- if os.path.exists(extract_build_dir):
- shutil.rmtree(extract_build_dir)
- os.makedirs(extract_build_dir)
-
- logger.info(f"\rConfiguring {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()
- logger.info(f"\rBuilding {name}... ")
- run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "VERBOSE=1"], cwd=extract_build_dir)
- logger.info(f"\rInstalling {name}... ")
- run([make, "install"], cwd=extract_build_dir)
- logger.info(f"\rInstalled {name} \n")
- else:
- logger.info(f"\rConfiguring {name}...")
- run([bash, "./bootstrap.sh"], cwd=extract_dir)
- logger.info(f"\rBuilding {name}... ")
- run(["./b2", f"-j{IFCOS_NUM_BUILD_PROCS}"] + build_tool_args, cwd=extract_dir, can_fail="wasm" in flags)
- logger.info(f"\rInstalling {name}... ")
- shutil.copytree(os.path.join(extract_dir, "boost"), os.path.join(DEPS_DIR, "install", f"boost-{BOOST_VERSION}", "boost"))
- logger.info(f"\rInstalled {name} \n")
-
-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 "wasm" in flags:
- ADDITIONAL_ARGS.extend(("-sWASM_BIGINT", "-fexceptions"))
-
-# If the linker supports GC sections, set it up to reduce binary file size
-# -fPIC is required for the shared libraries to work
-
-CXXFLAGS = os.environ.get("CXXFLAGS", "")
-CFLAGS = os.environ.get("CFLAGS", "")
-LDFLAGS = os.environ.get("LDFLAGS", "")
-
-ADDITIONAL_ARGS_STR = " ".join(ADDITIONAL_ARGS)
-if "wasm" not in flags and sp.call([bash, "-c", "ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev/null"]) != 0:
- CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
- CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
- 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:
- CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
- CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
- 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:
- CXXFLAGS=CXXFLAGS_MINIMAL
- CFLAGS=CFLAGS_MINIMAL
- LDFLAGS = f"{LDFLAGS} {ADDITIONAL_ARGS_STR}"
-
-os.environ["CXXFLAGS"] = CXXFLAGS
-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,))
-
-if 'hdf5' in targets:
- HDF5_MAJOR = ".".join(HDF5_VERSION.split(".")[:-1])
- build_dependency(
- name=f"hdf5-{HDF5_VERSION}",
- mode="ctest",
- build_tool_args=[],
- download_url=f"https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-{HDF5_MAJOR}/hdf5-{HDF5_VERSION}/src/",
- download_name=f"CMake-hdf5-{HDF5_VERSION}.tar.gz",
- ctest_result=f"HDF5-{HDF5_VERSION}-{platform.system()}",
- ctest_result_path=f"HDF_Group/HDF5/{HDF5_VERSION}"
- )
-
-if "json" in targets:
- json_url = f"https://github.com/nlohmann/json/releases/download/{JSON_VERSION}/json.hpp"
- json_install_path = f"{DEPS_DIR}/install/json/nlohmann/json.hpp"
- if not os.path.exists(os.path.dirname(json_install_path)):
- os.makedirs(os.path.dirname(json_install_path))
- if not os.path.exists(json_install_path):
- urlretrieve(json_url, json_install_path)
-
-if "eigen" in targets:
- git_clone_or_pull_repository("https://gitlab.com/libeigen/eigen.git", f"{DEPS_DIR}/install/eigen-3.3.9", revision="3.3.9")
-
-if "pcre" in targets:
- 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}/",
- download_name=f"pcre-{PCRE_VERSION}.tar.bz2"
- )
-
-# 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",
- build_tool_args=["--disable-ccache", f"--with-pcre-prefix={DEPS_DIR}/install/pcre-{PCRE_VERSION}"],
- download_url="https://github.com/swig/swig.git",
- download_name="swig",
- download_tool=download_tool_git,
- revision=f"rel-{SWIG_VERSION}"
- )
-
-if "freetype" in targets:
- build_dependency(
- name=f"freetype",
- mode="cmake",
- build_tool_args=[
- f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/freetype"
- ],
- download_url = "https://github.com/freetype/freetype",
- download_name = "freetype2",
- download_tool=download_tool_git,
- )
-
-if USE_OCCT and "occ" in targets:
- patches = []
- if OCCT_VERSION < "7.4":
- patches.append("./patches/occt/enable-exception-handling.patch")
-
- if OCCT_VERSION == "7.7.1":
- patches.append("./patches/occt/no_ExpToCasExe.patch")
-
- 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}",
- "-DBUILD_MODULE_Draw=0",
- "-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off",
- f"-D3RDPARTY_FREETYPE_DIR={DEPS_DIR}/install/freetype"
- ],
- download_url = "https://git.dev.opencascade.org/repos/occt.git",
- download_name = "occt",
- download_tool=download_tool_git,
- patch=patches,
- revision="V" + OCCT_VERSION.replace('.', '_')
- )
-elif "occ" in targets:
- build_dependency(
- name=f"oce-{OCE_VERSION}",
- mode="cmake",
- build_tool_args=[
- "-DOCE_DISABLE_TKSERVICE_FONT=ON",
- "-DOCE_TESTING=OFF",
- "-DOCE_BUILD_SHARED_LIB=OFF",
- "-DOCE_DISABLE_X11=ON",
- "-DOCE_VISUALISATION=OFF",
- "-DOCE_OCAF=OFF",
- f"-DOCE_INSTALL_PREFIX={DEPS_DIR}/install/oce-{OCE_VERSION}"
- ],
- download_url="https://github.com/tpaviot/oce/archive/",
- download_name=f"OCE-{OCE_VERSION}.tar.gz"
- )
-
-if "libxml2" in targets:
- build_dependency(
- f"libxml2-{LIBXML2_VERSION}",
- "autoconf",
- build_tool_args=[
- "--without-python",
- ENABLE_FLAG,
- DISABLE_FLAG,
- "--without-zlib",
- "--without-iconv",
- "--without-lzma"
- ],
- download_url="ftp://xmlsoft.org/libxml2/",
- download_name=f"libxml2-{LIBXML2_VERSION}.tar.gz"
- )
-
-if "OpenCOLLADA" in targets:
- patches = ["./patches/opencollada/pr622_and_disable_subdirs.patch"]
-
- 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}",
- f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/OpenCOLLADA/"
- ],
- download_url="https://github.com/KhronosGroup/OpenCOLLADA.git",
- download_name="OpenCOLLADA",
- download_tool=download_tool_git,
- patch=patches,
- revision=OPENCOLLADA_VERSION
- )
-
-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
- OLD_CXX_FLAGS = os.environ["CXXFLAGS"]
- OLD_C_FLAGS = os.environ["CFLAGS"]
- os.environ["CXXFLAGS"] = CXXFLAGS_MINIMAL
- 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
- PYTHON_CONFIGURE_ARGS = []
- if platform.system() == "Darwin":
- PYTHON_CONFIGURE_ARGS = ["--enable-shared"]
-
- 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}/",
- f"Python-{PYTHON_VERSION}.tgz"
- )
- 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.
- if not os.path.exists(
- os.path.join(DEPS_DIR, "install", f"python-{PYTHON_VERSION}")
- ):
- raise e
-
- 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",
- f"link={LINK_TYPE}",
- *toolset,
- *map(str_concat("cxxflags"), CXXFLAGS.strip().split(' ')),
- *map(str_concat("linkflags"), LDFLAGS.strip().split(' ')),
- "stage", "-s", "NO_BZIP2=1"],
- download_url=BOOST_LOCATION,
- patch="./patches/boost/boostorg_regex_62.patch",
- download_name=f"boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2"
- )
- if "wasm" in flags:
- # only supported on nix for now
- run(("find", ".", "-name", "*.bc", "-exec", "bash", "-c", "emar q ${1%.bc}.a $1", "bash", "{}", ";"), cwd=f"{DEPS_DIR}/install/boost-{BOOST_VERSION}/lib")
-
-if "cgal" in targets:
- gmp_args = []
- mpfr_args = []
- if "wasm" in flags:
- gmp_args.extend(("--disable-assembly", "--host", "none", "--enable-cxx"))
- mpfr_args.extend(("--host", "none"))
-
- build_dependency(
- name=f"gmp-{GMP_VERSION}",
- mode="autoconf",
- build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, "--with-pic", *gmp_args],
- download_url="https://ftp.gnu.org/gnu/gmp/",
- download_name=f"gmp-{GMP_VERSION}.tar.bz2"
- )
-
- 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}/",
- download_name=f"mpfr-{MPFR_VERSION}.tar.bz2"
- )
-
- 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",
- f"-DMPFR_LIBRARIES={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib/libmpfr.{LIBRARY_EXT}" ,
- 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}/",
- "-DCGAL_HEADER_ONLY=On",
- "-DBUILD_SHARED_LIBS=Off"
- ],
- download_url="https://github.com/CGAL/cgal.git",
- download_name="cgal",
- download_tool=download_tool_git,
- revision=f"v{CGAL_VERSION}"
- )
-
-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)
-
-OFF_ON = ["OFF", "ON"]
-
-cmake_args = [
- "-DUSE_MMAP=" "OFF",
- "-DBUILD_EXAMPLES=" "OFF",
- "-DBUILD_SHARED_LIBS=" +OFF_ON[not BUILD_STATIC],
- "-DBOOST_ROOT=" f"{DEPS_DIR}/install/boost-{BOOST_VERSION}",
- "-DGLTF_SUPPORT=" "ON",
- "-DJSON_INCLUDE_DIR=" f"{DEPS_DIR}/install/json",
- "-DEIGEN_DIR=" f"{DEPS_DIR}/install/eigen-3.3.9",
- "-DBoost_NO_BOOST_CMAKE=" "On",
- "-DADD_COMMIT_SHA=" +("On" if ADD_COMMIT_SHA else "Off")
-]
-
-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")
-
-if "cgal" in targets:
- cmake_args.extend([
- "-DCGAL_INCLUDE_DIR=" f"{DEPS_DIR}/install/cgal-{CGAL_VERSION}/include",
- "-DGMP_INCLUDE_DIR=" f"{DEPS_DIR}/install/gmp-{GMP_VERSION}/include",
- "-DGMP_LIBRARY_DIR=" f"{DEPS_DIR}/install/gmp-{GMP_VERSION}/lib",
- "-DMPFR_INCLUDE_DIR=" f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
- "-DMPFR_LIBRARY_DIR=" f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib",
- ])
-
-if "occ" in targets and USE_OCCT:
- occ_include_dir = f"{DEPS_DIR}/install/occt-{OCCT_VERSION}/include/opencascade"
- occ_library_dir = f"{DEPS_DIR}/install/occt-{OCCT_VERSION}/lib"
- cmake_args.extend([
- "-DOCC_INCLUDE_DIR=" +occ_include_dir,
- "-DOCC_LIBRARY_DIR=" +occ_library_dir
- ])
-
-elif "occ" in targets:
- 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
- ])
-
-if "OpenCOLLADA" in targets:
- cmake_args.extend([
- "-DOPENCOLLADA_INCLUDE_DIR=" f"{DEPS_DIR}/install/OpenCOLLADA/include/opencollada",
- "-DOPENCOLLADA_LIBRARY_DIR=" f"{DEPS_DIR}/install/OpenCOLLADA/lib/opencollada"
- ])
-else:
- cmake_args.extend([
- "-DCOLLADA_SUPPORT=" "Off",
- ])
-
-if "pcre" in targets:
- cmake_args.append(
- "-DPCRE_LIBRARY_DIR=" f"{DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib"
- )
-
-if "libxml2" in targets:
- cmake_args.extend([
- "-DLIBXML2_INCLUDE_DIR=" f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2",
- "-DLIBXML2_LIBRARIES=" f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}"
- ])
-
-if "hdf5" in targets:
- cmake_args.extend([
- "-DHDF5_INCLUDE_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/include",
- "-DHDF5_LIBRARY_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/lib"
- ])
-else:
- cmake_args.append("-DHDF5_SUPPORT=Off")
-
-if not explicit_targets or {"IfcGeom", "IfcConvert", "IfcGeomServer"} & set(explicit_targets):
- logger.info("\rConfiguring executables...")
-
- exec_args = [
- "-DBUILD_IFCGEOM=" +OFF_ON["IfcGeom" in targets],
- "-DBUILD_GEOMSERVER=" +OFF_ON["IfcGeomServer" in targets],
- "-DBUILD_CONVERT=" +OFF_ON["IfcConvert" in targets],
- "-DBUILD_IFCPYTHON=" "OFF",
- "-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell",
- ]
-
- run_cmake("", exec_args + cmake_args, cmake_dir=CMAKE_DIR, cwd=executables_dir)
-
- logger.info("\rBuilding executables... ")
-
- run([make, f"-j{IFCOS_NUM_BUILD_PROCS}"], cwd=executables_dir)
- 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":
- ADDITIONAL_ARGS = "-Wl,-flat_namespace,-undefined,suppress"
-
- if "wasm" in flags:
- ADDITIONAL_ARGS = f"-Wl,-undefined,suppress -sSIDE_MODULE=2 -sEXPORTED_FUNCTIONS=_PyInit__ifcopenshell_wrapper"
-
- 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)
-
- def compile_python_wrapper(python_version, python_library, python_include, python_executable):
- logger.info(f"\rConfiguring python {python_version} wrapper...")
-
- cache_path = os.path.join(python_dir, "CMakeCache.txt")
- if os.path.exists(cache_path):
- os.remove(cache_path)
-
- os.environ["PYTHON_LIBRARY_BASENAME"] = os.path.basename(python_library)
-
- swig_when_built = []
- if "swig" in targets:
- swig_when_built.append(f"-DSWIG_EXECUTABLE={DEPS_DIR}/install/swig/bin/swig")
-
- run_cmake("",
- cmake_args + [
- "-DPYTHON_LIBRARY=" +python_library,
- *([f"-DPYTHON_EXECUTABLE={python_executable}"] if python_executable else []),
- # *([f"-DPYTHON_MODULE_INSTALL_DIR={os.environ['PYTHONPATH']}/ifcopenshell"] if "wasm" in flags else []),
- *(["-DPYTHON_MODULE_INSTALL_DIR="+os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "package"))] if "wasm" in flags else []),
- "-DPYTHON_INCLUDE_DIR=" +python_include,
- "-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell/tmp",
- "-DUSERSPACE_PYTHON_PREFIX=" +["Off", "On"][os.environ.get("PYTHON_USER_SITE", "").lower() in {"1", "on", "true"}],
- *swig_when_built],
- cmake_dir=CMAKE_DIR, cwd=python_dir)
-
- logger.info(f"\rBuilding python {python_version} wrapper... ")
-
- run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "_ifcopenshell_wrapper"], cwd=python_dir)
- run([make, "install/local"], cwd=os.path.join(python_dir, "ifcwrap"))
-
- if python_executable:
- module_dir = os.path.dirname(run([python_executable, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"]))
-
- if platform.system() != "Darwin":
- # TODO: This symbol name depends on the Python version?
- run([strip, "-s", "-K", "PyInit__ifcopenshell_wrapper", "_ifcopenshell_wrapper.so"], cwd=module_dir)
-
- return module_dir
-
- if "wasm" in flags:
- compile_python_wrapper(
- f"{os.environ['PYMAJOR']}.{os.environ['PYMINOR']}.{os.environ['PYMICRO']}",
- f"{os.environ['TARGETINSTALLDIR']}/lib/libpython{os.environ['PYMAJOR']}.{os.environ['PYMINOR']}.a",
- os.environ['PYTHONINCLUDE'],
- None
- )
-
- elif USE_CURRENT_PYTHON_VERSION:
- python_info = sysconfig.get_paths()
-
- py_path_components = [
- sysconfig.get_config_var('LIBDIR'),
- sysconfig.get_config_var("INSTSONAME")
- ]
-
- if sysconfig.get_config_var('multiarchsubdir'):
- py_path_components.insert(1, sysconfig.get_config_var('multiarchsubdir').replace("/", ""))
-
- python_lib = os.path.join(*py_path_components)
-
- compile_python_wrapper(platform.python_version(), python_lib, python_info["include"], sys.executable)
- else:
- for python_version in PYTHON_VERSIONS:
- python_library = run([bash, "-c", f"ls {DEPS_DIR}/install/python-{python_version}/lib/libpython*.*"])
- python_include = run([bash, "-c", f"ls -d {DEPS_DIR}/install/python-{python_version}/include/python*"])
- python_executable = os.path.join(DEPS_DIR, "install", f"python-{python_version}", "bin", f"python{python_version[0]}")
-
- module_dir = compile_python_wrapper(python_version, python_library, python_include, python_executable)
- run([cp, "-R", module_dir, os.path.join(DEPS_DIR, "install", "ifcopenshell", f"python-{python_version}")])
-
-logger.info("\rBuilt IfcOpenShell...\n\n")
+#!/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 . #
+# #
+###############################################################################
+
+###############################################################################
+# #
+# 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]) #
+# #
+# 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 #
+# #
+# on ubuntu 14.04: #
+# $ apt-get install git gcc g++ autoconf bison make cmake #
+# mesa-common-dev libffi-dev libfontconfig1-dev #
+# #
+# on OS X El Capitan with homebrew: #
+# $ brew install git bison autoconf automake libffi cmake #
+# #
+###############################################################################
+import logging
+import os
+import sys
+import subprocess as sp
+import shutil
+import tarfile
+import multiprocessing
+import platform
+import sysconfig
+
+# @todo temporary for expired mpfr.org certificate on 2023-04-08
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+
+from urllib.request import urlretrieve
+
+
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.INFO)
+ch = logging.StreamHandler()
+ch.setLevel(logging.INFO)
+logger.addHandler(ch)
+
+PROJECT_NAME = "IfcOpenShell"
+USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
+ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
+
+PYTHON_VERSIONS = ["3.7.16", "3.8.16", "3.9.16", "3.10.10", "3.11.2"]
+JSON_VERSION = "v3.6.1"
+OCE_VERSION = "0.18.3"
+OCCT_VERSION = "7.7.1"
+BOOST_VERSION = "1.80.0"
+PCRE_VERSION = "8.41"
+LIBXML2_VERSION = "2.9.11"
+SWIG_VERSION = "4.0.2"
+OPENCOLLADA_VERSION = "v1.6.68"
+HDF5_VERSION = "1.12.1"
+
+GMP_VERSION = "6.2.1"
+MPFR_VERSION = "3.1.6" # latest is 4.1.0
+CGAL_VERSION = "5.3"
+USD_VERSION = "23.05"
+TBB_VERSION = "2021.9.0"
+
+# 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"
+
+explicit_targets = [s for s in sys.argv[1:] if not s.startswith("-")]
+flags = set(s.lstrip('-') for s in sys.argv[1:] if s.startswith("-"))
+
+# Helper function for coloured printing
+
+NO_COLOR = "\033[0m" # [http://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux]
+BLACK_ON_WHITE = "\033[0;30;107m"
+RED = "\033[31m"
+GREEN = "\033[32m"
+YELLOW = "\033[33m"
+MAGENTA = "\033[35m"
+
+def cecho(message, color=NO_COLOR):
+ """Logs message `message` in color `color`."""
+ logger.info(f"{color}{message}\033[0m")
+
+def which(cmd):
+ for path in os.getenv("PATH").split(":"):
+ if os.path.exists(path) and cmd in os.listdir(path):
+ return cmd
+ return None
+
+
+# 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
+ TOOLSET = "10.9" if USE_OCCT else "10.6"
+
+
+IFCOS_NUM_BUILD_PROCS = os.getenv("IFCOS_NUM_BUILD_PROCS", multiprocessing.cpu_count() + 1)
+
+CMAKE_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "cmake"))
+
+build_dir = os.environ.get("BUILD_DIR", os.path.join(os.path.dirname(__file__), "..", "build"))
+
+path = [build_dir, platform.system(), "wasm" if "wasm" in flags else platform.machine()]
+if TOOLSET:
+ path.append(TOOLSET)
+DEFAULT_DEPS_DIR = os.path.realpath(os.path.join(*path))
+
+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
+
+cecho (f"""This script fetches and builds {PROJECT_NAME} and its dependencies
+""", BLACK_ON_WHITE)
+cecho("""Script configuration:
+
+""", GREEN)
+cecho(f"""* USE_OCCT = {USE_OCCT}""", MAGENTA)
+if USE_OCCT:
+ cecho(" - Compiling against official Open Cascade")
+else:
+ cecho(" - Compiling against Open Cascade Community Edition")
+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)
+cecho(""" - The used build configuration type for the dependencies.
+ Defaults to RelWithDebInfo if not specified.""")
+
+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)
+cecho(""" - How many compiler processes may be run in parallel.
+""")
+
+dependency_tree = {
+ 'IfcParse': ('boost', 'libxml2', 'hdf5'),
+ 'IfcGeom': ('IfcParse', 'occ', 'json', 'cgal', 'eigen'),
+ 'IfcConvert': ('IfcGeom',),
+ 'OpenCOLLADA': ('libxml2', 'pcre'),
+ 'IfcGeomServer': ('IfcGeom',),
+ 'IfcOpenShell-Python': ('python', 'swig', 'IfcGeom'),
+ 'swig': ('pcre',),
+ 'boost': (),
+ 'libxml2': (),
+ 'python': (),
+ 'occ': ('freetype',),
+ 'pcre': (),
+ 'json': (),
+ 'hdf5': (),
+ 'cgal': (),
+ 'eigen': (),
+ 'freetype': (),
+ 'usd': ('boost', 'oneTBB'),
+}
+
+def v(dep):
+ yield dep
+ for d in dependency_tree[dep]:
+ for x in v(d):
+ yield x
+
+if "v" in flags:
+ logger.setLevel(logging.DEBUG)
+else:
+ logger.setLevel(logging.INFO)
+
+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"
+LINK_TYPE_UCFIRST = LINK_TYPE[0].upper() + LINK_TYPE[1:]
+LIBRARY_EXT = "a" if BUILD_STATIC else "so"
+PIC = "-fPIC" if BUILD_STATIC else ""
+
+if any(f.startswith("py-") for f in flags):
+ PYTHON_VERSIONS = [pyv for pyv in PYTHON_VERSIONS if "py-%s" % "".join(pyv.split('.')[0:2]) in flags]
+
+if len(explicit_targets):
+ targets = set(sum((list(v(target)) for target in explicit_targets), []))
+else:
+ targets = set(dependency_tree.keys())
+
+targets = set(t for t in targets if 'without-%s' % t.lower() not in flags)
+
+print("Building:", *sorted(targets, key=lambda t: len(list(v(t)))))
+
+# Check that required tools are in PATH
+
+for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, make, "patch", "cmake"]:
+ if which(cmd) is None:
+ raise ValueError(f"Required tool '{cmd}' not installed or not added to PATH")
+
+# 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}'")
+
+def run(cmds, cwd=None, can_fail=False):
+
+ """
+ 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.
+ """
+
+ logger.debug(f"running command {' '.join(cmds)} in directory {cwd}")
+ log_file_handle = open(LOG_FILE, "a")
+ proc = sp.Popen(cmds, cwd=cwd, stdout=sp.PIPE, stderr=sp.PIPE, encoding="utf-8")
+ stdout, stderr = proc.communicate()
+ log_file_handle.write(stdout)
+ log_file_handle.write(stderr)
+ log_file_handle.close()
+ logger.debug(f"command returned {proc.returncode}")
+
+ if proc.returncode != 0 and not can_fail:
+ print("-" * 70)
+ print(stderr)
+ print("-" * 70)
+ raise RuntimeError(f"Command `{' '.join(cmds)}` returned exit code {proc.returncode}")
+
+ return stdout.strip()
+
+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":
+ # Apparently not supported
+ PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) < (3, 11)]
+
+BOOST_VERSION_UNDERSCORE = BOOST_VERSION.replace(".", "_")
+
+OCE_LOCATION = f"https://github.com/tpaviot/oce/archive/OCE-{OCE_VERSION}.tar.gz"
+BOOST_LOCATION = f"https://boostorg.jfrog.io/artifactory/main/release/{BOOST_VERSION}/source/"
+
+# Helper functions
+
+
+def run_autoconf(arg1, configure_args, cwd):
+ configure_path = os.path.realpath(os.path.join(cwd, "..", "configure"))
+ if not os.path.exists(configure_path):
+ 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
+ prefix = os.path.realpath(f"{DEPS_DIR}/install/{arg1}")
+
+ wasm = []
+ if "wasm" in flags:
+ wasm.append("emconfigure")
+
+ run([*wasm, "/bin/sh", "../configure"] + configure_args + [f"--prefix={prefix}"], cwd=cwd)
+
+
+def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
+ if cmake_dir is None:
+ P = ".."
+ else:
+ P = cmake_dir
+
+ wasm = []
+ if "wasm" in flags:
+ wasm.append("emcmake")
+
+ run([*wasm, "cmake", P, *cmake_args, f"-DCMAKE_BUILD_TYPE={BUILD_CFG}"], cwd=cwd)
+
+
+def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
+ """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.")
+
+ # 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:
+ run([git, "checkout", revision], cwd=target_dir)
+
+
+def build_dependency(name, mode, build_tool_args, download_url, download_name, download_tool=download_tool_default, revision=None, patch=None, additional_files={}, no_append_name=False, **kwargs):
+ """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
+ linker flags."""
+ 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)
+
+ logger.info(f"\rFetching {name}... ")
+
+ if download_tool == download_tool_py:
+ if no_append_name:
+ url = download_url
+ else:
+ url = os.path.join(download_url, download_name)
+
+ download_path = os.path.join(build_dir, download_name)
+ if not os.path.exists(download_path):
+ urlretrieve(url, os.path.join(build_dir, download_path))
+ else:
+ logger.info(f"Download '{download_path}' already exists, assuming it's an undamaged download and that it has been extracted if possible, skipping")
+ elif download_tool == download_tool_git:
+ logger.info(f"\rChecking {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)
+
+ 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"
+ 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 != "."])
+ #run([tar, "--exclude=\"*/*\"", "-tf", download_name], cwd=build_dir).strip() no longer works
+ if extract_dir_name is None:
+ extract_dir_name = run([bash, "-c", f"tar -tf {download_name} 2> /dev/null | head -n 1 | cut -f1 -d /"], cwd=build_dir)
+ 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)
+
+ for path, url in additional_files.items():
+ if not os.path.exists(path):
+ urlretrieve(url, os.path.join(extract_dir, path))
+
+ if patch is not None:
+ if isinstance(patch, str):
+ patch = [patch]
+ for p in patch:
+ patch_abs = os.path.abspath(os.path.join(os.path.dirname(__file__), p))
+ if os.path.exists(patch_abs):
+ try: run(["patch", "-p1", "--batch", "--forward", "-i", patch_abs], cwd=extract_dir)
+ 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)
+
+ if mode == "ctest":
+ run(["ctest", "-S", "HDF5config.cmake,BUILD_GENERATOR=Unix", "-C", BUILD_CFG, "-V", "-O", "hdf5.log"], cwd=extract_dir)
+ run([tar, "-xf", kwargs["ctest_result"] + ".tar.gz"], cwd=os.path.join(extract_dir, 'build'))
+ shutil.copytree(
+ os.path.join(extract_dir, "build", kwargs["ctest_result"], kwargs["ctest_result_path"]),
+ os.path.join(DEPS_DIR, "install", name)
+ )
+ elif mode != "bjam":
+ extract_build_dir = os.path.join(extract_dir, "build")
+ if os.path.exists(extract_build_dir):
+ shutil.rmtree(extract_build_dir)
+ os.makedirs(extract_build_dir)
+
+ logger.info(f"\rConfiguring {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()
+ logger.info(f"\rBuilding {name}... ")
+ run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "VERBOSE=1"], cwd=extract_build_dir)
+ logger.info(f"\rInstalling {name}... ")
+ run([make, "install"], cwd=extract_build_dir)
+ logger.info(f"\rInstalled {name} \n")
+ else:
+ logger.info(f"\rConfiguring {name}...")
+ run([bash, "./bootstrap.sh"], cwd=extract_dir)
+ logger.info(f"\rBuilding {name}... ")
+ run(["./b2", f"-j{IFCOS_NUM_BUILD_PROCS}"] + build_tool_args, cwd=extract_dir, can_fail="wasm" in flags)
+ logger.info(f"\rInstalling {name}... ")
+ shutil.copytree(os.path.join(extract_dir, "boost"), os.path.join(DEPS_DIR, "install", f"boost-{BOOST_VERSION}", "boost"))
+ logger.info(f"\rInstalled {name} \n")
+
+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 "wasm" in flags:
+ ADDITIONAL_ARGS.extend(("-sWASM_BIGINT", "-fexceptions"))
+
+# If the linker supports GC sections, set it up to reduce binary file size
+# -fPIC is required for the shared libraries to work
+
+CXXFLAGS = os.environ.get("CXXFLAGS", "")
+CFLAGS = os.environ.get("CFLAGS", "")
+LDFLAGS = os.environ.get("LDFLAGS", "")
+
+ADDITIONAL_ARGS_STR = " ".join(ADDITIONAL_ARGS)
+if "wasm" not in flags and sp.call([bash, "-c", "ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev/null"]) != 0:
+ CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
+ CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
+ 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:
+ CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
+ CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
+ 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:
+ CXXFLAGS=CXXFLAGS_MINIMAL
+ CFLAGS=CFLAGS_MINIMAL
+ LDFLAGS = f"{LDFLAGS} {ADDITIONAL_ARGS_STR}"
+
+os.environ["CXXFLAGS"] = CXXFLAGS
+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,))
+
+if 'hdf5' in targets:
+ HDF5_MAJOR = ".".join(HDF5_VERSION.split(".")[:-1])
+ build_dependency(
+ name=f"hdf5-{HDF5_VERSION}",
+ mode="ctest",
+ build_tool_args=[],
+ download_url=f"https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-{HDF5_MAJOR}/hdf5-{HDF5_VERSION}/src/",
+ download_name=f"CMake-hdf5-{HDF5_VERSION}.tar.gz",
+ ctest_result=f"HDF5-{HDF5_VERSION}-{platform.system()}",
+ ctest_result_path=f"HDF_Group/HDF5/{HDF5_VERSION}"
+ )
+
+if "json" in targets:
+ json_url = f"https://github.com/nlohmann/json/releases/download/{JSON_VERSION}/json.hpp"
+ json_install_path = f"{DEPS_DIR}/install/json/nlohmann/json.hpp"
+ if not os.path.exists(os.path.dirname(json_install_path)):
+ os.makedirs(os.path.dirname(json_install_path))
+ if not os.path.exists(json_install_path):
+ urlretrieve(json_url, json_install_path)
+
+if "eigen" in targets:
+ git_clone_or_pull_repository("https://gitlab.com/libeigen/eigen.git", f"{DEPS_DIR}/install/eigen-3.3.9", revision="3.3.9")
+
+if "pcre" in targets:
+ 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}/",
+ download_name=f"pcre-{PCRE_VERSION}.tar.bz2"
+ )
+
+# 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",
+ build_tool_args=["--disable-ccache", f"--with-pcre-prefix={DEPS_DIR}/install/pcre-{PCRE_VERSION}"],
+ download_url="https://github.com/swig/swig.git",
+ download_name="swig",
+ download_tool=download_tool_git,
+ revision=f"rel-{SWIG_VERSION}"
+ )
+
+if "freetype" in targets:
+ build_dependency(
+ name=f"freetype",
+ mode="cmake",
+ build_tool_args=[
+ f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/freetype"
+ ],
+ download_url = "https://github.com/freetype/freetype",
+ download_name = "freetype2",
+ download_tool=download_tool_git,
+ )
+
+if USE_OCCT and "occ" in targets:
+ patches = []
+ if OCCT_VERSION < "7.4":
+ patches.append("./patches/occt/enable-exception-handling.patch")
+
+ if OCCT_VERSION == "7.7.1":
+ patches.append("./patches/occt/no_ExpToCasExe.patch")
+
+ 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}",
+ "-DBUILD_MODULE_Draw=0",
+ "-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off",
+ f"-D3RDPARTY_FREETYPE_DIR={DEPS_DIR}/install/freetype"
+ ],
+ download_url = "https://git.dev.opencascade.org/repos/occt.git",
+ download_name = "occt",
+ download_tool=download_tool_git,
+ patch=patches,
+ revision="V" + OCCT_VERSION.replace('.', '_')
+ )
+elif "occ" in targets:
+ build_dependency(
+ name=f"oce-{OCE_VERSION}",
+ mode="cmake",
+ build_tool_args=[
+ "-DOCE_DISABLE_TKSERVICE_FONT=ON",
+ "-DOCE_TESTING=OFF",
+ "-DOCE_BUILD_SHARED_LIB=OFF",
+ "-DOCE_DISABLE_X11=ON",
+ "-DOCE_VISUALISATION=OFF",
+ "-DOCE_OCAF=OFF",
+ f"-DOCE_INSTALL_PREFIX={DEPS_DIR}/install/oce-{OCE_VERSION}"
+ ],
+ download_url="https://github.com/tpaviot/oce/archive/",
+ download_name=f"OCE-{OCE_VERSION}.tar.gz"
+ )
+
+if "libxml2" in targets:
+ build_dependency(
+ f"libxml2-{LIBXML2_VERSION}",
+ "autoconf",
+ build_tool_args=[
+ "--without-python",
+ ENABLE_FLAG,
+ DISABLE_FLAG,
+ "--without-zlib",
+ "--without-iconv",
+ "--without-lzma"
+ ],
+ download_url="ftp://xmlsoft.org/libxml2/",
+ download_name=f"libxml2-{LIBXML2_VERSION}.tar.gz"
+ )
+
+if "OpenCOLLADA" in targets:
+ patches = ["./patches/opencollada/pr622_and_disable_subdirs.patch"]
+
+ 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}",
+ f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/OpenCOLLADA/"
+ ],
+ download_url="https://github.com/KhronosGroup/OpenCOLLADA.git",
+ download_name="OpenCOLLADA",
+ download_tool=download_tool_git,
+ patch=patches,
+ revision=OPENCOLLADA_VERSION
+ )
+
+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
+ OLD_CXX_FLAGS = os.environ["CXXFLAGS"]
+ OLD_C_FLAGS = os.environ["CFLAGS"]
+ os.environ["CXXFLAGS"] = CXXFLAGS_MINIMAL
+ 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
+ PYTHON_CONFIGURE_ARGS = []
+ if platform.system() == "Darwin":
+ PYTHON_CONFIGURE_ARGS = ["--enable-shared"]
+
+ 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}/",
+ f"Python-{PYTHON_VERSION}.tgz"
+ )
+ 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.
+ if not os.path.exists(
+ os.path.join(DEPS_DIR, "install", f"python-{PYTHON_VERSION}")
+ ):
+ raise e
+
+ 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",
+ f"link={LINK_TYPE}",
+ *toolset,
+ *map(str_concat("cxxflags"), CXXFLAGS.strip().split(' ')),
+ *map(str_concat("linkflags"), LDFLAGS.strip().split(' ')),
+ "stage", "-s", "NO_BZIP2=1"],
+ download_url=BOOST_LOCATION,
+ patch="./patches/boost/boostorg_regex_62.patch",
+ download_name=f"boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2"
+ )
+ if "wasm" in flags:
+ # only supported on nix for now
+ run(("find", ".", "-name", "*.bc", "-exec", "bash", "-c", "emar q ${1%.bc}.a $1", "bash", "{}", ";"), cwd=f"{DEPS_DIR}/install/boost-{BOOST_VERSION}/lib")
+
+if "cgal" in targets:
+ gmp_args = []
+ mpfr_args = []
+ if "wasm" in flags:
+ gmp_args.extend(("--disable-assembly", "--host", "none", "--enable-cxx"))
+ mpfr_args.extend(("--host", "none"))
+
+ build_dependency(
+ name=f"gmp-{GMP_VERSION}",
+ mode="autoconf",
+ build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, "--with-pic", *gmp_args],
+ download_url="https://ftp.gnu.org/gnu/gmp/",
+ download_name=f"gmp-{GMP_VERSION}.tar.bz2"
+ )
+
+ 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}/",
+ download_name=f"mpfr-{MPFR_VERSION}.tar.bz2"
+ )
+
+ 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",
+ f"-DMPFR_LIBRARIES={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib/libmpfr.{LIBRARY_EXT}" ,
+ 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}/",
+ "-DCGAL_HEADER_ONLY=On",
+ "-DBUILD_SHARED_LIBS=Off"
+ ],
+ download_url="https://github.com/CGAL/cgal.git",
+ download_name="cgal",
+ download_tool=download_tool_git,
+ revision=f"v{CGAL_VERSION}"
+ )
+
+if "usd" in targets:
+ build_dependency(
+ name=f"oneTBB-{TBB_VERSION}",
+ mode="cmake",
+ build_tool_args=[
+ f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/tbb-{TBB_VERSION}",
+ f"-DTBB_TEST=OFF"
+ ],
+ download_url="https://github.com/oneapi-src/oneTBB",
+ download_name="oneTBB",
+ download_tool=download_tool_git,
+ revision=f"v{TBB_VERSION}"
+ )
+
+ 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",
+ f"-DPXR_BUILD_TESTS=FALSE"
+ ],
+ download_url="https://github.com/PixarAnimationStudios/USD",
+ download_name="USD",
+ download_tool=download_tool_git,
+ revision=f"v{USD_VERSION}"
+ )
+
+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)
+
+OFF_ON = ["OFF", "ON"]
+
+cmake_args = [
+ "-DUSE_MMAP=" "OFF",
+ "-DBUILD_EXAMPLES=" "OFF",
+ "-DBUILD_SHARED_LIBS=" +OFF_ON[not BUILD_STATIC],
+ "-DBOOST_ROOT=" f"{DEPS_DIR}/install/boost-{BOOST_VERSION}",
+ "-DGLTF_SUPPORT=" "ON",
+ "-DJSON_INCLUDE_DIR=" f"{DEPS_DIR}/install/json",
+ "-DEIGEN_DIR=" f"{DEPS_DIR}/install/eigen-3.3.9",
+ "-DBoost_NO_BOOST_CMAKE=" "On",
+ "-DADD_COMMIT_SHA=" +("On" if ADD_COMMIT_SHA else "Off")
+]
+
+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")
+
+if "cgal" in targets:
+ cmake_args.extend([
+ "-DCGAL_INCLUDE_DIR=" f"{DEPS_DIR}/install/cgal-{CGAL_VERSION}/include",
+ "-DGMP_INCLUDE_DIR=" f"{DEPS_DIR}/install/gmp-{GMP_VERSION}/include",
+ "-DGMP_LIBRARY_DIR=" f"{DEPS_DIR}/install/gmp-{GMP_VERSION}/lib",
+ "-DMPFR_INCLUDE_DIR=" f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
+ "-DMPFR_LIBRARY_DIR=" f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib",
+ ])
+
+if "occ" in targets and USE_OCCT:
+ occ_include_dir = f"{DEPS_DIR}/install/occt-{OCCT_VERSION}/include/opencascade"
+ occ_library_dir = f"{DEPS_DIR}/install/occt-{OCCT_VERSION}/lib"
+ cmake_args.extend([
+ "-DOCC_INCLUDE_DIR=" +occ_include_dir,
+ "-DOCC_LIBRARY_DIR=" +occ_library_dir
+ ])
+
+elif "occ" in targets:
+ 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
+ ])
+
+if "OpenCOLLADA" in targets:
+ cmake_args.extend([
+ "-DOPENCOLLADA_INCLUDE_DIR=" f"{DEPS_DIR}/install/OpenCOLLADA/include/opencollada",
+ "-DOPENCOLLADA_LIBRARY_DIR=" f"{DEPS_DIR}/install/OpenCOLLADA/lib/opencollada"
+ ])
+else:
+ cmake_args.extend([
+ "-DCOLLADA_SUPPORT=" "Off",
+ ])
+
+if "pcre" in targets:
+ cmake_args.append(
+ "-DPCRE_LIBRARY_DIR=" f"{DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib"
+ )
+
+if "libxml2" in targets:
+ cmake_args.extend([
+ "-DLIBXML2_INCLUDE_DIR=" f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2",
+ "-DLIBXML2_LIBRARIES=" f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}"
+ ])
+
+if "hdf5" in targets:
+ cmake_args.extend([
+ "-DHDF5_INCLUDE_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/include",
+ "-DHDF5_LIBRARY_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/lib"
+ ])
+else:
+ cmake_args.append("-DHDF5_SUPPORT=Off")
+
+if "usd" in targets:
+ cmake_args.extend([
+ "-DUSD_SUPPORT=" "On",
+ "-DUSD_INCLUDE_DIR=" f"{DEPS_DIR}/install/usd-{USD_VERSION}/include",
+ "-DUSD_LIBRARY_DIR=" f"{DEPS_DIR}/install/usd-{USD_VERSION}/lib"
+ ])
+
+if not explicit_targets or {"IfcGeom", "IfcConvert", "IfcGeomServer"} & set(explicit_targets):
+ logger.info("\rConfiguring executables...")
+
+ exec_args = [
+ "-DBUILD_IFCGEOM=" +OFF_ON["IfcGeom" in targets],
+ "-DBUILD_GEOMSERVER=" +OFF_ON["IfcGeomServer" in targets],
+ "-DBUILD_CONVERT=" +OFF_ON["IfcConvert" in targets],
+ "-DBUILD_IFCPYTHON=" "OFF",
+ "-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell",
+ ]
+
+ run_cmake("", exec_args + cmake_args, cmake_dir=CMAKE_DIR, cwd=executables_dir)
+
+ logger.info("\rBuilding executables... ")
+
+ run([make, f"-j{IFCOS_NUM_BUILD_PROCS}"], cwd=executables_dir)
+ 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":
+ ADDITIONAL_ARGS = "-Wl,-flat_namespace,-undefined,suppress"
+
+ if "wasm" in flags:
+ ADDITIONAL_ARGS = f"-Wl,-undefined,suppress -sSIDE_MODULE=2 -sEXPORTED_FUNCTIONS=_PyInit__ifcopenshell_wrapper"
+
+ 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)
+
+ def compile_python_wrapper(python_version, python_library, python_include, python_executable):
+ logger.info(f"\rConfiguring python {python_version} wrapper...")
+
+ cache_path = os.path.join(python_dir, "CMakeCache.txt")
+ if os.path.exists(cache_path):
+ os.remove(cache_path)
+
+ os.environ["PYTHON_LIBRARY_BASENAME"] = os.path.basename(python_library)
+
+ swig_when_built = []
+ if "swig" in targets:
+ swig_when_built.append(f"-DSWIG_EXECUTABLE={DEPS_DIR}/install/swig/bin/swig")
+
+ run_cmake("",
+ cmake_args + [
+ "-DPYTHON_LIBRARY=" +python_library,
+ *([f"-DPYTHON_EXECUTABLE={python_executable}"] if python_executable else []),
+ # *([f"-DPYTHON_MODULE_INSTALL_DIR={os.environ['PYTHONPATH']}/ifcopenshell"] if "wasm" in flags else []),
+ *(["-DPYTHON_MODULE_INSTALL_DIR="+os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "package"))] if "wasm" in flags else []),
+ "-DPYTHON_INCLUDE_DIR=" +python_include,
+ "-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell/tmp",
+ "-DUSERSPACE_PYTHON_PREFIX=" +["Off", "On"][os.environ.get("PYTHON_USER_SITE", "").lower() in {"1", "on", "true"}],
+ *swig_when_built],
+ cmake_dir=CMAKE_DIR, cwd=python_dir)
+
+ logger.info(f"\rBuilding python {python_version} wrapper... ")
+
+ run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "_ifcopenshell_wrapper"], cwd=python_dir)
+ run([make, "install/local"], cwd=os.path.join(python_dir, "ifcwrap"))
+
+ if python_executable:
+ module_dir = os.path.dirname(run([python_executable, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"]))
+
+ if platform.system() != "Darwin":
+ # TODO: This symbol name depends on the Python version?
+ run([strip, "-s", "-K", "PyInit__ifcopenshell_wrapper", "_ifcopenshell_wrapper.so"], cwd=module_dir)
+
+ return module_dir
+
+ if "wasm" in flags:
+ compile_python_wrapper(
+ f"{os.environ['PYMAJOR']}.{os.environ['PYMINOR']}.{os.environ['PYMICRO']}",
+ f"{os.environ['TARGETINSTALLDIR']}/lib/libpython{os.environ['PYMAJOR']}.{os.environ['PYMINOR']}.a",
+ os.environ['PYTHONINCLUDE'],
+ None
+ )
+
+ elif USE_CURRENT_PYTHON_VERSION:
+ python_info = sysconfig.get_paths()
+
+ py_path_components = [
+ sysconfig.get_config_var('LIBDIR'),
+ sysconfig.get_config_var("INSTSONAME")
+ ]
+
+ if sysconfig.get_config_var('multiarchsubdir'):
+ py_path_components.insert(1, sysconfig.get_config_var('multiarchsubdir').replace("/", ""))
+
+ python_lib = os.path.join(*py_path_components)
+
+ compile_python_wrapper(platform.python_version(), python_lib, python_info["include"], sys.executable)
+ else:
+ for python_version in PYTHON_VERSIONS:
+ python_library = run([bash, "-c", f"ls {DEPS_DIR}/install/python-{python_version}/lib/libpython*.*"])
+ python_include = run([bash, "-c", f"ls -d {DEPS_DIR}/install/python-{python_version}/include/python*"])
+ python_executable = os.path.join(DEPS_DIR, "install", f"python-{python_version}", "bin", f"python{python_version[0]}")
+
+ module_dir = compile_python_wrapper(python_version, python_library, python_include, python_executable)
+ run([cp, "-R", module_dir, os.path.join(DEPS_DIR, "install", "ifcopenshell", f"python-{python_version}")])
+
+logger.info("\rBuilt IfcOpenShell...\n\n")
diff --git a/win/build-deps.cmd b/win/build-deps.cmd
index 9d5ac6f07a..81645f1495 100644
--- a/win/build-deps.cmd
+++ b/win/build-deps.cmd
@@ -478,6 +478,47 @@ set DEPENDENCY_NAME=Eigen
set DEPENDENCY_DIR=%INSTALL_DIR%\%DEPENDENCY_NAME%
call :GitCloneAndCheckoutRevision https://gitlab.com/libeigen/eigen.git "%DEPENDENCY_DIR%" 3.3.9
+:tbb
+set DEPENDENCY_NAME=tbb
+set DEPENDENCY_DIR=%DEPS_DIR%\tbb
+call :GitCloneAndCheckoutRevision https://github.com/oneapi-src/oneTBB.git "%DEPENDENCY_DIR%" v2021.9.0
+IF NOT %ERRORLEVEL%==0 GOTO :Error
+cd "%DEPENDENCY_DIR%"
+:: git reset --hard
+:: git apply --ignore-whitespace "%~dp0patches\cgal_no_zlib.patch"
+call :RunCMake -DCMAKE_INSTALL_PREFIX="%INSTALL_DIR%\tbb" ^
+ -DTBB_TEST=OFF
+IF NOT %ERRORLEVEL%==0 GOTO :Error
+call :BuildSolution "%DEPENDENCY_DIR%\%BUILD_DIR%\oneTBB.sln" %BUILD_CFG%
+IF NOT %ERRORLEVEL%==0 GOTO :Error
+call :InstallCMakeProject "%DEPENDENCY_DIR%\%BUILD_DIR%" %BUILD_CFG%
+IF NOT %ERRORLEVEL%==0 GOTO :Error
+
+:usd
+set DEPENDENCY_NAME=usd
+set DEPENDENCY_DIR=%DEPS_DIR%\usd
+call :GitCloneAndCheckoutRevision https://github.com/PixarAnimationStudios/USD.git "%DEPENDENCY_DIR%" v23.05
+IF NOT %ERRORLEVEL%==0 GOTO :Error
+cd "%DEPENDENCY_DIR%"
+:: git reset --hard
+:: git apply --ignore-whitespace "%~dp0patches\cgal_no_zlib.patch"
+call :RunCMake -DCMAKE_INSTALL_PREFIX="%INSTALL_DIR%\usd" ^
+ -DBOOST_ROOT="%DEPS_DIR%\boost_%BOOST_VER%" ^
+ -DTBB_ROOT_DIR="%INSTALL_DIR%\tbb" ^
+ -DPXR_ENABLE_PYTHON_SUPPORT=FALSE ^
+ -DPXR_ENABLE_GL_SUPPORT=FALSE ^
+ -DPXR_BUILD_IMAGING=FALSE ^
+ -DPXR_BUILD_TUTORIALS=FALSE ^
+ -DPXR_BUILD_EXAMPLES=FALSE ^
+ -DPXR_BUILD_USD_TOOLS=FALSE ^
+ -DPXR_BUILD_TESTS=FALSE ^
+ -DBOOST_LIBRARYDIR="%DEPS_DIR%\boost_%BOOST_VER%\stage\vs%VS_VER%-%VS_PLATFORM%\lib"
+IF NOT %ERRORLEVEL%==0 GOTO :Error
+call :BuildSolution "%DEPENDENCY_DIR%\%BUILD_DIR%\USD.sln" %BUILD_CFG%
+IF NOT %ERRORLEVEL%==0 GOTO :Error
+call :InstallCMakeProject "%DEPENDENCY_DIR%\%BUILD_DIR%" %BUILD_CFG%
+IF NOT %ERRORLEVEL%==0 GOTO :Error
+
:Successful
echo.
call "%~dp0\utils\cecho.cmd" 0 10 "%PROJECT_NAME% dependencies built."