打包是程序员必经之路。如何实现自动化是一个程序员的基本素质。(都是因为我懒,哈哈哈)。
使用环境 :unity3D 2017.4.7 Mac Pro
打包分一下几个部分 :
1、unity3D导出xcode 工程
2、设置xcode配置及各种sdk配置
3、命令行打包
4、上传蒲公英
5、上传bugly
【IOS自动化解放双手系列一】unity3D导出xcode 工程并配置各种权限
【IOS自动化解放双手系列二】命令行打IPA包和上传蒲公英
【IOS自动化解放双手系列三】上传bugly
接下来开始正式看代码
1、unity3D导出xcode 工程
我们使用assetbundle打包所以需要先打ab包 让配置相关服务器文件,在进行导出 代码如下
public enum BUILDTYPE {
QA,
PREFE,
REALSE
}
private static BUILDTYPE currenBulidType = BUILDTYPE.REALSE;
public static void ExportIOSProject(BUILDTYPE uILDTYPE) {
currenBulidType = uILDTYPE;
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.iOS, BuildTarget.iOS);
AssetDatabase.Refresh();
MoveGameConfig(uILDTYPE);
string shellPath = Application.dataPath.Replace("Assets", "Build/iOS/");
//step1.打包资源。通过AssetBundleGraph 生成Build AssetsBundles
BuildAssetsBundles();
UnityEngine.Debug.Log("step1 Finished!!!");
//Step2.移除无关文件,为Export IOS 工程做准备,调用shell脚本
RunShell(shellPath + "RemoveFromUnity.sh");
AssetDatabase.Refresh();
UnityEngine.Debug.Log("step2 Finished!!!");
//Step3.导出Apple 真机工程
ExportAppleProject(iOSSdkVersion.DeviceSDK);
UnityEngine.Debug.Log("step3 Finished!!!");
//Step4.删除临时文件,调用shell脚本
RunShell(shellPath + "RecoverUnity.sh");
UnityEngine.Debug.Log("step4 Finished!!!");
PlayerSettings.applicationIdentifier = "com.XXXX.XXX";
AssetDatabase.Refresh();
}
public static void ExportAppleProject(iOSSdkVersion sdk) {
List<string> scenes = new List<string>();
foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes) {
if (!scene.enabled)
continue;
scenes.Add(scene.path);
}
bool isQA = false;
switch (currenBulidType) {
case BUILDTYPE.QA:
case BUILDTYPE.PREFE:
isQA = true;
PlayerSettings.applicationIdentifier = "com.XXXX.XXX";
break;
case BUILDTYPE.REALSE:
PlayerSettings.applicationIdentifier = "com.XXXX.XXX";
isQA = false;
break;
}
EditorUserBuildSettings.development = isQA;
PlayerSettings.iOS.sdkVersion = sdk;
PlayerSettings.iOS.buildNumber = GetVersion().ToString();
//EditorUserBuildSettings.target
BuildOptions buildOption = BuildOptions.None;
string res = BuildPipeline.BuildPlayer(scenes.ToArray(), PathUtils.CombinePath("Build", ToolsFunctions.GetPlatformDefines(), "Project", sdk.ToString()), BuildTarget.iOS, buildOption);
if (res.Length > 0) {
throw new Exception("BuildPlayer failure : " + res);
}
}
//=====================================公共函数=======================================
private static void BuildAssetsBundles() {
UnityEngine.AssetBundles.GraphTool.BatchBuildWindow.Open();
UnityEngine.AssetBundles.GraphTool.BatchBuildWindow.BuildFromMenu();
}
private static void RunShell(string shellFile) {
// Thread thread = new Thread(new )
//为了可以重试多线程所以加了一层
UnityRunShell(shellFile);
}
private static void UnityRunShell(string shellFile) {
Process process = new Process();
process.StartInfo.FileName = shellFile;
// process.StartInfo.Arguments = "";
process.StartInfo.UseShellExecute = false;
process.StartInfo.ErrorDialog = true;
process.StartInfo.CreateNoWindow = false;
process.Start();
process.WaitForExit();
process.Close();
}
private static int GetVersion(){
return ClientVersion.GetIncrementBundleVersion();
}
private static void MoveGameConfig(BUILDTYPE uILDTYPE ){
string path = PathUtils.CombinePath(Application.dataPath,"../GameConfig/gameRealse.xml");
switch(uILDTYPE){
case BUILDTYPE.QA:
path = PathUtils.CombinePath(Application.dataPath,"../GameConfig/gameQA.xml");
break;
case BUILDTYPE.PREFE:
path = PathUtils.CombinePath(Application.dataPath,"../GameConfig/gamePrefe.xml");
break;
case BUILDTYPE.REALSE:
path = PathUtils.CombinePath(Application.dataPath,"../GameConfig/gameRealse.xml");
break;
}
CopyConfig(path);
}
private static void CopyConfig(string path) {
string targePath = PathUtils.CombinePath(PathUtils.GameConfigWWWPath, "game.xml");
string configStr = FileUtils.ReadAllText(path);
if (string.IsNullOrEmpty(configStr)) {
Client.LogManager.LogError("配置信息有误请查看后重新写入");
}
FileUtils.WriteAllText(targePath, configStr);
Client.LogManager.Log("copy GameConfig finish");
}
2、设置xcode配置及各种sdk配置
分一下几部分:
添加类库
框架的搜索追加设置
设置推送权限配置XXXXX.entitlements文件
unity修改oc代码
#if (UNITY_IOS || UNITY_IPHONE)
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System;
using System.Linq;
using System.Diagnostics;
using CDBGUnity;
public class CDBGPluginBuilder : MonoBehaviour {
public static bool isUseTestXG = false;
public static bool enableIosAppController = true;
[PostProcessBuildAttribute(6)]
static void onPostProcessBuild(BuildTarget buildTarget, string path) {
UnityEngine.Debug.Log("PostProcessBuild Start");
if (buildTarget == BuildTarget.iOS) {
// project path
string projectPath = Path.Combine(path, "Unity-iPhone.xcodeproj/project.pbxproj");
// xcodeproj
PBXProject proj = new PBXProject();
proj.ReadFromString(File.ReadAllText(projectPath));
// target
string target = PBXProject.GetUnityTargetName();
target = proj.TargetGuidByName(target);
UnityEngine.Debug.Log(">>>>target: " + target + "path :" + path);
// Add System Frameworks
//UnityEngine.Debug.Log(">>>>>Adding system frameworks to xcodeproj...");
//proj.AddFrameworkToProject(target, "CoreText.framework", false);
//proj.AddFrameworkToProject(target, "MediaPlayer.framework", false);
proj.AddFrameworkToProject(target,"AgoraSigKit.framework",false);
// Macro
//proj.AddBuildProperty(target, "GCC_PREPROCESSOR_DEFINITIONS", "");
// 信鸽相关
//string[] macros = { "USE_TestXG=1" };
//if (isUseTestXG) {
// UnityEngine.Debug.Log("Enabling USE_TestXG...");
// proj.UpdateBuildProperty(target, "GCC_PREPROCESSOR_DEFINITIONS", macros, null);
//} else {
// UnityEngine.Debug.Log("Disabling USE_TestXG...");
// proj.UpdateBuildProperty(target, "GCC_PREPROCESSOR_DEFINITIONS", null, macros);
//}
//proj.AddBuildProperty(target, "GCC_PREPROCESSOR_DEFINITIONS", "");
//macros[0] = "USE_LGCAppController=1";
//if (enableIosAppController) {
// UnityEngine.Debug.Log("Enabling USE_LGCAppController...");
// proj.UpdateBuildProperty(target, "GCC_PREPROCESSOR_DEFINITIONS", macros, null);
//} else {
// UnityEngine.Debug.Log("Disabling USE_LGCAppController...");
// proj.UpdateBuildProperty(target, "GCC_PREPROCESSOR_DEFINITIONS", null, macros);
//}
//UnityEngine.Debug.Log("Setting up linker flags...");
//string[] linkerFlagsToAdd = { "-ObjC", "-force_load", "$(PROJECT_DIR)/Frameworks/Plugins/iOS/LongtuSDK/LTShareSDK.framework/LTShareSDK" };
//proj.UpdateBuildProperty(target, "OTHER_LDFLAGS", linkerFlagsToAdd, null);
////设置报名
//if (isUseTestXG) {
// proj.SetBuildProperty(target, "CODE_SIGN_IDENTITY", "iPhone Developer: jin dingjun (M39LLSEB5V)");
// proj.SetBuildProperty(target, "PROVISIONING_PROFILE", "xxaxc_cj_dev_20170609");
//} else {
// proj.SetBuildProperty(target, "CODE_SIGN_IDENTITY", "iPhone Distribution: Beijing Zhong Qing Long Tu Network Technology Co.,LTD. (UC3LC49W33)");
// proj.SetBuildProperty(target, "PROVISIONING_PROFILE", "xxaxc_dist_20170607");
//}
//proj.overwriteBuildSetting ("CODE_SIGN_IDENTITY", "iPhone Developer: Yu Song (XXXXXXXXXX)", "Release");
//proj.overwriteBuildSetting ("CODE_SIGN_IDENTITY", "iPhone Developer: Yu Song (XXXXXXXXXX)", "Debug");
/****** Game End**/
//UnityEngine.Debug.Log(">>>>target :" + target + "path :" + path);
// 框架的搜索追加设置XXXXX.entitlements
//UnityEngine.Debug.Log(">>>>>LONGTU Setting up framework search paths...");
//proj.SetBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(inherited)");
//proj.AddBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(PROJECT_DIR)/Libraries/");
//proj.AddBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(PROJECT_DIR)/Frameworks/");
//proj.AddBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(PROJECT_DIR)/Frameworks/Plugins/iOS/");
//proj.AddBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(PROJECT_DIR)/Frameworks/Plugins/iOS/LongtuSDK/");
//proj.AddBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(PROJECT_DIR)/Frameworks/ArmorSDK/Plugins/iOS/");
#if UNITY_5
//proj.AddFileToBuild(target, proj.AddFile("usr/lib/libiconv.2.tbd", "Frameworks/libiconv.2.tbd", PBXSourceTree.Sdk));
//proj.AddFileToBuild(target, proj.AddFile("usr/lib/libsqlite3.tbd", "Frameworks/libsqlite3.tbd", PBXSourceTree.Sdk));
//proj.AddFileToBuild(target, proj.AddFile("usr/lib/libstdc++.6.0.9.tbd", "Frameworks/libstdc++.6.0.9.tbd", PBXSourceTree.Sdk));
//proj.AddFileToBuild(target, proj.AddFile("usr/lib/libz.1.tbd", "Frameworks/libz.1.tbd", PBXSourceTree.Sdk));
//proj.AddFileToBuild(target, proj.AddFile("usr/lib/libxml2.2.tbd", "Frameworks/libxml2.2.tbd", PBXSourceTree.Sdk));
//proj.AddFileToBuild(target,proj.AddFile("usr/lib/libsqlite3.0.tbd" ,"Frameworks/libsqlite3.0.tbd" ,PBXSourceTree.Sdk));
//proj.AddFileToBuild(target,proj.AddFile("usr/lib/libz.tbd" ,"Frameworks/libz.tbd" ,PBXSourceTree.Sdk));
//string libiconvDylibPath = proj.AddFile("usr/lib/libiconv.2.dylib", "Frameworks/libiconv.2.dylib", PBXSourceTree.Sdk);
////proj.AddFileToBuild(target, libiconvDylibPath);
//proj.RemoveFileFromBuild(target, libiconvDylibPath);
#else
//proj.AddFileToBuild(target, proj.AddFile("usr/lib/libiconv.2.dylib", "Frameworks/libiconv.2.dylib", PBXSourceTree.Source));
//proj.AddFileToBuild(target, proj.AddFile("usr/lib/libsqlite3.dylib", "Frameworks/libsqlite3.dylib", PBXSourceTree.Source));
//proj.AddFileToBuild(target, proj.AddFile("usr/lib/libstdc++.6.0.9.dylib", "Frameworks/libstdc++.6.0.9.dylib", PBXSourceTree.Source));
//proj.AddFileToBuild(target, proj.AddFile("usr/lib/libz.1.dylib", "Frameworks/libz.1.dylib", PBXSourceTree.Source));
//proj.AddFileToBuild(target, proj.AddFile("usr/lib/libxml2.2.dylib", "Frameworks/libxml2.2.dylib", PBXSourceTree.Source));
//proj.AddFileToBuild(target, proj.AddFile("usr/lib/libsqlite3.0.dylib", "Frameworks/libsqlite3.0.dylib", PBXSourceTree.Source));
//proj.AddFileToBuild(target, proj.AddFile("usr/lib/libz.dylib", "Frameworks/libz.dylib", PBXSourceTree.Source));
#endif
//proj.RemoveFile(proj.FindFileGuidByProjectPath("Libraries/Plugins/iOS/LGUnityiOSPlugin/LGCAppController.mm"));
//proj.AddFileToBuildWithFlags(target,
// proj.AddFile("Libraries/Plugins/iOS/LGUnityiOSPlugin/LGCAppController.mm",
// "Libraries/Plugins/iOS/LGUnityiOSPlugin/LGCAppController.mm",
// PBXSourceTree.Source),
// "-fno-objc-arc");
//proj.RemoveFile(proj.FindFileGuidByProjectPath("Libraries/Plugins/iOS/UnitySysFont.mm"));
//proj.AddFileToBuildWithFlags(target,
//proj.AddFile("Libraries/Plugins/iOS/UnitySysFont.mm",
// "Libraries/Plugins/iOS/UnitySysFont.mm",
// PBXSourceTree.Source),
//"-fno-objc-arc");
// 書き出し
File.WriteAllText(projectPath, proj.WriteToString());
// Info.plist
UnityEngine.Debug.Log(">>>>>>>>>>>> game Updating Info.plist...");
UpdateInfoPlist(path);
//#endif
var capManager = new ProjectCapabilityManager(projectPath, "XXXXX.entitlements", PBXProject.GetUnityTargetName());//创建设置Capability类
capManager.AddPushNotifications(true);//设置Capability
//capManager.AddGameCenter();
capManager.AddInAppPurchase();
capManager.WriteToFile();//写入文件保存
//3、修改代码
//读取UnityAppController.mm文件
UnityEditor.XCodeEditor.XClass UnityAppController = new UnityEditor.XCodeEditor.XClass(path + "/Classes/UnityAppController.mm");
//在指定代码后面增加一行代码
UnityAppController.WriteBelow("#include \"PluginBase/AppDelegateListener.h\"", "\n#import \"GeTuiSdk.h\"");
//个推获取device token
string adInitCds = " // [ GTSdk ]:向个推服务器注册deviceToken \n NSString *token = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@\"<>\"]];\n token = [token stringByReplacingOccurrencesOfString: @\" \" withString: @\"\"]; \n [GeTuiSdk registerDeviceToken:token];";
UnityAppController.WriteBelow("UnitySendDeviceToken(deviceToken);\n", adInitCds);
//读取UnityAppController.mm文件
UnityEditor.XCodeEditor.XClass Preprocessor = new UnityEditor.XCodeEditor.XClass(path + "/Classes/Preprocessor.h");
Preprocessor.Replace("#define UNITY_USES_REMOTE_NOTIFICATIONS 0","#define UNITY_USES_REMOTE_NOTIFICATIONS 1");
}
}
internal static void UpdateInfoPlist(string path) {
var plist = PlistBuddyHelper.ForPlistFile(Path.Combine(path, "Info.plist"));
// Bundle Identifier
//plist.SetString("CFBundleIdentifier", PlayerSettings.applicationIdentifier);
plist.SetBool("CFBundleAllowMixedLocalizations", true);
plist.SetString("CFBundleDevelopmentRegion", "zh_CN");
plist.SetString("CFBundleName", "XXXXXXX");
plist.AddBool("NSAppTransportSecurity:NSAllowsArbitraryLoads", true);
plist.AddString("NSPhotoLibraryUsageDescription", "需要您的同意,才能访问相册");//
plist.AddString("NSMicrophoneUsageDescription", "需要您的同意,才能访问麦克风");
plist.AddString("NSCameraUsageDescription", "需要您的同意,才能访问相机");
plist.AddString("NSLocationWhenInUseUsageDescription","需要您的同意,才能访问位置");
plist.AddBool("ITSAppUsesNonExemptEncryption",false);
//LSApplicationQueriesSchemes
//plist.AddArray("LSApplicationQueriesSchemes");
//plist.AddString("LSApplicationQueriesSchemes:0", "mqq");//
//plist.AddString("LSApplicationQueriesSchemes:1", "mqqapi");//
//plist.AddString("LSApplicationQueriesSchemes:2", "mqqwpa");//
//plist.AddString("LSApplicationQueriesSchemes:3", "mqqbrowser");
//plist.AddString("LSApplicationQueriesSchemes:4", "mttbrowser");
//plist.AddString("LSApplicationQueriesSchemes:5", "mqqOpensdkSSoLogin");
//plist.AddString("LSApplicationQueriesSchemes:6", "mqqopensdkapiV2");
//plist.AddString("LSApplicationQueriesSchemes:7", "sinaweibo");
//plist.AddString("LSApplicationQueriesSchemes:8", "sinaweibohd");
//plist.AddString("LSApplicationQueriesSchemes:9", "weibosdk");
//plist.AddString("LSApplicationQueriesSchemes:10", "weibosdk2.5");
//plist.AddString("LSApplicationQueriesSchemes:11", "weixin");
//plist.AddString("LSApplicationQueriesSchemes:12", "wtloginmqq2");
//plist.AddString("LSApplicationQueriesSchemes:13", "mqqapiwallet");
//plist.AddString("LSApplicationQueriesSchemes:14", "mqqopensdkapiV3");
//plist.AddString("LSApplicationQueriesSchemes:15", "mqqapiwallet");
//plist.AddString("LSApplicationQueriesSchemes:16", "wechat");
//UIBackgroundModes
plist.AddArray("UIBackgroundModes");
plist.AddString("UIBackgroundModes:0", "remote-notification");
plist.AddString("UIBackgroundModes:1","voip");
plist.AddBool("UIPushNotifications", true);
////weixin
//plist.AddDictionary("CFBundleURLTypes", "1");
//plist.AddString("CFBundleURLTypes:1:CFBundleTypeRole", "Editor");
//plist.AddArray("CFBundleURLTypes", "1", "CFBundleURLSchemes");
//plist.AddString("CFBundleURLTypes:1:CFBundleURLSchemes:0", "wx3ff69cc81203b2e1");
////tencent
//plist.AddDictionary("CFBundleURLTypes", "2");
//plist.AddString("CFBundleURLTypes:2:CFBundleTypeRole", "Editor");
//plist.AddString("CFBundleURLTypes:2:CFBundleURLName", "tencentopenapi");
//plist.AddArray("CFBundleURLTypes", "2", "CFBundleURLSchemes");
//plist.AddString("CFBundleURLTypes:2:CFBundleURLSchemes:0", "tencent1106067576");
////weibo
//plist.AddDictionary("CFBundleURLTypes", "3");
//plist.AddString("CFBundleURLTypes:3:CFBundleTypeRole", "Editor");
//plist.AddString("CFBundleURLTypes:3:CFBundleURLName", "com.weibo");
//plist.AddArray("CFBundleURLTypes", "3", "CFBundleURLSchemes");
//plist.AddString("CFBundleURLTypes:3:CFBundleURLSchemes:0", "wb2888694102");
UnityEngine.Debug.Log(">>>>>>>>>>>>> Game Update Info Plist end");
}
internal static void AddDirectoryToXcodeproj(PBXProject proj, string targetGuid, string projectPath, string path, bool addSubDir = true) {
UnityEngine.Debug.Log(">>>>> Game Adding to xcodeproj projectPath: " + projectPath + " path :" + path);
string fullPath = Path.Combine(projectPath, path);
UnityEngine.Debug.Log("fullPath:" + fullPath);
if (Directory.Exists(fullPath)) {
foreach (var file in Directory.GetFiles(fullPath)) {
if (file.EndsWith(".meta")) continue;
string filePath = Path.Combine(path, Path.GetFileName(file));
UnityEngine.Debug.Log(">>>>> Game Adding to xcodeproj: " + filePath);
proj.AddFileToBuild(targetGuid, proj.AddFile(filePath, filePath, PBXSourceTree.Source));
}
if (!addSubDir) {
return;
}
foreach (var dir in Directory.GetDirectories(fullPath)) {
string dirPath = Path.Combine(path, Path.GetFileName(dir));
UnityEngine.Debug.Log(">>>> Game Adding dirPath to xcodeproj: " + dirPath);
proj.AddFileToBuild(targetGuid, proj.AddFile(dirPath, dirPath, PBXSourceTree.Source));
}
} else {
UnityEngine.Debug.LogWarning(">>>>Directory doesn't exist: " + fullPath);
}
}
internal static void CopyDirectory(string srcPath, string dstPath) {
using (var process = new Process()) {
process.StartInfo.FileName = "/bin/cp";
process.StartInfo.Arguments = string.Format("-Rf \"{0}\" \"{1}\"", srcPath, dstPath);
UnityEngine.Debug.Log("Executing cp command: " + process.StartInfo.Arguments);
process.StartInfo.CreateNoWindow = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
try {
process.Start();
process.StandardError.ReadToEnd();
var stdError = process.StandardError.ReadToEnd();
if (stdError.Trim().Length > 0) {
UnityEngine.Debug.Log("cp stderr: " + stdError);
}
if (!process.WaitForExit(10 * 1000)) {
throw new Exception("cp did not exit in a timely fashion");
}
if (process.ExitCode != 0) {
return;
}
return;
} catch (Exception e) {
throw new Exception("Encountered unexpected error while cp files.", e);
}
}
}
internal static void MoveItem(string srcPath, string dstPath) {
using (var process = new Process()) {
process.StartInfo.FileName = "/bin/mv";
process.StartInfo.Arguments = string.Format("\"{0}\" \"{1}\"", srcPath, dstPath);
UnityEngine.Debug.Log("Executing mv command: " + process.StartInfo.Arguments);
process.StartInfo.CreateNoWindow = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
try {
process.Start();
process.StandardError.ReadToEnd();
var stdError = process.StandardError.ReadToEnd();
if (stdError.Trim().Length > 0) {
UnityEngine.Debug.Log("mv stderr: " + stdError);
}
if (!process.WaitForExit(10 * 1000)) {
throw new Exception("mv did not exit in a timely fashion");
}
if (process.ExitCode != 0) {
return;
}
return;
} catch (Exception e) {
throw new Exception("Encountered unexpected error while cp files.", e);
}
}
}
}
#endif