powerfun-unity/Assets/AppCenter/Editor/AppCenterPostBuild.cs

341 lines
14 KiB
C#

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.AppCenter.Unity;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using System;
using System.Collections.Generic;
using UnityEditor.Build.Reporting;
using UnityEditor.Build;
using UnityEditor;
using UnityEngine;
using System.Reflection;
// Warning: Don't use #if #endif for conditional compilation here as Unity
// doesn't always set the flags early enough.
public class AppCenterPostBuild : IPostprocessBuildWithReport
{
public int callbackOrder { get { return 0; } }
private const string AppManifestFileName = "Package.appxmanifest";
private const string CapabilitiesElement = "Capabilities";
private const string CapabilityElement = "Capability";
private const string CapabilityNameAttribute = "Name";
private const string CapabilityNameAttributeValue = "internetClient";
private const string AppIl2cppXaml = "App.xaml.cpp";
private const string AppIl2cppD3d = "App.cpp";
public void OnPostprocessBuild(BuildReport report)
{
OnPostprocessBuild(report.summary.platform, report.summary.outputPath);
}
public void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject)
{
if (target == BuildTarget.WSAPlayer)
{
AddInternetClientCapability(pathToBuiltProject);
if (PlayerSettings.GetScriptingBackend(BuildTargetGroup.WSA) == ScriptingImplementation.IL2CPP)
{
// Fix System.Diagnostics.Debug IL2CPP implementation.
FixIl2CppLogging(pathToBuiltProject);
}
}
if (target == BuildTarget.iOS &&
PBXProjectWrapper.PBXProjectIsAvailable &&
PlistDocumentWrapper.PlistDocumentIsAvailable)
{
var pbxProject = new PBXProjectWrapper(pathToBuiltProject);
// Update project.
OnPostprocessProject(pbxProject);
pbxProject.WriteToFile();
// Update Info.plist.
var settings = AppCenterSettingsContext.SettingsInstance;
var infoPath = pathToBuiltProject + "/Info.plist";
var info = new PlistDocumentWrapper(infoPath);
OnPostprocessInfo(info, settings);
info.WriteToFile();
// Update capabilities (if possible).
if (ProjectCapabilityManagerWrapper.ProjectCapabilityManagerIsAvailable)
{
var capabilityManager = new ProjectCapabilityManagerWrapper(pbxProject.ProjectPath,
PBXProjectWrapper.GetUnityTargetName(),
pbxProject.GetUnityTargetGuid());
capabilityManager.WriteToFile();
}
}
}
#region UWP Methods
/// <summary>
/// In order to have App Center SDK logs we are using 'OutputDebugStringW' func to display them.
/// To use 'OutputDebugStringW' we should update autogenerated Debugger.cpp file.
/// </summary>
/// <param name="pathToBuiltProject">Path to build project</param>
public static void FixIl2CppLogging(string pathToBuiltProject)
{
var destDebuggerPath = Path.Combine(pathToBuiltProject,
"Il2CppOutputProject\\IL2CPP\\libil2cpp\\icalls\\mscorlib\\System.Diagnostics\\Debugger.cpp");
if (!File.Exists(destDebuggerPath))
{
throw new FileNotFoundException("Debugger.cpp file not found");
}
var codeLines = File.ReadAllLines(destDebuggerPath).ToList();
// Update #include and #undef derictives.
var lastIncludeLineIndex = SearchForLine(codeLines, "#include", true);
if (lastIncludeLineIndex == -1)
{
throw new Exception("Unexpected content of Debugger.cpp");
}
// Add '#include <Windows.h>' which provides 'OutputDebugStringW'.
codeLines.Insert(lastIncludeLineIndex + 1, "#include <Windows.h>");
/*
* 'GetCurrentDirectory' define conflicts with generated code and new versions of Unity
* combine some files on the compilation so the changes in one file can affect another.
*/
codeLines.Insert(lastIncludeLineIndex + 2, "#undef GetCurrentDirectory");
// Add logging method.
var logMethodLineIndex = SearchForLine(codeLines, "void Debugger::Log");
if (logMethodLineIndex == -1)
{
throw new Exception("Unexpected content of Debugger.cpp");
}
var insertingPosition = GetFirstLineInMethodBody(codeLines, logMethodLineIndex);
codeLines.Insert(insertingPosition, "OutputDebugStringW(message->chars);");
// Enable logging.
var isLoggingMethodLineIndex = SearchForLine(codeLines, "bool Debugger::IsLogging");
if (isLoggingMethodLineIndex == -1)
{
throw new Exception("Unexpected content of Debugger.cpp");
}
var firstLineInMethodBody = GetFirstLineInMethodBody(codeLines, isLoggingMethodLineIndex);
var lastLineInMethodBody = GetLastLineInMethodBody(codeLines, isLoggingMethodLineIndex);
codeLines.RemoveRange(firstLineInMethodBody, lastLineInMethodBody - firstLineInMethodBody);
codeLines.Insert(firstLineInMethodBody, "return true;");
File.WriteAllLines(destDebuggerPath, codeLines.ToArray());
}
private static int GetFirstLineInMethodBody(List<string> lines, int currentLineIndex)
{
while (currentLineIndex <= lines.Count && !lines[currentLineIndex].Contains("{"))
{
currentLineIndex++;
}
if (currentLineIndex >= lines.Count)
{
throw new Exception("Unexpected content of Debugger.cpp");
}
return currentLineIndex + 1;
}
private static int GetLastLineInMethodBody(List<string> lines, int currentLineIndex)
{
var lineIndex = GetFirstLineInMethodBody(lines, currentLineIndex);
int bracketsBalance = lines[lineIndex - 1].Count((item) => item == '{');
while (lineIndex <= lines.Count && bracketsBalance != 0)
{
bracketsBalance += lines[lineIndex].Count((item) => item == '{');
bracketsBalance -= lines[lineIndex].Count((item) => item == '}');
lineIndex++;
}
if (bracketsBalance != 0)
{
throw new Exception("Unexpected content of Debugger.cpp");
}
return lineIndex - 1;
}
private static int SearchForLine(List<string> lines, string searchString, bool returnTheLast = false)
{
int position = -1;
for (var i = 0; i < lines.Count; i++)
{
if (lines[i].Contains(searchString))
{
if (returnTheLast)
{
position = i;
}
else
{
return i;
}
}
}
return position;
}
public static string GetAppFilePath(string pathToBuiltProject, string filename)
{
var candidate = Path.Combine(pathToBuiltProject, Application.productName);
candidate = Path.Combine(candidate, filename);
return File.Exists(candidate) ? candidate : null;
}
public static void ProcessUwpIl2CppDependencies()
{
var binaries = AssetDatabase.FindAssets("*", new[] { AppCenterSettingsContext.AppCenterPath + "/Plugins/WSA/IL2CPP" });
foreach (var guid in binaries)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
var importer = AssetImporter.GetAtPath(assetPath) as PluginImporter;
if (importer != null)
{
importer.SetPlatformData(BuildTarget.WSAPlayer, "SDK", "UWP");
importer.SetPlatformData(BuildTarget.WSAPlayer, "ScriptingBackend", "Il2Cpp");
importer.SaveAndReimport();
}
}
}
private static void ExecuteCommand(string command, string arguments, int timeout = 600)
{
try
{
var buildProcess = new System.Diagnostics.Process
{
StartInfo =
{
FileName = command,
Arguments = arguments
}
};
buildProcess.Start();
buildProcess.WaitForExit(timeout * 1000);
}
catch (Exception exception)
{
Debug.LogException(exception);
}
}
private static void AddInternetClientCapability(string pathToBuiltProject)
{
/* Package.appxmanifest file example:
<Package>
<Capabilities>
<Capability Name="internetClient" />
</Capabilities>
</Package> */
var appManifests = Directory.GetFiles(pathToBuiltProject, AppManifestFileName, SearchOption.AllDirectories);
if (appManifests.Length == 0)
{
Debug.LogWarning("Failed to add the `InternetClient` capability, file `" + AppManifestFileName + "` is not found");
return;
}
else if (appManifests.Length > 1)
{
Debug.LogWarning("Failed to add the `InternetClient` capability, multiple `" + AppManifestFileName + "` files found");
return;
}
var appManifestFilePath = appManifests[0];
var xmlFile = XDocument.Load(appManifestFilePath);
var defaultNamespace = xmlFile.Root.GetDefaultNamespace().NamespaceName;
var capabilitiesElements = xmlFile.Root.Elements().Where(element => element.Name.LocalName == CapabilitiesElement).ToList();
if (capabilitiesElements.Count > 1)
{
Debug.LogWarning("Failed to add the `InternetClient` capability, multiple `Capabilities` elements found inside `" + appManifestFilePath + "` file");
return;
}
else if (capabilitiesElements.Count == 0)
{
xmlFile.Root.Add(new XElement(XName.Get(CapabilitiesElement, defaultNamespace), GetInternetClientCapabilityElement(defaultNamespace)));
}
else // capabilitiesElements.Count == 1
{
var capabilitiesElement = capabilitiesElements[0];
foreach (var element in capabilitiesElement.Elements())
{
if (element.Name.LocalName == CapabilityElement &&
GetAttributeValue(element, CapabilityNameAttribute) == CapabilityNameAttributeValue)
{
return;
}
}
capabilitiesElement.Add(GetInternetClientCapabilityElement(defaultNamespace));
}
xmlFile.Save(appManifestFilePath);
}
private static XElement GetInternetClientCapabilityElement(string defaultNamespace)
{
return new XElement(XName.Get(CapabilityElement, defaultNamespace),
new XAttribute(CapabilityNameAttribute, CapabilityNameAttributeValue));
}
internal static string GetAttributeValue(XElement element, string attributeName)
{
var attribute = element.Attribute(attributeName);
return attribute == null ? null : attribute.Value;
}
#endregion
#region iOS Methods
private static void OnPostprocessProject(PBXProjectWrapper project)
{
// Need to add SQLite and zlib dependencies.
project.AddBuildProperty("OTHER_LDFLAGS", "-lsqlite3 -lz");
#if UNITY_2019_3_OR_NEWER
project.AddBuildProperty("CLANG_ENABLE_MODULES", "YES", true);
#else
project.AddBuildProperty("CLANG_ENABLE_MODULES", "YES");
#endif
}
private static void OnPostprocessInfo(PlistDocumentWrapper info, AppCenterSettings settings)
{
if (settings.UseDistribute && AppCenter.Distribute != null)
{
// Add App Center URL scemes.
var schemes = new List<string>() { "appcenter-" + settings.iOSAppSecret };
// Create a reflection call for getting custom schemes from iOS settings.
var playerSettingsClass = typeof(PlayerSettings.iOS);
var iOSURLSchemesMethod = playerSettingsClass.GetMethod("GetURLSchemes", BindingFlags.Static | BindingFlags.NonPublic);
// Verify that method exists and call it for getting custom schemes.
if (iOSURLSchemesMethod != null)
{
var schemesFromSettings = (string[])iOSURLSchemesMethod.Invoke(null, null);
schemes.AddRange(schemesFromSettings.ToList<string>());
}
// Generate scheme information.
var root = info.GetRoot();
var urlTypes = root.GetType().GetMethod("CreateArray").Invoke(root, new object[] { "CFBundleURLTypes" });
if (settings.UseDistribute && AppCenter.Distribute != null)
{
var urlType = urlTypes.GetType().GetMethod("AddDict").Invoke(urlTypes, null);
var setStringMethod = urlType.GetType().GetMethod("SetString");
setStringMethod.Invoke(urlType, new object[] { "CFBundleTypeRole", "None" });
setStringMethod.Invoke(urlType, new object[] { "CFBundleURLName", ApplicationIdHelper.GetApplicationId() });
var urlSchemes = urlType.GetType().GetMethod("CreateArray").Invoke(urlType, new[] { "CFBundleURLSchemes" });
// Add custom schemes defined in Unity players settings.
foreach (var scheme in schemes)
{
urlSchemes.GetType().GetMethod("AddString").Invoke(urlSchemes, new[] { scheme });
}
}
}
}
#endregion
}