Skip to content

[BUG]: Visibility is incorrectly exported on ARM based windows #5883

@K20shores

Description

@K20shores

Required prerequisites

What version (or hash if on master) of pybind11 are you using?

v3.0.0

Problem description

I work on a project, musica, which includes some Fortran sources. We’re starting to support building it on Windows using msys2. The error I’m about to describe is unrelated to Fortran, but I encountered it while trying to build a pybind11 module on ARM-based Windows, hence the use of msys2.

Building on AMD64-based Windows works just fine. However, building with a windows-11-arm runner in GitHub Actions and using clang, a pybind11 module fails to build because of symbol visibility.

I recreated the same error I see in the musica library in this much smaller repository:

-- Building for: Ninja
-- The CXX compiler identification is Clang 21.1.4
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/a/_temp/msys64/clangarm64/bin/c++.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- pybind11 v3.0.0 
-- Found Python: C:/hostedtoolcache/windows/Python/3.11.9/arm64/python.exe (found suitable version "3.11.9", minimum required is "3.8") found components: Interpreter Development.Module Development.Embed
Using compatibility mode for Python, set PYBIND11_FINDPYTHON to NEW/OLD to silence this message
-- pybind11::lto disabled (problems with undefined symbols for MinGW for now)
-- pybind11::thin_lto disabled (problems with undefined symbols for MinGW for now)
-- Configuring done (7.1s)
-- Generating done (0.0s)
-- Build files have been written to: C:/a/_temp/msys64/tmp/tmpbydxfxd2/build
*** Building project with Ninja...
2025-10-27 12:55:48,604 - scikit_build_core - DEBUG - RUNENV - changes since last run only:
  
2025-10-27 12:55:48,604 - scikit_build_core - INFO - RUN: C:\a\_temp\msys64\clangarm64\bin\cmake.EXE --build C:\a\_temp\msys64\tmp\tmpbydxfxd2\build -v
Change Dir: 'C:/a/_temp/msys64/tmp/tmpbydxfxd2/build'

