iOS多target下的自動構建

weixin_33912445發表於2017-05-08

專案中測試,開發,本地各種環境每次都需要註釋或者手動切換環境,每天都需要打各種不同環境的包,這些重複而繁瑣的工作,有什麼方法可以避免呢? 本文或許可以幫到你。

一,新建專案

1.1 這裡以Autobuild為例


4490624-7fc861e55ce42ab6.png
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


4490624-ae8e5a89655e7f73.png
02.png

1,4 選擇第二個Duplicate Only,複製單個


4490624-ce20cad9dffd7bc0.png
03.png

1.5 將Autobuild-live copy更改為Autobuild-live-dev


4490624-b5336617ec4e33d9.png
04.png

1,6 重複上述步驟,如下圖依次建立並改名為
Autobuild-live-live(釋出)
Autobuild-live-dev(開發)
Autobuild-live-test(測試)


4490624-35032f93600fb9de.png
05.png

1.7 將生成的info.plist檔案統一放入Plist檔案並命名,不同的info.plist檔案在右側皮膚選擇不同的TARGET

4490624-1c38b5599b2b9d33.png
06.png

1.8 Bulid Settings下配置所有TARGET Info.plist的路徑,確保切換所有TARGET均無報錯

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

4490624-1274b628d736412a.png
07.png

1.9 選擇shemes, 點選Manage Schemes


4490624-9263f44abfb95fb7.png
08.png

2.0 選中所有shemes,點選 一 刪除,依次重新建立對應每一個TARGET的shemes

4490624-124f8effaf27fb4e.png
09.png

2.1 操作完成後如下圖,注意勾選右側Shared共享,不然指令碼沒有許可權讀取相應的scheme許可權。


4490624-5a6a56070cdf2c1a.png
10.png

2.2 到這裡三個TARGET(釋出,開發,測試)已部署完畢,注意以後往專案拖入檔案,如果需要所有環境都能讀取,需要勾選所有TARGET,否則切換到對應的TARGET會報找不到檔案的錯誤!

4490624-b5e2358a5295cf45.png
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

4490624-717c5a0eb9c4509e.png
12.png

2.1 確保所有環境預編譯巨集均配置正確


4490624-6dd97167decbabee.png
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資料夾複製到專案根目錄下

4490624-b2c012f331adc7bb.png
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

4490624-e5e114db4a338ca3.png
15.png
4490624-c8cd737b4843fc18.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 **

4490624-9d950118996606d3.png
17.png

3.6 成功後會在上面3.1下設定 EXPORT_MAIN_DIRECTORY的路徑下生成對應的.ipa檔案


4490624-c4fb70e6997da952.png
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/

相關文章