Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## Unreleased (1.0.0)

**Breaking / Important behavior changes**:

- macOS/Linux: shared libraries now use ABI-versioned filenames, such as `libsentry.1.dylib` or `libsentry.so.1` for 1.x. Windows, Android, and static libraries remain unchanged. ([#2103](https://github.com/getsentry/sentry-native/pull/2103))
- NOTE: Applications must package the real versioned library and its symlinks, including `libsentry.1.dylib` or `libsentry.so.1`, because they link to and load the ABI-versioned name.

## Unreleased

**Breaking / Important behavior changes**:
Expand Down
21 changes: 21 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,27 @@ add_library(sentry ${SENTRY_LIBRARY_TYPE} "${PROJECT_SOURCE_DIR}/src/sentry_mpac
if (XBOX)
set_target_properties(sentry PROPERTIES VS_USER_PROPS gdk_build.props)
endif()

set_target_properties(sentry PROPERTIES
VERSION "${SENTRY_VERSION_BASE}"
SOVERSION "${SENTRY_VERSION_MAJOR}")
Comment thread
jpnurmi marked this conversation as resolved.

if(WIN32 AND SENTRY_BUILD_SHARED_LIBS)
set(_SENTRY_IMAGE_VERSION "${SENTRY_VERSION_MAJOR}.${SENTRY_VERSION_MINOR}")
if(CMAKE_GENERATOR MATCHES "^Visual Studio " AND CMAKE_VERSION VERSION_LESS 4.1)
# VS generators before CMake 4.1 ignore VERSION for the PE image header.
target_link_options(sentry PRIVATE "/VERSION:${_SENTRY_IMAGE_VERSION}")
elseif(MINGW)
include(CheckLinkerFlag)
check_linker_flag(C "LINKER:/version:0.0" _SENTRY_LINKER_SUPPORTS_PE_VERSION)
if(_SENTRY_LINKER_SUPPORTS_PE_VERSION)
# lld silently ignores CMake's GNU image-version flags.
target_link_options(sentry PRIVATE "LINKER:/version:${_SENTRY_IMAGE_VERSION}")
endif()
endif()
unset(_SENTRY_IMAGE_VERSION)
endif()

target_sources(sentry PRIVATE "${PROJECT_SOURCE_DIR}/include/sentry.h")
add_library(sentry::sentry ALIAS sentry)

Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,17 @@ install
└── lib
├── cmake
│ └── sentry
├── libsentry.dylib
└── libsentry.dylib.dSYM
├── libsentry.1.0.0.dylib
├── libsentry.1.dylib -> libsentry.1.0.0.dylib
├── libsentry.dylib -> libsentry.1.dylib
└── libsentry.1.0.0.dylib.dSYM
```

Please refer to the CMake Manual for more details.

Starting with 1.0.0, shared libraries on macOS and Linux use ABI-versioned
runtime names such as `libsentry.1.dylib` or `libsentry.so.1`.

**Android**:

The CMake project can also be configured to correctly work with the Android NDK,
Expand Down
54 changes: 54 additions & 0 deletions tests/test_build_static.py → tests/test_build.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,71 @@
import subprocess
import sys
import os
import struct

import pytest

from . import SENTRY_VERSION, lib_name
from .conditions import (
has_breakpad,
has_crashpad,
has_native,
is_aix,
is_android,
is_qemu,
is_wine,
)


def _assert_symlink(path, target):
assert path.is_symlink()
assert os.readlink(path) == target


def _assert_shared_version(directory):
major, minor, _ = SENTRY_VERSION.split(".")

if sys.platform == "darwin":
real_name = f"libsentry.{SENTRY_VERSION}.dylib"
abi_name = f"libsentry.{major}.dylib"
_assert_symlink(directory / "libsentry.dylib", abi_name)
_assert_symlink(directory / abi_name, real_name)
output = subprocess.check_output(
["otool", "-L", directory / real_name], text=True
)
assert abi_name in output
assert f"compatibility version {major}.0.0" in output
assert f"current version {SENTRY_VERSION}" in output
elif sys.platform == "win32":
dll = directory / lib_name("sentry")
with dll.open("rb") as binary:
binary.seek(0x3C)
pe_offset = struct.unpack("<I", binary.read(4))[0]
binary.seek(pe_offset + 4 + 20 + 44)
assert struct.unpack("<HH", binary.read(4)) == (int(major), int(minor))
Comment thread
cursor[bot] marked this conversation as resolved.
elif is_wine:
assert (directory / "libsentry.dll").is_file()
else:
real_name = f"libsentry.so.{SENTRY_VERSION}"
abi_name = f"libsentry.so.{major}"
_assert_symlink(directory / "libsentry.so", abi_name)
_assert_symlink(directory / abi_name, real_name)
output = subprocess.check_output(
["readelf", "-d", directory / real_name], text=True
)
assert f"Library soname: [{abi_name}]" in output


@pytest.mark.skipif(is_android or is_aix, reason="test requires a desktop build")
def test_shared_lib(cmake):
directory = cmake(
["sentry"],
{"SENTRY_BACKEND": "none", "SENTRY_TRANSPORT": "none"},
)

_assert_shared_version(directory)


def test_static_lib(cmake):
tmp_path = cmake(
["sentry_example"],
Expand Down
9 changes: 8 additions & 1 deletion tests/test_integration_crashpad.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
is_replay_envelope,
lib_name,
REPLAY_ID,
SENTRY_VERSION,
)
from .conditions import has_crashpad, has_files, has_oom
from .proxy import (
Expand Down Expand Up @@ -160,7 +161,13 @@ def test_crashpad_codeview(cmake, httpserver):
name.replace("\\", "/").rsplit("/", 1)[-1]: codeview
for name, codeview in _minidump_modules(attachments.minidump)
}
codeview = codeviews[lib_name("sentry")]
library = lib_name("sentry")
major = SENTRY_VERSION.split(".", 1)[0]
if sys.platform == "linux":
library = f"{library}.{major}"
elif sys.platform == "darwin":
library = library.replace(".dylib", f".{SENTRY_VERSION}.dylib")
codeview = codeviews[library]
signature = codeview[:4]
if sys.platform == "linux":
assert signature == b"LEpB"
Expand Down
20 changes: 13 additions & 7 deletions tests/test_integration_macos_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ def _create_sandbox_app_bundle(tmp_path, exe_name="sentry_example"):
app_dir = os.path.join(str(tmp_path), "SentryTest.app")
contents_dir = os.path.join(app_dir, "Contents")
macos_dir = os.path.join(contents_dir, "MacOS")
os.makedirs(macos_dir, exist_ok=True)
if os.path.exists(app_dir):
shutil.rmtree(app_dir)
os.makedirs(macos_dir)

# Copy executable and sentry-crash into the bundle
src_exe = os.path.join(str(tmp_path), exe_name)
Expand All @@ -48,11 +50,13 @@ def _create_sandbox_app_bundle(tmp_path, exe_name="sentry_example"):
if os.path.exists(src_daemon):
shutil.copy2(src_daemon, dst_daemon)

# Copy libsentry.dylib if it exists (shared library build)
src_lib = os.path.join(str(tmp_path), "libsentry.dylib")
if os.path.exists(src_lib):
dst_lib = os.path.join(macos_dir, "libsentry.dylib")
shutil.copy2(src_lib, dst_lib)
# Copy the shared library and its versioned symlink chain into the bundle
for src_lib in tmp_path.glob("libsentry*.dylib"):
shutil.copy2(
src_lib,
os.path.join(macos_dir, src_lib.name),
follow_symlinks=False,
)

# Write minimal Info.plist
info_plist = {
Expand Down Expand Up @@ -97,6 +101,8 @@ def _codesign_bundle(app_dir, entitlements_path, exe_name="sentry_example"):
if name == exe_name:
continue # Main exe gets signed with the bundle
path = os.path.join(macos_dir, name)
if os.path.islink(path):
continue
if os.access(path, os.X_OK) or name.endswith(".dylib"):
subprocess.run(
["codesign", "--force", "--sign", "-", path],
Expand Down Expand Up @@ -128,7 +134,7 @@ def _run_sandboxed(app_dir, exe_name, args, env, expect_failure=False):
"""
exe_path = os.path.join(app_dir, "Contents", "MacOS", exe_name)

# Set DYLD_LIBRARY_PATH so the executable can find libsentry.dylib
# Set DYLD_LIBRARY_PATH so the executable can find the bundled dylib
# inside the bundle (dyld respects this even in sandbox for ad-hoc signed)
run_env = dict(env)
run_env["DYLD_LIBRARY_PATH"] = os.path.join(app_dir, "Contents", "MacOS")
Expand Down
Loading