本文我们使用Bazel构建一个最简单的Android项目。Bazel提供了编译Android程序内置的方法,具体参考:Android Rules
1. 环境准备
Bazel只是编译工具,不是真正的编译器,所以还是需要Andorid开发的SD、NDK以及Android Studio,并配置开发环境。
接下来就是安装Bazel。由于平时在macos开发为主,所以主要介绍macos系统的安装。macos下bazel有四种安装方式:
- 使用二进制安装器(官方推荐);
- 使用hombrew;
- 使用bazelisk;
- 从源码编译安装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"程序点击打开即可看到下面效果:
现在我们跑通了第一个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平台的编译。