優(yōu)雅地使用fastlane構(gòu)建iOS內(nèi)測(cè)安裝包(自動(dòng)分發(fā)到蒲公英,自動(dòng)發(fā)送釘釘群消息,自動(dòng)上傳符號(hào)表文件到bugly)(進(jìn)階)

前言

本文需要用到RubyMine集成開(kāi)發(fā)環(huán)境,F(xiàn)astlane的蒲公英插件,需要用戶自行安裝配置,引用鏈接如下:

本項(xiàng)目源碼地址:https://github.com/cba023/FastlaneAdhoc

RubyMine官方網(wǎng)站https://www.jetbrains.com/ruby/

蒲公英Fastlane插件安裝教程:https://www.pgyer.com/doc/view/fastlane

我在之前的一篇文章(http://www.itdecent.cn/p/50cff529358f)已經(jīng)講到了如何從零開(kāi)始使用fastlane實(shí)現(xiàn)iOS內(nèi)測(cè)安裝包的構(gòu)建,顯然,實(shí)現(xiàn)這樣的功能并不困難,同時(shí)會(huì)有朋友問(wèn):這樣構(gòu)建的意思是什么?為什么不用XCode給我提供的打包工具實(shí)現(xiàn)項(xiàng)目的構(gòu)建呢?
其實(shí),上一篇講述的只是基礎(chǔ),現(xiàn)在我們要圍繞這個(gè)基礎(chǔ)來(lái)搭建一款真正符合我們使用場(chǎng)景的構(gòu)建項(xiàng)。當(dāng)然,如果正在閱讀的你懂點(diǎn)Ruby,可能會(huì)理解起來(lái)更輕松。

功能概述

這次構(gòu)建可以讓我們解決平常構(gòu)建打包中的一些痛點(diǎn),通通變得自動(dòng)化,如下:

  • 一鍵根據(jù)日期時(shí)間生成安裝包,同時(shí)生成Markdown格式的更新日志到本地文件夾
  • 自動(dòng)上傳安裝包到蒲公英,實(shí)現(xiàn)App分發(fā)
  • 自動(dòng)發(fā)送應(yīng)用更新消息到釘釘群,并且在釘釘群中展示本次構(gòu)建的信息和更新描述,實(shí)現(xiàn)參與安裝測(cè)試的朋友可以很清晰的了解到這一次構(gòu)建的詳細(xì)信息
  • 自動(dòng)上傳dSYM符號(hào)表到bugly,這樣,我們開(kāi)發(fā)人員在App的崩潰日志管理上會(huì)顯得更加輕松
釘釘群消息的效果
蒲公英下載頁(yè)展示本次構(gòu)建信息
fastlane輸出文件夾生成的內(nèi)容
本地Markdown格式的版本更新日志

當(dāng)然,還可以配置各種各樣的功能,不過(guò),這對(duì)于我們程序員來(lái)說(shuō)都不是難題,要做什么,取決于我們的需求。

開(kāi)始使用Ruby編寫構(gòu)建腳本

因?yàn)閒astfile是基于Ruby編寫,所以我們?yōu)榱四芘涮自撐募_(kāi)設(shè)一個(gè)基于Ruby語(yǔ)言的工程,需要用到RubyMine(VSCode也支持Ruby調(diào)試,但是沒(méi)有RubyMine方便,這里統(tǒng)一使用RubyMine),RubyMine是大名鼎鼎的捷克公司JetBrains開(kāi)發(fā)的一款針對(duì)Ruby語(yǔ)言的集成開(kāi)發(fā)環(huán)境,比較出名的PhpStorm, WebStorm, idea都是JetBrains旗下的產(chǎn)品。具體安裝RubyMine的教程可以自己到網(wǎng)上查詢資料。

RubyMine官網(wǎng):https://www.jetbrains.com/ruby/

使用Ruby打開(kāi)Fastfile所在文件夾(即fastlane文件夾,可以查看我的上一篇文章找到文件夾位置),以該文件夾作為Ruby工程目錄,如下:

