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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions