diff --git a/nix/build-all.py b/nix/build-all.py index a939373a75..701f1e4858 100644 --- a/nix/build-all.py +++ b/nix/build-all.py @@ -52,7 +52,7 @@ Used environment variables: - ``USE_OCCT`` - whether to use official Open CASCADE instead of Community Edition (`true` by default, any other value is considered `false`) - ``WASM_PYTHON_PATH`` - path to WASM Python installation, - used to deduce `PYMAJOR`, `PYMINOR`, `PYMICRO`, `TARGETINSTALLDIR`, `PYTHONINCLUDE`, + used to deduce `PYVERSION` (e.g. '3.13.2'), `PYTHONINCLUDE`, `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' @@ -214,14 +214,10 @@ if WASM: wasm_python_path = os.environ["WASM_PYTHON_PATH"] # Deduce version from path, assuming format .../python-X.Y.Z version_match = re.search(r"python-(\d+)\.(\d+)\.(\d+)", wasm_python_path) - if version_match: - os.environ["PYMAJOR"] = version_match.group(1) - os.environ["PYMINOR"] = version_match.group(2) - os.environ["PYMICRO"] = version_match.group(3) - os.environ["TARGETINSTALLDIR"] = wasm_python_path - os.environ["PYTHONINCLUDE"] = ( - f"{wasm_python_path}/include/python{os.environ['PYMAJOR']}.{os.environ['PYMINOR']}" - ) + assert version_match, f"Could not deduce python version from '{wasm_python_path}'" + python_version = version_match.group(1) + os.environ["PYVERSION"] = python_version + os.environ["PYTHONINCLUDE"] = f"{wasm_python_path}/include/python{python_version.rpartition('.')[0]}" os.environ["SIDE_MODULE_CFLAGS"] = "" # Required, otherwise library will compile as .a, not .so. os.environ["SIDE_MODULE_LDFLAGS"] = "-s SIDE_MODULE=1" @@ -229,12 +225,8 @@ if WASM: assert "WASM_TOOLCHAIN_FILE" in os.environ, "WASM_TOOLCHAIN_FILE must be set when WASM_PYTHON_PATH is provided" WASM_DEBUG = True required_vars = ( - "PYMAJOR", - "PYMINOR", - "PYMICRO", - # Folder where WASM-Python was installed. - # e.g '/pyodide/cpython/installs/python-3.13.2'. - "TARGETINSTALLDIR", + # E.g. '3.13.2'. + "PYVERSION", # 'include' folder in WASM-Python installation. # e.g. '/pyodide/cpython/installs/python-3.13.2/include/python3.13' "PYTHONINCLUDE", @@ -1446,7 +1438,6 @@ if "IfcOpenShell-Python" in targets: def compile_python_wrapper( python_version: str, - python_library: Union[str, None] = None, python_include: Union[str, None] = None, python_executable: Union[str, None] = None, python_path: Union[Path, None] = None, @@ -1454,7 +1445,7 @@ if "IfcOpenShell-Python" in targets: """ :return: Path to module dir if ``python_executable`` was provided, otherwise ``None``. """ - assert bool(python_path) ^ bool(python_library and python_include) + assert bool(python_path) ^ bool(python_include) logger.info(f"\rConfiguring python {python_version} wrapper...") @@ -1467,7 +1458,7 @@ if "IfcOpenShell-Python" in targets: prefix_paths.append(f"{DEPS_DIR}/install/swig") if python_path: # We couldn't just prefix PATH and have to provide all variables explicitly, - # see run-cmake.bat note for details. + # see ifcwrap/cmake for the details. python_executable = (Path(python_path) / "bin" / "python3").__str__() python_include = run( [ @@ -1476,20 +1467,8 @@ if "IfcOpenShell-Python" in targets: "import sysconfig; print(sysconfig.get_config_var('INCLUDEPY'))", ] ) - python_library = run( - [ - python_executable, - "-c", - "import sysconfig, pathlib; " - "lib_dir = pathlib.Path(sysconfig.get_config_var('LIBDIR')); " - "print((lib_dir / sysconfig.get_config_var('LIBRARY')).__str__())", - ] - ) - if platform.system() == "Darwin": - # Oddly on Mac `LIBRARY` returns .a that doesn't even exists. - python_library = Path(python_library).with_suffix(".dylib").__str__() - assert python_library and python_include + assert python_include run_cmake( "", cmake_args @@ -1498,7 +1477,6 @@ if "IfcOpenShell-Python" in targets: *([f"-DPYTHON_EXECUTABLE={python_executable}"] if python_executable else []), # Needed because pyodide is expecting setup.py to be in the root. *([f"-DPYTHON_MODULE_INSTALL_DIR={REPO_PATH}"] * WASM), - f"-DPYTHON_LIBRARY={python_library}", f"-DPYTHON_INCLUDE_DIR={python_include}", f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/ifcopenshell/tmp", "-DUSERSPACE_PYTHON_PREFIX=" @@ -1536,26 +1514,13 @@ if "IfcOpenShell-Python" in targets: 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, - ) + compile_python_wrapper(os.environ["PYVERSION"], os.environ["PYTHONINCLUDE"]) # Copy setup.py where pyodide build system expects it. shutil.copy(REPO_PATH / "pyodide" / "setup.py", REPO_PATH) 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) + compile_python_wrapper(platform.python_version(), python_info["include"], sys.executable) else: for python_version in PYTHON_VERSIONS: python_path = Path(DEPS_DIR) / "install" / f"python-{python_version}" diff --git a/src/ifcwrap/CMakeLists.txt b/src/ifcwrap/CMakeLists.txt index b216146502..c331f4c85c 100644 --- a/src/ifcwrap/CMakeLists.txt +++ b/src/ifcwrap/CMakeLists.txt @@ -46,17 +46,20 @@ IF(NOT "${PYTHON_LIBRARY}" STREQUAL "") MESSAGE(STATUS "Looking for Python library file in: ${Python_LIBRARY}") ENDIF() -# NOTE PYTHONLIBS_FOUND and PYTHONINTERP_FOUND cannot seem to be trusted so -# we need further checks to see whether the packages were actually found or not. -FIND_PACKAGE(Python COMPONENTS Development) -IF(NOT Python_Development_FOUND) +# Development.Module = headers on Unix, headers+libraries on Windows. +# Required variables: +# - Windows - Python_INCLUDE_DIR, Python_LIBRARY (MSVC doesn't allow undefined symbols at link time) +# - Unix - Python_INCLUDE_DIR +# Unfortunately all paths must be always provided explicitly, not by prefixing PATH. +# Otherwise FindPython will break on newer Python versions (list of supported versions is hardcoded). +find_package(Python COMPONENTS Development.Module) +IF(NOT Python_Development.Module_FOUND) MESSAGE(FATAL_ERROR "BUILD_IFCPYTHON enabled, but unable to find Python lib or header. Disable BUILD_IFCPYTHON or fix Python paths to proceed.") ENDIF() # Ensure version is saved here, from wasm libraries, # not from Python interpreter that might be unrelated to pyodide Python version. set(_python_libs_version "${Python_VERSION_MAJOR}${Python_VERSION_MINOR}") -INCLUDE_DIRECTORIES(${Python_INCLUDE_DIRS}) INCLUDE_DIRECTORIES(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}) SET(CMAKE_SWIG_FLAGS ${SWIG_DEFINES}) @@ -90,6 +93,7 @@ SET_PROPERTY( set(SWIG_MODULE_ifcopenshell_wrapper_EXTRA_FLAGS "-interface" "_ifcopenshell_wrapper") swig_add_library(ifcopenshell_wrapper LANGUAGE python SOURCES IfcPython.i) +swig_link_libraries(ifcopenshell_wrapper PRIVATE Python::Module) SET_PROPERTY(TARGET ${SWIG_MODULE_ifcopenshell_wrapper_REAL_NAME} PROPERTY SWIG_DEPENDS ${IFCOPENSHELL_LIBRARIES}) if (WASM_BUILD) # SIDE_MODULE=1 - add to .so all symbols from linked archives (default used by pyodide). @@ -113,9 +117,9 @@ if (WASM_BUILD) endif() if("$ENV{LDFLAGS}" MATCHES ".undefined.suppress") # On osx there is some state in the python dylib. With `-Wl,undefined,suppress` we can ignore the missing symbols at compile time. -SWIG_LINK_LIBRARIES(ifcopenshell_wrapper ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${LIBSVGFILL}) +swig_link_libraries(ifcopenshell_wrapper PRIVATE ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${LIBSVGFILL}) else() -SWIG_LINK_LIBRARIES(ifcopenshell_wrapper ${IFCOPENSHELL_LIBRARIES} ${Python_LIBRARIES} ${LIBSVGFILL}) +swig_link_libraries(ifcopenshell_wrapper PRIVATE ${IFCOPENSHELL_LIBRARIES} ${LIBSVGFILL}) endif() if ((NOT WIN32) AND BUILD_SHARED_LIBS) SET_INSTALL_RPATHS(${SWIG_MODULE_ifcopenshell_wrapper_REAL_NAME} "${IFCDIRS};${OCC_LIBRARY_DIR}") diff --git a/win/run-cmake.bat b/win/run-cmake.bat index 67de595de5..d250161407 100755 --- a/win/run-cmake.bat +++ b/win/run-cmake.bat @@ -92,10 +92,6 @@ set LIBXML2_INCLUDE_DIR=%DEPS_DIR%\OpenCOLLADA\Externals\LibXML\include set LIBXML2_LIBRARIES=%INSTALL_DIR%\OpenCOLLADA\lib\opencollada\xml.lib set HDF5_INSTALL_DIR=%INSTALL_DIR%\HDF5-%HDF5_VERSION%-win%ARCH_BITS% -:: Unfortunately we have to provide all 3 paths explicitly, -:: because if just prefix PATH, then FindPython will have an issue with newer versions of Python. -:: E.g. older FindPython that didn't s added explicit support for Python 3.14 will fail to find. -:: So setting paths expliicitly is more robust. set PYTHON_EXECUTABLE=%PYTHONHOME%\python.exe for /f "usebackq delims=" %%v in (` call "%PYTHON_EXECUTABLE%" -c "import sys; print(f'{sys.version_info[0]}{sys.version_info[1]}')"