以fastlane文件夾為工程目錄打開(kāi)后RubyMine顯示的內(nèi)容

Fastfile是不能通過(guò)RubyMine來(lái)運(yùn)行的,怎么辦呢,我們可以借助創(chuàng)建一個(gè)類Controller來(lái)幫他實(shí)現(xiàn)一些操作。同時(shí)我們還要?jiǎng)?chuàng)建幾個(gè)類,把Controller包含在內(nèi)則有這么幾個(gè)類:

類名 用途
Controller 控制器類,用于處理業(yè)務(wù)邏輯
ProjectInfo 數(shù)據(jù)模型類,只讀,用于讀取項(xiàng)目信息
BuildInfo 數(shù)據(jù)模型類,可讀寫,用戶可以編輯其屬性,實(shí)現(xiàn)構(gòu)建的可定制化
CodeConf 數(shù)據(jù)模型類,只讀,用于讀取項(xiàng)目代碼中配置項(xiàng)目信息(通常使用Shell腳本或Ruby腳本獲取,比如當(dāng)前代碼里配置的網(wǎng)絡(luò)環(huán)境是開(kāi)發(fā)環(huán)境還是線上環(huán)境)
UserSetting 該類在專用于用戶在構(gòu)建前設(shè)定構(gòu)建數(shù)據(jù),實(shí)現(xiàn)差異化構(gòu)建(比如每次打包時(shí)本次的更新內(nèi)容)
DataSource 數(shù)據(jù)源類,可讀寫,這里主要配置蒲公英,應(yīng)用簽名信息,釘釘群消息等,供UserSetting類選擇調(diào)用,實(shí)現(xiàn)構(gòu)建時(shí)用戶可手動(dòng)選擇預(yù)先配置的一種實(shí)現(xiàn)可定制化構(gòu)建

拆分業(yè)務(wù)后,整個(gè)構(gòu)建的業(yè)務(wù)更加清晰。更重要的是,我們可以非常愉快地對(duì)構(gòu)建的業(yè)務(wù)邏輯進(jìn)行調(diào)試,這些類和Fastfile都是分離的,而且是由Fastfile單向調(diào)用他們,調(diào)試這些類對(duì)Fastfile沒(méi)有任何影響,這樣我們就可以對(duì)整個(gè)構(gòu)建功能的業(yè)務(wù)邏輯進(jìn)行隨意的擴(kuò)展而不會(huì)顯得混亂。

右鍵 => Debug就能調(diào)試,很方便

ProjectInfo類

