头图

【Android 构建新工具】Bazel 构建Android项目

本文我们使用Bazel构建一个最简单的Android项目。Bazel提供了编译Android程序内置的方法,具体参考:Android Rules

1. 环境准备

Bazel只是编译工具,不是真正的编译器,所以还是需要Andorid开发的SD、NDK以及Android Studio,并配置开发环境。

接下来就是安装Bazel。由于平时在macos开发为主,所以主要介绍macos系统的安装。macos下bazel有四种安装方式:

  1. 使用二进制安装器(官方推荐);
  2. 使用hombrew;
  3. 使用bazelisk;
  4. 从源码编译安装bazel。

下面介绍前两种常用方法。

1.1 二进制安装器安装

GitHub releases page 下载对应版本安装器。

第一步安装Xcode 命令行工具

如果不打算使用ios_*相关的规则编译,则只需要使用xcode-select安装Xcode命令行工具:

xcode-select --install

如果需要构建ios相关程序,则必须安装Xcode6.1或者最新版本,并且iOS SDK 版本8.1以上。我们可以在App Store中下载到Xcode。

Xcode安装成功后需要我们通过一下命令接收用户使用说明协议:

sudo xcodebuild -license accept
第二步:下载Bazel安装器

我们先在Github basel release页面下载bazel安装器baze-<version>-installer-darwin-x86_64.sh,mac中沃恩可以使用curl工具下载:

# Example installing version `3.2.0`. Replace the version below as appropriate.
export BAZEL_VERSION=3.2.0
curl -fLO "https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel-${BAZEL_VERSION}-installer-darwin-x86_64.sh"
第三步:执行安装器脚本

使用下面命令安装bazel:

chmod +x "bazel-${BAZEL_VERSION}-installer-darwin-x86_64.sh"
./bazel-${BAZEL_VERSION}-installer-darwin-x86_64.sh --user

--user参数将Bazel安装到HOME/bin目录。

第四步:设置环境变量

--user参数将bazel可执行文件安装到$HOME/bin目录,我们将该路径配置到环境变量,在~/.bashrc或者~/.zshrc或者~/.profile文件中增加如下配置:

export PATH="$PATH:$HOME/bin"

此时我们可以运行bazel --version查看bazezl是否安装成功。

1.2通过HomeBrew安装

第一步:安装HomeBrew

如果系统未安装HomeBrew,可以通过如下命令安装HomeBrew:

/bin/bash -c "$(curl -fsSL \
https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
第二步:安装Bazel

可以通过下面命令安装bazel:

$ brew install bazel

通过bazel --version既可看到是否安装成功。还可以通过下列命令更新bazel:

brew upgrade bazel

2.准备Android项目

我们使用bazel官方提供的Android demo程序,这个程序只是在一个Activity中放置了一个按钮,点击可以在TextView中显示一句话。

下载到代码后我们进入examples/android/tutorial目录,代码结构如下:

.
├── README.html
└── src
    └── main
        ├── AndroidManifest.xml
        └── java
            └── com
                └── example
                    └── bazel
                        ├── AndroidManifest.xml
                        ├── Greeter.java
                        ├── MainActivity.java
                        └── res
                            ├── layout
                            │   └── activity_main.xml
                            └── values
                                ├── colors.xml
                                └── strings.xml

就是一个最简单的Android项目结构。

3. 初始化项目空间

工作空间workspace是一个一个活多个软件项目源码的目录,在它的根目录下有一个WORKSPACE的文件。

WORKSPACE可能是空文件也可能包含外部依赖配置。我们可以通过bazel info workspace查看是否运行在正确的工作空间下。如果打印当前路径的地址,则表示是合理的空间,如果WORKSPACE文件不存在,会有如下报错:

ERROR: The 'info' command is only supported from within a workspace.

4.集成Android SDK

编译Android程序肯定要用到Android编译器,我们需要告诉Bazel Android相关SDK路径,我们可以在WORKSPACE文件中指定。

在WORKSPACE文件中增加如下代码:

android_sdk_repository(name = "androidsdk")

这里指定了编译Android程序的SDK路径通过ANDROID_HOME环境变量获取,并且会自动检测最高API版本以及已经安装的最新build tools工具。

我们还可以通过包含path、api_level和build_tools_version属性来明确指定Android SDK的绝对路径、API级别和要使用的构建工具的版本。如果api_level和build_tools_version没有指定,android_sdk_repository规则将使用SDK中相应的最新版本。只要它们在SDK中存在,我们可以任意组合这些属性,例如:

android_sdk_repository(
    name = "androidsdk",
    path = "/path/to/Android/sdk",
    api_level = 25,
    build_tools_version = "30.0.3"
)

如果我们还要编译JNI代码,还需要在WORKSPACE中指定Android NDK路径:

ndroid_ndk_repository(name = "androidndk")

与Android SDK类似,这里面也是通过ANDROID_NDK_HOME环境变量控制NDK路径。

5. 创建BUILD文件

BUILD文件描述了一组构建输出(比如来自aapt的编译过的Android资源或来自javac编译的类文件)和它们之间的关系。这些依赖项可能是工作区中的源文件(Java、c++)或其他构建输出。BUILD文件是用Starlark语言编写的。

