Repository navigation
Enable pypi distribution for pythonocc #1502
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
9b4c016
f70ab5c
5fce647
7f8a19b
7e05f27
556053e
e4e14a5
655125a
5c9905a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| # pythonocc-core wheels for PyPI: Linux x86_64/aarch64, macOS x86_64/arm64, | ||
| # Windows AMD64, for CPython 3.11 to 3.15, plus the sdist. | ||
| # | ||
| # For each platform, a first job builds the native dependencies (SWIG, OCCT | ||
| # and its 3rd parties) with the scripts of ci/wheels/deps/ and saves them to | ||
| # the GitHub cache, then one job per Python version restores them and runs | ||
| # cibuildwheel (configured in pyproject.toml). Pushing a tag also uploads | ||
| # the wheels and the sdist to PyPI, through trusted publishing. | ||
|
|
||
| name: Wheels | ||
|
|
||
| # the pypi/* branches build the wheels, the review/* branches are left to | ||
| # the Azure conda pipeline. No pull_request trigger: a full build takes hours | ||
| on: | ||
| push: | ||
| branches: ["master", "pypi/**"] | ||
| # the paths filter does not apply to the tags | ||
| tags: ["*"] | ||
| paths: | ||
| - pyproject.toml | ||
| - CMakeLists.txt | ||
| - ci/wheels/** | ||
| - .github/workflows/wheels.yml | ||
| workflow_dispatch: | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| deps: | ||
| name: "${{ matrix.platform.name }}: dependencies" | ||
| runs-on: ${{ matrix.platform.os }} | ||
| timeout-minutes: 360 | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| platform: &platforms | ||
| # deps_path: the Linux tarball directory (mounted at /host in the | ||
| # manylinux container), the macOS/Windows install prefix. The | ||
| # prefix must be the same in the deps and wheels jobs. | ||
| - name: linux-x86_64 | ||
| os: ubuntu-24.04 | ||
| manylinux: quay.io/pypa/manylinux_2_28_x86_64 | ||
| deps_path: /home/runner/pyocc-deps-cache | ||
| deps_files: ci/wheels/deps/linux.sh | ||
| - name: linux-aarch64 | ||
| os: ubuntu-24.04-arm | ||
| manylinux: quay.io/pypa/manylinux_2_28_aarch64 | ||
| deps_path: /home/runner/pyocc-deps-cache | ||
| deps_files: ci/wheels/deps/linux.sh | ||
| - name: macos-x86_64 | ||
| os: macos-15-intel | ||
| arch: x86_64 | ||
| deps_path: /Users/runner/pyocc-deps | ||
| deps_files: ci/wheels/deps/macos.sh ci/wheels/triplets/x64-osx-pyocc.cmake | ||
| - name: macos-arm64 | ||
| os: macos-15 | ||
| arch: arm64 | ||
| deps_path: /Users/runner/pyocc-deps | ||
| deps_files: ci/wheels/deps/macos.sh ci/wheels/triplets/arm64-osx-pyocc.cmake | ||
| - name: windows-AMD64 | ||
| os: windows-2022 | ||
| arch: AMD64 | ||
| deps_path: C:/pyocc-deps | ||
| deps_files: ci/wheels/deps/windows.ps1 ci/wheels/triplets/x64-windows-pyocc.cmake | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
|
|
||
| - id: key | ||
| name: Dependencies cache key | ||
| shell: bash | ||
| run: | | ||
| set -eu | ||
| # bump DEPS_CACHE_VERSION to invalidate the caches | ||
| DEPS_CACHE_VERSION=1 | ||
| HASH=$(cat ${{ matrix.platform.deps_files }} | sha256sum | cut -c1-16) | ||
| echo "key=pyocc-deps-${{ matrix.platform.name }}-${DEPS_CACHE_VERSION}-${HASH}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - id: cache | ||
| uses: actions/cache@v6 | ||
| with: | ||
| path: ${{ matrix.platform.deps_path }} | ||
| key: ${{ steps.key.outputs.key }} | ||
|
|
||
| - name: Build SWIG and OCCT (manylinux container) | ||
| if: steps.cache.outputs.cache-hit != 'true' && runner.os == 'Linux' | ||
| run: | | ||
| set -eux | ||
| mkdir -p "${{ matrix.platform.deps_path }}" | ||
| docker run --rm \ | ||
| -v "$GITHUB_WORKSPACE:/project" \ | ||
| -v "${{ matrix.platform.deps_path }}:/deps-cache" \ | ||
| -e PYOCC_DEPS_CACHE=/deps-cache \ | ||
| ${{ matrix.platform.manylinux }} \ | ||
| bash /project/ci/wheels/deps/linux.sh | ||
|
|
||
| - name: Build SWIG and OCCT | ||
| if: steps.cache.outputs.cache-hit != 'true' && runner.os == 'macOS' | ||
| run: bash ci/wheels/deps/macos.sh | ||
| env: | ||
| ARCH: ${{ matrix.platform.arch }} | ||
| PYOCC_DEPS_PREFIX: ${{ matrix.platform.deps_path }} | ||
|
|
||
| - name: Build OCCT | ||
| if: steps.cache.outputs.cache-hit != 'true' && runner.os == 'Windows' | ||
| run: ./ci/wheels/deps/windows.ps1 | ||
| shell: pwsh | ||
| env: | ||
| PYOCC_DEPS_PREFIX: ${{ matrix.platform.deps_path }} | ||
|
|
||
| wheels: | ||
| name: "${{ matrix.platform.name }}: ${{ matrix.python }}" | ||
| needs: deps | ||
| runs-on: ${{ matrix.platform.os }} | ||
| timeout-minutes: 360 | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| platform: *platforms | ||
| python: [cp311, cp312, cp313, cp314, cp315] | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
|
|
||
| - id: key | ||
| name: Dependencies cache key | ||
| shell: bash | ||
| run: | | ||
| set -eu | ||
| # must match the deps job | ||
| DEPS_CACHE_VERSION=1 | ||
| HASH=$(cat ${{ matrix.platform.deps_files }} | sha256sum | cut -c1-16) | ||
| echo "key=pyocc-deps-${{ matrix.platform.name }}-${DEPS_CACHE_VERSION}-${HASH}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - uses: actions/cache/restore@v6 | ||
| with: | ||
| path: ${{ matrix.platform.deps_path }} | ||
| key: ${{ steps.key.outputs.key }} | ||
| fail-on-cache-miss: true | ||
|
|
||
| - uses: pypa/cibuildwheel@v4.3.1 | ||
| env: | ||
| CIBW_BUILD: ${{ matrix.python }}-* | ||
| CIBW_ARCHS_MACOS: ${{ matrix.platform.arch }} | ||
| # Linux: the host filesystem is mounted at /host in the container | ||
| PYOCC_DEPS_CACHE: /host${{ matrix.platform.deps_path }} | ||
| PYOCC_DEPS_PREFIX: ${{ matrix.platform.deps_path }} | ||
|
|
||
| - uses: actions/upload-artifact@v7 | ||
| with: | ||
| name: wheels-${{ matrix.platform.name }}-${{ matrix.python }} | ||
| path: wheelhouse/*.whl | ||
|
|
||
| sdist: | ||
| name: Source distribution | ||
| runs-on: ubuntu-24.04 | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| - run: pipx run build --sdist | ||
| - uses: actions/upload-artifact@v7 | ||
| with: | ||
| name: sdist | ||
| path: dist/*.tar.gz | ||
|
|
||
| publish: | ||
| name: Upload to PyPI | ||
| if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium · Mismatched tags publish wrong version When a pushed release tag does not match the CMake-derived package version, the workflow publishes artifacts using the version derived from Validate the release tag against the built distribution version and fail before publishing when they differ. Also at Prompt for AI agentsIn `.github/workflows/wheels.yml` at line 170:
**Mismatched tags publish wrong version**
When a pushed release tag does not match the CMake-derived package version, the workflow publishes artifacts using the version derived from `CMakeLists.txt` without checking it against the tag, so a mismatched release tag uploads a differently versioned distribution to PyPI.
Validate the release tag against the built distribution version and fail before publishing when they differ.
Also at `pyproject.toml:75`. |
||
| needs: [wheels, sdist] | ||
| runs-on: ubuntu-24.04 | ||
| # the trusted publisher configured on PyPI for this repository, this | ||
| # workflow and this environment | ||
| environment: | ||
| name: pypi | ||
| url: https://pypi.org/p/pythonocc-core | ||
| permissions: | ||
| id-token: write | ||
| steps: | ||
| - uses: actions/download-artifact@v8 | ||
| with: | ||
| path: dist | ||
| merge-multiple: true | ||
| - run: ls -l dist | ||
| - uses: pypa/gh-action-pypi-publish@v1.14.2 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium · Retries leave releases incomplete When a PyPI upload fails or is canceled after one or more distributions have been accepted, the publish action rejects already-uploaded files on retry, leaving the remaining distributions unpublished. Configure publishing to skip files already present on PyPI so a retry can upload the remaining distributions. Prompt for AI agentsIn `.github/workflows/wheels.yml` at line 186:
**Retries leave releases incomplete**
When a PyPI upload fails or is canceled after one or more distributions have been accepted, the publish action rejects already-uploaded files on retry, leaving the remaining distributions unpublished.
Configure publishing to skip files already present on PyPI so a retry can upload the remaining distributions. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,3 +64,6 @@ _build/ | |
| .Trashes | ||
| ehthumbs.db | ||
| Thumbs.db | ||
|
|
||
| # cibuildwheel | ||
| wheelhouse/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,6 +67,13 @@ if(APPLE) | |
| set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) | ||
| endif(APPLE) | ||
|
|
||
| if(SKBUILD AND UNIX AND NOT APPLE) | ||
| # wheel build: keep the OCCT library directory in the RPATH so that the | ||
| # unrepaired wheel is usable on the build machine. auditwheel replaces it | ||
| # with the vendored libraries location when repairing the wheel. | ||
| set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) | ||
| endif() | ||
|
|
||
| ##################################################################### | ||
| # OpenGL. If available, enable compilation for Visualization module # | ||
| ##################################################################### | ||
|
|
@@ -99,7 +106,7 @@ set(Python3_FIND_STRATEGY LOCATION) | |
| set(Python3_FIND_FRAMEWORK NEVER) | ||
| # numpy is required: the Geom, Geom2d, Poly, TColStd, TColgp and TShort modules | ||
| # use its C API. PYTHONOCC_MESHDS_NUMPY only enables the numpy arrays of MeshDS | ||
| find_package(Python3 COMPONENTS Interpreter Development NumPy REQUIRED) | ||
| find_package(Python3 COMPONENTS Interpreter Development.Module NumPy REQUIRED) | ||
| include_directories(${Python3_NumPy_INCLUDE_DIRS}) | ||
| message(STATUS "Numpy include directory: ${Python3_NumPy_INCLUDE_DIRS}") | ||
| if(PYTHONOCC_MESHDS_NUMPY) | ||
|
|
@@ -165,7 +172,11 @@ endif() | |
| # by default, installed to site-packages/OCC # | ||
| ############################################## | ||
| if(NOT DEFINED PYTHONOCC_INSTALL_DIRECTORY) | ||
| if(DEFINED ENV{SP_DIR} AND WIN32) | ||
| if(SKBUILD) | ||
| # wheel build (pip/scikit-build-core): the install prefix is the | ||
| # wheel platlib directory, install relative to it | ||
| set(PYTHONOCC_INSTALL_DIRECTORY OCC CACHE PATH "pythonocc install directory") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Critical · Wheel installs outside staging When scikit-build-core configures a wheel build, cMake treats the cache entry as a Keep the install destination relative, for example by using a Prompt for AI agentsIn `CMakeLists.txt` at line 178:
**Wheel installs outside staging**
When scikit-build-core configures a wheel build, cMake treats the cache entry as a `PATH` and resolves the relative `OCC` value to an absolute path. The later install rules then place the package outside scikit-build-core’s wheel staging prefix, so the wheel lacks `OCC` and its import tests fail.
Keep the install destination relative, for example by using a `STRING` cache entry or a normal non-cache variable. |
||
| elseif(DEFINED ENV{SP_DIR} AND WIN32) | ||
| # TODO: following hack is azure specific, a recent update in azure | ||
| # prevent cmake to find correct paths of python3 on windows | ||
| # this should be removed as soon as possible | ||
|
|
@@ -177,7 +188,7 @@ if(NOT DEFINED PYTHONOCC_INSTALL_DIRECTORY) | |
| OUTPUT_VARIABLE python_lib | ||
| OUTPUT_STRIP_TRAILING_WHITESPACE) | ||
| set(PYTHONOCC_INSTALL_DIRECTORY ${python_lib}/OCC CACHE PATH "pythonocc install directory") | ||
| endif(DEFINED ENV{SP_DIR} AND WIN32) | ||
| endif() | ||
| endif(NOT DEFINED PYTHONOCC_INSTALL_DIRECTORY) | ||
|
|
||
| message(STATUS "pythonocc modules will be installed to: ${PYTHONOCC_INSTALL_DIRECTORY}") | ||
|
|
@@ -460,7 +471,8 @@ if(PYTHONOCC_WRAP_VISU) | |
| endif(PYTHONOCC_WRAP_VISU) | ||
|
|
||
| # install Display | ||
| install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/Display DESTINATION ${PYTHONOCC_INSTALL_DIRECTORY}) | ||
| install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/Display DESTINATION ${PYTHONOCC_INSTALL_DIRECTORY} | ||
| PATTERN "__pycache__" EXCLUDE PATTERN ".ruff_cache" EXCLUDE) | ||
|
|
||
| # install LICENSE file | ||
| install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE DESTINATION ${PYTHONOCC_INSTALL_DIRECTORY}) | ||
|
|
@@ -477,7 +489,9 @@ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/PkgBase/Exception.py DESTINATION $ | |
| install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/PkgBase/py.typed DESTINATION ${PYTHONOCC_INSTALL_DIRECTORY}/Core) | ||
|
|
||
| # install Extend | ||
| install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/Extend DESTINATION ${PYTHONOCC_INSTALL_DIRECTORY}) | ||
| install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/Extend DESTINATION ${PYTHONOCC_INSTALL_DIRECTORY} | ||
| PATTERN "__pycache__" EXCLUDE PATTERN ".ruff_cache" EXCLUDE) | ||
|
|
||
| # install Wrapper utils | ||
| install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/Wrapper DESTINATION ${PYTHONOCC_INSTALL_DIRECTORY}) | ||
| install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/Wrapper DESTINATION ${PYTHONOCC_INSTALL_DIRECTORY} | ||
| PATTERN "__pycache__" EXCLUDE PATTERN ".ruff_cache" EXCLUDE) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| # PyPI wheels | ||
|
|
||
| The pythonocc-core wheels published on PyPI are built by | ||
| [cibuildwheel](https://cibuildwheel.pypa.io) in the GitHub Actions workflow | ||
| `.github/workflows/wheels.yml`: | ||
|
|
||
| | Platform | Wheels | Runner | | ||
| |----------|--------------------------------------------|------------------------------| | ||
| | Linux | `manylinux_2_28_x86_64`, `manylinux_2_28_aarch64` | `ubuntu-24.04`, `ubuntu-24.04-arm` | | ||
| | macOS | `macosx_11_0_x86_64`, `macosx_11_0_arm64` | `macos-15-intel`, `macos-15` | | ||
| | Windows | `win_amd64` | `windows-2022` | | ||
|
|
||
| for CPython 3.11 to 3.15, plus the sdist. | ||
|
|
||
| * `pyproject.toml`: the scikit-build-core build, the version is read from | ||
| `CMakeLists.txt`, and the cibuildwheel configuration. | ||
| * `deps/`: the native dependencies build scripts: SWIG, OCCT and its 3rd | ||
| parties (freetype, FreeImage, RapidJSON). | ||
| * Linux: built in the manylinux_2_28 container, 3rd parties from the | ||
| system (EPEL for FreeImage). | ||
| * macOS, Windows: built on the runner, 3rd parties built by vcpkg through | ||
| the OCCT vcpkg manifest, with the triplets of `triplets/`. | ||
|
|
||
| For each platform, a first job builds the dependencies and saves them to the | ||
| GitHub cache (the key is a hash of the platform scripts and triplet), then one | ||
| job per Python version restores them and builds and tests a wheel. The | ||
| libraries are vendored in the wheels by auditwheel (Linux), delocate (macOS) | ||
| and delvewheel (Windows). | ||
|
|
||
| The workflow runs on the pushes to master and pypi/* that change the | ||
| packaging, on demand (workflow_dispatch), and on the tags. The review/* | ||
| branches are only built by the Azure conda pipeline. | ||
|
|
||
| ## Releasing | ||
|
|
||
| Pushing a tag builds the wheels and the sdist and uploads them to PyPI | ||
| through [trusted publishing](https://docs.pypi.org/trusted-publishers/). | ||
| This requires, once: | ||
|
|
||
| * on PyPI, a trusted publisher for the `pythonocc-core` project: owner | ||
| `tpaviot`, repository `pythonocc-core`, workflow `wheels.yml`, environment | ||
| `pypi`. | ||
| * on GitHub, a `pypi` environment in the repository settings (optionally | ||
| with required reviewers, to approve each upload). | ||
|
|
||
| When upgrading SWIG or OCCT, update the versions in the three `deps/` | ||
| scripts, the cache is invalidated by their change. | ||
|
|
||
| ## Building locally | ||
|
|
||
| Linux wheels can be built on any machine with docker: | ||
|
|
||
| ```bash | ||
| pip install cibuildwheel | ||
| # optional: cache SWIG and OCCT between runs (/host is the host filesystem) | ||
| export PYOCC_DEPS_CACHE=/host$HOME/.cache/pyocc-deps | ||
| CIBW_BUILD='cp312-*' cibuildwheel --platform linux | ||
| ``` | ||
|
|
||
| from a clean checkout: cibuildwheel copies the whole source tree into the | ||
| container. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟠 High · Unreviewed tags publish releases
When a contributor who can create tags but is not authorized to approve releases tags an unreviewed commit, the wildcard tag trigger runs the release workflow for any tag, and the publish job uploads its artifacts without a required approval gate. A contributor able to tag an unreviewed commit but not authorized to approve a release can therefore publish that commit's package to PyPI.
Restrict release tags and require approval through a protected GitHub environment or tag ruleset before publishing.
Also at
.github/workflows/wheels.yml:170.Prompt for AI agents