mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-28 15:53:00 +00:00
7845f8e8cd
`geom.tree().select()` silently returns zero results (or raises SWIG's "An unknown error occurred") in the Linux release packages, while the same commit built from source returns correct answers. `select_box()` agrees between both, and geometry conversion is bit-identical -- only operations that touch a stored TopoDS_Shape diverge. Cause is linkage, not code.8bdaa8c7cnarrowed the Rocky builds from `--shared` to `--ifcopenshell-shared`, which shares IfcOpenShell's own libraries but leaves every dependency static. OCCT is then compiled privately into each plug-in that uses it -- 15 of them, verified by their own copies of the BRepClass3d/BRepExtrema/Standard_Failure strings. That contradicts what the binaries already declare. tree.h:1748 casts a `conversion_result_shape*` to `open_cascade_shape*`, moves the TopoDS_Shape out of it and frees it; `open_cascade_shape` is defined once in ifcopenshell_geometry_kernel_opencascade.so and left undefined in ifcopenshell_geometry_tree_opencascade_brep.so for the loader to resolve. So the two plug-ins are designed to share one OCCT-based type system, yet static linking gives each its own Standard_Type registry and allocator. Shapes get read and released by a different OCCT instance than made them. Add `--opencascade-shared`, mirroring the existing `--ifcopenshell-shared` precedent, and use it on both Rocky workflows. It cannot be spelled `--occt-shared`: build-all.py parses any `occt-*` flag as a version override. BUILD_STATIC drives three things at once -- dependency link type, -fvisibility=hidden, and BUILD_SHARED_LIBS -- so making one dependency shared means overriding all three for it. Visibility is the subtle one: OCCT's Standard_EXPORT expands to nothing on Unix, so it relies on default visibility to export its API. Built shared under -fvisibility=hidden it exports almost nothing and its own libraries cannot resolve against each other (libTKMath.so fails to find NCollection_BaseAllocator::CommonBaseAllocator in libTKernel.so). Static archives are immune, which is why this surfaces only once OCCT goes shared. Compile OCCT with the pre-visibility flag set instead. Link the OCCT set with --as-needed. FindOpenCASCADE.cmake's config branch uses OCCT's *complete* module list, Visualization included, which against a static OCCT costs nothing -- an unreferenced module contributes no objects. Against a shared OCCT all 47 become hard DT_NEEDED entries, and libTKV3d pulls libGL.so.1 + libEGL.so.1, so `import ifcopenshell` fails on any headless machine with "libEGL.so.1: cannot open shared object file" even though nothing ever opens a window. Measured through the real find_package path with the LINK_GROUP workaround composed: 67 DT_NEEDED without the flag, 3 with it, TKV3d and TKOpenGl gone. The flag is deliberately left open rather than closed with -Wl,--no-as-needed. CMake emits the imported targets' INTERFACE_LINK_LIBRARIES -- where OCCT lists libGL/libEGL -- after that item, so closing the bracket switches the flag off immediately before the libraries it exists to exclude. Verified against the shipped artifact: closed, the kernel plug-in fell from 47 DT_NEEDED libTK entries to 14 and lost TKV3d, yet still carried a direct libEGL.so.1 and still failed to import on a headless server; open, the same 14 remain and libGL/libEGL are gone. None of the 14 retained modules depends on GL. Put the shared OCCT on LD_LIBRARY_PATH for the build itself. Nothing else points at it -- IfcOpenShell's libraries get INSTALL_RPATH=$ORIGIN and OCCT sits in its own dependency prefix -- so the post-build `import ifcopenshell` check fails the same way. This is build-time only; the shipped packages get libTK*.so* staged beside the payload with an $ORIGIN RUNPATH instead. Suffix OCCT's install directory with `-shared` when it applies. Static and shared installs are not interchangeable, but `build_dependency` skips any dependency whose install dir already exists and cache_dependencies.py keys its tarballs purely on that directory name -- so the static `cache-occt-7.8.1.tar.gz` restored from the build-outputs repo silently satisfied the build and BUILD_LIBRARY_TYPE was never applied. This is the same cache stickiness8bdaa8c7cdescribed, pointing the other way. The suffix makes the key configuration-aware, so it self-invalidates and the static tarball stays valid for builds that still want static. Packaging is the other half, and is why8bdaa8c7cbacked the flag out -- `stage_runtime_payload` only copies from install/ifcopenshell, so OCCT in install/occt-* was never staged and `--shared` "worked by accident" off cached static outputs. Stage libTK*.so* alongside, then give every staged library an $ORIGIN RUNPATH: the core libs currently carry dead build-machine RPATHs and the plug-ins carry none, resolving only because the Python wrapper pulls them in by SONAME first. Shared OCCT has no such first loader, since it is reached through the dlopen'd plug-ins. The packages shrink: the python zip goes from 109.4 MB to 85.6 MB, because the duplicated OCCT was 113 MB of the 287 MB unpacked payload (the eight geometry_writer_ifc* plug-ins alone were 4.6 MB each) against ~67 MB for one shared copy. Same argument asa91b1da28("Reduce Rocky package size") and402591e71. macOS and Windows are affected too but are not fixed here. Their packaging resolves via @loader_path install names and would need install_name_tool rewriting, which cannot be verified from Linux; adding the flag without that would ship a package that fails to load. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
227 lines
8.6 KiB
CMake
227 lines
8.6 KiB
CMake
#
|
|
# Input variables:
|
|
# - `OCC_INCLUDE_DIR`
|
|
# - `OCC_LIBRARY_DIR`
|
|
# If input variables are not specified, try to find OpenCASCADE config.
|
|
# Input variables could also be provided as environment variables.
|
|
#
|
|
# Output variables
|
|
# - `OpenCASCADE_LIBRARIES`
|
|
#
|
|
|
|
UNIFY_ENVVARS_AND_CACHE(OCC_INCLUDE_DIR)
|
|
UNIFY_ENVVARS_AND_CACHE(OCC_LIBRARY_DIR)
|
|
|
|
if(OCC_INCLUDE_DIR)
|
|
set(OCC_INCLUDE_DIR ${OCC_INCLUDE_DIR} CACHE FILEPATH "Open CASCADE header files")
|
|
message(STATUS "Looking for Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
|
|
endif()
|
|
|
|
if(OCC_LIBRARY_DIR)
|
|
set(OCC_LIBRARY_DIR ${OCC_LIBRARY_DIR} CACHE FILEPATH "Open CASCADE library files")
|
|
message(STATUS "Looking for Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
|
|
endif()
|
|
|
|
if(NOT OCC_INCLUDE_DIR AND NOT OCC_LIBRARY_DIR)
|
|
# OCE is not supported for find_package, because it's using a different name (`oce`)
|
|
# and also has an odd directory structure (install/lib/oce-0.18/*.cmake).
|
|
# find_package creates variables:
|
|
# - `OpenCASCADE_INCLUDE_DIR`
|
|
# - `OpenCASCADE_LIBRARIES`
|
|
|
|
# OpenCASCADE may be built with VTK support. Try to find VTK first to avoid
|
|
# CMake errors when OpenCASCADE's config references VTK targets.
|
|
find_package(VTK QUIET)
|
|
mark_as_advanced(VTK_DIR)
|
|
|
|
find_package(OpenCASCADE CONFIG REQUIRED)
|
|
mark_as_advanced(OpenCASCADE_DIR)
|
|
message(STATUS "Found OpenCASCADE config: ${OpenCASCADE_DIR}")
|
|
|
|
if(OpenCASCADE_VERSION VERSION_LESS "7.7.0")
|
|
# cmake configs < 7.7.0 were not adding include directories to targets automatically.
|
|
set_target_properties(TKernel PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${OpenCASCADE_INCLUDE_DIR}")
|
|
endif()
|
|
|
|
if(
|
|
OpenCASCADE_VERSION VERSION_LESS "7.9.0"
|
|
AND CMAKE_VERSION GREATER_EQUAL "3.24"
|
|
AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU"
|
|
)
|
|
# Before 7.9.0 targets in OCCT cmake configs are not linked to each other
|
|
# leading to missing symbols on Unix. Link them as a single group as a workaround.
|
|
# Only needed for gcc, because other compilers (e.g. Apple Clang, MSVC) do rescan automatically.
|
|
set(OpenCASCADE_LIBRARIES "$<LINK_GROUP:RESCAN,${OpenCASCADE_LIBRARIES}>")
|
|
endif()
|
|
|
|
if(UNIX AND NOT APPLE)
|
|
# Record only the OCCT modules whose symbols are actually referenced.
|
|
#
|
|
# OpenCASCADE_LIBRARIES is OCCT's *complete* module list, Visualization
|
|
# included -- TKV3d, TKOpenGl, TKService. Against a static OCCT that
|
|
# costs nothing, because an unreferenced module contributes no objects.
|
|
# Against a shared OCCT every entry becomes a hard DT_NEEDED, and
|
|
# libTKV3d pulls libGL.so.1 + libEGL.so.1, so `import ifcopenshell`
|
|
# dies on any headless machine with
|
|
# ImportError: libEGL.so.1: cannot open shared object file
|
|
# even though IfcOpenShell never opens a window. Measured on a full
|
|
# 67-module link: 67 DT_NEEDED entries without the flag, 3 with it,
|
|
# and TKV3d/TKOpenGl gone in the second case.
|
|
#
|
|
# Goes before the LINK_GROUP above rather than inside it, and is a
|
|
# no-op for a static OCCT (--as-needed only governs shared libraries),
|
|
# so this is safe for both link types.
|
|
#
|
|
# Deliberately NOT closed with -Wl,--no-as-needed. CMake emits the
|
|
# imported targets' INTERFACE_LINK_LIBRARIES -- which is where OCCT
|
|
# lists libGL/libEGL -- *after* this item, so a closing bracket
|
|
# switches the flag back off just before the libraries it was added to
|
|
# exclude. Measured: with the bracket closed the plug-ins dropped from
|
|
# 47 DT_NEEDED libTK entries to 14 and lost TKV3d, yet still carried a
|
|
# direct libEGL.so.1 and failed to import on a headless server; with it
|
|
# left open the same 14 remain and libGL/libEGL are gone.
|
|
#
|
|
# The cost is that --as-needed stays in effect for whatever follows on
|
|
# the link line. That is the default on Debian/Ubuntu so it is
|
|
# well-trodden, but it does mean a library needed only for
|
|
# static-initialiser side effects could be dropped -- the thing to
|
|
# suspect first if a serialiser stops registering itself.
|
|
set(OpenCASCADE_LIBRARIES "-Wl,--as-needed" "${OpenCASCADE_LIBRARIES}")
|
|
endif()
|
|
|
|
if(OpenCASCADE_VERSION VERSION_LESS "7.9.0" AND WIN32)
|
|
# Bug in OCCT cmake configs < 7.9.0 - missing linked library.
|
|
list(APPEND OpenCASCADE_LIBRARIES WSOCK32.lib)
|
|
endif()
|
|
|
|
return()
|
|
endif()
|
|
|
|
# No specific paths specified, try to find package.
|
|
if(OCC_INCLUDE_DIR AND OCC_LIBRARY_DIR)
|
|
message(
|
|
STATUS
|
|
"Using provided OCC_INCLUDE_DIR ('${OCC_INCLUDE_DIR}') "
|
|
"and OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}')."
|
|
)
|
|
# Parse OCC_VERSION_STRING.
|
|
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAJOR REGEX "#define OCC_VERSION_MAJOR.*")
|
|
string(REGEX MATCH "[0-9]+" OCC_MAJOR ${OCC_MAJOR})
|
|
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MINOR REGEX "#define OCC_VERSION_MINOR.*")
|
|
string(REGEX MATCH "[0-9]+" OCC_MINOR ${OCC_MINOR})
|
|
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAINT REGEX "#define OCC_VERSION_MAINTENANCE.*")
|
|
string(REGEX MATCH "[0-9]+" OCC_MAINT ${OCC_MAINT})
|
|
set(OCC_VERSION_STRING "${OCC_MAJOR}.${OCC_MINOR}.${OCC_MAINT}")
|
|
else()
|
|
message(
|
|
FATAL_ERROR
|
|
"Couldn't find Open CASCADE installation. "
|
|
"Either both OCC_INCLUDE_DIR ('${OCC_INCLUDE_DIR}') and OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}') "
|
|
"must be specified or OpenCASCADE package should be discoverable. "
|
|
"If you're using OCE, then providing a package is not available "
|
|
"and you need to provide OCE_INCLUDE_DIR and OCE_LIBRARY_DIR directly."
|
|
)
|
|
endif()
|
|
|
|
set(OpenCASCADE_LIBRARIES
|
|
TKernel
|
|
TKMath
|
|
TKBRep
|
|
TKGeomBase
|
|
TKGeomAlgo
|
|
TKG3d
|
|
TKG2d
|
|
TKShHealing
|
|
TKTopAlgo
|
|
TKMesh
|
|
TKPrim
|
|
TKBool
|
|
TKBO
|
|
TKFillet
|
|
TKXSBase
|
|
TKOffset
|
|
TKHLR
|
|
# @todo investigate the exact conditions when this is necessary
|
|
TKBin
|
|
)
|
|
|
|
if(OCC_VERSION_STRING VERSION_LESS 7.8.0)
|
|
list(
|
|
APPEND OpenCASCADE_LIBRARIES
|
|
TKIGES
|
|
TKSTEPBase
|
|
TKSTEPAttr
|
|
TKSTEP209
|
|
TKSTEP
|
|
)
|
|
else(OCC_VERSION_STRING VERSION_LESS 7.8.0)
|
|
list(APPEND OpenCASCADE_LIBRARIES TKDESTEP TKDEIGES)
|
|
endif(OCC_VERSION_STRING VERSION_LESS 7.8.0)
|
|
|
|
find_library(libTKernel NAMES TKernel TKerneld PATHS ${OCC_LIBRARY_DIR} NO_DEFAULT_PATH)
|
|
|
|
if(libTKernel)
|
|
message(STATUS "Required Open Cascade Library files found")
|
|
else()
|
|
message(FATAL_ERROR "Unable to find Open Cascade library files in OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}'), aborting")
|
|
endif()
|
|
|
|
if(MSVC)
|
|
add_definitions(-DHAVE_NO_DLL)
|
|
add_debug_variants(OpenCASCADE_LIBRARIES "${OpenCASCADE_LIBRARIES}" d)
|
|
endif()
|
|
|
|
if(WIN32)
|
|
# OCC might require linking to Winsock depending on the version and build configuration
|
|
list(APPEND OpenCASCADE_LIBRARIES ws2_32.lib)
|
|
endif()
|
|
|
|
# Make sure cross-referenced symbols between static OCC libraries get
|
|
# resolved. Also add thread and rt libraries.
|
|
get_filename_component(libTKernelExt ${libTKernel} EXT)
|
|
if("${libTKernelExt}" STREQUAL ".a")
|
|
set(OCCT_STATIC ON)
|
|
endif()
|
|
|
|
if(OCCT_STATIC)
|
|
find_package(Threads)
|
|
|
|
if(WASM_BUILD)
|
|
set(OpenCASCADE_LIBRARIES ${OpenCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
|
else()
|
|
# OpenCASCADE_LIBRARIES repeated N times below in order to fix cyclic dependencies
|
|
# tfk: --start-group ... --end-group didn't work on the apple linker when last tested
|
|
if(APPLE)
|
|
set(OpenCASCADE_LIBRARIES
|
|
${OpenCASCADE_LIBRARIES}
|
|
${OpenCASCADE_LIBRARIES}
|
|
${OpenCASCADE_LIBRARIES}
|
|
${OpenCASCADE_LIBRARIES}
|
|
${OpenCASCADE_LIBRARIES}
|
|
${CMAKE_THREAD_LIBS_INIT}
|
|
)
|
|
else()
|
|
set(OpenCASCADE_LIBRARIES
|
|
-Wl,--start-group
|
|
${OpenCASCADE_LIBRARIES}
|
|
-Wl,--end-group
|
|
${CMAKE_THREAD_LIBS_INIT}
|
|
)
|
|
endif()
|
|
endif()
|
|
|
|
if(NOT APPLE AND NOT WIN32)
|
|
set(OpenCASCADE_LIBRARIES ${OpenCASCADE_LIBRARIES} "rt")
|
|
endif()
|
|
if(NOT WIN32)
|
|
set(OpenCASCADE_LIBRARIES ${OpenCASCADE_LIBRARIES} "dl")
|
|
endif()
|
|
endif()
|
|
|
|
add_library(OpenCASCADE_INTERFACE INTERFACE)
|
|
target_include_directories(OpenCASCADE_INTERFACE INTERFACE "${OCC_INCLUDE_DIR}")
|
|
target_link_libraries(OpenCASCADE_INTERFACE INTERFACE ${OpenCASCADE_LIBRARIES})
|
|
target_link_directories(OpenCASCADE_INTERFACE INTERFACE "${OCC_LIBRARY_DIR}")
|
|
set(OpenCASCADE_LIBRARIES OpenCASCADE_INTERFACE)
|
|
install(TARGETS OpenCASCADE_INTERFACE EXPORT ${IFCOPENSHELL_EXPORT_TARGETS})
|