typescript

Typescript 2.0之后,tsd和typings都可以去掉了。要获得lodash的类型定义文件只需要

npm install @types/lodash

这样一来,typescript的工作流就和普通的Node.js项目没什么区别了。
更重要的是Typescript 2.1之后,async/await可以直接编译到ES5。babel什么的,再见吧

https://github.com/typings/typings

关于持续集成打包平台的Jenkins配置和构建脚本实现细节

关于iOS的构建

对iOS源码进行构建,目标是要生成.ipa文件,即iOS应用安装包。

当前,构建方式主要包括两种:

  • 源码 -> .archive文件 -> .ipa文件
  • 源码 -> .app文件 -> .ipa文件

这两种方式的主要差异是生成的中间产物不同,对应的,两种构建方式采用的命令也不同。

源码 -> .archive -> .ipa

1
2
3
4
5
6
7
8
# build archive file from source code
xcodebuild \ # xctool
-workspace ${WORKSPACE_PATH} \
-scheme ${SCHEME} \
-configuration ${CONFIGURATION} \
-sdk ${SDK}
-archivePath ${archive_path}
archive

archive:对编译结果进行归档,会生成一个.xcarchive的文件,位于-archivePath指定的目录中。需要注意的是,对模拟器类型的sdk无法使用archive命令。

1
2
3
4
5
6
7
8
# export ipa file from .archive
xcodebuild -exportArchive \
-exportFormat format \
-archivePath xcarchivepath \
-exportPath destinationpath \
-exportProvisioningProfile profilename \
[-exportSigningIdentity identityname]
[-exportInstallerIdentity identityname]

源码 -> .app -> .ipa

1
2
3
4
5
6
7
# build .app file from source code
xcodebuild \ # xctool
-workspace ${WORKSPACE_PATH} \
-scheme ${SCHEME} \
-configuration ${CONFIGURATION} \
-sdk ${SDK}
-derivedDataPath ${OUTPUT_FOLDER}
1
2
3
4
5
6
# convert .app file to ipa file
xcrun \
-sdk iphoneos \
PackageApplication \
-v ${OUTPUT_FOLDER}/Release-iphoneos/xxx.app \
-o ${OUTPUT_FOLDER}/Release-iphoneos/xxx.ipa

参数说明

xcodebuild/xctool参数

  • -workspace:需要打包的workspace,后面接的文件一定要是.xcworkspace结尾的;
  • -scheme:需要打包的Scheme,一般与$project_name相同;
  • -sdk:区分iphone device和Simulator,可通过xcodebuild -showsdks获取,例如iphoneosiphonesimulator9.3
  • -configuration:需要打包的配置文件,我们一般在项目中添加多个配置,适合不同的环境,Release/Debug;
  • -exportFormat:导出的格式,通常填写为ipa
  • -archivePath.xcarchive文件的路径;
  • -exportPath:导出文件(.ipa)的路径;
  • -exportProvisioningProfile:profile文件证书;
  • -derivedDataPath:指定编译结果文件的存储路径;例如,指定-derivedDataPath ${OUTPUT_FOLDER}时,将在项目根目录下创建一个${OUTPUT_FOLDER}文件夹,生成的.app文件将位于${OUTPUT_FOLDER}/Build/Products/${CONFIGURATION}-iphoneos中。

除了采用官方的xcodebuild命令,还可以使用由Facebook开发维护的xctoolxctool命令的使用方法基本与xcodebuild一致,但是输出的日志会清晰很多,而且还有许多其它优化,详情请参考xctool的官方文档。

xcrun参数

  • -v:指定.app文件的路径
  • -o:指定生成.ipa文件的路径

补充说明

1、获取Targets、Schemes、Configurations参数

在填写target/workspace/scheme/configuration等参数时,如果不知道该怎么填写,可以在项目根目录下执行xcodebuild -list命令,它会列出当前项目的所有可选参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
➜ Store_iOS git:(NPED) ✗ xcodebuild -list
Information about project “Store”:
Targets:
Store
StoreCI
Build Configurations:
Debug
Release
If no build configuration is specified and -scheme is not passed then “Release” is used.
Schemes:
Store
StoreCI

2、清除缓存文件

在每次build之后,工程目录下会遗留一些缓存文件,以便下次build时减少编译时间。然而,若因为工程配置错误等问题造成编译失败后,下次再编译时就可能会受到缓存的影响。

因此,在持续集成构建脚本中,比较好的做法是在每次build之前都清理一下上一次编译遗留的缓存文件。

