Skip to content

Commit 516c150

Browse files
vaindclaude
andauthored
feat: Add SENTRY_SDK_VERSION CMake override for downstream SDKs (#1417)
* feat: Auto-split build ID from version in sentry_library_info When downstream SDKs modify sentry.h to include build metadata in the version string (e.g., 0.11.3+20251016-9e31c9f-dirty), the embedded library info now automatically extracts the build ID from the version. Changes: - Parse build metadata from SENTRY_VERSION_FULL if present - Use base version (major.minor.patch) for SENTRY_VERSION field - Use extracted build ID for BUILD field - SENTRY_BUILD_ID cache variable still takes precedence - Update template to use new SENTRY_EMBEDDED_VERSION and SENTRY_EMBEDDED_BUILD_ID - Update tests to validate base version format - Add test for build ID field This ensures the embedded info format is: SENTRY_VERSION:0.11.3;BUILD:20251016-9e31c9f-dirty instead of: SENTRY_VERSION:0.11.3+20251016-9e31c9f-dirty;BUILD:0.11.3+20251016-9e31c9f-dirty 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * refactor: Use SENTRY_VERSION_BASE directly instead of SENTRY_EMBEDDED_VERSION Simplifies the code by reusing the existing SENTRY_VERSION_BASE variable which already contains the major.minor.patch format without build metadata. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * refactor: Skip parsing if SENTRY_BUILD_ID is already set Optimizes the logic to check SENTRY_BUILD_ID cache variable first. Only attempts to extract build ID from version string if not explicitly provided. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * refactor: Simplify build ID logic with if-elseif-else chain Cleaner and more readable conditional structure. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * fix: Use semver-compliant format for default build ID timestamp According to semver.org spec, build metadata must contain only ASCII alphanumerics and hyphens [0-9A-Za-z-]. Spaces are not allowed. Changed from: "2025-10-17 10:59:00 UTC" Changed to: "20251017-105900" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * docs: Add changelog entry for automatic build ID extraction 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * feat: Add SENTRY_SDK_VERSION cache variable for downstream SDK override Downstream SDKs can now override the SDK version at CMake configuration time using -DSENTRY_SDK_VERSION="version+build-id". This allows setting the version without modifying sentry.h. When SENTRY_SDK_VERSION is set: - The full version (with build metadata) is used for embedded library info - Build ID is automatically extracted from the version string - sentry.h uses the overridden version via compile definition - Version parsing happens at CMake configuration time Example usage: cmake -DSENTRY_SDK_VERSION="0.11.3+20251016-9e31c9f-dirty" ... Results in embedded info: SENTRY_VERSION:0.11.3;BUILD:20251016-9e31c9f-dirty;... 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * fix: Check SENTRY_SDK_VERSION for empty string instead of truthiness Aligns with the pattern used for SENTRY_SDK_NAME check. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * fix: Use SENTRY_SDK_VERSION directly for compile definitions instead of SENTRY_VERSION_FULL * test: Add Python tests for SENTRY_SDK_VERSION override Added two new integration tests: 1. test_sdk_version_override: Verifies that setting SENTRY_SDK_VERSION correctly separates version and build ID in the embedded library info. 2. test_sdk_version_override_with_explicit_build_id: Verifies that explicit SENTRY_BUILD_ID takes precedence over extracted build ID from the version string. Both tests inspect the actual binary using the strings command to validate the embedded information. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * fix: Update regex to handle whitespace before SENTRY_SDK_VERSION define The #ifndef guard adds leading whitespace to the #define line, so the regex needs to handle optional leading whitespace and whitespace between tokens. This fixes the issue where CMake couldn't parse the version from sentry.h when SENTRY_SDK_VERSION was not overridden. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * fix: Update changelog entry for SENTRY_SDK_VERSION CMake cache variable to clarify build ID extraction --------- Co-authored-by: Claude <[email protected]>
1 parent 781bfc3 commit 516c150

File tree

7 files changed

+179
-16
lines changed

7 files changed

+179
-16
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
- Add logs flush on crash. This is not available for macOS with the `crashpad` backend. ([#1404](https://github.com/getsentry/sentry-native/pull/1404))
88

9+
**Internal**:
10+
11+
- Add `SENTRY_SDK_VERSION` CMake cache variable to allow downstream SDKs to override the SDK version at configuration time. ([#1417](https://github.com/getsentry/sentry-native/pull/1417))
12+
913
## 0.11.3
1014

1115
**Features**:

CMakeLists.txt

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,34 @@ else()
99
cmake_policy(SET CMP0077 NEW)
1010
endif()
1111

12-
# Extract version string from SENTRY_SDK_VERSION define in sentry.h
13-
file(READ "include/sentry.h" _VERSION_STR_TMP)
14-
# Supports full semver format including prerelease and build metadata
15-
string(REGEX MATCH "#define SENTRY_SDK_VERSION \"([^\"]+)\"" _VERSION_STR_TMP "${_VERSION_STR_TMP}")
16-
set(SENTRY_VERSION_FULL "${CMAKE_MATCH_1}")
12+
# Allow downstream SDKs to override the SDK version at CMake configuration time
13+
set(SENTRY_SDK_VERSION "" CACHE STRING "Override the SDK version (supports full semver format with build metadata)")
14+
15+
# Extract version string from SENTRY_SDK_VERSION cache variable or sentry.h
16+
if(NOT SENTRY_SDK_VERSION STREQUAL "")
17+
set(SENTRY_VERSION_FULL "${SENTRY_SDK_VERSION}")
18+
else()
19+
file(READ "include/sentry.h" _VERSION_STR_TMP)
20+
# Supports full semver format including prerelease and build metadata
21+
# The regex handles optional leading whitespace for the #define
22+
string(REGEX MATCH "#[ \t]*define[ \t]+SENTRY_SDK_VERSION[ \t]+\"([^\"]+)\"" _VERSION_STR_TMP "${_VERSION_STR_TMP}")
23+
set(SENTRY_VERSION_FULL "${CMAKE_MATCH_1}")
24+
unset(_VERSION_STR_TMP)
25+
endif()
26+
1727
# Extract just the major.minor.patch part from the full version
18-
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" _VERSION_STR_TMP "${SENTRY_VERSION_FULL}")
28+
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" _VERSION_PARSE_TMP "${SENTRY_VERSION_FULL}")
1929
set(SENTRY_VERSION_MAJOR "${CMAKE_MATCH_1}")
2030
set(SENTRY_VERSION_MINOR "${CMAKE_MATCH_2}")
2131
set(SENTRY_VERSION_PATCH "${CMAKE_MATCH_3}")
2232
set(SENTRY_VERSION_BASE "${SENTRY_VERSION_MAJOR}.${SENTRY_VERSION_MINOR}.${SENTRY_VERSION_PATCH}")
23-
unset(_VERSION_STR_TMP)
33+
unset(_VERSION_PARSE_TMP)
2434

25-
message(STATUS "Sentry SDK version (full): ${SENTRY_VERSION_FULL}")
35+
if(NOT SENTRY_SDK_VERSION STREQUAL "")
36+
message(STATUS "Sentry SDK version (full): ${SENTRY_VERSION_FULL} (overridden via SENTRY_SDK_VERSION)")
37+
else()
38+
message(STATUS "Sentry SDK version (full): ${SENTRY_VERSION_FULL}")
39+
endif()
2640
message(STATUS "Sentry SDK version (base): ${SENTRY_VERSION_BASE}")
2741
message(STATUS "Sentry SDK version major='${SENTRY_VERSION_MAJOR}' minor='${SENTRY_VERSION_MINOR}' patch='${SENTRY_VERSION_PATCH}'")
2842

@@ -322,9 +336,17 @@ add_library(sentry::sentry ALIAS sentry)
322336

323337
# Configure version embedding if enabled
324338
if(SENTRY_EMBED_INFO)
325-
# Use timestamp as default build ID if not specified
326-
if(NOT SENTRY_BUILD_ID)
327-
string(TIMESTAMP SENTRY_BUILD_ID "%Y-%m-%d %H:%M:%S UTC" UTC)
339+
# SENTRY_BUILD_ID cache variable takes precedence
340+
if(SENTRY_BUILD_ID)
341+
set(SENTRY_EMBEDDED_BUILD_ID "${SENTRY_BUILD_ID}")
342+
elseif(SENTRY_VERSION_FULL MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+\\+(.+)$")
343+
# Extract build ID from SENTRY_VERSION_FULL if present (format: major.minor.patch+build-id)
344+
# This handles cases where downstream SDKs modify sentry.h to include build metadata
345+
set(SENTRY_EMBEDDED_BUILD_ID "${CMAKE_MATCH_1}")
346+
else()
347+
# No build ID found in version or provided, use timestamp as default
348+
# Format: YYYYMMDD-HHMMSS (semver-compliant: alphanumerics and hyphens only)
349+
string(TIMESTAMP SENTRY_EMBEDDED_BUILD_ID "%Y%m%d-%H%M%S" UTC)
328350
endif()
329351

330352
# Validate and escape special characters in custom items if needed
@@ -367,6 +389,12 @@ if (NOT SENTRY_SDK_NAME STREQUAL "")
367389
target_compile_definitions(sentry PRIVATE SENTRY_SDK_NAME="${SENTRY_SDK_NAME}")
368390
endif()
369391

392+
# If SDK version was overridden via CMake, pass it as a compile definition
393+
# This allows sentry.h to use the overridden version
394+
if (NOT SENTRY_SDK_VERSION STREQUAL "")
395+
target_compile_definitions(sentry PRIVATE SENTRY_SDK_VERSION="${SENTRY_SDK_VERSION}")
396+
endif()
397+
370398
# we do not need this on android, only linux
371399
if(LINUX)
372400
target_sources(sentry PRIVATE

include/sentry.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ extern "C" {
7878
# define SENTRY_SDK_NAME "sentry.native"
7979
# endif
8080
#endif
81-
#define SENTRY_SDK_VERSION "0.11.3"
81+
#ifndef SENTRY_SDK_VERSION
82+
# define SENTRY_SDK_VERSION "0.11.3"
83+
#endif
8284
#define SENTRY_SDK_USER_AGENT SENTRY_SDK_NAME "/" SENTRY_SDK_VERSION
8385

8486
/* marks a function as part of the sentry API */

src/sentry_embedded_info.cpp.in

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ extern "C" SENTRY_API const char sentry_library_info[];
66

77
// Definition
88
extern "C" SENTRY_API const char sentry_library_info[] =
9-
"SENTRY_VERSION:" SENTRY_SDK_VERSION ";"
9+
"SENTRY_VERSION:@SENTRY_VERSION_BASE@;"
1010
"PLATFORM:@SENTRY_BUILD_PLATFORM@;"
11-
"BUILD:@SENTRY_BUILD_ID@;"
11+
"BUILD:@SENTRY_EMBEDDED_BUILD_ID@;"
1212
"VARIANT:@SENTRY_BUILD_VARIANT@;"
1313
"CONFIG:@CMAKE_BUILD_TYPE@;"
1414
"@SENTRY_EMBED_INFO_ITEMS@"

tests/test_embedded_info.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,95 @@ def test_embedded_info_custom_items(cmake):
150150
check=True,
151151
env=env,
152152
)
153+
154+
155+
@pytest.mark.skipif(not has_http, reason="test needs http transport (curl)")
156+
def test_sdk_version_override(cmake):
157+
"""Test that SENTRY_SDK_VERSION override works correctly"""
158+
cwd = cmake(
159+
["sentry"],
160+
{
161+
"SENTRY_EMBED_INFO": "ON",
162+
"SENTRY_BUILD_PLATFORM": "version-test",
163+
"SENTRY_BUILD_VARIANT": "override",
164+
"SENTRY_SDK_VERSION": "0.11.3+20251016-test-build",
165+
},
166+
)
167+
168+
# Find the library file
169+
library_file = None
170+
for file in os.listdir(cwd):
171+
if file.startswith("libsentry") and (
172+
file.endswith(".so") or file.endswith(".dylib") or file.endswith(".dll")
173+
):
174+
library_file = os.path.join(cwd, file)
175+
break
176+
177+
if library_file is None:
178+
pytest.skip("Could not find sentry library file")
179+
180+
# Use strings command to check embedded content
181+
try:
182+
result = subprocess.run(
183+
["strings", library_file], capture_output=True, text=True, check=True
184+
)
185+
output = result.stdout
186+
187+
# Verify that version and build ID are correctly separated
188+
assert (
189+
"SENTRY_VERSION:0.11.3;" in output
190+
), "Version should be base version without build metadata"
191+
assert (
192+
"BUILD:20251016-test-build;" in output
193+
), "Build ID should be extracted from version string"
194+
assert "PLATFORM:version-test" in output
195+
assert "VARIANT:override" in output
196+
197+
except (subprocess.CalledProcessError, FileNotFoundError):
198+
pytest.skip("strings command not available or failed")
199+
200+
201+
@pytest.mark.skipif(not has_http, reason="test needs http transport (curl)")
202+
def test_sdk_version_override_with_explicit_build_id(cmake):
203+
"""Test that explicit SENTRY_BUILD_ID takes precedence over extracted build ID"""
204+
cwd = cmake(
205+
["sentry"],
206+
{
207+
"SENTRY_EMBED_INFO": "ON",
208+
"SENTRY_BUILD_PLATFORM": "version-test",
209+
"SENTRY_BUILD_VARIANT": "explicit-build",
210+
"SENTRY_SDK_VERSION": "0.11.3+20251016-ignored",
211+
"SENTRY_BUILD_ID": "explicit-build-id",
212+
},
213+
)
214+
215+
# Find the library file
216+
library_file = None
217+
for file in os.listdir(cwd):
218+
if file.startswith("libsentry") and (
219+
file.endswith(".so") or file.endswith(".dylib") or file.endswith(".dll")
220+
):
221+
library_file = os.path.join(cwd, file)
222+
break
223+
224+
if library_file is None:
225+
pytest.skip("Could not find sentry library file")
226+
227+
# Use strings command to check embedded content
228+
try:
229+
result = subprocess.run(
230+
["strings", library_file], capture_output=True, text=True, check=True
231+
)
232+
output = result.stdout
233+
234+
# Verify that explicit build ID takes precedence
235+
assert "SENTRY_VERSION:0.11.3;" in output, "Version should be base version"
236+
assert (
237+
"BUILD:explicit-build-id;" in output
238+
), "Explicit build ID should take precedence"
239+
assert (
240+
"BUILD:20251016-ignored" not in output
241+
), "Extracted build ID should not be used"
242+
243+
except (subprocess.CalledProcessError, FileNotFoundError):
244+
pytest.skip("strings command not available or failed")

tests/unit/test_embedded_info.c

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,51 @@ SENTRY_TEST(embedded_info_sentry_version)
8686
// Version should contain at least one dot (e.g., "0.10.0")
8787
TEST_CHECK(strchr(embedded_version, '.') != NULL);
8888

89-
// Test that it matches the actual SDK version
90-
TEST_CHECK_STRING_EQUAL(embedded_version, SENTRY_SDK_VERSION);
89+
// Test that embedded version is the base version (major.minor.patch)
90+
// It should NOT contain build metadata (e.g., +build-id)
91+
TEST_CHECK(strchr(embedded_version, '+') == NULL);
92+
93+
// Verify embedded version starts with the same prefix as SENTRY_SDK_VERSION
94+
// (handles cases where SENTRY_SDK_VERSION may include build metadata)
95+
size_t embedded_len = strlen(embedded_version);
96+
TEST_CHECK(
97+
strncmp(embedded_version, SENTRY_SDK_VERSION, embedded_len) == 0);
9198

9299
free(embedded_version);
93100
#else
94101
SKIP_TEST();
95102
#endif
96103
}
97104

105+
SENTRY_TEST(embedded_info_build_id)
106+
{
107+
#ifdef SENTRY_EMBED_INFO
108+
// Test that BUILD field is present and non-empty
109+
const char *build_field = strstr(sentry_library_info, "BUILD:");
110+
TEST_ASSERT(build_field != NULL);
111+
112+
// Extract the build ID value
113+
const char *build_start = build_field + strlen("BUILD:");
114+
const char *build_end = strchr(build_start, ';');
115+
TEST_ASSERT(build_end != NULL);
116+
117+
size_t build_len = build_end - build_start;
118+
TEST_ASSERT(build_len > 0);
119+
120+
// Build ID should not be empty
121+
char *build_id = malloc(build_len + 1);
122+
TEST_ASSERT(build_id != NULL);
123+
strncpy(build_id, build_start, build_len);
124+
build_id[build_len] = '\0';
125+
126+
TEST_CHECK(strlen(build_id) > 0);
127+
128+
free(build_id);
129+
#else
130+
SKIP_TEST();
131+
#endif
132+
}
133+
98134
SENTRY_TEST(embedded_info_disabled)
99135
{
100136
#ifndef SENTRY_EMBED_INFO

tests/unit/tests.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ XX(dsn_with_non_http_scheme_is_invalid)
6969
XX(dsn_without_project_id_is_invalid)
7070
XX(dsn_without_url_scheme_is_invalid)
7171
XX(embedded_info_basic)
72+
XX(embedded_info_build_id)
7273
XX(embedded_info_disabled)
7374
XX(embedded_info_format)
7475
XX(embedded_info_sentry_version)

0 commit comments

Comments
 (0)