# 采集工程信息,包含XCode工程和打包導(dǎo)出配置
class ProjectConf

  @project_name = "<XCode工程名>"
  @app_name = "<App的名字,比如:微信>"


  @@CurTime = Time.now   # 類變量:當(dāng)前時(shí)間
 
  def self.app_build_time   # 類方法:app開(kāi)始構(gòu)建時(shí)間
    return @@CurTime.strftime('%Y-%m-%d %H:%M:%S')
  end

  def self.output_year_month # 類方法:app開(kāi)始構(gòu)建的年月,用于導(dǎo)出IPA和更新日志時(shí)使用
    return @@CurTime.strftime('%Y_%m')
  end

  def self.output_file_build_time # 類方法:存入文件格式的app開(kāi)始構(gòu)建的時(shí)間,用于存入文件名
    return @@CurTime.strftime('%Y%m%d_%H%M%S')
  end

  def self.project_name  # 工程名get方法
    return @project_name
  end

  def self.app_name # 應(yīng)用名get方法
    return @app_name
  end

  def self.output_dir  # 輸出根目錄
    return "/Users/#{`whoami`.chomp}/FastlaneOutput"
  end

  def self.ipa_dir  # 輸入ipa文件夾
    return "#{self.output_dir}/Apps/#{ProjectConf.output_year_month}"
  end

  def self.log_dir  # 輸出更新日志文件夾
    return "#{self.output_dir}/Logs"
  end

  def self.ipa_name # 輸入的ipa文件的文件名
    return "#{self.project_name}_#{ProjectConf.output_file_build_time}.ipa"
  end

  def self.cur_branch  # 項(xiàng)目構(gòu)建時(shí)所在的分支(沒(méi)有配置Git的情況不會(huì)有值)
    return `git rev-parse --abbrev-ref HEAD`.to_s.chomp
  end

  def self.cur_commit_id # 項(xiàng)目構(gòu)建時(shí)Git的Commit ID(沒(méi)有配置Git的情況不會(huì)有值)
    return `git log --pretty=format:"%h" | head -1  | awk '{print $1}'`.to_s.chomp
  end

  def self.pbxproj_path  #  project.pbxproj文件路徑,讀取工程信息需要讀取該文件
    return "../#{self.project_name}.xcodeproj/project.pbxproj"
  end

  def self.app_version # 應(yīng)用版本信息(如果工程中沒(méi)有配置則預(yù)設(shè)為1.0)
    ver = "1.0"
    app_version_line = `sed -n '/MARKETING_VERSION/'p #{self.pbxproj_path}`
    if app_version_line != nil then
      ver = app_version_line[/\= .*;/].delete('"').delete('=').delete(' ').delete(';')
    end
    return ver
  end

  def self.build_version # 應(yīng)用構(gòu)建版本信息(如果工程中沒(méi)有配置則預(yù)設(shè)為1)
    ver = "1"
    build_version_line = `sed -n '/CURRENT_PROJECT_VERSION/'p #{self.pbxproj_path}`
    if build_version_line != nil then
      ver = build_version_line[/\= .*;/].delete('"').delete('=').delete(' ').delete(';')
    end
    return ver
  end
end

BuildInfo類

該類的屬性都可以配置。用戶構(gòu)建時(shí)根據(jù)自己的需要來(lái)配置。

class BuildConf

  def self.update_desc  # 更新描述信息,當(dāng)前每次都不一樣啦
    return @update_desc
  end
  def self.update_desc=(val) # 這是set方法
    @update_desc = val
  end

  def self.build_configuration  # 構(gòu)建配置 Release或Debug
    return @build_configuration
  end
  def self.build_configuration=(val)
    @build_configuration = val
  end

  def self.app_export_method  # 導(dǎo)出方式,我們用的ad-hoc
    return @app_export_method
  end
  def self.app_export_method=(val)
    @app_export_method = val
  end

  def self.is_upload_to_pgyer # 構(gòu)建完成后是否自動(dòng)上傳到蒲公英
    return @is_upload_to_pgyer
  end
  def self.is_upload_to_pgyer=(val)
    @is_upload_to_pgyer = val
  end

  def self.is_send_to_dingtalk # 構(gòu)建完成后是否自動(dòng)發(fā)送釘釘群消息
    return @is_send_to_dingtalk
  end
  def self.is_send_to_dingtalk=(val)
    @is_send_to_dingtalk = val
  end

  def self.is_upload_dSYM_to_bugly # 構(gòu)建完成后是否自動(dòng)上傳符號(hào)表到bugly
    return @is_upload_dSYM_to_bugly
  end
  def self.is_upload_dSYM_to_bugly=(val)
    @is_upload_dSYM_to_bugly = val
  end

  def self.pgyer_selected_index # 蒲公英數(shù)據(jù)源的選定索引值
    return @pgyer_selected_index
  end
  def self.pgyer_selected_index=(val)
    @pgyer_selected_index= val
  end

  def self.app_sign_seleted_index # 應(yīng)用簽名的選定索引值
    return @app_sign_seleted_index
  end
  def self.app_sign_seleted_index=(val)
    @app_sign_seleted_index= val
  end

  def self.ding_webhook_index # 釘釘群消息Webhook的選定索引值
    return @ding_webhook_index
  end
  def self.ding_webhook_index=(val)
    @ding_webhook_index = val
  end