Run Build Command(s): C:\a\_temp\msys64\clangarm64\bin\ninja.EXE -v
[1/4] C:\Windows\system32\cmd.exe /C ""C:/a/_temp/msys64/clangarm64/bin/clang-scan-deps.exe" -format=p1689 -- C:\a\_temp\msys64\clangarm64\bin\c++.exe -DPy_NO_LINK_LIB -Dtest_EXPORTS -isystem C:/hostedtoolcache/windows/Python/3.11.9/arm64/Include -isystem C:/a/_temp/msys64/tmp/tmpbydxfxd2/build/_deps/pybind11-src/include -O3 -DNDEBUG -std=gnu++20 -fvisibility=hidden -x c++ C:/a/scratch/scratch/pybind/test.cpp -c -o CMakeFiles\test.dir\test.cpp.obj -resource-dir "C:/a/_temp/msys64/clangarm64/lib/clang/21" -MT CMakeFiles\test.dir\test.cpp.obj.ddi -MD -MF CMakeFiles\test.dir\test.cpp.obj.ddi.d > CMakeFiles\test.dir\test.cpp.obj.ddi.tmp && "C:/a/_temp/msys64/clangarm64/bin/cmake.exe" -E rename CMakeFiles\test.dir\test.cpp.obj.ddi.tmp CMakeFiles\test.dir\test.cpp.obj.ddi"
[2/4] C:\a\_temp\msys64\clangarm64\bin\cmake.exe -E cmake_ninja_dyndep --tdi=CMakeFiles\test.dir\CXXDependInfo.json --lang=CXX --modmapfmt=clang --dd=CMakeFiles/test.dir/CXX.dd @CMakeFiles/test.dir/CXX.dd.rsp
[3/4] C:\a\_temp\msys64\clangarm64\bin\c++.exe -DPy_NO_LINK_LIB -Dtest_EXPORTS -isystem C:/hostedtoolcache/windows/Python/3.11.9/arm64/Include -isystem C:/a/_temp/msys64/tmp/tmpbydxfxd2/build/_deps/pybind11-src/include -O3 -DNDEBUG -std=gnu++20 -fvisibility=hidden -MD -MT CMakeFiles/test.dir/test.cpp.obj -MF CMakeFiles\test.dir\test.cpp.obj.d @CMakeFiles\test.dir\test.cpp.obj.modmap -o CMakeFiles/test.dir/test.cpp.obj -c C:/a/scratch/scratch/pybind/test.cpp
FAILED: [code=1] CMakeFiles/test.dir/test.cpp.obj 
C:\a\_temp\msys64\clangarm64\bin\c++.exe -DPy_NO_LINK_LIB -Dtest_EXPORTS -isystem C:/hostedtoolcache/windows/Python/3.11.9/arm64/Include -isystem C:/a/_temp/msys64/tmp/tmpbydxfxd2/build/_deps/pybind11-src/include -O3 -DNDEBUG -std=gnu++20 -fvisibility=hidden -MD -MT CMakeFiles/test.dir/test.cpp.obj -MF CMakeFiles\test.dir\test.cpp.obj.d @CMakeFiles\test.dir\test.cpp.obj.modmap -o CMakeFiles/test.dir/test.cpp.obj -c C:/a/scratch/scratch/pybind/test.cpp
In file included from C:/a/scratch/scratch/pybind/test.cpp:1:
C:/a/_temp/msys64/tmp/tmpbydxfxd2/build/_deps/pybind11-src/include/pybind11/pybind11.h:3334:20: error: hidden visibility cannot be applied to 'dllexport' declaration
 3334 | error_already_set::m_fetched_error_deleter(detail::error_fetch_and_normalize *raw_ptr) {
      |                    ^
C:/a/_temp/msys64/tmp/tmpbydxfxd2/build/_deps/pybind11-src/include/pybind11/pybind11.h:3340:39: error: hidden visibility cannot be applied to 'dllexport' declaration
 3340 | inline const char *error_already_set::what() const noexcept {
      |                                       ^
2 errors generated.
ninja: build stopped: subcommand failed.


*** CMake build failed

ERROR Backend subprocess exited when trying to invoke build_wheel
  • Even if I set the visibility on the cmake target to default, there's still a build error
    if (WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
      set_property(TARGET test PROPERTY CXX_VISIBILITY_PRESET "default")
    endif()
    
-- Building for: Ninja
-- The CXX compiler identification is Clang 21.1.4
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/a/_temp/msys64/clangarm64/bin/c++.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- pybind11 v3.0.0 
-- Found Python: C:/hostedtoolcache/windows/Python/3.13.5/arm64/python.exe (found suitable version "3.13.5", minimum required is "3.8") found components: Interpreter Development.Module Development.Embed
Using compatibility mode for Python, set PYBIND11_FINDPYTHON to NEW/OLD to silence this message
-- pybind11::lto disabled (problems with undefined symbols for MinGW for now)
-- pybind11::thin_lto disabled (problems with undefined symbols for MinGW for now)
-- Configuring done (6.7s)
-- Generating done (0.0s)
-- Build files have been written to: C:/a/_temp/msys64/tmp/tmpgiskwr9z/build
*** Building project with Ninja...
2025-10-27 13:27:25,636 - scikit_build_core - DEBUG - RUNENV - changes since last run only:
  
2025-10-27 13:27:25,637 - scikit_build_core - INFO - RUN: C:\a\_temp\msys64\clangarm64\bin\cmake.EXE --build C:\a\_temp\msys64\tmp\tmpgiskwr9z\build -v
Change Dir: 'C:/a/_temp/msys64/tmp/tmpgiskwr9z/build'

Run Build Command(s): C:\a\_temp\msys64\clangarm64\bin\ninja.EXE -v
[1/4] C:\Windows\system32\cmd.exe /C ""C:/a/_temp/msys64/clangarm64/bin/clang-scan-deps.exe" -format=p1689 -- C:\a\_temp\msys64\clangarm64\bin\c++.exe -DPy_NO_LINK_LIB -Dtest_EXPORTS -isystem C:/hostedtoolcache/windows/Python/3.13.5/arm64/Include -isystem C:/a/_temp/msys64/tmp/tmpgiskwr9z/build/_deps/pybind11-src/include -O3 -DNDEBUG -std=gnu++20 -fvisibility=default -x c++ C:/a/scratch/scratch/pybind/test.cpp -c -o CMakeFiles\test.dir\test.cpp.obj -resource-dir "C:/a/_temp/msys64/clangarm64/lib/clang/21" -MT CMakeFiles\test.dir\test.cpp.obj.ddi -MD -MF CMakeFiles\test.dir\test.cpp.obj.ddi.d > CMakeFiles\test.dir\test.cpp.obj.ddi.tmp && "C:/a/_temp/msys64/clangarm64/bin/cmake.exe" -E rename CMakeFiles\test.dir\test.cpp.obj.ddi.tmp CMakeFiles\test.dir\test.cpp.obj.ddi"
[2/4] C:\a\_temp\msys64\clangarm64\bin\cmake.exe -E cmake_ninja_dyndep --tdi=CMakeFiles\test.dir\CXXDependInfo.json --lang=CXX --modmapfmt=clang --dd=CMakeFiles/test.dir/CXX.dd @CMakeFiles/test.dir/CXX.dd.rsp
[3/4] C:\a\_temp\msys64\clangarm64\bin\c++.exe -DPy_NO_LINK_LIB -Dtest_EXPORTS -isystem C:/hostedtoolcache/windows/Python/3.13.5/arm64/Include -isystem C:/a/_temp/msys64/tmp/tmpgiskwr9z/build/_deps/pybind11-src/include -O3 -DNDEBUG -std=gnu++20 -fvisibility=default -MD -MT CMakeFiles/test.dir/test.cpp.obj -MF CMakeFiles\test.dir\test.cpp.obj.d @CMakeFiles\test.dir\test.cpp.obj.modmap -o CMakeFiles/test.dir/test.cpp.obj -c C:/a/scratch/scratch/pybind/test.cpp
FAILED: [code=1] CMakeFiles/test.dir/test.cpp.obj 
C:\a\_temp\msys64\clangarm64\bin\c++.exe -DPy_NO_LINK_LIB -Dtest_EXPORTS -isystem C:/hostedtoolcache/windows/Python/3.13.5/arm64/Include -isystem C:/a/_temp/msys64/tmp/tmpgiskwr9z/build/_deps/pybind11-src/include -O3 -DNDEBUG -std=gnu++20 -fvisibility=default -MD -MT CMakeFiles/test.dir/test.cpp.obj -MF CMakeFiles\test.dir\test.cpp.obj.d @CMakeFiles\test.dir\test.cpp.obj.modmap -o CMakeFiles/test.dir/test.cpp.obj -c C:/a/scratch/scratch/pybind/test.cpp
In file included from C:/a/scratch/scratch/pybind/test.cpp:1:
C:/a/_temp/msys64/tmp/tmpgiskwr9z/build/_deps/pybind11-src/include/pybind11/pybind11.h:3334:20: error: hidden visibility cannot be applied to 'dllexport' declaration
 3334 | error_already_set::m_fetched_error_deleter(detail::error_fetch_and_normalize *raw_ptr) {
      |                    ^
C:/a/_temp/msys64/tmp/tmpgiskwr9z/build/_deps/pybind11-src/include/pybind11/pybind11.h:3340:39: error: hidden visibility cannot be applied to 'dllexport' declaration
 3340 | inline const char *error_already_set::what() const noexcept {
      |                                       ^
2 errors generated.
ninja: build stopped: subcommand failed.


*** CMake build failed

ERROR Backend subprocess exited when trying to invoke build_wheel

I think what may be happening is that dllexport gets added from PYBIND11_EXPORT, but that -fvisibility=hidden gets added by tools or maybe new tools.

I can't tell if this is a bug in pybind11, or just a tooling issue that I need to work around. Either way, since I have not changed any defaults, I figured it was worth reporting.

Reproducible example code

cmake_minimum_required(VERSION 3.20)
project(test VERSION 0.0.0 LANGUAGES CXX)

include(FetchContent)

FetchContent_Declare(pybind11
    GIT_REPOSITORY https://github.com/pybind/pybind11
    GIT_TAG        v3.0.0
    FIND_PACKAGE_ARGS NAMES pybind11
)

FetchContent_MakeAvailable(pybind11)

find_package(pybind11 REQUIRED)

pybind11_add_module(test 
  test.cpp
)
 target_compile_features(test PUBLIC cxx_std_20)
#include <pybind11/pybind11.h>
using namespace pybind11;

PYBIND11_MODULE(test, m) {
    m.def("add", [](int a, int b){ return a + b; });
}
[build-system]
requires = ["scikit-build-core>=0.11.0"]
build-backend = "scikit_build_core.build"

[project]
name = "test"
dynamic = ["version"]

[tool.scikit-build]
cmake.build-type = "Release"
build.verbose = true
logging.level = "DEBUG"

[tool.scikit-build.metadata.version]
provider = "scikit_build_core.metadata.regex"
input = "CMakeLists.txt"
regex = 'test VERSION\s+(?P<value>[0-9.]+)'

[[tool.scikit-build.generate]]
path = "test/_version.py"
template = '''
version = "${version}"
'''

[tool.cibuildwheel]
build-verbosity = 3

[tool.cibuildwheel.windows]
repair-wheel-command = "bash repair_wheel_windows.sh {wheel} {dest_dir}"
name: Python tests

on:
  push:
    branches:
      - main
  pull_request:
  workflow_dispatch:

jobs:
  pybind:
    name: Test Python (${{ matrix.os }}, ${{ matrix.python-version }})
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [windows-11-arm]
        python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
        exclude:
          - os: windows-11-arm
            python-version: "3.9"
          - os: windows-11-arm
            python-version: "3.10"

    steps:
    - uses: actions/checkout@v4
      with:
        submodules: true

    - uses: msys2/setup-msys2@v2
      with:
        msystem: CLANGARM64
        update: true
        install: |
          mingw-w64-clang-aarch64-cmake
          mingw-w64-clang-aarch64-clang
          mingw-w64-clang-aarch64-flang
          mingw-w64-clang-aarch64-hdf5
          mingw-w64-clang-aarch64-ninja
          mingw-w64-clang-aarch64-netcdf
          mingw-w64-clang-aarch64-netcdf-fortran
          mingw-w64-clang-aarch64-openblas
          mingw-w64-clang-aarch64-pkgconf

    - uses: actions/setup-python@v5
      with:
        python-version: ${{ matrix.python-version }}

    - name: Add python build tools
      run: python -m pip install --upgrade wheel setuptools build scikit-build-core

    - name: Build wheel (Windows)  
      if: startsWith(matrix.os, 'windows')
      shell: msys2 {0}
      run: |
        cd pybind
        MSYS2_PYTHON=$(cygpath -u "$pythonLocation")
        "$MSYS2_PYTHON/python.exe" -m build --wheel --no-isolation --outdir wheelhouse

Is this a regression? Put the last known working version here if it is.

Not a regression

Metadata

Metadata

Assignees

No one assigned

    Labels

    triageNew bug, unverified

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions