文章目錄
  1. 1. Unity自动化打包
    1. 1.1. 版本控制
    2. 1.2. 打包
      1. 1.2.1. 多平台
      2. 1.2.2. 自动化
        1. 1.2.2.1. Jenkins
        2. 1.2.2.2. Jenkins实战
          1. 1.2.2.2.1. 自由风格构建系统
          2. 1.2.2.2.2. Pipeline
            1. 1.2.2.2.2.1. What
            2. 1.2.2.2.2.2. Why
            3. 1.2.2.2.2.3. How
            4. 1.2.2.2.2.4. Jenkinsfile
            5. 1.2.2.2.2.5. Problems
          3. 1.2.2.2.3.  Tools
            1. 1.2.2.2.3.1. Pipeline Tools
            2. 1.2.2.2.3.2. Email Plugin
            3. 1.2.2.2.3.3. Unity3D Plugin
        3. 1.2.2.3. Jenkins从零搭建
        4. 1.2.2.4. 脚本语言
          1. 1.2.2.4.1. bat
          2. 1.2.2.4.2. Shell
            1. 1.2.2.4.2.1. bash
          3. 1.2.2.4.3. Python(严格意义上不算脚本语言)
        5. 1.2.2.5. IOS自动化打包
          1. 1.2.2.5.1. IOS打包准备
          2. 1.2.2.5.2. XUPorter or Unity API(PBXFroject)
            1. 1.2.2.5.2.1. XUPoter
            2. 1.2.2.5.2.2. PBXProject
        6. 1.2.2.6. IOS自动化签包
  2. 2. References
    1. 2.1. Jenkins Part
    2. 2.2. Shell Part
    3. 2.3. Xcodebuild Part
    4. 2.4. Other Part

Unity自动化打包

本章节主要是为了学习Unity自动化打包相关知识以及运用。
本文大部分属于本人学习理解的东西,如有不对欢迎指出,学习交流,共同进步。

本章节的流程如下:

  1. 了解什么是打包以及打包过程中的一些相关概念
  2. 了解什么是自动化以及帮助我们快速完成一键打包的工具
  3. 自动化打包工具或编程语言的选取
  4. Unity自动化打包过程中的相关工具
  5. 实战集成运用到Unity构建一键打包
  6. 更多学习程序发布以及相关概念

在了解相关自动化工具之前,我们需要先简单了解下什么是版本控制。在我们的日常开发工作中必不可少。

版本控制

版本控制相关概念学习

打包

平时做游戏开发,在完成整个游戏功能开发后,需要通过打包上真机测试。
把所有游戏资源代码打包成安装包的过程称为打包。

多平台

在多平台的处理上,Unity已经帮我们做了很大一部分工作了。让我们大部分时间只需关心游戏核心开发(Unity部分API在不同平台有差异),在打包这一块只需打选择对应平台即可。

但Unity自带的打包,终究只包含最基本的打包流程,当我们有特殊需求时,我们需要通过Unity以及相关工具完成支持我们自定义的打包流程。

自动化

在了解什么是自动化之前,让我们看看打包的基本流程。
在打包过程中,打包往往包含4个步骤。

  1. Preprocess(打包准备步骤)
    这一步主要是为正式打包编译做准备,比如做一些平台相关设置,资源打包处理,文件复制等
  2. Build(打包编译步骤)
    这一步是程序代码和资源正式打包成安装包的步骤,主要是通过指定打包参数执行打包。
    Unity在打包到Android和IOS平台时有差异。Android是直接就打包出APK,而IOS要先打包成XCode工程,然后在通过编译XCode工程打包出Ipa安装包。
  3. Postprocess(打包结束后期处理步骤)
    这一步是打包完成后负责后续的自动化操作以及善后工作,比如在打包IOS时打包出XCode工程后,可以在这一步进行XCode工程的自动化处理部分(比如动态添加info.plist文件信息,动态插入代码等)
  4. Deploy(安装步骤)
    这一步主要是打包好的安装包安装到真机的步骤

如果没有自动化,我们需要一步一步人工手动去完成上面的步骤。
而自动化就是只需人工设置打包最基本的参数信息,然后通过一步就自动化完成上述步骤的过程。

Jenkins

Introduction
Jenkins是一个用Java编写的开源的持续集成工具。Jenkins提供了软件开发的持续集成服务。它运行在Servlet容器中(例如Apache Tomcat)。它支持软件配置管理(SCM)工具(包括AccuRev SCM、CVS、Subversion、Git、Perforce、Clearcase和RTC),可以执行基于Apache Ant和Apache Maven的项目,以及任意的Shell脚本和Windows批处理命令。Jenkins的主要开发者是川口耕介。)

本人使用目的:
做Unity游戏打包机,持续集成自动化打包流程,做到一键打包。

Deployment

  1. Download Jeakins
  2. Open up a terminal in the download directory and run java -jar jenkins.war
  3. Browse to http://localhost:8080 and follow the instructions to complete the installation.(安装插件)

Jenkins实战

自由风格构建系统

主要是采用特定的构建系统去构建自动化。(比如java通过ant||Maven,windows通过bat||python等)
OpenAutomation

如果不想采用Pipeline编写自动化流程,也可以通过直接编写调用Windows Bat或者Python等命令触发自动化流程。
OpenAutomationWithCommands

Unity3d Plugin不支持Pipeline,所以为了使用Unity3d Plugins,这里采用自由风格构建系统来搭建Unity3D的自动化打包。

  1. Create Free Style(创建自由风格项目)
    新建 -> 构建一个自由风格的软件项目
    JenkinsFreeStyleProject
  2. 设置参数构造(用于自定义参数添加)
    FreeStyleWithParametersSetting
  3. 设置代码来源
    FreeStyleWithSourceSetting
  4. 设置构建命令(bat || python || unity3d……)
    FreeStyleWithCommandsSetting
  5. 设置构建完毕后的一些行为(比如邮件通知)
    FreeStyleWithPostProcessing

Note:
想要改变默认的workspace路径的话。
设置->使用自定义的工作空间(只针对当前设置的自由风格项目有效)
FreeStyleWithOwnWorkspace

Pipeline
What

Jenkins Pipeline is a suite of plugins which supports implementing and integrating continuous delivery pipelines into Jenkins. Pipeline provides an extensible set of tools for modeling simple-to-complex delivery pipelines “as code”.(这里的Pipeline是一套插件,用于支持持续自动化集成编写。好比脚本文件定义一系列的自动化流程,但并不等价于脚本。)

那么Pipeline是用什么语言编写了?
Jenkins是基于Java的,Pipeline的编写是基于Groovy。
Groovy是Java平台上设计的面向对象编程语言。这门动态语言拥有类似Python、Ruby和Smalltalk中的一些特性,可以作为Java平台的脚本语言使用。

Why

为什么需要Pipeline?

  1. Code — 可管理可编辑可视化的自动化流程脚本
  2. Durable — 可持续化开发
  3. Pausable — 可以自动也可以停止等待输入
  4. Versatile — 支持复杂的持续交付需求
  5. Extensible — “The Pipeline plugin supports custom extensions to its DSL [5: Domain-Specific
    Language] and multiple options for integration with other plugins.”
How

接下来让我们看看如何创建一个Pipeline。

  1. 下载Pipeline插件
    DownloadPipelinePlugin
  2. New Item — 新建Pipeline(选择Pipeline类型)
    CreatePipeline
  3. Configure Pipeline
    • 配置Pipeline的名字
      ConfigurePipeline
    • 设置Jenkins Pipeline Code的来源
      • 网页版编写
        CreatePipelineSetPipelineSource
      • SVN下含Jenksfile文件
        CreatePipelineSetPipelineSourceSVN
        这里不深入讨论Jenkins的各种设置,详情查看官方文档Jenkins User Handbook

Note:
Pipeline supports two syntaxes, Declarative and Scripted Pipeline

Jenkinsfile

除了直接在网页上编写Pipeline自动化流程。我们也可以指定Jenkinsfile文件作为编写Pipeline的地方。

为什么需要Jenkinsfile?
主要还是为了放入版本管理,方便所有人可视化看到自动化流程代码。

e.g.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#!groovy
pipeline {
//agent指定jenkins从哪里以及如何执行pipeline
//any表示从any available agent处执行
agent any

//看定义在那一层,就只针对那一层起作用(pipeline一层针对pipeline。stage一层针对特定stage)
//也可以修改已定义的环境变量值
environment {
UNITY_PROJECT_PATH = 'E:\\TonyTang\\Work\\Projects\\TGame_code'
}

//自定义参数
//可以通过pipeline代码编写,也可以直接在web UI界面上添加
parameters{
string(name: 'Platform', defaultValue: 'Android', description: 'Which plaftform is using?')
booleanParam(name: 'DEBUG_BUILD', defaultValue: true, description: 'Is debug version?')
}

stages {
stage('postprocess'){
//when表示执行条件判断
//决定stage是否应该执行
when{
expression
{
return params.DEBUG_BUILD
}
}
steps{
//params是访问pipeline里定义的所有parameters入口
echo "Current platform is ${params.Platform}"
echo 'postprocess'
echo UNITY_PROJECT_PATH
//env是jenkins定义的全局的环境变量
//http://localhost:8080/job/TonyTangPipeline/pipeline-syntax/globals
echo env.WORKSPACE
}
}
stage('build') {
steps {
//Windows调用bat
//Mac Linux采用sh
bat 'TonyTangPipeline'
}
}
stage('deploy'){
steps{
echo 'deploy'
//支持调用python
bat 'python FirstPython.py'
}
}
}

//post表示Pipeline执行完之后执行,做一些clean up操作
post {
//根据pipeline的结果做特定操作
always{
echo 'Always!'
}
success {
echo 'Success!'
}
failure{
echo 'Failed!'
}
}
}

// Script Pipeline(Optional)
//在pipeline执行完成之后执行
//支持大部分Groovy
//Script Pipeline的参数定义需要在properties里
properties([parameters([string(defaultValue: 'Android', description: 'Which platform is using?', name: 'Platform')])])

node {
//定义字符串
def Date = '2017//4//26'
stage('postprocess') {
//要使用${}访问string,需要用""
echo "Date = ${Date}"
echo "Current platform is ${params.Platform}"
echo 'postprocess....'
}
stage('build') {
echo 'build....'
}
stage('deploy') {
echo 'deploy....'
}
}

Note:

  1. 指定使用版本管理后,Jenkinsfile必须放到版本管理里才能找到。
  2. Jenkinsfile必需放到前面设置的本机相对路径下才能找到。
Problems
  1. 构建参数一旦通过代码指定添加后,代码删除了依然存在
  2. No valid crumb was included in the request

Solutions:

  1. 通过Pipeline -> Setting的UI界面手动删除参数
  2. 在系统管理 –> Configure Global Security
    取消“防止跨站点请求伪造(Prevent Cross Site Request Forgery exploits)”的勾选。
 Tools
Pipeline Tools

Snippet Generator
PipelineSnippetGenerator
这是一个很有用帮助快速编写Pipeline的工具,他可以帮助我们把我们熟悉的自动化脚本转换成Pipeline格式的语句,也可以快速生成一些特定功能的脚本(比如邮件功能等)。(Jenkins提供)

Pipiline全局环境变量

Email Plugin

利用现有的SMTP(腾讯,网易等)服务进行搭建邮件提醒,详情参见如下:
Jenkins——应用篇——插件使用——Mailer Plugin
Jenkins自带Email配置如下:
Manage Jenkins -> Configure System -> E-mail Notification
EmailNotificationSetting
Jenkins自带的Mailer提供的功能很有限,为了提供更多更广的功能,这里我们需要用到Jenkins Email Extension Plugin
Jenkins进阶系列之——01使用email-ext替换Jenkins的默认邮件通知
Email Extension Plugin配置详情参见如下:
Manage Jenkins -> Configure System -> Extended E-mail Notification
ExtendedEmailNotificationPart1
针对workspace自定义邮件内容配置如下:
WorkspaceExtendedEmailSetting
最终收到Email提醒:
EmailResult

Unity3D Plugin

管理插件->Unity3d Plugin
Unity本身是支持从命令行启动U3D,执行Editor特定方法的。
但Unity3D Plugin是一个针对Jenkins集成Unity命令行的插件,可以帮助我们直接在Jenkins上编写U3D命令,对于Jenkins更加友好。
好处如下:

  1. Log file redirection(Log重定位到Jenkins界面)
  2. Distributed builds(分布式编译)

Unity3D Plugin实战配置

  1. Configure Unity Installation Path
    System Setting -> Global Tool Configuration
    JenkinsUnityPathConfiguration
  2. 编写Unity下可通过命令行调用的代码
1
2
3
4
5
6
7
8
9
10
11
12
using UnityEngine;
using System.Collections;
using UnityEditor;