end

CodeConf類

該類可定制性非常強(qiáng),這里不做講解,大家了解Shell腳本或Ruby文件內(nèi)容讀取的可以自己做相關(guān)的處理,這里默認(rèn)我們?nèi)缦绿幚怼H缬幸蓡?wèn),歡迎留言。

# require './project_conf'  # 如果有依賴項(xiàng),則要引入其他的類(默認(rèn)沒(méi)有引入)
# require './build_conf'

# 從項(xiàng)目代碼中讀取配置的類,日常構(gòu)建項(xiàng)目時(shí)不需要更改該文件
class CodeConf
  # 從項(xiàng)目代碼中讀取當(dāng)前工程的網(wǎng)絡(luò)環(huán)境
  def self.enviroment
    return 'dev'
  end

  # 通過(guò)代碼讀取當(dāng)前工程是否支持用戶自切網(wǎng)絡(luò)環(huán)境
  def self.is_mutable_enviroment
    return false
  end
end

# puts "網(wǎng)絡(luò)環(huán)境 => #{CodeConf.enviroment}"
# puts "是否可自切換網(wǎng)絡(luò)環(huán)境 => #{CodeConf.is_mutable_enviroment}"

DataSource類

數(shù)據(jù)源類,是用于存儲(chǔ)各種配置數(shù)組的地方,可以供用戶選擇,他為BuildInfo類服務(wù)。

# 用戶配置的數(shù)據(jù)源
class DataSource
    # 蒲公英配置數(shù)組, 可以配置多組數(shù)據(jù),構(gòu)建時(shí)選其一
    @pgyer_configs  = Array[
        {
            "api_key" => "xxxxxxxxxxx",
            "user_key" => "xxxxxxxxxxx",
            "app_url" => "https://www.pgyer.com/xxxx",
            "app_icon" => "https://www.pgyer.com/app/qrcode/xxxx"
        },
        {
           "api_key" => "xxxxxxxxxxx",
            "user_key" => "xxxxxxxxxxx",
            "app_url" => "https://www.pgyer.com/xxxx",
            "app_icon" => "https://www.pgyer.com/app/qrcode/xxxx"
        },
        {
            "api_key" => "xxxxxxxxxxx",
            "user_key" => "xxxxxxxxxxx",
            "app_url" => "https://www.pgyer.com/xxxx",
            "app_icon" => "https://www.pgyer.com/app/qrcode/xxxx"
        }
    ]

    # 應(yīng)用配置簽名數(shù)組(構(gòu)建時(shí)選其一)
    @app_signs = Array[
        {"bundle_id" => "<bundle id1>", "provisioning_profile" => "<描述文件名1>"},
        {"bundle_id" => "<bundle id2>", "provisioning_profile" => "<描述文件名2>"},
    ]

    # 釘釘webhook鏈接數(shù)組
    @ding_webhooks = Array[
        {"name" => "<釘釘群1>", "url" => "https://oapi.dingtalk.com/robot/send?access_token=xxxxx"},
        {"name" => "<釘釘群2>", "url" => "https://oapi.dingtalk.com/robot/send?access_token=xxxxx"},
        {"name" => "<釘釘群3>", "url" => "https://oapi.dingtalk.com/robot/send?access_token=xxxxx"},
        {"name" => "<釘釘群4>", "url" => "https://oapi.dingtalk.com/robot/send?access_token=xxxxx"}
    ]

    def self.pgyer_configs
        return @pgyer_configs
    end

    def self.app_signs
        return @app_signs
    end

    def self.ding_webhooks
        return @ding_webhooks
    end
end

UserSetting類

require './build_conf'

