在去年年底,Sebastian Witalec 和我访问了 Google 在加州山景城的 Angular 团队。在过去的一年里,我们一直在与 Angular 团队紧密合作。这次旅行的目的很简单——尝试使用 Bazel 构建一个 NativeScript 应用程序。我对 NativeScript 应用程序的构建方式非常了解,因为我在过去几年一直致力于 NativeScript CLI,但 Bazel...对于我来说,这是一个全新的世界!
另一方面,Angular 团队已经熟悉 Bazel,事实上,有一种方法可以使用 Bazel 构建 Angular 应用程序。因此,我们花了几天时间与 Angular 团队在一起,在这篇文章中,我将尝试描述我们所做的事情(如果你读到最后,你会发现用 Bazel 构建 NativeScript 应用程序是可能的!)。
Bazel 是一款开源构建和测试工具,类似于 Make、Maven 和 Gradle。它使用一种人类可读的高级构建语言。Bazel 支持多种语言的项目,并为多个平台构建输出。Bazel 还支持跨多个存储库的大型代码库,以及大量用户。
Bazel 的设计是为了适应 Google 的软件开发方式。它具有以下功能
为了更好地理解什么是 Bazel,谁在使用它以及为什么创建它,你可以查看 官方网站 和 常见问题解答部分。
我们想尝试用 Bazel 构建 NativeScript 应用程序有很多原因。Bazel 处理大型项目的强大功能以及每次构建应用程序时都能获得完全相同的结果的能力,无论安装了哪些工具,这似乎是一个“我们梦寐以求的玩具”。几年前,我们在 NativeScript CLI 中遇到了类似的问题——我们想最大程度地减少在设备上显示代码库中用户更改所需的步骤和时间。在那时,我们实现了我们自己的非通用解决方案,它仍然是我们 CLI 的一部分,它试图确定我们需要执行的最少步骤数量,以便将更改应用到设备上。Bazel 似乎以一种更通用的方式解决了这个问题,我们有兴趣尝试一下,看看它是否有效。
另一方面,我们知道有一种方法可以使用 Bazel 构建 Angular 应用程序,我们很嫉妒你不能使用 Bazel 构建 NativeScript Angular 应用程序!
还有许多其他的原因,其中一些可能看起来并不重要,但最终,我们都喜欢解决这样的技术挑战。因此,我和塞巴斯蒂安前往加州山景城,以找出如何使用 Bazel。
当我走出 Uber 看到谷歌大厦前的 Google 标志时,我无法形容我的激动。这不是我第一次来美国,但站在 Google 办公室面前的情绪无法用语言表达。这是一种荣誉,一个梦想成真。
在办公室里,我们受到了 Alex Eagle 的欢迎——一个非常令人印象深刻的人,他是我们的主人,也是我在接下来的几天里在 Bazel 世界中的私人导游。我们还遇到了 Brad Green,以及 Angular 团队、Material 团队等的许多人。与你们所有人见面真是太好了,感谢你们成为如此棒的主人!
Angular 团队愿意帮助我们在用 Bazel 构建 NativeScript 应用程序这一雄心勃勃的任务中,因此我们立即开始了工作。Alex Eagle 和我一起工作了接下来的几天——我解释了 NativeScript 的构建方式(甚至我自己也对我们在构建过程中需要执行的一些步骤感到惊讶😊),而 Alex 将这些步骤转换为 Bazel 规则。
我们讨论了如何处理这项任务,并决定从一个简单的 JavaScript NativeScript "Hello World" 应用程序开始(当你使用 tns create myApp --js
时收到的应用程序)。我们的想法是使用 Bazel 的 Android 规则,看看它们是否可以用于构建 NativeScript CLI 在项目 platforms/android
目录中创建的本机 Android 项目。
构建应用程序的过程相当复杂,但我们可以将其分为几个步骤
platforms/android
目录中创建本机 Android 项目。.js
文件(来自项目 app/src
目录和 node_modules
)放在本机项目的 assets
目录中。App_Resources
放置在本机项目中的正确位置。node_modules
的本机文件(.aar
文件、.jar
文件、include.gradle
文件)包含到本机项目的构建过程中。build.gradle
文件中定义的所有任务。最后的部分执行了很多东西:NativeScript 的 build.gradle
执行了几个特定于 NativeScript 应用程序的任务,例如元数据生成,这是能够直接从 JavaScript 调用 Java 方法所必需的。
我们的想法是从最后一点开始,用 Bazel 替换 Gradle。
由于 NativeScript 项目的 build.gradle
非常复杂,并且 Bazel 使用 BUILD.bazel
文件而不是 gradle 文件,因此我们决定从一个非常小的步骤开始——使用 NativeScript CLI 构建 NativeScript 应用程序,并在它完成工作后,将所需的 Bazel 文件放入本机项目中,并尝试用 Bazel 构建它。这样,我们就可以保证所有必需的操作、文件生成、元数据类等都已经完成,我们只需要看看 Bazel Android 规则是否有效。
因此,我们开始着手进行。在花了些时间构建生成的类之后,我们终于成功了。我们用 Bazel 的 Android 规则构建了一个正在运行的 NativeScript 应用程序!
这对我们来说是一个巨大的胜利,它给了我们继续前进的力量。Bazel 很棒,但它有自己的特点,你需要习惯。然而,我们有 Alex Eagle,他就像一个魔术师——他能够连续几个小时不停地工作,而且他总是有想法去尝试下一个步骤。
在这一点上,我们证明了我们可以使用 Bazel,但我们只是针对已经构建的应用程序做了这件事,也就是说,我们使用了 Gradle,之后我们使用了 Bazel。现在,我们想从图中删除 Gradle——我们想直接用 Bazel 执行我们在 build.gradle
中所做的一切。
将所有步骤“翻译”的过程并不容易。我们遇到的主要问题之一与我们在构建过程中使用的工具有关——从 build.gradle
中,我们启动了两个 Node.js 进程来生成一些元数据文件,这些文件会生成必须包含在构建的项目中的资源/文件。从 Bazel 中生成 Node.js 进程并不成问题,也有 Node.js Bazel 规则。棘手的部分是何时执行这些规则,因为它们必须在构建过程中的精确时间点执行。另一个问题是,执行的脚本依赖于它们期望在文件系统上与它们并排放置的文件。在 Bazel 中,你不能依赖于有这样的文件——Bazel 规则中的逻辑是,你有输入文件和输出文件。如果输入自上次构建以来被修改,则将执行该步骤,否则将跳过该步骤。在我们的例子中,我们不得不“修改”一些步骤,以便它们能够与 Bazel 处理构建的方式兼容。有关此问题的更多详细信息,请参阅 Alex Eagle 的博文。
我们遇到的另一个问题与在构建过程中从插件 include.gradle
文件中包含的本机库有关。在 build.gradle
中,我们有逻辑来迭代所有 node_modules
,找到 include.gradle
文件,然后直接应用它们。出于 POC 的目的,我们决定手动进行——只是将项目中需要的所有库硬编码。
经过了几次修改,以及来自从事 Android Bazel 规则的开发人员、从事 NativeScript Android 构建的开发人员以及 Alex Eagle 的天才的帮助,我们终于成功了。我们用 NativeScript CLI 准备了一个 NativeScript 应用程序(也就是说,我们用 NativeScript CLI 创建了它,并用 NativeScript CLI 创建了本机项目),并用 Bazel 在设备上构建和部署了它!我们在设备上启动了它,并且...它正在平稳地运行用 Bazel 构建的 NativeScript 应用程序!你可以看到我们在这之后脸上露出的笑容
从左到右分别是 Rosen Vladimirov(我)、Sebastian Witalec 和 Alex Eagle。
在这一点上,我们已经做了很多工作,是时候离开山景城了,但我们已经有了可行的概念验证。我们也尝试了一个 NativeScript Angular 应用程序,它运行良好,因此我们对最终结果非常满意。
我们讨论了向用户展示此 POC 并赋予他们扩展它的能力的正确方法。在 NativeScript 项目中,我们有开发人员插件的概念,它们作为项目的 devDependency
安装。它们的目的是成为构建过程的一部分,因此我们决定创建一个 nativescript-dev-bazel 插件 是一个合适的方法。将它添加到你的应用程序后,你必须执行 tns prepare android
,插件会将所需的 Bazel 特定文件添加到你的应用程序中。
现在你可以尝试使用 bazel build //platforms/android:android
构建你的应用程序。
在与 Angular 团队共事期间,我们能够为使用 Bazel 构建 NativeScript 应用程序创建一个 POC。POC 允许你使用 NativeScript CLI 准备 NativeScript 应用程序,并使用 Bazel 为 Android 构建它们。POC 不包括适用于 Angular 应用程序的 Bazel 规则,也就是说,AOT 之类的功能没有被使用,但你仍然可以构建 NativeScript Angular 应用程序。我们不建议在你的生产就绪应用程序中使用此 POC,但如果你能尝试一下并看看它是如何工作的,我们将很高兴。
Bazel 是一款很棒的工具,在很多情况下都很有用。能够用 Bazel 构建 NativeScript 应用程序,是 NativeScript 工具家族的巨大补充。想象一个世界,你可以在其中创建移动端和 Web 端的 Angular 应用程序,在它们之间共享代码,并使用 Bazel 构建它们。这听起来是否很诱人?
如果您想扩展当前的 POC,请查看仓库 - 它包含一些待办事项,可以尝试一下!我们很乐意接受 PR。