ruby實現(xiàn)podfile依賴SDK解析

iOS依賴的SDK一般都是通過Cocoapod來管理的,我們一般會把依賴的SDK的信息描述在Podfile文件中,比如SDK的name、branch辛块、version等,但是podfile中的代碼都是ruby代碼铅碍,我們?nèi)绾螌⒁蕾嚨腟DK解析成json格式的文件呢润绵?比如下面podfile中的依賴

pod 'AFNetWorking','1.1.26'

或者

pod 'Reachability', :git => 'xxx.git', :tag => 'v3.2.1', :branch=> 'dev'

我們要把他解析成

{
    "AFNetWorking": {
        "comes_from": "",
        "version": "1.1.26"
    },
    "Reachability": {
        "comes_from": "tag",
        "version": "v3.2.1",
    },
}

下面我們就來實現(xiàn)這個解析過程。

基本語法

首先需要做的是胞谈,看懂一個 Podfile尘盼。那么需要了解一些最基本的 ruby 語法,這部分非常簡單:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
pod 'FLEX', :configurations => ['Debug'], :branch => 'develop'
use_frameworks!

以上三行代碼是 Podfile 中最為常見的烦绳,其實這三行是在調(diào)用不同的方法卿捎。

方法調(diào)用

Ruby 中,方法調(diào)用的參數(shù)列表可以以空格形式接在方法名后径密,多個參數(shù)以逗號隔開午阵,所以等價于:

source 'https://github.com/CocoaPods/Specs.git'
# =>
source('https://github.com/CocoaPods/Specs.git')

platform :ios, '8.0'
# =>
platform(:ios, '8.0')

如果是最后一個參數(shù)是字典,那么字典的大括號也可以省略享扔,所以 pod 的調(diào)用等價于:

pod 'pop', 1.0.7 :configurations => ['Debug'], :branch => 'develop'
#=>
map = {
  :configurations => ['Debug'],
  :branch => 'develop'
}
pod('pop',1.0.7, map)

符號(Symbol)

Symbol是 Ruby 中的一種對象類型底桂,一般作為名稱標(biāo)簽,為了不影響閱讀惧眠,我把 Symbol 的定義放在最后籽懦,這里可以暫且把它當(dāng)做前面加了 : 的 string。
所以氛魁,上面的代碼中暮顺,出現(xiàn)的 :ios 厅篓, :configuration:branch 以及常見的 :git 拖云, :tag 等都是 Symbol 贷笛。

方法定義

Ruby 的方法定義更加靈活,語義也更加豐富宙项。

方法名
比如 nil? 乏苦, empty?merge!這類方法尤筐。

方法名小寫汇荐,可包含!?這類符號盆繁。用法可以學(xué)習(xí)系統(tǒng)的定義:

? 常用于判斷掀淘,取代了 is_ 開頭的定義習(xí)慣。
! 常用于需要注意的方法油昂,比如 arr.merge!(other_arr) 表示合并到 arr 革娄;與之對應(yīng)的是 arr.merge(other_arr) ,表示合并冕碟,但不修改 arr 拦惋,而是返回合并后的結(jié)果。
在很多開源庫中安寺, ! 的用法就比較巧妙厕妖,有可能并不表示在當(dāng)前對象上進(jìn)行修改,僅僅為了優(yōu)雅好看也是可能的挑庶。

所以言秸,Podfile 中出現(xiàn)的 use_frameworks!也是在調(diào)用方法。
參數(shù)列表
為了簡單迎捺,這里僅介紹可空的參數(shù)定義举畸。還是以 pod 方法舉栗子:

pod 'Masonry'
pod 'pop', '~> 1.0.7'
pod 'Reachability', :git => 'xxx.git', :tag => 'v3.2.1'

常見的 pod 調(diào)用如上,通過調(diào)用就能猜出 pod方法的聲明:

# pname: 庫名
# version: 指定版本凳枝,且可空
# map: 用鍵值對接收其他參數(shù)
def pod(pod_name, version = nil, **map)
  # ...
end

大致就是這樣抄沮,這里的 * 和指針沒關(guān)系 :new_moon_with_face:。完整參數(shù)列表的定義方式范舀,我寫在文末吧合是。
返回值
其實解析這部分用不上返回值,不過可以介紹一下锭环。Ruby 返回值有以下幾個特點:
如果是最后一行聪全,可以不寫 return 。
支持多個返回值辅辩。

代碼塊(Block)

這個和 Objective-C 差不多难礼,常用于回調(diào)娃圆。當(dāng)然 Podfile 也不缺少:

target :Meitu do
  pod 'Masonry'
