You've already forked CC-Framework.CrashReport
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 08ea53580d | |||
| c6f37c94ba |
19
CHANGELOG.md
19
CHANGELOG.md
@@ -19,3 +19,22 @@
|
||||
|
||||
* Unity 工程版本升级到 2022.3.62f3c1。
|
||||
* package.json 的 Unity 兼容版本调整为 2022.3。
|
||||
|
||||
# [1.0.3]
|
||||
|
||||
### 修复
|
||||
|
||||
* 修复 `CHANGELOG.md.meta` 与 `package.json.meta` 和 Commercialization 包重复导致的 UPM GUID 冲突告警。
|
||||
|
||||
# [1.0.4]
|
||||
|
||||
### 新增
|
||||
|
||||
* 新增 Crash/Bugly 构建面板扩展,由 CrashReport 包自身维护 Bugly 构建配置。
|
||||
* 支持按构建 profile 保存 Android 符号表归档/上传配置。
|
||||
* 支持按构建 profile 配置 iOS Bugly Pod 注入版本和构建后 `pod install`。
|
||||
|
||||
### 调整
|
||||
|
||||
* iOS Bugly Pod 后处理改为读取 CrashReport 构建配置,不再固定写入单一版本。
|
||||
* 声明对 `com.foldcc.cc-framework.common` 的构建面板 API 依赖。
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fca4353ab66a48e4ebe5032dc5057c7a
|
||||
guid: cccafbb7533d45778a56bbd73c16b88f
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
|
||||
651
Editor/BuglyAndroidSymbolUtility.cs
Normal file
651
Editor/BuglyAndroidSymbolUtility.cs
Normal file
@@ -0,0 +1,651 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
namespace CCFramework.CrashReport.Editor
|
||||
{
|
||||
public static class BuglyAndroidSymbolUtility
|
||||
{
|
||||
public delegate void LogHandler(string message, LogType type = LogType.Log);
|
||||
|
||||
public const string BuglyDownloadUrl = "https://bugly.tds.tencent.com/docs/assets/files/bugly_symbol_oversea_v3.4.23-a449c7a62d7792de76fca1af623584f5.zip";
|
||||
public const string BuglyToolGuideUrl = "https://bugly.tds.tencent.com/docs/tutorial/symbol/tool/";
|
||||
|
||||
private const string SymbolFolderSuffix = "_bugly_symbols";
|
||||
private const string SymbolPackageSuffix = "_bugly_symbols.zip";
|
||||
private const string UploadStagingDirectory = "Library/BuglySymbolUpload";
|
||||
|
||||
public static void ApplyAndroidSymbolSettings(BuildProfile profile, CrashReportBuglyProfileSettings settings, LogHandler log)
|
||||
{
|
||||
if (!IsAndroidProfile(profile) || settings == null || !settings.enableAndroidSymbolArchive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EditorUserBuildSettings.androidCreateSymbols = AndroidCreateSymbols.Debugging;
|
||||
WriteLog(log, "已启用 Android Debugging 符号表生成,构建后会产出 native-debug-symbols.zip。");
|
||||
}
|
||||
|
||||
public static BuglySymbolArchiveResult ArchiveAfterBuild(
|
||||
BuildProfile profile,
|
||||
CrashReportBuglyProfileSettings settings,
|
||||
string buildOutputPath,
|
||||
LogHandler log)
|
||||
{
|
||||
BuglySymbolArchiveResult result = new BuglySymbolArchiveResult();
|
||||
if (!IsAndroidProfile(profile) || settings == null || !settings.enableAndroidSymbolArchive)
|
||||
{
|
||||
result.message = "当前不是 Android 构建或未启用 Bugly 符号表归档。";
|
||||
WriteLog(log, result.message);
|
||||
return result;
|
||||
}
|
||||
|
||||
string resolvedBuildOutputPath = ResolveFullPath(buildOutputPath);
|
||||
string buildDirectory = Path.GetDirectoryName(resolvedBuildOutputPath);
|
||||
string buildName = Path.GetFileNameWithoutExtension(resolvedBuildOutputPath);
|
||||
string archiveDirectory = Path.Combine(buildDirectory, buildName + SymbolFolderSuffix);
|
||||
Directory.CreateDirectory(archiveDirectory);
|
||||
|
||||
string nativeSymbolPath = FindNativeDebugSymbols(profile);
|
||||
string mappingPath = FindMappingFile(profile, resolvedBuildOutputPath);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(nativeSymbolPath))
|
||||
{
|
||||
result.nativeSymbolPath = Path.Combine(archiveDirectory, buildName + "_native-debug-symbols.zip");
|
||||
File.Copy(nativeSymbolPath, result.nativeSymbolPath, true);
|
||||
WriteLog(log, $"已归档 Bugly SO 符号表:{result.nativeSymbolPath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog(log, "未找到 native-debug-symbols.zip,Native Crash 可能无法还原。", LogType.Warning);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(mappingPath))
|
||||
{
|
||||
result.mappingPath = Path.Combine(archiveDirectory, buildName + "_mapping.txt");
|
||||
File.Copy(mappingPath, result.mappingPath, true);
|
||||
WriteLog(log, $"已归档 Bugly mapping 文件:{result.mappingPath}");
|
||||
}
|
||||
|
||||
result.archiveDirectory = archiveDirectory;
|
||||
result.manifestPath = WriteManifest(profile, resolvedBuildOutputPath, archiveDirectory, result);
|
||||
result.packagePath = CreatePackage(archiveDirectory, Path.Combine(buildDirectory, buildName + SymbolPackageSuffix));
|
||||
result.hasSymbols = File.Exists(result.nativeSymbolPath) || File.Exists(result.mappingPath);
|
||||
result.message = result.hasSymbols ? "Bugly 符号表归档完成。" : "Bugly 符号表归档完成,但没有找到可上传的符号表文件。";
|
||||
WriteLog(log, $"Bugly 符号表包:{result.packagePath}");
|
||||
return result;
|
||||
}
|
||||
|
||||
public static BuglySymbolUploadResult UploadPreparedSymbols(
|
||||
BuildProfile profile,
|
||||
CrashReportBuglyProfileSettings settings,
|
||||
BuglySymbolArchiveResult archive,
|
||||
LogHandler log)
|
||||
{
|
||||
if (profile == null || settings == null)
|
||||
{
|
||||
return FailUpload("构建配置为空,无法上传 Bugly 符号表。", log);
|
||||
}
|
||||
|
||||
if (archive == null || !archive.hasSymbols)
|
||||
{
|
||||
return FailUpload("没有可上传的 Bugly 符号表文件。", log);
|
||||
}
|
||||
|
||||
string javaPath = string.IsNullOrWhiteSpace(settings.buglyJavaPath) ? "java" : settings.buglyJavaPath.Trim();
|
||||
string toolPath = ResolveSymbolToolPath(settings);
|
||||
if (string.IsNullOrWhiteSpace(settings.buglyAppId))
|
||||
{
|
||||
return FailUpload("Bugly App ID 为空。", log);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings.buglyAppKey))
|
||||
{
|
||||
return FailUpload("Bugly App Key 为空。", log);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(toolPath) || !File.Exists(toolPath))
|
||||
{
|
||||
return FailUpload($"Bugly 符号表工具不存在:{toolPath}", log);
|
||||
}
|
||||
|
||||
BuglySymbolArchiveResult uploadArchive = PrepareUploadInputs(profile, archive, log);
|
||||
List<string> arguments = new List<string>
|
||||
{
|
||||
"-jar",
|
||||
Quote(toolPath),
|
||||
"-appid",
|
||||
Quote(settings.buglyAppId.Trim()),
|
||||
"-appkey",
|
||||
Quote(settings.buglyAppKey.Trim()),
|
||||
"-version",
|
||||
Quote(profile.version),
|
||||
"-buildNo",
|
||||
Quote(profile.buildNumber.ToString()),
|
||||
"-platform",
|
||||
"Android"
|
||||
};
|
||||
|
||||
if (PathExists(uploadArchive.nativeSymbolPath))
|
||||
{
|
||||
arguments.Add("-inputSymbol");
|
||||
arguments.Add(Quote(uploadArchive.nativeSymbolPath));
|
||||
}
|
||||
|
||||
if (File.Exists(uploadArchive.mappingPath))
|
||||
{
|
||||
arguments.Add("-inputMapping");
|
||||
arguments.Add(Quote(uploadArchive.mappingPath));
|
||||
}
|
||||
|
||||
string argumentText = string.Join(" ", arguments);
|
||||
WriteLog(log, $"开始上传 Bugly 符号表:{javaPath} {MaskUploadArguments(argumentText)}");
|
||||
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = javaPath,
|
||||
Arguments = argumentText,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
StandardOutputEncoding = GetBuglyToolOutputEncoding(),
|
||||
StandardErrorEncoding = GetBuglyToolOutputEncoding()
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using (Process process = Process.Start(startInfo))
|
||||
{
|
||||
string output = process.StandardOutput.ReadToEnd();
|
||||
string error = process.StandardError.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
|
||||
BuglySymbolUploadResult result = new BuglySymbolUploadResult
|
||||
{
|
||||
success = process.ExitCode == 0 && IsBuglyUploadOutputSuccessful(output, error),
|
||||
exitCode = process.ExitCode,
|
||||
output = output,
|
||||
error = error,
|
||||
command = javaPath + " " + MaskUploadArguments(argumentText)
|
||||
};
|
||||
result.logPath = WriteUploadLog(archive.archiveDirectory, result);
|
||||
|
||||
if (result.success)
|
||||
{
|
||||
WriteLog(log, $"Bugly 符号表上传完成,日志:{result.logPath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog(log, $"Bugly 符号表上传失败,ExitCode={result.exitCode},日志:{result.logPath}", LogType.Error);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return FailUpload($"执行 Bugly 符号表上传失败:{e.Message}", log);
|
||||
}
|
||||
}
|
||||
|
||||
public static string BuildUploadCommandTemplate(BuildProfile profile, CrashReportBuglyProfileSettings settings, bool maskSecret)
|
||||
{
|
||||
if (profile == null || settings == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string buildOutputPath = ResolveFullPath(BuildPipelineCore.GetExpectedBuildOutputPath(profile));
|
||||
string buildDirectory = Path.GetDirectoryName(buildOutputPath);
|
||||
string buildName = Path.GetFileNameWithoutExtension(buildOutputPath);
|
||||
string archiveDirectory = Path.Combine(buildDirectory, buildName + SymbolFolderSuffix);
|
||||
string appKey = string.IsNullOrWhiteSpace(settings.buglyAppKey) ? "<Bugly App Key>" : settings.buglyAppKey.Trim();
|
||||
if (maskSecret && !string.IsNullOrWhiteSpace(settings.buglyAppKey))
|
||||
{
|
||||
appKey = "******";
|
||||
}
|
||||
|
||||
return (string.IsNullOrWhiteSpace(settings.buglyJavaPath) ? "java" : settings.buglyJavaPath.Trim()) +
|
||||
" -jar " + Quote(ResolveSymbolToolPath(settings)) +
|
||||
" -appid " + Quote(string.IsNullOrWhiteSpace(settings.buglyAppId) ? "<Bugly App ID>" : settings.buglyAppId.Trim()) +
|
||||
" -appkey " + Quote(appKey) +
|
||||
" -version " + Quote(profile.version) +
|
||||
" -buildNo " + Quote(profile.buildNumber.ToString()) +
|
||||
" -platform Android" +
|
||||
" -inputSymbol " + Quote(Path.Combine(archiveDirectory, buildName + "_native-debug-symbols.zip")) +
|
||||
" -inputMapping " + Quote(Path.Combine(archiveDirectory, buildName + "_mapping.txt"));
|
||||
}
|
||||
|
||||
public static string ResolveSymbolToolPath(CrashReportBuglyProfileSettings settings)
|
||||
{
|
||||
if (settings != null && !string.IsNullOrWhiteSpace(settings.buglySymbolToolPath))
|
||||
{
|
||||
string configuredPath = ResolveFullPath(settings.buglySymbolToolPath);
|
||||
if (File.Exists(configuredPath))
|
||||
{
|
||||
return configuredPath;
|
||||
}
|
||||
}
|
||||
|
||||
string bundledRoot = Path.Combine(GetProjectRoot(), "Tools", "BuglySymbolTool");
|
||||
if (!Directory.Exists(bundledRoot))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string newestPath = string.Empty;
|
||||
DateTime newestWriteTime = DateTime.MinValue;
|
||||
foreach (string jarPath in Directory.GetFiles(bundledRoot, "*.jar", SearchOption.AllDirectories))
|
||||
{
|
||||
DateTime writeTime = File.GetLastWriteTimeUtc(jarPath);
|
||||
if (writeTime > newestWriteTime)
|
||||
{
|
||||
newestPath = jarPath;
|
||||
newestWriteTime = writeTime;
|
||||
}
|
||||
}
|
||||
|
||||
return newestPath;
|
||||
}
|
||||
|
||||
public static string ToProfilePath(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string fullPath = ResolveFullPath(path);
|
||||
string projectRoot = GetProjectRoot().TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
string rootPrefix = projectRoot + Path.DirectorySeparatorChar;
|
||||
if (fullPath.StartsWith(rootPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return fullPath.Substring(rootPrefix.Length).Replace(Path.DirectorySeparatorChar, '/');
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private static bool IsAndroidProfile(BuildProfile profile)
|
||||
{
|
||||
if (profile == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Type profileType = profile.GetType();
|
||||
string targetPlatform = GetStringField(profileType, profile, "targetPlatform");
|
||||
string buildTargetGroup = GetStringField(profileType, profile, "buildTargetGroup");
|
||||
string buildTarget = GetStringField(profileType, profile, "buildTarget");
|
||||
bool hasExplicitPlatformValue =
|
||||
!string.IsNullOrWhiteSpace(targetPlatform) ||
|
||||
!string.IsNullOrWhiteSpace(buildTargetGroup) ||
|
||||
!string.IsNullOrWhiteSpace(buildTarget);
|
||||
return IsPlatformValue(targetPlatform, "Android") ||
|
||||
IsPlatformValue(buildTargetGroup, "Android") ||
|
||||
IsPlatformValue(buildTarget, "Android") ||
|
||||
(!hasExplicitPlatformValue && EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android);
|
||||
}
|
||||
|
||||
private static string GetStringField(Type ownerType, object instance, string fieldName)
|
||||
{
|
||||
FieldInfo field = ownerType.GetField(fieldName, BindingFlags.Public | BindingFlags.Instance);
|
||||
return field?.GetValue(instance) as string ?? string.Empty;
|
||||
}
|
||||
|
||||
private static bool IsPlatformValue(string value, string expectedValue)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(value) &&
|
||||
string.Equals(value, expectedValue, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string FindNativeDebugSymbols(BuildProfile profile)
|
||||
{
|
||||
string buildType = profile != null && profile.isDevelopment ? "debug" : "release";
|
||||
string preferredPath = Path.Combine(
|
||||
GetProjectRoot(),
|
||||
"Library",
|
||||
"Bee",
|
||||
"Android",
|
||||
"Prj",
|
||||
"IL2CPP",
|
||||
"Gradle",
|
||||
"launcher",
|
||||
"build",
|
||||
"outputs",
|
||||
"native-debug-symbols",
|
||||
buildType,
|
||||
"native-debug-symbols.zip");
|
||||
|
||||
return File.Exists(preferredPath)
|
||||
? preferredPath
|
||||
: FindNewestFile(Path.Combine(GetProjectRoot(), "Library", "Bee", "Android"), "native-debug-symbols.zip");
|
||||
}
|
||||
|
||||
private static string FindMappingFile(BuildProfile profile, string buildOutputPath)
|
||||
{
|
||||
string buildDirectory = Path.GetDirectoryName(buildOutputPath);
|
||||
string newestOutputMapping = FindNewestFile(buildDirectory, "*mapping*.txt");
|
||||
if (!string.IsNullOrEmpty(newestOutputMapping))
|
||||
{
|
||||
return newestOutputMapping;
|
||||
}
|
||||
|
||||
string buildType = profile != null && profile.isDevelopment ? "debug" : "release";
|
||||
string preferredRoot = Path.Combine(
|
||||
GetProjectRoot(),
|
||||
"Library",
|
||||
"Bee",
|
||||
"Android",
|
||||
"Prj",
|
||||
"IL2CPP",
|
||||
"Gradle",
|
||||
"launcher",
|
||||
"build",
|
||||
"outputs",
|
||||
"mapping",
|
||||
buildType);
|
||||
|
||||
string preferredMapping = FindNewestFile(preferredRoot, "mapping.txt");
|
||||
return !string.IsNullOrEmpty(preferredMapping)
|
||||
? preferredMapping
|
||||
: FindNewestFile(Path.Combine(GetProjectRoot(), "Library", "Bee", "Android"), "mapping.txt");
|
||||
}
|
||||
|
||||
private static string FindNewestFile(string root, string fileName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(root) || !Directory.Exists(root))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string newestPath = string.Empty;
|
||||
DateTime newestWriteTime = DateTime.MinValue;
|
||||
foreach (string file in Directory.GetFiles(root, fileName, SearchOption.AllDirectories))
|
||||
{
|
||||
DateTime writeTime = File.GetLastWriteTimeUtc(file);
|
||||
if (writeTime > newestWriteTime)
|
||||
{
|
||||
newestPath = file;
|
||||
newestWriteTime = writeTime;
|
||||
}
|
||||
}
|
||||
|
||||
return newestPath;
|
||||
}
|
||||
|
||||
private static string WriteManifest(BuildProfile profile, string buildOutputPath, string archiveDirectory, BuglySymbolArchiveResult result)
|
||||
{
|
||||
BuglySymbolManifest manifest = new BuglySymbolManifest
|
||||
{
|
||||
productName = profile.productName,
|
||||
bundleIdentifier = profile.bundleIdentifier,
|
||||
version = profile.version,
|
||||
buildNumber = profile.buildNumber,
|
||||
buildOutputPath = buildOutputPath,
|
||||
buildOutputSha256 = File.Exists(buildOutputPath) ? GetSHA256Hash(buildOutputPath) : string.Empty,
|
||||
nativeSymbolPath = result.nativeSymbolPath,
|
||||
nativeSymbolSha256 = File.Exists(result.nativeSymbolPath) ? GetSHA256Hash(result.nativeSymbolPath) : string.Empty,
|
||||
mappingPath = result.mappingPath,
|
||||
mappingSha256 = File.Exists(result.mappingPath) ? GetSHA256Hash(result.mappingPath) : string.Empty,
|
||||
createdAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
};
|
||||
|
||||
string manifestPath = Path.Combine(archiveDirectory, "bugly-symbol-manifest.json");
|
||||
File.WriteAllText(manifestPath, JsonUtility.ToJson(manifest, true), Encoding.UTF8);
|
||||
return manifestPath;
|
||||
}
|
||||
|
||||
private static string CreatePackage(string archiveDirectory, string packagePath)
|
||||
{
|
||||
if (File.Exists(packagePath))
|
||||
{
|
||||
File.Delete(packagePath);
|
||||
}
|
||||
|
||||
ZipFile.CreateFromDirectory(
|
||||
archiveDirectory,
|
||||
packagePath,
|
||||
System.IO.Compression.CompressionLevel.Optimal,
|
||||
false);
|
||||
return packagePath;
|
||||
}
|
||||
|
||||
private static BuglySymbolArchiveResult PrepareUploadInputs(BuildProfile profile, BuglySymbolArchiveResult archive, LogHandler log)
|
||||
{
|
||||
string stagingRoot = Path.Combine(GetProjectRoot(), UploadStagingDirectory.Replace("/", Path.DirectorySeparatorChar.ToString()));
|
||||
string stagingDirectory = Path.Combine(stagingRoot, BuildSafeStagingName(profile) + "_" + DateTime.Now.ToString("yyyyMMddHHmmss"));
|
||||
Directory.CreateDirectory(stagingDirectory);
|
||||
|
||||
BuglySymbolArchiveResult uploadArchive = new BuglySymbolArchiveResult
|
||||
{
|
||||
hasSymbols = archive.hasSymbols,
|
||||
archiveDirectory = archive.archiveDirectory,
|
||||
packagePath = archive.packagePath,
|
||||
manifestPath = archive.manifestPath,
|
||||
message = archive.message
|
||||
};
|
||||
|
||||
if (File.Exists(archive.nativeSymbolPath))
|
||||
{
|
||||
string symbolDirectory = Path.Combine(stagingDirectory, "symbols");
|
||||
Directory.CreateDirectory(symbolDirectory);
|
||||
ZipFile.ExtractToDirectory(archive.nativeSymbolPath, symbolDirectory);
|
||||
uploadArchive.nativeSymbolPath = symbolDirectory;
|
||||
WriteLog(log, $"已解压 Bugly SO 符号表到英文临时目录:{symbolDirectory}");
|
||||
}
|
||||
|
||||
if (File.Exists(archive.mappingPath))
|
||||
{
|
||||
uploadArchive.mappingPath = Path.Combine(stagingDirectory, "mapping.txt");
|
||||
File.Copy(archive.mappingPath, uploadArchive.mappingPath, true);
|
||||
}
|
||||
|
||||
return uploadArchive;
|
||||
}
|
||||
|
||||
private static string BuildSafeStagingName(BuildProfile profile)
|
||||
{
|
||||
string rawName = profile == null ? "bugly" : profile.profileName + "_" + profile.version + "_" + profile.buildNumber;
|
||||
StringBuilder builder = new StringBuilder(rawName.Length);
|
||||
foreach (char ch in rawName)
|
||||
{
|
||||
builder.Append(char.IsLetterOrDigit(ch) || ch == '_' || ch == '-' || ch == '.' ? ch : '_');
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string WriteUploadLog(string archiveDirectory, BuglySymbolUploadResult result)
|
||||
{
|
||||
string logDirectory = Directory.Exists(archiveDirectory) ? archiveDirectory : GetProjectRoot();
|
||||
string logPath = Path.Combine(logDirectory, "bugly-symbol-upload.log");
|
||||
File.WriteAllText(
|
||||
logPath,
|
||||
$"Time: {DateTime.Now:yyyy-MM-dd HH:mm:ss}\nCommand: {result.command}\nExitCode: {result.exitCode}\n--- Output ---\n{result.output}\n--- Error ---\n{result.error}",
|
||||
Encoding.UTF8);
|
||||
return logPath;
|
||||
}
|
||||
|
||||
private static BuglySymbolUploadResult FailUpload(string message, LogHandler log)
|
||||
{
|
||||
WriteLog(log, message, LogType.Error);
|
||||
return new BuglySymbolUploadResult
|
||||
{
|
||||
success = false,
|
||||
exitCode = -1,
|
||||
error = message
|
||||
};
|
||||
}
|
||||
|
||||
private static bool PathExists(string path)
|
||||
{
|
||||
return File.Exists(path) || Directory.Exists(path);
|
||||
}
|
||||
|
||||
private static Encoding GetBuglyToolOutputEncoding()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Encoding.GetEncoding("GB18030");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Encoding.Default;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsBuglyUploadOutputSuccessful(string output, string error)
|
||||
{
|
||||
string combinedText = (output ?? string.Empty) + "\n" + (error ?? string.Empty);
|
||||
return combinedText.IndexOf("plugin execute result status: failure", StringComparison.OrdinalIgnoreCase) < 0 &&
|
||||
combinedText.IndexOf("event_res:failure", StringComparison.OrdinalIgnoreCase) < 0 &&
|
||||
combinedText.IndexOf("##[error]", StringComparison.OrdinalIgnoreCase) < 0 &&
|
||||
combinedText.IndexOf("errorMsg is", StringComparison.OrdinalIgnoreCase) < 0;
|
||||
}
|
||||
|
||||
private static string ResolveFullPath(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string normalizedPath = path.Replace("{inproject}: ", string.Empty);
|
||||
return Path.IsPathRooted(normalizedPath)
|
||||
? Path.GetFullPath(normalizedPath)
|
||||
: Path.GetFullPath(Path.Combine(GetProjectRoot(), normalizedPath));
|
||||
}
|
||||
|
||||
private static string GetProjectRoot()
|
||||
{
|
||||
return Directory.GetParent(Application.dataPath).FullName;
|
||||
}
|
||||
|
||||
private static string Quote(string value)
|
||||
{
|
||||
return string.IsNullOrEmpty(value) ? "\"\"" : "\"" + value.Replace("\"", "\\\"") + "\"";
|
||||
}
|
||||
|
||||
private static string MaskUploadArguments(string arguments)
|
||||
{
|
||||
return MaskArgumentValue(arguments, "-appkey");
|
||||
}
|
||||
|
||||
private static string MaskArgumentValue(string arguments, string key)
|
||||
{
|
||||
int keyIndex = arguments.IndexOf(key, StringComparison.OrdinalIgnoreCase);
|
||||
if (keyIndex < 0)
|
||||
{
|
||||
return arguments;
|
||||
}
|
||||
|
||||
int valueStart = keyIndex + key.Length;
|
||||
while (valueStart < arguments.Length && char.IsWhiteSpace(arguments[valueStart]))
|
||||
{
|
||||
valueStart++;
|
||||
}
|
||||
|
||||
if (valueStart >= arguments.Length)
|
||||
{
|
||||
return arguments;
|
||||
}
|
||||
|
||||
int tokenEnd = valueStart;
|
||||
if (arguments[valueStart] == '"')
|
||||
{
|
||||
tokenEnd = arguments.IndexOf('"', valueStart + 1);
|
||||
return tokenEnd > valueStart
|
||||
? arguments.Substring(0, valueStart + 1) + "******" + arguments.Substring(tokenEnd)
|
||||
: arguments;
|
||||
}
|
||||
|
||||
while (tokenEnd < arguments.Length && !char.IsWhiteSpace(arguments[tokenEnd]))
|
||||
{
|
||||
tokenEnd++;
|
||||
}
|
||||
|
||||
return arguments.Substring(0, valueStart) + "******" + arguments.Substring(tokenEnd);
|
||||
}
|
||||
|
||||
private static string GetSHA256Hash(string filePath)
|
||||
{
|
||||
using (SHA256 sha256 = SHA256.Create())
|
||||
using (FileStream stream = File.OpenRead(filePath))
|
||||
{
|
||||
return BitConverter.ToString(sha256.ComputeHash(stream)).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteLog(LogHandler log, string message, LogType type = LogType.Log)
|
||||
{
|
||||
if (log != null)
|
||||
{
|
||||
log(message, type);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case LogType.Error:
|
||||
case LogType.Exception:
|
||||
Debug.LogError(message);
|
||||
break;
|
||||
case LogType.Warning:
|
||||
Debug.LogWarning(message);
|
||||
break;
|
||||
default:
|
||||
Debug.Log(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class BuglySymbolArchiveResult
|
||||
{
|
||||
public bool hasSymbols;
|
||||
public string archiveDirectory;
|
||||
public string packagePath;
|
||||
public string nativeSymbolPath;
|
||||
public string mappingPath;
|
||||
public string manifestPath;
|
||||
public string message;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class BuglySymbolUploadResult
|
||||
{
|
||||
public bool success;
|
||||
public int exitCode;
|
||||
public string command;
|
||||
public string output;
|
||||
public string error;
|
||||
public string logPath;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class BuglySymbolManifest
|
||||
{
|
||||
public string productName;
|
||||
public string bundleIdentifier;
|
||||
public string version;
|
||||
public int buildNumber;
|
||||
public string buildOutputPath;
|
||||
public string buildOutputSha256;
|
||||
public string nativeSymbolPath;
|
||||
public string nativeSymbolSha256;
|
||||
public string mappingPath;
|
||||
public string mappingSha256;
|
||||
public string createdAt;
|
||||
}
|
||||
}
|
||||
11
Editor/BuglyAndroidSymbolUtility.cs.meta
Normal file
11
Editor/BuglyAndroidSymbolUtility.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ecfd0727debd42f7bde54f6b3d1f5f74
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using CCFramework.CrashReport.Editor;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEngine;
|
||||
@@ -10,7 +11,6 @@ namespace Editor
|
||||
{
|
||||
public static class CrashPostProcessBuildIOS
|
||||
{
|
||||
private const string PodLine = " pod 'Bugly', '~> 2.6'";
|
||||
private const string UnityFrameworkTarget = "target 'UnityFramework' do";
|
||||
|
||||
[PostProcessBuild(980)]
|
||||
@@ -21,13 +21,21 @@ namespace Editor
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureBuglyPod(pathToBuiltProject);
|
||||
CrashReportBuglyProfileSettings settings = CrashReportBuildSettingsStore.GetLastBuildProfileSettings();
|
||||
if (settings == null || !settings.enableIOSPod)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureBuglyPod(pathToBuiltProject, settings.iosPodVersion);
|
||||
}
|
||||
|
||||
private static void EnsureBuglyPod(string pathToBuiltProject)
|
||||
public static void EnsureBuglyPod(string pathToBuiltProject, string podVersion)
|
||||
{
|
||||
string podfilePath = Path.Combine(pathToBuiltProject, "Podfile");
|
||||
string content = File.Exists(podfilePath) ? File.ReadAllText(podfilePath) : string.Empty;
|
||||
string normalizedVersion = string.IsNullOrWhiteSpace(podVersion) ? "~> 2.6" : podVersion.Trim();
|
||||
string podLine = $" pod 'Bugly', '{normalizedVersion}'";
|
||||
|
||||
if (content.Contains("pod 'Bugly'") || content.Contains("pod \"Bugly\""))
|
||||
{
|
||||
@@ -40,12 +48,12 @@ namespace Editor
|
||||
"platform :ios, '9.0'" + Environment.NewLine +
|
||||
Environment.NewLine +
|
||||
UnityFrameworkTarget + Environment.NewLine +
|
||||
PodLine + Environment.NewLine +
|
||||
podLine + Environment.NewLine +
|
||||
"end" + Environment.NewLine);
|
||||
}
|
||||
else if (content.Contains(UnityFrameworkTarget))
|
||||
{
|
||||
content = content.Replace(UnityFrameworkTarget, UnityFrameworkTarget + Environment.NewLine + PodLine);
|
||||
content = content.Replace(UnityFrameworkTarget, UnityFrameworkTarget + Environment.NewLine + podLine);
|
||||
File.WriteAllText(podfilePath, content);
|
||||
}
|
||||
else
|
||||
@@ -53,11 +61,11 @@ namespace Editor
|
||||
File.AppendAllText(podfilePath,
|
||||
Environment.NewLine +
|
||||
UnityFrameworkTarget + Environment.NewLine +
|
||||
PodLine + Environment.NewLine +
|
||||
podLine + Environment.NewLine +
|
||||
"end" + Environment.NewLine);
|
||||
}
|
||||
|
||||
Debug.Log("CrashReport iOS 已写入 Bugly CocoaPods 依赖,请在 Xcode 构建前执行 pod install。");
|
||||
Debug.Log($"CrashReport iOS 已写入 Bugly CocoaPods 依赖({normalizedVersion}),请在 Xcode 构建前执行 pod install。");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
481
Editor/CrashReportBuildExtension.cs
Normal file
481
Editor/CrashReportBuildExtension.cs
Normal file
@@ -0,0 +1,481 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Runtime;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
namespace CCFramework.CrashReport.Editor
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public static class CrashReportBuildExtensionBootstrap
|
||||
{
|
||||
static CrashReportBuildExtensionBootstrap()
|
||||
{
|
||||
BuildWindowExtensionRegistry.Register(new CrashReportBuildExtension());
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CrashReportBuildExtension : IBuildWindowExtension
|
||||
{
|
||||
public string Id => "ccframework.crashreport.bugly";
|
||||
public string DisplayName => "Crash/Bugly";
|
||||
public int Order => 50;
|
||||
|
||||
public bool IsEnabled(BuildWindowExtensionContext context)
|
||||
{
|
||||
BuildProfile profile = context?.profile;
|
||||
return profile != null && (IsAndroidProfile(profile) || IsIOSProfile(profile));
|
||||
}
|
||||
|
||||
public void DrawSection(BuildWindowExtensionContext context)
|
||||
{
|
||||
BuildProfile profile = context?.profile;
|
||||
if (profile == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("当前没有构建配置。", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
CrashReportBuildSettings settings = CrashReportBuildSettingsStore.Load();
|
||||
CrashReportBuglyProfileSettings profileSettings =
|
||||
CrashReportBuildSettingsStore.GetProfileSettings(settings, profile.profileName);
|
||||
bool migrated = TryMigrateLegacyProfileSettings(profile, profileSettings);
|
||||
CrashReportBuildSettingsStore.Normalize(profileSettings);
|
||||
if (migrated)
|
||||
{
|
||||
CrashReportBuildSettingsStore.Save(settings);
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
DrawRuntimeCrashConfig();
|
||||
EditorGUILayout.Space(8);
|
||||
|
||||
if (IsAndroidProfile(profile))
|
||||
{
|
||||
DrawAndroidSection(profile, profileSettings);
|
||||
}
|
||||
else if (IsIOSProfile(profile))
|
||||
{
|
||||
DrawIOSSection(profileSettings);
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
CrashReportBuildSettingsStore.Normalize(profileSettings);
|
||||
CrashReportBuildSettingsStore.Save(settings);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
}
|
||||
|
||||
public BuildWindowExtensionReport Preflight(BuildWindowExtensionContext context)
|
||||
{
|
||||
BuildProfile profile = context?.profile;
|
||||
if (profile == null)
|
||||
{
|
||||
return BuildWindowExtensionReport.Pass();
|
||||
}
|
||||
|
||||
CrashReportBuildSettings settings = CrashReportBuildSettingsStore.Load();
|
||||
settings.lastBuildProfileName = profile.profileName;
|
||||
CrashReportBuglyProfileSettings profileSettings =
|
||||
CrashReportBuildSettingsStore.GetProfileSettings(settings, profile.profileName);
|
||||
TryMigrateLegacyProfileSettings(profile, profileSettings);
|
||||
CrashReportBuildSettingsStore.Normalize(profileSettings);
|
||||
CrashReportBuildSettingsStore.Save(settings);
|
||||
|
||||
if (IsAndroidProfile(profile))
|
||||
{
|
||||
BuglyAndroidSymbolUtility.ApplyAndroidSymbolSettings(profile, profileSettings, WriteLog);
|
||||
return profileSettings.enableAndroidSymbolArchive
|
||||
? BuildWindowExtensionReport.Pass().AddMessage("Bugly Android 符号表生成已启用。")
|
||||
: BuildWindowExtensionReport.Pass();
|
||||
}
|
||||
|
||||
if (IsIOSProfile(profile) && profileSettings.enableIOSPod)
|
||||
{
|
||||
return BuildWindowExtensionReport.Pass().AddMessage("Bugly iOS Pod 注入已启用。");
|
||||
}
|
||||
|
||||
return BuildWindowExtensionReport.Pass();
|
||||
}
|
||||
|
||||
public BuildWindowExtensionReport PostBuild(BuildWindowExtensionContext context)
|
||||
{
|
||||
BuildProfile profile = context?.profile;
|
||||
if (profile == null || !context.lastBuildSuccess)
|
||||
{
|
||||
return BuildWindowExtensionReport.Pass();
|
||||
}
|
||||
|
||||
CrashReportBuildSettings settings = CrashReportBuildSettingsStore.Load();
|
||||
CrashReportBuglyProfileSettings profileSettings =
|
||||
CrashReportBuildSettingsStore.GetProfileSettings(settings, profile.profileName);
|
||||
CrashReportBuildSettingsStore.Normalize(profileSettings);
|
||||
|
||||
if (IsAndroidProfile(profile))
|
||||
{
|
||||
return PostBuildAndroid(profile, profileSettings, context.lastBuildOutputPath);
|
||||
}
|
||||
|
||||
if (IsIOSProfile(profile))
|
||||
{
|
||||
return PostBuildIOS(profileSettings, context.lastBuildOutputPath);
|
||||
}
|
||||
|
||||
return BuildWindowExtensionReport.Pass();
|
||||
}
|
||||
|
||||
private static void DrawRuntimeCrashConfig()
|
||||
{
|
||||
EditorGUILayout.LabelField("运行时上报配置", EditorStyles.boldLabel);
|
||||
SerializedObject serializedObject = GetCrashConfigSerializedObject();
|
||||
if (serializedObject == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("未找到 CrashConfig.asset。", MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("EnableCrashReport"), new GUIContent("启用上报"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("HasDebugMode"), new GUIContent("Debug日志"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("BuglyAppID"), new GUIContent("Bugly App ID"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("BuglyChannel"), new GUIContent("Bugly Channel"));
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private static void DrawAndroidSection(BuildProfile profile, CrashReportBuglyProfileSettings settings)
|
||||
{
|
||||
EditorGUILayout.LabelField("Bugly Android 符号表", EditorStyles.boldLabel);
|
||||
settings.enableAndroidSymbolArchive = EditorGUILayout.Toggle("构建后整理符号表", settings.enableAndroidSymbolArchive);
|
||||
settings.androidAutoUploadSymbols = EditorGUILayout.Toggle("构建后自动上传", settings.androidAutoUploadSymbols);
|
||||
settings.buglyAppId = EditorGUILayout.TextField("Bugly App ID", settings.buglyAppId ?? string.Empty);
|
||||
settings.buglyAppKey = EditorGUILayout.PasswordField("Bugly App Key", settings.buglyAppKey ?? string.Empty);
|
||||
settings.buglySymbolToolPath = EditorGUILayout.TextField("符号表工具 Jar", settings.buglySymbolToolPath ?? string.Empty);
|
||||
settings.buglyJavaPath = EditorGUILayout.TextField("Java 命令", string.IsNullOrWhiteSpace(settings.buglyJavaPath) ? "java" : settings.buglyJavaPath);
|
||||
|
||||
string resolvedToolPath = BuglyAndroidSymbolUtility.ResolveSymbolToolPath(settings);
|
||||
EditorGUILayout.LabelField("实际工具路径", string.IsNullOrEmpty(resolvedToolPath) ? "未找到" : resolvedToolPath);
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("下载 Zip", GUILayout.Width(100)))
|
||||
{
|
||||
Application.OpenURL(BuglyAndroidSymbolUtility.BuglyDownloadUrl);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("打开说明", GUILayout.Width(100)))
|
||||
{
|
||||
Application.OpenURL(BuglyAndroidSymbolUtility.BuglyToolGuideUrl);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("选择 Jar", GUILayout.Width(100)))
|
||||
{
|
||||
SelectBuglySymbolTool(settings);
|
||||
GUI.changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("复制命令", GUILayout.Width(100)))
|
||||
{
|
||||
EditorGUIUtility.systemCopyBuffer =
|
||||
BuglyAndroidSymbolUtility.BuildUploadCommandTemplate(profile, settings, false);
|
||||
Debug.Log("Bugly 符号表上传命令已复制到剪贴板。");
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("上传命令模板");
|
||||
EditorGUILayout.TextArea(BuglyAndroidSymbolUtility.BuildUploadCommandTemplate(profile, settings, true), GUILayout.Height(76));
|
||||
}
|
||||
|
||||
private static void DrawIOSSection(CrashReportBuglyProfileSettings settings)
|
||||
{
|
||||
EditorGUILayout.LabelField("Bugly iOS CocoaPods", EditorStyles.boldLabel);
|
||||
settings.enableIOSPod = EditorGUILayout.Toggle("注入 Bugly Pod", settings.enableIOSPod);
|
||||
settings.iosPodVersion = EditorGUILayout.TextField("Pod 版本", string.IsNullOrWhiteSpace(settings.iosPodVersion) ? "~> 2.6" : settings.iosPodVersion);
|
||||
settings.iosRunPodInstall = EditorGUILayout.Toggle("构建后执行 pod install", settings.iosRunPodInstall);
|
||||
settings.podExecutablePath = EditorGUILayout.TextField("Pod 命令", string.IsNullOrWhiteSpace(settings.podExecutablePath) ? "pod" : settings.podExecutablePath);
|
||||
EditorGUILayout.HelpBox("iOS 原生桥由 CrashReport 包提供;证书签名由基础构建页的 iOS 设置写入 Xcode 工程。", MessageType.Info);
|
||||
}
|
||||
|
||||
private static BuildWindowExtensionReport PostBuildAndroid(
|
||||
BuildProfile profile,
|
||||
CrashReportBuglyProfileSettings settings,
|
||||
string buildOutputPath)
|
||||
{
|
||||
if (settings == null || !settings.enableAndroidSymbolArchive)
|
||||
{
|
||||
return BuildWindowExtensionReport.Pass();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(buildOutputPath))
|
||||
{
|
||||
buildOutputPath = BuildPipelineCore.GetExpectedBuildOutputPath(profile);
|
||||
}
|
||||
|
||||
BuglySymbolArchiveResult archive =
|
||||
BuglyAndroidSymbolUtility.ArchiveAfterBuild(profile, settings, buildOutputPath, WriteLog);
|
||||
if (settings.androidAutoUploadSymbols)
|
||||
{
|
||||
BuglySymbolUploadResult upload =
|
||||
BuglyAndroidSymbolUtility.UploadPreparedSymbols(profile, settings, archive, WriteLog);
|
||||
return upload != null && upload.success
|
||||
? BuildWindowExtensionReport.Pass().AddMessage("Bugly 符号表已归档并上传。")
|
||||
: BuildWindowExtensionReport.Pass().AddWarning("Bugly 符号表已归档,但上传未成功,请查看日志。");
|
||||
}
|
||||
|
||||
return BuildWindowExtensionReport.Pass().AddMessage("Bugly 符号表已归档。");
|
||||
}
|
||||
|
||||
private static BuildWindowExtensionReport PostBuildIOS(CrashReportBuglyProfileSettings settings, string xcodeProjectPath)
|
||||
{
|
||||
if (settings == null || !settings.enableIOSPod || !settings.iosRunPodInstall)
|
||||
{
|
||||
return BuildWindowExtensionReport.Pass();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(xcodeProjectPath) || !Directory.Exists(xcodeProjectPath))
|
||||
{
|
||||
return BuildWindowExtensionReport.Pass().AddWarning("未找到 iOS Xcode 工程目录,已跳过 pod install。");
|
||||
}
|
||||
|
||||
return RunPodInstall(settings, xcodeProjectPath)
|
||||
? BuildWindowExtensionReport.Pass().AddMessage("pod install 执行完成。")
|
||||
: BuildWindowExtensionReport.Pass().AddWarning("pod install 未成功,请查看 Unity Console 日志。");
|
||||
}
|
||||
|
||||
private static bool RunPodInstall(CrashReportBuglyProfileSettings settings, string xcodeProjectPath)
|
||||
{
|
||||
string podCommand = string.IsNullOrWhiteSpace(settings.podExecutablePath) ? "pod" : settings.podExecutablePath;
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = podCommand,
|
||||
Arguments = "install",
|
||||
WorkingDirectory = xcodeProjectPath,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using (Process process = Process.Start(startInfo))
|
||||
{
|
||||
string output = process.StandardOutput.ReadToEnd();
|
||||
string error = process.StandardError.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
if (!string.IsNullOrWhiteSpace(output))
|
||||
{
|
||||
Debug.Log(output);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(error))
|
||||
{
|
||||
Debug.LogWarning(error);
|
||||
}
|
||||
|
||||
return process.ExitCode == 0;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[CrashReport] 执行 pod install 失败:{e.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static SerializedObject GetCrashConfigSerializedObject()
|
||||
{
|
||||
const string path = "Assets/Resources";
|
||||
const string assetPath = "Assets/Resources/CrashConfig.asset";
|
||||
CrashConfig config = AssetDatabase.LoadAssetAtPath<CrashConfig>(assetPath);
|
||||
if (config == null)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
config = ScriptableObject.CreateInstance<CrashConfig>();
|
||||
AssetDatabase.CreateAsset(config, assetPath);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
return config == null ? null : new SerializedObject(config);
|
||||
}
|
||||
|
||||
private static bool TryMigrateLegacyProfileSettings(
|
||||
BuildProfile profile,
|
||||
CrashReportBuglyProfileSettings settings)
|
||||
{
|
||||
if (profile == null || settings == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
|
||||
if (!settings.enableAndroidSymbolArchive && profile.enableBuglySymbolArchive)
|
||||
{
|
||||
settings.enableAndroidSymbolArchive = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!settings.androidAutoUploadSymbols && profile.buglyAutoUploadSymbols)
|
||||
{
|
||||
settings.androidAutoUploadSymbols = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings.buglyAppId) &&
|
||||
!string.IsNullOrWhiteSpace(profile.buglyAppId))
|
||||
{
|
||||
settings.buglyAppId = profile.buglyAppId;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings.buglyAppKey) &&
|
||||
!string.IsNullOrWhiteSpace(profile.buglyAppKey))
|
||||
{
|
||||
settings.buglyAppKey = profile.buglyAppKey;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (IsDefaultSymbolToolPath(settings.buglySymbolToolPath) &&
|
||||
!string.IsNullOrWhiteSpace(profile.buglySymbolToolPath))
|
||||
{
|
||||
settings.buglySymbolToolPath = profile.buglySymbolToolPath;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (string.Equals(settings.buglyJavaPath, "java", StringComparison.Ordinal) &&
|
||||
!string.IsNullOrWhiteSpace(profile.buglyJavaPath) &&
|
||||
!string.Equals(profile.buglyJavaPath, "java", StringComparison.Ordinal))
|
||||
{
|
||||
settings.buglyJavaPath = profile.buglyJavaPath;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
private static bool IsDefaultSymbolToolPath(string value)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value) ||
|
||||
string.Equals(value, "Tools/BuglySymbolTool/buglyqq-upload-symbol.jar", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsAndroidProfile(BuildProfile profile)
|
||||
{
|
||||
return string.Equals(ResolveProfilePlatformName(profile), "Android", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsIOSProfile(BuildProfile profile)
|
||||
{
|
||||
string platformName = ResolveProfilePlatformName(profile);
|
||||
return string.Equals(platformName, "iOS", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(platformName, "iPhone", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string ResolveProfilePlatformName(BuildProfile profile)
|
||||
{
|
||||
if (profile == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
Type profileType = profile.GetType();
|
||||
string targetPlatform = GetStringField(profileType, profile, "targetPlatform");
|
||||
string buildTargetGroup = GetStringField(profileType, profile, "buildTargetGroup");
|
||||
string buildTarget = GetStringField(profileType, profile, "buildTarget");
|
||||
bool hasExplicitPlatformValue =
|
||||
!string.IsNullOrWhiteSpace(targetPlatform) ||
|
||||
!string.IsNullOrWhiteSpace(buildTargetGroup) ||
|
||||
!string.IsNullOrWhiteSpace(buildTarget);
|
||||
|
||||
if (IsPlatformValue(targetPlatform, "iOS", "iPhone") ||
|
||||
IsPlatformValue(buildTargetGroup, "iOS") ||
|
||||
IsPlatformValue(buildTarget, "iOS", "iPhone"))
|
||||
{
|
||||
return "iOS";
|
||||
}
|
||||
|
||||
if (IsPlatformValue(targetPlatform, "Android") ||
|
||||
IsPlatformValue(buildTargetGroup, "Android") ||
|
||||
IsPlatformValue(buildTarget, "Android"))
|
||||
{
|
||||
return "Android";
|
||||
}
|
||||
|
||||
if (hasExplicitPlatformValue)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS)
|
||||
{
|
||||
return "iOS";
|
||||
}
|
||||
|
||||
return EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android ? "Android" : string.Empty;
|
||||
}
|
||||
|
||||
private static string GetStringField(Type ownerType, object instance, string fieldName)
|
||||
{
|
||||
FieldInfo field = ownerType.GetField(fieldName, BindingFlags.Public | BindingFlags.Instance);
|
||||
return field?.GetValue(instance) as string ?? string.Empty;
|
||||
}
|
||||
|
||||
private static bool IsPlatformValue(string value, params string[] expectedValues)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (string expectedValue in expectedValues)
|
||||
{
|
||||
if (string.Equals(value, expectedValue, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SelectBuglySymbolTool(CrashReportBuglyProfileSettings settings)
|
||||
{
|
||||
string selectedPath = EditorUtility.OpenFilePanel("选择 Bugly 符号表工具", Application.dataPath, "jar");
|
||||
if (string.IsNullOrEmpty(selectedPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
settings.buglySymbolToolPath = BuglyAndroidSymbolUtility.ToProfilePath(selectedPath);
|
||||
}
|
||||
|
||||
private static void WriteLog(string message, LogType type = LogType.Log)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case LogType.Error:
|
||||
case LogType.Exception:
|
||||
Debug.LogError(message);
|
||||
break;
|
||||
case LogType.Warning:
|
||||
Debug.LogWarning(message);
|
||||
break;
|
||||
default:
|
||||
Debug.Log(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/CrashReportBuildExtension.cs.meta
Normal file
11
Editor/CrashReportBuildExtension.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b8fdf55210d41f6906198c6f5e3901b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
130
Editor/CrashReportBuildSettings.cs
Normal file
130
Editor/CrashReportBuildSettings.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace CCFramework.CrashReport.Editor
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class CrashReportBuildSettings
|
||||
{
|
||||
public string lastBuildProfileName = string.Empty;
|
||||
public List<CrashReportBuglyProfileSettings> profiles = new List<CrashReportBuglyProfileSettings>();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class CrashReportBuglyProfileSettings
|
||||
{
|
||||
public string profileName = "default";
|
||||
public bool enableAndroidSymbolArchive = true;
|
||||
public bool androidAutoUploadSymbols = false;
|
||||
public string buglyAppId = string.Empty;
|
||||
public string buglyAppKey = string.Empty;
|
||||
public string buglySymbolToolPath = "Tools/BuglySymbolTool/buglyqq-upload-symbol.jar";
|
||||
public string buglyJavaPath = "java";
|
||||
public bool enableIOSPod = true;
|
||||
public string iosPodVersion = "~> 2.6";
|
||||
public bool iosRunPodInstall = false;
|
||||
public string podExecutablePath = "pod";
|
||||
}
|
||||
|
||||
public static class CrashReportBuildSettingsStore
|
||||
{
|
||||
private const string SettingsDirectory = "ProjectSettings/CrashReport";
|
||||
private const string SettingsFileName = "crashreport-build-settings.json";
|
||||
|
||||
public static string SettingsPath => Path.Combine(SettingsDirectory, SettingsFileName);
|
||||
|
||||
public static CrashReportBuildSettings Load()
|
||||
{
|
||||
if (!File.Exists(SettingsPath))
|
||||
{
|
||||
return new CrashReportBuildSettings();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(SettingsPath);
|
||||
CrashReportBuildSettings settings = JsonUtility.FromJson<CrashReportBuildSettings>(json);
|
||||
return settings ?? new CrashReportBuildSettings();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[CrashReport] 构建配置读取失败,将使用默认配置:{e.Message}");
|
||||
return new CrashReportBuildSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Save(CrashReportBuildSettings settings)
|
||||
{
|
||||
if (!Directory.Exists(SettingsDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(SettingsDirectory);
|
||||
}
|
||||
|
||||
File.WriteAllText(SettingsPath, JsonUtility.ToJson(settings ?? new CrashReportBuildSettings(), true));
|
||||
}
|
||||
|
||||
public static CrashReportBuglyProfileSettings GetProfileSettings(CrashReportBuildSettings settings, string profileName)
|
||||
{
|
||||
settings = settings ?? new CrashReportBuildSettings();
|
||||
string key = string.IsNullOrWhiteSpace(profileName) ? "default" : profileName;
|
||||
|
||||
foreach (CrashReportBuglyProfileSettings profileSettings in settings.profiles)
|
||||
{
|
||||
if (profileSettings != null && string.Equals(profileSettings.profileName, key, StringComparison.Ordinal))
|
||||
{
|
||||
Normalize(profileSettings);
|
||||
return profileSettings;
|
||||
}
|
||||
}
|
||||
|
||||
CrashReportBuglyProfileSettings created = new CrashReportBuglyProfileSettings
|
||||
{
|
||||
profileName = key
|
||||
};
|
||||
Normalize(created);
|
||||
settings.profiles.Add(created);
|
||||
return created;
|
||||
}
|
||||
|
||||
public static CrashReportBuglyProfileSettings GetLastBuildProfileSettings()
|
||||
{
|
||||
CrashReportBuildSettings settings = Load();
|
||||
return GetProfileSettings(settings, settings.lastBuildProfileName);
|
||||
}
|
||||
|
||||
public static void Normalize(CrashReportBuglyProfileSettings settings)
|
||||
{
|
||||
if (settings == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings.profileName))
|
||||
{
|
||||
settings.profileName = "default";
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings.buglyJavaPath))
|
||||
{
|
||||
settings.buglyJavaPath = "java";
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings.buglySymbolToolPath))
|
||||
{
|
||||
settings.buglySymbolToolPath = "Tools/BuglySymbolTool/buglyqq-upload-symbol.jar";
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings.iosPodVersion))
|
||||
{
|
||||
settings.iosPodVersion = "~> 2.6";
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings.podExecutablePath))
|
||||
{
|
||||
settings.podExecutablePath = "pod";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/CrashReportBuildSettings.cs.meta
Normal file
11
Editor/CrashReportBuildSettings.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bcb8cf0a589747d490149a0769a07a42
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "com.foldcc.cc-framework.crashreport",
|
||||
"displayName": "CC-Framework.CrashReport",
|
||||
"description": "Crash检测, 异常上报",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.4",
|
||||
"unity": "2022.3",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
@@ -16,6 +16,7 @@
|
||||
},
|
||||
"dependencies":
|
||||
{
|
||||
"com.foldcc.cc-framework.common": "1.0.5"
|
||||
},
|
||||
"keywords": [
|
||||
"Framework"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6bdc75239432ff3458a7b98e29519e6d
|
||||
guid: 69559901a28448778705f9b6d1092264
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
|
||||
Reference in New Issue
Block a user