iOS多target下的自动构建

项目中测试,开发,本地各种环境每次都需要注释或者手动切换环境,每天都需要打各种不同环境的包,这些重复而繁琐的工作,有什么方法可以避免呢? 本文或许可以帮到你。

一,新建项目

1.1 这里以Autobuild为例


01.png

1.2 配置info.plist文件,后续复制TARGET会依据此配置


<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>

<key>NSCameraUsageDescription</key>
<string>App需要您的同意,才能访问相机</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>App需要您的同意,才能始终访问位置</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>App需要您的同意,才能在使用期间访问位置</string>
<key>NSMicrophoneUsageDescription</key>
<string>App需要您的同意,才能访问麦克风</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>App需要您的同意,才能访问相册</string>

1.3 创建不同的TARGET时,有个细节需要注意,那就是你的项目如果是xcworkspace时,先pod install后再创建三个TARGET(发布,开发,测试),选中当前TARGET,右键Duplicate复制TARGET


02.png

1,4 选择第二个Duplicate Only,复制单个


03.png

1.5 将Autobuild-live copy更改为Autobuild-live-dev


04.png

1,6 重复上述步骤,如下图依次创建并改名为
Autobuild-live-live(发布)
Autobuild-live-dev(开发)
Autobuild-live-test(测试)


05.png

1.7 将生成的info.plist文件统一放入Plist文件并命名,不同的info.plist文件在右侧面板选择不同的TARGET

06.png

1.8 Bulid Settings下配置所有TARGET Info.plist的路径,确保切换所有TARGET均无报错

$(PROJECT_DIR)/Autobuild/Plist/Autobuild-xxx-Info.plist

07.png

1.9 选择shemes, 点击Manage Schemes


08.png

2.0 选中所有shemes,点击 一 删除,依次重新创建对应每一个TARGET的shemes

09.png

2.1 操作完成后如下图,注意勾选右侧Shared共享,不然脚本没有权限读取相应的scheme权限。


10.png

2.2 到这里三个TARGET(发布,开发,测试)已部署完毕,注意以后往项目拖入文件,如果需要所有环境都能读取,需要勾选所有TARGET,否则切换到对应的TARGET会报找不到文件的错误!

11.png

二,配置预编译宏

2.0 Build Setting –> Preprocessor Macros 配置预编译宏,这里命名为STRUCTURE_ENVIRONMENT,Debug与Release模块均需要配置

Autobuild-live-live(发布) STRUCTURE_ENVIRONMENT=0
Autobuild-live-dev(开发) STRUCTURE_ENVIRONMENT=1
Autobuild-live-test(测试) STRUCTURE_ENVIRONMENT=2

12.png

2.1 确保所有环境预编译宏均配置正确


13.png

2.2 新建ServiceApIConst类, 添加测试代码,注意勾选所有环境TARGET
#import <Foundation/Foundation.h>

@interface ServiceApIConst : NSObject

extern NSString *const ServiceApI_Url;
extern NSString *const ServiceWeb_Url;

@end


@implementation ServiceApIConst

/**
 环境切换需使用预编译宏配置STRUCTURE_ENVIRONMENT
 环境统一配置名为:STRUCTURE_ENVIRONMENT =  0为生产环境、1为开发环境、2为测试环境
 */

#ifdef STRUCTURE_ENVIRONMENT

#if STRUCTURE_ENVIRONMENT == 0

NSString *const ServiceApI_Url = @"http://www.api.releaseUrl.xxxxx";
NSString *const ServiceWeb_Url = @"http://www.web.releaseUrl.xxxxx";

#elif STRUCTURE_ENVIRONMENT == 1

NSString *const ServiceApI_Url = @"http://www.api.developmentUrl.xxxxx";
NSString *const ServiceWeb_Url = @"http://www.web.developmentUrl.xxxxx";

#elif STRUCTURE_ENVIRONMENT == 2

NSString *const ServiceApI_Url = @"http://www.api.testUrl.xxxxx";
NSString *const ServiceWeb_Url = @"http://www.web.testUrl.xxxxx";

#endif

#else

#warning"为找到匹配的预编译宏"

#endif

@end

2.3 测试输入结果

#import "ViewController.h"
#import "ServiceApIConst.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    
    [super viewDidLoad];

    NSLog(@"ServiceApI_Url:  %@",ServiceApI_Url);
    NSLog(@"ServiceWeb_Url:  %@",ServiceWeb_Url);
}

@end

2.4 依次切换TARGET,控制台打印为

2017-05-08 21:40:17.442 Autobuild-live[17950:338866] ServiceApI_Url:  http://www.api.releaseUrl.xxxxx
2017-05-08 21:40:17.442 Autobuild-live[17950:338866] ServiceWeb_Url:  http://www.web.releaseUrl.xxxxx

2017-05-08 21:40:56.082 Autobuild-dev[18015:340799] ServiceApI_Url:  http://www.api.developmentUrl.xxxxx
2017-05-08 21:40:56.082 Autobuild-dev[18015:340799] ServiceWeb_Url:  http://www.web.developmentUrl.xxxxx

