Skip to content

Commit

Permalink
feat: Automated symbols upload for iOS w/ bitcode (#443)
Browse files Browse the repository at this point in the history
* uploading symbols if bitcode disabled

* uploading symbols if bitcode disabled

* added to readme

* cleanup

* updated CHANGELOG.md

* Format code

* review cleanup

Co-authored-by: Sentry Github Bot <bot+github-bot@sentry.io>
  • Loading branch information
bitsandfoxes and getsentry-bot authored Dec 2, 2021
1 parent 0a5c58b commit 38b0f79
Show file tree
Hide file tree
Showing 15 changed files with 201 additions and 29 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Automated symbols upload for iOS builds when bitcode is disabled ([#443](https://github.com/getsentry/sentry-unity/pull/443))

### Fixes

- Sentry no longer requires Xcode projects to be exported on macOS ([#442](https://github.com/getsentry/sentry-unity/pull/442))
Expand Down
33 changes: 30 additions & 3 deletions src/Sentry.Unity.Editor.iOS/BuildPostProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public static void OnPostProcessBuild(BuildTarget target, string pathToProject)
}

var options = ScriptableSentryUnityOptions.LoadSentryUnityOptions(BuildPipeline.isBuildingPlayer);
var logger = options?.DiagnosticLogger ?? new UnityLogger(new SentryUnityOptions());

try
{
Expand All @@ -27,22 +28,48 @@ public static void OnPostProcessBuild(BuildTarget target, string pathToProject)

if (options?.Validate() != true)
{
new UnityLogger(new SentryOptions()).LogWarning("Failed to validate Sentry Options. Native support disabled.");
logger.LogWarning("Failed to validate Sentry Options. Native support disabled.");
return;
}

if (!options.IosNativeSupportEnabled)
{
options.DiagnosticLogger?.LogDebug("iOS Native support disabled through the options.");
logger.LogDebug("iOS Native support disabled through the options.");
return;
}

sentryXcodeProject.AddNativeOptions(options);
sentryXcodeProject.AddSentryToMain(options);

var sentryCliOptions = SentryCliOptions.LoadCliOptions();
if (!sentryCliOptions.UploadSymbols)
{
logger.LogDebug("Automated symbols upload has been disabled.");
return;
}

if (EditorUserBuildSettings.development && !sentryCliOptions.UploadDevelopmentSymbols)
{
logger.LogDebug("Automated symbols upload for development builds has been disabled.");
return;
}

if (!sentryCliOptions.Validate(logger))
{
logger.LogWarning("sentry-cli validation failed. Symbols will not be uploaded." +
"\nYou can disable this warning by disabling the automated symbols upload under " +
"Tools -> Sentry -> Editor");
return;
}

SentryCli.CreateSentryProperties(pathToProject, sentryCliOptions);
SentryCli.AddExecutableToXcodeProject(pathToProject);

sentryXcodeProject.AddBuildPhaseSymbolUpload(logger);
}
catch (Exception e)
{
options?.DiagnosticLogger?.LogError("Failed to add the Sentry framework to the generated Xcode project", e);
logger.LogError("Failed to add the Sentry framework to the generated Xcode project", e);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Sentry.Unity.Editor.iOS/Sentry.Unity.Editor.iOS.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<ItemGroup>
<ProjectReference Include="../sentry-dotnet/src/Sentry/Sentry.csproj" Private="false" />
<ProjectReference Include="../Sentry.Unity/Sentry.Unity.csproj" Private="false" />
<ProjectReference Include="../Sentry.Unity.Editor/Sentry.Unity.Editor.csproj" Private="false"/>
<ProjectReference Include="../Sentry.Unity.Editor/Sentry.Unity.Editor.csproj" Private="false" />
</ItemGroup>

<!-- Add reference once we figure out where the DLL is (find Unity version and install location) -->
Expand Down
37 changes: 37 additions & 0 deletions src/Sentry.Unity.Editor.iOS/SentryXcodeProject.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.IO;
using System.Linq;
using Sentry.Extensibility;
using UnityEditor.iOS.Xcode;
using UnityEditor.iOS.Xcode.Extensions;

Expand All @@ -8,6 +10,7 @@ namespace Sentry.Unity.Editor.iOS
internal class SentryXcodeProject : IDisposable
{
private const string FrameworkName = "Sentry.framework";
internal const string SymbolUploadPhaseName = "SymbolUpload";

private readonly string _mainPath = Path.Combine("MainApp", "main.mm");
private readonly string _optionsPath = Path.Combine("MainApp", "SentryOptions.m");
Expand Down Expand Up @@ -71,9 +74,37 @@ public void AddSentryFramework()
_project.SetBuildProperty(unityFrameworkTargetGuid, "FRAMEWORK_SEARCH_PATHS", "$(inherited)");
_project.AddBuildProperty(unityFrameworkTargetGuid, "FRAMEWORK_SEARCH_PATHS", "$(PROJECT_DIR)/Frameworks/");

_project.SetBuildProperty(mainTargetGuid, "DEBUG_INFORMATION_FORMAT", "dwarf-with-dsym");
_project.SetBuildProperty(unityFrameworkTargetGuid, "DEBUG_INFORMATION_FORMAT", "dwarf-with-dsym");

_project.AddBuildProperty(mainTargetGuid, "OTHER_LDFLAGS", "-ObjC");
}

public void AddBuildPhaseSymbolUpload(IDiagnosticLogger? logger)
{
if (MainTargetContainsSymbolUploadBuildPhase())
{
logger?.LogDebug("Build phase '{0}' already added.", SymbolUploadPhaseName);
return;
}

var mainTargetGuid = _project.GetUnityMainTargetGuid();
_project.AddShellScriptBuildPhase(mainTargetGuid,
SymbolUploadPhaseName,
"/bin/sh",
$@"export SENTRY_PROPERTIES=sentry.properties
if [ ""$ENABLE_BITCODE"" = ""NO"" ] ; then
echo ""Bitcode is disabled - Uploading symbols""
ERROR=$(./{SentryCli.SentryCliMacOS} upload-dif $BUILT_PRODUCTS_DIR > ./sentry-symbols-upload.log 2>&1 &)
if [ ! $? -eq 0 ] ; then
echo ""warning: sentry-cli - $ERROR""
fi
else
echo ""Bitcode is enabled - Skipping symbols upload""
fi"
);
}

public void AddNativeOptions(SentryUnityOptions options)
{
_nativeOptions.CreateFile(Path.Combine(_projectRoot, _optionsPath), options);
Expand All @@ -83,6 +114,12 @@ public void AddNativeOptions(SentryUnityOptions options)
public void AddSentryToMain(SentryUnityOptions options) =>
_nativeMain.AddSentry(Path.Combine(_projectRoot, _mainPath), options.DiagnosticLogger);

internal bool MainTargetContainsSymbolUploadBuildPhase()
{
var allBuildPhases = _project.GetAllBuildPhasesForTarget(_project.GetUnityMainTargetGuid());
return allBuildPhases.Any(buildPhase => _project.GetBuildPhaseName(buildPhase) == SymbolUploadPhaseName);
}

internal string ProjectToString() => _project.WriteToString();

public void Dispose() => _project.WriteToFile(_projectPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,9 @@ internal void SetupSymbolsUpload(string basePath)

if (!sentryCliOptions.Validate(logger))
{
logger.LogWarning("Loading sentry-cli configuration failed. Symbols will not be uploaded");
logger.LogWarning("sentry-cli validation failed. Symbols will not be uploaded." +
"\nYou can disable this warning by disabling the automated symbols upload under " +
"Tools -> Sentry -> Editor");
return;
}

Expand Down
24 changes: 21 additions & 3 deletions src/Sentry.Unity.Editor/SentryCli.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ namespace Sentry.Unity.Editor
{
internal static class SentryCli
{
internal const string SentryCliWindows = "sentry-cli-Windows-x86_64.exe";
internal const string SentryCliMacOS = "sentry-cli-Darwin-universal";
internal const string SentryCliLinux = "sentry-cli-Linux-x86_64";

[DllImport("libc", SetLastError = true)]
private static extern int chmod(string pathname, int mode);

Expand All @@ -34,9 +38,9 @@ internal static string GetSentryCliPlatformName(IApplication? application = null

return application.Platform switch
{
RuntimePlatform.WindowsEditor => "sentry-cli-Windows-x86_64.exe ",
RuntimePlatform.OSXEditor => "sentry-cli-Darwin-universal",
RuntimePlatform.LinuxEditor => "sentry-cli-Linux-x86_64 ",
RuntimePlatform.WindowsEditor => SentryCliWindows,
RuntimePlatform.OSXEditor => SentryCliMacOS,
RuntimePlatform.LinuxEditor => SentryCliLinux,
_ => throw new InvalidOperationException(
$"Cannot get sentry-cli for the current platform: {Application.platform}")
};
Expand Down Expand Up @@ -69,5 +73,19 @@ internal static void SetExecutePermission(string? filePath = null, IApplication?
throw new UnauthorizedAccessException($"Failed to set permission to {filePath}");
}
}

internal static void AddExecutableToXcodeProject(string projectPath)
{
var executableSource = GetSentryCliPath(SentryCliMacOS);
var executableDestination = Path.Combine(projectPath, SentryCliMacOS);

if (!Directory.Exists(projectPath))
{
throw new DirectoryNotFoundException($"Xcode project directory not found at {executableDestination}");
}

File.Copy(executableSource, executableDestination);
SetExecutePermission(executableDestination);
}
}
}
3 changes: 2 additions & 1 deletion src/Sentry.Unity/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

[assembly: InternalsVisibleTo("Sentry.Unity.Tests")]
[assembly: InternalsVisibleTo("Sentry.Unity.Editor")]
[assembly: InternalsVisibleTo("Sentry.Unity.Editor.iOS")]
[assembly: InternalsVisibleTo("Sentry.Unity.Editor.Tests")]
[assembly: InternalsVisibleTo("Sentry.Unity.Editor.iOS")]
[assembly: InternalsVisibleTo("Sentry.Unity.Editor.iOS.Tests")]
[assembly: InternalsVisibleTo("Sentry.Unity.iOS")]
[assembly: InternalsVisibleTo("Sentry.Unity.iOS.Tests")]
[assembly: InternalsVisibleTo("Sentry.Unity.Android")]
Expand Down
25 changes: 22 additions & 3 deletions test/Sentry.Unity.Editor.Tests/SentryCliTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ public void GetSentryCliPlatformName_UnrecognizedPlatform_ThrowsInvalidOperation
}

[Test]
[TestCase(RuntimePlatform.WindowsEditor, "sentry-cli-Windows-x86_64.exe ")]
[TestCase(RuntimePlatform.OSXEditor, "sentry-cli-Darwin-universal")]
[TestCase(RuntimePlatform.LinuxEditor, "sentry-cli-Linux-x86_64 ")]
[TestCase(RuntimePlatform.WindowsEditor, SentryCli.SentryCliWindows)]
[TestCase(RuntimePlatform.OSXEditor, SentryCli.SentryCliMacOS)]
[TestCase(RuntimePlatform.LinuxEditor, SentryCli.SentryCliLinux)]
public void GetSentryPlatformName_RecognizedPlatform_SetsSentryCliName(RuntimePlatform platform, string expectedName)
{
var application = new TestApplication(platform: platform);
Expand Down Expand Up @@ -80,5 +80,24 @@ public void CreateSentryProperties_PropertyFileCreatedAndContainsSentryCliOption

Directory.Delete(propertiesDirectory, true);
}

[Test]
public void AddExecutableToXcodeProject_ProjectPathDoesNotExist_ThrowsDirectoryNotFoundException()
{
Assert.Throws<DirectoryNotFoundException>(() => SentryCli.AddExecutableToXcodeProject("non-existent-path"));
}

[Test]
public void AddExecutableToXcodeProject_ProjectPathExists_CopiesSentryCliForMacOS()
{
var fakeXcodeProjectDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(fakeXcodeProjectDirectory);

SentryCli.AddExecutableToXcodeProject(fakeXcodeProjectDirectory);

Assert.IsTrue(File.Exists(Path.Combine(fakeXcodeProjectDirectory, SentryCli.SentryCliMacOS)));

Directory.Delete(fakeXcodeProjectDirectory, true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,11 @@
<ProjectReference Include="../../src/Sentry.Unity.Editor/Sentry.Unity.Editor.csproj" Private="false" />
<ProjectReference Include="../../src/Sentry.Unity.Editor.iOS/Sentry.Unity.Editor.iOS.csproj" Private="false" />
</ItemGroup>

<ItemGroup>
<Compile Include="../SharedClasses/*.cs">
<Link>%(RecursiveDir)/%(Filename)%(Extension)</Link>
</Compile>
</ItemGroup>

</Project>
47 changes: 46 additions & 1 deletion test/Sentry.Unity.Editor.iOS.Tests/SentryXcodeProjectTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using NUnit.Framework;
using Sentry.Extensibility;
using Sentry.Unity.Tests.SharedClasses;

namespace Sentry.Unity.Editor.iOS.Tests
{
Expand All @@ -21,10 +23,22 @@ private class Fixture
{
public string ProjectRoot { get; set; } =
Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestFiles", "2019_4");
public SentryUnityOptions Options { get; set; } = new();
public SentryUnityOptions Options { get; set; }
public TestLogger TestLogger { get; set; }
public INativeMain NativeMain { get; set; } = new NativeMainTest();
public INativeOptions NativeOptions { get; set; } = new NativeOptionsTest();

public Fixture()
{
TestLogger = new TestLogger();
Options = new SentryUnityOptions
{
Debug = true,
DiagnosticLevel = SentryLevel.Debug,
DiagnosticLogger = TestLogger
};
}

public SentryXcodeProject GetSut() => new(ProjectRoot, NativeMain, NativeOptions);
}

Expand Down Expand Up @@ -83,5 +97,36 @@ public void CreateNativeOptions_CleanXcodeProject_NativeOptionsAdded()

StringAssert.Contains("SentryOptions.m", xcodeProject.ProjectToString());
}

[Test]
public void AddBuildPhaseSymbolUpload_CleanXcodeProject_BuildPhaseSymbolUploadAdded()
{
var xcodeProject = _fixture.GetSut();
xcodeProject.ReadFromProjectFile();

var didContainUploadPhase = xcodeProject.MainTargetContainsSymbolUploadBuildPhase();
xcodeProject.AddBuildPhaseSymbolUpload(_fixture.Options.DiagnosticLogger);
var doesContainUploadPhase = xcodeProject.MainTargetContainsSymbolUploadBuildPhase();

Assert.IsFalse(didContainUploadPhase);
Assert.IsTrue(doesContainUploadPhase);
}

[Test]
public void AddBuildPhaseSymbolUpload_PhaseAlreadyAdded_LogsAndDoesNotAddAgain()
{
const int expectedBuildPhaseOccurence = 1;
var xcodeProject = _fixture.GetSut();
xcodeProject.ReadFromProjectFile();

xcodeProject.AddBuildPhaseSymbolUpload(_fixture.Options.DiagnosticLogger);
xcodeProject.AddBuildPhaseSymbolUpload(_fixture.Options.DiagnosticLogger);

var actualBuildPhaseOccurence = Regex.Matches(xcodeProject.ProjectToString(),
Regex.Escape(SentryXcodeProject.SymbolUploadPhaseName)).Count;

Assert.AreEqual(1, _fixture.TestLogger.Logs.Count);
Assert.AreEqual(expectedBuildPhaseOccurence, actualBuildPhaseOccurence);
}
}
}
1 change: 1 addition & 0 deletions test/Sentry.Unity.Tests/Json/SafeSerializerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using NUnit.Framework;
using Sentry.Unity.Json;
using Sentry.Unity.Tests.SharedClasses;

namespace Sentry.Unity.Tests.Json
{
Expand Down
5 changes: 5 additions & 0 deletions test/Sentry.Unity.Tests/Sentry.Unity.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@
<ProjectReference Include="../../src/Sentry.Unity/Sentry.Unity.csproj" Private="false" />
<ProjectReference Include="../../src/sentry-dotnet/src/Sentry/Sentry.csproj" Private="false" />
</ItemGroup>
<ItemGroup>
<Compile Include="../SharedClasses/*.cs">
<Link>%(RecursiveDir)/%(Filename)%(Extension)</Link>
</Compile>
</ItemGroup>
</Project>
17 changes: 1 addition & 16 deletions test/Sentry.Unity.Tests/UnityEventProcessorTests.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using NUnit.Framework;
using Sentry.Extensibility;
using Sentry.Unity.Tests.SharedClasses;
using Sentry.Unity.Tests.Stubs;
using UnityEngine;
using UnityEngine.TestTools;
Expand Down Expand Up @@ -545,19 +543,6 @@ public IEnumerator Process_GpuProtocolGraphicsShaderLevelMinusOne_Ignored()
}
}

internal sealed class TestLogger : IDiagnosticLogger
{
internal readonly ConcurrentBag<(SentryLevel logLevel, string message, Exception? exception)> Logs = new();

public bool IsEnabled(SentryLevel level) => true;

public void Log(SentryLevel logLevel, string message, Exception? exception = null, params object?[] args)
{
var log = (logLevel, string.Format(message, args), exception);
Logs.Add(log);
}
}

internal sealed class TestSentrySystemInfo : ISentrySystemInfo
{
public int? MainThreadId { get; set; } = 1;
Expand Down
2 changes: 2 additions & 0 deletions test/SharedClasses/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Here we place classes that are used in multiple projects. Unity re-imports all DLLs and complains about duplications so
the regular approach of adding a "Helpers" project does not work.
Loading

0 comments on commit 38b0f79

Please sign in to comment.