class UserSetting
# 初始化數(shù)據(jù),
  def self.initData
    # 更新描述信息,每一項(xiàng)用`;`隔開(kāi)
    BuildConf.update_desc = "更新了A功能;優(yōu)化了B功能;修復(fù)了C問(wèn)題;"
    BuildConf.build_configuration = "Release"
    BuildConf.app_export_method = "ad-hoc"
    BuildConf.is_upload_to_pgyer = true
    BuildConf.is_send_to_dingtalk = true
    BuildConf.is_upload_dSYM_to_bugly = true
    # pgyer_selected_index, app_sign_seleted_index, ding_webhook_index的索引值選擇請(qǐng)參照DataSource類
    BuildConf.pgyer_selected_index = 2
    BuildConf.app_sign_seleted_index = 0
    BuildConf.ding_webhook_index = 3
  end
end

Controller類

Controller則是上述各個(gè)類的管理類,同時(shí)Controller還負(fù)責(zé)供Fastfile的調(diào)度,以此來(lái)實(shí)現(xiàn)對(duì)Fastfile解耦,同時(shí)也解決了Fastfile不方便調(diào)試的問(wèn)題。該類代碼相對(duì)較多,代碼類我會(huì)貼出相關(guān)注釋。

# Fastlane的數(shù)據(jù)配置和方法調(diào)用文件
# 注: 該文件所在文件夾可以用RubyMine以工程形式打開(kāi)調(diào)試

require 'fileutils'
require 'net/http'
require 'json'
require './data_source'
require './project_conf'
require './build_conf'
require './code_conf'
require './user_setting'

# 控制器類
class Controller
  def self.initConf
    # 從UserSetting類中初始化數(shù)組配置
    UserSetting.initData  # 首先通過(guò)UserSetting類去預(yù)設(shè)構(gòu)建配置
    # 是否可以自切環(huán)境的中文描述
    @is_mutable_enviroment_desc = CodeConf.is_mutable_enviroment == true ? "可切換" : "不可切換"
    # 以下幾項(xiàng)是更具BuildConf中配置的索引值去取DataSource類中數(shù)據(jù)源配置,也是要在UserSetting中預(yù)設(shè)
    @pgyer_conf = DataSource.pgyer_configs.at(BuildConf.pgyer_selected_index)
    @app_sign_conf = DataSource.app_signs.at(BuildConf.app_sign_seleted_index)
    @ding_webhook_conf = DataSource.ding_webhooks.at(BuildConf.ding_webhook_index)
  end

  def self.pgyer_conf
    return @pgyer_conf
  end

  def self.app_sign_conf
    return @app_sign_conf
  end

  def self.ding_webhook_conf
    return @ding_webhook_conf
  end

  # 打印構(gòu)建配置
  def self.print_configs
    puts("\n? 蒲公英配置 ?\napi_key: #{@pgyer_conf["api_key"]}\nuser_key: #{@pgyer_conf["user_key"]}\napp_url: #{@pgyer_conf["app_url"]}\napp_icon: #{@pgyer_conf["app_icon"]}")
    puts("\n? 簽名配置 ?\nbundle_id: #{@app_sign_conf["bundle_id"]}\nprovisioning_profile: #{@app_sign_conf["provisioning_profile"]}")
    puts("\n? 釘消息配置 ?\n釘消息目標(biāo)群: #{@ding_webhook_conf["name"]}\nwebhook URL: #{@ding_webhook_conf["url"]}")
    puts "\n"
    puts "\n? markdown消息配置 ?\n#{self.ding_release_note}"
  end

  # 格式化更新信息(規(guī)則:中英文分號(hào)或分號(hào)帶空格通通轉(zhuǎn)換成中文分號(hào)間隔)
  def self.formated_update_desc
    # 更新內(nèi)容規(guī)范化
    formated_desc = BuildConf.update_desc.gsub(/; |; |;/, ";").chomp
    if formated_desc[-1, 1] == ";" then
      formated_desc.chop!
    end
    if formated_desc.length == 0 then
      formated_desc = "<暫無(wú)記錄>"
    end
    return formated_desc
  end

  # 通過(guò)格式化后的更新信息 => 更新信息數(shù)組(規(guī)則:利用分號(hào)分割)
  def self.updates
    updates = self.formated_update_desc.split(";")
    return updates
  end

  # 生成本次構(gòu)建信息項(xiàng)目通用哈希數(shù)組
  def self.build_items
    items = Array[
        {"item" => "應(yīng)用版本", "value" => ProjectConf.app_version + "(build #{ProjectConf.app_version})"},
        {"item" => "構(gòu)建時(shí)間", "value" => ProjectConf.app_build_time},
        {"item" => "構(gòu)建環(huán)境", "value" => "#{CodeConf.enviroment}(#{@is_mutable_enviroment_desc})"},
        {"item" => "構(gòu)建途徑", "value" => "#{BuildConf.build_configuration} => #{BuildConf.app_export_method}"},
        {"item" => "構(gòu)建分支", "value" => "#{ProjectConf.cur_branch}(#{ProjectConf.cur_commit_id})"},
        {"item" => "應(yīng)用簽名", "value" => @app_sign_conf["bundle_id"]}
    ]
    return items
  end

  # 生成更新描述的markdown格式的無(wú)需列表字符串(用途:發(fā)送釘消息;存入本地更新日志;蒲公英更新項(xiàng)也采用此方案,換行 + `*`)
  def self.md_update_content
    content = String.new
    (0..updates.count - 1).each { |i|
      content += "* " + updates[i] + "\n"
    }
    return content
  end

  # 生成構(gòu)建信息項(xiàng)信息:用于存入markdown無(wú)序列表
  def self.md_build_content
    content = String.new
    (0..self.build_items.length - 1).each { |i|
      content += "* #{self.build_items[i]["item"]}: #{self.build_items[i]["value"]}\n"
    }
    return content
  end

  # 生成構(gòu)建項(xiàng)信息:用于寫入到上傳蒲公英的描述中
  def self.pyger_build_content
    content = String.new
    (0..self.build_items.length - 1).each { |i|
      content += "#{self.build_items[i]["item"]}: #{self.build_items[i]["value"]}\n"
    }
    return content
  end

  # 生成蒲公英工程上傳時(shí)的描述信息
  def self.pgyer_build_article
    return self.pyger_build_content + "更新描述:\n" + self.md_update_content
  end

  # 用于寫入本地日志文件的更新內(nèi)容
  def self.logs_release_note
    release_note = "\n## v#{ProjectConf.app_version} (#{ProjectConf.ipa_name})\n\n" + "> 構(gòu)建信息\n\n" + "#{self.md_build_content}\n" + "> 更新描述\n\n" + "#{self.md_update_content}\n" + "------------------------------\n"
    return release_note
  end

  # 用于發(fā)送釘釘webhook消息的更新內(nèi)容
  def self.ding_release_note
    msg_title = "發(fā)現(xiàn)#{ProjectConf.app_name}(iOS)新版本!\n"
    release_note = "### #{msg_title}\n" +
        "![#{@pgyer_conf["app_icon"]}](#{@pgyer_conf["app_icon"]})\n\n" +
        "###### *鏈接*: [#{@pgyer_conf["app_url"]}](#{@pgyer_conf["app_url"]})\n\n" +
        "------------------------------\n\n" + "> 構(gòu)建信息\n\n" + "#{self.md_build_content}\n" + "> 更新描述\n\n" + "#{self.md_update_content}\n"
    return release_note
  end

  # 寫入更新日志到本地
  def self.write_to_local_logs
    # puts "\nself.md_build_content\n"
    # puts "\nself.md_update_content\n"
    release_note_path = "#{ProjectConf.log_dir}/#{ProjectConf.app_name}_iOS_#{ProjectConf.output_year_month}更新記錄.md"
    r_n_path_temp = "#{ProjectConf.log_dir}/.latest_log.tmp"
    # 如果本次構(gòu)建有更新,則寫入更新日志(先要檢測(cè)Log文件夾是否存在,不存在則需要?jiǎng)?chuàng)建)
    FileUtils.mkpath "#{ProjectConf.log_dir}"
    if File::exists?(release_note_path) == false then
      # 如果文件不存在,創(chuàng)建文件并寫入內(nèi)容
      `touch #{release_note_path}`
      `echo "# #{ProjectConf.app_name}(iOS) #{ProjectConf.output_year_month}更新記錄\n\n" >> #{release_note_path}`
    end
    # 寫入本次更新內(nèi)容
    `echo "#{self.logs_release_note}" >  "#{r_n_path_temp}"` # 先將本次更新內(nèi)容存入到緩存文件
    `sed -i '' '/更新記錄/r #{r_n_path_temp}' #{release_note_path}` # 將緩存文件的內(nèi)容插入到`release_note_path`文件的`更新記錄`所在行的下一行
    `rm -rf #{r_n_path_temp}`  # 刪除緩存文件
    puts "\n------------------------------\n"
    puts "? 已經(jīng)成功寫入更新日志到:#{release_note_path} ?"
  end

  # 發(fā)送更新信息到釘釘群
  def self.send_ding_msg
    msg_title = "發(fā)現(xiàn)#{ProjectConf.app_name}(iOS)新版本!\n"
    markdown = {
        "msgtype": "markdown",
        "markdown": {title: msg_title, text: ding_release_note}
    }

    # 發(fā)起發(fā)送請(qǐng)求
    uri = URI.parse(@ding_webhook_conf["url"])
    https = Net::HTTP.new(uri.host, uri.port)
    https.use_ssl = true

    request = Net::HTTP::Post.new(uri.request_uri)
    request.add_field('Content-Type', 'application/json')
    request.body = markdown.to_json

    response = https.request(request)
    puts "------------------------------"
    puts "Response #{response.code} #{response.message}: #{response.body}"
    if response.code == "200" then
      puts "? 已發(fā)送釘消息 ==> #{@ding_webhook_conf["name"]}(url: #{@ding_webhook_conf["url"]}) ?"
    else
      puts "? 釘消息發(fā)送失敗 ?"
    end
  end

  # 上傳符號(hào)表到bugly
  def self.upload_dSYM_to_bugly
    bugly_app_id = "<bugly的app_id,這里其實(shí)也可以抽取到數(shù)據(jù)源配置類中>"
    bugly_app_key = "<bugly的app_key,可以抽取到數(shù)據(jù)源配置類中>"
    dSYM_name = "#{ProjectConf.project_name}_#{ProjectConf.output_file_build_time}.app.dSYM.zip"
    dSYM_path = "#{ProjectConf.ipa_dir}/#{dSYM_name}"
    channel = "default"

    bugly_desc = "? Bugly符號(hào)表配置 ?\n" +
        "bugly_app_id: #{bugly_app_id}\n" +
        "bugly_app_key: #{bugly_app_key}\n" +
        "dSYM_path: #{dSYM_path}\n" +
        "channel: #{channel}\n"
    puts bugly_desc
    `curl -k "https://api.bugly.qq.com/openapi/file/upload/symbol?app_key=#{bugly_app_key}&app_id=#{bugly_app_id}" --form "api_version=1" --form "app_id=#{bugly_app_id}" --form "app_key=#{bugly_app_key}" --form "symbolType=2"  --form "bundleId=#{@app_sign_conf["bundle_id"]}" --form "productVersion=#{ProjectConf.app_version}" --form "channel=#{channel}" --form "fileName=#{dSYM_name}" --form "file=@#{dSYM_path}" --verbose`
  end
end

# 這里是需要調(diào)用控制器自己的initConf方法,作為Fastfile引入Controller類時(shí)調(diào)用棧中最先執(zhí)行的方法
Controller.initConf
Controller.print_configs

Fastlane中Fastfile的配置

從上一篇博文中可以了解到,我們Fastfile文件中配置好項(xiàng)目構(gòu)建信息就可以給App打包了。而本文中,我們封裝了好幾個(gè)抽象類,全都為Fastfile服務(wù)。
直接在Fastfile中配置有簡(jiǎn)單、快速的優(yōu)點(diǎn),但是缺點(diǎn)很明顯,如下:

  • 不方便調(diào)試。Fastfile不是.rb后綴的文件,必須要通過(guò)bundle exec fastlane ...相關(guān)的方式去執(zhí)行才能暴露問(wèn)題,不僅干擾項(xiàng)目的正常構(gòu)建,還會(huì)在構(gòu)建的App項(xiàng)目很大的時(shí)候查錯(cuò)會(huì)變得難上加難
  • Fastfile容易變得異常臃腫。由于處理了一些不應(yīng)該由它去處理的業(yè)務(wù),違背了類的單一職責(zé)的設(shè)計(jì)理念
  • 代碼可讀性差,可維護(hù)性差。Fastfile設(shè)計(jì)的初心就是通過(guò)簡(jiǎn)單配置實(shí)現(xiàn)強(qiáng)大的構(gòu)建過(guò)程,我們?cè)诤笃谶€要對(duì)Fastfile配置進(jìn)行擴(kuò)展會(huì)變得麻煩且易錯(cuò)

