寫在前面
這是《BitBake使用攻略》系列文章的第二篇,主要講解BitBake的基本語法。由於此篇的實驗依賴於第一篇的項目,建議先將HelloWorld項目完成之后再食用此篇為好。
第一篇的鏈接在這:BitBake使用攻略--從HelloWorld講起。
1. BitBake中的賦值
1.1 直接賦值
BitBake提供了幾種直接賦值的方式,有如下幾種:
-
= 賦值,這種賦值方式可以在賦值表達式被解析的那一刻為變量賦好值。另外要注意的是,在賦值內容中,字符串的前導和后置空格是不會被省略的,因此下面的變量VAR1和VAR2是不同的值:
VAR1 = " var" VAR2 = "var " # EMPTY_VAR 是空變量 EMPTY_VAR = "" NOEMPTY_VAR = " "
-
?= 默認賦值,這種方式允許你為某個變量定義一個默認值,即如果你在解析這個賦值表達式之前已經為改變量賦好值,那么這個賦值將不會執行,看下面的兩個例子:
# VAR1最后的值為"hello" VAR1 = "hello" VAR1 ?= "world" # VAR2最后的值為"world" VAR2 ?= "world" VAR2 ?= “hello”
-
??= 弱默認賦值,這個賦值是所有賦值里最弱的一個,弱到什么程度呢,弱到即使是對於兩個表達式,其對同一個變量使用??=賦值,bitbake也只會執行后面那個,前面那一個不會執行。這是因為這個賦值它只會在所有解析項解析完之后才會執行,如果在解析完之前有一個表達式為該變量賦好值,那么這個賦值就不會執行,看下面的例子:
# VAR1最后的值為"hello" VAR1 ?= "hello" VAR1 ??= "world" # VAR2最后的值為"world" VAR2 ??= "hello" VAR2 ??= "world"
-
:= 立即賦值,這個效果和=賦值基本一樣,但是在擴展變量的方面還是有些差距,具體參考間接賦值章節。
接下來,我們不妨借用HelloWorld工程測試一下上面的內容,修改printhello.bb
文件,內容如下:
DESCRIPTION = "Prints Hello World"
PN = 'printhello'
PV = '1'
VAR1 = "Hello"
VAR2 := "Hello"
VAR3 ?= "Hello"
VAR4 ?= "Hello"
VAR4 ??= "World"
VAR5 ??= "World"
python do_build() {
bb.plain("********************");
bb.plain("* *");
bb.plain("* Hello, World! *");
bb.plain("* *");
bb.plain("********************");
bb.plain("VAR1: " + d.getVar("VAR1", True))
bb.plain("VAR2: " + d.getVar("VAR2", True))
bb.plain("VAR3: " + d.getVar("VAR3", True))
bb.plain("VAR4: " + d.getVar("VAR4", True))
bb.plain("VAR5: " + d.getVar("VAR5", True))
}
上面我們使用了d.getVar
函數去獲取BB文件中的變量值,具體內容之后的章節會講到,執行bitbake -f printhello
,觀察打印結果:
...
VAR1: Hello
VAR2: Hello
VAR3: Hello
VAR4: Hello
VAR5: World
...
另外你也可以自己設計一些例子去驗證一下我們上面提到的內容,有助於你加深理解。
1.2 間接賦值
間接賦值指的是一個變量的值依賴於另一個變量的賦值,我們通常可以用shell的語法${VAR}
來獲取一個變量的值,例如下面的例子,我們在B中使用了A的值來賦值:
# B的值是Hello World
A = "Hello "
B = "${A}World"
這種賦值方式通常我們借助於=和:=實現,但是其實際使用起來還是有點差距。以A、B為例對於=賦值,在賦值的時候,其並不會直接把A的值展開賦給B,而是在調用B的時候才會展開;相反,對於:=賦值,其在賦值的時候就已經展開賦值給B了,所以下面的例子很好理解了:
DESCRIPTION = "Prints Hello World"
PN = 'printhello'
PV = '1'
VAR1 = "Hello "
VAR2 = "${VAR1}world"
VAR3 := "${VAR1}world"
VAR1 = "hello"
python do_build() {
bb.plain("********************");
bb.plain("* *");
bb.plain("* Hello, World! *");
bb.plain("* *");
bb.plain("********************");
bb.plain("VAR2: " + d.getVar("VAR2", True))
bb.plain("VAR3: " + d.getVar("VAR3", True))
}
測試結果可以發現VAR2和VAR3是不一樣的:
VAR2: helloworld
VAR3: Hello world
1.3 追加與前加賦值
這種賦值類似與字符串操作中的連接操作,涉及到的操作符有四個:
- +=,帶空格的追加操作,表示在字符串后面先加一個空格,然后再把追加的內容連接到變量后。
- =+,帶空格的前加操作,表示在字符串后前面先加一個空格,然后再把前加的內容連接到變量前。
- .=,不帶空格的追加操作,把追加的內容連接到變量后。
- =.,不帶空格的前加操作,把前加的內容連接到變量前。
嘗試下面的例子:
DESCRIPTION = "Prints Hello World"
PN = 'printhello'
PV = '1'
VAR1 = "world"
VAR2 = "hello"
VAR3 = "hello"
VAR4 = "hello"
VAR5 = "hello"
VAR2 += "${VAR1}"
VAR3 =+ "${VAR1}"
VAR4 .= "${VAR1}"
VAR5 =. "${VAR1}"
python do_build() {
bb.plain("********************");
bb.plain("* *");
bb.plain("* Hello, World! *");
bb.plain("* *");
bb.plain("********************");
bb.plain("VAR2: " + d.getVar("VAR2", True))
bb.plain("VAR3: " + d.getVar("VAR3", True))
bb.plain("VAR4: " + d.getVar("VAR4", True))
bb.plain("VAR5: " + d.getVar("VAR5", True))
}
觀察運行結果:
VAR2: hello world
VAR3: world hello
VAR4: helloworld
VAR5: worldhello
1.4 Override風格的賦值語法
這種賦值語法不同於上訴所講的有特殊的操作符,其操作符的含義通常是直接由變量名來表達,什么意思呢?舉個例子,我們表示追加通常使用.=
符號,但是OVERRIDE風格的賦值不使用這個,而是在原變量名的后面加上_append
來表示為此變量名使用追加操作。你可能要問,那兩者有啥區別呢,區別就是Override的操作要等到菜譜全部解析完成后才會執行,其優先級比較弱。
我們介紹三種Override風格的操作符:
:append
,表示不帶空格的追加操作,注意一下,舊版本不是使用的冒號(:)而是下划線(_)。:prepend
,表示不帶空格的前加操作。:remove
,刪除指定的字符串。我們詳細介紹一下這個操作的機制,首先BitBake將一個變量的值看作是由若干個空格分隔開的字符串列表,同樣的被刪除項也是一個由若干個空格分隔開的字符串列表,:remove
操作就是將原值的字符串列變中所有出現在刪除字符串列表的字符串刪除。
我們測試一下下面的一些例子:
DESCRIPTION = "Prints Hello World"
PN = 'printhello'
PV = '1'
VAR1 = "world"
VAR2 = "hello"
VAR3 = "hello"
VAR2:append = "${VAR1}"
VAR3:prepend = "${VAR1}"
VAR4 = "123 456 123456 789 123"
VAR4:remove = "123"
VAR4:remove = "456"
VAR5 = "123 456 123456 789 123"
VAR5:remove = "123 456"
python do_build() {
bb.plain("********************");
bb.plain("* *");
bb.plain("* Hello, World! *");
bb.plain("* *");
bb.plain("********************");
bb.plain("VAR2: " + d.getVar("VAR2", True))
bb.plain("VAR3: " + d.getVar("VAR3", True))
bb.plain("VAR4: " + d.getVar("VAR4", True))
bb.plain("VAR5: " + d.getVar("VAR5", True))
}
觀察實驗效果:
VAR2: helloworld
VAR3: worldhello
VAR4: 123456 789
VAR5: 123456 789
1.5 標志賦值
BitBake可以像結構體一樣有自己的成員變量,即標志。標志變量與普通變量一樣可以用上述提到的所有操作符,除了Override類型的賦值。
我們使用[]
指定一個變量的標志,而且可以在未聲明的情況下直接使用,參考下面的例子,我們未變量添加一個doc
標志,並賦值為hello,同時追加world:
DESCRIPTION = "Prints Hello World"
PN = 'printhello'
PV = '1'
VAR1[doc] = "hello"
VAR1[doc] += "world"
python do_build() {
bb.plain("********************");
bb.plain("* *");
bb.plain("* Hello, World! *");
bb.plain("* *");
bb.plain("********************");
# 獲取標志變量值,要用getVarFlag值
bb.plain("VAR1: " + d.getVarFlag("VAR1", "doc", True))
}
實驗結果如下:
VAR1: hello world
1.6 內聯函數賦值
這種方式允許我們調用python函數去給我們的變量賦值,其格式通常如下:
VAR = "${@inline python function}"
下面我們使用python函數獲取時間字符串為我們的DATE變量賦值:
DESCRIPTION = "Prints Hello World"
PN = 'printhello'
PV = '1'
DATE = "${@time.strftime('%Y%m%d',time.gmtime())}"
python do_build() {
bb.plain("********************");
bb.plain("* *");
bb.plain("* Hello, World! *");
bb.plain("* *");
bb.plain("********************");
bb.plain("VAR1: " + d.getVar("DATE", True))
}
運行查看效果,可以發現打印結果為我們現在的日期。
1.7 其他一些賦值時注意的地方
我們可以使用unset
去刪除一個變量或標志:
# 刪除變量
unset VAR
# 刪除doc標志
unset VAR[doc]
在賦值路徑的時候,要避免使用~
指代我們的home目錄,因為BitBake並不會去解析這個符號。
2. BitBake中的條件語法
在BitBake中,一個變量可能會有多個版本,為了能夠將變量切換到某個版本,我們可以將版本的名稱添加到OVERRIDES變量中,接下來,我們對這個過程做一個詳細的描述。
通常,我們使用:
追加一個版本名稱到變量名后(早期BitBake使用_
代替:
)表示不同版本的變量,例如下面的所有變量都是同一個變量VAR
:
VAR = "var"
VAR:a = "vara"
VAR:b = "varb"
但如果你使用VAR
這個變量,你會發現它的值始終是var
,怎么樣使用其他版本的VAR
呢?這時候就要用到全局變量OVERRIDES
,BitBake在解析完所有內容后會查看OVERRIDES
變量,這個變量是由若干個字符串(注意字符串內容必須是小寫字符)組成的,然后BitBake會將所有有多個版本的變量替換為OVERRIDES
變量中指定的版本。例如我們可以選擇VAR
的a版本:
OVERRIDES = "a"
另外,還有一個有意思的是,這種替換版本的條件語法可以和:append
組合起來一起使用。看下面的兩個例子:
# 不同版本下OVERRIDES的字符串分割符是不同的,有空格或者冒號
OVERRIDES = "a:b"
VAR1 = "var"
VAR1:a:append = "a"
VAR2 = "var"
VAR2:append:b = "b"
在上面的例子中,可以看到append出現的位置不一樣,我們逐個分析一下。首先先看VAR1,我們先賦值var
,然后在解析完成后為VAR1:a
追加值a
,由於其本身是未定義的,所以此時VAR1:a
是a
,最后查看OVERRIDES執行版本替換,VAR1
替換為VAR1:a
,即a
。相反,對於VAR2
先執行版本替換(由於並未定義VAR2:b,所以VAR的值還是原值),然后再執行追加,追加的內容是b
,因此最后VAR2
的結果是varb
。
我們現在編一些測試代碼驗證一下上面的內容:
DESCRIPTION = "Prints Hello World"
PN = 'printhello'
PV = '1'
OVERRIDES = "a:b"
VAR1 = "var"
VAR1:a = "vara"
VAR1:a:append = "a"
VAR2 = "var"
VAR2:b = "varb"
VAR2:append:b = "b"
VAR3 = "var"
VAR3:a:append = "a"
VAR4 = "var"
VAR4:append:b = "b"
VAR5 = "var"
VAR5:a = "vara"
VAR5:a:append = "a"
VAR5:a:append = "a"
python do_build() {
bb.plain("********************");
bb.plain("* *");
bb.plain("* Hello, World! *");
bb.plain("* *");
bb.plain("********************");
bb.plain("OVERRIDES: " + d.getVar("OVERRIDES", True));
bb.plain("VAR1: " + d.getVar("VAR1", True));
bb.plain("VAR2: " + d.getVar("VAR2", True));
bb.plain("VAR3: " + d.getVar("VAR3", True));
bb.plain("VAR4: " + d.getVar("VAR4", True));
bb.plain("VAR5: " + d.getVar("VAR5", True));
}
查看一下結果:
OVERRIDES: task-build:a:b
VAR1: varaa
VAR2: varbb
VAR3: a
VAR4: varb
VAR5: varaaa
細致的同學可能發現OVERRIDES多了一項字符串task-build
,這個比較特殊,是BitBake幫我們追加上去的,此處先不說,以后會提到的。
還有另外一種方法能達到和OVERRIDES
一樣的效果,那便是關鍵字擴展技術,通過使用${}
追加到變量名之后,我們可以替換切換變量的版本,看下面的例子:
A${B} = "X"
B = "2"
A2 = "Y"
這個例子A2
結果是X,這是因為${}
會在解析完成后才擴展,這就意味着即使A2
已經賦值了,但是最后A${B} = "X"
在最后才解析運行,也就覆蓋掉了原先的賦值。
3. 函數
BitBake提供了定義函數的四種方法,這些函數只能定義在BBCLASS、BB和INC文件中,通過函數,我們可以方便的構建一些函數塊,下面我們分別介紹這四種類型。
3.1 Shell函數
這種函數的代碼必須符合/bin/sh
腳本執行器的規范,因為這些函數就是通過它來執行的。這種類型的定義通常如下所示:
some_function () {
echo "Hello World"
}
這些函數是可以作為子函數或者任務執行的,而且通用地,其也可以用之前講的Override風格的操作符,例如:
some_function:append () {
echo "Hello World, again"
}
此時,這個追加操作的含義就是將第二個函數的內容全加到第一個函數中,即如下形式:
some_function:append () {
echo "Hello World"
echo "Hello World, again"
}
不妨在程序里試一下,我們修改printhello.bb
文件如下:
DESCRIPTION = "Prints Hello World"
PN = 'printhello'
PV = '1'
some_function () {
echo "Hello world"
}
some_function () {
echo "Hello world, again"
}
# 注意:python風格函數不能和shell風格函數混用
do_build() {
echo "================="
some_function
echo "================="
}
執行bitbitbake -f printhello -v
觀察結果。
3.2 BitBake風格的Python函數
這種形式的函數我們在最開始就已經見過了,其格式如下:
python some_python_function () {
d.setVar("TEXT", "Hello World")
print d.getVar("TEXT")
}
這種函數是由bb.build.exec_func()
Python函數解釋執行的,這種類型的函數擴展了原有的Python庫,提供了bb
和os
模塊,以及還有數據倉庫d
和一些全局變量,建議使用這種類型的函數。
同樣地,它也支持Override風格的表達式。由於我們之前一直用的這種類型函數,此處不再舉例。
3.3 Python風格的函數
這種函數定義方式與普通Python函數無異,其一般用於內聯函數賦值(參考1.6),例如下面的代碼將會為ITEMS
賦值item
:
DESCRIPTION = "Prints Hello World"
PN = 'printhello'
PV = '1'
def get_item(d):
if d.getVar('SOMECONDITION'):
return "item"
else:
return "noitem"
SOMECONDITION = "1"
ITEMS = "${@get_item(d)}"
python do_build() {
bb.plain("********************");
bb.plain("* *");
bb.plain("* Hello, World! *");
bb.plain("* *");
bb.plain("********************");
bb.plain("ITEMS: " + d.getVar("ITEMS", True))
}
在上面的內聯函數中,我們傳遞數據倉庫d
到python函數中,這是因為這個變量不能自動獲得,相反對於bb
和os
模塊是不用傳遞的,它們都可以自動擴展。
你可能疑惑普通Python函數和BitBake風格的Python函數有什么區別,有興趣的同學可以閱讀這篇內容:BitBake-Style Python Functions Versus Python Functions
3.4 匿名Python函數
這個函數與BitBake風格的Python函數基本一致,區別在於BitBake風格的Python函數,匿名函數沒有函數名(或者用__anonymous
作為其函數名),其只要被定義就一定會在文件解析完之后運行(如果有多個匿名函數,就按在文件定義的順序來)。
另外,需要注意的是,override風格的表達式執行順序要優先於匿名函數的順序。
下面的例子你可以試着跑一下:
DESCRIPTION = "Prints Hello World"
PN = 'printhello'
PV = '1'
python () {
# 為 FOO 重新賦值
d.setVar('FOO', 'foo 2')
}
FOO = "foo 1"
python () {
# 追加操作
d.appendVar('BAR',' bar 2')
}
BAR = "bar 1"
BAR:append = " from outside"
python do_build() {
bb.plain("********************");
bb.plain("* *");
bb.plain("* Hello, World! *");
bb.plain("* *");
bb.plain("********************");
bb.plain("FOO: " + d.getVar("FOO", True))
bb.plain("BAR: " + d.getVar("BAR", True))
}
猜一下他的運行結果是什么?沒錯,其執行順序應該是:FOO賦值 --> BAR賦值 --> BAR追加 --> 第一個匿名函數 --> 第二個匿名函數,所以結果如下:
FOO: foo 2
BAR: bar 1 from outside bar 2
在本篇文章中,我們已經掌握了BitBake的基本操作和函數,在之后的時間里,我們將繼續學習其他的語法知識,命令用法以及怎樣使用它完成一個復雜的工程構建任務。當然啦,也希望大家能多多支持一下博主,碼字不易,還望一鍵三連,如果想請博主喝杯茶也可以,右下角可以打賞哦,再次謝謝大家能看到這個地方。
我是chegxy,歡迎關注!!!