BitBake使用攻略--BitBake的语法知识一


写在前面

这是《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,欢迎关注!!!

posted @ 2021-12-25 20:14  chegxy  阅读(3542)  评论(3编辑  收藏  举报