首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

编程语言的支撑体系:构建系统、IDE 和依赖管理

年关(annual review)将近,这一段时间,我在梳理 2020 年做的一些事情,并试着制定下一年的计划。过程中,我发现我做的一些事情,或是工作相关,或是兴趣上的探索,还都可以继续总结出一些文章。在工作上,很多一部分做的事情就是编程语言的支撑体系。外加业余时间里,和同事一起花了一些时间在研究编程语言。在这几部分的结合之下,我对于整个体系的端到端实现有一个整体的认识。

作为一个职业的程序员,在我们的职业生涯里,不可避免地要学习一个又一个的编程语言。虽然多数情况下,我们对于使用什么语言并没有太多的选择权。但是,当我们选择一门语言时,都要考虑一系列的要素,比如:

构建系统

IDE/Editor 支撑

依赖管理

……

PS:当然了,对于那些使用 C/C++ 的人来说,这些可能都是例外:他/她觉得自己不需要这些工具,需要的时候可以自己创造一个。所以,这些语言在很长的一段时间里,都缺乏良好的依赖管理工具。

故事开始之前,让我们让 Android 使用的开发和构建来讲述这个过程。

从 Android 应用的开发与构建说起

在移动端开发上,虽比不上这个行业的诸多大佬,但我也算是颇有经验的。而恰好一年中有一半的时间,都在相关的项目上。所以,我从宏观上了解了整体的体系。

当我们开始一个新的移动应用时,会从 IDE 里通过模板创造一个崭新的应用,又或者是从某个地方(如 GitHub)寻找合适的模板。而后,为验证模板的有效性,我们通过执行 Gradle 的相关命令,完成一个应用的过程,运行这个 Demo。(PS:这一点与我们使用 Java 开发应用时,并没有太大区别)。

这个过程中,发生了这么一些事情:

IDE 通过某种通讯机制,与 Gradle 进行通讯,以执行对应的命令,如 build。

Gradle 接收到 IDE 的指令后,解析 build.gradle 相关的内容,寻找是否存在对应的 Task,如这里的 build。

执行 build 时,首先要去解决依赖关系,如从对应的 Maven 仓库中下载依赖。

随后,真正地执行对应的构建任务,如调用 javac。

这个过程看上去非常简单,但是背后还藏着诸多的细节问题。

构建与依赖管理

当我用 CLOC 工具统计了一下 Gradle 工具的源码时,我才发现这个工具并不简单。而进一步地,在半深入源码之后,我发现构建系统还是颇为复杂的。一个简单的 Java 应用就分为这么一些步骤:

而当我们有依赖的时候,需要添加上 ,即将依赖添加到编译的路径中。而对于一些非  类型的依赖而言,如 ,构建工具还要支持对他们的解析。因此,整体的过程就是:

判断是否存在本地的依赖,如果没有的话,从远程获取。如果有依赖冲突的话,解决这些冲突,或者报错。

获取依赖后,根据需要对依赖进行处理。如 Android 中的 aar 包的解压等。

结合依赖,对源码进行编译

将所需要的 Java Resources 从依赖的 Jar 拷贝到指定目录

打包构建后的产物到一个新的 jar 包中

这些只是表面上的一些工作。而为了更好地表述这个过程,需要抽象出一个  的概念,在这个概念里,一个 task 有输入和输出。如

解析依赖里。它的输出是 build.gradle 文件,输出是处理完的依赖路径。

编译任务里。它的输入是源码,输出是 .class 文件。

打包任务里。它的输入是一堆文件夹或者文件,输出是一个 .jar 包。

……

于是,在有了这些基础之后,为了加快构建,还需要缓存的机制。它对输入和输出进行计算,当两者发生变化的时候,再进行编译。否则就跳过这个任务。

而这些只是核心功能,在非核心的功能区里,还有诸如于 SDK 版本、多输入多输出的变体等等。

IDE 与构建系统

在那篇《编程语言的 IDE 支持》中,我们已经介绍了编程语言所需要的 IDE 功能,诸如于:

语法高亮

子系统关联与集成

跳转与引用分析

智能感知

重构

快速修复

结构化视图

……

在这篇文章中,大概再回顾一下它与构建系统之间的关系。IDE 与构建系统一般会存在这种关联:

解析构建系统中的任务。如 Gradle 提供的 task,又或者是 package.json 中的 scripts,并将它们显式地展示出来,如 IDEA 中的 line marker,又或者是独立的 Gradle pannel。

执行构建任务。即在 IDE 中的 UI 与构建命令相绑定,典型的如 IDEA 中的 Android 应用的构建。

动态修改构建系统(可选)。如 IDEA 中的更新依赖版本,它依赖于解析构建系统的 DSL,并更新对应的 DSL。

对应的有两种机制可以与构建系统通讯:

由构建系统提供构建 API。如 Gradle Tooling API,在那篇《Gradle IDEA 的项目模型》中,我们实际上介绍了由构建系统主动向 IDE 提供模型的方式。

由 IDE 构造一遍构建系统。如 IDEA 对于 Node.js 的处理方式。

简单来说,就是复杂的系统应该由构建系统提供机制,而简单的构建系统则就不会有这样的问题。

依赖管理的基础设施

不同语言对于依赖的管理机制都有所不同,但是它们的原理都是相似的:

源码包。即将源码打包,并以特定的格式发布,适用于脚本语言

仓库源。方式类似于源码包,唯一不同的地方是借助于版本管理工具,如 Golang。

类二进制包。典型的是 Java

其它包。如 Maven 可以支持其它自制的包

最有意思的是Maven 的机制,我可以自制依赖,并上传上去。而整个仓库并不关心这个包的内容,我们只需要依赖于它定义的格式即可。如果我们考虑围绕语言来设计依赖管理体系,那么可以考虑的是类似的方式,并借助于 Git 这样的版本工具。这样一来,我们就可以去中心化。

其它

嗯,人生苦短,多了解一些有意思的系统吧。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20201214A0J51100?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券