项目中测试,开发,本地各种环境每次都需要注释或者手动切换环境,每天都需要打各种不同环境的包,这些重复而繁琐的工作,有什么方法可以避免呢? 本文或许可以帮到你。
一,新建项目
1.1 这里以Autobuild为例
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
1,4 选择第二个Duplicate Only,复制单个
1.5 将Autobuild-live copy更改为Autobuild-live-dev
1,6 重复上述步骤,如下图依次创建并改名为
Autobuild-live-live(发布)
Autobuild-live-dev(开发)
Autobuild-live-test(测试)
1.7 将生成的info.plist文件统一放入Plist文件并命名,不同的info.plist文件在右侧面板选择不同的TARGET
1.8 Bulid Settings下配置所有TARGET Info.plist的路径,确保切换所有TARGET均无报错
$(PROJECT_DIR)/Autobuild/Plist/Autobuild-xxx-Info.plist
1.9 选择shemes, 点击Manage Schemes
2.0 选中所有shemes,点击 一 删除,依次重新创建对应每一个TARGET的shemes
2.1 操作完成后如下图,注意勾选右侧Shared共享,不然脚本没有权限读取相应的scheme权限。
2.2 到这里三个TARGET(发布,开发,测试)已部署完毕,注意以后往项目拖入文件,如果需要所有环境都能读取,需要勾选所有TARGET,否则切换到对应的TARGET会报找不到文件的错误!
二,配置预编译宏
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
2.1 确保所有环境预编译宏均配置正确
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文件夹复制到项目根目录下
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
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 **
3.6 成功后会在上面3.1下设置 EXPORT_MAIN_DIRECTORY的路径下生成对应的.ipa文件
四,注意
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()