public class UnityBuild {

[MenuItem("Custom/UnityBuild/OneKeyBuild")]
static void OneKeyBuild()
{

Debug.Log("OneKeyBuild() Called!");
}
}
  1. 配置Unity命令行运行启动以及参数
    JenkinsUnityMethodCall
    JenkinsUnityMethodCallOutput
    从上面可以看出我们成功的配置了Jenkins以及对于Unity打包方法的调用(在这一步我们可以做很多我们自己对于Unity打包的流程设置,资源打包或者其他的预处理等)
  2. 编写完整的打包设置以及相关预处理代码
    待续…….
    接下来是为了在IOS进行Jenkins自动化打包而设置的步骤
  3. 在IOS搭配SmartSVN拉VisualSVN上的文件(Windows上用TortoiseSVN作为SVN客户端即可)
  4. 在IOS上配置Jenkins(和Windows上类似,需要下载Java支持Jenkins运行)
    这里讲几个配置搭建Mac OSX的Jenkins时遇到的坑:
    1. 配置Unity插件Unity安装路径问题
      Windows上是到Editor上一层即可。IOS是到Unity.app这一层(Unity.app(e.g. /Applications/Unity/Unity.app/)在命令行里可以往里访问)
    2. 指定IOS Subversion URL(e.g. https://192.168.1.3/svn/***)时显示无法访问或者不正确的credential(这里是因为默认的Mac SVN版本过低需要更新Mac OSX自带的SVN)
    3. 更新Mac OSX自带SVN时在添加新装的SVN到环境变量里后依然显示是老版的SVN
      在~/目录下touch .bash_profile(创建.bash_profile)
      然后添加环境变量路径export PATH=”/opt/subversion/bin;$PATH”
      使其环境变量路径设置生效source .bash_profile
      查看当前SVN版本svn —version
      如果上一步依然显示是旧版SVN且当前系统是10.11
      那么我们就需要删除Mac OSX上原本的SVN以实现更新SVN的目的
      查看旧版SVN路径which svn
      删除旧版SVN(sudo -s rm -rf /svnpath/svn)(此时如果出现No Permission to remove,那么恭喜,你是用的10.11系统。10.11推出了SIP(System Integrity Protection)机制去防止root用户对于部分文件的操作权限限制用于防止病毒破坏或修改重要文件)
      为了能够删除旧版SVN,这里我们需要进到Recover Mode去临时关闭SIP(重启电脑一直按住cmd+R,然后在terminal里执行csrutil disable,关闭成功后就可以重启电脑去删除旧版svn了)
      然后将新版SVN链接到旧版SVN执行路径(sudo -s ln -s /opt/subversion/bin/svn
      /svnpath)
      最后到Recover Mode里恢复SIP(csrutil enable)
    4. 打包IOS时报错:_RegisterApplication(), FAILED TO establish the default connection to the WindowServer, _CGSDefaultConnection() is NULL.
      这是因为Jenkins默认安装时是以Jenkins作为用户和组。开机自启动时也是以Jenkins用户和组来运行的。而Unity我是默认安装在我自己的用户apple和admin组下。所以在Jenkins需要启动Unity时应该是没有权限调用 Unity Editor 的命令行。所以我们要做的就是确保Unity和Jenkins运行在同一个User和Group下。
      以下采用把Jenkins改到apple和wheel下运行以支持Unity的命令行运行。
      详情参见macOS 安装配置 Jenkins 持续集成 Unity 项目指南
    5. 指定Custom Workspace(自定义到桌面特定目录后每次都报Failed to mkdir,这个跟第四个问题是同一个问题,默认Jenkins用户权限问题)

Note:
很多文件是Unity自动生成的(比如Library目录),我们无需加入版本管理(SVN只要不加入版本管理即可)

Note:
Unity3d Plugin不支持在Pipeline里添加。
同时Unity3d Plugin只在3.4.2 to 5.0.1的版本下测试过。(经本人测试在Unity5.4.0f3的版本也能正常使用)

Jenkins从零搭建

这里的从零搭建,在借助Jenkins插件的基础上,尽量采用Pipeline编写为主,插件UI为辅的方式来实现一整套完整的Jenkins自动化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#!groovy
pipeline {
//agent指定jenkins从哪里以及如何执行pipeline
//any表示从any available agent处执行
agent any

//看定义在那一层,就只针对那一层起作用(pipeline一层针对pipeline。stage一层针对特定stage)
//也可以修改已定义的环境变量值
environment {
UNITY_PROJECT_PATH = 'E:\\TonyTang\\Work\\Projects\\MyGitHubProjects\\GitStudy2'
GIT_URL = 'git@github.com:TonyTang1990/GitStudy.git'
GIT_AUTH = '7d947f92-1e9c-44ba-9a80-9744f92f2ff6'
}

//自定义参数
//可以通过pipeline代码编写,也可以直接在web UI界面上添加
parameters{
string(name: 'Platform', defaultValue: 'Android', description: 'Which plaftform is using?')
booleanParam(name: 'DEBUG_BUILD', defaultValue: true, description: 'Is debug version?')
}

stages {
stage('postprocess'){
//when表示执行条件判断
//决定stage是否应该执行
when{
expression
{
return params.DEBUG_BUILD
}
}
steps{
//params是访问pipeline里定义的所有parameters入口
echo "Current platform is ${params.Platform}"
echo 'postprocess'
echo "${env.UNITY_PROJECT_PATH}"
echo "${env.GIT_URL}"
echo "${env.GIT_AUTH}"
//env是jenkins定义的全局的环境变量
//http://localhost:8080/job/TonyTangPipeline/pipeline-syntax/globals
echo env.WORKSPACE
}
}
stage('Git') {
steps {
echo 'Git operation'
checkout([$class: 'GitSCM',
userRemoteConfigs: [[url: "${env.GIT_URL}", credentialsId: "${env.GIT_AUTH}"]],
extensions: [[$class: 'CleanBeforeCheckout', $class: 'GitLFSPull']]
])
}
}
stage('build') {
steps {
//Windows调用bat
//Mac Linux采用sh
bat 'BatAutomation'
}
}
stage('deploy'){
steps{
echo 'deploy'
//支持调用python
bat 'python PythonAutomation.py'
}
}
}

//post表示Pipeline执行完之后执行,做一些clean up操作
post {
//根据pipeline的结果做特定操作
always{
echo 'Always!'
}
success {
echo 'Success!'
}
failure{
echo 'Failed!'
}
}
}

// Script Pipeline(Optional)
//在pipeline执行完成之后执行
//支持大部分Groovy
//Script Pipeline的参数定义需要在properties里
properties([parameters([string(defaultValue: 'Android', description: 'Which platform is using?', name: 'Platform')])])

node {
//定义字符串
def Date = '2017//4//26'
stage('postprocess') {
//要使用${}访问string,需要用""
echo "Date = ${Date}"
echo "Current platform is ${params.Platform}"
echo 'postprocess....'
}
stage('build') {
echo 'build....'
}
stage('deploy') {
echo 'deploy....'
}
}

脚本语言

为了快速编写一些自动化任务,我们往往需要用到脚本语言。

bat

批处理文件(英语:Batch file),又称批次档,在DOS、OS/2、微软视窗系统中,是一种用来当成脚本语言运作程序的文件。它本身是文本文件,其中包含了一系列让具备命令行界面的解释器读取并运行的指令。它相当于是类Unix系统下的Shell script。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
:: 关闭命令输出打印
@echo off
:: UTF8
chcp 65001
:: 清除命令行界面内容
CLS
:: 输出打印信息
echo Tony Tang bat study
:: 设置打印传入参数
set par1=%1
set par2=%2
echo %par1%
echo %par2%
:: 输出打印当前目录路径
echo %cd%
:: 切换到上一层路径
cd ..
:: 输出打印当前目录路径
echo %cd%
:: 切换到BatStudy目录
cd BatStudy
:: 输出打印当前目录路径
echo %cd%
:: 打印帮助信息
::HELP
:: 自定义变量(定义变量时=号两边不能有空格)
set batvariable=tonytang
:: 打印自定义变量
echo %batvariable%
:: 自定义数字变量
set number=3
:: 打印自定义数字变量
echo number = %number%
:: 条件语句
if %number%==2 (echo %number%==2) else (echo %number%!=2)
:: 检查文件是否存在
set filename=batcontent.txt
echo filename=%filename%
if EXIST ./%filename% (echo %filename% exits) else (echo %filename% not exist)
:: 普通循环语句
for %%i in (1 2 3) do echo %%i
:: 当前目录文件循环遍历语句
for %%f in (*) do @echo %%f
:: 启用变量执行时解析
SetLocal EnableDelayedExpansion
for %%i in (1 2 3) do (
set numberVariable=%%i
echo !numberVariable!
set numberVariable=!numberVariable!+!numberVariable!
echo !numberVariable!
)
:: 暂停等待输入
pause
:: 退出命令界面 0代表正常退出
EXIT 0

BatStudyOutput

这里只列举一些常用基础的,详细Windows Batch学习参考:
Batch file – Programming tutorial
Batch Script Tutorial

Shell

Windows上的bat,power shell
Linux上的shell
好处:
简单快速的编写一些系统相关的任务(比如文件复制等)
缺点:
有平台差异

在正式学习Shell之前,让我们依然从What,How,Why,When这四个问题着手:
What?
Unix shell,一种壳层与命令行界面,是UNIX操作系统下传统的用户和计算机的交互界面。第一个用户直接输入命令来执行各种各样的任务。

第一个Unix shell是由肯·汤普逊,仿效Multic上的shell所实现出来,称为sh。

后续出了其他的Shell:

  1. Bourne-Again shell - bash
  2. Debian Almquist shell - dash
  3. Korn shell - ksh
  4. Z shell - zsh

上面的几个Shell都可以看作是后续发展功能越来越强大的Shell解析器,至于具体分别包含些什么功能就不在本文讨论的范围内了。这里我们主要以bash和shell作为解析器来学习编写shell脚本。

How?
Shell脚本(英语:Shell script),又称Shell命令稿、程序化脚本,是一种计算机程序与文本文件,内容由一连串的shell命令组成,经由Unix Shell直译其内容后运作。被当成是一种脚本语言来设计,其运作方式与解释型语言相当,由Unix shell扮演命令行解释器的角色,在读取shell脚本之后,依序运行其中的shell命令,之后输出结果。利用shell脚本可以进行系统管理,文件操作等。

可以看出Shell脚本是被当做脚本语言来解释执行,通过调用系统内核的一些工具来实现特定功能任务的。

Why?
Shell并不适合编写复杂的程序任务,因为他只提供了一些比较基础的工具,并且不具备面向对象编程等高级语言的特性。
既然如此那我们为何还要坚持使用Shell了?为何不直接选择Python,Ruby,Perl这些相对高级一点的解释性语言了?
理论上是可以的,但Shell属于原生就具备提供的工具,可以很快捷的编写出一些看似简单但却方便的工具脚本。即使Python可以实现,但针对特定的功能可能没有Shell来的那么快捷方便,根据需求选择合适的脚本语言也是很重要的。后续我会结合Python夸平台的特性,使用Python结合Shell编写一些自动化相关的工具。

Note:
Shell跟操作系统紧密相关,原本并非跨系统平台的,但通过移植很多shell具备了跨系统平台的能力(Windows上通过Cygwin与MinGW可以模拟执行Unix Shell)。

When?
那么什么时候适合用Shell了?从前面三个问题不难看出,Shell只适合用于一些不复杂的功能任务里,无论是语言特性还是跨平台都没有Python那些高级语言支持的好。如果需要实现复杂的且跨平台,需要高级语言特性(面向对象等)时,不应该考虑Shell而是Python,Perl,Ruby这些。

Note:
这里所说的shell和Window的batch并不是同一个东西。

bash
  • 指定解析器
1
2
#!/bin/bash
# Proper header for a Bash script

#!是two-byte的magic number
表示这个文本是executable shell script,后面紧跟的/bin/bash表示shell解析器是用的bash

  • 输出打印
    学程序,最初开始的地方肯定是Hello World。
    在bash里打印输出是通过echo指令。
1
2
3
#!/bin/bash
# Proper header for a Bash script
echo "Hello World"

Bash echo

  • 特殊符号
  1. 井号(#) — 注释符号
1
# Proper header for a Bash script(这一行#号开头表示注释)
  1. ; — 命令分隔符(允许同一行多个指令)
1
2
#! /bin.bash
echo "Hello"; echo "World"
![CommandSeparator](/img/Bash/CommandSeparator.png)
  1. ;; — 跳出case语句
1
2
3
4
5
6
#! /bin/bash
variable="TonyTang"
case "$variable" in
TonyTang) echo "TonyTang1";;
TonyTang) echo "TonyTang2";;
esac
![TerminateCommand](/img/Bash/TerminateCommand.png)
  1. .
    • 表示文件是隐藏的
