Unity 构建IOS和ANDROID工程 (二)

之前记录了下Unity构建ios和android框架设计方面的方案

上文地址:

http://blog.csdn.net/jbl20078/article/details/77715570


这次记录下Unity打包IOS相关脚本的整理和库工程的依赖关系(下篇继续介绍android)

        实现目标:

        1、持续构建:Unity+Jenkins+Xcode一键打包并做到持续化构建,Jenkins方面本文不讨论,就是个构建服务器

        2、多渠道多平台构建:做到一键多渠道多平台构建,其实就是使劲折腾构建脚本

        3、灵活的构建框架:在支持一键打包的同时也要方便本地的调试,这个我认为也是蛮重要的,希望构建结构方便程序员切入和修改


        第一点:网上资源很多,尤其是momo的文章早在n年前就总结了,Unity平台打包无非是两步走:1、导出xcode/android工程   2、工程分别打包签名  3、放入jenkins构建(如果有用Jenkins的话),我这边再引用下上次文章的框架结构图

        我希望xcode工程被导出来后是完全独立的,要做一个库工程给它依赖,这样做是为了保证公共sdk方法最大程度的复用(库工程是唯一的),同时Unity导出的Xcode工程我们也可以单独拿出来扩展功能,这也是第三点的要求,后面脚本的每一步设计都基于这个考虑来的(没做过大项目群维护工作的同学可能理解不了这样做的好处)。

做法步骤:写一个python脚本(shell/ruby都可以,感觉python更简洁),脚本就两个功能:

         以下做法是以5.x介绍,4.x差别比较大,请忽罩套

        1、导出xcode工程(脚本放在Unity工程根目录下)

#检测必须的环境变量
def checkEnvironVariate(export):
	variate = os.environ.get( export )
	if variate == None:
		print '环境变量没有定义,请先定义它:=>  ',export
		sys.exit(0)
		pass
	return variate

#unity 路径
UNITY_PATH = checkEnvironVariate( "UNITY_PATH" )

#导出xcode工程
def exportXcodeProj():
	os.system(UNITY_PATH + " -projectPath "+ os.getcwd() + " -executeMethod ProjectBuild.BuildForIPhone -quit")
	print "OK!, 导出xcode工程完成"

      脚本直接调用静态方法:ProjectBuild类的BuildForIPhone方法(函数名照抄的mono,偷了个懒)

      这个脚本随便放入Assets下任意路径都可以,具体方法:

//=============================================
//工程编译平台相关方法类
//=============================================
class ProjectBuild : Editor{
	//在这里找出你当前工程所有的场景文件,假设你只想把部分的scene文件打包 那么这里可以写你的条件判断 总之返回一个字符串数组。
	static string[] GetBuildScenes(){
		List<string> names = new List<string>();

		foreach(EditorBuildSettingsScene e in EditorBuildSettings.scenes){
			if(e==null)
				continue;
			if(e.enabled)
				names.Add(e.path);
		}
		return names.ToArray();
	}


	//shell脚本直接调用这个静态方法
	static void BuildForIPhone(){ 
		//解析参数(如果脚本传入参数需要在这边处理,可以写在下面方法中)
		OnParseArgv();

		PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.iOS, "USE_SHARE");

		//注意最后一个参数一定不能填别的,否则build xcode工程的时候会被覆盖掉,当前参数代表的是以append的方式编译xcode工程
		BuildPipeline.BuildPlayer(GetBuildScenes(), "XcodeProj", BuildTarget.iOS, BuildOptions.AcceptExternalModificationsToPlayer);
	}

         BuildForIphone方法中最重要的是BuildPipeline.BuildPlayer函数最后一个参数 BuildOptions.AcceptExternalModificationsToPlayer,这个函数作用保证build ios工程的时候是以Append的方式覆盖原来的xcode工程(4.x只设置这一个属性还不行),这样我们在Xcode工程中修改的依赖关系等一些配置在每次编译的时候不会被覆盖。

         第一步完成,继续第二步,打包这个xcode工程

          Unity提供了build工程之后的回调方法:OnPostProcessBuild  我直接写在上面的类里面,只要是静态的就可以,代码如下:

