在我看来,大多数人只有在需要向项目中添加某些内容或突然中断了某些内容时才开始处理gradle-并且在解决了“劳累所致”的问题之后,安全地忘记了经验。 此外,Internet上的许多示例都与高度专业化的咒语相似,它们并不能增加对正在发生的事情的理解:
android { compileSdkVersion 28 defaultConfig { applicationId "com.habr.hello" minSdkVersion 20 targetSdkVersion 28 } buildTypes { release { minifyEnabled false } } }
我不会详细描述以上各行的用途-这些是android插件实现的私有细节。 还有一些更有价值的东西-了解一切的组织方式。 这些信息散布在各个站点/官方文档/通讯座和插件的来源中-总的来说,这是我不希望忘记的通用性知识。
对于刚刚掌握了gradle或已被遗忘的人,可以将其他文本视为备忘单。
有用的链接
主控台
Android studio / IDEA辛苦地隐藏了开发人员的gradle命令,即使更改build.gradle文件,它也会开始变得愚蠢或重新启动项目。
在这种情况下,从控制台调用gradle会变得更加轻松快捷。 grapper包装器通常随项目一起提供,并且可以在linux / macos / windows中正常工作,除非在后者中您需要调用bat文件而不是包装器。
挑战任务
./gradlew tasks
编写可用的任务。
./gradlew subprojectName:tasks --all
您可以显示一个单独的子项目的任务,即使使用--all
选项,也将显示所有任务,包括次要任务。
您可以调用任何任务,并且依赖它的所有任务都将被调用。
./gradlew app:assembleDevelopDebug
如果您懒得写全名,可以扔掉小写字母:
./gradlew app:assembleDD
如果冰雹不能清楚地猜出他们打算要完成的任务,它将显示适当选项的列表。
记录中
启动任务时控制台中显示的信息量在很大程度上取决于日志记录的级别。
除了缺省值外,还有-q, -w, -i, -d
,well或--quiet, --warn, --info, --debug
越来越大。 在复杂的项目中,带有-d的输出可能会占用一个兆字节以上的空间,因此最好立即将其保存到文件中并通过搜索关键字查找该文件:
./gradlew app:build -d > myLog.txt
如果在某处引发异常,则堆栈跟踪的选项-s
。
您可以自己写日志:
logger.warn('A warning log message.')
记录器是SLF4J的实现。
Groovy
build.gradle
文件中发生的只是常规代码。
由于某种原因,Groovy作为一种编程语言并不是很受欢迎,尽管就我看来,至少值得研究一下。 语言诞生于2003年,并且发展缓慢。 有趣的功能:
- 几乎所有的Java代码都是有效的Groovy代码。 直观地编写工作代码很有帮助。
- 同时在凹槽中支持静态,动态键入,而不是
String a = "a"
您可以放心地写def a = "a"
甚至def map = ['one':1, 'two':2, 'list' = [1,false]]
- 有些闭包可以动态确定执行上下文。 那些相同的
android {...}
块接受闭包,然后对某个对象执行它们。 - 存在字符串
"$a, ${b}"
,多行字符串"""yep, ${c}"""
插值,普通的Java字符串用单引号引起来: 'text'
- 有很多种扩展方法。 标准语言集合已经具有任何,每个,每个findAll之类的方法。 就我个人而言,这些方法的名称似乎并不寻常,但主要是它们是 。
- 美味的语法糖,代码更短,更简单。 对于列表和哈希
[a,b,c], [key1: value1, key2: value2]
的声明,您不必在函数的参数周围加上括号[a,b,c], [key1: value1, key2: value2]
漂亮的语法是: [a,b,c], [key1: value1, key2: value2]
总的来说,为什么Python / Javascript之类的语言飞速发展,而Groovy却没有?-这对我来说是个谜。 当时,当Java中甚至没有lambda时,诸如kotlin / scala之类的替代物就刚刚出现或尚不存在,Groovy必须看起来像一种非常有趣的语言。
groovy语法和动态类型的灵活性使我们能够在gradle中创建简洁的DSL。
现在,在Gradle的官方文档中,示例在Kotlin上重复了,并且似乎计划切换到它,但是代码不再看起来那么简单,而变得更像常规代码:
task hello { doLast { println "hello" } }
与
tasks.register("hello") { doLast { println("hello") } }
但是,尚未计划在Kradle中重命名。
组装阶段
它们分为初始化,配置和执行。
这个想法是gradle收集一个非循环的依赖图,并仅调用它们中所需的最小值。 如果我理解正确,那么初始化阶段将在执行build.gradle中的代码时发生。
例如,这:
copy { from source to dest }
或像这样:
task epicFail { copy{ from source to dest } }
也许这并不明显,但是上面的操作会减慢初始化速度。 为了在每次初始化时不处理复制文件,您需要在任务中使用doLast{...}
或doFirst{...}
块-然后将代码包装在闭包中,并在任务完成时调用它。
task properCopy { doLast { copy { from dest to source } } }
大概
task properCopy(type: Copy) { from dest to source }
在旧示例中, doLast
可以看到<<
运算符,而不是doLast
,但是由于行为不明显,他们后来放弃了它。
task properCopy << { println("files copied") }
所有任务
有趣的是,使用doLast
和doFirst
您可以在任何任务上挂起某种动作:
tasks.all { doFirst { println("task $name started") } }
IDE建议tasks
具有一个whenTaskAdded(Closure ...)
方法,但是all(Closure ...)
方法的作用要有趣得多-所有现有任务以及添加新任务时都会调用闭包。
创建一个打印所有任务相关性的任务:
task printDependencies { doLast { tasks.all { println("$name dependsOn $dependsOn") } } }
左右:
task printDependencies { doLast { tasks.all { Task task -> println("${task.name} dependsOn ${task.dependsOn}") } } }
如果在运行时(在doLast
块中)调用tasks.all{}
,那么我们将看到所有任务和依赖项。
如果在没有doLast
的情况doLast
(即在初始化过程中)执行相同的操作,则由于尚未添加打印任务,因此它们可能缺少依赖项。
哦,是的,上瘾! 如果另一个任务应该取决于实现的结果,那么值得添加一个依赖项:
anotherTask.dependsOn properCopy
甚至像这样:
tasks.all{ task -> if (task.name.toLowerCase().contains("debug")) { task.dependsOn properCopy } }
每次都会调用一个常见任务。 如果您指定基于文件A的任务生成文件B,则在这些文件未更改的情况下gradle将跳过该任务。 而且gradle不会检查文件修改日期,而是检查其内容。
task generateCode(type: Exec) { commandLine "generateCode.sh", "input.txt", "output.java" inputs.file "input.txt" output.file "output.java" }
同样,您可以指定文件夹以及一些值: inputs.property(name, value)
。
任务描述
调用./gradlew tasks --all
标准任务都有漂亮的描述,并且以某种方式进行了分组。 对于您的任务,这非常简单地添加:
task hello { group "MyCustomGroup" description "Prints 'hello'" doLast{ print 'hello' } }
启用任务
您可以“关闭”任务-然后仍会调用其依赖项,但不会调用它。
taskName.enabled false
几个项目(模块)
文档中的多项目构建
在主项目中,您可以放置更多模块。 例如,它用于android项目-根项目中几乎没有任何内容,子项目中包含android插件。 如果要添加新模块,则可以添加另一个模块,例如,您还可以连接android插件,但对其使用其他设置。
另一个例子:当使用jitpack发布项目时,根项目描述了使用什么设置来发布甚至不会怀疑发布的子模块。
子模块在settings.gradle中指定:
include 'name'
在此处阅读有关项目之间的依赖关系的更多信息。
buildSrc
如果build.gradle
很多代码或重复了这些代码,则可以将其移到单独的模块中。 我们需要一个魔术名称为buildSrc
的文件夹,您可以在其中将代码放入groovy或java中。 (或者,或者在buildSrc/src/main/java/com/smth/
代码中,可以将测试添加到buildSrc/src/test
)。 例如,如果您需要其他功能,请在scala上编写任务或使用一些依赖项,然后直接在buildSrc
中创建build.gradle
并在它/ enable插件中指定必要的依赖项。
不幸的是,对于buildSrc
的项目buildSrc
IDE可能会愚蠢buildSrc
有提示,在那里您将不得不编写导入和类/任务,还必须将其导入到通常的build.gradle
。 import com.smth.Taskname
并不困难,您只需要记住这一点,而不必buildSrc
为什么buildSrc
的任务)。
因此,先编写直接在build.gradle
中build.gradle
,然后再将代码传输到buildSrc
,这是很方便的。
自己的任务类型
该任务继承自DefaultTask
,其中有许多很多字段,方法和其他内容。 从DefaultTask继承的AbstractTask代码。
有用的要点:
- 您可以对它们使用字段和注释,而不是手动添加
inputs
和outputs
: @Input, @OutputFile
等。 - 执行任务时将运行的方法:
@TaskAction
。 - 仍然可以调用诸如
copy{from ... , into... }
这样的便捷方法,但是您必须为项目显式调用它们: project.copy{...}
当build.gradle
某人为我们的任务写信时
taskName { ...
在任务上调用configure(Closure)
方法。
我不确定这是否是正确的方法,但是如果一个任务有多个字段,并且相互之间的状态很难用getter-setter来控制,那么重新定义该方法似乎很方便,如下所示:
override def configure(Closure closure){ def result = super().configure(closure)
即使你写
taskName.fieldName value
那么仍然会调用configure
方法。
自己的插件
像任务一样,您可以编写自己的插件,该插件将配置某些内容或创建任务。 例如, android{...}
发生的事情完全是一个优点。 黑暗魔法 Android插件,它另外创建了一堆任务,例如app:assembleDevelopDebug,用于风味/构建类型/尺寸的所有可能组合。 编写插件没有什么复杂的,为了更好地理解,您可以查看其他插件的代码。
第三步-您可以将代码放置在buildSrc
,而不是将其放置在单独的项目中。 然后,使用https://jitpack.io或其他方式发布插件,并将其与其他插件类似地连接。
结束
上面的示例可能包括错别字和不正确之处。 写个人笔记或用ctrl+enter
标记-我会予以纠正。 最好从文档中获取特定的示例,并将本文作为“如何做”的一些清单。