在Bazel中,BUILD文件是包层次结构概念的一部分。包层次结构是覆盖工作区中的目录结构的逻辑结构。每个包都是一个目录(及其子目录),其中包含一组相关的源文件和一个BUILD文件。包还包括任何子目录,不包括那些包含自己的BUILD文件的子目录。包名是相对于工作区的BUILD文件的路径。

注意:Bazel的包层次结构在概念上不同于BUILD文件所在的Android App目录的Java包层次结构,即使目录可能是相同的组织方式。

对于本文使用的简单Android Demo,src/main/中的源文件包含一个Bazel包。更复杂的项目可能有许多嵌套的包。

5.1 添加android_library规则

BUILD文件包含几种不同类型的Bazel声明。最重要的类型是构建规则,它告诉Bazel如何从一组源文件或其他依赖项构建中间或最终的软件输出。Bazel提供了两个构建规则,android_library和android_binary,我们可以用它们来构建Android应用。

我们首先使用android_library规则来告诉Bazel从应用程序源代码和资源文件构建一个Android库模块。然后使用android_binary规则告诉Bazel如何构建Android应用程序包。

我们在src/main/java/com/example/bazel目录中创建BUILD文件,并且声明一个新的android_library目标:

package(
    default_visibility = ["//src:__subpackages__"],
)

android_library(
    name = "greeter_activity",
    srcs = [
        "Greeter.java",
        "MainActivity.java",
    ],
    manifest = "AndroidManifest.xml",
    resource_files = glob(["res/**"]),
)

android_library构建规则包含一组属性,这些属性指定了Bazel从源文件构建库模块所需的信息。还要注意,规则的名称是greeter_activity,我们将在android_binary规则中使用这个名字作为依赖项引用规则。

5.2 添加android_binary规则

android_binary规则帮助我们编译成最终的apk文件。

我们在src/main/路径中增加BUILD文件,并且声明android_binary目标:

android_binary(
    name = "app",
    manifest = "AndroidManifest.xml",
    deps = ["//src/main/java/com/example/bazel:greeter_activity"],
)

在这里,deps属性引用我们之前添加到上面BUILD文件中的greeter_activity规则的输出。这意味着当Bazel构建该规则的输出时,它首先检查greeter_activity库规则的输出是否已经构建并且是最新的。如果没有,Bazel构建它,然后使用该输出构建应用程序包文件。

6.构建APP

通过运行bazel build //src/main:app构建android_binary目标。

build子命令指示Bazel构建下面的目标。目标被指定为build文件中的构建规则的名称,以及相对于工作区目录的包路径。在本例中,目标是app,包路径是//src/main/。

注意:有时我们可以忽略包路径或目标名称,这取决于我们在命令行上的当前工作目录和目标名称。

Bazel将开始构建样例应用程序。在构建过程中,它的输出如下所示:

INFO: Analysed target //src/main:app (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //src/main:app up-to-date:
  bazel-bin/src/main/app_deploy.jar
  bazel-bin/src/main/app_unsigned.apk
  bazel-bin/src/main/app.apk

7.构建输出文件路径

Bazel将中间和最终构建操作的输出都放在一组每个用户、每个工作区的输出目录中。这些目录从项目目录的顶层的以下位置进行symlink,其中的WORKSPACE是:

  • bazel-bin 存储二进制可执行文件和其他可运行的构建输出
  • bazel-genfiles存储由Bazel规则生成的中间源文件
  • bazel-out 存储其他类型的构建输出

Bazel将使用android_binary规则生成的Android .apk文件存储在Bazel -bin/src/main目录中,其中的子目录名src/main来自于Bazel包的名称。

8. 运行APP

现在我们可以通过命令行使用bazel mobile-install命令将应用程序部署到连接的Android设备或模拟器上。该命令使用adb与设备通信。在部署之前,我们必须按照adb中的说明设置我们的设备来使用adb。我们也可以选择在Android Studio中包含的Android模拟器上安装应用程序。

bazel mobile-install //src/main:app

接下来我们在手机上找到"Bazel Tutorial App"程序点击打开即可看到下面效果:

image.png

现在我们跑通了第一个Bazel构建的Android项目。

注意:mobile-install子命令还支持——incremental标志,该标志可用于仅部署自上次部署以来已更改的应用程序部分;它还支持——start_app标志,以便在安装应用程序时立即启动它。

9. 总结

我们我们使用Bazel构建了一个Android应用程序。主要步骤总结如下:

  • 通过安装Bazel和Android Studio来设置环境,并下载示例项目。
  • 设置一个Bazel工作空间,其中包含应用程序的源代码和一个识别工作空间目录顶层的workspace文件。
  • 更新WORKSPACE文件,以包含对所需外部依赖项的引用,如Android SDK。
  • 创建了BUILD文件。
  • 使用bazel构建该应用。
  • 在Android模拟器或物理设备上部署和运行应用程序。

我们通过最简单的Android项目编译了解了Bazel相关语法及规则,后续对我们学习谷歌新的基于Bazel构建的项目提供了很大帮助。后续我们介绍TensorFlow Lite在Android平台和iOS平台的编译。


轻口味
16.9k 声望3.9k 粉丝

移动端十年老人,主要做IM、音视频、AI方向,目前在做鸿蒙化适配,欢迎这些方向的同学交流:wodekouwei