feat(windows): add WINE and Proton runtime context - #1995
Conversation
|
Thank you for the contribution, @GtechGovind! While this implements the old minimal Wine metadata proposed in #1004, there's now a more recent and comprehensive reference implementation in sentry-godot: @limbonaut What do you think? Should we rather port the Godot implementation? |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #1995 +/- ##
==========================================
- Coverage 74.69% 74.29% -0.41%
==========================================
Files 104 104
Lines 26182 26318 +136
Branches 4740 4780 +40
==========================================
- Hits 19557 19552 -5
- Misses 5295 5431 +136
- Partials 1330 1335 +5 🚀 New features to boost your workflow:
|
|
The problem with the bare Wine context from I see no reason why we shouldn't move Godot's implementation into native, maybe even device/OS detection too. Currently, we maintain parallel implementations in Godot and Unreal. Unity would probably need a separate treatment though, since it's based primarily on the .NET SDK. I think it would be best if we also add this in the .NET SDK. What we do in Godot:
What else can we do (that I didn't get to):
One |
|
Hi @GtechGovind, I'm fixing up the basics for Wine and setting up a CI test pipeline at #2001. Once we have it merged, we can update this PR to test the Wine context in an actual Wine environment. I'd prefer it over the current unit test that not only forces us to expose internal details in the module API, but also doesn't guarantee that it actually works on Wine. :) |
…ontext # Conflicts: # CHANGELOG.md
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit be92308. Configure here.
jpnurmi
left a comment
There was a problem hiding this comment.
#2001 has been merged. There's now a dedicated Wine CI job. Let's replace the unit test with an integration test that runs in an actual Wine environment. Here's a starting point:
diff --git a/tests/test_integration_stdout.py b/tests/test_integration_stdout.py
index 54485adc..83cc0da8 100644
--- a/tests/test_integration_stdout.py
+++ b/tests/test_integration_stdout.py
@@ -389,3 +389,32 @@ def test_breakpad_stack_overflow_stdout(cmake, stack_size):
assert_attachment(envelope)
assert_minidump(envelope)
assert_breakpad_crash(envelope)
+
+
+@pytest.mark.skipif(not is_wine, reason="test needs Wine")
+def test_wine_context(cmake):
+ tmp_path = cmake(
+ ["sentry_example"],
+ {
+ "SENTRY_BACKEND": "none",
+ "SENTRY_TRANSPORT": "none",
+ },
+ )
+ env = dict(os.environ)
+ env.pop("STEAM_COMPAT_DATA_PATH", None)
+
+ output = check_output(
+ tmp_path,
+ "sentry_example",
+ ["stdout", "capture-event"],
+ env=env,
+ )
+ context = Envelope.deserialize(output).get_event()["contexts"]["wine"]
+
+ version = subprocess.check_output(["wine", "--version"], text=True).split()[0]
+ assert version.startswith("wine-")
+ assert context == {
+ "type": "runtime",
+ "name": "Wine",
+ "version": version.removeprefix("wine-"),
+ }| *is_proton = false; | ||
| char *compat_path | ||
| = sentry__string_from_wstr(_wgetenv(L"STEAM_COMPAT_DATA_PATH")); | ||
| if (!compat_path || !compat_path[0]) { |
There was a problem hiding this comment.
| if (!compat_path || !compat_path[0]) { | |
| if (sentry__string_empty(compat_path)) { |
There was a problem hiding this comment.
Thank you so much! It's starting to look good, I think. I left a few more minor findings in comments.
P.S. Feel free to let me know if you're tired of iterating the PR, and we can help with the last remaining touches. Thank you bearing with me through the constant rebases and nitpicks. :)
| bool is_proton = false; | ||
| char *proton_version = get_proton_version(&is_proton); |
There was a problem hiding this comment.
Unlike the Godot implementation, which calls Proton detection only after wine_get_version confirms Wine, here Proton filesystem probing happens first. So native Windows processes could perform synchronous reads through Z: even though the resulting context is discarded.
There was a problem hiding this comment.
Agreed. I suggest returning early if the function address didn't resolve.
const sentry__wine_get_version_t wine_get_version
= (sentry__wine_get_version_t)GetProcAddress(ntdll, "wine_get_version");
if (!wine_get_version) {
return sentry_value_new_null();
}| sentry__stringbuilder_cleanup(&sb); | ||
| return NULL; | ||
| } | ||
| return sentry__stringbuilder_into_string(&sb); |
There was a problem hiding this comment.
all three callers convert the result to sentry_path_t. what about returning sentry__path_from_str_owned and changing read_wine_file to accept const sentry_path_t *?
limbonaut
left a comment
There was a problem hiding this comment.
Thanks for adapting it! I've checked against different Proton distributions, and it works well:
Wine: {"type": "runtime", "name": "Wine", "version": "11.15"}
Proton 10.0: {"type": "runtime", "name": "Proton", "version": "10.0-4b"}
Proton Experimental: {"type": "runtime", "name": "Proton Experimental", "version": "11.0-20260805"}
Proton Hotfix: {"type": "runtime", "name": "Proton Hotfix", "version": "20260730"}
GE-Proton10-32: {"type": "runtime", "name": "GE-Proton", "version": "10-32"}
proton_tkg: {"type": "runtime", "name": "Proton Custom", "version": "TKG-proton-experimental.bleeding.edge.10.0.326241.20260312"}
Just needs a little bit more work before we can merge it.
| const char *runtime_name = "Wine"; | ||
| const char *runtime_version = wine_get_version(); | ||
| if (!runtime_version || !runtime_version[0]) { | ||
| return sentry_value_new_null(); |
There was a problem hiding this comment.
Let's move this check after proton version is checked, instead of bailing out here. If this returns emtpy/null before the proton version is ever consulted, the whole context will be dropped.
| return NULL; | ||
| } | ||
|
|
||
| char *version = strpbrk(version_file, " \t"); |
There was a problem hiding this comment.
| char *version = strpbrk(version_file, " \t"); | |
| // Format: "<timestamp> <git-tag>", e.g. "1769167055 proton-10.0-4". | |
| char *version = strpbrk(version_file, " \t"); |
Here, the code is hard follow and needs some context: what are we parsing and extracting. Not every reader will have an easy access to a Linux box with a proton installation. Let's add a comment.
| bool is_proton = false; | ||
| char *proton_version = get_proton_version(&is_proton); |
There was a problem hiding this comment.
Agreed. I suggest returning early if the function address didn't resolve.
const sentry__wine_get_version_t wine_get_version
= (sentry__wine_get_version_t)GetProcAddress(ntdll, "wine_get_version");
if (!wine_get_version) {
return sentry_value_new_null();
}|
|
||
| const char *runtime_name = "Wine"; | ||
| const char *runtime_version = wine_get_version(); | ||
| if (!runtime_version || !runtime_version[0]) { |
There was a problem hiding this comment.
| if (!runtime_version || !runtime_version[0]) { | |
| if (sentry__string_empty(runtime_version)) { |
| return sentry_value_new_null(); | ||
| } | ||
|
|
||
| if (proton_version && proton_version[0]) { |
There was a problem hiding this comment.
| if (proton_version && proton_version[0]) { | |
| if (!sentry__string_empty(proton_version)) { |
| context, "type", sentry_value_new_string("runtime")); | ||
| sentry_value_set_by_key( | ||
| context, "name", sentry_value_new_string(runtime_name)); | ||
| if (runtime_version[0]) { |
There was a problem hiding this comment.
| if (runtime_version[0]) { | |
| if (!sentry__string_empty(runtime_version)) { |

Closes #1004.
Summary
Validation
Notes
The Proton lookup follows the reference implementation in getsentry/sentry-godot#591 while keeping this PR focused on compatibility runtime detection. Broader SteamOS, device, and host OS enrichment can be added separately.