end

do...end 可以看成大括號, :Meitutarget 方法的第一個參數(shù)蛾茉。綜合之前介紹的語法讼呢,target 的定義就呼之欲出了:

# tname: target 名稱
# block: 回調(diào)
def target(tname, &block)
  # ...
  # 調(diào)用
  yield if block_given?
end

語法到這里就基本夠用了,接著介紹如何解析谦炬。

解析

既然 Podfile 中是 Ruby 代碼悦屏,也就表示,可以通過調(diào)用 Ruby 腳本的方式键思,直接執(zhí)行 Podfile础爬。

ruby ~/Desktop/Podfile

然后就報錯了…(編譯器又不知道 sourcepod 這都是些什么方法…

定義方法

首先需要定義解析需要調(diào)用的方法吼鳞,讓指定的變量乖乖的被對應(yīng)參數(shù)接收看蚜。最簡易的版本,需要實現(xiàn) targetpod兩個方法:
target
工程可能對應(yīng)多個 target赔桌,具體要解析哪個 target供炎,需要對應(yīng)到打包時指定的 target,所以采用外部傳入的方式: $target_argv

def target(target_name = nil, &block = nil)
  # target name 可能是 String疾党,可能是 Symbol音诫,統(tǒng)一 to_s 一下
  # 如果不是當(dāng)前打包的 target,直接返回就行了
  return if target_name.to_s != $target_argv
  # 調(diào)用 block
  yield if block_given?
end

pod
實現(xiàn) pod 以后仿贬,就可以通過參數(shù)讀取這種值了纽竣。同樣墓贿, pod 可能包含 configuration 信息茧泪,這也是需要對應(yīng)打包的 configuration 參數(shù)的:

def pod(pod_name, version = nil, **args)
  git = args[:git]
  branch = args[:branch]
  tag = args[:tag]
  commit = args[:commit]
  configurations = args[:configurations]

  # 如果 pod 指定了 configuration,則判斷是否包含當(dāng)前 configuration
  unless configurations.nil?
    return unless configurations.include?($configuration)
  end

  # 通過哪種方式引用聋袋,這里可以通過 tag队伟、commit、branch 的 nil? 來判斷來源
  comes_from = "tag"

  # $map 為全局變量
  $map[pod_name] = {
    comes_from: comes_from,
    version: version
  }
end

method_missing

除了 targetpod 方法外幽勒,Podfile 中還存在 source 嗜侮、 platform 等各種各樣的方法,一一實現(xiàn)是不可能的啥容。對此锈颗,Ruby 提供了 method_missing 方法,該方法的作用類似于消息轉(zhuǎn)發(fā)咪惠。當(dāng)程序調(diào)用沒有實現(xiàn)的方法時击吱,統(tǒng)一走 method_missing

# m: 方法名
# args: 位置參數(shù)(也就是數(shù)組)
def method_missing(m, *args); end

導(dǎo)出

到此遥昧,整個解析就已經(jīng)完成了覆醇,比起以前用正則寫的版本朵纷,清爽了很多。最后一步永脓,將解析結(jié)果導(dǎo)出為 JSON 文件袍辞。代碼很簡單:

File.open($result_path, "w") do |f|
  f.write(JSON.pretty_generate($map))
end

Podfile.lock

這里簡單提一下 lock 文件,因為 lock 文件中有準(zhǔn)確的版本號常摧,所以對應(yīng)引用版本都從 lock 當(dāng)中讀取搅吁。而 lock 文件其實是 yaml 格式的,可以通過 yaml 庫將它解析為 hash 和 array 進(jìn)行讀取落午。

整個流程

那么似芝,應(yīng)該如何將解析和導(dǎo)出兩個步驟串起來呢?方法需要定義在代碼開頭板甘,導(dǎo)出需要放在代碼末尾党瓮,所以有了以下結(jié)構(gòu):

# 定義

#INJECT_PODFILE#

# 導(dǎo)出

然后整個文件其實就是一個模板, inject_template.rb 盐类,在解析之前寞奸,將 #INJECT_PODFILE# 替換為 Podfile 的內(nèi)容,最后是調(diào)用和傳參:

ruby inject_template.rb target_name configuration_name result_path

其他

Symbol

symbol是 Ruby 中最為基礎(chǔ)的對象類型在跳,存儲在 Symbol Table 中枪萄,可以看做 name 和 ID 的對應(yīng)。Symbol 不可寫猫妙,地址不變瓷翻,全局唯一。這和 String 不同割坠,兩個值相同的 String齐帚,其實是不同的地址。

"some_string".object_id == "some_string".object_id #=> false
:some_string.object_id == :some_string.object_id #=> true

類似于 Java 的 Stringstatic String 彼哼,一個是用完重新分配对妄,一個是始終是一個存儲單元。針對于這個特性敢朱,Symbol 的效率會比 String 高一些剪菱。常用于成員變量名,hash 的 key 等拴签。

后記 2019-11-18

上面的整個流程部分還有些問題孝常,比如#INJECT_PODFILE如何替換為Pofile中的內(nèi)容呢, 其實對于我們來說# 定義#導(dǎo)出的一般是不會變化的蚓哩,而Podfile中的內(nèi)容可能隨時都會變動构灸,我們不可能每次都要手動把Podfile中的內(nèi)容替換#INJECT_PODFILE,這種重復(fù)性的工作交給腳本來實現(xiàn)就行了杖剪,這里我們定義一個source.rb文件冻押,該文件中只有# 定義#導(dǎo)出的代碼驰贷,我們在shell腳本中執(zhí)行一個copy指令,每次會把source.rb copy一份到destination.rb中洛巢,然后將Podfile中的內(nèi)容插入到destination.rb# 定義#導(dǎo)出中間就可以了括袒。
source.rb代碼實例

#!/usr/bin/ruby
require 'json'

$map = Hash.new

def target(target_name = nil, &block)
  # target name 可能是 String,可能是 Symbol稿茉,統(tǒng)一 to_s 一下
  # 如果不是當(dāng)前打包的 target锹锰,直接返回就行了
#  return if target_name.to_s != $target_argv
  puts "當(dāng)前的target和變量保持一致!"
  # 調(diào)用 block
  yield if block_given?
end

def pod(pod_name, version = nil, **args)
  git = args[:git]
  branch = args[:branch]
  tag = args[:tag]
  commit = args[:commit]
  configureations = args[:configureations]
  comes_from = "tag"
  $map[pod_name] = {
    comes_from: comes_from,
    version: version
  }
end

def method_missing(m, *args); end


File.open("rubyJson.json", "w+") do |f|
  f.write(JSON.pretty_generate($map))
end
  

整合Podfile腳本示例漓库,Integrate.sh

#!/bin/bash
cp -f source.rb destination.rb
sed -i "" '/method_missing/r Podfile' destination.rb
ruby destination.rb

后記2019-11-19

本地工程里的Podfile我們已經(jīng)解析出來了恃慧,但是如果我們想要和gitlab工程中的某一個版本的Podfile做對比,那么我們也要解析出gitlab中某個版本的Podfile渺蒿,主要有以下步驟:
1痢士、通過gitlab提供的api獲取訪問工程里的Podfile文件內(nèi)容
2、將podfile中的依賴版本信息轉(zhuǎn)換成json格式并保留在本地temp文件中茂装。
3怠蹂、執(zhí)行比對腳本,列出不一樣的依賴版本信息少态。

對于第一步城侧,我們可以根據(jù)gitlab api中的get-file-from-repository說明,可以知道彼妻,如果要訪問工程文件嫌佑,需要:access_token項目id侨歉、file_path這三個參數(shù)

1> access_token獲任菀 :先我們可以在userSetting ---> AccessToken--->Add a personal access token,添加一個access_token,注意添加accessToken時scope要選擇api,否則會報訪問權(quán)限錯誤为肮。生成的token要copy一份摊册,否則添加以后就看不到了肤京。
2> 項目id獲燃昭蕖:setting—>General
3> file_path獲取:Repository --->Files---> 鍵盤點擊t --->項目后的輸入框中中輸入的路徑才是file_path

我們新建一個request.sh,獲取這三個參數(shù)后我們就可以用curl來請求獲取Podfile內(nèi)容了

curl --request GET --header 'PRIVATE-TOKEN: <your_access_token>' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fmodels%2Fkey%2Erb?ref=master'

但是我們發(fā)現(xiàn)忘分,請求返回的是內(nèi)容是

{
  "file_name": "key.rb",
  "file_path": "app/models/key.rb",
  "size": 1476,
  "encoding": "base64",
  "content": "IyA9PSBTY2hlbWEgSW5mb3...",
  "content_sha256": "4c294617b60715c1d218e61164a3abd4808a4284cbc30e6728a01ad9aada4481",
  "ref": "master",
  "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83",
  "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50",
  "last_commit_id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d"
}

其中content是podfile中真正的內(nèi)容棋枕,但是他被base64編碼了,所以我們需要解析返回的json中的content字段妒峦,并用base64 decode, 通常sh中我們用
jq
來解析json,通過jq -r .content我們就可以解析處理content字段的內(nèi)容重斑,注意這里的-r可以去掉content字段中的引號
通過jq -r .content > temp我們可以將content字段中的內(nèi)容保存到本地temp中肯骇,但是temp中的是base64 encode的內(nèi)容窥浪,所以我們要decode ,并將decode的內(nèi)容保存到新的文件,base64 -D temp > remotePodfile,這樣我們就將遠(yuǎn)程的Podfile祖很,讀取到本地了。接下來就是和前面的步驟一樣漾脂,通過ruby將remotePodfile中的內(nèi)容解析成json文件假颇。

對于第二步,我們已經(jīng)獲取到了remote的Podfile骨稿,那么可以通過上面的Integrate.sh腳本來實現(xiàn)remotePodfile的解析笨鸡,但是我們發(fā)現(xiàn)Integrate.shsource.rb中的對解析的Podfile源文件和解析后生成的json文件路徑都是寫死的,那么需要我們通過參數(shù)做進(jìn)一步區(qū)分坦冠,如果是解析的是remotePodfile形耗,那么Integrate.sh中的
sed -i "" "/method_missing/r Podfile" destination.rb
應(yīng)改為
sed -i "" "/method_missing/r remotePodfile" destination.rb
source.rb中的

File.open("rubyJson.json", "w+") do |f|
  f.write(JSON.pretty_generate($map))
end

應(yīng)改為

File.open("remote.json", "w+") do |f|
  f.write(JSON.pretty_generate($map))
end

所以我們只需要給這兩個文件設(shè)置一個參數(shù)控制文件的讀取和輸出路徑就可以了≌藁耄可以通過環(huán)境變量來來實現(xiàn)參數(shù)的傳遞激涤,ruby中的環(huán)境變量都是通過ENV對象管理的,我們定義一個REMOTE的環(huán)境變量判呕,如果是REMOTE存在那么我問就將json的輸出路徑改為
remote.json昔期,否則還是之前的rubyJson.json;在Integrate.sh中佛玄,我們可以通過$1硼一,$2...接收傳遞的參數(shù),我們將Integrate.sh接收的第一個參數(shù)定義為源文件的名稱梦抢,如果設(shè)置了源文件名稱那么我們就解析同目錄下的源文件般贼,如果沒有設(shè)置,默認(rèn)讀取的是目錄下Podfile文件奥吩。但Integrate.sh會執(zhí)行destination.rb文件哼蛆,我們要根據(jù)$1的值,判斷是否向destination.rb傳遞環(huán)境變量霞赫,如果我們傳入的$1為remotePodfile腮介,那么Integrate.sh中執(zhí)行ruby時應(yīng)該是REMOTE=1 ruby destination.rb -e "ENV['REMOTE']"

第三步: 只要不叫兩個json文件的內(nèi)容就可以了。

參考
ruby
sh
gitlab api get file
gitlab api finder

jq工具

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叠洗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子腾节,更是在濱河造成了極大的恐慌庆冕,老刑警劉巖愧杯,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邑闺,死亡現(xiàn)場離奇詭異陡舅,居然都是意外死亡灾炭,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來燕刻,“玉大人,你說我怎么就攤上這事×裥ィ” “怎么了勋功?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵潜的,是天一觀的道長。 經(jīng)常有香客問我亡呵,道長锰什,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任罪既,我火速辦了婚禮萝衩,結(jié)果婚禮上千劈,老公的妹妹穿的比我還像新娘。我一直安慰自己喜滨,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般七蜘。 火紅的嫁衣襯著肌膚如雪垒在。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天签舞,我揣著相機與錄音秕脓,去河邊找鬼。 笑死儒搭,一個胖子當(dāng)著我的面吹牛吠架,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播搂鲫,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼傍药,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了魂仍?” 一聲冷哼從身側(cè)響起拐辽,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎擦酌,沒想到半個月后俱诸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡赊舶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年睁搭,在試婚紗的時候發(fā)現(xiàn)自己被綠了赶诊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡介袜,死狀恐怖甫何,靈堂內(nèi)的尸體忽然破棺而出出吹,到底是詐尸還是另有隱情遇伞,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布捶牢,位于F島的核電站鸠珠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏秋麸。R本人自食惡果不足惜渐排,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灸蟆。 院中可真熱鬧驯耻,春花似錦、人聲如沸炒考。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斋枢。三九已至帘靡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瓤帚,已是汗流浹背描姚。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留戈次,地道東北人轩勘。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像怯邪,于是被迫代替她去往敵國和親绊寻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

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