轉載請注明出處:http://www.cnblogs.com/fangkm/p/3405959.html
Chromium項目采用Grit工具來打包生成程序需要的資源,如圖片資源、字符串資源等,尤其是字符串資源,牽涉到國際化的問題。Chromium為需要的資源創建單獨的項目工程,工程類型為實用工具,自定義工程的生成事件, 在CustomBuild里調用grit命令,根據grd資源描述文件生成相關的資源。如chrome_strings工程生成國際化字符串資源、chrome_resources工程生成除字符串以外的資源,比如圖片資源。
Grit工具接受grd資源描述文件,生成.h、.rc、.pak等文件。工具位於src\tools\grit文件夾下,采用python腳本編制。
src\tools\gritsettings下有個resource_ids文件,該文件定義Chromium工程中所有的grd文件所生成的資源編號開始區段,從而確保所有的資源ID不發生沖突。該文件格式描述如下:
{
"SRCDIR": "../..",
"chrome/browser/browser_resources.grd": {
"includes": [500],
"structures": [750],
},
"chrome/app/chromium_strings.grd": {
"messages": [11500],
},
}
整個文件內容就是python的dict的定義,python的eval函數能直接該文件內容以字符串的方式讀取,並生成dict結構,省去繁雜的解析過程, python真是一門神奇的語言。
Grit工具使用示例:
python '..\..\..\tools\grit\grit.py' '-i' ' chromium_strings.grd' 'build' '-f' '..\..\..\tools\gritsettings\resource_ids'
'-o' '$(OutDir)obj\global_intermediate\ui\ui_strings' '-D' '_chromium' '-E' 'CHROMIUM_BUILD=chromium' '-D' 'toolkit_views' '-D' 'use_aura' '-D' 'use_ash' '-D' 'remoting' '-D' 'enable_extensions' '-D' 'enable_printing' '-D' 'enable_themes' '-D' 'enable_app_list' '-D' 'enable_settings_app' '-D' 'enable_google_now' '-D' 'use_concatenated_impulse_responses' '-D' 'enable_webrtc' '-D' 'enable_mdns'
命令行參數: -i指定grd源文件;
build指定生成工具,類似的工具還有buildinfo、count等等,
每種工具對應於grit/tool下面的相應python文件,如build對應tool下面的build.py文件,詳情請參考grit_runner.py中的相關定義
build后面的命令行參數都是build.py執行的的參數
目前我見到的生成示例,無論是字符串資源還是圖片等其他資源,都是用build工具生成,下面就簡單走一遍build.py的執行流程。
build.py主要通過提供RcBuilder類來完成解析操作. RcBuilder類派生自interface.Tool, Tool類是各種與build同級的tools的接口類,提供有ShortDescription和Run接口。
其Run傳入的命令行參數為:
-o 指定資源生成的目錄
-D 指定類似於C語言的預處理宏定義,grd描述中有條件控制生成節點的邏輯,
這些定義宏可以當命令開關
-f 指定first_ids_file,即前面提到的gritsettings\resource_ids文件
-E 設置grit內部使用的環境變量
Run函數內部使用grd_reader.py的Parse方法來解析輸入的grd文件,該Parse方法將xml描述的grd文件解析成樹狀節點結構,方法返回值為樹的根節點對象GritNode。grd_reader.py的解析流程容后細說。
在得到Parse函數返回的根節點后,運行根節點的RunGatherers方法,該方法內部遍歷所有子節點,調用節點的RunPostSubstitutionGatherer(如果有的話), 目前只發現FileNode節點有此方法,用於解析對應的file元素指定的xtb文件。為了不影響整個框架流程的表述,解析xtb文件的流程也容稍后再表。
在節點樹數據准備完畢后,接着就需要生成資源文件了.在RcBuilder的Process方法中遍歷outputs節點下的所有output項,根據type屬性指定的資源類型來選擇grit.format下對應的打包工具。
如: type="data_package" 選擇grit.format.data_pack工具;
type="rc_header" 選擇grit.format.rc_header工具;
其他格式參見tool/build.py代碼中的_format_modules映射
grit.format下對應的工具會將所有的資源節點拼接成格式化的字符串並返回, Process方法將RcBuilder將返回的格式串以相應的編碼格式保存到output節點指定的文件中。
資源格式化簡介:以rc_header和data_pack為例
rc_header.py解析: rc_header是所有grd文件都必須生成的資源類型,該格式為grd的資源項生成相應的
.h文件,如chromium_strings.h, 里面保存資源的唯一ID值,供C++代碼運行期使用.
每一項的形式為: #define name節點值 編號值.
該腳本內部會遍歷root節點下的所有資源節點,為每個節點生成一個唯一的編號,
外部可以通過GetIds來訪問這個生成的id映射,映射格式為node.GetTextualIds() : id
節點的GetTextualIds返回節點的標志性文本,一般為name屬性指定的文本,即.h文件中
的#define宏名部分,所以資源的標識名不能沖突。
node的id生成規則大致如下:
1. 優先采用節點自定義的GetId方法
2. 采用group節點的first_id屬性和本節點的offset屬性做累加
3. 采用group節點的first_id屬性(如果沒有,則根據節點的的name
屬性值做md5計算)做累加計值
data_pack.py解析: 該格式為grd資源生成.pak格式的文件。其通過遍歷每個資源節點,獲取到指定語言對應的
文本value(通過節點對象的GetDataPackPair函數獲取) ,將節點的id和value值以一定的格式
保存到pak文件中
grd_reader生成節點流程
在介紹生成節點的流程之前,首先介紹下grd中資源節點的類型。在node. mapping.py文件中有如下映射關系:
_ELEMENT_TO_CLASS = {
'identifiers' : empty.IdentifiersNode,
'includes' : empty.IncludesNode,
'messages' : empty.MessagesNode,
'outputs' : empty.OutputsNode,
'structures' : empty.StructuresNode,
'translations' : empty.TranslationsNode,
'include' : include.IncludeNode,
'emit' : io.EmitNode,
'file' : io.FileNode,
'output' : io.OutputNode,
'ex' : message.ExNode,
'message' : message.MessageNode,
'ph' : message.PhNode,
'else' : misc.ElseNode,
'grit' : misc.GritNode,
'identifier' : misc.IdentifierNode,
'if' : misc.IfNode,
'part' : misc.PartNode,
'release' : misc.ReleaseNode,
'then' : misc.ThenNode,
'structure' : structure.StructureNode,
'skeleton' : variant.SkeletonNode,
}
左邊的key是grd文件中xml節點名,右邊是grit生成的python對象,外部通過ElementToClass方法來訪問該映射,從而決定生成什么內存對象。節點的繼承結構簡略如下:這里僅僅列舉等下需要講解的節點以及節點的方法。
Node為所有節點的基類, GritNode為樹的根節點,程序可以通過此入口遍歷其所有的子節點。
OutputNode節點對應grd文件中的output節點,表示要生成的資源文件
FileNode節點對應grd文件中的file節點,通常做為translations子節點,指定某種語言本地化需要的xtb翻譯文件
MessageNode節點表示需要打包的文本資源項,同樣的資源項還有IncludeNode和StructuresNode節點,用於保存圖片等其他資源類型,由於精力有限,這里就不討論 IncludeNode和StructuresNode節點了。
grd_reader使用xml.sax庫來解析grd文件,從xml.sax.handler.ContentHandler類派生出一個GrdContentHandler類來接收xml的解析回調. GrdContentHandler在startElement方法中根據讀取到的節點名來生成與其對應的內存節點對象,之后調用節點的StartParsing做解析的相關定制處理,再調用節點的HandleAttribute方法讀取節點的所有屬性值。由於不熟悉python的使用, 我當時閱讀此段代碼時,很疑惑用棧的方式定位節點之間的父子關系,想了很一會才理解xml.sax.handler.ContentHandler類在解析某個節點開始時調用startElement方法,此時將自己壓棧,結束時調用endElement方法,此時將自己出棧。在解析子節點時,父節點還沒有解析完成,故父節點對應的endElement還未被調用,所以棧頂就是當前節點的父節點。
解析完畢后, grd_reader調用GritNode根節點的AssignFirstIds方法, 解析之前指定的gritsettings\resource_ids, 遍歷所有的GroupingNode子節點(如MessagesNode、IncludesNode等節點),添加相應的first_id屬性值(由當前的grd文件名和節點的name屬性值在resource_ids中定位)。
字符串本地化的翻譯流程:
基類Node節點有一uberclique成員,如果該成員沒設定,則向上取父節點的uberclique值,直到根節點(如果沒有就創建一個clique.UberClique對象)。當前只有根節點的此成員有值。
在 grd_reader.py解析grd生成節點的時候,資源節點(如message節點)會調用tclib.py為節點的文本部分生成一個tclib.Message對象,該對象為節點的文本value生成一個唯一的標識ID, 即xtb文件中的translation節點的id部分。 如果節點指定use_name_for_id屬性為true,則直接用name屬性值做translation的id部分, 這樣就可以滿足在英語中相同的單詞在其他語種中需要不同的表達的場景,因為根據相同的文本生成的標識ID肯定是相同的。
文本生成標識ID的規則的代碼在grit.extern.tclib.GenerateMessageId函數中,根據node的value文本和meaning屬性(如果有的話)調用FP.FingerPrint做md5計算,取計算后的前16位.
根據生成的tclib.Message對象,調用UberClique的MakeClique方法生成一個MessageClique對象,並添加在UberClique內部維護的映射中。外部需要翻譯message節點中對應語言的文本時,調用MessageNode的Translate方法,該方法調用MakeClique生成的MessageClique對象的MessageForLanguage方法,傳入需要翻譯的語言類型,返回MessageClique內部lang對應的 tclib.Translation對象。
下面探討下MessageClique對象內部lang與tclib.Translation對象的映射是什么時候填充的。
前面提到的RcBuilder的Run方法在節點樹結構生成完成后, 調用GritNode的RunGatherers方法,該方法遍歷所有FileNode子節點,調用RunPostSubstitutionGatherer, 內部調用xtb_reader.Parse函數,傳入根節點uberclique成員的GenerateXtbParserCallback函數生成的回調來保存解析到的id:value,下面看看UberClique類的GenerateXtbParserCallback具體實現:
GenerateXtbParserCallback函數傳入當前file節點的語言類型,返回一個回調函數,函數形式為Callback(id, structure)
第一個參數id為translation的id部分,
第二個參數structure參數為translation值部分,單詞序列形式,
內部根據id和structure數據生成一個tclib.Translation對象,並保存到UberClique類的cliques_成員里。
cliques_成員是一個字典對象,存放文本串的id 到 MessageClique對象序列的映射
MessageClique對象內部存放{ lang : tclib.Translation }的映射