Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Automated symbols upload for iOS w/ bitcode #443

Merged
merged 10 commits into from
Dec 2, 2021
Merged
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
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
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