看懂這篇文章須要一點使用waf的經驗,不過也不費事,看看例子也夠了。java
軟件構建系統不像是個不少人在研究的東西,因此在網絡上不多能找到剖析某個構建系統原理、或者闡述構建系統principle的文章。看ns3的過程當中接觸到了waf,發現其文檔waf book[https://waf.io/book/]很好的闡述了構建系統的一些基礎知識,我的認爲比cmake的文檔好一些。由於其核心只有十幾個文件,這個構建系統只須要一個10k+的waf文件,因此能夠放到版本庫裏(像對python的評價同樣,batteries included),惟一要求就是環境中有python,而這對一個開發人員來講顯然不是一件困難的事情。python
|-- Build.py |-- ConfigSet.py |-- Configure.py |-- Context.py |-- Errors.py |-- Logs.py |-- Node.py |-- Options.py |-- Runner.py |-- Scripting.py |-- Task.py |-- TaskGen.py |-- Tools [directory] |-- Utils.py |-- ansiterm.py |-- extras |-- fixpy2.py `-- processor.py
以上即是全部waf的內容,能夠看到涉及到的文件不算多。Tools下包含了不少語言的構建工具,好比c/c++/java/qt/ruby/tex等等,若是本身有能力定製,能夠只保留本身項目裏須要的tool,能夠作到更小。(雖然我的認爲沒有必要)c++
若是是寫編譯語言的(c/c++/rust/go/fc/d),那麼構建系統是天天都在用的。在敲擊make<cr>以後,屏幕上出現了一系列的自動運行的命令,而後就是漫長的等待。用waf也同樣,通常是./waf configure build clean dist...再等機器的轟鳴中止後繼續工做流。waf提供了一些核心的抽象,從而可以表達出構建這個活動的幾個關鍵方面:api
這3個抽象幾乎相互獨立,我的認爲是很好的一個抽象。ruby
每個跟在./waf後面的指令,都對應一個Context。若是是build/configure/list/step/install/uninstall,waf自行提供了對應的Context的子類用於執行這些命令,若是是其餘的自定義函數,那麼就會依託於Context自己,能夠在自定義函數裏用Context自定義的函數,好比recurse來遍歷子目錄執行子目錄裏的同名自定義函數。
若是項目根目錄下的wscript
有do_sth,就能夠./waf do_sth網絡
def do_sth(ctx): ctx.load('compiler_cxx') # 加載工具 ctx.recurse(['src','dep']) # 遍歷子目錄,執行子目錄下wscript裏的do_sth ctx.exec_command('touch foo.txt') ctx.msg('hello')
這裏函數參數ctx就是指向了Context的一個實例,而do_sth是做爲Context上的一個方法而存在的,能夠直觀的理解爲,咱們爲Context增長了一個自定義的do_sth方法,因此能夠自由調用Context裏原本提供的方法。
./waf build執行時綁定的Context是BuildConetxt,在Build.py裏被定義,在waf build的時候,執行的是wscript裏def build(bld)
這個方法。舉一個例子app
def configure(conf): conf.load('compiler_cxx') def build(bld): bld.shlib(source='a.cpp', target='mylib3') bld.program(source='main.cpp', target='app', use='mylib') bld.stlib(target='foo', source='b.cpp') # 直接調用bld bld(features = 'c cprogram glib2', use = 'GLIB GIO GOBJECT', source = 'main.c org.glib2.test.gresource.xml', target = 'gsettings-test')
這裏bld指向了BuildContext的一個實例,這意味着BuildContext裏全部的方法都在這個函數裏都是可用的,能夠經過bld.xxx
來調用。
值得注意的是,在Build.py中,但是找不到shlib/probram/stlib
這3個方法的,可是在這裏卻調用成功沒有報錯,這所有依賴於conf.load('compiler_cxx')
這一句。執行這句話後,就給bld指向的BuildContext實例綁定了shlib/program/stlib
這3個方法。
那直接調用bld()
呢?這個就要看Build.py裏的BuildContex():__call__
方法了。從這裏開始,就涉及到TaskGen
這個抽象了。框架
最終須要執行的編譯指令、中間代碼生成等,每一條都對應一個task,咱們不可能去一個一個的寫task,而是但願以一種聲明式的方法表達想要作的事情,這就是task_gen所完成的任務。從聲明式表達到生成task的這項任務,由waf build完成。在執行的過程當中,會對蒐集到的每一個task_gen執行一下post(),而後這個task_gen就生成了本身全部的task。做爲一個靈活的構建系統,waf提供了不少方法來讓咱們hook到post()的過程當中。對於每一個task,到底該不應執行需不須要執行,它本身會追蹤本身的依賴,職責分離,我很喜歡這個設計思路。
之前一小節爲例,共在build(bld)裏一共進行了4次調用,這意味着生成了4個task_gen的實例,在真正執行構建過程以前,會有一個地方對這4個實例各自調用一下post(),把全部的task_gen都消滅掉,變成task。至於怎麼hook,這是個比較關鍵的點,若是理解了,就能很好的自定義waf了。
首先看看寫好的wscript,它的聲明式體如今什麼地方呢?體如今函數參數裏。得益於python的語言特色,能夠隨便加參數,而後在函數實現裏用**kw來取這些值。這意味着能夠隨便加本身想要的key=value進去,這些加進去的參數是能夠在自定義的hook過程當中取到的,這算是可自定義的一個基礎。(ruby自定義的能力更強,畢竟dsl是其強項,但可能限於ruby的流行程度以及發行版是否默認安裝,讓做者最後選擇了python,不過也已經夠用了)
在post()的過程當中,會從task_gen.meths[]裏依次取出方法來執行,hook的方式就是把自定義的方法塞到這個task_gen.meths[]之中。這隻要在自定義的方法上加一個@TaskGen.taskgen_method的註解就能實現,仍是挺簡潔的吧?聲明式中寫的key=val,都能經過taskgen.key取到,這樣一來,幾乎就得到了無限的能力來自定義構建過程了。
在taskgen.meths[]裏有幾項預約義的方法,waf也提供了指令來讓咱們定製本身方法執行的位置。總而言之,想要什麼內容,直接在wscript裏以key=val的方式指定,而後在本身的方法裏用getattr來取就好了。
這也只是個支持性框架,具體到某個語言(c/c++)是怎麼作的,到後面再看。函數
waf本身會默認起和cpu core相同數量的進程來執行構建認任務,並且構建過程的輸出也很清晰漂亮。waf也提供了lazy的模式,不是一會兒把全部的task_gen都轉化,因此也是用了一些技巧來達成這個目的。在看waf代碼的過程當中,能看到不少pythonic和近乎炫技的技法,可見做者真是把python語言玩弄於股掌之中。工具