作为ios开发员,打包是家常便饭啦…
所以,把复杂的流程简单化,有助于减轻自己的工作量,还能有效的防止问题发生…最重要的,没那么快秃顶!
unity打ios包共4个步骤
1.编译unity生成xcode工程
2.部署更新ios打包工程
3.编译ios工程
4.签名生成包
我们一步一步来
1 编译unity生成xcode工程
unity APP 里面提供的方法进行脚本调用,只用在脚本里写好打包的类,参数,,,只用对应unity 版本的APP 调用该类就行。
1.1 在unity写好打包的方法
using System.Collections;
using System.IO;
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System;
class ProjectBuilderIOS : ProjectBuilderAndroid
{
protected new static void InitSetting()
{
ApplicationPath = Application.dataPath.Replace("/Assets", "");
Scenes = FindEnabledEditorScenes();
BuildTarget = BuildTarget.iOS;//设置为发布平台ios
ScriptingImplementation = ScriptingImplementation.IL2CPP;//设置打包模式为IL2CPP
BuildOptions = BuildOptions.AcceptExternalModificationsToPlayer;
OutputPath = ApplicationPath + "/IOSProject";//导出ios工程相对路径
PlayerSettings.applicationIdentifier = "com.dddd.aaa";//bundleid
PlayerSettings.bundleVersion = "0.0.1";//版本号
PlayerSettings.productName = "il2cpp";
EditorUserBuildSettings.symlinkLibraries = false;
}
static void BuildForIosProjectIl2Cpp()
{
InitSetting();
doBuild();
}
protected static void doBuild()
{
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup,
"HOTFIX_ENABLE");
if(ScriptingImplementation == ScriptingImplementation.Mono2x) //Mono2x包才打开混淆
{
Beebyte.Obfuscator.OptionsManager.SetObfuscatorEnable(true);
}else if(ScriptingImplementation == ScriptingImplementation.IL2CPP)
{
Beebyte.Obfuscator.OptionsManager.SetObfuscatorEnable(false);
}
CSObjectWrapEditor.Generator.ClearAll();
CSObjectWrapEditor.Generator.GenAll();
DirectoryInfo dir = new DirectoryInfo(OutputPath+"/"+PlayerSettings.productName);
if (dir.Exists)
dir.Delete(true);
if (TargetName != null)
{
dir.Create();
OutputPath += "/" + TargetName;
}
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTarget);
EditorUserBuildSettings.exportAsGoogleAndroidProject = true;
EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle;
PlayerSettings.SetPropertyInt("ScriptingBackend", (int) ScriptingImplementation, BuildTarget);
EditorUserBuildSettings.buildScriptsOnly = true;
Scenes = FindEnabledEditorScenes();
BuildPipeline.BuildPlayer(Scenes, OutputPath, BuildTarget, BuildOptions);
Debug.Log("Application.buildGUID===========>" + Application.buildGUID);
}
}
1.2 使用脚本调用unity的方法导出ios工程
使用shell能直接调用unity方法掉起工程里面对应的类和方法。这里我们为了整合流程,是在python脚本里调用shell去执行的。
#编译unity工程,导出iOS工程!
def exportIosProject():
print "编译unity工程..."
command = "/Applications/Unity2017.2.3f1/Unity.app/Contents/MacOS/Unity -quit -batchmode -projectPath /Users/admin/projectName -executeMethod ProjectBuilderIOS.BuildForIosProjectIl2Cpp"
unityCompile_statue = os.system(command)
unityCompile_statue>>=8
print unityCompile_statue
if unityCompile_statue==0 :
print "exportIosProject success"
else:
print "exportIosProject fail"
exit(0)
2 部署更新ios打包工程
2.1 将新生成的xcode工程文件,更新到之前配置好的ios工程
需要将文件拷贝到打包ios工程路径下,再更新文件引用(正常情况下,两次发布unity生成的ios工程,只有project->Classes->Native下的类会改变,如果有新接SDK,Libraries下文件才会改变)
ios导出工程文件夹意义
Classes:unity前端生成的类,Classes->Native下是前端自己代码生成的cpp文件,其他是unity自己的类,当更改unity版本时会改变
Libraries:unity前端接入的SDK文件。unity前端接的三方SDK里的ios相关SDK都会在这里面,包括unity自带的引擎ios实现,新接SDK或unity版本更新,该文件加会改变。
Data:unity前端生成的资源文件,底层不应该关心,直接覆盖并打到包里就行。
正常打包,只会跟新Classes->Native下的类,所以,脚本里只做文件覆盖,并更新Classes->Native下的类引用。
2.1.1 拷贝覆盖三个文件夹
copytree(os.environ["BASKETBALL_PRO_DIR"]+"/IOSProject/Data", os.environ["BASKETBALL_PRO_DIR"]+"/iOSBuild/Basketball/Data")
copytree(os.environ["BASKETBALL_PRO_DIR"]+"/IOSProject/Libraries", os.environ["BASKETBALL_PRO_DIR"]+"/iOSBuild/Basketball/Libraries")
copytree(os.environ["BASKETBALL_PRO_DIR"]+"/IOSProject/Classes/", os.environ["BASKETBALL_PRO_DIR"]+"/iOSBuild/Basketball/Classes/")
2.1.2 自动更新Classes->Native下的类引用
####################################################################################################################################################
#新加的编辑xcodeproj工程文件的代码
pbxproj_dir = '%s/%s/project.pbxproj'%(pro_dir,fileName)
pro_dir_arr = pro_dir.split('/')
arr_len = len(pro_dir_arr)
pro_dir_arr[arr_len-1] = 'Classes/Native/'
Native_dir = '/'.join(pro_dir_arr)
Native_files = file_name(Native_dir)
print Native_files
for file in Native_files:
data = ''
uuid_f = ''.join(str(uuid.uuid4()).upper().split('-')[1:])
uuid_r = ''.join(str(uuid.uuid4()).upper().split('-')[1:])
if (isincludestr(pbxproj_dir, file)):
print '%s is already included' % (file)
continue
while(isincludestr(pbxproj_dir, uuid_f)):
uuid_f =''.join(str(uuid.uuid4()).upper().split('-')[1:])
while(isincludestr(pbxproj_dir, uuid_r)):
uuid_r =''.join(str(uuid.uuid4()).upper().split('-')[1:])
buildfilesection_str = creat_buildfilesection(file, uuid_f, uuid_r)
refsection_str = creat_refsection(file, uuid_r)
print buildfilesection_str
print refsection_str
PBXBuildFile_line = getstrlineinfile(pbxproj_dir, '/* Begin PBXBuildFile section */')
insertline(pbxproj_dir, PBXBuildFile_line ,buildfilesection_str)
PBXFileReference_line = getstrlineinfile(pbxproj_dir, '/* Begin PBXFileReference section */')
insertline(pbxproj_dir, PBXFileReference_line ,refsection_str)
folder_line = getstrlineinfile(pbxproj_dir, '/* Native */ = {')+2
insertline(pbxproj_dir, folder_line ,('%s /* %s */,' %(uuid_r,file)))
PBXSourcesBuildPhase_line = getstrlineinfile(pbxproj_dir, '/* Begin PBXSourcesBuildPhase section */')+4
insertline(pbxproj_dir, PBXSourcesBuildPhase_line ,('%s /* %s in Sources */,' %(uuid_f,file)))
print PBXBuildFile_line
print PBXFileReference_line
print folder_line
print PBXSourcesBuildPhase_line
###################################################################################################################################################
2.1.3 更改版本号
#获取项目的info配置文件
try:
if os.path.exists(pro_dir+"/Info.plist"):
plist=readPlist(pro_dir+"/Info.plist");
print pro_dir+"/Info.plist"
print plist
print plist['CFBundleVersion']
else:
#这里需要注意工程目录下是否有该目录
plist=readPlist(pro_dir+"/"+target+"/Info.plist");
print pro_dir+"/"+target+"/Info.plist"
print plist
print plist['CFBundleVersion']
except InvalidPlistException,e:
print "not a plist or plist invalid:",e
#修改项目的info配置文件(修改了项目的游戏版本和编译版本号)
3 编译ios工程
3.1 清理工程
在编译之前需要先清理下xcode工程
def clean(dir,pro_name):
print "开始清理!"
print dir
print pro_name
command = "cd %s; xcodebuild -target %s clean "% (dir,pro_name)
os.system(command)
3.2 编译
编译分为两种:
第一种build,这种打出来的包里面会有调试信息,包体会增大,建议测试包使用,
第二种是Archive,这种是比较好的打包方式,没有调试信息,发布包建议用这种方式。
推荐使用第二种
3.2.1 build方式编译ios工程
def build(dir,pro_name,build_config,code_sign):
print '\033[1;31;40m'
print "*******************************************************************编译!*********************************************************"
print dir
print pro_name
print '\033[0m'
command = "cd %s;xcodebuild -target %s -sdk iphoneos -configuration %s CODE_SIGN_IDENTITY='%s'" % (dir,pro_name,build_config,code_sign)
print command
iosBuild_status = os.system(command)
iosBuild_status>>=8
print iosBuild_status
if iosBuild_status==0 :
print "ios project compile success"
else:
print "ios project compile fail"
exit(0)
3.2.2 Archive方式编译ios工程
def buildForArchive(dir,pro_name):
print '\033[1;31;40m'
print "*******************************************************************编译!*********************************************************"
print dir
print pro_name
print '\033[0m'
ipa_out_put_archive = "%s/build/%s.xcarchive" % (dir,pro_name)
command = "cd %s;xcodebuild archive -project %s.xcodeproj -scheme %s -archivePath %s" % (dir,pro_name,pro_name,ipa_out_put_archive)
print command
iosBuild_status = os.system(command)
iosBuild_status>>=8
print iosBuild_status
if iosBuild_status==0 :
print "ios project compile success"
else:
print "ios project compile fail"
exit(0)
4 .签名生成包
和编译对应的,生成包的方式也有两种,
第一种build,对build出来的app签名生成ipa
第二种Archive,对Archive出来的.xcarchive文件签名生成ipa
4.1 签名导出build包
def export(current_section,dir,build_config,pro_name,version,code_sign,code_profile):
print '\033[1;31;40m'
print "*******************************************************************打包!**********************************************************"
print dir
print build_config;
print pro_name
print version
print '\033[0m'
print code_sign
print code_profile
#设置ipa包的包名和存储位置
current_time=time.strftime("%Y%m%d%H%M",time.localtime(time.time()))
ipa_out_put = os.path.join(sys.path[0],"pack/%s-%s-%s-%s-%s.ipa"%(current_time,version,pro_name,current_section,build_config))
print "打包ipa!输出到 %s" % ipa_out_put
if not os.path.exists(os.path.join(sys.path[0],"pack")):
os.makedirs(os.path.join(sys.path[0],"pack"))
print "pack 文件夹不存在 新建一个"
_appPath = "%s/build/%s" % (dir,build_config)
_appName = getFileFromDir(_appPath,"app")
command = "cd %s;xcrun -sdk iphoneos PackageApplication -v %s/build/%s/%s.app -o '%s' —sign '%s' —embed %s" % (dir,dir,build_config,_appName,ipa_out_put,code_sign,code_profile)
print command
os.system(command)
4.2 签名导出Archive包
def exportForArchive(current_section,dir,build_config,pro_name,version):
print '\033[1;31;40m'
print "*******************************************************************打包!**********************************************************"
print dir
print build_config;
print pro_name
print version
print '\033[0m'
#设置ipa包的包名和存储位置
current_time=time.strftime("%Y%m%d%H%M",time.localtime(time.time()))
ipa_out_put = os.path.join(sys.path[0],"pack/%s-%s-%s-%s-%s"%(current_time,version,pro_name,current_section,build_config))
print "打包ipa!输出到 %s" % ipa_out_put
if not os.path.exists(os.path.join(sys.path[0],"pack")):
os.makedirs(os.path.join(sys.path[0],"pack"))
print "pack 文件夹不存在 新建一个"
#获取项目的打包配置文件
pakegePlistPath = "%s/release.plist" % (dir)
#开始打包
command = "cd %s;xcodebuild -exportArchive -archivePath %s/build/%s.xcarchive -exportPath %s -exportOptionsPlist %s" % (dir,dir,pro_name,ipa_out_put,pakegePlistPath)
print command
os.system(command)
#将生成的IPA重命名
copyfile(ipa_out_put+"/"+pro_name+".ipa",ipa_out_put+".ipa")
deletetree(ipa_out_put);
对此,整个打包流程已走完
其他
其中有用到一些自己封装的python方法,贴在下面
def insertline(file_name, line_n, text):
with open(file_name, 'r+') as fp:
lines = []
for line in fp:
lines.append(line)
fp.close()
lines.insert(line_n, '\n')
lines.insert(line_n, text)
s = ''.join(lines)
with open(file_name, 'r+') as fp:
fp.write(s)
fp.close()
def isincludestr(file_name, str):
IsIncludeStr = False
with open(file_name, 'r+') as f:
for line in f.readlines():
# if(line.find(file) == 0):
if file in line:
IsIncludeStr = True
break;
return IsIncludeStr
def getstrlineinfile(file_name,str):
line_count = 0
with open(file_name, 'r') as f:
for line in f.readlines():
line_count = line_count+1
if str in line:
return line_count
return 0
def file_name(file_dir):
L = []
for root, dirs, files in os.walk(file_dir):
for file in files:
if os.path.splitext(file)[1] == '.cpp':
L.append(file)
return L
def creat_refsection(f_name, uuid_ref):
return '%s /*%s*/ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = %s; sourceTree = "<group>"; };' % (
uuid_ref, f_name, f_name)
def creat_buildfilesection(f_name, uuid_file, uuid_ref):
return '%s /*%s*/ = {isa = PBXBuildFile; fileRef = %s /* %s */; };' % (uuid_file, f_name, uuid_ref, f_name)
def copytree(file,outdir):
if os.path.exists(outdir):
shutil.rmtree(outdir)
if os.path.exists(file):
shutil.copytree(file,outdir)
def deletetree(outdir):
if os.path.exists(outdir):
shutil.rmtree(outdir)
def copyfile(file,outdir):
if os.path.exists(file):
shutil.copy(file,outdir)
def deletefile(file):
if os.path.exists(file):
os.remove(file)
def getFileFromDir(dir,ext):
items = os.listdir(dir)
print items
for names in items:
if names.endswith(ext):
(_target,_extension) = os.path.splitext(names)
print names
return _target