Skip to content

[build] Ship the clang builtin headers and prefer them at runtime - #33

Open
aaronj0 wants to merge 1 commit into
mainfrom
bundle-clang-resource-headers
Open

[build] Ship the clang builtin headers and prefer them at runtime#33
aaronj0 wants to merge 1 commit into
mainfrom
bundle-clang-resource-headers

Conversation

@aaronj0

@aaronj0 aaronj0 commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

This is the second part of the patch that ships the required runtime clang headers. With this change, a build/install of cppjit is self-contained and with the LLVM that is statically linked into libClangCppInterOp.so, we drop the LLVM dependency on a target machine. The wheels build PR will exercise this on clean images without LLVM.

As settled in review: the bundle is produced by the CMake install rules from the LLVM the build was pointed at, so it is identical to the headers libClangCppInterOp was compiled against under every install channel (pip, conda recipes, distro tooling, bare cmake --install) and for development builds against any LLVM_DIR. Only include/ ships; the JIT reads nothing else from a resource directory. Setups without a bundle such as raw build trees, or distributions that strip vendored copies, fall back to the existing CppInterOp DetectResourceDir probing, which was the previous default behaviour (unchanged). This increases wheel size by 3-5MB

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown

Test Results

Configuration Result
macos-26-intel-llvm21-py3.14-cxx20 = 437 passed, 44 skipped, 85 xfailed, 32 xpassed, 100 warnings in 96.58s (0:01:36) =
macos-26-llvm21-py3.14-cxx20 ==== 438 passed, 37 skipped, 93 xfailed, 30 xpassed, 100 warnings in 44.91s ====
ubuntu-24.04-llvm21-py3.14-cxx20-vg ====== 553 passed, 25 skipped, 18 xfailed, 2 xpassed in 93.56s (0:01:33) =======
ubuntu-24.04-llvm22-py3.14-cxx20 ====== 553 passed, 25 skipped, 19 xfailed, 1 xpassed in 105.60s (0:01:45) ======
ubuntu-24.04-llvm22-py3.14-cxx20-cling ====== 543 passed, 25 skipped, 27 xfailed, 3 xpassed in 108.59s (0:01:48) ======
ubuntu-24.04-llvm22-py3.14-cxx23 ====== 574 passed, 3 skipped, 20 xfailed, 1 xpassed in 110.72s (0:01:50) =======

@aaronj0
aaronj0 requested a review from vgvassilev August 21, 2026 08:55
Comment thread CMakeLists.txt
@aaronj0
aaronj0 force-pushed the bundle-clang-resource-headers branch from 198d0e3 to 46a6799 Compare August 21, 2026 13:13
// #include. Prefer the bundled copy: it matches the build clang and
// needs no LLVM on the host.
std::string resourceDir = Paths.ClangIncludeDir;
if (resourceDir.empty() && Cpp::DetectResourceDir("clang").empty())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we prioritize the system's resource directory.
First, check clang-version, then the one we include, then simple clang.

@aaronj0 aaronj0 Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No that would not work. If we build a binary for manylinux or OS X based on llvm22 and a user installs it on their system which happens to have a lower LLVM version (apple always has clang) that would crash the interpreter. The point is that cppjit should not depend on system LLVM.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

That is exactly the problem I described in last weeks meeting.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No that would not work. If we build a binary for manylinux or OS X based on llvm22 and a user installs it on their system which happens to have a lower LLVM version (apple always has clang) that would crash the interpreter. The point is that cppjit should not depend on system LLVM.

That would fail the match of the resource dir folder name which incorporates the version. We can also protect against older clangs such a check...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

That would fail the match of the resource dir folder name which incorporates the version. We can also protect against older clangs such a check...

Can you point me to which match fail you are referring to? Yes we can incorporate a check if the major version matches but just to clarify, we decided to bundle the headers so that we don't run into this problem and introduce mechanisms to ensure that system or package manager LLVM's agree with what cppjit needs.

If we always prioritize system-installed LLVM, that goes against the very solution you proposed (and we agreed on) to bundle the headers, leading to a self contained binary and not depend on an arbitrary LLVM picked up at runtime which can differ based on user environments

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

What the review suggestion here proposes is to prioritize the arbitrary clang headers detected at runtime over the one that we bundle and I disagree with that. If anything, system clang can be used as a fallback but not as the first pick for resource dir since there is no guarantee that exists or even work if it does.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

pip install --no-build-isolation -ve .

That's 1 package manager. Does that solution work for non-pip setups?

What the review suggestion here proposes is to prioritize the arbitrary clang headers detected at runtime over the one that we bundle and I disagree with that. If anything, system clang can be used as a fallback but not as the first pick for resource dir since there is no guarantee that exists or even work if it does.

Where my comments suggested that?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

That's 1 package manager. Does that solution work for non-pip setups?