1
2
3
4
5
6
# clean before build
xctool \
-workspace ${WORKSPACE_PATH} \
-scheme ${SCHEME} \
-configuration ${CONFIGURATION} \
clean

clean:清除编译产生的问题,下次编译就是全新的编译了

3、处理Cocoapod依赖库

另外一个需要注意的是,若项目是采用Cocoapod管理项目依赖,每次拉取最新代码后直接编译可能会报错。这往往是因为其他同事更新了依赖库(新增了第三方库或升级了某些库),而本地还采用之前的第三方库进行编译,从而会出现依赖库缺失或版本不匹配等问题。

应对的做法是,在每次build之前都更新一下Cocoapod。

1
2
3
4
# Update pod repository
pod repo update
# Install pod dependencies
pod install

4、修改编译包的版本号

通过持续集成打包,我们会得到大量的安装包。为了便于区分,比较好的做法是在App中显示版本号,并将版本号与Jenkins的BUILD_NUMBER关联起来。

例如,当前项目的主版本号为2.6.0,本次构建的BUILD_NUMBER为130,那么我们就可以将本次构建的App版本号设置为2.6.0.130。通过这种方式,我们可以通过App中显示的版本号快速定位到具体到构建历史,从而对应到具体的代码提交记录。

要实现对App版本号的设置,只需要在打包前对Info.plist文件中的CFBundleVersionCFBundleShortVersionString进行修改即可。在Python中,利用plistlib库可以很方便地实现对Info.plist文件的读写。

5、模拟器运行

如果持续集成测试是要运行在iOS模拟器上,那么就需要构建生成.app文件。

在前面讲解的两种构建方式中,中间产物都包含了.app文件。对于以.xcarchive为中间产物的方式,生成的.app文件位于output_dir/StoreCI_Release.xcarchive/Products/Applications/目录中。

不过,这个.app文件在模拟器中还无法直接运行,还需要在Xcode中修改Supported Platforms,例如,将iphoneos更改为iOS。详细原因请参考《从0到1搭建移动App功能自动化测试平台(1):模拟器中运行iOS应用》

关于Android的构建

待续

关于构建脚本

对于构建脚本(build.py)本身,源码应该是最好的说明文档。

build.py脚本中,主要实现的功能就四点:

  • 执行构建命令,编译生成.ipa文件,这部分包含了关于iOS的构建部分的全部内容;
  • 构建时动态修改Info.plist,将编译包的版本号与Jenkins的BuildNumber关联起来;
  • 上传.ipa文件至pyger/fir.im平台,并且做了失败重试机制;
  • 解析pyger/fir.im平台页面中的二维码,将二维码图片保存到本地。

需要说明的是,对于构建任务中常用的可配置参数,例如BRANCH/SCHEME/CONFIGURATION/OUTPUT_FOLDER等,需要在构建脚本中通过OptionParser的方式实现可传参数机制。这样我们不仅可以命令行中通过传参的方式灵活地调用构建脚本,也可以在Jenkins中实现参数传递。

之所以强调常用的可配置参数,这是为了尽可能减少参数数目,降低脚本调用的复杂度。像PROVISIONING_PROFILEpgyer/fir.im账号这种比较固定的配置参数,就可以写死在脚本中。因此,在使用构建脚本(build.py)之前,需要先在脚本中配置下PROVISIONING_PROFILEpgyer/fir.im账号。

另外还想多说一句,pyger/fir.im这类第三方平台在为我们提供便利的同时,稳定性不可控也是一个不得不考虑的问题。在我使用pgyer平台期间,就遇到了平台服务变动、接口时而不稳定出现502等问题。因此,最好的方式还是自行搭建一套类似的服务,反正我是打算这么做了。

Jenkins的详细配置

对于Jenkins的详细配置,需要补充说明的有四点。

1、参数的传递

在构建脚本中,我们已经对常用的可配置参数实现了可传参机制。例如,在Terminal中可以通过如下形式调用构建脚本。

1
$ python build.py –scheme SCHEME –workspace Store.xcworkspace –configuration CONFIGURATION –output OUTPUT_FOLDER

那么我们在Jenkins中要怎样才能指定参数呢?

实际上,Jenkins针对项目具有参数化的功能。在项目的配置选项中,勾选This project is parameterized后,就可以为当前project添加多种类型的参数,包括:

  • Boolean Parameter
  • Choice Parameter
  • Credentials Parameter
  • File Parameter
  • Multi-line String Parameter
  • Password Parameter
  • Run Parameter
  • String Parameter