而我們使用多個(gè)Ruby類把這些數(shù)據(jù)和業(yè)務(wù)處理抽象出來(lái)后,F(xiàn)astfile的負(fù)擔(dān)會(huì)小很多。如下是我們處理后的Fastfile:

require './controller' # 引入控制器,由控制器去調(diào)用其他類并處理業(yè)務(wù)邏輯

default_platform(:ios)

platform :ios do
  lane :adhoc do
    cocoapods
    build_app(scheme: ProjectConf.project_name,
              workspace: ProjectConf.project_name + ".xcworkspace",
              include_bitcode: true,
              configuration: BuildConf.build_configuration,
              export_method: BuildConf.app_export_method,
              output_directory: ProjectConf.ipa_dir,
              output_name: ProjectConf.ipa_name,
              silent: false,
              include_symbols: true,
              export_xcargs: "-allowProvisioningUpdates",
              export_options: {
                  provisioningProfiles: {
                      Controller.app_sign_conf["bundle_id"] => Controller.app_sign_conf["provisioning_profile"]
                  }
              })

    # 打包成功后寫入更新日志到本地
    Controller.write_to_local_logs

    # 分發(fā)應(yīng)用到蒲公英
    if BuildConf.is_upload_to_pgyer == true then
      pgyer(
          api_key: Controller.pgyer_conf["api_key"],
          user_key: Controller.pgyer_conf["user_key"],
          update_description: Controller.pgyer_build_article
      )
    end

    # 釘釘群消息
    if BuildConf.is_send_to_dingtalk == true then
      Controller.send_ding_msg
    end

    # 上傳符號(hào)表到bugly
    if BuildConf.is_upload_dSYM_to_bugly == true then
      Controller.upload_dSYM_to_bugly
    end
  end
end

這里要說(shuō)明的是pgyer上傳方法沒(méi)有在外部實(shí)現(xiàn),因?yàn)镕astlane的蒲公英插件只能在Fastfile中調(diào)用,不多是否上傳我們通過(guò)引入外部的變量BuildConf.is_upload_to_pgyer來(lái)控制。

總結(jié)

按照上述的各項(xiàng)配置,我們就可以實(shí)現(xiàn)我們的定制化構(gòu)建功能了。
終端進(jìn)入XCode工程的根目錄,執(zhí)行bundle exec fastlane adhoc, 會(huì)看到屏幕上會(huì)先打印我們預(yù)先配置好的參數(shù),這都是Controller類中print_configs方法的功勞。

當(dāng)我們的項(xiàng)目構(gòu)建完成時(shí)。就可以實(shí)現(xiàn)文章最前面功能概述描述的那些功能了。

本項(xiàng)目源碼地址:https://github.com/cba023/FastlaneAdhoc
轉(zhuǎn)載請(qǐng)注明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容