Skip to content

Conversation

@nightlark
Copy link
Contributor

This migrates the CI workflow from https://github.com/trolldbois/python-clang/blob/master/.github/workflows/release.yml and adapts it to work within the structure of the LLVM monorepo.

It builds upon the Python packaging files added in #125806, and should be merged after that one.

The CI workflow is set up to use two jobs:

  • the first just builds the package and saves it as an artifact and will work for testing in forks, PRs, and untagged commits
  • the second checks for release manager permissions and uses trusted publishing to upload the package to PyPI

This adds a pyproject.toml file for creating sdist and wheel packages for the clang python bindings. It is required to move updates of the clang and libclang PyPI packages to the LLVM monorepo. Versioning information is derived from LLVM git tags, so no manual updates are needed to bump version numbers. The minimum python version required is set to 3.10 due to cindex.py using PEP 604 union type syntax (str | bytes | None).

Signed-off-by: Ryan Mast <mast.ryan@gmail.com>
This migrates the CI workflow from https://github.com/trolldbois/python-clang/blob/master/.github/workflows/release.yml and adapts it to work within the structure of the LLVM monorepo.

It builds upon the Python packaging files added in llvm#125806, and should be merged after that one.

The CI workflow is set up to use two jobs:
- the first just builds the package and saves it as an artifact and will work for testing in forks, PRs, and untagged commits
- the second checks for release manager permissions and uses trusted publishing to upload the package to PyPI

Signed-off-by: Ryan Mast <mast.ryan@gmail.com>
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:as-a-library libclang and C++ API github:workflow labels Nov 15, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 15, 2025

@llvm/pr-subscribers-github-workflow

Author: Ryan Mast (nightlark)

Changes

This migrates the CI workflow from https://github.com/trolldbois/python-clang/blob/master/.github/workflows/release.yml and adapts it to work within the structure of the LLVM monorepo.

It builds upon the Python packaging files added in #125806, and should be merged after that one.

The CI workflow is set up to use two jobs:

  • the first just builds the package and saves it as an artifact and will work for testing in forks, PRs, and untagged commits
  • the second checks for release manager permissions and uses trusted publishing to upload the package to PyPI

Full diff: https://github.com/llvm/llvm-project/pull/168234.diff

5 Files Affected:

  • (modified) .gitattributes (+2)
  • (added) .github/workflows/release-clang-pypi.yml (+111)
  • (added) clang/bindings/python/.git_archival.txt (+3)
  • (added) clang/bindings/python/.gitignore (+21)
  • (added) clang/bindings/python/pyproject.toml (+43)
diff --git a/.gitattributes b/.gitattributes
index 6b281f33f737d..142d6689f1088 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,3 +1,5 @@
+clang/bindings/python/.git_archival.txt  export-subst
+
 libcxx/src/**/*.cpp     merge=libcxx-reformat
 libcxx/include/**/*.h   merge=libcxx-reformat
 
