Skip to content

Instantly share code, notes, and snippets.

@Artein
Created May 20, 2022 05:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Artein/d91e9280838ea5a464fd861081645490 to your computer and use it in GitHub Desktop.
Save Artein/d91e9280838ea5a464fd861081645490 to your computer and use it in GitHub Desktop.
AndroidSymbolsShrinkerPostProcessor updates Android debug symbols to decrease the size via 7z
#if UNITY_EDITOR && UNITY_ANDROID
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
using UnityEngine.Assertions;
using Debug = UnityEngine.Debug;
namespace Evolver.Editor.Tools.Build
{
public class AndroidSymbolsShrinkerPostProcessor : IPostprocessBuildWithReport
{
private static string LastSymbolToShrinkLocationSaveKey => nameof(LastSymbolToShrinkLocationSaveKey);
private static string DataPathNoAssets => Application.dataPath.Substring(0, Application.dataPath.Length - 7); // 7=/Assets
private static string BaseToolFullPath => Path.GetFullPath(Path.Combine(DataPathNoAssets, "Tools", "7z", "7zz"));
private static string App7ZFullPath => Application.platform == RuntimePlatform.WindowsEditor ? BaseToolFullPath + ".exe" : BaseToolFullPath;
private class ProcessResult
{
private int ExitCode { get; }
private string StdOut { get; }
private string StdErr { get; }
internal bool Failure => ExitCode != 0;
internal ProcessResult(int exitCode, string stdOut, string stdErr)
{
ExitCode = exitCode;
StdOut = stdOut;
StdErr = stdErr;
}
public override string ToString()
{
return $"Exit Code: {ExitCode}\nStdOut:\n{StdOut}\nStdErr:\n{StdErr}";
}
}
private static ProcessResult RunProcess(string workingDirectory, string fileName, string args)
{
Log($"Executing {fileName} {args} (Working Directory: {workingDirectory}");
var process = new Process();
process.StartInfo.FileName = fileName;
process.StartInfo.Arguments = args;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.WorkingDirectory = workingDirectory;
process.StartInfo.CreateNoWindow = true;
var output = new StringBuilder();
process.OutputDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
output.AppendLine(e.Data);
}
};
var error = new StringBuilder();
process.ErrorDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
error.AppendLine(e.Data);
}
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
Log($"{fileName} exited with {process.ExitCode}");
return new ProcessResult(process.ExitCode, output.ToString(), error.ToString());
}
private static void Cleanup(string path)
{
if (Directory.Exists(path))
{
Log($"Delete {path}");
Directory.Delete(path, true);
}
if (File.Exists(path))
{
Log($"Delete {path}");
File.Delete(path);
}
}
[MenuItem("Tools/Build/Shrink Android Symbols")]
public static void ShrinkSymbols()
{
var location = EditorPrefs.GetString(LastSymbolToShrinkLocationSaveKey, Path.Combine(Application.dataPath, ".."));
location = EditorUtility.OpenFilePanel("Open Android Symbol Package to shrink", location, "*.zip");
ShrinkSymbols(location);
}
private static void ShrinkSymbols(string location)
{
if (string.IsNullOrEmpty(location))
{
Log($"{nameof(AndroidSymbolsShrinkerPostProcessor)}: Selected location is null");
return;
}
var targetDirectory = Path.GetDirectoryName(location);
Assert.IsNotNull(targetDirectory, "targetDirectory != null");
var intermediatePath = Path.Combine(targetDirectory, "TempShrink");
var newZipFilePath = Path.Combine(targetDirectory, Path.GetFileNameWithoutExtension(location) + ".shrank.zip");
EditorPrefs.SetString(LastSymbolToShrinkLocationSaveKey, targetDirectory);
if (!File.Exists(App7ZFullPath))
{
throw new BuildFailedException($"Failed to locate {App7ZFullPath}");
}
Cleanup(intermediatePath);
Cleanup(newZipFilePath);
var result = RunProcess(targetDirectory, App7ZFullPath, $"x -o\"{intermediatePath}\" \"{location}\"");
if (result.Failure)
{
throw new BuildFailedException(result.ToString());
}
EditorUtility.DisplayProgressBar("Shrinking Android debug symbols", "Deleting/Renaming/Compressing symbol files", 0.5f);
var files = Directory.GetFiles(intermediatePath, "*.*", SearchOption.AllDirectories);
const string symSo = ".sym.so";
foreach (var file in files)
{
if (file.EndsWith(".dbg.so"))
{
Cleanup(file);
}
if (file.EndsWith(symSo))
{
var fileSO = file.Substring(0, file.Length - symSo.Length) + ".so";
Log($"Rename {file} --> {fileSO}");
File.Move(file, fileSO);
}
}
const Compression7Zip compressionType = Compression7Zip.Normal;
const int passNumber = 1; // [1; 15] - bigger is more compression, but slower
// for other arguments check Tools/7z/MANUAL/start.htm
var compressionArgs = $"a -tzip -mm=Deflate -mx={(int)compressionType} -mfb=257 -mpass={passNumber} -mmt -r \"{newZipFilePath}\"";
// ReSharper disable once StringLiteralTypo
result = RunProcess(intermediatePath, App7ZFullPath, compressionArgs);
EditorUtility.ClearProgressBar();
if (result.Failure)
{
throw new BuildFailedException(result.ToString());
}
Cleanup(intermediatePath);
// Check results size
var resultZipFileInfo = new FileInfo(newZipFilePath);
var resultZipFileMB = (double)resultZipFileInfo.Length / 1024 / 1024;
if (resultZipFileMB > 299.9999d)
{
throw new BuildFailedException("Android debug symbols too big to be uploaded to GooglePlay (must be up to 300 MB). " +
"Try remove unused code or increase compression ratio (will increase build time)");
}
if (resultZipFileMB > 290)
{
LogWarning($"Android debug symbols were shrank to {resultZipFileMB} MB. It is too close to limit of 300 MB for GooglePlay");
}
Log($"The new shrank symbols: {newZipFilePath} ({resultZipFileMB} MB)");
}
int IOrderedCallback.callbackOrder => (int)PostprocessorOrder.Android_ShrinkDebugSymbols;
void IPostprocessBuildWithReport.OnPostprocessBuild(BuildReport report)
{
if (!EditorUserBuildSettings.androidCreateSymbolsZip)
{
return;
}
var outputFilePath = report.files.FirstOrDefault(file =>
file.path.Substring(Math.Max(0, file.path.Length - 4)) ==
(EditorUserBuildSettings.buildAppBundle ? ".aab" : ".apk")).path;
if (string.IsNullOrWhiteSpace(outputFilePath))
{
return;
}
var outputFileNameWithoutExtension = Path.GetFileNameWithoutExtension(outputFilePath);
var outputFileParentDirectoryPath = Path.GetDirectoryName(outputFilePath);
var symbolFileExistRegex = new Regex($"^{outputFileNameWithoutExtension}.+\\.symbols\\.zip$");
var symbolFilePath = Directory
.GetFiles(outputFileParentDirectoryPath ?? string.Empty, "*zip", SearchOption.TopDirectoryOnly)
.FirstOrDefault(path => symbolFileExistRegex.IsMatch(Path.GetFileName(path)));
if (string.IsNullOrWhiteSpace(symbolFilePath))
{
LogError("Symbol file not found with given format!");
return;
}
ShrinkSymbols(symbolFilePath);
File.Delete(symbolFilePath);
}
private static void Log(string message)
{
Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, message);
}
private static void LogWarning(string message)
{
Debug.LogFormat(LogType.Warning, LogOption.NoStacktrace, null, message);
}
private static void LogError(string message)
{
Debug.LogFormat(LogType.Error, LogOption.NoStacktrace, null, message);
}
private enum Compression7Zip
{
NoCompression = 0,
Fastest = 1,
Fast = 3,
Normal = 5,
Maximum = 7,
Ultra = 9
}
}
}
#endif // UNITY_EDITOR && UNITY_ANDROID
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment