ios 自動化打包-基于xcodeproj

背景

因公司做的都是一些獨(dú)立部署的項(xiàng)目监氢,經(jīng)常會遇到以下這種情況绎谦。項(xiàng)目經(jīng)理新拿到一個項(xiàng)目,過來說XX給我包打一下定续。簡而言之就是代碼都是同一份代碼谍咆,但是需要替換app里面的圖標(biāo)&啟動圖&三方key&標(biāo)題&bundleid等等等禾锤。雖然不是什么復(fù)雜的工作,但是設(shè)想一下摹察,當(dāng)你寫著代碼恩掷,你需要貯藏掉改動的代碼,檢出新打包分支的代碼供嚎,還需要上蘋果開發(fā)者后臺申請證書黄娘,導(dǎo)出moboileprovision文件。(開發(fā)者后臺還卡的要死 -_-)查坪,弄好證書后還要將代碼里的配置修改掉寸宏,替換掉三方的appkey,替換掉域名等一些列操作。差不多都個把小時花進(jìn)去了偿曙。而且公司一個項(xiàng)目至少還得打2個包氮凝。還有些時候打完包,測試說app請求失敗望忆,發(fā)現(xiàn)是域名打錯了罩阵,又得重新打包。這做的都是一些重復(fù)性極高的事情启摄,對于自身的提升來說毫無意義稿壁。因此想到讓代碼來代替我們做這部分的工作。理想情況歉备,起一個web服務(wù) 讓項(xiàng)目經(jīng)理自行去修改配置文件傅是,然后導(dǎo)出相對應(yīng)的包(再配個模塊應(yīng)用功能選擇,這不就是涂鴉的管理后臺嗎?? )蕾羊,現(xiàn)有資源無法匹配喧笔,自身技術(shù)還不能實(shí)現(xiàn),因此先實(shí)現(xiàn)一個本地自動化的腳本提高效率龟再。

思路

  1. 從git上拉取代碼
  2. 修改bundleid书闸、displayname、provision file等
  3. 修改三方key利凑、baseurl等
  4. 修改appIcon浆劲、其他資源圖片
  5. 打包導(dǎo)出

準(zhǔn)備

需要用到的工具

  • ruby
  • xcodeproj 用于修改項(xiàng)目配置
#安裝方法
gem install xcodeproj
#安裝方法
curl https://raw.githubusercontent.com/0xc010d/mobileprovision-read/master/main.m | clang -framework Foundation -framework Security -o /usr/local/bin/mobileprovision-read -x objective-c -
gem install chunky_png

修改前項(xiàng)目

地址

20220319141448.jpg

20220319141506.jpg
20220319141523.jpg

在ruby腳本項(xiàng)目中創(chuàng)建這3個文件夾 用于存放相關(guān)信息

  • exportplist: export ipa文件需要用到ExportOptions.plist文件
  • images-appIcon 用于存放需要替換的appIcon
  • images-other 用于存放其他的圖片資源
  • mobileprovision 用于存放provision file 文件
20220319144323.jpg

開始

1.從git上拉取代碼
def gitcloneCode(path, barch)
  #替換成自己的代碼庫地址
  puts "下拉代碼"
  targetGitUrl = path
  targetBranch = barch
  system "git clone -b #{targetBranch} #{targetGitUrl}"
  system "git branch"
  puts "代碼下拉完成"
end
2. 修改bundleid、displayname哀澈、provision file等

可以使用一個json 去配置讀取相關(guān)信息:config.json
// team: 證書名稱 可以從鑰匙串中獲取 注意替換成自己的


20220319175143.jpg

// json文件上半部分 是項(xiàng)目配置信息 下半部分是代碼里的一些配置信息 例如三方sdk key等 注意要和工程項(xiàng)目中的名稱對應(yīng)起來牌借,方便修改

// 有需要其他配置 自行添加
{
 "appname": "測試2",
 "bundleid": "com.ch.test2",
 "version": "1.1",
 "build": "10086",
 "team": "-----------",

  "AAA": "AAAA2",
  "BBB": "BBBB2",
  "CCC": "CCCC2"
}

讀取配置信息

#========================================讀取配置信息
jsonPath = 'config.json'
json = File.read(jsonPath)
configObj = JSON.parse(json)
puts "解析json數(shù)據(jù)#{configObj}"
#bundleid
bundleid = configObj['bundleid']
#todaybundleid
todaybundleid = configObj['todaybundleid']
#appname
appname = configObj['appname']
#version
version = configObj['version']
#build
build = configObj['build']
#team
team = configObj['team']

讀取 mobileprovision

mobileprovision_name = %x(ls mobileprovision).split(' ')[0].split('.')[0]
todaymobileprovision_name = %x(ls todaymobileprovision).split(' ')[0].split('.')[0]
mobileprovision_path = "mobileprovision/" + %x(ls mobileprovision).split(' ')[0]
todaymobileprovision_path = "todaymobileprovision/" + %x(ls todaymobileprovision).split(' ')[0]
mobileprovision_uuid = %x(mobileprovision-read -f #{mobileprovision_path} -o UUID)
todaymobileprovision_uuid = %x(mobileprovision-read -f #{todaymobileprovision_path} -o UUID)
teamId = %x(mobileprovision-read -f #{mobileprovision_path} -o TeamIdentifier).strip

修改基本配置

def updatePlist(path, key, value)
  puts "修改 infoplist: #{path}, #{key}: #{value}"
  infoPlistHash = Xcodeproj::Plist.read_from_path(path)
  infoPlistHash[key] = value
  Xcodeproj::Plist.write_to_path(infoPlistHash, path)
  puts "修改 infoplist完成"
end

###更新app應(yīng)用名稱 path: plist路徑 name: 目標(biāo)名稱
def updateAppName(path, name)
  updatePlist(path, 'CFBundleDisplayName', name)
end
#打開proj
#=======================================更改proj信息
projName = 'repackage.xcodeproj'
targetName = 'repackage'
proj_path = projpath + '/' + projName
puts '解析完成'
#修改app名稱
#infolist 路徑
infoPlistPath = projpath + '/repackage/info.plist'
updateAppName(infoPlistPath, appname)

#打開proj
proj = Xcodeproj::Project.open(proj_path)
puts "打開了項(xiàng)目#{proj}"
proj.targets.each do |target|
  # puts target.copy_files_build_phases
  if target.to_s == targetName
    target.build_configurations.each do |b|
      #修改版本號
      b.build_settings['MARKETING_VERSION'] = version
      #修改build
      b.build_settings['CURRENT_PROJECT_VERSION'] = build
      #修改bundleid
      b.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = bundleid
      #修改team
      b.build_settings['team'] = certificate_name
      #PROVISIONING_PROFILE_SPECIFIER
      b.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = mobileprovision_name
      #PROVISIONING_PROFILE
      b.build_settings['PROVISIONING_PROFILE'] = mobileprovision_uuid
    end
end 
proj.save()
puts "修改基本信息完成"

3.修改三方key、baseurl等

# 自定義修改文件內(nèi)容 obj(hash)
def updateCustomFileContent(path, obj) 
   #目標(biāo)文本
   targetTxt = ""
   File.open(path, 'r') do |f|
     targetTxt = f.read()
   end
   File.open(path, 'r') do |f|
    fs = f.readlines
    resf = targetTxt
    fs.each do |line|
      obj.each_key do |key|
        #匹配有對應(yīng)字段的行  若則匹配有誤 請自行修改正則
        regex = /#{key}(.*?)=/
        if regex.match(line)
          #替換文本
          newline = modifierTxt(line, obj[key])
          resf = resf.gsub(line, newline)
        end
      end
    end
    #寫入文件
    File.open(path, 'w') do |f|
      f.write(resf)
    end
   end
end

puts "修改內(nèi)部文件"
puts configObj
commonFilePaths = [
  "#{proj_path}/Config.h"
]
commonFilePaths.each do |path|
  updateCustomFileContent(path, configObj)
end
puts "內(nèi)部文件內(nèi)容修改完成"

4.修改appIcon割按、其他資源圖片

注意不能使用jpg格式 不然chunky_png可能會讀取不出來
替換appIcon 只需將不同尺寸的Icon添加到images/appIcon文件夾下就好 已經(jīng)通過chunky_png 實(shí)現(xiàn)解析尺寸覆蓋原來的圖片
替換其他資源圖片 需要和項(xiàng)目中的資源文件夾名稱相同 具體查看源碼

# 自定義修改文件內(nèi)容 obj(hash)
def updateCustomFileContent(path, obj) 
   #目標(biāo)文本
   targetTxt = ""
   File.open(path, 'r') do |f|
     targetTxt = f.read()
   end
   File.open(path, 'r') do |f|
    fs = f.readlines
    resf = targetTxt
    fs.each do |line|
      obj.each_key do |key|
        #匹配有對應(yīng)字段的行 若則匹配有誤 請自行修改正則
        regex = /#{key}(.*?)=/
        if regex.match(line)
          #替換文本
          newline = modifierTxt(line, obj[key])
          resf = resf.gsub(line, newline)
        end
      end
    end
    #寫入文件
    File.open(path, 'w') do |f|
      f.write(resf)
    end
   end
end

#替換appIcon
def updateAppIcon(iconPath)
  puts iconPath
  #替換icon
  oriIconPath = "images/appIcon"
  iconNames = {
    40 => ["icon_20pt@2x.png"],
    58 => ["icon_29pt@2x.png"],
    60 => ["icon_20pt@3x.png"],
    80 => ["icon_40pt@2x.png"],
    87 => ["icon_29pt@3x.png"],
    120 => ["icon_40pt@3x.png", "icon_60pt@2x.png"],
    180 => ["icon_60pt@3x.png"],
    1024 => ["icon.png"]
  }
  #刪除原先文件
  Dir.foreach(iconPath) do |f|
    if File::file?("#{iconPath}/#{f}")
      puts "del----#{iconPath}/#{f}"
      File::delete("#{iconPath}/#{f}")
    end
  end
  images = []
  Dir.foreach(oriIconPath) do |name|
    if name.include?('png')
      img_path = "#{oriIconPath}/#{name}"
      img = ChunkyPNG::Image.from_file(img_path)
      img_wid = img.dimension.width
      targetNames = iconNames[img_wid]
      if targetNames == nil 
        next
      end
      iconNames.delete(img_wid)
      targetNames.each do |targetName|
        puts targetName
        scale = "1x"
        if targetName.include?("x") 
          scale = /(?<=@).*?(?=.png)/.match(targetName).to_s
        end
        target_path = "#{iconPath}/#{targetName}"
        FileUtils.cp(img_path, target_path)
        puts scale.class
        size = img_wid / scale.to_i
        puts size
        puts size.class
        puts size.to_s == "1024"
        idiom = "iphone"
        if size.to_s == "1024"
          idiom = "ios-marketing"
        end
        puts idiom
        obj = {
          "filename" => targetName,
          "idiom" => idiom,
          "scale" => scale,
          "size" => "#{size}x#{size}",
        }
        images.push(obj)
        puts images
      end
    end
  end
  #寫入json文件
  img_json = {
    "images" => images,
    "info" => {
      "version" => 1,
      "author" => "xcode"
    }
  }
  json_path = "#{iconPath}/Contents.json"
  File.open(json_path, 'w') do |f|
    f.write(img_json.to_json)
  end
end

#遍歷一個文件夾
def browseImageDirectory(route, target_path)
  filepath = "images/other#{route}"
  puts filepath
  Dir.foreach(filepath) do |subPath|
    # puts subPath
    # puts File::directory?(subPath)
    if subPath != ".." && subPath != "."
      if subPath.include?(".imageset")
        puts subPath
         #刪除原來的文件
        to_path = "#{target_path}#{route}/#{subPath}"
        puts to_path
        Dir.foreach(to_path) do |f|
          if File::file?("#{to_path}/#{f}")
            File::delete("#{to_path}/#{f}")
          end
        end
        #轉(zhuǎn)移里面的image
        images = []
        Dir.foreach("#{filepath}/#{subPath}") do |file|
          if file.include?("@2x") || file.include?("@3x")
            puts file
            #替換圖片
            FileUtils.cp("#{filepath}/#{subPath}/#{file}", "#{to_path}/#{file}")
            #拼湊json文件
            scale = /(?<=@).*?(?=.png)/.match(file)
            puts scale
            obj = {
              "idiom" => "universal",
              "filename" => file,
              "scale" => scale,
            }
            images.push(obj)
            puts images
          end
        end
        img_json = {
          "images" => images,
          "info" => {
            "version" => 1,
            "author" => "xcode"
          }
        }
        puts img_json
        #寫入json文件
        json_path = "#{to_path}/Contents.json"
        File.open(json_path, 'w') do |f|
          f.write(img_json.to_json)
        end
      elsif File::directory?("#{filepath}/#{subPath}")
        #如果是文件夾 繼續(xù)遍歷
        browseImageDirectory("#{route}/#{subPath}", target_path)
      end
    end
  end
end

#替換其他圖片 1.必須原工程中存在且文件夾名稱相同 2.只替換2x 3x文件 
def updateOtherImages(path)
  browseImageDirectory("", path)
end

# icon等資源文件替換
def updateImages(path)
  puts "開始替換圖片"
  updateAppIcon("#{path}/AppIcon.appiconset")
  updateOtherImages(path)

  puts "替換圖片結(jié)束"
end

#修改icon
#====================
updateImages("#{projpath}/repackage/Assets.xcassets")

5.打包導(dǎo)出

導(dǎo)出需要用到ExportOptions.plist 文件 將獲取到的信息 填充到我們之前準(zhǔn)備好的文件中

#==================打包===================
puts "開始打包"
build_path = "#{workpath}/build"
archive_path = "#{build_path}/app.xcarchive"
if File::exists?(build_path)
  system "rm -rf #{build_path}"
end
FileUtils.makedirs(build_path)
#1.archive
archive_flag = system "xcodebuild archive -project #{proj_path} -scheme #{targetName} -configuration Release -archivePath #{archive_path}"
if !archive_flag
  puts "archive 失敗"
  exit 1
end
puts "archive完成 開始導(dǎo)出ipa "
method = judgeMobileProvisionType(mobileprovision_path)
#生成plist
plistTxt = ""
File.open('exportplist/ExportOptions.plist','r') do |f|
  plistTxt = f.read()
end
plistTxt = plistTxt.gsub("$method", method)
plistTxt = plistTxt.gsub("$boundid", bundleid)
plistTxt = plistTxt.gsub("$mobileprofilename", mobileprovision_name)
plistTxt = plistTxt.gsub("$teamID", teamId)
plist_path = "#{build_path}/ExportOptions.plist"
File.open(plist_path,'w') do |f|
  f.write(plistTxt)
end
# 導(dǎo)出ipa
ipa_path = "#{build_path}/app"
result = system "xcodebuild -exportArchive -archivePath #{archive_path} -exportPath #{ipa_path} -exportOptionsPlist #{plist_path}"
if result
  puts "導(dǎo)出成功"
else 
  puts "導(dǎo)出失敗"
  exit 0
end
#刪除archive
FileUtils.cp("#{ipa_path}/#{targetName}.ipa", "#{build_path}/app.ipa")
system("rm -rf #{ipa_path}")
system("rm -rf #{plist_path}")
system("rm -rf #{archive_path}")
system("open #{build_path}")

printInterestingLog()

使用

  1. 將json文件內(nèi)容修改為自己想要的內(nèi)容
  2. 把provision file文件拖入到mobileprovision文件夾內(nèi)
  3. 進(jìn)入到根目錄 執(zhí)行
ruby repackage.rb

執(zhí)行成功 獲取到ipa包 對應(yīng)的資源已全部修改完畢


20220319173705.jpg
20220319172029.jpg
20220319170535.jpg

至此一個自動化打包的腳本就完成了走哺,但是證書部分還是得手動去添加。推薦一個功能十分強(qiáng)大的工具 能實(shí)現(xiàn)證書部分的自動化: fastlane。等成功實(shí)現(xiàn)證書部分的自動化之后再寫一篇記錄一下丙躏。最后貼上源碼git地址择示,僅供參考。

參考文獻(xiàn)
ruby菜鳥教程
iOS自動打包之xcodeproj
xcodeproj官方文檔
chunky_png

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晒旅,一起剝皮案震驚了整個濱河市栅盲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌废恋,老刑警劉巖谈秫,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鱼鼓,居然都是意外死亡拟烫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進(jìn)店門迄本,熙熙樓的掌柜王于貴愁眉苦臉地迎上來硕淑,“玉大人,你說我怎么就攤上這事嘉赎≈孟保” “怎么了?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵公条,是天一觀的道長拇囊。 經(jīng)常有香客問我,道長靶橱,這世上最難降的妖魔是什么寥袭? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮关霸,結(jié)果婚禮上传黄,老公的妹妹穿的比我還像新娘。我一直安慰自己谒拴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布涉波。 她就那樣靜靜地躺著英上,像睡著了一般。 火紅的嫁衣襯著肌膚如雪啤覆。 梳的紋絲不亂的頭發(fā)上苍日,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天,我揣著相機(jī)與錄音窗声,去河邊找鬼相恃。 笑死,一個胖子當(dāng)著我的面吹牛笨觅,可吹牛的內(nèi)容都是我干的拦耐。 我是一名探鬼主播耕腾,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼杀糯!你這毒婦竟也來了扫俺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤固翰,失蹤者是張志新(化名)和其女友劉穎狼纬,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骂际,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疗琉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了歉铝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盈简。...
    茶點(diǎn)故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖犯戏,靈堂內(nèi)的尸體忽然破棺而出送火,到底是詐尸還是另有隱情,我是刑警寧澤先匪,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布种吸,位于F島的核電站,受9級特大地震影響呀非,放射性物質(zhì)發(fā)生泄漏坚俗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一岸裙、第九天 我趴在偏房一處隱蔽的房頂上張望猖败。 院中可真熱鬧,春花似錦降允、人聲如沸恩闻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽幢尚。三九已至,卻和暖如春翅楼,著一層夾襖步出監(jiān)牢的瞬間尉剩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工毅臊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留理茎,地道東北人。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像皂林,于是被迫代替她去往敵國和親朗鸠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內(nèi)容