2017-05-08 21:41:26.510 Autobuild-test[18082:342321] ServiceApI_Url:  http://www.api.testUrl.xxxxx
2017-05-08 21:41:26.510 Autobuild-test[18082:342321] ServiceWeb_Url:  http://www.web.testUrl.xxxxx

三,自动打包

3.0 脚本地址:https://github.com/carya/Util.git 解压过后将autobuild文件夹复制到项目根目录下

14.png

3.1 复制autobuild.py,分别命名为autobuild-dev.py,autobuild-test.py,分别打开对应的.py文件修改如下内容

CONFIGURATION = "Release"

.ipa包输出路径
autobuild.py:  EXPORT_MAIN_DIRECTORY = "~/Desktop/ipa-live/"
autobuild-dev.py:  EXPORT_MAIN_DIRECTORY = "~/Desktop/ipa-dev/"
autobuild-test.py:  EXPORT_MAIN_DIRECTORY = "~/Desktop/ipa-test/"

如果不需要上传到蒲公英,将 def uploadIpaToPgyer(ipaPath):代码块内的代码注释

3.2 配置exportOptions.plist

method表示包的用途,上线使用app-store 测试使用 ad-hoc,企业使用 enterprise,默认值为development,需要配置不同的证书环境。

<?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>compileBitcode</key>
<false/>
<key>method</key>
<string>ad-hoc</string>
<key>uploadBitcode</key>
<false/>
<key>uploadSymbols</key>
<false/>
</dict>
</plist>

3.3 配置证书,更改scheme为Release

15.png
16.png

3.4 确保所有证书配置完毕,关闭Xcode,终端cd到autobuildScript下,根据项目类型选择下列命令

//xcodeproj项目:
python autobuild.py -p ../youproject.xcodeproj -s schemename
//xcworkspace项目
python autobuild.py -w ../youproject.xcworkspace -s schemename

这里项目为Autobuild.xcodeproj,你需要指定项目与scheme的名称,这里使用发布环境TARGET对应的Autobuild-live,终端执行如下命令,注意这里的autobuild.py为上面配置好对应环境的.py文件,如你需要打测试包,需要使用
python autobuild-test.py ...... 并且将-s后的scheme名称替换为Autobuild-test

