iOS AD-HOC 自己打包分发 内部测试

下载页面

<!DOCTYPE html>  
<html>  
<head>  
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
  <title>游戏Demo</title>  
</head>  
<body>  
<h1>1. iOS安装 <a href="https://xxxx/iOS/xxx_public.crt">证书</a><br/>(如果已经有https站点,这步跳过,如果之前尚未安装过此证书,请先安装证书)</h1>
<br/>  
<h1>2. iOS安装 <a href="itms-services://?action=download-manifest&url=https://xxxx/iOS/manifest.plist">游戏包</a></h1>
<br/>
<h1>3. Android安装 <a href="https://xxxx/ipa/thirtysix.apk">apk安装包</a></h1>  
</body>
</html>  

重点 manifest.plist 格式 和制作

<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0"><dict>
  <key>items</key>
  <array>
    <dict>
      <key>assets</key>
      <array>
        <dict>
          <key>kind</key>
          <string>software-package</string>
          <key>url</key>
          <string><![CDATA[https://xxxx/iOS/ProductName.ipa]]></string>
        </dict>
        <dict>
          <key>kind</key>
          <string>display-image</string>
          <key>needs-shine</key>
          <integer>0</integer>
          <key>url</key>
          <string><![CDATA[https://xxxx/iOS/Icon-Store-1024.png]]></string>
        </dict>
        <dict>
          <key>kind</key>
          <string>full-size-image</string>
          <key>needs-shine</key>
          <true/>
          <key>url</key>
          <string><![CDATA[https://xxxx/iOS/Icon-Store-1024.png]]></string>
        </dict>
      </array>
      <key>metadata</key>
      <dict>
        <key>bundle-identifier</key>
        <string>com.xxx.xxx</string>
        <key>bundle-version</key>
        <string><![CDATA[1.204.5]]></string>
        <key>kind</key>
        <string>software</string>
        <key>title</key>
        <string><![CDATA[app名字]]></string>
      </dict>
    </dict>
  </array>
</dict>
</plist>

把对应的 ipa 上传到文件服务器(注意需要时https支持下载)即可.

对了,下面给出 命令行 编译 ipa 需要的 plist 配置文件
ad-hoc.plist

<?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>method</key>
    <string>ad-hoc</string>
    <key>teamID</key>
    <string>xxxxxx</string>
    <key>uploadBitcode</key>
    <true/>
    <key>uploadSymbols</key>
    <true/>
    <key>manifest</key>
    <dict>
        <key>appURL</key>
        <string>https://xxxx/iOS/ProductName.ipa</string>
        <key>displayImageURL</key>
        <string>https://xxxx/iOS/Icon-Store-1024.png</string>
        <key>fullSizeImageURL</key>
        <string>https://xxxx/iOS/Icon-Store-1024.png</string>
        <key>assetPackManifestURL</key>
        <string>https://xxxx/iOS/manifest.plist</string>
    </dict>
</dict>
</plist>

app-store.plist

<?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>method</key>
    <string>app-store</string>
    <key>teamID</key>
    <string>XXXX</string>
    <key>uploadBitcode</key>
    <true/>
    <key>uploadSymbols</key>
    <true/>
</dict>
</plist>

编译命令

'/usr/bin/xcodebuild -exportArchive -archivePath %s -exportOptionsPlist %s -exportPath %s -allowProvisioningUpdates -allowProvisioningDeviceRegistration' % (archivePath,exportOptionsPlist,exportPath)

完整的编译脚本 autobuild.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from optparse import OptionParser
import subprocess
import requests
import datetime
import os
import myemail
import re

curPath=os.path.split(os.path.realpath(__file__))[0]

messagetxt="本次上传包含:"

def cleanBuildDir(workspaceName,configuration,scheme):
    buildCmd = '/usr/bin/xcodebuild -quiet clean -workspace %s -configuration %s -scheme %s' % (workspaceName,configuration,scheme)
    if workspaceName is None :
        buildCmd = '/usr/bin/xcodebuild -quiet clean -configuration %s -scheme %s' % (configuration,scheme)
    print(buildCmd)
    process = subprocess.Popen(buildCmd, shell = True)
    process.wait()

def uploadIpaToFir(ipaPath ,firkey,changelog):
    buildCmd = '/usr/local/bin/fir login %s && /usr/local/bin/fir publish %s -c %s' % (firkey,ipaPath,changelog)
    print(buildCmd)
    process = subprocess.Popen(buildCmd, shell = True)
    process.wait()

def buildWorkspace(workspaceName, scheme, configuration, archivePath):
    buildCmd = '/usr/bin/xcodebuild -quiet archive -workspace %s -scheme %s -configuration %s -archivePath %s' % (workspaceName,scheme,configuration,archivePath)
    if workspaceName is None :
        buildCmd = '/usr/bin/xcodebuild -quiet archive -scheme %s -configuration %s -archivePath %s' % (scheme,configuration,archivePath)
    print(buildCmd)
    process = subprocess.Popen(buildCmd, shell = True)
    process.wait()

def exportipa(archivePath, exportPath, exportOptionsPlist):
    buildCmd = '/usr/bin/xcodebuild -exportArchive -archivePath %s -exportOptionsPlist %s -exportPath %s -allowProvisioningUpdates -allowProvisioningDeviceRegistration' % (archivePath,exportOptionsPlist,exportPath)
    print(buildCmd)
    process = subprocess.Popen(buildCmd, shell=True)
    '''out_value=process.communicate()
    regex = re.compile("Errors=\{")
    if regex.match("".join(out_value)) :
        logfile = writelog(out_value)
        print("build error please log:%s"%(logfile))
        exit()'''
    process.wait()

def uploadIpaToAppStore(ipaPath_appstore, appleID, applepassword):
    altoolPath="xcrun altool"
    #buildCmd = '%s --validate-app -f %s -u %s -p %s -t ios --output-format xml' % (altoolPath, ipaPath_appstore, appleID, applepassword)
    #process = subprocess.Popen(buildCmd, shell = True)
    #process.wait()
    buildCmd = '%s --upload-app -f %s --apiIssuer %s --apiKey %s -t ios --output-format xml' % (altoolPath, ipaPath_appstore, appleID, applepassword)
    print(buildCmd)
    process = subprocess.Popen(buildCmd, shell = True)
    process.wait()
    
def writelog(logstr):
    timenow = datetime.datetime.now()
    timestr = datetime.datetime.strftime(timenow,'%H-%M-%S')
    datestr = datetime.datetime.strftime(timenow,'%Y-%m-%d')
    dirstr = '%s_%s' % (datestr,timestr)
    logfile = '%s/log/%s.txt'%(curPath,dirstr)
    file_object=open(logfile, 'w+')
    file_object.write(logstr)
    file_object.close()
    return logfile

    
def xcbuild(options):
    global messagetxt
    if options.scheme is not None:
        if options.needCompile:
            cleanBuildDir(options.workspace,options.configuration,options.scheme)
            buildWorkspace(options.workspace, options.scheme, options.configuration, options.archivePath)
            if options.backUpArchive is not None:
                buildCmd = 'cp -a %s %s' % (options.archivePath,options.backUpArchive)
                print(buildCmd)
                process = subprocess.Popen(buildCmd, shell = True)
                process.wait()
                
        if options.export == 1:
            exportipa(options.archivePath,options.exportPath_adhoc,options.exportOptionsPlist_adhoc)
            messagetxt = messagetxt + "导出test的ipa"
        elif options.export == 2:
            exportipa(options.archivePath,options.exportPath_appstore,options.exportOptionsPlist_appstore)
            messagetxt = messagetxt + "导出AppStore的ipa"
        elif options.export == 3:
            exportipa(options.archivePath,options.exportPath_adhoc,options.exportOptionsPlist_adhoc)
            exportipa(options.archivePath,options.exportPath_appstore,options.exportOptionsPlist_appstore)
            messagetxt = messagetxt + "导出test、AppStore的ipa"

        if options.upload == 1:
            print ("build and upload test")
            uploadIpaToFir(options.ipaPath_adhoc, options.firkey,options.changelog)
            messagetxt = messagetxt + "上传测试版本"
            #myemail.send_mail(options.scheme, "%s:%s"%(messagetxt,options.note), ",http://hp3h1.hidog.net/shareapp/test.php")
        elif options.upload == 2:
            print ("build and upload appstore")
            uploadIpaToAppStore(options.ipaPath_appstore,options.appleid, options.applepassword)
            messagetxt = messagetxt + "上传AppStore发布版本"
            #myemail.send_mail(options.scheme, "%s:%s"%(messagetxt,options.note), "")
        elif options.upload == 3:
            print ("build and upload appstore & test")
            uploadIpaToFir(options.ipaPath_adhoc, options.firkey,options.changelog)
            messagetxt = messagetxt + "上传测试版本"
            uploadIpaToAppStore(options.ipaPath_appstore,options.appleid, options.applepassword)
            messagetxt = messagetxt + "、AppStore发布版本,备注:%s,http://hp3h1.hidog.net/shareapp/test.php" % options.note
            #myemail.send_mail(options.scheme, messagetxt, "")
        

def main():
    parser = OptionParser()
    parser.add_option("-s", "--scheme",         type="string",  help="Build the scheme specified by schemename. Required if building a workspace.", metavar="schemename")
    parser.add_option("-e", "--export",         type="int",     help="specify export filename",                                                     metavar="output_filename")
    parser.add_option("-u", "--upload",         type="int",     help="upload filename",                                                             metavar="upload_filename")
    parser.add_option("-n", "--note",           type="string",  help="specify publish note",                                                        metavar="publish note")
    parser.add_option("-a", "--archivePath",    type="string",  help="archivePath",                                                                 metavar="archivePath")
    parser.add_option("-t", "--team",           type="int",     help="whichTeam",                                                                   metavar="whichTeam")
    parser.add_option("-w", "--workspace",      type="string",  help="whichWorkspace",                                                              metavar="whichWorkspace")
    parser.add_option("-c", "--changelog",      type="string",  help="changelog",                                                                   metavar="changelog")
    parser.add_option("-d", "--debug",          type="string",  help="debug",                                                                       metavar="debug mode")
    parser.add_option("-b", "--backUpArchive",  type="string",  help="backUpArchive",                                                               metavar="backUpArchive")
    parser.add_option("-r", "--upUser",         type="string",  help="upUser",                                                                      metavar="upUser")
    parser.add_option("-p", "--upPass",         type="string",  help="upPass",                                                                      metavar="upPass")
    parser.add_option("-o", "--ipaPath",        type="string",  help="ipaPath",                                                                     metavar="ipaPath")

    (options, args) = parser.parse_args()

    timenow = datetime.datetime.now()
    datestr = datetime.datetime.strftime(timenow,'%Y-%m-%d')
    timestr = datetime.datetime.strftime(timenow,'%H-%M-%S')
    #datestr = '2019-10-15'
    #timestr = '11-06-59'
    dirstr = '%s/%s_%s' % (datestr,options.scheme,timestr)

    #options.home = os.path.expanduser('~')
    options.home = curPath
    options.ipaPath_adhoc = '%s/../ipa_test/%s/%s.ipa' % (options.home,dirstr,options.scheme)
    options.ipaPath_appstore = '%s/../ipa_release/%s/%s.ipa' % (options.home,dirstr,options.scheme)
    options.exportPath_adhoc = '%s/../ipa_test/%s' % (options.home,dirstr)
    options.exportPath_appstore = '%s/../ipa_release/%s' % (options.home,dirstr)

    if options.ipaPath is not None:
        options.ipaPath_adhoc = '%s/%s/%s.ipa' % (options.ipaPath,dirstr,options.scheme)
        options.ipaPath_appstore = '%s/%s/%s.ipa' % (options.ipaPath,dirstr,options.scheme)
        print("ipaPath "+options.ipaPath_appstore)
        
    if options.team is None:
        options.exportOptionsPlist_adhoc = '%s/packcfghz/ad-hoc.plist' % (options.home)
        options.exportOptionsPlist_appstore = '%s/packcfghz/app-store.plist' % (options.home)
        options.appleid=options.upUser
        options.applepassword=options.upPass
    
    if options.archivePath is None :
        options.archivePath = "%s/Library/Developer/Xcode/Archives/%s/%s.xcarchive" % (os.path.expanduser('~'),datestr,dirstr)
        options.needCompile=True
    else:
        options.needCompile=False

    if options.debug is None or options.debug == "false" :
        options.configuration = "Release"
    else:
        options.configuration = "Debug"
    
    if options.changelog is None:
        options.changelog=""

    options.firkey = "xxxxxxxx"
    
    if options.export is None:
        options.export = 0
    if options.upload is None:
        options.upload = 0
    if options.note is None:
        options.note = ""

    print ("options: %s, args: %s" % (options, args))

    if options.scheme is not None:
        xcbuild(options)

if __name__ == '__main__':
    main()

使用示例,可以结合 Jenkins PipeLine 来使用

/usr/local/bin/python3 ~/Desktop/desk/BuildScript/autobuild.py \
            -s Proj-mobile \
            -d $debug \
            -e $IOS_UPLOAD_TYPE \
            -u $IOS_UPLOAD_TYPE \
            -w Proj.xcworkspace \
            -c "吧啦吧啦" \
            -b ${updateServerPath}/apkipa/Proj-mobile.${APP_VERSION_CODE}.${APP_VERSION_NAME}-$buildTime.xcarchive \
            -o $ipaPath


#下面是 bugly 上传符号 脚本

#上传符号文件 必须编译完立即上传,每次编译产生的 map 都会变化
            $JAVAHOME1_8/bin/java -jar $ProjRoot/buglyqq-upload-symbol.jar -appid 5fe2f21f91 \
                                    -appkey xxxx \
                                    -bundleid xxxxx \
                                    -version ${APP_VERSION_NAME}.${APP_VERSION_CODE} \
                                    -platform IOS \
                                    -inputSymbol ${updateServerPath}/apkipa/Proj-mobile.${APP_VERSION_CODE}.${APP_VERSION_NAME}-$buildTime.xcarchive/dSYMs/

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

推荐阅读更多精彩内容