這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)碛嘘P(guān)xmake如何通過自定義腳本實(shí)現(xiàn)更靈活地配置,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
創(chuàng)新互聯(lián)建站致力于互聯(lián)網(wǎng)品牌建設(shè)與網(wǎng)絡(luò)營銷,包括成都做網(wǎng)站、網(wǎng)站設(shè)計(jì)、SEO優(yōu)化、網(wǎng)絡(luò)推廣、整站優(yōu)化營銷策劃推廣、電子商務(wù)、移動(dòng)互聯(lián)網(wǎng)營銷等。創(chuàng)新互聯(lián)建站為不同類型的客戶提供良好的互聯(lián)網(wǎng)應(yīng)用定制及解決方案,創(chuàng)新互聯(lián)建站核心團(tuán)隊(duì)10余年專注互聯(lián)網(wǎng)開發(fā),積累了豐富的網(wǎng)站經(jīng)驗(yàn),為廣大企業(yè)客戶提供一站式企業(yè)網(wǎng)站建設(shè)服務(wù),在網(wǎng)站建設(shè)行業(yè)內(nèi)樹立了良好口碑。
xmake是一個(gè)基于Lua的輕量級(jí)現(xiàn)代化c/c++的項(xiàng)目構(gòu)建工具,主要特點(diǎn)是:語法簡單易上手,提供更加可讀的項(xiàng)目維護(hù),實(shí)現(xiàn)跨平臺(tái)行為一致的構(gòu)建體驗(yàn)。
如何通過添加自定義的腳本,在腳本域?qū)崿F(xiàn)更加復(fù)雜靈活的定制。
xmake.lua采用二八原則實(shí)現(xiàn)了描述域、腳本域兩層分離式配置。
什么是二八原則呢,簡單來說,大部分項(xiàng)目的配置,80%的情況下,都是些基礎(chǔ)的常規(guī)配置,比如:add_cxflags
, add_links
等,
只有剩下不到20%的地方才需要額外做些復(fù)雜來滿足一些特殊的配置需求。
而這剩余的20%的配置通常比較復(fù)雜,如果直接充斥在整個(gè)xmake.lua里面,會(huì)把整個(gè)項(xiàng)目的配置整個(gè)很混亂,非常不可讀。
因此,xmake通過描述域、腳本域兩種不同的配置方式,來隔離80%的簡單配置以及20%的復(fù)雜配置,使得整個(gè)xmake.lua看起來非常的清晰直觀,可讀性和可維護(hù)性都達(dá)到最佳。
對(duì)于剛?cè)腴T的新手用戶,或者僅僅是維護(hù)一些簡單的小項(xiàng)目,通過完全在描述配置就已經(jīng)完全滿足需求了,那什么是描述域呢?它長這樣:
target("test")
set_kind("binary")
add_files("src/*.c")
add_defines("DEBUG")
add_syslinks("pthread")
一眼望去,其實(shí)就是個(gè) set_xxx
/add_xxx
的配置集,對(duì)于新手,完全可以不把它當(dāng)做lua腳本,僅僅作為普通的,但有一些基礎(chǔ)規(guī)則的配置文件就行了。
這是不是看著更像配置文件了?其實(shí)描述域就是配置文件,類似像json等key/values的配置而已,所以即使完全不會(huì)lua的新手,也是能很快上手的。
而且,對(duì)于通常的項(xiàng)目,僅通過set_xxx/add_xxx
去配置各種項(xiàng)目設(shè)置,已經(jīng)完全滿足需求了。
這也就是開頭說的:80%的情況下,可以用最簡單的配置規(guī)則去簡化項(xiàng)目的配置,提高可讀性和可維護(hù)性,這樣對(duì)用戶和開發(fā)者都會(huì)非常的友好,也更加直觀。
如果我們要針對(duì)不同平臺(tái),架構(gòu)做一些條件判斷怎么辦?沒關(guān)系,描述域除了基礎(chǔ)配置,也是支持條件判斷,以及for循環(huán)的:
target("test")
set_kind("binary")
add_files("src/*.c")
add_defines("DEBUG")
if is_plat("linux", "macosx") then
add_links("pthread", "m", "dl")
end
target("test")
set_kind("binary")
add_files("src/*.c")
add_defines("DEBUG")
for _, name in ipairs({"pthread", "m", "dl"}) do
add_links(name)
end
這是不是看著有點(diǎn)像lua了?雖說,平??梢园阉?dāng)做普通配置問題,但是xmake畢竟基于lua,所以描述域還是支持lua的基礎(chǔ)語言特性的。
!> 不過需要注意的是,描述域雖然支持lua的腳本語法,但在描述域盡量不要寫太復(fù)雜的lua腳本,比如一些耗時(shí)的函數(shù)調(diào)用和for循環(huán)
并且在描述域,主要目的是為了設(shè)置配置項(xiàng),因此xmake并沒有完全開放所有的模塊接口,很多接口在描述域是被禁止調(diào)用的,
即使開放出來的一些可調(diào)用接口,也是完全只讀的,不耗時(shí)的安全接口,比如:os.getenv()
等讀取一些常規(guī)的系統(tǒng)信息,用于配置邏輯的控制。
!> 另外需要注意一點(diǎn),xmake.lua是會(huì)被多次解析的,用于在不同階段解析不同的配置域:比如:option()
, target()
等域。
因此,不要想著在xmake.lua的描述域,寫復(fù)雜的lua腳本,也不要在描述域調(diào)用print去顯示信息,因?yàn)闀?huì)被執(zhí)行多遍,記?。簳?huì)被執(zhí)行多遍?。?!
限制描述域?qū)憦?fù)雜的lua,各種lua模塊和接口都用不了?怎么辦?這個(gè)時(shí)候就是腳本域出場(chǎng)的時(shí)候了。
如果用戶已經(jīng)完全熟悉了xmake的描述域配置,并且感覺有些滿足不了項(xiàng)目上的一些特殊配置維護(hù)了,那么我們可以在腳本域做更加復(fù)雜的配置邏輯:
target("test")
set_kind("binary")
add_files("src/*.c")
on_load(function (target)
if is_plat("linux", "macosx") then
target:add("links", "pthread", "m", "dl")
end
end)
after_build(function (target)
import("core.project.config")
local targetfile = target:targetfile()
os.cp(targetfile, path.join(config.buildir(), path.filename(targetfile)))
print("build %s", targetfile)
end)
只要是類似:on_xxx
, after_xxx
, before_xxx
等字樣的function body內(nèi)部的腳本,都屬于腳本域。
在腳本域中,用戶可以干任何事,xmake提供了import接口可以導(dǎo)入xmake內(nèi)置的各種lua模塊,也可以導(dǎo)入用戶提供的lua腳本。
我們可以在腳本域?qū)崿F(xiàn)你想實(shí)現(xiàn)的任意功能,甚至寫個(gè)獨(dú)立項(xiàng)目出來都是可以的。
對(duì)于一些腳本片段,不是很臃腫的話,像上面這么內(nèi)置寫寫就足夠了,如果需要實(shí)現(xiàn)更加復(fù)雜的腳本,不想充斥在一個(gè)xmake.lua里面,可以把腳本分離到獨(dú)立的lua文件中去維護(hù)。
例如:
target("test")
set_kind("binary")
add_files("src/*.c")
on_load("modules.test.load")
on_install("modules.test.install")
我們可以把自定義的腳本放置到xmake.lua對(duì)應(yīng)目錄下,modules/test/load.lua
和modules/test/install.lua
中獨(dú)立維護(hù)。
單獨(dú)的lua腳本文件以main作為主入口,例如:
-- 我們也可以在此處導(dǎo)入一些內(nèi)置模塊或者自己的擴(kuò)展模塊來使用
import("core.project.config")
import("mymodule")
function main(target)
if is_plat("linux", "macosx") then
target:add("links", "pthread", "m", "dl")
end
end
這些獨(dú)立的lua腳本里面,我們還可以通過import導(dǎo)入各種內(nèi)置模塊和自定義模塊進(jìn)來使用,就跟平常寫lua, java沒啥區(qū)別。
而對(duì)于腳本的域的不同階段,on_load
主要用于target加載時(shí)候,做一些動(dòng)態(tài)化的配置,這里不像描述域,只會(huì)執(zhí)行一遍哦!!!
其他階段,還有很多,比如:on/after/before
_build/install/package/run
等,我們下面會(huì)詳細(xì)描述。
在講解各個(gè)腳本域之前,我們先來簡單介紹下xmake的模塊導(dǎo)入和使用方式,xmake采用import來引入其他的擴(kuò)展模塊,以及用戶自己定義的模塊,它可以在下面一些地方使用:
自定義腳本(on_build, on_run ..)
插件開發(fā)
模板開發(fā)
平臺(tái)擴(kuò)展
自定義任務(wù)task
導(dǎo)入機(jī)制如下:
優(yōu)先從當(dāng)前腳本目錄下導(dǎo)入
再從擴(kuò)展類庫中導(dǎo)入
導(dǎo)入的語法規(guī)則:
基于.
的類庫路徑規(guī)則,例如:
import("core.base.option")
import("core.base.task")
function main()
-- 獲取參數(shù)選項(xiàng)
print(option.get("version"))
-- 運(yùn)行任務(wù)和插件
task.run("hello")
end
導(dǎo)入當(dāng)前目錄下的自定義模塊:
目錄結(jié)構(gòu):
plugin
- xmake.lua
- main.lua
- modules
- hello1.lua
- hello2.lua
在main.lua中導(dǎo)入modules
import("modules.hello1")
import("modules.hello2")
導(dǎo)入后就可以直接使用里面的所有公有接口,私有接口用_
前綴標(biāo)示,表明不會(huì)被導(dǎo)出,不會(huì)被外部調(diào)用到。。
除了當(dāng)前目錄,我們還可以導(dǎo)入其他指定目錄里面的類庫,例如:
import("hello3", {rootdir = "/home/xxx/modules"})
為了防止命名沖突,導(dǎo)入后還可以指定的別名:
import("core.platform.platform", {alias = "p"})
function main()
-- 這樣我們就可以使用p來調(diào)用platform模塊的plats接口,獲取所有xmake支持的平臺(tái)列表了
print(p.plats())
end
2.1.5版本新增兩個(gè)新屬性:import("xxx.xxx", {try = true, anonymous = true})
try為true,則導(dǎo)入的模塊不存在的話,僅僅返回nil,并不會(huì)拋異常后中斷xmake.
anonymous為true,則導(dǎo)入的模塊不會(huì)引入當(dāng)前作用域,僅僅在import接口返回導(dǎo)入的對(duì)象引用。
一種方式我們可以在on_load等腳本中,直接調(diào)用print去打印模塊的調(diào)用結(jié)果信息,來測(cè)試和驗(yàn)證。
不過xmake還提供了xmake lua
插件可以更加靈活方便的測(cè)試腳本。
比如,我們可以直接指定lua腳本來加載運(yùn)行,這對(duì)于想要快速測(cè)試一些接口模塊,驗(yàn)證自己的某些思路,都是一個(gè)不錯(cuò)的方式。
我們先寫個(gè)簡單的lua腳本:
function main()
print("hello xmake!")
end
然后直接運(yùn)行它就行了:
$ xmake lua /tmp/test.lua
所有內(nèi)置模塊和擴(kuò)展模塊的接口,我們都可以通過xmake lua
直接調(diào)用,例如:
$ xmake lua lib.detect.find_tool gcc
上面的命令,我們直接調(diào)用了import("lib.detect.find_tool")
模塊接口來快速執(zhí)行。
有時(shí)候在交互模式下,運(yùn)行命令更加的方便測(cè)試和驗(yàn)證一些模塊和api,也更加的靈活,不需要再去額外寫一個(gè)腳本文件來加載。
我們先看下,如何進(jìn)入交互模式:
# 不帶任何參數(shù)執(zhí)行,就可以進(jìn)入
$ xmake lua
>
# 進(jìn)行表達(dá)式計(jì)算
> 1 + 2
3
# 賦值和打印變量值
> a = 1
> a
1
# 多行輸入和執(zhí)行
> for _, v in pairs({1, 2, 3}) do
>> print(v)
>> end
1
2
3
我們也能夠通過 import
來導(dǎo)入擴(kuò)展模塊:
> task = import("core.project.task")
> task.run("hello")
hello xmake!
如果要中途取消多行輸入,只需要輸入字符:q
就行了
> for _, v in ipairs({1, 2}) do
>> print(v)
>> q <-- 取消多行輸入,清空先前的輸入數(shù)據(jù)
> 1 + 2
3
在target初始化加載的時(shí)候,將會(huì)執(zhí)行此腳本,在里面可以做一些動(dòng)態(tài)的目標(biāo)配置,實(shí)現(xiàn)更靈活的目標(biāo)描述定義,例如:
target("test")
on_load(function (target)
target:add("defines", "DEBUG", "TEST=\"hello\"")
target:add("linkdirs", "/usr/lib", "/usr/local/lib")
target:add({includedirs = "/usr/include", "links" = "pthread"})
end)
可以在on_load
里面,通過target:set
, target:add
來動(dòng)態(tài)添加各種target屬性,所有描述域的set_
, add_
配置都可以通過這種方式動(dòng)態(tài)配置。
另外,我們可以調(diào)用target的一些接口,獲取和設(shè)置一些基礎(chǔ)信息,比如:
這個(gè)是在v2.2.7之后新加的接口,用于定制化處理target的鏈接過程。
target("test")
on_link(function (target)
print("link it")
end)
覆蓋target目標(biāo)默認(rèn)的構(gòu)建行為,實(shí)現(xiàn)自定義的編譯過程,一般情況下,并不需要這么做,除非確實(shí)需要做一些xmake默認(rèn)沒有提供的編譯操作。
你可以通過下面的方式覆蓋它,來自定義編譯操作:
target("test")
-- 設(shè)置自定義編譯腳本
on_build(function (target)
print("build it")
end)
注:2.1.5版本之后,所有target的自定義腳本都可以針對(duì)不同平臺(tái)和架構(gòu),分別處理,例如:
target("test")
on_build("iphoneos|arm*", function (target)
print("build for iphoneos and arm")
end)
其中如果第一個(gè)參數(shù)為字符串,那么就是指定這個(gè)腳本需要在哪個(gè)平臺(tái)|架構(gòu)
下,才會(huì)被執(zhí)行,并且支持模式匹配,例如arm*
匹配所有arm架構(gòu)。
當(dāng)然也可以只設(shè)置平臺(tái),不設(shè)置架構(gòu),這樣就是匹配指定平臺(tái)下,執(zhí)行腳本:
target("test")
on_build("windows", function (target)
print("build for windows")
end)
注:一旦對(duì)這個(gè)target目標(biāo)設(shè)置了自己的build過程,那么xmake默認(rèn)的構(gòu)建過程將不再被執(zhí)行。
通過此接口,可以用來hook指定target內(nèi)置的構(gòu)建過程,自己重新實(shí)現(xiàn)每個(gè)源文件編譯過程:
target("test")
set_kind("binary")
add_files("src/*.c")
on_build_file(function (target, sourcefile, opt)
end)
通過此接口,可以用來hook指定target內(nèi)置的構(gòu)建過程,替換一批同類型源文件編譯過程:
target("test")
set_kind("binary")
add_files("src/*.c")
on_build_files(function (target, sourcebatch, opt)
end)
設(shè)置此接口后,對(duì)應(yīng)源文件列表中文件,就不會(huì)出現(xiàn)在自定義的target.on_build_file了,因?yàn)檫@個(gè)是包含關(guān)系。
其中sourcebatch描述了這批同類型源文件:
sourcebatch.sourcekind
: 獲取這批源文件的類型,比如:cc, as, ..
sourcebatch.sourcefiles()
: 獲取源文件列表
sourcebatch.objectfiles()
: 獲取對(duì)象文件列表
sourcebatch.dependfiles()
: 獲取對(duì)應(yīng)依賴文件列表,存有源文件中編譯依賴信息,例如:xxx.d
覆蓋target目標(biāo)的xmake [c|clean}
的清理操作,實(shí)現(xiàn)自定義清理過程。
target("test")
-- 設(shè)置自定義清理腳本
on_clean(function (target)
-- 僅刪掉目標(biāo)文件
os.rm(target:targetfile())
end)
覆蓋target目標(biāo)的xmake [p|package}
的打包操作,實(shí)現(xiàn)自定義打包過程,如果你想對(duì)指定target打包成自己想要的格式,可以通過這個(gè)接口自定義它。
target("demo")
set_kind("shared")
add_files("jni/*.c")
on_package(function (target)
os.exec("./gradlew app:assembleDebug")
end)
當(dāng)然這個(gè)例子有點(diǎn)老了,這里只是舉例說明下用法而已,現(xiàn)在xmake提供了專門的xmake-gradle插件,來與gradle更好的集成。
覆蓋target目標(biāo)的xmake [i|install}
的安裝操作,實(shí)現(xiàn)自定義安裝過程。
例如,將生成的apk包,進(jìn)行安裝。
target("test")
-- 設(shè)置自定義安裝腳本,自動(dòng)安裝apk文件
on_install(function (target)
-- 使用adb安裝打包生成的apk文件
os.run("adb install -r ./bin/Demo-debug.apk")
end)
覆蓋target目標(biāo)的xmake [u|uninstall}
的卸載操作,實(shí)現(xiàn)自定義卸載過程。
target("test")
on_uninstall(function (target)
...
end)
覆蓋target目標(biāo)的xmake [r|run}
的運(yùn)行操作,實(shí)現(xiàn)自定義運(yùn)行過程。
例如,運(yùn)行安裝好的apk程序:
target("test")
-- 設(shè)置自定義運(yùn)行腳本,自動(dòng)運(yùn)行安裝好的app程序,并且自動(dòng)獲取設(shè)備輸出信息
on_run(function (target)
os.run("adb shell am start -n com.demo/com.demo.DemoTest")
os.run("adb logcat")
end)
需要注意的是,target:on_xxx的所有接口都覆蓋內(nèi)部默認(rèn)實(shí)現(xiàn),通常我們并不需要完全復(fù)寫,只是額外掛接自己的一些邏輯,那么可以使用target:before_xxx
和target:after_xxx
系列腳本就行了。
所有的on_xxx都有對(duì)應(yīng)的before_和after_xx版本,參數(shù)也完全一致,例如:
target("test")
before_build(function (target)
print("")
end)
在自定義腳本中,除了使用import接口導(dǎo)入各種擴(kuò)展模塊使用,xmake還提供了很多基礎(chǔ)的內(nèi)置模塊,比如:os,io等基礎(chǔ)操作,實(shí)現(xiàn)更加跨平臺(tái)的處理系統(tǒng)接口。
os.cp的行為和shell中的cp
命令類似,不過更加強(qiáng)大,不僅支持模式匹配(使用的是lua模式匹配),而且還確保目的路徑遞歸目錄創(chuàng)建、以及支持xmake的內(nèi)置變量。
例如:
os.cp("$(scriptdir)/*.h", "$(buildir)/inc")
os.cp("$(projectdir)/src/test/**.h", "$(buildir)/inc")
上面的代碼將:當(dāng)前xmake.lua
目錄下的所有頭文件、工程源碼test目錄下的頭文件全部復(fù)制到$(buildir)
輸出目錄中。
其中$(scriptdir)
, $(projectdir)
這些變量是xmake的內(nèi)置變量,具體詳情見:內(nèi)置變量的相關(guān)文檔。
而*.h
和**.h
中的匹配模式,跟add_files中的類似,前者是單級(jí)目錄匹配,后者是遞歸多級(jí)目錄匹配。
上面的復(fù)制,會(huì)把所有文件全部展開復(fù)制到指定目錄,丟失源目錄層級(jí),如果要按保持原有的目錄結(jié)構(gòu)復(fù)制,可以設(shè)置rootdir參數(shù):
os.cp("src/**.h", "/tmp/", {rootdir = "src"})
上面的腳本可以按src
根目錄,將src下的所有子文件保持目錄結(jié)構(gòu)復(fù)制過去。
注:盡量使用os.cp
接口,而不是os.run("cp ..")
,這樣更能保證平臺(tái)一致性,實(shí)現(xiàn)跨平臺(tái)構(gòu)建描述。
此接口會(huì)安靜運(yùn)行原生shell命令,用于執(zhí)行第三方的shell命令,但不會(huì)回顯輸出,僅僅在出錯(cuò)后,高亮輸出錯(cuò)誤信息。
此接口支持參數(shù)格式化、內(nèi)置變量,例如:
-- 格式化參數(shù)傳入
os.run("echo hello %s!", "xmake")
-- 列舉構(gòu)建目錄文件
os.run("ls -l $(buildir)")
此接口相比os.run,在執(zhí)行過程中還會(huì)回顯輸出,并且參數(shù)是通過列表方式傳入,更加的靈活。
os.execv("echo", {"hello", "xmake!"})
另外,此接口還支持一個(gè)可選的參數(shù),用于傳遞設(shè)置:重定向輸出,執(zhí)行環(huán)境變量設(shè)置,例如:
os.execv("echo", {"hello", "xmake!"}, {stdout = outfile, stderr = errfile, envs = {PATH = "xxx;xx", CFLAGS = "xx", curdir = "/tmp"}}
其中,stdout和stderr參數(shù)用于傳遞重定向輸出和錯(cuò)誤輸出,可以直接傳入文件路徑,也可以傳入io.open打開的文件對(duì)象。
另外,如果想在這次執(zhí)行中臨時(shí)設(shè)置和改寫一些環(huán)境變量,可以傳遞envs參數(shù),里面的環(huán)境變量設(shè)置會(huì)替換已有的設(shè)置,但是不影響外層的執(zhí)行環(huán)境,只影響當(dāng)前命令。
我們也可以通過os.getenvs()
接口獲取當(dāng)前所有的環(huán)境變量,然后改寫部分后傳入envs參數(shù)。
另外,還能通過curdir參數(shù)設(shè)置,在執(zhí)行過程中修改子進(jìn)程的工作目錄。
其相關(guān)類似接口還有,os.runv, os.exec, os.execv, os.iorun, os.iorunv等等,比如os.iorun可以獲取運(yùn)行的輸出內(nèi)容。
這塊的具體詳情和差異,還有更多os接口,都可以到:os接口文檔 查看。
此接口,從指定路徑文件讀取所有內(nèi)容,我們可在不打開文件的情況下,直接讀取整個(gè)文件的內(nèi)容,更加的方便,例如:
local data = io.readfile("xxx.txt")
此接口寫入所有內(nèi)容到指定路徑文件,我們可在不打開文件的情況下,直接寫入整個(gè)文件的內(nèi)容,更加的方便,例如:
io.writefile("xxx.txt", "all data")
此接口實(shí)現(xiàn)跨平臺(tái)地路徑拼接操作,將多個(gè)路徑項(xiàng)進(jìn)行追加拼接,由于windows/unix
風(fēng)格的路徑差異,使用api來追加路徑更加跨平臺(tái),例如:
print(path.join("$(tmpdir)", "dir1", "dir2", "file.txt"))
上述拼接在unix上相當(dāng)于:$(tmpdir)/dir1/dir2/file.txt
,而在windows上相當(dāng)于:$(tmpdir)\\dir1\\dir2\\file.txt
上述就是小編為大家分享的xmake如何通過自定義腳本實(shí)現(xiàn)更靈活地配置了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。