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
可以看成大括號, :Meitu
是 target
方法的第一個參數(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
然后就報錯了…(編譯器又不知道 source
, pod
這都是些什么方法…
定義方法
首先需要定義解析需要調(diào)用的方法吼鳞,讓指定的變量乖乖的被對應(yīng)參數(shù)接收看蚜。最簡易的版本,需要實現(xiàn) target
和 pod
兩個方法:
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
除了 target
和 pod
方法外幽勒,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 的 String
和 static 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.sh
和source.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)容就可以了。