Skip to content

Commit 9c08d07

Browse files
authored
Add sentry-cli downloading tool (#362)
* Add editor interface for downloading sentry-cli * Extract sentry-cli donload logic to separate class * Add sentry-cli status update notification * Move sentry-cli properties to plugin folder * Add sentry-cli properties parsing * Update symbol uploading scripts * Update plugin snapshot * Update CI script * Fix build errors * Fix more build errors * Fix build errors for older engine version * Fix build errors for older engine version * Update changelog * Added ability to re-load Sentry CLI executable
1 parent 9992811 commit 9c08d07

15 files changed

+281
-14
lines changed

.github/workflows/ci.yml

-4
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,6 @@ jobs:
4343
- name: Checkout
4444
uses: actions/checkout@v3
4545

46-
- name: Download CLI
47-
shell: pwsh
48-
run: ./scripts/download-cli.ps1
49-
5046
- uses: actions/download-artifact@v2
5147
with:
5248
name: Android-sdk

.github/workflows/update-dependencies.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
cli:
3838
uses: getsentry/github-workflows/.github/workflows/updater.yml@v2
3939
with:
40-
path: modules/sentry-cli.properties
40+
path: plugin-dev/sentry-cli.properties
4141
name: CLI
4242
secrets:
4343
api-token: ${{ secrets.CI_DEPLOY_KEY }}

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- Add extra crash context for native integration ([#342](https://github.com/getsentry/sentry-unreal/pull/342))
1010
- Add missing plugin settings ([#335](https://github.com/getsentry/sentry-unreal/pull/335))
1111
- Update event context categories for desktop ([#356](https://github.com/getsentry/sentry-unreal/pull/356))
12+
- Add sentry-cli downloading tool ([#362](https://github.com/getsentry/sentry-unreal/pull/362))
1213
- Add breakpad support for Windows ([#363](https://github.com/getsentry/sentry-unreal/pull/363))
1314
- Added Options for enabling platforms & Promoted Builds via the GUI ([#360](https://github.com/getsentry/sentry-unreal/pull/360))
1415

plugin-dev/Config/FilterPlugin.ini

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
; /README.txt
77
; /Extras/...
88
; /Binaries/ThirdParty/*.dll
9-
/Scripts/*
9+
/Scripts/*
10+
/sentry-cli.properties

plugin-dev/Scripts/upload-debug-symbols-win.bat

+5
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ if not exist "%PropertiesFile%" (
5353
exit
5454
)
5555

56+
if not exist "%CliExec%" (
57+
echo Warning: Sentry: Sentry CLI is not configured in plugin settings. Skipping...
58+
exit
59+
)
60+
5661
echo Sentry: Upload started using PropertiesFile '%PropertiesFile%'
5762

5863
set "SENTRY_PROPERTIES=%PropertiesFile%"

plugin-dev/Scripts/upload-debug-symbols.sh

+5
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ if [ ! -f "$SENTRY_PROPERTIES" ]; then
5454
exit
5555
fi
5656

57+
if [ ! -f "$SENTRY_CLI_EXEC" ]; then
58+
echo "Sentry: Sentry CLI is not configured in plugin settings. Skipping..."
59+
exit
60+
fi
61+
5762
echo "Sentry: Upload started using PropertiesFile '$SENTRY_PROPERTIES'"
5863

5964
chmod +x $SENTRY_CLI_EXEC
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright (c) 2023 Sentry. All Rights Reserved.
2+
3+
#include "SentryCliDownloader.h"
4+
5+
#include "HttpModule.h"
6+
#include "Interfaces/IHttpRequest.h"
7+
#include "Interfaces/IHttpResponse.h"
8+
#include "Interfaces/IPluginManager.h"
9+
#include "HAL/PlatformFileManager.h"
10+
#include "GenericPlatform/GenericPlatformFile.h"
11+
#include "Misc/FileHelper.h"
12+
#include "Misc/Paths.h"
13+
14+
#if PLATFORM_WINDOWS
15+
const FString FSentryCliDownloader::SentryCliExecName = TEXT("sentry-cli-Windows-x86_64.exe");
16+
#elif PLATFORM_MAC
17+
const FString FSentryCliDownloader::SentryCliExecName = TEXT("sentry-cli-Darwin-universal");
18+
#elif PLATFORM_LINUX
19+
const FString FSentryCliDownloader::SentryCliExecName = TEXT("sentry-cli-Linux-x86_64");
20+
#endif
21+
22+
void FSentryCliDownloader::Download(const TFunction<void(bool)>& OnCompleted)
23+
{
24+
SentryCliDownloadRequest = FHttpModule::Get().CreateRequest();
25+
26+
SentryCliDownloadRequest->OnProcessRequestComplete().BindLambda([=](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess)
27+
{
28+
if (!bSuccess || !Response.IsValid())
29+
{
30+
OnCompleted(false);
31+
return;
32+
}
33+
34+
const FString SentryCliExecPath = GetSentryCliPath();
35+
36+
FString SentryCliDirPath, SentryCliFilename, SentryCliExtension;
37+
FPaths::Split(SentryCliExecPath, SentryCliDirPath, SentryCliFilename, SentryCliExtension);
38+
39+
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
40+
41+
if (!PlatformFile.DirectoryExists(*SentryCliDirPath))
42+
{
43+
if (!PlatformFile.CreateDirectoryTree(*SentryCliDirPath))
44+
{
45+
OnCompleted(false);
46+
return;
47+
}
48+
}
49+
50+
if(PlatformFile.FileExists(*SentryCliExecPath))
51+
{
52+
if (!PlatformFile.DeleteFile(*SentryCliExecPath))
53+
{
54+
OnCompleted(false);
55+
return;
56+
}
57+
}
58+
59+
FFileHelper::SaveArrayToFile(Response->GetContent(), *SentryCliExecPath);
60+
61+
OnCompleted(true);
62+
});
63+
64+
SentryCliDownloadRequest->SetURL(FString::Printf(TEXT("https://github.com/getsentry/sentry-cli/releases/download/%s/%s"), *GetSentryCliVersion(), *SentryCliExecName));
65+
SentryCliDownloadRequest->SetVerb(TEXT("GET"));
66+
67+
SentryCliDownloadRequest->ProcessRequest();
68+
}
69+
70+
ESentryCliStatus FSentryCliDownloader::GetStatus()
71+
{
72+
if(SentryCliDownloadRequest.IsValid() && SentryCliDownloadRequest->GetStatus() == EHttpRequestStatus::Processing)
73+
{
74+
return ESentryCliStatus::Downloading;
75+
}
76+
77+
if(FPaths::FileExists(GetSentryCliPath()))
78+
{
79+
return ESentryCliStatus::Configured;
80+
}
81+
82+
return ESentryCliStatus::Missing;
83+
}
84+
85+
FString FSentryCliDownloader::GetSentryCliPath() const
86+
{
87+
const FString PluginDir = IPluginManager::Get().FindPlugin(TEXT("Sentry"))->GetBaseDir();
88+
89+
return FPaths::Combine(PluginDir, TEXT("Source"), TEXT("ThirdParty"), TEXT("CLI"), SentryCliExecName);
90+
}
91+
92+
FString FSentryCliDownloader::GetSentryCliVersion() const
93+
{
94+
const FString PluginDir = IPluginManager::Get().FindPlugin(TEXT("Sentry"))->GetBaseDir();
95+
const FString SentryCliPropertiesPath = FPaths::Combine(PluginDir, TEXT("sentry-cli.properties"));
96+
97+
TArray<FString> CliPropertiesContents;
98+
FFileHelper::LoadFileToStringArray(CliPropertiesContents, *SentryCliPropertiesPath);
99+
100+
FString SentryCliVersion;
101+
FParse::Value(*CliPropertiesContents[0], TEXT("version="), SentryCliVersion);
102+
103+
return SentryCliVersion;
104+
}

plugin-dev/Source/SentryEditor/Private/SentrySettingsCustomization.cpp

+114-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "SentrySettingsCustomization.h"
44
#include "SentrySettings.h"
5+
#include "SentryCliDownloader.h"
56

67
#include "DetailCategoryBuilder.h"
78
#include "DetailLayoutBuilder.h"
@@ -10,11 +11,16 @@
1011
#include "Misc/Paths.h"
1112
#include "Misc/ConfigCacheIni.h"
1213
#include "PropertyHandle.h"
14+
#include "Framework/Notifications/NotificationManager.h"
1315
#include "Runtime/Launch/Resources/Version.h"
1416

1517
#include "Widgets/Text/SRichTextBlock.h"
18+
#include "Widgets/Text/STextBlock.h"
1619
#include "Widgets/Layout/SBorder.h"
20+
#include "Widgets/Layout/SWidgetSwitcher.h"
1721
#include "Widgets/Input/SButton.h"
22+
#include "Widgets/Notifications/SNotificationList.h"
23+
#include "Widgets/Images/SImage.h"
1824

1925
#if ENGINE_MAJOR_VERSION >= 5
2026
#include "Styling/AppStyle.h"
@@ -26,6 +32,11 @@ const FString FSentrySettingsCustomization::DefaultCrcEndpoint = TEXT("https://d
2632

2733
void OnDocumentationLinkClicked(const FSlateHyperlinkRun::FMetadata& Metadata);
2834

35+
FSentrySettingsCustomization::FSentrySettingsCustomization()
36+
: CliDownloader(MakeShareable(new FSentryCliDownloader()))
37+
{
38+
}
39+
2940
TSharedRef<IDetailCustomization> FSentrySettingsCustomization::MakeInstance()
3041
{
3142
return MakeShareable(new FSentrySettingsCustomization);
@@ -45,12 +56,44 @@ void FSentrySettingsCustomization::DrawDebugSymbolsNotice(IDetailLayoutBuilder&
4556

4657
TSharedPtr<IPropertyHandle> CrashReporterUrlHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(USentrySettings, CrashReporterUrl));
4758

59+
TSharedRef<SWidget> CliMissingWidget = MakeSentryCliStatusRow(FName(TEXT("SettingsEditor.WarningIcon")),
60+
FText::FromString(TEXT("Sentry CLI is not configured.")), FText::FromString(TEXT("Configure Now")));
61+
62+
TSharedRef<SWidget> CliDownloadingWidget = MakeSentryCliStatusRow(FName(TEXT("SettingsEditor.WarningIcon")),
63+
FText::FromString(TEXT("Downloading Sentry CLI...")), FText());
64+
65+
TSharedRef<SWidget> CliConfiguredWidget = MakeSentryCliStatusRow(FName(TEXT("SettingsEditor.GoodIcon")),
66+
FText::FromString(TEXT("Sentry CLI is configured.")), FText::FromString(TEXT("Reload")));
67+
4868
#if ENGINE_MAJOR_VERSION >= 5
4969
const ISlateStyle& Style = FAppStyle::Get();
5070
#else
5171
const ISlateStyle& Style = FEditorStyle::Get();
5272
#endif
5373

74+
DebugSymbolsCategory.AddCustomRow(FText::FromString(TEXT("DebugSymbols")), false)
75+
.WholeRowWidget
76+
[
77+
SNew(SBorder)
78+
.Padding(8.0f)
79+
[
80+
SNew(SWidgetSwitcher)
81+
.WidgetIndex(this, &FSentrySettingsCustomization::GetSentryCliStatusAsInt)
82+
+SWidgetSwitcher::Slot()
83+
[
84+
CliMissingWidget
85+
]
86+
+SWidgetSwitcher::Slot()
87+
[
88+
CliDownloadingWidget
89+
]
90+
+SWidgetSwitcher::Slot()
91+
[
92+
CliConfiguredWidget
93+
]
94+
]
95+
];
96+
5497
DebugSymbolsCategory.AddCustomRow(FText::FromString(TEXT("DebugSymbols")), false)
5598
.WholeRowWidget
5699
[
@@ -167,6 +210,66 @@ void FSentrySettingsCustomization::SetPropertiesUpdateHandler(IDetailLayoutBuild
167210
AuthTokenHandle->SetOnPropertyValueChanged(OnUpdateAuthToken);
168211
}
169212

213+
TSharedRef<SWidget> FSentrySettingsCustomization::MakeSentryCliStatusRow(FName IconName, FText Message, FText ButtonMessage)
214+
{
215+
TSharedRef<SHorizontalBox> Result = SNew(SHorizontalBox)
216+
+SHorizontalBox::Slot()
217+
.AutoWidth()
218+
.VAlign(VAlign_Center)
219+
[
220+
SNew(SImage)
221+
#if ENGINE_MAJOR_VERSION >= 5
222+
.Image(FAppStyle::Get().GetBrush(IconName))
223+
#else
224+
.Image(FEditorStyle::Get().GetBrush(IconName))
225+
#endif
226+
]
227+
228+
+SHorizontalBox::Slot()
229+
.FillWidth(1.0f)
230+
.Padding(16.0f, 0.0f)
231+
.VAlign(VAlign_Center)
232+
[
233+
SNew(STextBlock)
234+
.ColorAndOpacity(FLinearColor::White)
235+
.ShadowColorAndOpacity(FLinearColor::Black)
236+
.ShadowOffset(FVector2D::UnitVector)
237+
.Text(Message)
238+
];
239+
240+
if (!ButtonMessage.IsEmpty())
241+
{
242+
Result->AddSlot()
243+
.AutoWidth()
244+
.VAlign(VAlign_Center)
245+
[
246+
SNew(SButton)
247+
.OnClicked_Lambda([=]() -> FReply
248+
{
249+
if(CliDownloader.IsValid() && CliDownloader->GetStatus() != ESentryCliStatus::Downloading)
250+
{
251+
CliDownloader->Download([](bool Result)
252+
{
253+
FNotificationInfo Info(FText::FromString(Result
254+
? TEXT("Sentry CLI was configured successfully.")
255+
: TEXT("Sentry CLI configuration failed.")));
256+
Info.ExpireDuration = 3.0f;
257+
Info.bUseSuccessFailIcons = true;
258+
259+
TSharedPtr<SNotificationItem> EditorNotification = FSlateNotificationManager::Get().AddNotification(Info);
260+
EditorNotification->SetCompletionState(Result ? SNotificationItem::CS_Success : SNotificationItem::CS_Fail);
261+
});
262+
}
263+
264+
return FReply::Handled();
265+
})
266+
.Text(ButtonMessage)
267+
];
268+
}
269+
270+
return Result;
271+
}
272+
170273
void FSentrySettingsCustomization::UpdateProjectName()
171274
{
172275
FString Value;
@@ -231,11 +334,21 @@ void FSentrySettingsCustomization::UpdateCrcConfig(const FString& Url)
231334
CrcConfigFile.SetString(*CrcSectionName, *DataRouterUrlKey, *DataRouterUrlValue, CrcConfigFilePath);
232335
}
233336

234-
FString FSentrySettingsCustomization::GetCrcConfigPath()
337+
FString FSentrySettingsCustomization::GetCrcConfigPath() const
235338
{
236339
return FPaths::Combine(FPaths::EngineDir(), TEXT("Programs"), TEXT("CrashReportClient"), TEXT("Config"), TEXT("DefaultEngine.ini"));
237340
}
238341

342+
int32 FSentrySettingsCustomization::GetSentryCliStatusAsInt() const
343+
{
344+
if(CliDownloader.IsValid())
345+
{
346+
return static_cast<int32>(CliDownloader->GetStatus());
347+
}
348+
349+
return 0;
350+
}
351+
239352
void OnDocumentationLinkClicked(const FSlateHyperlinkRun::FMetadata& Metadata)
240353
{
241354
const FString* UrlPtr = Metadata.Find(TEXT("href"));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) 2023 Sentry. All Rights Reserved.
2+
3+
#pragma once
4+
5+
#include "CoreMinimal.h"
6+
7+
class IHttpRequest;
8+
9+
enum class ESentryCliStatus : uint8
10+
{
11+
Missing = 0,
12+
Downloading,
13+
Configured
14+
};
15+
16+
class FSentryCliDownloader
17+
{
18+
public:
19+
void Download(const TFunction<void(bool)>& OnCompleted);
20+
21+
ESentryCliStatus GetStatus();
22+
23+
private:
24+
FString GetSentryCliPath() const;
25+
FString GetSentryCliVersion() const;
26+
27+
TSharedPtr<IHttpRequest, ESPMode::ThreadSafe> SentryCliDownloadRequest;
28+
29+
const static FString SentryCliExecName;
30+
};

0 commit comments

Comments
 (0)