1
2
3
4
#! /bin/bash
touch .hidden-file
ls -l
ls -al
![HidenProfileCommand](/img/Bash/HidenProfileCommand.png)
- 表示当前目录
1
2
3
pwd
cd .
pwd
![CurrentDirectoryCommand](/img/Bash/CurrentDirectoryCommand.png)
- 正则里面表示匹配单个字母
  1. “” - 字符串(保留大部分特殊符号)
  2. ‘’ - 字符串(保留所有特殊符号)
  3. ,
  4. \ - 反斜杠,忽略特殊符号
1
2
#! /bin/bash
echo "\"Hello World"\"
![Backslash](/img/Bash/Backslash.png)
  1. / - 文件路径分隔符
  2. : - 相当于内置true
1
2
3
4
5
6
while :
do
echo "Number 1"
echo "Number 2"
echo "Number 3"
done
![ColonOperator](/img/Bash/ColonOperator.png)
  1. ! - 取反
  2. 星号(*) - 通配符或者乘法
  3. ? - 三目运算符或者表示测试条件
  4. $
    • $ 变量取值符号
    • $? 退出状态码
    • $$ 进程号
1
2
3
4
5
#! /bin/bash
var1=100
echo "var1 = $var1"
echo $?
echo $$
![VariableSubstitution](/img/Bash/VariableSubstitution.png)
  1. &> >& >> < <>

    • &> 重定向输出到指定文件

    • <> 文件读取