通常,我们可以选择使用String Parameter来定义自定义参数,并可对每个参数设置默认值。

当我们配置了BRANCHSCHEMECONFIGURATIONOUTPUT_FOLDERBUILD_VERSION这几个参数后,我们就可以在Build配置区域的Execute shell通过如下形式来进行参数传递。

1
2
3
4
5
6
$ python ${WORKSPACE}/Build_scripts/build.py \
–scheme ${SCHEME} \
–workspace ${WORKSPACE}/Store.xcworkspace \
–configuration ${CONFIGURATION} \
–output ${WORKSPACE}/${OUTPUT_FOLDER} \
–build_version ${BUILD_VERSION}.${BUILD_NUMBER}

可以看出,参数的传递方式很简单,只需要预先定义好了自定义参数,然后就可以通过${Param}的形式来进行调用了。

不过你也许会问,WORKSPACEBUILD_NUMBER这两个参数我们并未进行定义,为什么也能进行调用呢?这是因为Jenkins自带部分与项目相关的环境变量,例如BRANCH_NAMEJOB_NAME等,这部分参数可以在shell脚本中直接进行调用。完整的环境变量可在Jenkins_Url/env-vars.html/中查看。

配置完成后,就可以在Build with Parameters中通过如下形式手动触发构建。

Jenkins manul build

2、修改build名称

Build History列表中,构建任务的名称默认显示为按照build次数递增的BUILD_NUMBER。有时候我们可能想在build名称中包含更多的信息,例如包含当次构建的SCHEMECONFIGURATION,这时我们就可以通过修改BuildName实现。

Jenkins默认不支持BuildName设置,但可通过安装build-name-setter插件进行实现。安装build-name-setter插件后,在配置页面的Build Environment栏目下会出现Set Build Name配置项,然后在Build Name中就可以通过环境变量参数来设置build名称。

例如,要将build名称设置为上面截图中的StoreCI_Release_#130样式,就可以在Build Name中配置为${SCHEME}_${CONFIGURATION}_#${BUILD_NUMBER}

除了在Build Name中传递环境变量参数,build-name-setter还可以实现许多更加强大的自定义功能,大家可自行探索。

3、展示二维码图片

然后再说下如何在Build History列表中展示每次构建对应的二维码图片。

Jenkins build history

需要说明的是,在上图中,绿色框对应的内容是BuildName,我们可以通过build-name-setter插件来实现自定义配置;但是红色框已经不在BuildName的范围之内,而是对应的BuildDescription

同样地,Jenkins默认不支持在构建过程中自动修改BuildDescription,需要通过安装description setter plugin插件来辅助实现。安装description setter plugin插件后,在配置页面的Build栏目下,Add build step中会出现Set build description配置项,添加该配置项后就会出现如下配置框。

Jenkins set build description

该功能的强大之处在于,它可以在构建日志中通过正则表达式来匹配内容,并将匹配到的内容添加到BuildDescription中去。

例如,我们想要展示的二维码图片是在每次构建过程中生成的,因此我们首先要获取到二维码图片文件。

我的做法是,在build.py中将蒲公英平台返回的应用下载页面地址和二维码图片地址打印到log中。

1
2
3
appDownloadPage: https://www.pgyer.com/035aaf10acf5dd7c279c4fe423a57674
appQRCodeURL: https://o1wjx1evz.qnssl.com/app/qrcodeHistory/fe7a8c9051f0c7fc0affc78f40c20a4b5e4bdb4c77b91a29501f55fd9039c659
Save QRCode image to file: /Users/Leo/.jenkins/workspace/DebugTalk_Plus_Store_iOS/build_outputs/QRCode.png

然后,在Set build description配置项的Regular expression就可以按照如下正则表达式进行匹配:

1
appDownloadPage: (.*)$

接下来,就可以在Description中对匹配到的结果进行引用。

1

在这里,我们用到了HTML的标签,而Jenkins的Markup Formatter默认是采用Plain text模式,因此还需要对Jenkins对系统配置进行修改,在《使用Jenkins搭建iOS/Android持续集成打包平台》中已进行了详细说明,在此就不再重复。

通过以上方式,就可以实现前面图片中的效果。

4、收集编译成果物

在上面讲解的展示二维码图片一节中,用到了${BUILD_URL}artifact/build_outputs/QRCode.png一项,这里的URL就是用到了编译成果物收集后保存的路径。

Archives build artifacts是Jenkins默认自带的功能,无需安装插件。该功能在配置页面的Post-build Actions栏目下,在Add post-build action的列表中选择添加Archives build artifacts

