(James Ward和我在“快乐路径编程播客”中详细介绍了这篇文章。)
我从80年代开始使用make。当我用C ++编写《 Thinking》时,我创建了一个名为makebuilder的工具,该工具分析了从书中摘录的示例并生成了适当的makefile。 make是一个专用工具,只关心依赖关系和操作,因此可以合理地使用。
当我用Java写《 Thinking》时,我创建了一个名为antbuilder的类似工具,该工具生成了一个适当的Ant构建文件。尽管Ant是在XML泛滥成灾期间创建的,而XML是万物的未来,但是ant仍然是专用的构建工具,因此它像make一样可以合理地使用。
当我开始研究Gradle时,这就是我的背景,并且我有一定的期望,主要是该工具将具有简单的配置和设置过程,并且看起来会相当熟悉。我所读到的有关Gradle的内容为这个想法提供了支持,并承诺大多数配置将很简单,并且您通常不需要浸入该配置的表面之下。
相反,Gradle对我来说非常神秘。它似乎有无数的悬崖,而我一直在发现这些悬崖并掉下。最重要的是,我没有任何心智模型可以指导我制定决策和解决问题。
当我开始在Java 8上写东西时,我的朋友James Ward宣布我不能使用Ant而不得不使用Gradle,而他会提供帮助。他最终为本书创建了各种Gradle构建文件,我可以阅读甚至修改某些部分,但即使在Gradle上阅读了几本书之后,大局还是令人困惑。
在那之后,我开始研究Atomic Kotlin,为此我们也使用了Gradle,但是我的合著者Svetlana创建并管理了TheGradle文件。在撰写本书的过程中,Gradle进行了更改,因此也使用了Kotlin,但我对Grodle和Graovy的Gradle仍然了解得不够多,因此,我不想冒险尝试通过改用Kotlin来破坏我们的系统。
最近,我看了一个有关Gradle的YouTube视频,他们只是在做事情,但从未真正解释过。您知道那种记得整个Unix命令行套件并毫不费力地使用它的人吗?那似乎是我在看的东西,并没有使人放心。
我在这个地方呆了很多年,要依靠其他人来帮助弄清楚它,而当他们这样做时却不理解。这非常令人沮丧,我尽可能避免与Gradle交易。但是在下面,被一种软件技术迷住了,这让我很烦。
On Java 8的中国发行商最近要求我在Java 11上添加最后一章。当我开始对此进行挖掘时,我意识到我需要将示例提取到具有自己的Gradle构建的单独的存储库中,该存储库需要Java11。我确实没有这么做。这次不想寻求帮助(主要是因为离手很近的帮助者对使用Gradle表示极大的反感)。因此,我决定花些时间弄清楚并理解它,至少对我来说足以处理Java 11章节。
理想情况下,不要丢东西(出于无奈之意,我指的不是异常,而是物理对象)。
通过互联网搜索,我变得更加喜欢Gradle文档。该过程需要花费很多时间(除了我过去所做的尝试之外),并且需要大量自我保证,可以花点时间继续戳下去。
有了耐心和毅力,Gradle慢慢开始放弃它的奥秘。同时,我开始理解为什么它让我感到如此困惑,以及为什么将Gradle作为配置练习不起作用。你不能以一种肮脏的态度对待它,这就是使我受阻的原因。这是我在Gradle中遇到的问题:
是的,假设可以为基础构建创建一个简单的build.gradle文件。但是通常到您需要Gradlebuild的时候,您的问题已经非常复杂,您必须做更多的事情。事实证明,“做更多的事情”转化为“了解一切”。一旦您摆脱了简单的事情,您就会掉下悬崖。
想想里克(Rick)和莫蒂(Morty)的第一集中的鞋子。 Rick解释说,这双鞋可以让您在垂直的地面上行走,因此Morty穿上了鞋子,并立即从悬崖上掉下来,此后Rick解释说“必须打开它们”。摇篮是我的格斗鞋。
我的目的是给您一个视角,这样当您从悬崖上跌下来时,您将了解正在发生的事情以及重新爬升的必要条件。
任务:这些任务包括构建操作菜单。一个构建通常具有多个任务,并且您通常像在gradle构建中一样,从命令行调用所需的任务。其他构建系统具有不同的任务名称。例如,将其称为目标。
依赖关系:依赖关系说“这不可能在发生之前发生。”通常,这意味着“我无法在该组件可用/更新之前编译/运行该组件,”但是依赖关系可以引用任何表示“先这样做,然后再这样做。”
可以将依赖项视为“内部”(依赖于构建中的其他组件)或“外部”(从本地或远程外部存储库更新组件)。
构建工具将读取脚本,该脚本通常位于标准名称的文件中,例如build.gradle或Makefile。根据脚本中的指示,构建工具执行更新项目所需的操作。
从历史上看,诸如make之类的构建工具都是针对配置的,并且是对外部程序的委托。例如,一个简单的Makefile如下所示:
非缩进线建立了依赖关系:vip依赖于vip.o,vip.o依赖于vip.c。如果您修改vip.c,make将看到该vip.cis现在比vip.o更新,因此vip.o已过时,make运行命令cc -c vip.c -o vip.o来带来vip.o最新。现在,vip比vip.o早,因此make运行命令cc vip.o -o vip。
命令是缩进的(可怕的是,通过制表符缩进,因为make是在Unix早期创建的,当时他们仍然沉迷于保存字节),这些命令不是make的一部分,而只是shell命令行程序(在这种情况下,C编译器cc )。根据Makefile的说明,make并不知道其他任何信息,执行命令将使目标保持最新状态。
制造的简单性是优雅的,并且今天仍在积极使用。随着时间的推移,随着人们开始依赖它来开发更大,更复杂的程序,一个重要的限制出现了。始终依赖于外部程序作为命令变得具有挑战性,因此一些版本的make开始添加越来越多的内部功能来满足这些需求。对于那些遵循make优势的人来说,您可能会有相同的“啊哈!” Idid出现的那一刻:“这是make的创造者意识到他们正在构建一种编程语言,然后他们停下来的地方。”
现代构建系统的创建者知道,构建工具中需要一定程度的编程支持,并且使用现有语言通常比失去创建新语言更有意义(毕竟,这就是我们最终使用Java的方式)。他们本应制造电视机专用设备)。问题是:
哪种语言?最好的选择似乎是用户已经熟悉的一个选择,以降低阻碍学习构建系统的认知障碍。
多么令人讨厌?这种语言在使用您的构建系统的经验中占主导地位?使用buildtool需要多少语言专业知识?
有何影响?我的理想选择是看起来像现有语言的构建系统,并添加了一些最少的语法来配置目标规则。如您所见,Gradle的设计受到Groovy语言使用的极大影响。
我们仍处于“在现有语言之上添加构建系统”范例的初期。 Gradle是该范式的实验,因此我们期望一些次优的选择。但是,通过了解它的问题,您可能比我对Gradle的了解更轻松。
尽管Gradle看起来只是在声明配置,但这些配置实际上都是函数调用。基本上,除了某些语言指令外,其他所有东西都是创建对象或调用函数。我发现意识到这一点非常有帮助,因为现在查看配置声明,看看它们实际上是在调用函数,这使我更容易理解。
您需要掌握Groovy语言的重要部分,才能创建有用的Gradle构建文件。在深入探究Groovy之前,我几乎无法理解发生了什么。
Groovy是对Java的重大改进,Groovy中的一些功能影响了Kotlin的语言设计。我对Groovy的了解越多,我意识到我就从没考虑过。
Groovy语法让人想起Java,但是它是另一种语言,您需要学习一套新的规则和技巧。 Groovy可以访问现有Java库的事实主要是对Gradle开发人员有利。
特定领域语言(DSL)专用于特定的应用程序域。所谓的内部DSL(一种内置于现有语言中的内部DSL)的目标是将重点放在眼前的问题(例如配置软件版本)上,以便在理想情况下,用户只需要了解DSL即可。做这份工作。
例如,要告诉Gradle在哪里可以找到Java源文件,您可以说:
这旨在创建一种描述性的方式来描述您的构建,并且依赖于Groovy的lambda的语法(不幸的是,它们称为闭包)。如果函数调用中的最后一个参数是lambda,则可以将其放在参数列表之后。在这里,sourceSets,main和java都是采用单个lambda参数的所有函数,因此不需要带括号的列表,只需使用lambda。因此,sourceSets,main和java都是函数调用,但是生成的语法使它看起来像……其他东西。
确实,这种DSL语法有多有用?阅读时,我必须将其转换为脑海中的函数调用。因此,对我来说,这是额外的认知负担,这最终是一个障碍。 DSL操作都可以通过函数调用完成,并且程序员已经了解函数调用。
有些人喜欢用函数调用来表示其构建文件,而忽略DSL语法。
正如我之前观察到的,您除了要做最基本的构建之外,还必须了解DSL语法以外的更多知识,因此DSL完全失败了。不幸的是,DSL不仅是混合的一部分,而且通常是Gradle引入新手的方式。从本质上讲,这只是进行函数调用的一种嘈杂的方式,而且常常使人更加困惑。这使我想到:
Groovy允许您以多种不同的方式表达事物,而Gradledocumentation似乎深深地吸引了这种多样性。当您只是尝试通过它时,立即添加变体只会使其变得更难。更糟糕的是,人们倾向于随便使用不同的方法,因此您必须认识并弄清竞争的语法。
您可以自由选择不同的方法,人们可以自由选择。因此,在阅读代码示例时,您必须了解所有的变化。这复合了学习Gradle的复杂性。
应该有一种(最好只有一种)明显的方式来做到这一点。
在您完全了解发生了什么之前,似乎有许多不可思议的事情需要特殊的奥术知识。
现在,当您在命令行上说gradle hello时,hello任务将运行。
事实证明,您还可以在函数内部动态创建任务。为此,您必须了解任务对象,该对象自动且无形地位于每个gradle构建中。如果在一个空的build.gradle文件中输入:
它将打印任务列表中的所有任务。无需自己创建任何任务,您将看到:
任务':buildEnvironment'任务':components'任务':dependencies'任务&#39 ;:帮助'任务&#39 ;:初始化'任务'任务&#39 ;: outgoingVariants&任务&#39 ;: prepareKotlinBuildScriptModel'任务&#39 ;:项目'任务&#39 ;:属性'任务&#39 ;:任务&#39 ;:包装'
任务对象是我们在其中找到用于动态任务创建的create()方法的地方(由于某种原因,任务似乎也包含自身)。正如您在hello2中看到的那样,我们传递希望创建的任务的名称:
任务hello1 {doLast {println&#39Hello 1!' }} 任务。 create(" hello2"){依靠On hello1 doLast {println' Hello 2!' }} task(" hello3"){dependOnhello2 doLast {println' Hello 3!' }}全部任务{doLast {任务。匹配{名称。 startsWith(" hello")}。 forEach {println。名称 } }}
hello3显示了另一种创建任务的方法,只需调用task()函数即可。请注意,每个hello任务都明确地依赖于上一个任务,因此,如果运行gradle hello3,您还将看到hello2和hello1也已执行。
所有搜索都在任务列表中进行(如我们之前所见,其中包括许多其他任务),查找名称以hello开头的任务,并显示它们。
通常,如果您要设置项目级别的值以供多段代码使用,则可以使用ext,它是另一个存在的对象。它不仅保存项目级别的值,还可以从其他文件中收集值,并确定在发生冲突时如何覆盖它们。
有时,您需要在文件范围内定义和使用的值。要使用Groovy类型推断定义值,请使用def:
def config ="配置"任务x {println config}字符串useConfig(){return config //失败:无法看到' config' }
如果函数不返回任何内容,则使用def进行定义,否则返回类型。在这里,useConfig()返回一个String。
虽然config在任务x中可见,但在功能useConfig()中不可见。我不确定为什么会这样,但是要解决此问题,您需要创建一个包含静态属性的类,该类既可以在任务中使用,也可以在函数中使用:
class Vals {static def config =" Configuration"}任务x1 {doLast {println" x1:$ {Vals.config}" }}字符串useConfig(){返回Vals。 config //成功}任务x2 {doLast {println" x2:$ {useConfig()}" }}任务全部{依靠任务。匹配{名称。 startsWith(" x")}}
请注意,所有操作都取决于名称以x开头的所有任务,因此运行all将同时执行x1和x2。
如果您不了解生命周期,很容易犯错。例如,假设您不小心将代码放入任务lambda中,如下所示:
它像我告诉它那样显示任务。有多余的> Configureproject:但我确实知道有一个项目配置阶段,因此很可能会如此。
它试图告诉您的是,在该配置阶段正在调用println,而不是在执行任务时调用。不幸的是,这实际上有时可以达到预期的效果。
要告诉它在任务执行时运行代码,可以使用doFirst或doLast,如下所示:
任务a {doFirst {println"任务a doFirst" } println"任务初始化" doLast {println" task a doLast" }}
您需要了解很多类似的事情,否则您会感到惊讶。
Gradle文档假定您已经了解很多。它不是教程,而是核心转储。我现在明白为什么了,因为要做任何事情您都必须了解一切。但是这种假设对新来者来说具有挑战性。
启动时间慢。多年来,他们一直在努力加快速度,但是如果您的Gradle经常使用,那么启动时间就会变得很烦人。相反,make非常快。甚至我用Python创建的所有构建工具通常都能在Gradle启动之前完成。
发现Gradle的能力并非易事,而且功能太多,您常常不知道解决问题的可能或已经存在的能力。在发现已经存在(或没有解决方案)之前,很容易就挣扎了一段时间。
我终于可以开始理解我现有的脚本了,这是让我不必考虑使用Grat脚本切换到Kotlin的原因之一。但是,现在我有了一个大的图景,很明显,我可以做到,而且我愿意。特别是,IntelliJ IDEA对Groovy的支持通常无法推断类型,这对于IDE查找对象的可用属性和方法是必需的。仅此一项就值得转向Kotlin(与Groovy一样,是另一种语言)。我认为这肯定会吸引使用Gradle构建的Kotlin程序员。
如果您一直在努力为Gradle建立心理模型,我希望这篇文章能提供一些见解。
如果您想要更多,James Ward和我将在Happy Path Programming Podcast中深入探讨这篇文章。
我是Atomic Kotlin(与Svetlana Isakova一起),Java 8和其他书籍的作者。