diff --git a/.github/workflows/release-clang-pypi.yml b/.github/workflows/release-clang-pypi.yml
new file mode 100644
index 0000000000000..8c0b00a574cf5
--- /dev/null
+++ b/.github/workflows/release-clang-pypi.yml
@@ -0,0 +1,111 @@
+name: Release Clang Python Bindings
+
+permissions:
+  contents: read
+
+on:
+  push:
+    branches:
+      - main
+      - release/*
+    paths:
+      - .github/workflows/release-clang-pypi.yml
+      - 'clang/bindings/python/**'
+
+  pull_request:
+    paths:
+      - .github/workflows/release-clang-pypi.yml
+      - 'clang/bindings/python/**'
+
+  workflow_dispatch:
+    inputs:
+      release-version:
+        description: 'Release Version'
+        required: false
+        type: string
+
+  workflow_call:
+    inputs:
+      release-version:
+        description: 'Release Version'
+        required: true
+        type: string
+    secrets:
+      RELEASE_TASKS_USER_TOKEN:
+        description: "Secret used to check user permissions."
+        required: false
+
+jobs:
+  build-release:
+    if: github.repository_owner == 'llvm' || github.event_name == 'workflow_dispatch'
+    runs-on: ubuntu-24.04
+    steps:
+      - name: Checkout LLVM (tagged release)
+        if: inputs.release-version != ''
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+        with:
+          ref: "llvmorg-${{ inputs.release-version }}"
+          fetch-depth: 0
+          sparse-checkout: clang/bindings/python/
+
+      - name: Checkout LLVM (latest commit)
+        if: inputs.release-version == ''
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+        with:
+          fetch-depth: 0
+          sparse-checkout: clang/bindings/python/
+
+      - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
+        with:
+          python-version: '3.13'
+
+      - name: Package Clang Python Bindings
+        working-directory: clang/bindings/python
+        run: |
+          pip install build twine
+          python -m build
+          python -m twine check dist/*
+          
+      - name: Upload Clang Python Bindings package dist artifacts
+        uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
+        with:
+          name: python-package-dist
+          path: clang/bindings/python/dist
+
+  pypi-publish:
+    name: Upload release to PyPI
+    runs-on: ubuntu-24.04
+    needs: build-release
+    # Only run this job in the main llvm repository when a release version is provided.
+    if: github.repository_owner == 'llvm' && inputs.release-version != ''
+    environment:
+      name: pypi
+      url: https://pypi.org/p/clang
+    permissions:
+      id-token: write  # IMPORTANT: this permission is mandatory for trusted publishing
+    steps:
+      - name: Checkout github-upload-release.py for permissions check
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+        with:
+          sparse-checkout: llvm/utils/release/github-upload-release.py
+
+      - name: Install dependencies for github-upload-release.py
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y python3-github
+
+      - name: Check Permissions
+        env:
+          GITHUB_TOKEN: ${{ github.token }}
+          USER_TOKEN: ${{ secrets.RELEASE_TASKS_USER_TOKEN }}
+        run: |
+          ./llvm/utils/release/github-upload-release.py --token "$GITHUB_TOKEN" --user ${{ github.actor }} --user-token "$USER_TOKEN" check-permissions
+
+      - name: Download Python package dist artifacts
+        uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
+        with:
+            name: python-package-dist
+            path: dist
+
+      - name: Publish package distributions to PyPI
+        uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
\ No newline at end of file
diff --git a/clang/bindings/python/.git_archival.txt b/clang/bindings/python/.git_archival.txt
new file mode 100644
index 0000000000000..7876d4af4c620
--- /dev/null
+++ b/clang/bindings/python/.git_archival.txt
@@ -0,0 +1,3 @@
+node: $Format:%H$
+node-date: $Format:%cI$
+describe-name: $Format:%(describe:tags=true,match=llvmorg-*[0-9]*)$
diff --git a/clang/bindings/python/.gitignore b/clang/bindings/python/.gitignore
new file mode 100644
index 0000000000000..1641a745fb682
--- /dev/null
+++ b/clang/bindings/python/.gitignore
@@ -0,0 +1,21 @@
+# setuptools_scm auto-generated version file
+_version.py
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# Distribution / packaging
+build/
+dist/
+*.egg-info/
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
diff --git a/clang/bindings/python/pyproject.toml b/clang/bindings/python/pyproject.toml
new file mode 100644
index 0000000000000..69fd628e8222a
--- /dev/null
+++ b/clang/bindings/python/pyproject.toml
@@ -0,0 +1,43 @@
+[build-system]
+requires = ["hatchling>=1.27", "hatch-vcs>=0.4"]
+build-backend = "hatchling.build"
+
+[project]
+name = "clang"
+description = "clang python bindings"
+readme = {file = "README.txt", content-type = "text/plain"}
+
+license = "Apache-2.0 WITH LLVM-exception"
+authors = [
+    { name = "LLVM" }
+]
+keywords = ["llvm", "clang", "libclang"]
+classifiers = [
+    "Intended Audience :: Developers",
+    "Development Status :: 5 - Production/Stable",
+    "Topic :: Software Development :: Compilers",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python :: 3",
+]
+requires-python = ">=3.10"
+dynamic = ["version"]
+
+[project.urls]
+Homepage = "https://clang.llvm.org/"
+Download = "https://llvm.org/releases/download.html"
+Discussions = "https://discourse.llvm.org/"
+"Issue Tracker" = "https://github.com/llvm/llvm-project/issues"
+"Source Code" = "https://github.com/llvm/llvm-project/tree/main/clang/bindings/python"
+
+[tool.hatch.version]
+source = "vcs"
+version-scheme = "no-guess-dev"
+# regex version capture group gets x.y.z with optional -rcN, -aN, -bN suffixes; -init is just consumed
+tag-pattern = "^llvmorg-(?P<version>\\d+(?:\\.\\d+)*(?:-rc\\d+)?)"
+
+[tool.hatch.build.hooks.vcs]
+version-file = "clang/_version.py"
+
+[tool.hatch.version.raw-options]
+search_parent_directories = true
+version_scheme = "no-guess-dev"

@llvmbot
Copy link
Member

llvmbot commented Nov 15, 2025

@llvm/pr-subscribers-clang

Author: Ryan Mast (nightlark)

Changes

This migrates the CI workflow from https://github.com/trolldbois/python-clang/blob/master/.github/workflows/release.yml and adapts it to work within the structure of the LLVM monorepo.

It builds upon the Python packaging files added in #125806, and should be merged after that one.

The CI workflow is set up to use two jobs:

  • the first just builds the package and saves it as an artifact and will work for testing in forks, PRs, and untagged commits
  • the second checks for release manager permissions and uses trusted publishing to upload the package to PyPI

Full diff: https://github.com/llvm/llvm-project/pull/168234.diff

5 Files Affected:

  • (modified) .gitattributes (+2)
  • (added) .github/workflows/release-clang-pypi.yml (+111)
  • (added) clang/bindings/python/.git_archival.txt (+3)
  • (added) clang/bindings/python/.gitignore (+21)
  • (added) clang/bindings/python/pyproject.toml (+43)
diff --git a/.gitattributes b/.gitattributes
index 6b281f33f737d..142d6689f1088 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,3 +1,5 @@
+clang/bindings/python/.git_archival.txt  export-subst
+
 libcxx/src/**/*.cpp     merge=libcxx-reformat
 libcxx/include/**/*.h   merge=libcxx-reformat
 