//=============================================
	//以下内容只适用Unity5.x+,4.x版本请使用XUPorter插件
	//=============================================
	#if UNITY_EDITOR && UNITY_5
	[PostProcessBuild(100)]
	/// <summary>
	/// unity导出xcode结束回调
	/// </summary>
	/// <param name="buildTarget">Build target.</param>
	/// <param name="pathToBuiltProject">Path to built project.</param>
	public static void OnPostProcessBuild (BuildTarget buildTarget, string xcodeProjPath){
		//判断当前平台是否ios
		if (buildTarget != BuildTarget.iOS) {
			Debug.LogWarning ("Target is not ios. XCodePostProcess will not run");
			return;
		}

		//获取project.pbxproj路径
		string projPath = PBXProject.GetPBXProjectPath(xcodeProjPath);  
		PBXProject proj = new PBXProject();  
		proj.ReadFromString(File.ReadAllText(projPath));  

		// 获取当前target名字  
		string target = proj.TargetGuidByName(PBXProject.GetUnityTargetName());

		// 对所有的编译配置设置选项  
		proj.SetBuildProperty(target, "ENABLE_BITCODE", "NO"); 

		//添加依赖库
		//例如:(系统库添加方法)
		//特别注意:系统库建议还是直接在xcode工程中添加引入即可,因为我们目前使用的append方式导出xcode不会覆盖xcode工程系统引入部分(build phases部分)
		//下面还是给出例子
		//proj.AddFrameworkToProject (target, "Security.framework", false);  
		//proj.AddFrameworkToProject (target, "libc++.1.tbd", false); 

		//外部依赖库需要把路径添加到Build Settings中(Frameworkd search path),但是这部分会在每次Unity Build的过程中被清理,所以这部分要手动写入
		//other frameworkd库添加方法(比如Umeng framework):
		//注意:下面的search路径 完全是按照库工程路径与主工程相对路径编写,一旦修改库工程相关库的路径,下面内容也要修改
		proj.AddBuildProperty(target,FRAMEWORK_SEARCH_PATHS_KEY,"$(SRCROOT)/../lib_sg_projects/lib_common_ios/umeng");
		proj.AddBuildProperty(target,FRAMEWORK_SEARCH_PATHS_KEY,"$(SRCROOT)/../lib_sg_projects/lib_common_ios/googleMobileAds");
		proj.AddBuildProperty(target,FRAMEWORK_SEARCH_PATHS_KEY,"$(SRCROOT)/../lib_sg_projects/lib_common_ios/vungleAds");

		//给工程添加头文件搜索路径
		//实例:
		//proj.AddBuildProperty(target,HEADER_SEARCH_PATHS_KEY,"$(SRCROOT)/../File");
		//给工程添加Lib搜索路径
		//实例:
		//proj.AddBuildProperty(target,LIBRARY_SEARCH_PATHS_KEY,"$(SRCROOT)/../Lib");

		//其他引入方式参考
		//proj.AddFileToBuild(target, proj.AddFile("Frameworks/mylib.framework", "Frameworks/mylib.framework", PBXSourceTree.Source));

		//设置签名
		//proj.SetBuildProperty (target, "CODE_SIGN_IDENTITY", "iPhone Distribution: _______________");
		//proj.SetBuildProperty (target, "PROVISIONING_PROFILE", "********-****-****-****-************");   

		// 保存工程  
		proj.WriteToFile (projPath);  

		// 修改plist  
		string plistPath = xcodeProjPath + "/Info.plist";  
		PlistDocument plist = new PlistDocument();  
		plist.ReadFromString(File.ReadAllText(plistPath));  
		PlistElementDict rootDict = plist.root;  

		// 声明权限(不必要,我们按照append的方式覆盖导出xcode,info.plist中的内容可以保留)
		//rootDict.SetString("NSContactsUsageDescription", "是否允许此游戏使用麦克风?");
		//rootDict.SetString("NSContactsUsageDescription", "是否允许此App访问您的蓝牙?");
		//rootDict.SetString("NSContactsUsageDescription", "是否允许此App使用日历?");
		//rootDict.SetString("NSContactsUsageDescription", "是否允许此App访问您的地理位置?");
		//rootDict.SetString("NSContactsUsageDescription", "是否允许此App访问您的相册?");

		//修改包名:
		rootDict.SetString("CFBundleIdentifier", "com.biemore.ios.Carnival");

		//可以通过传入脚本参数来修改工程的一些配置(比如说版本号和code值)
		//通过脚本传入参数设置版本号和code值
		//注意:下面的操作也不是必须的,建议直接放在打包脚本中进行
		if(!string.IsNullOrEmpty(VERSION_VALUE)) rootDict.SetString("CFBundleShortVersionString", VERSION_VALUE);
		if(!string.IsNullOrEmpty(CODE_VALUE))    rootDict.SetString("CFBundleVersion", CODE_VALUE);
			
		// 保存plist  
		plist.WriteToFile (plistPath);  
	}