添加后的配置页面如下图所示:

Jenkins archive the artifacts

通常,我们只需要配置Files to archive即可。定位文件时,可以通过正则表达式进行匹配,也可以调用项目的环境变量;多个文件通过逗号进行分隔。

例如,假如我们想收集QRCode.pngStoreCI_Release.ipaInfo.plist这三个文件,那么我们就可以通过如下表达式来进行指定。

1
${OUTPUT_FOLDER}/*.ipa,${OUTPUT_FOLDER}/QRCode.png,${OUTPUT_FOLDER}/*.xcarchive/Info.plist

当然,目标文件的具体位置是我们在构建脚本(build.py)中预先进行处理的。

通过这种方式,我们就可以实现在每次完成构建后将需要的文件收集起来进行存档,以便后续在Jenkins的任务页面中进行下载。

show artifacts of Jenkins

也可以直接通过归档文件的URL进行访问。例如,上图中QRCode.png的URL为Jenkins_Url/job/JenkinsJobName/131/artifact/build_outputs/QRCode.png,而Jenkins_Url/job/JenkinsJobName/131/即是${BUILD_URL},因此可以直接通过${BUILD_URL}artifact/build_outputs/QRCode.png引用。

总结

至此,《使用Jenkins搭建iOS/Android持续集成打包平台》一文中涉及到的Jenkins配置和构建脚本实现细节均已补充完毕了。相信大家结合这两篇文章,应该会对如何使用Jenkins搭建iOS/Android持续集成打包平台的基础概念和实现细节都有一个比较清晰的认识。

对于还未完善的部分,我后续将在博客中进行更新。

操作手册请参考文章末尾的【开箱即用】部分,祝大家玩得愉快!

开箱即用

GitHub地址:https://github.com/debugtalk/JenkinsTemplateForApp

1、添加构建脚本

  • 在构建脚本中配置PROVISIONING_PROFILEpgyer/fir.im账号;
  • 在目标构建代码库的根目录中,创建Build_scripts文件夹,并将build.py拷贝到Build_scripts中;
  • Build_scripts/build.py提交到项目中。

除了与Jenkins实现持续集成,构建脚本还可单独使用,使用方式如下:

1
2
3
4
5
$ python ${WORKSPACE}/Build_scripts/build.py \
true–scheme ${SCHEME} \
–workspace ${WORKSPACE}/Store.xcworkspace \
–configuration ${CONFIGURATION} \
–output ${WORKSPACE}/${OUTPUT_FOLDER}

2、运行jenkins,安装必备插件

1
$ nohup java -jar jenkins_located_path/jenkins.war &

3、创建Jenkins Job

  • 在Jenkins中创建一个Freestyle project类型的Job,先不进行任何配置;
  • 然后将config.xml文件拷贝到~/.jenkins/jobs/YourProject/中覆盖原有配置文件,重启Jenkins;
  • 完成配置文件替换和重启后,刚创建好的Job就已完成了大部分配置;
  • Job Configure中根据项目实际情况调整配置,其中Git Repositories是必须修改的,其它配置项可选择性地进行调整。

4、done!

Unityaction vs Method

 void Awake() {
         action = new UnityAction (Func);
         action += Funcb;
         gameObject.GetComponent<Button> ().onClick.AddListener (action);
         gameObject.GetComponent<Button> ().onClick.AddListener (Func);
         gameObject.GetComponent<Button> ().onClick.AddListener (Funcb);
         gameObject.GetComponent<Button> ().onClick.RemoveListener (Func);
         gameObject.GetComponent<Button> ().onClick.RemoveListener (Funcb);
     }

 

Unityaction可以绑定多个动作….如果是一个匿名函数..可以使用unityaction..方便remove

 

 

 

在unity向量空间内通过将极坐标转换为直角坐标,绘制阿基米德螺线,对数螺线与玫瑰线等几何图形

极坐标内的每个点都有两个参数: r, 与 θ。r为此点到极点(中心点)的距离,θ 为此点到极点的线段与极轴(类似x轴)的夹角。

很多几何图形公式都可以用极坐标简洁的表示,例如:

阿基米德螺旋线:(公式1) r=a b*θ
这里写图片描述

对数螺旋线:(公式2)r=aebθ
这里写图片描述

心脏线:(公式3)r=a(1sinθ)
这里写图片描述
玫瑰线:(公式4)r=cos(kθ) c
这里写图片描述

以上的a,b,c,k都是常数,用来控制图形的形状。
已知了r 与 θ 的关系,只要依次算出θ从0至2π相应的r的值,我们就能得出一个连续的极坐标(r, θ)数组。

极坐标与直角坐标的转换
公式:(公式5)x=rcos(θ) y=rsin(θ)

将以上信息转化为代码既是:

public Vector3[] GeneratePolarRoses(int points,Vector3 centre,float k,float c,float scale){

        Vector3[] coordinates=new Vector3[points]; 
        //将2π均分为points份,并依次赋予theta
        float theta=2f*Mathf.PI/points;
        float radius;
        for(int t=0;t<points;t  ){
            //公式4转换为代码,算出theta从0至2π每个角度的r值
            radius=Mathf.Cos(k*theta) c;
            //公式5转换为代码,既是将极坐标转换为直角坐标
            coordinates[t]=new Vector3(scale*radius*Mathf.Cos(theta) centre.x,scale*radius*Mathf.Sin(theta) centre.y,0f);
            theta =2f*Mathf.PI/points;
        }
        return coordinates;
    }

以上函数将会返回一个玫瑰线坐标点数组,参数points为数组长度,centre为图形中心点坐标。参数k必须为整数,当为奇数时,花瓣数量为k,当为偶数时,花瓣数量为2k。参数c也会影响图形形状,当为0-1之间时,将会产生类似花蕊的效果。参数scale可以设置图形整体大小。

阿基米德螺旋线与对数螺旋线的思路基本相同,唯一不同的是,需要增加一个参数设置螺旋的圈数。

    public Vector3[] GenerateArchimedeanSpirals(int points,Vector3 centre,int circles,float a,float b){

        Vector3[] coordinates=new Vector3[points]; 

        float radius;
        //调整角度的分配模式,circles为螺旋的圈数
        float theta=2f*Mathf.PI/points*circles;
        for(int t=0;t<points;t  ){
            //公式1
            radius=a b*theta;   
            //公式5   
            coordinates[t]=new Vector3(radius*Mathf.Cos(theta) centre.x,radius*Mathf.Sin(theta) centre.y,0f);
            theta =2f*Mathf.PI/points*circles;
        }
        return coordinates;
    }

对数螺线

    public Vector3[] GenerateLogarithmicSpirals(int points,Vector3 centre,int circles,float a,float b){

        Vector3[] coordinates=new Vector3[points]; 

        float radius;
        float theta=2f*Mathf.PI/points*circles;
        for(int t=0;t<points;t  ){
            //公式2
            radius=a*Mathf.Exp(b*theta);
            coordinates[t]=new Vector3(radius*Mathf.Cos(theta) centre.x,radius*Mathf.Sin(theta) centre.y,0f);
            theta =2f*Mathf.PI/points*circles;
            scales[t]=radius;
        }
        return coordinates;
    }

对数螺旋线
上图:一个a为0.1,b为0.1,共13圈的对数螺线。

Canvas控件——CanvasGroup

Canvas Group可以用来控制一组不需要个别控制的UI元素的某些方面,CanvasGroup的属性会影响他所有children的GameObject
img
其中有四个选项:
-Alpha:这个选项很多组件都有,用处也是一样的,在美术中,这个叫做Alpha通道的东东是用来控制透明度的,他的值从0到1.0是完全透明,1是完全不透明;
-Interactable确认该组件是否接受输入,当他被设置为false时,交互功能将被禁用;
-Block Raycasts是否让该组件像collider一样接受射线检测?你需要在依赖于Canvas的图形射线检测者上唤醒射线检测方法。这个不会作用于Physics.Raycast;
-Ignore Parent Groups(忽略父级团)是否响应父级group的方法
细节:
Canvas Group的经典使用:
-在窗口的GameObject上添加一个CanvasGroup,通过控制它的Alpha值来淡入或淡出整个窗口;
-通过给父级GameObject添加一个CanvasGroup并设置它的Interactable值为false来制作一整套没有交互(灰色)的控制;
-通过将元素或元素的一个父级添加CanvasGroup并设置BlockRaycasts值为false来制作一个或多个不阻止鼠标事件的UI元素
应用:(重要的地方写大字)
结合后面两点或者1,3点,都可以实现很牛叉的功能
比如说游戏里某些情况某个按钮(或者其他UI)是不能点的,而另外一些情况可以点,这样就可以通过动态改变这个组件的BlocksRaycasts值以及Interactable来实现
再比如说游戏里点击某个按钮要让这个按钮不可点并逐渐消失掉,当然啦,也可以让别的东西消失啦,这就可以通过改变alpha值来实现