diff --git a/.github/workflows/release-clang-pypi.yml b/.github/workflows/release-clang-pypi.yml
new file mode 100644
index 0000000000000..8c0b00a574cf5
--- /dev/null
+++ b/.github/workflows/release-clang-pypi.yml
@@ -0,0 +1,111 @@
+name: Release Clang Python Bindings
+
+permissions:
+  contents: read
+
+on:
+  push:
+    branches:
+      - main
+      - release/*
+    paths:
+      - .github/workflows/release-clang-pypi.yml
+      - 'clang/bindings/python/**'
+
+  pull_request:
+    paths:
+      - .github/workflows/release-clang-pypi.yml
+      - 'clang/bindings/python/**'
+
+  workflow_dispatch:
+    inputs:
+      release-version:
+        description: 'Release Version'
+        required: false
+        type: string
+
+  workflow_call:
+    inputs:
+      release-version:
+        description: 'Release Version'
+        required: true
+        type: string
+    secrets:
+      RELEASE_TASKS_USER_TOKEN:
+        description: "Secret used to check user permissions."
+        required: false
+
+jobs:
+  build-release:
+    if: github.repository_owner == 'llvm' || github.event_name == 'workflow_dispatch'
+    runs-on: ubuntu-24.04
+    steps:
+      - name: Checkout LLVM (tagged release)
+        if: inputs.release-version != ''
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+        with:
+          ref: "llvmorg-${{ inputs.release-version }}"
+          fetch-depth: 0
+          sparse-checkout: clang/bindings/python/
+
+      - name: Checkout LLVM (latest commit)
+        if: inputs.release-version == ''
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+        with:
+          fetch-depth: 0
+          sparse-checkout: clang/bindings/python/
+
+      - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
+        with:
+          python-version: '3.13'
+
+      - name: Package Clang Python Bindings
+        working-directory: clang/bindings/python
+        run: |
+          pip install build twine
+          python -m build
+          python -m twine check dist/*
+          
+      - name: Upload Clang Python Bindings package dist artifacts
+        uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
+        with:
+          name: python-package-dist
+          path: clang/bindings/python/dist
+
+  pypi-publish:
+    name: Upload release to PyPI
+    runs-on: ubuntu-24.04
+    needs: build-release
+    # Only run this job in the main llvm repository when a release version is provided.
+    if: github.repository_owner == 'llvm' && inputs.release-version != ''
+    environment:
+      name: pypi
+      url: https://pypi.org/p/clang
+    permissions:
+      id-token: write  # IMPORTANT: this permission is mandatory for trusted publishing
+    steps:
+      - name: Checkout github-upload-release.py for permissions check
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+        with:
+          sparse-checkout: llvm/utils/release/github-upload-release.py
+
+      - name: Install dependencies for github-upload-release.py
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y python3-github
+
+      - name: Check Permissions
+        env:
+          GITHUB_TOKEN: ${{ github.token }}
+          USER_TOKEN: ${{ secrets.RELEASE_TASKS_USER_TOKEN }}
+        run: |
+          ./llvm/utils/release/github-upload-release.py --token "$GITHUB_TOKEN" --user ${{ github.actor }} --user-token "$USER_TOKEN" check-permissions
+
+      - name: Download Python package dist artifacts
+        uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
+        with:
+            name: python-package-dist
+            path: dist
+
+      - name: Publish package distributions to PyPI
+        uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
\ No newline at end of file
diff --git a/clang/bindings/python/.git_archival.txt b/clang/bindings/python/.git_archival.txt
new file mode 100644
index 0000000000000..7876d4af4c620
--- /dev/null
+++ b/clang/bindings/python/.git_archival.txt
@@ -0,0 +1,3 @@
+node: $Format:%H$
+node-date: $Format:%cI$
+describe-name: $Format:%(describe:tags=true,match=llvmorg-*[0-9]*)$
diff --git a/clang/bindings/python/.gitignore b/clang/bindings/python/.gitignore
new file mode 100644
index 0000000000000..1641a745fb682
--- /dev/null
+++ b/clang/bindings/python/.gitignore
@@ -0,0 +1,21 @@
+# setuptools_scm auto-generated version file
+_version.py
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# Distribution / packaging
+build/
+dist/
+*.egg-info/
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
diff --git a/clang/bindings/python/pyproject.toml b/clang/bindings/python/pyproject.toml
new file mode 100644
index 0000000000000..69fd628e8222a
--- /dev/null
+++ b/clang/bindings/python/pyproject.toml
@@ -0,0 +1,43 @@
+[build-system]
+requires = ["hatchling>=1.27", "hatch-vcs>=0.4"]
+build-backend = "hatchling.build"
+
+[project]
+name = "clang"
+description = "clang python bindings"
+readme = {file = "README.txt", content-type = "text/plain"}
+
+license = "Apache-2.0 WITH LLVM-exception"
+authors = [
+    { name = "LLVM" }
+]
+keywords = ["llvm", "clang", "libclang"]
+classifiers = [
+    "Intended Audience :: Developers",
+    "Development Status :: 5 - Production/Stable",
+    "Topic :: Software Development :: Compilers",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python :: 3",
+]
+requires-python = ">=3.10"
+dynamic = ["version"]
+
+[project.urls]
+Homepage = "https://clang.llvm.org/"
+Download = "https://llvm.org/releases/download.html"
+Discussions = "https://discourse.llvm.org/"
+"Issue Tracker" = "https://github.com/llvm/llvm-project/issues"
+"Source Code" = "https://github.com/llvm/llvm-project/tree/main/clang/bindings/python"
+
+[tool.hatch.version]
+source = "vcs"
+version-scheme = "no-guess-dev"
+# regex version capture group gets x.y.z with optional -rcN, -aN, -bN suffixes; -init is just consumed
+tag-pattern = "^llvmorg-(?P<version>\\d+(?:\\.\\d+)*(?:-rc\\d+)?)"
+
+[tool.hatch.build.hooks.vcs]
+version-file = "clang/_version.py"
+
+[tool.hatch.version.raw-options]
+search_parent_directories = true
+version_scheme = "no-guess-dev"

@nightlark
Copy link
Contributor Author

Here are some test runs of the workflow that I did in my fork:

  • https://github.com/nightlark/llvm-project/actions/runs/19394995716
    • workflow_dispatch release version 20.8.5 (fake tag in my fork)
    • checks out the corresponding llvmorg tag
    • uploaded dist artifacts have 20.8.5 version number in them
    • skips release step because the repo isn't owned by llvm, it's a fork
  • https://github.com/nightlark/llvm-project/actions/runs/19394988110
    • workflow_dispatch with no release version specified
    • checks out the latest commit (same as PR workflow that ran)
    • uploaded dist artifacts (with a post/dev version that doesn't really matter since it will never be published to PyPI)
    • skips release step because repo isn't owned by llvm, it's a fork (and no version number was given)
  • https://github.com/nightlark/llvm-project/actions/runs/19395404575
    • temporarily removed the condition for the repository owner being the llvm org
    • skips the release step because no version was specified (avoids unintentional upload of an untagged version)
  • https://github.com/nightlark/llvm-project/actions/runs/19395406366
    • temporarily removed the condition for the repository owner being the llvm org
    • tries to run the release step because a version input is specified, tries to run the permissions check but fails due to not having a suitable user token; similar to the upload step failing there due to the permissions check actually failing and thus preventing an upload)

Copy link
Contributor

@DeinAlptraum DeinAlptraum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for your work on this! I highly appreciate you getting the bindings releases into a better shape.
As mentioned before, I'm not familiar with Python packaging so I can't contribute too much here unfortunately, Insofar as I can tell though, this LGTM


[project.urls]
Homepage = "https://clang.llvm.org/"
Download = "https://llvm.org/releases/download.html"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This website seems super outdated, are you sure there isn't a better one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the homepage maybe the website theme is a bit on the dated side? Looking at the C++ status page, it lists features that were added in the v21 release: https://clang.llvm.org/cxx_status.html

The downloads page looks like it could use updating… or perhaps it could just get pointed at the GitHub releases page for LLVM?

Comment on lines +11 to +18
paths:
- .github/workflows/release-clang-pypi.yml
- 'clang/bindings/python/**'

pull_request:
paths:
- .github/workflows/release-clang-pypi.yml
- 'clang/bindings/python/**'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps the paths here should also include the underlying C/C++ sources? I.e.
'clang/tools/libclang/**'
as we currently also do in the test CI workflow:
https://github.com/llvm/llvm-project/blob/main/.github/workflows/libclang-python-tests.yml

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could trigger for the underlying C/C++ sources, but for this stage of the packaging process it’s a pure Python wheel with just the Python bindings code and no compiled library, so changes to those files shouldn’t have any effect on the resulting packages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:as-a-library libclang and C++ API clang Clang issues not falling into any other category github:workflow

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants