diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml new file mode 100644 index 0000000000..a496a66688 --- /dev/null +++ b/.github/workflows/wheel.yml @@ -0,0 +1,416 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright Contributors to the OpenImageIO Project. +# + +name: Wheel + +permissions: + contents: read + id-token: write + +on: + push: + # Workflow run on tags for v2.6 and v3 only. + tags: + - v2.6.* + - v3.* + pull_request: + # Workflow run on pull_request only when related files change. + branches-ignore: + - dev-* + - lg-* + - rtd + - sonar-1 + paths: + - .github/workflows/wheel.yml + - pyproject.toml + - src/python/OpenImageIO/*.py + - src/cmake/pythonutils.cmake + schedule: + # Nightly build + - cron: "0 8 * * *" + workflow_dispatch: + # This allows manual triggering of the workflow from the web + +jobs: + # Linux jobs run in Docker containers (manylinux), so the latest OS version + # is OK. macOS and Windows jobs need to be locked to specific virtual + # environment versions to mitigate issues from OS updates, and will require + # maintenance as OS versions are retired. + + # --------------------------------------------------------------------------- + # Source Distribution + # --------------------------------------------------------------------------- + + sdist: + name: Build SDist + runs-on: ubuntu-latest + # Don't run on OIIO forks + if: | + github.event_name != 'schedule' || + github.repository == 'AcademySoftwareFoundation/OpenImageIO' + + steps: + + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Build SDist + run: pipx run build --sdist + + - name: Check metadata + run: pipx run twine check dist/* + + - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: cibw-sdist + path: dist/*.tar.gz + + # --------------------------------------------------------------------------- + # Linux Wheels + # --------------------------------------------------------------------------- + + linux: + name: Build wheels on Linux + runs-on: ubuntu-latest + # Don't run on OIIO forks + if: | + github.event_name != 'schedule' || + github.repository == 'AcademySoftwareFoundation/OpenImageIO' + strategy: + matrix: + include: + # ------------------------------------------------------------------- + # CPython 64 bits manylinux_2_28 + # ------------------------------------------------------------------- + - build: CPython 3.8 64 bits manylinux_2_28 + manylinux: manylinux_2_28 + python: cp38-manylinux_x86_64 + arch: x86_64 + - build: CPython 3.9 64 bits manylinux_2_28 + manylinux: manylinux_2_28 + python: cp39-manylinux_x86_64 + arch: x86_64 + - build: CPython 3.10 64 bits manylinux_2_28 + manylinux: manylinux_2_28 + python: cp310-manylinux_x86_64 + arch: x86_64 + - build: CPython 3.11 64 bits manylinux_2_28 + manylinux: manylinux_2_28 + python: cp311-manylinux_x86_64 + arch: x86_64 + - build: CPython 3.12 64 bits manylinux_2_28 + manylinux: manylinux_2_28 + python: cp312-manylinux_x86_64 + arch: x86_64 + - build: CPython 3.13 64 bits manylinux_2_28 + manylinux: manylinux_2_28 + python: cp313-manylinux_x86_64 + arch: x86_64 + # ------------------------------------------------------------------- + # CPython 64 bits manylinux2014 + # ------------------------------------------------------------------- + - build: CPython 3.8 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp38-manylinux_x86_64 + arch: x86_64 + - build: CPython 3.9 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp39-manylinux_x86_64 + arch: x86_64 + - build: CPython 3.10 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp310-manylinux_x86_64 + arch: x86_64 + - build: CPython 3.11 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp311-manylinux_x86_64 + arch: x86_64 + - build: CPython 3.12 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp312-manylinux_x86_64 + arch: x86_64 + - build: CPython 3.13 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp313-manylinux_x86_64 + arch: x86_64 + + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + name: Install Python + with: + python-version: '3.9' + + - name: Build wheels + uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 + env: + CIBW_BUILD: ${{ matrix.python }} + CIBW_ARCHS: ${{ matrix.arch }} + CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} + + - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: cibw-wheels-${{ matrix.python }}-${{ matrix.manylinux }} + path: ./wheelhouse/*.whl + + # --------------------------------------------------------------------------- + # Linux ARM Wheels + # --------------------------------------------------------------------------- + + linux-arm: + name: Build wheels on Linux ARM + runs-on: ubuntu-latest + # Don't run on OIIO forks + if: | + github.event_name != 'schedule' || + github.repository == 'AcademySoftwareFoundation/OpenImageIO' + strategy: + matrix: + include: + # ------------------------------------------------------------------- + # CPython ARM 64 bits manylinux2014 + # ------------------------------------------------------------------- + - build: CPython 3.8 ARM 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp38-manylinux_aarch64 + arch: aarch64 + - build: CPython 3.9 ARM 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp39-manylinux_aarch64 + arch: aarch64 + - build: CPython 3.10 ARM 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp310-manylinux_aarch64 + arch: aarch64 + - build: CPython 3.11 ARM 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp311-manylinux_aarch64 + arch: aarch64 + - build: CPython 3.12 ARM 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp312-manylinux_aarch64 + arch: aarch64 + - build: CPython 3.13 ARM 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp313-manylinux_aarch64 + arch: aarch64 + + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + name: Install Python + with: + python-version: '3.9' + + - name: Set up QEMU + uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 + with: + platforms: all + + - name: Build wheels + uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 + env: + CIBW_BUILD: ${{ matrix.python }} + CIBW_ARCHS: ${{ matrix.arch }} + CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.manylinux }} + # Apparently, the x86_64 runners aren't able to execute tests + # tests for ARM wheels. + # TODO: Re-enable tests when linux ARM runners are available + CIBW_TEST_SKIP: '*' + + - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: cibw-wheels-${{ matrix.python }}-${{ matrix.manylinux }} + path: ./wheelhouse/*.whl + + # --------------------------------------------------------------------------- + # macOS Wheels + # --------------------------------------------------------------------------- + + macos: + name: Build wheels on macOS + runs-on: macos-13 + # Don't run on OIIO forks + if: | + github.event_name != 'schedule' || + github.repository == 'AcademySoftwareFoundation/OpenImageIO' + strategy: + matrix: + include: + # ------------------------------------------------------------------- + # CPython 64 bits + # ------------------------------------------------------------------- + - build: CPython 3.8 64 bits + python: cp38-macosx_x86_64 + arch: x86_64 + - build: CPython 3.9 64 bits + python: cp39-macosx_x86_64 + arch: x86_64 + - build: CPython 3.10 64 bits + python: cp310-macosx_x86_64 + arch: x86_64 + - build: CPython 3.11 64 bits + python: cp311-macosx_x86_64 + arch: x86_64 + - build: CPython 3.12 64 bits + python: cp312-macosx_x86_64 + arch: x86_64 + - build: CPython 3.13 64 bits + python: cp313-macosx_x86_64 + arch: x86_64 + + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + name: Install Python + with: + python-version: '3.9' + + - name: Build wheels + uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 + env: + CIBW_BUILD: ${{ matrix.python }} + CIBW_ARCHS: ${{ matrix.arch }} + # TODO: Re-enable HEIF when we provide a build recipe that does + # not include GPL-licensed dynamic libraries. + USE_Libheif: 'OFF' + + - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: cibw-wheels-${{ matrix.python }} + path: ./wheelhouse/*.whl + + # --------------------------------------------------------------------------- + # macOS ARM Wheels + # --------------------------------------------------------------------------- + + macos-arm: + name: Build wheels on macOS ARM + runs-on: macos-14 + # Don't run on OIIO forks + if: | + github.event_name != 'schedule' || + github.repository == 'AcademySoftwareFoundation/OpenImageIO' + strategy: + matrix: + include: + # ------------------------------------------------------------------- + # CPython ARM 64 bits + # ------------------------------------------------------------------- + - build: CPython 3.8 ARM 64 bits + python: cp38-macosx_arm64 + arch: arm64 + - build: CPython 3.9 ARM 64 bits + python: cp39-macosx_arm64 + arch: arm64 + - build: CPython 3.10 ARM 64 bits + python: cp310-macosx_arm64 + arch: arm64 + - build: CPython 3.11 ARM 64 bits + python: cp311-macosx_arm64 + arch: arm64 + - build: CPython 3.12 ARM 64 bits + python: cp312-macosx_arm64 + arch: arm64 + - build: CPython 3.13 ARM 64 bits + python: cp313-macosx_arm64 + arch: arm64 + + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + name: Install Python + # https://cibuildwheel.pypa.io/en/stable/faq/#macos-building-cpython-38-wheels-on-arm64 + with: + python-version: '3.8' + + + - name: Build wheels + uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 + env: + CIBW_BUILD: ${{ matrix.python }} + CIBW_ARCHS: ${{ matrix.arch }} + #TODO: Re-enable the PNG plugin until we can figure out why the system PNG is causing problems + ENABLE_PNG: 'OFF' + + - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: cibw-wheels-${{ matrix.python }} + path: ./wheelhouse/*.whl + + # --------------------------------------------------------------------------- + # Windows Wheels + # --------------------------------------------------------------------------- + + windows: + name: Build wheels on Windows + runs-on: windows-2022 + # Don't run on OIIO forks + if: | + github.event_name != 'schedule' || + github.repository == 'AcademySoftwareFoundation/OpenImageIO' + strategy: + matrix: + include: + # ------------------------------------------------------------------- + # CPython 64 bits + # ------------------------------------------------------------------- + - build: CPython 3.8 64 bits + python: cp38-win_amd64 + arch: AMD64 + - build: CPython 3.9 64 bits + python: cp39-win_amd64 + arch: AMD64 + - build: CPython 3.10 64 bits + python: cp310-win_amd64 + arch: AMD64 + - build: CPython 3.11 64 bits + python: cp311-win_amd64 + arch: AMD64 + - build: CPython 3.12 64 bits + python: cp312-win_amd64 + arch: AMD64 + - build: CPython 3.13 64 bits + python: cp313-win_amd64 + arch: AMD64 + + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + name: Install Python + with: + python-version: '3.9' + + - name: Build wheels + uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 + env: + CIBW_BUILD: ${{ matrix.python }} + CIBW_ARCHS: ${{ matrix.arch }} + + - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: cibw-wheels-${{ matrix.python }} + path: ./wheelhouse/*.whl + + + upload_pypi: + needs: [sdist, linux, linux-arm, macos, macos-arm, windows] + runs-on: ubuntu-latest + permissions: + id-token: write + if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v3') + steps: + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + + - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + pattern: cibw-* + path: dist + merge-multiple: true + + - uses: pypa/gh-action-pypi-publish@897895f1e160c830e369f9779632ebc134688e1b # release/v1 \ No newline at end of file diff --git a/.gitignore b/.gitignore index c8c897e967..a9dca689b3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,13 +4,17 @@ branches/ build/ dist/ ext/ +wheelhouse/ +_coverage/ +.python-version .idea .vscode .cproject .project .DS_Store *.pyc -_coverage/ +/*.lock +gastest.o # Exclude test files I tend to leave around at the top level. The leading # slash ensures that files with these extensions within subdirectories are not @@ -21,3 +25,5 @@ _coverage/ /*.jxl /*.tx /*.log + + diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e8acc7041..9e1e9ba05a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,8 @@ set (PLUGIN_SEARCH_PATH "" CACHE STRING "Default plugin search path") file (TO_NATIVE_PATH "${PLUGIN_SEARCH_PATH}" PLUGIN_SEARCH_PATH_NATIVE) set (CMAKE_DEBUG_POSTFIX "_d" CACHE STRING "Library naming postfix for Debug builds") + + if (CMAKE_UNITY_BUILD_BATCH_SIZE) set (UNITY_SMALL_BATCH_SIZE "${CMAKE_UNITY_BUILD_BATCH_SIZE}" CACHE STRING "Unity batch mode size for expensive files") else () @@ -169,6 +171,38 @@ include (compiler) # Dependency finding utilities and all dependency-related options include (dependency_utils) +option (IGNORE_HOMEBREWED_DEPS "If ON, will ignore homebrew-installed dependencies" OFF) +if (IGNORE_HOMEBREWED_DEPS) + # Define the list of prefixes to ignore + set (HOMEBREW_PREFIXES + /opt/homebrew + /usr/local + /usr/X11 + /usr/X11R6 + /opt/X11 + ) + message (STATUS "Ignoring Homebrew dependencies and adjusted environment and CMake variables accordingly.") + foreach (_cmake_var + CMAKE_SYSTEM_INCLUDE_PATH + CMAKE_SYSTEM_LIBRARY_PATH + CMAKE_PREFIX_PATH) + remove_prefixes_from_variable (CMAKE ${_cmake_var} "${HOMEBREW_PREFIXES}") + endforeach () + + # Adjust CMAKE_IGNORE_PATH + foreach (_prefix IN LISTS HOMEBREW_PREFIXES) + list (APPEND CMAKE_IGNORE_PATH + "${_prefix}" + "${_prefix}/lib" + "${_prefix}/bin" + "${_prefix}/include" + ) + endforeach () + + message (STATUS "CMAKE_IGNORE_PATH: ${CMAKE_IGNORE_PATH}") +endif () + + # Utilities and options related to finding python and making python bindings include (pythonutils) @@ -246,10 +280,16 @@ if (NOT EMBEDPLUGINS AND NOT BUILD_OIIOUTIL_ONLY) endforeach () endif () -if (USE_PYTHON AND Python3_Development_FOUND AND NOT BUILD_OIIOUTIL_ONLY) + +if (WIN32) + set (_py_dev_found Python3_Development_FOUND) +else () + set (_py_dev_found Python3_Development.Module_FOUND) +endif () +if (USE_PYTHON AND ${_py_dev_found} AND NOT BUILD_OIIOUTIL_ONLY) add_subdirectory (src/python) else () - message (STATUS "Not building Python bindings: USE_PYTHON=${USE_PYTHON}, Python3_Development_FOUND=${Python3_Development_FOUND}") + message (STATUS "Not building Python bindings: USE_PYTHON=${USE_PYTHON}, Python3_Development.Module_FOUND=${Python3_Development.Module_FOUND}") endif () add_subdirectory (src/include) diff --git a/INSTALL.md b/INSTALL.md index 6af6de9950..6e4b436920 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -230,19 +230,20 @@ Make targets you should know about: Additionally, a few helpful modifiers alter some build-time options: -| Target | Command | -| ------------------------- | ---------------------------------------------- | -| make VERBOSE=1 ... | Show all compilation commands | -| make STOP_ON_WARNING=0 | Do not stop building if compiler warns | -| make EMBEDPLUGINS=0 ... | Don't compile the plugins into libOpenImageIO | -| make USE_OPENGL=0 ... | Skip anything that needs OpenGL | -| make USE_QT=0 ... | Skip anything that needs Qt | -| make MYCC=xx MYCXX=yy ... | Use custom compilers | -| make USE_PYTHON=0 ... | Don't build the Python binding | -| make BUILD_SHARED_LIBS=0 | Build static library instead of shared | -| make LINKSTATIC=1 ... | Link with static external libraries when possible | -| make SOVERSION=nn ... | Include the specified major version number in the shared object metadata | -| make NAMESPACE=name | Wrap everything in another namespace | +| Target | Command | +| ----------------------------- | ------------------------------------------------------------------------- | +| make VERBOSE=1 ... | Show all compilation commands | +| make STOP_ON_WARNING=0 | Do not stop building if compiler warns | +| make EMBEDPLUGINS=0 ... | Don't compile the plugins into libOpenImageIO | +| make USE_OPENGL=0 ... | Skip anything that needs OpenGL | +| make USE_QT=0 ... | Skip anything that needs Qt | +| make MYCC=xx MYCXX=yy ... | Use custom compilers | +| make USE_PYTHON=0 ... | Don't build the Python binding | +| make BUILD_SHARED_LIBS=0 | Build static library instead of shared | +| make IGNORE_HOMEBREWED_DEPS=1 | Ignore homebrew-managed dependencies | +| make LINKSTATIC=1 ... | Link with static external libraries when possible | +| make SOVERSION=nn ... | Include the specified major version number in the shared object metadata | +| make NAMESPACE=name | Wrap everything in another namespace | The command 'make help' will list all possible options. @@ -357,6 +358,28 @@ produce a dynamic-linked version. 3. Execute the PowerShell command from where vcpkg is located in directory. ``vcpkg install openimageio`` + +Python-based Builds and Installs +-------------------------------- + +**Installing from prebuilt binary distributions** + +If you're only interested in the Python module and the CLI tools, you can install with `pip` or `uv`: + +> ```pip install OpenImageIO``` + +**Building and installing from source** + +If you have a C++ compiler installed, you can also use the Python build-backend to compile and install +from source by navigating to the root of the repository and running: ```pip install .``` + +This will download and install CMake and Ninja if necessary, and invoke the CMake build system; which, +in turn, will build missing dependencies, compile OIIO, and install the Python module, the libraries, +the headers, and the CLI tools to a platform-specific, Python-specific location. + +See the [scikit-build-core docs](https://scikit-build-core.readthedocs.io/en/latest/configuration.html#configuring-cmake-arguments-and-defines) +for more information on customizing and overriding build-tool options and CMake arguments. + Test Images ----------- diff --git a/Makefile b/Makefile index 40c8898d22..211fefd4a6 100644 --- a/Makefile +++ b/Makefile @@ -378,6 +378,7 @@ help: @echo " USE_NUKE=0 Don't build Nuke plugins" @echo " Nuke_ROOT=path Custom Nuke installation" @echo " NUKE_VERSION=ver Custom Nuke version" + @echo " IGNORE_HOMEBREWED_DEPS=1 Don't use dependencies installed by Homebrew" @echo " OIIO build-time options:" @echo " INSTALL_PREFIX=path Set installation prefix (default: ./${INSTALL_PREFIX})" @echo " NAMESPACE=name Override namespace base name (default: OpenImageIO)" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..53d27c5912 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,122 @@ +[project] +name = "OpenImageIO" +# The build backend ascertains the version from the CMakeLists.txt file. +dynamic = ["version"] +description = "Reading, writing, and processing images in a wide variety of file formats, using a format-agnostic API, aimed at VFX applications." +authors = [ + {name = "Larry Gritz", email = "lg@larrygritz.com"}, + {name = "OpenImageIO Contributors", email = "oiio-dev@lists.aswf.io"} +] +maintainers = [ + {name = "OpenImageIO Contributors", email="oiio-dev@lists.aswf.io"}, +] +readme = "README.md" +license = {text = "Apache-2.0"} +classifiers = [ + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "License :: OSI Approved :: Apache Software License", + "Topic :: Multimedia :: Graphics", + "Topic :: Multimedia :: Video", + "Topic :: Multimedia :: Video :: Display", + "Topic :: Software Development :: Libraries :: Python Modules", +] +requires-python = ">= 3.8" + +[project.urls] +Homepage = "https://openimageio.org/" +Source = "https://github.com/AcademySoftwareFoundation/OpenImageIO" +Documentation = "https://docs.openimageio.org" +Issues = "https://github.com/AcademySoftwareFoundation/OpenImageIO/issues" + + +[project.scripts] +# Use the convention below to expose CLI tools as Python scripts. +maketx = "OpenImageIO:_command_line" +oiiotool = "OpenImageIO:_command_line" + +[build-system] +build-backend = "scikit_build_core.build" +requires = [ + "scikit-build-core>=0.10.6,<1", + "pybind11", +] + +[tool.scikit-build] +build.verbose = true +# Exclude unnecessary directories from the source distribution. +sdist.exclude = [".github", "testsuite", "ASWF", "docs"] +# Pin minimum scikit-build-core to that specified in build-system.requires. +minimum-version = "build-system.requires" +# Pin minimum CMake version to that specified in CMakeLists.txt. +cmake.version = "CMakeLists.txt" +wheel.license-files = ["LICENSE.md", "THIRD-PARTY.md"] +# Make sure the package is structured as expected. +wheel.install-dir = "OpenImageIO" +# Only install the user and fonts components. +install.components = ["user", "fonts"] + +[tool.scikit-build.cmake.define] +# Build missing dependencies. See src/cmake for details. +OpenImageIO_BUILD_MISSING_DEPS = "all" +# Don't build tests. Dramatically improves build time. +OIIO_BUILD_TESTS = "0" +# Prefer linking static dependencies when possible. +LINKSTATIC = "1" +# Standardize the install directory for libraries, as expected by +# other parts of the wheels build process. +CMAKE_INSTALL_LIBDIR = "lib" + +# Dynamically set the package version metadata by pasrsing CMakeLists.txt. +[tool.scikit-build.metadata.version] +provider = "scikit_build_core.metadata.regex" +input = "CMakeLists.txt" +regex = 'set \(OpenImageIO_VERSION "(?P[0-9a-z.]+)"\)' + +# On macOS, ensure dependencies are only built for the target architecture. +[[tool.scikit-build.overrides]] +if.platform-system = "darwin" +if.platform-machine = "arm64" +inherit.cmake.define = "append" +cmake.define.CMAKE_OSX_ARCHITECTURES = "arm64" + +[[tool.scikit-build.overrides]] +if.platform-system = "darwin" +if.platform-machine = "x86_64" +inherit.cmake.define = "append" +cmake.define.CMAKE_OSX_ARCHITECTURES = "x86_64" + +[tool.cibuildwheel] +build-verbosity = 1 +skip = [ + # Skip 32-bit builds + "*-win32", + "*-manylinux_i686", + # Building with musl seems to work, but the repair-wheel step seems to fail... + # This may be a bug in repairwheel (or auditwheel)? + "*musllinux*", +] +test-command = "oiiotool --buildinfo" + +[tool.cibuildwheel.macos.environment] +SKBUILD_CMAKE_ARGS = "-DLINKSTATIC=1; -DIGNORE_HOMEBREWED_DEPS=1" +# C++17 - std::filesystem is only available in macOS 10.15 and later; ARM compatibility introduced in 11. +MACOSX_DEPLOYMENT_TARGET = "11" +# Optimize for size (not speed). +SKBUILD_CMAKE_BUILD_TYPE = "MinSizeRel" + +[tool.cibuildwheel.linux.environment] +SKBUILD_CMAKE_ARGS = "-DLINKSTATIC=1" +# Suppress warnings that cause linux cibuildwheel build to fail +CXXFLAGS = "-Wno-error=stringop-overflow -Wno-pragmas" +SKBUILD_CMAKE_BUILD_TYPE = "MinSizeRel" + +[tool.cibuildwheel.windows.environment] +SKBUILD_CMAKE_BUILD_TYPE = "MinSizeRel" \ No newline at end of file diff --git a/src/build-scripts/build_libtiff.bash b/src/build-scripts/build_libtiff.bash index 763fb4bc75..e069175cba 100755 --- a/src/build-scripts/build_libtiff.bash +++ b/src/build-scripts/build_libtiff.bash @@ -45,6 +45,7 @@ if [[ -z $DEP_DOWNLOAD_ONLY ]]; then -Dtiff-tests=${LIBTIFF_BUILD_TESTS:-OFF} \ -Dtiff-docs=${LIBTIFF_BUILD_TESTS:-OFF} \ -Dlibdeflate=ON \ + -Dwebp=OFF \ ${LIBTIFF_BUILDOPTS} time cmake --build build --target install fi diff --git a/src/cmake/build_OpenColorIO.cmake b/src/cmake/build_OpenColorIO.cmake index 6e772245f0..c66adc0dba 100644 --- a/src/cmake/build_OpenColorIO.cmake +++ b/src/cmake/build_OpenColorIO.cmake @@ -39,6 +39,8 @@ build_dependency_with_cmake(OpenColorIO -D OCIO_BUILD_TESTS=OFF -D OCIO_USE_OIIO_FOR_APPS=OFF -D OCIO_INSTALL_DOCS=OFF + # Disable versioning in OCIO shared library name + -D OCIO_USE_SOVERSION=OFF # Make OCIO build all its dependencies statically -D OCIO_INSTALL_EXT_PACKAGES=MISSING # Give the library a custom name and symbol namespace so it can't diff --git a/src/cmake/build_libdeflate.cmake b/src/cmake/build_libdeflate.cmake index 50543699ce..a7d46b7741 100644 --- a/src/cmake/build_libdeflate.cmake +++ b/src/cmake/build_libdeflate.cmake @@ -9,7 +9,7 @@ set_cache (libdeflate_BUILD_VERSION 1.20 "libdeflate version for local builds") set (libdeflate_GIT_REPOSITORY "https://github.com/ebiggers/libdeflate") set (libdeflate_GIT_TAG "v${libdeflate_BUILD_VERSION}") -set_cache (libdeflate_BUILD_SHARED_LIBS ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} +set_cache (libdeflate_BUILD_SHARED_LIBS OFF # ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} DOC "Should a local libdeflate build, if necessary, build shared libraries" ADVANCED) string (MAKE_C_IDENTIFIER ${libdeflate_BUILD_VERSION} libdeflate_VERSION_IDENT) @@ -20,6 +20,7 @@ build_dependency_with_cmake(libdeflate GIT_TAG ${libdeflate_GIT_TAG} CMAKE_ARGS -D BUILD_SHARED_LIBS=${libdeflate_BUILD_SHARED_LIBS} + -D LIBDEFLATE_BUILD_SHARED_LIB=${libdeflate_BUILD_SHARED_LIBS} -D CMAKE_POSITION_INDEPENDENT_CODE=ON -D CMAKE_INSTALL_LIBDIR=lib -D LIBDEFLATE_BUILD_GZIP=OFF @@ -32,6 +33,7 @@ set (libdeflate_ROOT ${libdeflate_LOCAL_INSTALL_DIR}) # Signal to caller that we need to find again at the installed location set (libdeflate_REFIND TRUE) set (libdeflate_REFIND_ARGS CONFIG) +set (libdeflate_REFIND_VERSION ${libdeflate_BUILD_VERSION}) if (libdeflate_BUILD_SHARED_LIBS) install_local_dependency_libs (libdeflate libdeflate) diff --git a/src/cmake/compiler.cmake b/src/cmake/compiler.cmake index b3a602ddc8..a1a6895084 100644 --- a/src/cmake/compiler.cmake +++ b/src/cmake/compiler.cmake @@ -655,7 +655,12 @@ if (CMAKE_SKIP_RPATH) unset (CMAKE_INSTALL_RPATH) else () if (NOT CMAKE_INSTALL_RPATH) - set (CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_FULL_LIBDIR}") + if(APPLE) + set(BASEPOINT @loader_path) + else() + set(BASEPOINT $ORIGIN) + endif() + set (CMAKE_INSTALL_RPATH ${BASEPOINT} ${BASEPOINT}/${CMAKE_INSTALL_LIBDIR}) endif () # add the automatically determined parts of the RPATH that # point to directories outside the build tree to the install RPATH @@ -679,9 +684,25 @@ set (CMAKE_EXPORT_COMPILE_COMMANDS ON) # install_targets (target1 [target2 ...]) # macro (install_targets) - install (TARGETS ${ARGN} + # Define options that can be passed to the macro + set(options NAMELINK_SKIP) + + # Parse the arguments passed to the macro + cmake_parse_arguments(INSTALL_TARGETS "${options}" "" "" "${ARGN}") + + # Check if NAMELINK_SKIP is specified + if (INSTALL_TARGETS_NAMELINK_SKIP) + # Set namelink_option to NAMELINK_SKIP if specified + set(namelink_option NAMELINK_SKIP) + else() + # Leave namelink_option empty if NAMELINK_SKIP is not specified + set(namelink_option) + endif() + + # Use the install command with the appropriate options + install (TARGETS ${INSTALL_TARGETS_UNPARSED_ARGUMENTS} EXPORT ${PROJ_NAME}_EXPORTED_TARGETS RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT user - LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT user + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT user ${namelink_option} ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT developer) endmacro() diff --git a/src/cmake/dependency_utils.cmake b/src/cmake/dependency_utils.cmake index 07433018dd..a30ddff47d 100644 --- a/src/cmake/dependency_utils.cmake +++ b/src/cmake/dependency_utils.cmake @@ -483,6 +483,74 @@ endmacro() + +# Function: remove_prefixes_from_variable +# Removes specified directory prefixes from a given environment variable or CMake variable. +# +# Parameters: +# VAR_TYPE - Type of variable to modify: +# 'ENV' for environment variables +# 'CMAKE' for CMake variables +# VAR_NAME - Name of the variable to modify. +# PREFIXES - List of directory prefixes to remove from the variable's value. +# +# Description: +# This function updates the specified variable by removing any paths that start +# with the given prefixes. It is useful for excluding certain directories +# (e.g., Homebrew paths) from environment variables or CMake variables to prevent +# unintended dependencies during the build process. +# +# Usage Example: +# remove_prefixes_from_variable(ENV LD_LIBRARY_PATH "/opt/homebrew" "/usr/local") +# remove_prefixes_from_variable(CMAKE CMAKE_PREFIX_PATH "${HOMEBREW_PREFIXES}") +function(remove_prefixes_from_variable VAR_TYPE VAR_NAME PREFIXES) + # Retrieve the original value based on the variable type + if(VAR_TYPE STREQUAL "ENV") + if(DEFINED ENV{${VAR_NAME}}) + set(_original_value "$ENV{${VAR_NAME}}") + else() + return() + endif() + elseif(VAR_TYPE STREQUAL "CMAKE") + if(DEFINED ${VAR_NAME}) + set(_original_value "${${VAR_NAME}}") + else() + return() + endif() + else() + message(FATAL_ERROR "Invalid VAR_TYPE: ${VAR_TYPE}. Expected 'ENV' or 'CMAKE'.") + endif() + + # Convert the variable value into a list of paths + string(REPLACE ":" ";" _path_list "${_original_value}") + + foreach(_prefix ${PREFIXES}) + # Normalize the prefix path + file(TO_CMAKE_PATH "${_prefix}" _norm_prefix) + foreach(_path IN LISTS _path_list) + # Normalize paths to avoid issues with different formats + file(TO_CMAKE_PATH "${_path}" _norm_path) + # Check if the normalized path starts with the normalized prefix + string(FIND "${_norm_path}" "${_norm_prefix}" _pos) + if(_pos EQUAL 0) + list(REMOVE_ITEM _path_list "${_path}") + endif() + endforeach() + endforeach() + + # Convert the list back to the appropriate separator + string(REPLACE ";" ":" _new_value "${_path_list}") + + # Update the variable based on the variable type + if(VAR_TYPE STREQUAL "ENV") + set(ENV{${VAR_NAME}} "${_new_value}") + message(STATUS "${ColorYellow}Updated environment variable ${VAR_NAME}: ${_new_value}") + else() + set(${VAR_NAME} "${_new_value}" PARENT_SCOPE) + message(STATUS "${ColorYellow}Updated CMake variable ${VAR_NAME}: ${_new_value}") + endif() +endfunction() + # Helper to build a dependency with CMake. Given a package name, git repo and # tag, and optional cmake args, it will clone the repo into the surrounding # project's build area, configures, and build sit, and installs it into a @@ -546,6 +614,13 @@ macro (build_dependency_with_cmake pkgname) ) endif () + # Make sure to inherit CMAKE_IGNORE_PATH + set(_pkg_CMAKE_ARGS ${_pkg_CMAKE_ARGS} ${_pkg_CMAKE_ARGS}) + if (CMAKE_IGNORE_PATH) + string(REPLACE ";" "\\;" CMAKE_IGNORE_PATH_ESCAPED "${CMAKE_IGNORE_PATH}") + list(APPEND _pkg_CMAKE_ARGS "-DCMAKE_IGNORE_PATH=${CMAKE_IGNORE_PATH_ESCAPED}") + endif() + execute_process (COMMAND ${CMAKE_COMMAND} # Put things in our special local build areas @@ -595,7 +670,7 @@ macro (install_local_dependency_libs pkgname libname) "${${pkgname}_LOCAL_INSTALL_DIR}/lib/*${libname}*" "${${pkgname}_LOCAL_INSTALL_DIR}/lib/${${PROJECT_NAME}_DEPENDENCY_BUILD_TYPE}/*${libname}*" ) - install (FILES ${_lib_files} TYPE LIB) + install (FILES ${_lib_files} TYPE LIB COMPONENT user) # message("${pkgname}_LOCAL_INSTALL_DIR = ${${pkgname}_LOCAL_INSTALL_DIR}") # message(" lib files = ${_lib_files}") if (WIN32) @@ -605,7 +680,7 @@ macro (install_local_dependency_libs pkgname libname) "${${pkgname}_LOCAL_INSTALL_DIR}/bin/${${PROJECT_NAME}_DEPENDENCY_BUILD_TYPE}/*${libname}*.dll" ) # message(" dll files = ${_lib_files}") - install (FILES ${_lib_files} TYPE BIN) + install (FILES ${_lib_files} TYPE BIN COMPONENT user) endif () unset (_lib_files) endmacro () diff --git a/src/cmake/fancy_add_executable.cmake b/src/cmake/fancy_add_executable.cmake index d517370ea9..82a8e704d1 100644 --- a/src/cmake/fancy_add_executable.cmake +++ b/src/cmake/fancy_add_executable.cmake @@ -53,6 +53,12 @@ macro (fancy_add_executable) target_link_libraries (${_target_NAME} PRIVATE ${_target_LINK_LIBRARIES}) target_link_libraries (${_target_NAME} PRIVATE ${PROFILER_LIBRARIES}) set_target_properties (${_target_NAME} PROPERTIES FOLDER ${_target_FOLDER}) + if (SKBUILD) + # The installed bin and lib directories are at the same level, so we + # need to set the rpath to look for the libraries in ../lib + set_target_properties (${_target_NAME} PROPERTIES + INSTALL_RPATH "$,@loader_path,$ORIGIN>/../${CMAKE_INSTALL_LIBDIR}") + endif () check_is_enabled (INSTALL_${_target_NAME} _target_NAME_INSTALL_enabled) if (CMAKE_UNITY_BUILD AND UNITY_BUILD_MODE STREQUAL GROUP) set_source_files_properties(${_target_SRC} PROPERTIES diff --git a/src/cmake/pythonutils.cmake b/src/cmake/pythonutils.cmake index c5f4720400..33c1584f54 100644 --- a/src/cmake/pythonutils.cmake +++ b/src/cmake/pythonutils.cmake @@ -32,13 +32,24 @@ macro (find_python) list (APPEND _req EXACT) endif () endif () + + # Support building on manylinux docker images, which do not contain + # the Development.Embedded component. + # https://pybind11.readthedocs.io/en/stable/compiling.html#findpython-mode + if (WIN32) + set (_py_components Interpreter Development) + else () + set (_py_components Interpreter Development.Module) + endif () + checked_find_package (Python3 ${PYTHON_VERSION} ${_req} VERSION_MIN 3.7 - COMPONENTS Interpreter Development + COMPONENTS ${_py_components} PRINT Python3_VERSION Python3_EXECUTABLE Python3_LIBRARIES Python3_Development_FOUND + Python3_Development.Module_FOUND Python3_Interpreter_FOUND ) # The version that was found may not be the default or user @@ -129,6 +140,10 @@ macro (setup_python_module) # SUFFIX ".pyd") # endif() + if (SKBUILD) + set (PYTHON_SITE_DIR .) + endif () + # In the build area, put it in lib/python so it doesn't clash with the # non-python libraries of the same name (which aren't prefixed by "lib" # on Windows). @@ -141,7 +156,7 @@ macro (setup_python_module) RUNTIME DESTINATION ${PYTHON_SITE_DIR} COMPONENT user LIBRARY DESTINATION ${PYTHON_SITE_DIR} COMPONENT user) - install(FILES __init__.py DESTINATION ${PYTHON_SITE_DIR}) + install(FILES __init__.py DESTINATION ${PYTHON_SITE_DIR} COMPONENT user) endmacro () diff --git a/src/iconvert/CMakeLists.txt b/src/iconvert/CMakeLists.txt index 735b88def5..89b1d7da3d 100644 --- a/src/iconvert/CMakeLists.txt +++ b/src/iconvert/CMakeLists.txt @@ -2,4 +2,9 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO -fancy_add_executable (LINK_LIBRARIES OpenImageIO) +# Don't build if installing as a Python wheel +if (SKBUILD) + return () +endif () + +fancy_add_executable (LINK_LIBRARIES OpenImageIO) \ No newline at end of file diff --git a/src/idiff/CMakeLists.txt b/src/idiff/CMakeLists.txt index 735b88def5..89b1d7da3d 100644 --- a/src/idiff/CMakeLists.txt +++ b/src/idiff/CMakeLists.txt @@ -2,4 +2,9 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO -fancy_add_executable (LINK_LIBRARIES OpenImageIO) +# Don't build if installing as a Python wheel +if (SKBUILD) + return () +endif () + +fancy_add_executable (LINK_LIBRARIES OpenImageIO) \ No newline at end of file diff --git a/src/igrep/CMakeLists.txt b/src/igrep/CMakeLists.txt index 735b88def5..bb909597b2 100644 --- a/src/igrep/CMakeLists.txt +++ b/src/igrep/CMakeLists.txt @@ -2,4 +2,9 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +# Don't build if installing as a Python wheel +if (SKBUILD) + return () +endif () + fancy_add_executable (LINK_LIBRARIES OpenImageIO) diff --git a/src/iinfo/CMakeLists.txt b/src/iinfo/CMakeLists.txt index 735b88def5..bb909597b2 100644 --- a/src/iinfo/CMakeLists.txt +++ b/src/iinfo/CMakeLists.txt @@ -2,4 +2,9 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +# Don't build if installing as a Python wheel +if (SKBUILD) + return () +endif () + fancy_add_executable (LINK_LIBRARIES OpenImageIO) diff --git a/src/iv/CMakeLists.txt b/src/iv/CMakeLists.txt index 30b2fa6bd7..4063e32b97 100644 --- a/src/iv/CMakeLists.txt +++ b/src/iv/CMakeLists.txt @@ -4,6 +4,12 @@ set (OIIO_IV_EXTRA_IV_LIBRARIES "" CACHE STRING "Paths to extra libraries to force iv to link against") + +# Don't build if installing as a Python wheel +if (SKBUILD) + return () +endif () + set (CMAKE_AUTOMOC ON) set (CMAKE_AUTOMOC_PATH_PREFIX OFF) if (Qt5_POSITION_INDEPENDENT_CODE OR Qt6_POSITION_INDEPENDENT_CODE) diff --git a/src/libOpenImageIO/CMakeLists.txt b/src/libOpenImageIO/CMakeLists.txt index 1dd60922c7..98b654edcb 100644 --- a/src/libOpenImageIO/CMakeLists.txt +++ b/src/libOpenImageIO/CMakeLists.txt @@ -229,7 +229,12 @@ if (UNIX AND NOT APPLE) endif () set_target_properties (OpenImageIO PROPERTIES LINK_FLAGS ${OpenImageIO_LINK_FLAGS}) -install_targets (OpenImageIO) +if (SKBUILD) + install_targets (NAMELINK_SKIP OpenImageIO ) +else () + install_targets (OpenImageIO) +endif () + # Testing diff --git a/src/libutil/CMakeLists.txt b/src/libutil/CMakeLists.txt index 941c1bbc9b..9af7e28a54 100644 --- a/src/libutil/CMakeLists.txt +++ b/src/libutil/CMakeLists.txt @@ -104,8 +104,16 @@ function (setup_oiio_util_library targetname) set_target_properties (${targetname} PROPERTIES LINK_FLAGS ${OpenImageIO_Util_LINK_FLAGS}) + + if (SKBUILD) + set (PYTHON_SITE_DIR .) + set_target_properties (${targetname} PROPERTIES + INSTALL_RPATH "$,@loader_path,$ORIGIN>/${CMAKE_INSTALL_LIBDIR}") + install_targets (NAMELINK_SKIP ${targetname}) + else () + install_targets (${targetname}) + endif () - install_targets (${targetname}) endfunction() diff --git a/src/python/__init__.py b/src/python/__init__.py index f3c345ec13..2a7a6e9e71 100644 --- a/src/python/__init__.py +++ b/src/python/__init__.py @@ -2,16 +2,47 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO -import os, sys, platform +import os +import sys +import platform +import subprocess -# This works around the python 3.8 change to stop loading DLLs from PATH on Windows. -# We reproduce the old behaviour by manually tokenizing PATH, checking that the directories exist and are not ".", -# then add them to the DLL load path. -# This behviour can be disabled by setting the environment variable "OIIO_LOAD_DLLS_FROM_PATH" to "0" -if sys.version_info >= (3, 8) and platform.system() == "Windows" and os.getenv("OIIO_LOAD_DLLS_FROM_PATH", "1") == "1": - for path in os.getenv("PATH", "").split(os.pathsep): - if os.path.exists(path) and path != ".": - os.add_dll_directory(path) +_here = os.path.abspath(os.path.dirname(__file__)) -from .OpenImageIO import * +# Set $OpenImageIO_ROOT if not already set *before* importing the OpenImageIO module. +if not os.getenv("OpenImageIO_ROOT"): + if all([os.path.exists(os.path.join(_here, i)) for i in ["share", "bin", "lib"]]): + os.environ["OpenImageIO_ROOT"] = _here +if platform.system() == "Windows": + # Python wheel module is dynamically linked to the OIIO DLL present in the bin folder. + _bin_dir = os.path.join(_here, "bin") + if os.path.exists(_bin_dir): + os.add_dll_directory(_bin_dir) + elif sys.version_info >= (3, 8): + # This works around the python 3.8 change to stop loading DLLs from PATH on Windows. + # We reproduce the old behavior by manually tokenizing PATH, checking that the + # directories exist and are not ".", then add them to the DLL load path. + # Set the environment variable "OIIO_LOAD_DLLS_FROM_PATH" to "0" to disable this behavior. + if os.getenv("OIIO_LOAD_DLLS_FROM_PATH", "1") == "1": + for path in os.getenv("PATH", "").split(os.pathsep): + if os.path.exists(path) and path != ".": + os.add_dll_directory(path) + +from .OpenImageIO import * # type: ignore # noqa: F401, F403, E402 + +__doc__ = """ +OpenImageIO is a toolset for reading, writing, and manipulating image files of +any image file format relevant to VFX / animation via a format-agnostic API +with a feature set, scalability, and robustness needed for feature film +production. +"""[1:-1] + + +def _call_program(name, args): + bin_dir = os.path.join(os.path.dirname(__file__), 'bin') + return subprocess.call([os.path.join(bin_dir, name)] + args) + +def _command_line(): + name = os.path.basename(sys.argv[0]) + raise SystemExit(_call_program(name, sys.argv[1:])) diff --git a/src/testtex/CMakeLists.txt b/src/testtex/CMakeLists.txt index 5cecb6ce79..89d111dcd3 100644 --- a/src/testtex/CMakeLists.txt +++ b/src/testtex/CMakeLists.txt @@ -8,6 +8,10 @@ # be local to this directory and not override properly by command line `-D`.) set (ENABLE_INSTALL_testtex OFF PARENT_SCOPE) +if (SKBUILD) + return () +endif () + fancy_add_executable (NAME testtex INCLUDE_DIRS ${ROBINMAP_INCLUDES} LINK_LIBRARIES OpenImageIO )