注释量还可以,就不解释了哈,里面很多注释的代码只是给几个选择,直接改xcode工程也可以,用代码控制也可以,看个人喜好,我倾向于直接修改xcode,反正每次build不会被覆盖(注意不是所有的都不会被覆盖哦:头文件和链接库、framework等search路径一定要代码控制,Icon和launch img也会被强行覆盖等等),直接修改xcode工程不是坏事,太依赖Unity容易不灵活的,等我们需要对Xcode深度功能开发大家就会体会到我的原则--->能修改xcode绝不代码控制。


        整个xcode的导出和修改都完成了,下面就是打包签名ipa,又回到脚本的事情,代码(直接把代码全上了):

# -*- coding: utf-8 -*-

import os  
import sys 
import getopt
import time
import datetime
import shutil
import subprocess
import plistlib

#检测必须的环境变量
def checkEnvironVariate(export):
	variate = os.environ.get( export )
	if variate == None:
		print '环境变量没有定义,请先定义它:=>  ',export
		sys.exit(0)
		pass
	return variate

#unity 路径
UNITY_PATH = checkEnvironVariate( "UNITY_PATH" )
GIT_VERSION = 'null'
VERSION_VALUE = '1.0.0'
CODE_VALUE = '0'
BUILDINFO_PATH = os.getcwd()+'/XcodeProj/Info.plist'
IPA_PATH = os.getcwd()+'/ipa'
BUILD_MODE = ["dev","dist"]
BUILD_STATUS = 'Release'
TARGET_NAME = 'Unity-iPhone'
PRODUCE_NAME = 'endlessrunnerbase'

#获取当前版本号和code值
def getBundleVersion():
	if (os.path.exists(BUILDINFO_PATH)):
		try:
			plist = plistlib.readPlist(BUILDINFO_PATH)
			VERSION_VALUE = plist["CFBundleShortVersionString"]
			CODE_VALUE = plist["CFBundleVersion"]
		except Exception as e:
			print "info.plist解析失败: " + BUILDINFO_PATH, e
        	return
	else:
		print "没有找到info文件: " + BUILDINFO_PATH

#修改当前版本号和code值
def modifyBundleVersion():
	if (os.path.exists(BUILDINFO_PATH)):
		#用plistlib.writePlist方法必须要重新覆盖整个info.plist,这边用系统的PlistBuddy方法修改
		os.system('/usr/libexec/PlistBuddy -c "Set:CFBundleShortVersionString %s" %s'%(VERSION_VALUE,BUILDINFO_PATH))
		os.system('/usr/libexec/PlistBuddy -c "Set:CFBundleVersion %s" %s'%(CODE_VALUE,BUILDINFO_PATH))
		return

#先获取系统值
getBundleVersion()

#下载库工程
def downLoadSG_iosLib():
    cwd = os.getcwd()
    #判断库工程是否存在,如果存在执行git pull,否则执行git clone
    if (os.path.exists('./lib_sg_projects/')):
        os.chdir('./lib_sg_projects/')
        os.system('git pull origin xxxxxxx')
    else:
        #下载库工程
        os.system('git clone -b xxxxxxxxx XXXXXXXXXXXX')

    os.chdir(cwd)
    return

#获取git哈希code
def getGitVersionHashCode():
	info = os.popen('git rev-list HEAD -n 1 | cut -c 1-7').readlines()
	for line in info:
		GIT_VERSION = line.strip('\r\n')
		break

#导出xcode工程
def exportXcodeProj():
	os.system(UNITY_PATH + " -projectPath "+ os.getcwd() + " -executeMethod ProjectBuild.BuildForIPhone " + VERSION_VALUE + " " + CODE_VALUE + " -quit")
	print "OK!, 导出xcode工程完成"

#创建ipa文件夹
def makeIpaFile():
	if (os.path.exists(IPA_PATH)):
		os.system("find ./ipa -type f -name \"*.ipa\" | xargs rm -rf")
	else:
		#创建新文件夹
		os.system("mkdir "+IPA_PATH)