1
2
3
4
5
#! /bin/bash
ls -l
echo "Hello World" >HelloWorldFile.txt
ls -l
cat HelloWorldFile.txt
![RedirectionCommand](/img/Bash/RedirectionCommand.png)
  1. ~ ~+ - home目录和当前目录
1
2
3
4
echo ~
echo $HOME
echo ~+
echo $PWD
![FolderCommands](/img/Bash/FolderCommands.png)
  1. ` - 命令执行符号(可以用于快速执行命令并将结果复制)
1
2
Date=`date +%Y%m%d%H%M%S`
echo $Date
![CommandsSubstitution](/img/Bash/CommandsSubstitution.png)
  • Shell小知识
  1. $0 - $9
    $0表示Shell文件名
    $1-$9表示Shell的第一个到第九个参数

  2. ls -l
    查看文件详细信息(比如读写权限等)
    LSCommand
    具体如何理解最前面的权限信息,参考第六章、Linux 的文件权限与目录配置

  3. chmod
    修改文件权限

  4. cd
    切换目录

  5. pwd or ~+
    当前所在目录

  6. echo
    输出信息

  7. exit
    退出shell

  8. $?
    前一段shell的返回输出值

  9. $ or ${}
    去变量值

  10. \
    多行shell支持

  11. $SHELL
    从当前Shell执行结束的地方打开一个新的terminal窗口(能保持之前所有的输出信息都在)

待续……

Python(严格意义上不算脚本语言)

好处:

  1. 跨平台
  2. 解释性语言,开发方便快速
  3. 库丰富
    待续……

IOS自动化打包

IOS打包准备
  1. Apple Account(发布到IOS设备上需要)
    任何账号都可以发布到自己的设备测试,但是如果要使用GC or In-App Purchases或者发布到App Store的话需要注册Apple Developer Program。

  2. Xcode(up-to-date version)
    因为Xcode是Mac上的软件,所以这里需要Mac电脑或者是黑苹果。(开发所需的SDK,IDE套装都在这)

  3. Adding your Apple ID to Xcode
    Open Xcode -> Xcode -> Preferences -> Account ->

  4. 在开发之前,Mac需要获得IOS Certificartes(用于对Mac开发授权,程序签名等)
    登陆Apple Developer Program -> Certificates ,identifiers & Profiles -> Certificate Signing Request
    Create CSR(Certificate Signing Request):

    1. Folder -> Utilities Folder -> Keychain Access
    2. Keychain Access -> Certificate Assistant -> Request a Certificate from a Certificate Authority -> 填完内容存储到本地
      CSR里包含了public key和private key。
      当CSR创建以后,需要使用CSR去请求一个Certificate:
    3. 上传CSR
    4. 下载Certificate
    5. 运行安装Certificate
      安装Certificate后public和private key pair就生成了(在Keychain Access下可以查看)而我们的应用程序签名是使用私钥来签名用公钥来进行验证, 而苹果生成的Certificate 只包含了公钥,当你用自己的私钥签名后,苹果会用公钥来进行验证,确保是你自己对程序签名而不是别人冒充的
      通过导出共享公钥和私钥,可以实现共同开发(Mac开发授权,程序签名等)。
  5. 设置App ID(相当于Android包名,用于识别App)
    App ID包含以下组成部分:

    1. Prefix — 作为Team ID
    2. Suffix — 作为Build ID的搜索字符串(可指定通配符)
    3. AppServices — 指定包含的Service(比如Game Center, IAP, iCloud等服务),只有指定了这些,使用包含了此APP ID的Provision Profile才能使用特定服务
  6. 添加设备列表(只有被添加了的设备列表才能用于安装和测试程序)
    需要通过Itunes查看UDID,然后添加到设备列表。

  7. 用上述我们创建的Certificate, Apple ID, Devices List等生成我们的Provision Profile(Unity Cloud Build的时候需要指定)
    不同的Provision Profile有不同的用处,比如iPhone Developer证书用于测试设备上运行,iPhone Distribution证书用于提交应用到App Store。
    让我们来理解一些相关概念,下文引用至IOS开发 证书总结
    Identifier:
    顾名思义App ID(application id, not apple id), 对于你的一个或者一组app他们的唯一标识, 这个App ID跟你Xcode中的Targets ——-> General——-> Identity中的Bundle Identifier是匹配的,(其余的那些推送服务啊什么的都是配置在APP ID下面的) 如下图:

    Provisioning Profile
    一个Provisioning Profile包含了上述所有内容 Certificate && App ID && Device, 这个Provisioning Profile文件会在打包时嵌入到.ipa的包里,如下图:

    所以一台设备上运行应用程序的过程如下(以Developer Provisioning Profile为例):
    1 检查app 的 bunld ID 是否 matches Provisioning Profile 的 App ID
    2 检查 app 的 entitements 是否 matches Provisioning Profile 的 entitements
    3 用Certificate来验证签名签名
    4 检查此设备的UDID是否存在于 Provisioning Profiles中 (仅在 非发布证书中)

    如何创建?
    在 Provisioning Profiles 中点加号,然后依次选择App ID, Certificate, Devices(development),再指定名称,最后下载, 双击则安装到Xcode中

    Xcode中的配置
    Project && Target 的 build settings 中搜索Code sign…
    然后分别选好对应的证书,如果选择列表中没有刚才创建的证书可以双击直接复制名字上去

    关于推送服务
    基于上面的操作,如果需要推送服务我们还需要申请一个推送证书
    依次进入 Certificates —>Production —>Apple Push Notification service SSL (Production)
    然后选择需要推送服务的App ID
    再选择前面创建的.cerSigningRequest文件
    最后点击generated生成推送证书
    IOS开发流程大致如下:
    IOSAppDistributionWorkflows

XUPorter or Unity API(PBXFroject)

PBXFroject是Unity 5.X版本加进来的。因为5.X以前没有关于XCode自动化修改的相关接口,所以以前的版本用的都是XUPoter这个第三方插件工具来实现自动化修改info.plist文件,自动添加library之类的功能。

本人只使用过XUPoter还没详细使用PBXProject,但XUPoter的作者在Unity 5.X开始已经不在更新支持了,因为有了PBXProject,所以可以这样说,Unity 5.X版本开始可以直接使用Unity 5.X不需要再使用XUPoter了。

XUPoter

这里直接给出作者工具链接,就不详细讲解如何集成使用了,具体参考官方作者网站。
onevcat/XUPorter

PBXProject

对IOS的打包后的XCode工程做后处理,我们需要在代码里实现IPostProcessBuild接口的OnPostProcessBuild方法。

XcodePostProcess.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
using UnityEngine;
using System;
using System.Collections.Generic;
UnityEditor.iOS.Xcode

public static class XCodePostProcess : IPostprocessBuild

public void OnPostprocessBuild(BuildTarget target, string path)
{
Debug.Log("XCodePostProcess.OnPostprocessBuild for target " + target + " at path " + path);

//通过PBXProject访问并修改XCode工程相关的设置
string projpath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
PBXProject proj = new PBXProject();

//读取XCode项目的数据
proj.ReadFromString(File.ReadAllText(projPath));

//获取XCode项目的Unity Target名字
string nativetargetname = PBXProject.GetUnityTargetName();

//获取XCode项目的Unity Target的GUID
string nativeTarget = proj.TargetGuidByName(nativetargetname);

//获取XCode项目的Test Target名字
string testTarget = proj.TargetGuidByName(PBXProject.GetUnityTestTargetName());

//设置需要设定的target列表
string[] buildTargets = new string[] { nativeTarget, testTarget };

//BitCode是用于提交AppStore后优化数据用的,IOS上是optional,但watchOS和tvOS是必须的。动态设置BitCode值
proj.SetBuildProperty(buildTargets, "ENABLE_BITCODE", "NO");

//动态修改UnityAppController.mm文件的arc编译参数
ArcOnFileByProjectPath(proj, nativetargetname, "Classes/UnityAppController.mm", false);

//自动化删除ReleaseForProfiling和ReleaseForRunning两个Configuration
proj.RemoveBuildConfig("ReleaseForProfiling");
proj.RemoveBuildConfig("ReleaseForRunning");

//整个XCode项目数据写回去
File.WriteAllText(projPath, proj.WriteToString());
}

/// <summary>
/// 开启或关闭指定target的指定文件的arc(Automatic Reference Counting)
/// </summary>
/// <param name="proj"></param>
/// <param name="targetname"></param>
/// <param name="filepath"></param>
/// <param name="isenablearc"></param>
private void ArcOnFileByProjectPath(PBXProject proj, string targetname, string filepath, bool isenablearc)
{
string targetguid = proj.TargetGuidByName(targetname);
var fileguid = proj.FindFileGuidByProjectPath(filepath);
Debug.Log("targetguid = " + targetguid);
Debug.Log("fileguid = " + fileguid);
var compileflaglist = new List<string>();
if(isenablearc)
{
compileflaglist.Add("-fobjc-arc");
}
else
{
compileflaglist.Add("-fno-objc-arc");
}
proj.SetCompileFlagsForFile(targetguid, fileguid, compileflaglist);
}
}

这里就不一一截Mac对应的图了,示例代码对XCode的各种参数设置都进行了修改,注释也很清楚了,具体详细的使用,请参考官网API解释。

IOS自动化签包

Xcodebuild是XCode上编译Xcode工程的工具。

所以接下来我们主要是要学习的是使用Xcodebuild去编写自动化签包的工具。

学习XCode Project项目里的一些相关概念。
一下概念来源iOS系统提供开发环境下命令行编译工具:xcodebuild:

  1. Workspace:简单来说,Workspace就是一个容器,在该容器中可以存放多个你创建的Xcode Project, 以及其他的项目中需要使用到的文件。
    使用Workspace的好处有:
    • 扩展项目的可视域,即可以在多个项目之间跳转,重构,一个项目可以使用另一个项目的输出。Workspace会负责各个Project之间提供各种相互依赖的关系;
    • 多个项目之间共享Build目录。
  2. Project:指一个项目,该项目会负责管理生成一个或者多个软件产品的全部文件和配置,一个Project可以包含多个Target。
  3. Target:一个Target是指在一个Project中构建的一个产品,它包含了构建该产品的所有文件,以及如何构建该产品的配置。
  4. Scheme:一个定义好构建过程的Target成为一个Scheme。可在Scheme中定义的Target的构建过程有:Build/Run/Test/Profile/Analyze/Archive
  5. BuildSetting:配置产品的Build设置,比方说,使用哪个Architectures?使用哪个版本的SDK?。在Xcode Project中,有Project级别的Build Setting,也有Target级别的Build Setting。Build一个产品时一定是针对某个Target的,因此,XCode中总是优先选择Target的Build Setting,如果Target没有配置,则会使用Project的Build Setting。

了解了XCode Project中的一些概念,让我们看看Xcodebuild到底是什么样的工具了?
xcodebuild is a command-line tool that allows you to perform build, query, analyze, test, and archive operations on your Xcode projects and workspaces from the command line. It operates on one or more targets contained in your project, or a scheme contained in your project or workspace.

如果想查看xcodebuild的详细说明,我们可以通过命令行输入:man xcodebuild查看即可:
XcodebuildManualPage

首先以XcodeProject为例,我们来看看上面的概念在实际操作过程中对应的东西:
XcodeProjectDemo
从上面可以看出,我们的Demo Xcode Project是没有workspace的,只有一个叫做Unity-iPhone.xcodeproj的Xcode工程文件,而这个文件对应的就是我们前面提到的Project的概念。

如果我们想查看Xcode Project里面的相关schemes或者target信息,我们可以通过命令行切换到Xcode Project目录下然后输入xcodebuild -list -project *.xcodeproj命令来查看:
XcodeProjectListInfo
从上面的信息我们可以看到,我们的Unity-iPhone.xcodeproj项目里有两个Targets分别为:Unity-iPhone和Unity-iPhone Tests以及一个Schemes:Unity-iPhone。这些都分别对应了我们前面最初所了解的相关Xcode Project的概念。

如果想要查看Xcode Project的详细Build Setting设置信息,我们可以通过输入xcodebuild -showBuildSettings来查看:
XcodeProjectBuildSettingsInfo
-showBuildSettings有不少东西,很多概念需要对比着Xcode工程里的设置来看,这里暂时不深究每一个代表什么意思。

了解了Workspace,Project,Target,Scheme,BuildSetting等等概念以及如何通过xcodebuild工具查看Xcode工程相关信息后,接下来我们看一下xcodebuild是如何编译打包导出IPA的。

这里参考网上,我发现Xcode9以后有两种方式来实现自动化打包。

  1. Automatic
  2. Manual

这里我先以Manual的形式来学习打包,后续再学习了解Automatic的形式打包。

首先我们来看看官方对xcodebuild工具的介绍:
xcodebuild is a command-line tool that allows you to perform build, query, analyze, test, and archive operations on your Xcode projects and workspaces from the command line.
可以看出xcodebuild正式苹果官方给出的通过命令行触发打包编译导出一系列操作的工具。

具体想知道xcodebuild的详细使用介绍,可以在shell里输入man xcodebuild查看即可:
ManXcodebuild

Manual:
手动打包之前,我们要用到下面几个比较重要的xcodebuild命令参数:

  1. clean — Remove build products and intermediate files from the build root(SYMROOT)(清楚通过build触发编译的所有中间文件)
  2. build — Build the target in the build root(SYMROOT). This is the default action, and is used if no action is given.(build指定target)
  3. archive — Archive a scheme from the build root(SYMROOT). This requires specifying a scheme.(打包指定scheme)
  4. exportArchive — Specifies that an archive should be exported. Requires -archivePath, -exportPath, and -exportOptionsPlist. Cannot be passed along with an action.(指定Archive文件导出IPA)

下面以工作中完整的命令行编译打包导出IPA为例来讲解学习(敏感信息中文代替):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#! /bin/bash
# 检查并创建文件夹
CheckAndCreateFolder()
{
echo "CheckOrCreateFolder parameter1 : $1"
if [ -d $1 ]
then
echo "$1 exits!"
else
echo "$1 not exits!"
mkdir -p $1
fi
}

currentfolder=~+
homefolderpath=~
xcodeexportoptionfolderpath="${homefolderpath}/Desktop/XcodeAutoSign/"
currentdatetime=`date +%Y%m%d%H%M%S`

#签包相关信息(默认大陆Appstore)
# 包名
bundleid="对应包名"
# 大陆
areas="dalu"
#证书类型
provisionprofiletype="AppStore"
# 证书名字
codesignidentity="对应证书名字"
# 描述文件名字
provisioningprofile="对应描述文件名字"
# Deployment最低版本指定
deploymenttarget="对应最低IOS支持版本"
# 打包导出配置文件
# 最终签名相关信息都是以配置文件为准
exportoptionsplist="对应签名配置文件"

echo "--------------------"
echo "1. 大陆Appstore"
echo "2. 大陆AdHoc"
echo "3. 台湾Appstore"
echo "4. 台湾AdHoc"
echo "--------------------"
echo "输入打包类型数字id(默认不输入是大陆AppStore):"
read countrynumber
# 默认大陆
echo "countrynumber = ${countrynumber:=1}"

echo "输入工程文件夹全路径:"
read xcodeprojectfolderpath
xcodeprojectfullpath="${xcodeprojectfolderpath}/Unity-iPhone.xcodeproj"
echo "xcodeprojectfullpath : ${xcodeprojectfullpath}"

# 检查工程Xcode项目文件是否存在
if [ -d ${xcodeprojectfullpath} ]
then
echo "Xcode项目文件存在!"
else
echo "Xcode项目:${xcodeprojectfullpath}文件不存在!签包IPA失败!" > ErrorLog.txt
exit 0
fi

if [ $countrynumber -eq 1 ]
then
#大陆Appstore
echo "大陆AppStore"
areas="dalu"
provisionprofiletype="AppStore"
bundleid="对应包名"
codesignidentity="对应证书名字"
provisioningprofile="对应描述文件名"
deploymenttarget="对应最低IOS支持版本"
exportoptionsplist="对应签名配置文件"
elif [ $countrynumber -eq 2 ]
then
#大陆Ad Hoc
echo "大陆AdHoc"
areas="dalu"
provisionprofiletype="AdHoc"
bundleid="对应包名"
codesignidentity="对应证书名字"
provisioningprofile="对应描述文件名"
deploymenttarget="对应最低IOS支持版本"
exportoptionsplist="对应签名配置文件"
elif [ $countrynumber -eq 3 ]
then
#台湾Appstore
echo "台湾Appstore"
areas="taiwan"
provisionprofiletype="AppStore"
bundleid="对应包名"
codesignidentity="对应证书名字"
provisioningprofile="对应描述文件名"
deploymenttarget="对应最低IOS支持版本"
exportoptionsplist="对应签名配置文件"
elif [ $countrynumber -eq 4 ]
then
#台湾AdHoc
echo "台湾AdHoc"
areas="taiwan"
provisionprofiletype="AdHoc"
bundleid="对应包名"
codesignidentity="对应证书名字"
provisioningprofile="对应描述文件名"
deploymenttarget="对应最低IOS支持版本"
exportoptionsplist="对应签名配置文件"
else
echo "请输入1或者2或者3或者4!签包失败!" > ErrorLog.txt
exit 0
fi

# IPA文件名
ipadname="${areas}_${provisionprofiletype}_$currentdatetime"
# IPA输出目录全路径
ipaexportpath="/Users/Shared/build-ios/${areas}/${provisionprofiletype}/${currentdatetime}"
# Archive文件名
archivefilename="${xcodeprojectfolderpath}/${areas}_archive.xcarchive"

# 切换到对应工程目录下
cd $xcodeprojectfolderpath
echo "切换到对应工程目录后当前目录:"
echo ~+

# 检查输出目录是否存在,不存在则创建一个
CheckAndCreateFolder $ipaexportpath

#检查导出配置文件是否路径正确且存在
echo "导出配置文件路径:"
echo "${xcodeexportoptionfolderpath}/${exportoptionsplist}"
if [ -f ${xcodeexportoptionfolderpath}/${exportoptionsplist} ]
then
echo "导出配置文件存在!"
else
echo "导出配置文件不存在!签包IPA失败!" > ErrorLog.txt
exit 0
fi

# 判定符合条件的Archive文件是否存在,
# 存在则直接Export,
# 不存在则完全重新签包
if [ ! -d ${archivefilename} ]
then
echo "${archivefilename}文件不存在!"
#clean下工程
echo "开始清理工程"
xcodebuild clean \
-project Unity-iPhone.xcodeproj \
-configuration Release -alltargets \
PRODUCT_BUNDLE_IDENTIFIER="$bundleid"

#开始编译
echo "开始编译打包"
xcodebuild -project Unity-iPhone.xcodeproj \
-scheme Unity-iPhone \
-configuration Release \
PRODUCT_BUNDLE_IDENTIFIER="$bundleid" \
CODE_SIGN_STYLE="Manual" \
CODE_SIGN_IDENTITY="$codesignidentity" \
PROVISIONING_PROFILE="$provisioningprofile" \
IPHONEOS_DEPLOYMENT_TARGET="$deploymenttarget" \
-archivePath "${archivefilename}" \
archive
else
echo "${archivefilename}文件存在!"
fi

#开始打包Archive
echo "开始导出IPA"
xcodebuild -exportArchive \
-archivePath "${archivefilename}" \
-exportOptionsPlist "${xcodeexportoptionfolderpath}/${exportoptionsplist}" \
-exportPath "${ipaexportpath}"

#修改最终输出的ipa文件名
mv -f "${ipaexportpath}/Unity-iPhone.ipa" "${ipaexportpath}/${ipadname}.ipa"

echo "xcodeprojectfolderpath : $xcodeprojectfolderpath"
echo "homefolderpath : $homefolderpath"
echo "ipaexportpath : $ipaexportpath"
echo "bundleid : $bundleid"
echo "codesignidentity : $codesignidentity"
echo "provisioningprofile : $provisioningprofile"
echo "deploymenttarget : $deploymenttarget"
echo "exportoptionsplist : $exportoptionsplist"
echo "xcodeexportoptionfolderpath : $xcodeexportoptionfolderpath"
echo "currentdatetime : $currentdatetime"
echo "ipadname : $ipadname"
echo "archivefilename : $archivefilename"

echo "签包结束."
$SHELL

这里针对上面的自动化签包脚本,我们需要理解几个关键概念:

  1. 包名 — 我们平时说的包名,Unity里平台设置那里设置的包名
    Xcode里显示参考下图:
    XcodeBundleId
  2. 证书名字 — IOS开发打包需要的证书文件(比如:Development,Distribution证书)
    因为Apple Developer Account最近刚到期,无法截后台账号的图,这里只放一张Keychain里面显示已经安装在本地的Certificate图:
    KeyChainCertificates
    证书名字通过右键对应Certificate后选择Get Info可查看:
    CertificateDetail
  3. 描述文件 — 是指后台指定了Certificate,Apple Id, Device List等信息的描述文件
    ProvisioningProfile
    如果想详细查看里面的信息,可以通过下面这个命令:
1
security cmd -D -i *.mobileprovision

这里面就包含了很多信息,这里我们暂时需要通过这个找到描述文件的名字:
ProvisionProfileName

  1. IOS最低版本支持 — 这个是对应Xcode里设置Deployment Target的那个参数,表示打包支持的最低IOS版本
    DeploymentTarget
  2. 签名配置文件 — 关于导出IPA时的签名信息以及相关参数等的配置文件
    ExportOptionFile
    让我们看下里面的详细信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store</string>
<key>provisioningProfiles</key>
<dict>
<key>对应包名</key>
<string>对应描述文件名</string>
</dict>
<key>signingCertificate</key>
<string>***</string>
<key>signingStyle</key>
<string>manual</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>***</string>
<key>uploadSymbols</key>
<false/>
</dict>
</plist>

可以看到签名配置文件里面指定了导出类型(app-store or development等),证书名以及包名,描述文件信息等信息。
那么这个配置文件哪里来的了?
手动Archive后,我们会在*.ipa的同层文件目录下得到一个叫ExportOption.plist的文件,这个就是那个配置文件。

Note:
如果我们的程序开启了苹果相关的服务,比如Apple Wallet等,那这些服务会以参数的信息显示在上面的配置文件里,如果后台证书配置了使用特定服务,但该配置文件里没有,就会提示特定字段信息对不上或者遗漏而报错。

可以看出通过上面的自动化,我们可以命令行指定以下内容:

  1. 包名
  2. 证书
  3. 描述文件
  4. IOS最低支持版本号
  5. 签名证书

接下来我们看看xcodebuild编译里是如何指定这些信息并成功导出IPA的:

  1. 清理工程
1
2
3
4
xcodebuild  clean \                              # 指定了清理工程
-project Unity-iPhone.xcodeproj \ # 指定清理哪一个project
-configuration Release -alltargets \ # 指定清理Release下的所有targets
PRODUCT_BUNDLE_IDENTIFIER="$bundleid"# 指定了包名(这一个可能不需要)
  1. 编译打包
1
2
3
4
5
6
7
8
9
10
xcodebuild -project Unity-iPhone.xcodeproj \                # 指定打包哪个project
-scheme Unity-iPhone \ # 指定打包哪个scheme
-configuration Release \ # 指定编译Release
PRODUCT_BUNDLE_IDENTIFIER="$bundleid" \ # 指定包名
CODE_SIGN_STYLE="Manual" \ # 指定手动签名
CODE_SIGN_IDENTITY="$codesignidentity" \ # 指定证书
PROVISIONING_PROFILE="$provisioningprofile" \ # 指定描述文件
IPHONEOS_DEPLOYMENT_TARGET="$deploymenttarget" \# 指定IOS最低版本
-archivePath "${archivefilename}" \ # 指定生成的Archive文件路径
archive # 指定编译打包
  1. 导出IPA
1
2
3
4
xcodebuild -exportArchive \                                         # 指定导出IPA
-archivePath "${archivefilename}" \ # 指定Archive文件路径
-exportOptionsPlist "${xcodeexportoptionfolderpath}/${exportoptionsplist}" \ # 指定签名配置文件路径
-exportPath "${ipaexportpath}" # 指定IPA输出路径

上面的注释已经很详细了,这里就不在详细讲解了。
通过archive命令,我们得到了一个.xcarchive的文件。
ArchiveFile
通过这个
.xcarchive文件,我们可以通过指定签名配置文件导出我们想要的IPA文件。

Automatic:
待续……

References

http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/001374027586935cf69c53637d8458c9aec27dd546a6cd6000)

Jenkins Part

Groovy
Uniy3d Plugin
Jenkins——应用篇——插件使用——Mailer Plugin
Jenkins进阶系列之——01使用email-ext替换Jenkins的默认邮件通知
macOS 安装配置 Jenkins 持续集成 Unity 项目指南
MAC EI Capitan上更新系统自带SVN版本(关闭SIP方能sudo rm)
About System Integrity Protection on your Mac
Email-ext plugin

Shell Part

Unix shell
Shell脚本
Advanced Bash-Scripting Guide

Xcodebuild Part

Xcode 9 最新 最准确 的自动化打包
iOS系统提供开发环境下命令行编译工具:xcodebuild
Building from the Command Line with Xcode FAQ
Build settings reference

Other Part




文章目錄
  1. 1. Unity自动化打包
    1. 1.1. 版本控制
    2. 1.2. 打包
      1. 1.2.1. 多平台
      2. 1.2.2. 自动化
        1. 1.2.2.1. Jenkins
        2. 1.2.2.2. Jenkins实战
          1. 1.2.2.2.1. 自由风格构建系统
          2. 1.2.2.2.2. Pipeline
            1. 1.2.2.2.2.1. What
            2. 1.2.2.2.2.2. Why
            3. 1.2.2.2.2.3. How
            4. 1.2.2.2.2.4. Jenkinsfile
            5. 1.2.2.2.2.5. Problems
          3. 1.2.2.2.3.  Tools
            1. 1.2.2.2.3.1. Pipeline Tools
            2. 1.2.2.2.3.2. Email Plugin
            3. 1.2.2.2.3.3. Unity3D Plugin
        3. 1.2.2.3. Jenkins从零搭建
        4. 1.2.2.4. 脚本语言
          1. 1.2.2.4.1. bat
          2. 1.2.2.4.2. Shell
            1. 1.2.2.4.2.1. bash
          3. 1.2.2.4.3. Python(严格意义上不算脚本语言)
        5. 1.2.2.5. IOS自动化打包
          1. 1.2.2.5.1. IOS打包准备
          2. 1.2.2.5.2. XUPorter or Unity API(PBXFroject)
            1. 1.2.2.5.2.1. XUPoter
            2. 1.2.2.5.2.2. PBXProject
        6. 1.2.2.6. IOS自动化签包
  2. 2. References
    1. 2.1. Jenkins Part
    2. 2.2. Shell Part
    3. 2.3. Xcodebuild Part
    4. 2.4. Other Part