From ae9f6975f7f295d670d09243168d2d83b8d1537c Mon Sep 17 00:00:00 2001 From: Nizan Mandelblit Date: Fri, 10 Oct 2025 12:17:57 +0200 Subject: [PATCH 1/4] feat: Add pkg-config support - Added support for generating and installing a .pc file for pkg-config - Controlled by the SENTRY_INSTALL_PKGCONFIG CMake option - Uses CMAKE_INSTALL_LIBDIR and CMAKE_INSTALL_INCLUDEDIR to support non-standard layouts (e.g., lib64) - Installs the generated file to the standard pkg-config directory --- CHANGELOG.md | 1 + CMakeLists.txt | 27 +++++++++++++++++++++++++++ sentry.pc.in | 11 +++++++++++ 3 files changed, 39 insertions(+) create mode 100644 sentry.pc.in diff --git a/CHANGELOG.md b/CHANGELOG.md index 26067c939c..ea1506fcc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Add `sentry_scope_begin_read`/`sentry_scope_end_read` and `sentry_scope_begin_write`/`sentry_scope_end_write` for consistent reads of multiple scope properties and applying multiple scope changes with a single scope flush. ([#2107](https://github.com/getsentry/sentry-native/pull/2107)) - Add `sentry_scope_acquire` and `sentry_scope_release` to access the isolation scope. ([#2160](https://github.com/getsentry/sentry-native/pull/2160)) - Native: Provide exception metadata, registers, and a basic crashing-thread stack to `on_crash` for crash identification. ([#2174](https://github.com/getsentry/sentry-native/pull/2174)) +- Add pkg-config support. ([#1411](https://github.com/getsentry/sentry-native/pull/1411)) **Fixes**: diff --git a/CMakeLists.txt b/CMakeLists.txt index 29647c24c0..26bac9f032 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -244,6 +244,7 @@ if("${CMAKE_SOURCE_DIR}" STREQUAL "${PROJECT_SOURCE_DIR}") endif() option(SENTRY_ENABLE_INSTALL "Enable sentry installation" "${SENTRY_MAIN_PROJECT}") +option(SENTRY_INSTALL_PKGCONFIG "Generate and install pkg-config file" ON) if(MSVC AND CMAKE_GENERATOR_TOOLSET MATCHES "_xp$") message(WARNING "Crashpad is not supported for MSVC with XP toolset. Default backend was switched to 'breakpad'") @@ -1197,3 +1198,29 @@ if(SENTRY_BUILD_SHARED_LIBS) "$<$:-Wl,-z,max-page-size=16384>" ) endif() + +# Generate and install pkg-config file +if(SENTRY_INSTALL_PKGCONFIG) + set(PREFIX "${CMAKE_INSTALL_PREFIX}") + set(PKGCONFIG_VERSION "${SENTRY_VERSION_FULL}") + # Use absolute paths for libdir and includedir to support non-standard layouts (e.g., lib64) + if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") + set(PKGCONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}") + else() + set(PKGCONFIG_LIBDIR "\${prefix}/${CMAKE_INSTALL_LIBDIR}") + endif() + if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") + set(PKGCONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}") + else() + set(PKGCONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") + endif() + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/sentry.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/sentry.pc" + @ONLY + ) + sentry_install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/sentry.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" + ) +endif() diff --git a/sentry.pc.in b/sentry.pc.in new file mode 100644 index 0000000000..2162f34568 --- /dev/null +++ b/sentry.pc.in @@ -0,0 +1,11 @@ +prefix=@PREFIX@ +exec_prefix=${prefix} +libdir=@PKGCONFIG_LIBDIR@ +includedir=@PKGCONFIG_INCLUDEDIR@ + +Name: sentry +Description: Sentry SDK for C, C++ and native applications +Version: @PKGCONFIG_VERSION@ +URL: https://sentry.io/ +Libs: -L${libdir} -lsentry +Cflags: -I${includedir} From df263d989a36f436e258dab6a9b6a1a9e1031f4e Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 16 Oct 2026 10:18:41 +0200 Subject: [PATCH 2/4] feat(pkgconfig): Expose the installed binary directory Export `bindir` in `sentry.pc` so consumers can locate the installed crash handler. Honor `CMAKE_INSTALL_BINDIR` when installing the native handler and its Windows companion so the metadata matches the layout. Add a standalone consumer that links through pkg-config and initializes the default backend using the installed handler and a writable database directory. --- CMakeLists.txt | 11 ++++-- sentry.pc.in | 1 + tests/cmake.py | 18 +++++---- tests/fixtures/pkgconfig/CMakeLists.txt | 15 ++++++++ tests/fixtures/pkgconfig/main.c | 20 ++++++++++ tests/test_build.py | 49 ++++++++++++++++++++++++- 6 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 tests/fixtures/pkgconfig/CMakeLists.txt create mode 100644 tests/fixtures/pkgconfig/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 26bac9f032..36d3aa3c5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1022,11 +1022,11 @@ elseif(SENTRY_BACKEND_NATIVE) # Install daemon install(TARGETS sentry-crash - RUNTIME DESTINATION bin + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" ) if(WIN32 AND NOT XBOX) install(TARGETS sentry-wer - RUNTIME DESTINATION bin + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" ) @@ -1203,7 +1203,7 @@ endif() if(SENTRY_INSTALL_PKGCONFIG) set(PREFIX "${CMAKE_INSTALL_PREFIX}") set(PKGCONFIG_VERSION "${SENTRY_VERSION_FULL}") - # Use absolute paths for libdir and includedir to support non-standard layouts (e.g., lib64) + # Use absolute paths for libdir, includedir, and bindir to support non-standard layouts (e.g., lib64) if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") set(PKGCONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}") else() @@ -1214,6 +1214,11 @@ if(SENTRY_INSTALL_PKGCONFIG) else() set(PKGCONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") endif() + if(IS_ABSOLUTE "${CMAKE_INSTALL_BINDIR}") + set(PKGCONFIG_BINDIR "${CMAKE_INSTALL_BINDIR}") + else() + set(PKGCONFIG_BINDIR "\${prefix}/${CMAKE_INSTALL_BINDIR}") + endif() configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/sentry.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/sentry.pc" diff --git a/sentry.pc.in b/sentry.pc.in index 2162f34568..0de461e287 100644 --- a/sentry.pc.in +++ b/sentry.pc.in @@ -2,6 +2,7 @@ prefix=@PREFIX@ exec_prefix=${prefix} libdir=@PKGCONFIG_LIBDIR@ includedir=@PKGCONFIG_INCLUDEDIR@ +bindir=@PKGCONFIG_BINDIR@ Name: sentry Description: Sentry SDK for C, C++ and native applications diff --git a/tests/cmake.py b/tests/cmake.py index 0da3d40532..d69e922faf 100644 --- a/tests/cmake.py +++ b/tests/cmake.py @@ -23,7 +23,7 @@ def __init__(self, factory): self.runs = dict() self.factory = factory - def compile(self, targets, options=None, cflags=None): + def compile(self, targets, options=None, cflags=None, source_dir=None): # We build in tmp for all of our tests. Disable the warning MSVC generates to not clutter the build logs. if cflags is None: cflags = [] @@ -35,11 +35,12 @@ def compile(self, targets, options=None, cflags=None): key = ";".join(f"{k}={v}" for k, v in options.items()) if cflags: key += ";" + ";".join(cflags) + key = (source_dir, key) # cache the build configuration if key not in self.runs: cwd = self.factory.mktemp("cmake") - cmake_configure(cwd, options, cflags) + cmake_configure(cwd, options, cflags, source_dir) cmake_build(cwd, targets, options) self.runs[key] = (cwd, set(targets)) else: @@ -97,6 +98,7 @@ def destroy(self): exe_name("sentry-crash"), exe_name("sentry_early_init"), exe_name("sentry_test_platform"), + exe_name("sentry_test_pkgconfig"), ] cmd = [ os.environ.get("LLVM_COV", "llvm-cov"), @@ -129,7 +131,7 @@ def destroy(self): ) -def cmake_configure(cwd, options, cflags=None): +def cmake_configure(cwd, options, cflags=None, source_dir=None): if cflags is None: cflags = [] __tracebackhide__ = True @@ -166,7 +168,8 @@ def cmake_configure(cwd, options, cflags=None): options.update(get_android_config()) - source_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + if source_dir is None: + source_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) cmake = ["cmake"] if "scan-build" in os.environ.get("RUN_ANALYZER", ""): @@ -202,11 +205,11 @@ def cmake_configure(cwd, options, cflags=None): cflags.extend(get_cflags()) if "llvm-cov" in os.environ.get("RUN_ANALYZER", ""): - configure_llvm_cov(config_cmd) + configure_llvm_cov(config_cmd, cflags) env = dict(os.environ) env["CFLAGS"] = env["CXXFLAGS"] = " ".join(cflags) - config_cmd.append(source_dir) + config_cmd.append(os.fspath(source_dir)) print("\n{} > {}".format(cwd, " ".join(config_cmd)), flush=True) try: @@ -328,7 +331,7 @@ def configure_clang_cl(config_cmd: list[str]): config_cmd.append("-T ClangCL") -def configure_llvm_cov(config_cmd: list[str]): +def configure_llvm_cov(config_cmd: list[str], cflags: list[str]): def cmake_flags(name: str, value: str) -> str: if os.environ.get("VS_GENERATOR_TOOLSET") == "ClangCL": return f"-D{name}={value}" @@ -359,6 +362,7 @@ def cmake_flags(name: str, value: str) -> str: config_cmd.append(f"-DCMAKE_EXE_LINKER_FLAGS={profile_lib}") config_cmd.append(f"-DCMAKE_SHARED_LINKER_FLAGS={profile_lib}") config_cmd.append(f"-DCMAKE_MODULE_LINKER_FLAGS={profile_lib}") + flags = " ".join([*cflags, flags]) config_cmd.append(cmake_flags("CMAKE_C_FLAGS", flags)) # Since we overwrite `CXXFLAGS` below, we must add the experimental library here for the GHA runner that builds diff --git a/tests/fixtures/pkgconfig/CMakeLists.txt b/tests/fixtures/pkgconfig/CMakeLists.txt new file mode 100644 index 0000000000..6cbfe3e90f --- /dev/null +++ b/tests/fixtures/pkgconfig/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.10) +project(sentry_pkgconfig C) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(SENTRY REQUIRED IMPORTED_TARGET sentry) + +pkg_get_variable(SENTRY_BINDIR sentry bindir) +find_program(SENTRY_HANDLER_PATH sentry-crash + PATHS "${SENTRY_BINDIR}" + NO_DEFAULT_PATH ONLY_CMAKE_FIND_ROOT_PATH) + +add_executable(sentry_test_pkgconfig main.c) +target_link_libraries(sentry_test_pkgconfig PRIVATE PkgConfig::SENTRY) +target_compile_definitions(sentry_test_pkgconfig PRIVATE + SENTRY_HANDLER_PATH=\"${SENTRY_HANDLER_PATH}\") diff --git a/tests/fixtures/pkgconfig/main.c b/tests/fixtures/pkgconfig/main.c new file mode 100644 index 0000000000..933d085519 --- /dev/null +++ b/tests/fixtures/pkgconfig/main.c @@ -0,0 +1,20 @@ +#include +#include + +int +main(void) +{ + const char *database = getenv("SENTRY_DATABASE_PATH"); + if (!database) { + return 1; + } + + sentry_options_t *options = sentry_options_new(); + sentry_options_set_handler_path(options, SENTRY_HANDLER_PATH); + sentry_options_set_database_path(options, database); + if (sentry_init(options) != 0) { + return 1; + } + sentry_close(); + return 0; +} diff --git a/tests/test_build.py b/tests/test_build.py index 956a90789d..807a8f3485 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -2,10 +2,14 @@ import sys import os import struct +import shutil +import shlex +from pathlib import Path import pytest -from . import SENTRY_VERSION, lib_name +from . import SENTRY_VERSION, lib_name, run +from .build_config import get_test_executable_cflags, get_test_executable_env from .conditions import ( has_breakpad, has_crashpad, @@ -193,3 +197,46 @@ def test_static_mpack(cmake): assert any( symbol.startswith("sentry__mpack_") for symbol in symbols ), f"{library.name} contains no Sentry-prefixed MPack symbols" + + +@pytest.mark.skipif( + sys.platform not in ("linux", "darwin") or not has_native or is_qemu or is_wine, + reason="test requires a native Unix build", +) +def test_pkgconfig(cmake, tmp_path, monkeypatch): + if shutil.which("pkg-config") is None: + pytest.skip("pkg-config is not installed") + + destdir = tmp_path / "install" + directory = cmake(["sentry"], {"SENTRY_TRANSPORT": "none"}) + subprocess.run( + ["cmake", "--install", directory], + env=dict(os.environ, DESTDIR=str(destdir)), + check=True, + ) + + (pc,) = destdir.rglob("sentry.pc") + (library,) = destdir.rglob(lib_name("sentry")) + _assert_shared_version(library.parent) + + monkeypatch.setenv("PKG_CONFIG_PATH", str(pc.parent)) + monkeypatch.setenv("PKG_CONFIG_LIBDIR", str(pc.parent)) + monkeypatch.setenv("PKG_CONFIG_SYSROOT_DIR", str(destdir)) + env = get_test_executable_env() + version = subprocess.check_output( + ["pkg-config", "--modversion", "sentry"], env=env, text=True + ) + assert version.strip() == SENTRY_VERSION + flags = subprocess.check_output( + ["pkg-config", "--libs-only-L", "sentry"], env=env, text=True + ) + assert shlex.split(flags) == [f"-L{library.parent}"] + + consumer = cmake( + ["sentry_test_pkgconfig"], + {"CMAKE_FIND_ROOT_PATH": str(destdir)}, + cflags=get_test_executable_cflags(), + source_dir=Path(__file__).parent / "fixtures" / "pkgconfig", + ) + env["SENTRY_DATABASE_PATH"] = str(tmp_path / "database") + run(consumer, "sentry_test_pkgconfig", [], env=env) From e652e396f1817e1358646a09dabfbe927aabdec8 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 16 Oct 2026 15:41:01 +0200 Subject: [PATCH 3/4] copy what inproc_stress_test does --- tests/cmake.py | 5 ++--- tests/fixtures/pkgconfig/CMakeLists.txt | 22 ++++++++++++++++++++++ tests/test_build.py | 3 +-- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/tests/cmake.py b/tests/cmake.py index d69e922faf..1eb7aef0cc 100644 --- a/tests/cmake.py +++ b/tests/cmake.py @@ -205,7 +205,7 @@ def cmake_configure(cwd, options, cflags=None, source_dir=None): cflags.extend(get_cflags()) if "llvm-cov" in os.environ.get("RUN_ANALYZER", ""): - configure_llvm_cov(config_cmd, cflags) + configure_llvm_cov(config_cmd) env = dict(os.environ) env["CFLAGS"] = env["CXXFLAGS"] = " ".join(cflags) @@ -331,7 +331,7 @@ def configure_clang_cl(config_cmd: list[str]): config_cmd.append("-T ClangCL") -def configure_llvm_cov(config_cmd: list[str], cflags: list[str]): +def configure_llvm_cov(config_cmd: list[str]): def cmake_flags(name: str, value: str) -> str: if os.environ.get("VS_GENERATOR_TOOLSET") == "ClangCL": return f"-D{name}={value}" @@ -362,7 +362,6 @@ def cmake_flags(name: str, value: str) -> str: config_cmd.append(f"-DCMAKE_EXE_LINKER_FLAGS={profile_lib}") config_cmd.append(f"-DCMAKE_SHARED_LINKER_FLAGS={profile_lib}") config_cmd.append(f"-DCMAKE_MODULE_LINKER_FLAGS={profile_lib}") - flags = " ".join([*cflags, flags]) config_cmd.append(cmake_flags("CMAKE_C_FLAGS", flags)) # Since we overwrite `CXXFLAGS` below, we must add the experimental library here for the GHA runner that builds diff --git a/tests/fixtures/pkgconfig/CMakeLists.txt b/tests/fixtures/pkgconfig/CMakeLists.txt index 6cbfe3e90f..d7b2a340a3 100644 --- a/tests/fixtures/pkgconfig/CMakeLists.txt +++ b/tests/fixtures/pkgconfig/CMakeLists.txt @@ -1,6 +1,11 @@ cmake_minimum_required(VERSION 3.10) project(sentry_pkgconfig C) +# Options that match the main sentry-native build +option(SENTRY_BUILD_FORCE32 "Force a 32bit compile on a 64bit host" OFF) +option(WITH_ASAN_OPTION "Build with address sanitizer" OFF) +option(WITH_TSAN_OPTION "Build with thread sanitizer" OFF) + find_package(PkgConfig REQUIRED) pkg_check_modules(SENTRY REQUIRED IMPORTED_TARGET sentry) @@ -13,3 +18,20 @@ add_executable(sentry_test_pkgconfig main.c) target_link_libraries(sentry_test_pkgconfig PRIVATE PkgConfig::SENTRY) target_compile_definitions(sentry_test_pkgconfig PRIVATE SENTRY_HANDLER_PATH=\"${SENTRY_HANDLER_PATH}\") + +if(NOT MSVC) + if(SENTRY_BUILD_FORCE32) + target_compile_options(sentry_test_pkgconfig PRIVATE -m32) + target_link_libraries(sentry_test_pkgconfig PRIVATE -m32) + endif() + + if(WITH_ASAN_OPTION) + target_compile_options(sentry_test_pkgconfig PRIVATE -fsanitize=address) + target_link_libraries(sentry_test_pkgconfig PRIVATE -fsanitize=address) + endif() + + if(WITH_TSAN_OPTION) + target_compile_options(sentry_test_pkgconfig PRIVATE -fsanitize=thread) + target_link_libraries(sentry_test_pkgconfig PRIVATE -fsanitize=thread) + endif() +endif() diff --git a/tests/test_build.py b/tests/test_build.py index 807a8f3485..15fa22a2da 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -9,7 +9,7 @@ import pytest from . import SENTRY_VERSION, lib_name, run -from .build_config import get_test_executable_cflags, get_test_executable_env +from .build_config import get_test_executable_env from .conditions import ( has_breakpad, has_crashpad, @@ -235,7 +235,6 @@ def test_pkgconfig(cmake, tmp_path, monkeypatch): consumer = cmake( ["sentry_test_pkgconfig"], {"CMAKE_FIND_ROOT_PATH": str(destdir)}, - cflags=get_test_executable_cflags(), source_dir=Path(__file__).parent / "fixtures" / "pkgconfig", ) env["SENTRY_DATABASE_PATH"] = str(tmp_path / "database") From 758b72334c5474c0100f00cd52b6fe0d820a001f Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 29 Oct 2026 11:17:57 +0100 Subject: [PATCH 4/4] fix(pkgconfig): Install sentry.pc only for shared builds A static `libsentry` needs its full private link graph, including vendored backend archives, system libraries and the C++ runtime. The CMake package already exports that graph. Install `sentry.pc` only for shared builds, where `-lsentry` is complete. --- CHANGELOG.md | 2 +- CMakeLists.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b53605eb3e..1208a49d40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ - Add `sentry_scope_acquire` and `sentry_scope_release` to access the isolation scope. ([#2160](https://github.com/getsentry/sentry-native/pull/2160)) - Native: Provide exception metadata, registers, and a basic crashing-thread stack to `on_crash` for crash identification. ([#2174](https://github.com/getsentry/sentry-native/pull/2174)) - Native: reduce disk I/O on scope changes by sending incremental updates to the crash daemon over IPC. ([#2155](https://github.com/getsentry/sentry-native/pull/2155), [#2157](https://github.com/getsentry/sentry-native/pull/2157)) -- Add pkg-config support. ([#1411](https://github.com/getsentry/sentry-native/pull/1411)) +- Add pkg-config support for shared builds. ([#1411](https://github.com/getsentry/sentry-native/pull/1411)) - Native: install handlers for the daemon's own fatal signals and unhandled exceptions, allowing best-effort recovery actions such as dumping pending envelopes before termination. ([#2220](https://github.com/getsentry/sentry-native/pull/2220)) - Report the host OS in the OS context under Wine and Proton, instead of the Windows version that Wine emulates. On Linux, prefer the host `os-release` inside Steam Runtime and Flatpak containers, and report SteamOS and Bazzite as the OS name. ([#2241](https://github.com/getsentry/sentry-native/pull/2241)) - Add `sentry_scope_set_trace` for per-scope traces. ([#2236](https://github.com/getsentry/sentry-native/pull/2236)) diff --git a/CMakeLists.txt b/CMakeLists.txt index 355b3e80a4..10c5f8bf73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -246,7 +246,7 @@ if("${CMAKE_SOURCE_DIR}" STREQUAL "${PROJECT_SOURCE_DIR}") endif() option(SENTRY_ENABLE_INSTALL "Enable sentry installation" "${SENTRY_MAIN_PROJECT}") -option(SENTRY_INSTALL_PKGCONFIG "Generate and install pkg-config file" ON) +option(SENTRY_INSTALL_PKGCONFIG "Generate and install pkg-config file for shared builds" ON) if(MSVC AND CMAKE_GENERATOR_TOOLSET MATCHES "_xp$") message(WARNING "Crashpad is not supported for MSVC with XP toolset. Default backend was switched to 'breakpad'") @@ -1212,7 +1212,7 @@ if(SENTRY_BUILD_SHARED_LIBS) endif() # Generate and install pkg-config file -if(SENTRY_INSTALL_PKGCONFIG) +if(SENTRY_INSTALL_PKGCONFIG AND SENTRY_BUILD_SHARED_LIBS) set(PREFIX "${CMAKE_INSTALL_PREFIX}") set(PKGCONFIG_VERSION "${SENTRY_VERSION_FULL}") # Use absolute paths for libdir, includedir, and bindir to support non-standard layouts (e.g., lib64)