#编译库工程
def buildLibSg():
	tmp = os.getcwd()
	libPath =  './lib_sg_projects/lib_common_ios/'
	if (os.path.exists(libPath)):
		os.chdir(libPath)
		os.system("xcodebuild -target SG_project_ios -configuration " + BUILD_STATUS + " -sdk iphoneos build") 
	else:
		print "库工程不存在,请确认是否从git中获取成功: "+libPath
		system.exit(0)
	
	#回到原目录
	os.chdir(tmp)

#编译主工程
def buildTarget(certificate):
	tmp = os.getcwd()
	os.chdir('./XcodeProj')
	# os.system("xcodebuild -target " + TARGET_NAME + " -configuration " + BUILD_STATUS + " -sdk iphoneos build CODE_SIGN_IDENTITY="+certificate) 
	#字符串尽量使用下面这种方法,特殊符号比较方便处理
	cmd = 'xcodebuild -target %s -configuration %s -sdk iphoneos build CODE_SIGN_IDENTITY="%s" ' %(TARGET_NAME,BUILD_STATUS,certificate)
	# xcodebuild -target %s -sdk %s -configuration %s GCC_PREPROCESSOR_DEFINITIONS="%s" build' %(project, , SDK, configuration, definitions)
	os.system(cmd)
	#回到原目录
	os.chdir(tmp)

#sign and package
def packageIpa(provisioningProfile,certificate):
	ORIIPA_PATH = os.getcwd() + '/XcodeProj/build/'+BUILD_STATUS+"-iphoneos/"+PRODUCE_NAME+".app"
	cmd = 'xcrun -sdk iphoneos PackageApplication -v %s -o %s/UnclearRun_%s.ipa --sign "%s" --embed %s ' %(ORIIPA_PATH,IPA_PATH,BUILD_STATUS,certificate,provisioningProfile)
	# os.system("xcrun -sdk iphoneos PackageApplication -v "+ORIIPA_PATH+" -o "+IPA_PATH+"/test.ipa"+" --sign "+certificate+" --embed "+provisioningProfile)
	os.system(cmd)

#根据打包模式打包
def execute_makeipa(buildmode):
	global BUILD_STATUS
	CODE_SIGN_IDENTITY = ""
	PROVISONNIING_PROFILE = ""
	if 'dev' == buildmode:
		BUILD_STATUS = 'Debug'
		PROVISONNIING_PROFILE = os.popen("find " + os.getcwd() + "/mobileProvision/dev -type f -name \"*.mobileprovision\"").read()
		CODE_SIGN_IDENTITY="iPhone Developer: xxxxxx"
	else:
		BUILD_STATUS = 'Release'
		PROVISONNIING_PROFILE = os.popen("find " + os.getcwd() + "/mobileProvision/dist -type f -name \"*.mobileprovision\"").read()
		CODE_SIGN_IDENTITY="iPhone Distribution: xxxxxxxx"

	#编译库工程
	buildLibSg()
	#编译主工程
	buildTarget(CODE_SIGN_IDENTITY)
	#打包主工程
	packageIpa(PROVISONNIING_PROFILE,CODE_SIGN_IDENTITY)
	

#生成ipa
def makeIPA():
	#下载库工程
	downLoadSG_iosLib()
	#修改配置文件
	modifyBundleVersion()
	#创建包文件
	makeIpaFile()

	for bm in BUILD_MODE:
		execute_makeipa(bm)
	return

#usage
def usage():
    print 'ipa_Builder.py usage:'
    print '-h, --help:    帮助信息.'
    print '-v, --version: 打包的版本号,不输入默认按照Unity PlayerSetting中设置并打包'
    print '-c, --code:    打包ipa的build值,Unity PlayerSetting中设置并打包'

# -------------- main --------------
if __name__ == '__main__':
	opts, args = getopt.getopt(sys.argv[1:], "hv:c:")
	for op, value in opts:
	  if op == "-v":
	    VERSION_VALUE = value
	  elif op == "-c":
	    CODE_VALUE = value
	  elif op == "-h":
    		usage()
    		sys.exit()

    # 打包
 	exportXcodeProj()
 	makeIPA()


        因为我有库工程,所以上面我加入了库工程的版本控制和下载编译等操作,同时将ipa打包成debug和release两个版本方便调试和发布。

        上面的脚本可以规整下放入jenkins打包了(里面有公司版权信息内容,用伪代码处理了几个地方,不耽误大家阅读理解),整个一键打包流程结束,后面介绍android的相关内容。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值