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

fix: Android dependency setup with custom mainTemplate.gradle #1446

Merged
merged 4 commits into from
Sep 27, 2023
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
### Fixes

- Fixed IL2CPP line number processor to no longer crash in Unity 2023 builds ([#1450](https://github.com/getsentry/sentry-unity/pull/1450))
- Fixed an issue with the Android dependency setup when using a custom `mainTemplate.gradle` ([#1446](https://github.com/getsentry/sentry-unity/pull/1446))

### Dependencies

Expand Down
140 changes: 58 additions & 82 deletions src/Sentry.Unity.Editor/Android/AndroidManifestConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ public class AndroidManifestConfiguration
private readonly bool _isDevelopmentBuild;
private readonly ScriptingImplementation _scriptingImplementation;

public const string SDKDependencies = @"
implementation(name: 'sentry-android-ndk-release', ext:'aar')
implementation(name: 'sentry-android-core-release', ext:'aar')";

public AndroidManifestConfiguration()
: this(
SentryScriptableObject.ConfiguredBuildTimeOptions,
Expand Down Expand Up @@ -174,6 +170,64 @@ internal void ModifyManifest(string basePath)
_ = androidManifest.Save();
}

internal void CopyAndroidSdkToGradleProject(string unityProjectPath, string gradlePath)
{
var androidSdkPath = Path.Combine(unityProjectPath, "Packages", SentryPackageInfo.GetName(), "Plugins", "Android", "Sentry~");
var targetPath = Path.Combine(gradlePath, "unityLibrary", "libs");

if (_options is { Enabled: true, AndroidNativeSupportEnabled: true })
{
if (!Directory.Exists(androidSdkPath))
{
throw new DirectoryNotFoundException($"Failed to find the Android SDK at '{androidSdkPath}'.");
}

_logger.LogInfo("Copying the Android SDK to '{0}'.", gradlePath);
foreach (var file in Directory.GetFiles(androidSdkPath))
{
var destinationFile = Path.Combine(targetPath, Path.GetFileName(file));
if (!File.Exists(destinationFile))
{
File.Copy(file, destinationFile);
}
}
}
else
{
_logger.LogInfo("Removing the Android SDK from the output project.");
foreach (var file in Directory.GetFiles(androidSdkPath))
{
var fileToDelete = Path.Combine(targetPath, Path.GetFileName(file));
if (File.Exists(fileToDelete))
{
File.Delete(fileToDelete);
}
}
}
}

internal void AddAndroidSdkDependencies(string gradleProjectPath)
{
var tool = new GradleSetup(_logger, gradleProjectPath);
var nativeSupportEnabled = _options is { Enabled: true, AndroidNativeSupportEnabled: true };

try
{
if (nativeSupportEnabled)
{
tool.UpdateGradleProject();
}
else
{
tool.ClearGradleProject();
}
}
catch (Exception e)
{
_logger.LogError($"Failed to {(nativeSupportEnabled ? "add" : "remove")} Android Dependencies in the gradle project", e);
}
}

internal void SetupSymbolsUpload(string unityProjectPath, string gradleProjectPath)
{
var disableSymbolsUpload = false;
Expand Down Expand Up @@ -249,84 +303,6 @@ private void SetupProguard(string gradleProjectPath)
}
}

internal void CopyAndroidSdkToGradleProject(string unityProjectPath, string gradlePath)
{
var androidSdkPath = Path.Combine(unityProjectPath, "Packages", SentryPackageInfo.GetName(), "Plugins", "Android", "Sentry~");
var targetPath = Path.Combine(gradlePath, "unityLibrary", "libs");

if (_options is { Enabled: true, AndroidNativeSupportEnabled: true })
{
if (!Directory.Exists(androidSdkPath))
{
throw new DirectoryNotFoundException($"Failed to find the Android SDK at '{androidSdkPath}'.");
}

_logger.LogInfo("Copying the Android SDK to '{0}'.", gradlePath);
foreach (var file in Directory.GetFiles(androidSdkPath))
{
var destinationFile = Path.Combine(targetPath, Path.GetFileName(file));
if (!File.Exists(destinationFile))
{
File.Copy(file, destinationFile);
}
}
}
else
{
_logger.LogInfo("Removing the Android SDK from the output project.");
foreach (var file in Directory.GetFiles(androidSdkPath))
{
var fileToDelete = Path.Combine(targetPath, Path.GetFileName(file));
if (File.Exists(fileToDelete))
{
File.Delete(fileToDelete);
}
}
}
}

internal void AddAndroidSdkDependencies(string gradleProjectPath)
{
const string regexPattern = @"(dependencies\s\{\n).+";

var gradleFilePath = Path.Combine(gradleProjectPath, "unityLibrary", "build.gradle");
if (!File.Exists(gradleFilePath))
{
throw new FileNotFoundException($"Failed to find 'build.gradle' at '{gradleFilePath}'.");
}

var gradle = File.ReadAllText(gradleFilePath);

if (_options is { Enabled: true, AndroidNativeSupportEnabled: true })
{
if (gradle.Contains(SDKDependencies))
{
_logger.LogDebug("Android SDK dependencies already added. Skipping.");
return;
}

_logger.LogInfo("Adding Android SDK dependencies to 'build.gradle'.");

var regex = new Regex(regexPattern);
var match = regex.Match(gradle);
if (!match.Success)
{
throw new ArgumentException($"Failed to add Sentry Android dependencies to 'build.gradle'.\n{gradle}", nameof(gradle));
}

File.WriteAllText(gradleFilePath, gradle.Insert(match.Index + match.Length, SDKDependencies));
}
else
{
if (gradle.Contains(SDKDependencies))
{
_logger.LogInfo("Android SDK dependencies have previously been added. Removing them.");

File.WriteAllText(gradleFilePath, gradle.Replace(SDKDependencies, ""));
}
}
}

internal static string GetManifestPath(string basePath) =>
new StringBuilder(basePath)
.Append(Path.DirectorySeparatorChar)
Expand Down
139 changes: 139 additions & 0 deletions src/Sentry.Unity.Editor/Android/GradleSetup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Sentry.Extensibility;
using UnityEditor.Build;

namespace Sentry.Unity.Editor.Android
{
internal class GradleSetup
{
private readonly IDiagnosticLogger _logger;

public const string DependencyScopeName = "dependencies";
public const string SdkDependencies = @"implementation(name: 'sentry-android-ndk-release', ext:'aar')
implementation(name: 'sentry-android-core-release', ext:'aar')";
public static readonly List<string> ScopesToSkip = new() { "buildscript", "pluginManagement" };

private readonly string _unityLibraryGradle;

public GradleSetup(IDiagnosticLogger logger, string gradleProjectPath)
{
_logger = logger;
_unityLibraryGradle = Path.Combine(gradleProjectPath, "unityLibrary", "build.gradle");
}

public void UpdateGradleProject()
{
_logger.LogInfo("Adding Sentry to the gradle project.");
var unityLibraryGradleContent = LoadGradleScript(_unityLibraryGradle);
unityLibraryGradleContent = InsertIntoScope(unityLibraryGradleContent, DependencyScopeName, SdkDependencies);
File.WriteAllText(_unityLibraryGradle, unityLibraryGradleContent);
}

public void ClearGradleProject()
{
_logger.LogInfo("Removing Sentry from the gradle project.");
var unityLibraryGradleContent = LoadGradleScript(_unityLibraryGradle);
unityLibraryGradleContent = RemoveFromGradleContent(unityLibraryGradleContent, SdkDependencies);
File.WriteAllText(_unityLibraryGradle, unityLibraryGradleContent);
}

internal string InsertIntoScope(string gradleContent, string scope, string insertion)
{
if (gradleContent.Contains(insertion))
{
_logger.LogDebug("The gradle file has already been updated. Skipping.");
return gradleContent;
}

var lines = gradleContent.Split('\n');
var scopeStart = FindBeginningOfScope(lines, scope);
if (scopeStart == -1)
{
throw new BuildFailedException($"Failed to find scope '{scope}'.");
}

var modifiedLines = new List<string>(lines);

var lineToInsert = string.Empty;
var whiteSpaceCount = lines[scopeStart].IndexOf(scope, StringComparison.Ordinal);
for (var i = 0; i < whiteSpaceCount; i++)
{
lineToInsert += " ";
}

lineToInsert += " " + insertion;
var lineOffset = lines[scopeStart].Contains("{") ? 1 : 2; // to make sure we're inside the scope
modifiedLines.Insert(scopeStart + lineOffset, lineToInsert);

return string.Join("\n", modifiedLines.ToArray());
}

private int FindBeginningOfScope(string[] lines, string scope)
{
for (var i = 0; i < lines.Length; i++)
{
var line = lines[i];
// There are potentially multiple, nested scopes. We cannot add ourselves to the ones listed in `ScopesToSkip`
if (ScopesToSkip.Any(line.Contains))
{
var startIndex = i;

// In case the '{' is on the next line
if (!line.Contains("{") && lines[startIndex + 1].Contains("{"))
{
startIndex += 1;
}

i = FindClosingBracket(lines, startIndex);
}
else if (lines[i].Contains(scope))
{
return i;
}
}

return -1;
}

private static int FindClosingBracket(string[] lines, int startIndex)
{
var openBrackets = 0;

for (var i = startIndex + 1; i < lines.Length; i++)
{
var line = lines[i];
if (line.Contains("{"))
{
openBrackets++;
}

if (line.Contains("}"))
{
if (openBrackets == 0)
{
return i;
}

openBrackets--;
}
}

throw new BuildFailedException("Failed to find the closing bracket.");
}

private static string RemoveFromGradleContent(string gradleContent, string toRemove)
=> gradleContent.Contains(toRemove) ? gradleContent.Replace(toRemove, "") : gradleContent;

internal static string LoadGradleScript(string path)
{
if (!File.Exists(path))
{
throw new FileNotFoundException($"Failed to find the gradle config.", path);
}
return File.ReadAllText(path);
}
}
}
Loading
Loading