年关(annual review)将近,这一段时间,我在梳理 2020 年做的一些事情,并试着制定下一年的计划。过程中,我发现我做的一些事情,或是工作相关,或是兴趣上的探索,还都可以继续总结出一些文章。在工作上,很多一部分做的事情就是编程语言的支撑体系。外加业余时间里,和同事一起花了一些时间在研究编程语言。在这几部分的结合之下,我对于整个体系的端到端实现有一个整体的认识。
作为一个职业的程序员,在我们的职业生涯里,不可避免地要学习一个又一个的编程语言。虽然多数情况下,我们对于使用什么语言并没有太多的选择权。但是,当我们选择一门语言时,都要考虑一系列的要素,比如:
PS:当然了,对于那些使用 C/C++ 的人来说,这些可能都是例外:他/她觉得自己不需要这些工具,需要的时候可以自己创造一个。所以,这些语言在很长的一段时间里,都缺乏良好的依赖管理工具。
故事开始之前,让我们让 Android 使用的开发和构建来讲述这个过程。
在移动端开发上,虽比不上这个行业的诸多大佬,但我也算是颇有经验的。而恰好一年中有一半的时间,都在相关的项目上。所以,我从宏观上了解了整体的体系。
当我们开始一个新的移动应用时,会从 IDE 里通过模板创造一个崭新的应用,又或者是从某个地方(如 GitHub)寻找合适的模板。而后,为验证模板的有效性,我们通过执行 Gradle 的相关命令,完成一个应用的过程,运行这个 Demo。(PS:这一点与我们使用 Java 开发应用时,并没有太大区别)。
这个过程中,发生了这么一些事情:
这个过程看上去非常简单,但是背后还藏着诸多的细节问题。
当我用 CLOC 工具统计了一下 Gradle 工具的源码时,我才发现这个工具并不简单。而进一步地,在半深入源码之后,我发现构建系统还是颇为复杂的。一个简单的 Java 应用就分为这么一些步骤:
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:run
而当我们有依赖的时候,需要添加上 classpath
,即将依赖添加到编译的路径中。而对于一些非 .jar
类型的依赖而言,如 .war
,构建工具还要支持对他们的解析。因此,整体的过程就是:
这些只是表面上的一些工作。而为了更好地表述这个过程,需要抽象出一个 task
的概念,在这个概念里,一个 task 有输入和输出。如
于是,在有了这些基础之后,为了加快构建,还需要缓存的机制。它对输入和输出进行计算,当两者发生变化的时候,再进行编译。否则就跳过这个任务。
而这些只是核心功能,在非核心的功能区里,还有诸如于 SDK 版本、多输入多输出的变体等等。
在那篇《编程语言的 IDE 支持》中,我们已经介绍了编程语言所需要的 IDE 功能,诸如于:
在这篇文章中,大概再回顾一下它与构建系统之间的关系。IDE 与构建系统一般会存在这种关联:
对应的有两种机制可以与构建系统通讯:
简单来说,就是复杂的系统应该由构建系统提供机制,而简单的构建系统则就不会有这样的问题。
不同语言对于依赖的管理机制都有所不同,但是它们的原理都是相似的:
最有意思的是Maven 的机制,我可以自制依赖,并上传上去。而整个仓库并不关心这个包的内容,我们只需要依赖于它定义的格式即可。如果我们考虑围绕语言来设计依赖管理体系,那么可以考虑的是类似的方式,并借助于 Git 这样的版本工具。这样一来,我们就可以去中心化。
嗯,人生苦短,多了解一些有意思的系统吧。