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