作为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