iOS多target下的自动构建

作者: paintingStyle | 来源:发表于2017-05-08 23:27 被阅读519次

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

    一,新建项目

    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/

    相关文章

      网友评论

      • 梧雨北辰:判断target的时候报错:Invalid token at start of a preprocessor expression,请问是否遇到这个问题?
        paintingStyle:@风恣 客气:relaxed:
        梧雨北辰:@paintingStyle 是的,我已经找到原因了,的确是预编译宏手误写错了,谢谢哈
        paintingStyle:判断target报错,我使用中并未遇到此错误,请检查你的预编译宏是否配置正确,这种错误一般是预编译判断有必须运行的代码,在预编译阶段,代码并未运行出错。
      • 春田花花幼儿园:赞!
        paintingStyle:@春田花花幼儿园 :wink:

      本文标题:iOS多target下的自动构建

      本文链接:https://www.haomeiwen.com/subject/vprptxtx.html