基于Facebook Buck改造Android构建系统之基本概念

96
asce1885
0.1 2015.09.29 20:35* 字数 1724

@author ASCE1885的 Github 简书 微博 CSDN
本文由于潜在的商业目的,不开放全文转载许可,谢谢!

使用Facebook Buck对已有的Android项目进行改造,首先需要理解Buck的一些基本概念,在这个的基础上,我们才能既快又好的进行改造工作。本文就先来介绍4个主要的概念,它们对于编写Buck构建脚本至关重要:

  • 构建规则(Build Rule)
  • 构建目标(Build Target)
  • 构建文件(Build File)
  • 构建目标模式(Build Target Pattern)

构建规则(Build Rule)

构建规则是从一组输入文件中生成输出文件的过程。Buck已经内建了一系列构建规则,定义了构建Android代码时会用到的公共操作,例如

构建规则是一个泛称,在构建文件中使用内建Python函数定义的规则以及创建的用于执行这个操作的Java对象,都称之为构建规则。每个构建规则至少需要具备如下三个参数:

  • name:构建规则的名字,在一个构建文件中必须保持唯一性
  • deps:构建规则的依赖项,以一个构建目标的列表的形式表示。
  • visibility:可见性,表明其他构建规则是否可以依赖该构建规则,以一个构建目标模式的列表形式表示。

在Buck中,每一个构建规则可以产出零个或者一个输出文件,其他构建规则可以声明依赖这些输出文件。例如:

  • 构建规则android_library的输出是一个JAR文件,因此,构建规则android_binary可以声明依赖于规则android_library,这样就可以在最终生成的APK文件中包含android_library输出的JAR包。
  • 构建规则android_libraryB依赖于另一个规则android_libraryA,那么在B编译时,会将A生成的JAR文件包含进来。

讲了这么多,我们还是来看一下构建规则的示例,这个示例是一个android_binary规则,它依赖于android_resourceandroid_library,分别表示生成APK所需的资源和Java代码文件:

android_resource(
  name = 'res',
  res = 'res',
  assets = 'assets',
)

android_library(
  name = 'src',
  srcs = glob(['src/**/*.java']),
  deps = [
    ':res',
  ],
)

# 构建这个规则将会生成一个名为 messenger.apk的文件
android_binary(
  name = 'messenger',
  manifest = 'AndroidManifest.xml',
  keystore = '//keystores:prod',
  package_type = 'release',
  proguard_config = 'proguard.cfg',
  deps = [
    ':res',
    ':src',
  ],
)

有一点需要明确,构建规则依赖的规则,会先于构建规则本身进行构建。构建规则和它的依赖之间是一个有向图,Buck要求这个图是无环的,也就是最终形成一个有向无环图。这样Buck就可以对独立的子图进行并行构建,从而提高构建效率。

构建目标(Build Target)

构建目标是一个字符串,用于标识工程中的某个构建规则,一个完整的构建目标示例:

//java/com/facebook/share:ui

主要有三个组成部分:

  • //前缀表示路径是相对于工程根目录
  • java/com/facebook/share表示构建文件BUCK位于java/com/facebook/share目录中
  • 冒号后面的ui表示构建文件里面定义的构建规则名字,在一个构建文件中,构建规则的名字是唯一的。

在同一个构建文件中,可以使用构建目标的相对路径来引用它。构建目标的相对路径以冒号(:)开始,紧跟着三大组成部分的第三部分:构建规则的名字。例如在构建文件java/com/facebook/share/BUCK中,:ui可以用来引用//java/com/facebook/share:ui

# 这是java/com/facebook/share/BUCK文件的内容
java_binary(
  name = 'ui_jar',
  deps = [
    # 跟使用 '//java/com/facebook/share:ui' 效果一样
    ':ui',
  ],
)

构建目标经常作为构建规则的参数,并在Buck的命令行界面中使用。完整的情况下,使用命令行构建buck时,我们需要输入完整的构建目标如下:

buck build //java/com/facebook/share:share

幸运的是,Buck在解析命令行的构建目标时很宽松(解析构建文件中的构建目标时很严格),所以可以把前缀//去掉,一样可以正常构建:

buck build java/com/facebook/share:share

如果在冒号前面多了一个/,同样也会被忽略,因此我们可以这么写:

buck build java/com/facebook/share/:share

可以注意到,其中的java/com/facebook/share/可以通过tab键帮我们自动补齐,不用手动一个字母一个字母的输入了。更进一步,如果冒号后面的构建规则名字和最后一个路径相同,也可以忽略:

# 相当于 //java/com/facebook/share:share.
buck build java/com/facebook/share/

构建文件(Build File)

构建文件用于定义一个或者多个构建规则,统一命名为BUCK。工程中的Java源文件只能被离它最近的BUCK文件引用,这里的“最近“指的是文件目录树中离该Java源文件所在目录最近的。例如,如果我们的工程中有如下所示的BUCK文件:

java/com/facebook/base/BUCK
java/com/facebook/common/BUCK
java/com/facebook/common/collect/BUCK

那么这些BUCK文件中定义的构建规则有如下限制:

  • java/com/facebook/base/BUCK文件中的构建规则只能引用java/com/facebook/base/目录中的源文件
  • java/com/facebook/common/BUCK文件中的构建规则可以引用该目录中除了子目录java/com/facebook/common/collect/之外的所有Java源文件
  • java/com/facebook/common/collect/目录中的Java源文件只能被java/com/facebook/common/collect/BUCK这个文件中的构建规则所引用。

构建文件所能引用到的源文件范围我们称之为构建包(build package),想要引用到其他构建包的源文件,需要在构建规则中使用deps引用这些文件。回到前面的例子,位于java/com/facebook/common/concurrent/的代码需要依赖java/com/facebook/common/collect/包下面的代码,假设java/com/facebook/common/collect/BUCK文件中有一个构建规则如下:

java_library(
  name = 'collect',
  srcs = glob(['*.java']),
  deps = [
    '//java/com/facebook/base:base',
  ],
)

那么java/com/facebook/common/BUCK文件应该定义规则如下:

java_library(
  name = 'concurrent',
  srcs = glob(['concurrent/*.java']),
  deps = [
    '//java/com/facebook/base:base',
    '//java/com/facebook/common/collect:collect',
  ],
)

错误的做法我们也说明一下,直接在srcs中引用concurrent包中的源文件是无效的:

java_library(
  name = 'concurrent',
  srcs = glob(['collect/*.java', 'concurrent/*.java']),
  deps = [
    '//java/com/facebook/base:base',
  ],
)

构建目标模式(Build Target Pattern)

构建目标模式是一个用来匹配一个或者多个构建目标的字符串,一般作为参数传递给构建规则中的visibility属性。一个构建目标其实也是一个构建目标模式,只是它匹配的是它自己:

# 匹配到 '//apps/myapp:app'.
'//apps/myapp:app'

以冒号:结尾的构建目标模式可以匹配到同一目录中的构建目标:

# 匹配到 '//apps/myapp:app_debug' 和'//apps/myapp:app_release'.
'//apps/myapp:'

/...结尾的构建目标模式可以匹配到该目录及其子目录中的构建目标:

# 匹配到 '//apps:common' and '//apps/myapp:app'.
'//apps/...'

欢迎关注我的微信公众号

Web note ad 1