//先切换使用系统的rvm,否则会报错 error: exportArchive: No applicable devices found
命令1: [[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm" && rvm use system

命令2: python autobuild.py -p ../Autobuild.xcodeproj -s Autobuild-live

3.5 打包成功,终端输出** EXPORT SUCCEEDED **

17.png

3.6 成功后会在上面3.1下设置 EXPORT_MAIN_DIRECTORY的路径下生成对应的.ipa文件


18.png

四,注意

4.1 拖入项目的文件如果所有环境都要使用,勾选所有TARGET
4.2 由于切换到系统的rvm会导致pod命令失效或报错,重启终端即可
4.3 使用了三个TARGET,每个TARGET都需要在Podfile文件配置对应的cocopods库

# Uncomment the next line to define a global platform for your project
platform :ios, '8.0'
# ignoring warning from pods
inhibit_all_warnings!

target 'Autobuild-dev' do
 pod 'AFNetworking', '3.1.0'

end

target 'Autobuild-live' do
  pod 'AFNetworking', '3.1.0'

end

target 'Autobuild-test' do
 pod 'AFNetworking', '3.1.0'

end

五,补充

4.1 报错找不到request module.

import requests
ImportError: No module named requests
使用 $ sudo pip install requests或者sudo easy_install -U requests

4.2 查看 project 中的 targets 和 configurations,或者 workspace 中 schemes

xcodebuild -list

4.3 exportOptions.plist文件配置详细信息

xcodebuild --help

4.4 脚本示例
#!/usr/bin/env python
# -- coding:utf-8 --

#./autobuild.py -p youproject.xcodeproj -s schemename
#./autobuild.py -w youproject.xcworkspace -s schemename

import argparse
import subprocess
import requests
import os
import datetime

#configuration for iOS build setting
CONFIGURATION = "Release"
EXPORT_OPTIONS_PLIST = "exportOptions.plist"

#要打包的TARGET名字
TARGET = 'ipa-live'

#存放路径以时间命令
DATE = datetime.datetime.now().strftime('%Y-%m-%d_%H.%M.%S')

#1,会在桌面创建输出ipa文件的目录
EXPORT_MAIN_DIRECTORY = "~/Desktop/" + TARGET + DATE

# configuration for pgyer http://www.pgyer.com/doc/api
PGYER_UPLOAD_URL = "http://www.pgyer.com/apiv1/app/upload"
DOWNLOAD_BASE_URL = "http://www.pgyer.com"
USER_KEY = "xxxxx"
API_KEY = "x'x'x'x"
#设置从蒲公英下载应用时的密码
PYGER_PASSWORD = ""

def cleanArchiveFile(archiveFile):
cleanCmd = "rm -r %s" %(archiveFile)
process = subprocess.Popen(cleanCmd, shell = True)
process.wait()
print "cleaned archiveFile: %s" %(archiveFile)


def parserUploadResult(jsonResult):
resultCode = jsonResult['code']
if resultCode == 0:
downUrl = DOWNLOAD_BASE_URL +"/"+jsonResult['data']['appShortcutUrl']
print "Upload Success"
print "DownUrl is:" + downUrl
else:
print "Upload Fail!"
print "Reason:"+jsonResult['message']

def uploadIpaToPgyer(ipaPath):
print "成功"

#注释蒲公英上传
#def uploadIpaToPgyer(ipaPath):
#    print "ipaPath:"+ipaPath
#    ipaPath = os.path.expanduser(ipaPath)
#    ipaPath = unicode(ipaPath, "utf-8")
#    files = {'file': open(ipaPath, 'rb')}
#    headers = {'enctype':'multipart/form-data'}
#    payload = {'uKey':USER_KEY,'_api_key':API_KEY,'publishRange':'2','isPublishToPublic':'2', 'password':PYGER_PASSWORD}
#    print "uploading...."
#    r = requests.post(PGYER_UPLOAD_URL, data = payload ,files=files,headers=headers)
#    if r.status_code == requests.codes.ok:
#        result = r.json()
#            parserUploadResult(result)
#    else:
#        print 'HTTPError,Code:'+r.status_code



#创建输出ipa文件路径: ~/Desktop/{scheme}{2016-12-28_08-08-10}
def buildExportDirectory(scheme):
dateCmd = 'date "+%Y-%m-%d_%H-%M-%S"'
process = subprocess.Popen(dateCmd, stdout=subprocess.PIPE, shell=True)
(stdoutdata, stderrdata) = process.communicate()
exportDirectory = "%s" %(EXPORT_MAIN_DIRECTORY)
return exportDirectory

def buildArchivePath(tempName):
process = subprocess.Popen("pwd", stdout=subprocess.PIPE)
(stdoutdata, stderrdata) = process.communicate()
archiveName = "%s.xcarchive" %(tempName)
archivePath = stdoutdata.strip() + '/' + archiveName
return archivePath

def getIpaPath(exportPath):
cmd = "ls %s" %(exportPath)
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
(stdoutdata, stderrdata) = process.communicate()
ipaName = stdoutdata.strip()
ipaPath = exportPath + "/" + ipaName
return ipaPath

def exportArchive(scheme, archivePath):
exportDirectory = buildExportDirectory(scheme)
exportCmd = "xcodebuild -exportArchive -archivePath %s -exportPath %s -exportOptionsPlist %s" %(archivePath, exportDirectory, EXPORT_OPTIONS_PLIST)
process = subprocess.Popen(exportCmd, shell=True)
(stdoutdata, stderrdata) = process.communicate()

signReturnCode = process.returncode
if signReturnCode != 0:
print "export %s failed" %(scheme)
return ""
else:
return exportDirectory

def buildProject(project, scheme):
archivePath = buildArchivePath(scheme)
print "archivePath: " + archivePath
archiveCmd = 'xcodebuild -project %s -scheme %s -configuration %s archive -archivePath %s -destination generic/platform=iOS' %(project, scheme, CONFIGURATION, archivePath)
process = subprocess.Popen(archiveCmd, shell=True)
process.wait()

archiveReturnCode = process.returncode
if archiveReturnCode != 0:
print "archive workspace %s failed" %(workspace)
cleanArchiveFile(archivePath)
else:
exportDirectory = exportArchive(scheme, archivePath)
cleanArchiveFile(archivePath)
if exportDirectory != "":
ipaPath = getIpaPath(exportDirectory)
uploadIpaToPgyer(ipaPath)

def buildWorkspace(workspace, scheme):
archivePath = buildArchivePath(scheme)
print "archivePath: " + archivePath
archiveCmd = 'xcodebuild -workspace %s -scheme %s -configuration %s archive -archivePath %s -destination generic/platform=iOS' %(workspace, scheme, CONFIGURATION, archivePath)
process = subprocess.Popen(archiveCmd, shell=True)
process.wait()

archiveReturnCode = process.returncode
if archiveReturnCode != 0:
print "archive workspace %s failed" %(workspace)
cleanArchiveFile(archivePath)
else:
exportDirectory = exportArchive(scheme, archivePath)
cleanArchiveFile(archivePath)
if exportDirectory != "":
ipaPath = getIpaPath(exportDirectory)
uploadIpaToPgyer(ipaPath)

def xcbuild(options):
project = options.project
workspace = options.workspace
scheme = options.scheme

if project is None and workspace is None:
pass
elif project is not None:
buildProject(project, scheme)
elif workspace is not None:
buildWorkspace(workspace, scheme)

def main():

parser = argparse.ArgumentParser()
parser.add_argument("-w", "--workspace", help="Build the workspace name.xcworkspace.", metavar="name.xcworkspace")
parser.add_argument("-p", "--project", help="Build the project name.xcodeproj.", metavar="name.xcodeproj")
parser.add_argument("-s", "--scheme", help="Build the scheme specified by schemename. Required if building a workspace.", metavar="schemename")

options = parser.parse_args()

print "options: %s" % (options)

xcbuild(options)

if __name__ == '__main__':
main()

参考文章:http://liumh.com/2015/11/25/ios-auto-archive-ipa/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容