BitBake使用攻略--BitBake的語法知識一

chegxy發表於2021-12-25


寫在前面

這是《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:aa,最後檢視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庫,提供了bbos模組,以及還有資料倉儲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函式中,這是因為這個變數不能自動獲得,相反對於bbos模組是不用傳遞的,它們都可以自動擴充套件。
你可能疑惑普通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,歡迎關注!!!

相關文章