Yes. Development mode works because this is not a pip-specific solution but in the the underlying build system. The installation layout is fixed and under our control irrespective of the package manager. Here is a pure CMake build, without pip, against an uninstalled developer build tree of LLVM:

$ python3 -m venv demo-venv          # python to build against
$ cmake -S cppjit -B build \
      -DCMAKE_BUILD_TYPE=Release \
      -DLLVM_DIR=$HOME/llvm-project/release_build/lib/cmake/llvm \
      -DPython_EXECUTABLE=$PWD/demo-venv/bin/python
$ cmake --build build -j$(nproc)
$ cmake --install build --prefix $PWD/cppjit-install
$ cp -r cppjit/python/cppjit cppjit-install/   # normally done by the wheel

The installed tree carries the builtin headers of that LLVM:

$ diff -r $HOME/llvm-project/release_build/lib/clang/21/include \
          cppjit-install/cppjit_backend/lib/clang/21/include && echo IDENTICAL
IDENTICAL

So irrespective of what LLVM we develop against, the runtime prefers the headers libclangCppInterOp was compiled against. In a developer's uninstalled build tree there are no bundled headers, and the loader falls back to DetectResourceDir, which is unchanged in this patch.

The result is self-contained either way. With an empty PATH (no clang visible to the process):

$ PYTHONPATH=$PWD/cppjit-install PATH=/nonexistent demo-venv/bin/python \
      -c "import cppjit; cppjit.cppdef('#include <vector>\nint f(){ std::vector<int> v{41}; return v[0]+1; }'); print('f() =', cppjit.gbl.f())"
f() = 42

The point: This does not depend on a package manager, but a property of the CMake install rules which is run by every install channel. At the end of the day, cppjit is a Python package and any PEP 517 (Python standard that separates the build frontend from the build backend) frontend, drives it through scikit-build-core. So this satisfies conda recipes, conda build environments, a linux distro specific package manager, and a bare cmake --install in a developers setup (with no Python packaging at all). We are satisfying the Python build standard. If any distribution or install does not contain the bundled headers (which is impossible unless someone removes them on purpose) we try the DetectResourceDir fallback with clang. We can improve that fallback to restrict to the supported LLVM version but that is outside the scope of this PR.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Where my comments suggested that?

I was referring to the original review comment in this thread:

Can we prioritize the system's resource directory.
First, check clang-version, then the one we include, then simple clang.

cc @Vipul-Cariappa

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That's 1 package manager. Does that solution work for non-pip setups?

Yes. Development mode works because this is not a pip-specific solution but in the the underlying build system. The installation layout is fixed and under our control irrespective of the package manager. Here is a pure CMake build, without pip, against an uninstalled developer build tree of LLVM:

$ python3 -m venv demo-venv          # python to build against
$ cmake -S cppjit -B build \
      -DCMAKE_BUILD_TYPE=Release \
      -DLLVM_DIR=$HOME/llvm-project/release_build/lib/cmake/llvm \
      -DPython_EXECUTABLE=$PWD/demo-venv/bin/python
$ cmake --build build -j$(nproc)
$ cmake --install build --prefix $PWD/cppjit-install
$ cp -r cppjit/python/cppjit cppjit-install/   # normally done by the wheel

The installed tree carries the builtin headers of that LLVM:

$ diff -r $HOME/llvm-project/release_build/lib/clang/21/include \
          cppjit-install/cppjit_backend/lib/clang/21/include && echo IDENTICAL
IDENTICAL

So irrespective of what LLVM we develop against, the runtime prefers the headers libclangCppInterOp was compiled against. In a developer's uninstalled build tree there are no bundled headers, and the loader falls back to DetectResourceDir, which is unchanged in this patch.

The result is self-contained either way. With an empty PATH (no clang visible to the process):

$ PYTHONPATH=$PWD/cppjit-install PATH=/nonexistent demo-venv/bin/python \
      -c "import cppjit; cppjit.cppdef('#include <vector>\nint f(){ std::vector<int> v{41}; return v[0]+1; }'); print('f() =', cppjit.gbl.f())"
f() = 42

The point: This does not depend on a package manager, but a property of the CMake install rules which is run by every install channel. At the end of the day, cppjit is a Python package and any PEP 517 (Python standard that separates the build frontend from the build backend) frontend, drives it through scikit-build-core. So this satisfies conda recipes, conda build environments, a linux distro specific package manager, and a bare cmake --install in a developers setup (with no Python packaging at all). We are satisfying the Python build standard. If any distribution or install does not contain the bundled headers (which is impossible unless someone removes them on purpose) we try the DetectResourceDir fallback with clang. We can improve that fallback to restrict to the supported LLVM version but that is outside the scope of this PR.

Ok, that clarifies it -- can you update the PR description and the commit message? That would have helped me.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants