学习笔记 - 5步理解Gradle. How build execution is controlled by gradle tasks?

本文是关于Gradle构建工具的学习笔记,介绍了Gradle作为通用构建工具的核心特性,包括基于任务的构建模型,固定构建阶段,以及其可扩展性和API使用。详细阐述了Gradle的初始化、配置和执行阶段,并探讨了声明式与命令式代码在构建脚本中的应用。
摘要由CSDN通过智能技术生成

https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:projects_and_tasks
https://docs.gradle.org/current/userguide/build_lifecycle.html
这是来自官网的学习笔记,含有很多复制粘贴,链接也都贴在上面和文章超链接里了

1. Five things you need to know about Gradle

1.1 Gradle is a general-purpose build tool

The most notable restriction is that dependency management currently only supports Maven- and Ivy-compatible repositories and the filesystem. But, Gradle makes it easy to build common types of project — say Java libraries — by adding a layer of conventions and prebuilt functionality through plugins.

1.2 The core model is based on tasks

  • Gradle models its builds as Directed Acyclic Graphs (DAGs) of tasks (units of work)… Once the task graph has been created, Gradle determines which tasks need to be run in which order and then proceeds to execute them.
  • Almost any build process can be modeled as a graph of tasks. And that task graph can be defined by both plugins and your own build scripts
  • Tasks themselves consist of:
    1. Actions
    2. Inputs
    3. Outputs

1.3 Gradle has several fixed build phases

1.3.1 Three phases

  1. Initialization
    Sets up the environment for the build and determine which projects will take part in it.

  2. Configuration
    Constructs and configures the task graph for the build and then determines which tasks need to run and in which order, based on the task the user wants to run.

  3. Execution
    Runs the tasks selected at the end of the configuration phase.

These phases form Gradle’s Build Lifecycle.

1.3.2 Declarative vs. Imperative code

  • Well-designed build scripts consist mostly of declarative configuration rather than imperative logic.
    That configuration is understandably evaluated during the configuration phase. Even so, many such builds also have task actions — for example via doLast {} and doFirst {} blocks — which are evaluated during the execution phase.
    This is important because code evaluated during the configuration phase won’t see changes that happen during the execution phase.

Declarative code: Built-in, language-agnostic DSL elements (e.g. Project.dependencies{}or Project.repositories{}) or DSLs exposed by plugins
Imperative code: Conditional logic or very complex task action implementations. (e.g. doLast {}and doFirst {})
The end goal of every build script should be to only contain declarative language elements which makes the code easier to understand and maintain. Imperative logic should live in binary plugins and which in turn is applied to the build script.

  • Another important aspect of the configuration phase is that everything involved in it is evaluated every time the build runs. That is why it’s best practice to avoid expensive work during the configuration phase. Build scans can help you identify such hotspots, among other things.

这里没有很理解,像Imperative code,应该是在execution phase才被evaluate的吧,那为什么还说,为了避免configuration phase任务繁重,要避免把imperative code写在build script里呢?即使写在里面,它也不会在configuration phase被evaluate吧。是不是因为不是所有的imperative code都会在execution phase被evaluate?

1.4 Gradle is extensible in

Most builds have some special requirements that mean you need to add custom build logic.
Gradle provides several mechanisms that allow you to extend it, such as:

  1. Custom task types.
  2. Custom task actions.
  3. Extra properties on projects and tasks.
  4. Custom conventions.
  5. A custom model.

1.5 Build scripts operate against an API

View Gradle’s build scripts as executable code is correct.
Understanding how the syntax of the build script maps to Gradle’s API.

2. Build Script Basics

2.1 Projects and tasks

Everything in Gradle sits on top of two basic concepts: projects and tasks.

  • Every Gradle build is made up of one or more projects.
  • Project: can represent a Jar, a web application, a distribution zip assembled from Jars…
    A project does not necessarily represent a thing to be built. It might represent a thing to be done, such as deploying your application to staging or production environments.

Each project is made up of one or more tasks. A task represents some atomic piece of work which a build performs.

2.2 Build script

build.gradle file == a build script, strictly, build.gradle file is a build configuration script.

build.gradle

task hello {
    doLast {
        println 'Hello world!'
    }
}

Above build script defines a single task, called hello, and adds an action to it

  • Gradle’s build scripts give you the full power of Groovy and Kotlin

2.3 Task dependencies

  • Lazy dependsOn - the other task does not exist (yet)
task taskX {
    dependsOn 'taskY'
    doLast {
        println 'taskX'
    }
}
task taskY {
    doLast {
        println 'taskY'
    }
}

// output of "gradle -q taskX"
> gradle -q taskX
taskY
taskX

… and more

3. Writing Build Scripts

4. Build Lifecycle

  • The core of Gradle is a language for dependency based programming - you can define tasks and dependencies between tasks.
  • Gradle guarantees that these tasks are executed in the order of their dependencies, and that each task is executed only once.
  • Gradle builds the complete dependency graph before any task is executed.
  • Your build scripts configure this dependency graph.

4.1 Build phases

  1. Initialization
    Gradle supports single and multi-project builds. During the initialization phase, Gradle determines which projects are going to take part in the build, and creates a Project instance for each of these projects.
  2. Configuration
    During this phase the project objects are configured. The build scripts of all projects which are part of the build are executed.
  3. Execution
    Gradle determines the subset of the tasks, created and configured during the configuration phase, to be executed. The subset is determined by the task name arguments passed to the gradle command and the current directory. Gradle then executes each of the selected tasks.

4.2 Settings file

Beside the build script files, Gradle defines a settings file - settings.gradle, indicating multi-project hierarchy.
The settings file is executed during the initialization phase.

settings.gradle

println 'This is executed during the initialization phase.'

build.gradle

println 'This is executed during the configuration phase.'

task configured {
    println 'This is also executed during the configuration phase.'
}

task test {
    doLast {
        println 'This is executed during the execution phase.'
    }
}

task testBoth {
	doFirst {
	  println 'This is executed first during the execution phase.'
	}
	doLast {
	  println 'This is executed last during the execution phase.'
	}
	println 'This is executed during the configuration phase as well.'
}

Output of gradle test testBoth:

> gradle test testBoth
This is executed during the initialization phase.

> Configure project :
This is executed during the configuration phase.
This is also executed during the configuration phase.
This is executed during the configuration phase as well.

> Task :test
This is executed during the execution phase.

> Task :testBoth
This is executed first during the execution phase.
This is executed last during the execution phase.

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed

4.3 Multi-project builds

4.3.1 Project locations

  • The project tree is created in the settings.gradle file.
  • By default it is assumed that the location of the settings file is also the location of the root project. But you can redefine the location of the root project in the settings file

4.3.2 Building the tree

  • Hierarchical layouts
    settings.gradle
include 'project1', 'project2:child', 'project3:child1'
  • The include method takes project paths as arguments.
  • A path ‘services:api’ is mapped by default to a folder ‘services/api’ (relative from the project root)
  • The inclusion of the path ‘services:hotels:api’ will result in creating 3 projects: ‘services’, ‘services:hotels’ and ‘services:hotels:api’.
  • Flat layouts
    settings.gradle
includeFlat 'project3', 'project4'
  • The includeFlat method takes directory names as an argument.
  • The location of these directories are considered as child projects of the root project in the multi-project tree.

4.3.3 Modifying elements of the project tree

The multi-project tree created in the settings file is made up of so called project descriptors. You can modify these descriptors in the settings file at any time. To access a descriptor you can do:

  1. Lookup of elements of the project tree
    settings.gradle
println rootProject.name
println project(':projectA').name
  1. Modification of elements of the project tree
    settings.gradle
rootProject.name = 'main'
project(':projectA').projectDir = new File(settingsDir, '../my-project-a')
project(':projectA').buildFileName = 'projectA.gradle'

4.4 Initialization

4.4.1 How Gradle finds settings.gradle

  • If you execute Gradle from within a project with no settings.gradle file, Gradle looks for a settings.gradle file in the following way:
  1. It looks in a directory called master which has the same nesting level as the current dir.
  2. If not found yet, it searches parent directories.
  3. If not found yet, the build is executed as a single project build.
  4. If a settings.gradle file is found, Gradle checks if the current project is part of the multi-project hierarchy defined in the found settings.gradle file. If not, the build is executed as a single project build. Otherwise a multi-project build is executed.

Gradle needs to determine whether the project you are in is a subproject of a multi-project build or not. Of course, if it is a subproject, only the subproject and its dependent projects are built, but Gradle needs to create the build configuration for the whole multi-project build (see Authoring Multi-Project Builds).

4.4.2 Is build executed as single- or multi-project

  • If the current project contains a settings.gradle file, the build is always executed as:
  1. A single project build, if the settings.gradle file does not define a multi-project hierarchy
  2. A multi-project build, if the settings.gradle file does define a multi-project hierarchy.

4.4.3 Multi-project vs. flat layout

  • The automatic search for a settings.gradle file only works for multi-project builds with a physical hierarchical or flat layout. For a flat layout you must additionally follow the naming convention described above (“master”).
  • Gradle supports arbitrary physical layouts for a multi-project build, but for such arbitrary layouts you need to execute the build from the directory where the settings file is located.
  • Gradle creates a Project object for every project taking part in the build. For a multi-project build these are the projects specified in the Settings object (plus the root project).

4.5 Configuration and execution of a single project build

For a single project build, the workflow of the after initialization phases are pretty simple. The build script is executed against the project object that was created during the initialization phase. Then Gradle looks for tasks with names equal to those passed as command line arguments, if these task names exist, they are executed as a separate build in the order you have passed them.

4.6 Responding to the lifecycle in the build script

Your build script can receive notifications as the build progresses through its lifecycle. These notifications generally take two forms:

  1. You can either implement a particular listener interface
  2. Or you can provide a closure to execute when the notification is fired.

The examples below use closures.

4.6.1 Project evaluation

4.6.1.1 You can receive a notification immediately before and after a project is evaluated.

Below is an example which adds a test task to each project with “hasTests == True”:
build.gradle

allprojects {
    afterEvaluate { project ->
        if (project.hasTests) {
            println "Adding test task to $project"
            project.task('test') {
                doLast {
                    println "Running tests for $project"
                }
            }
        }
    }
}

projectA.gradle

hasTests = true

Output of gradle -q test

> gradle -q test
Adding test task to project ':projectA'
Running tests for project ':projectA'

This example uses method Project.afterEvaluate() to add a closure which is executed after the project is evaluated.

4.6.1.2 Receive notifications when any project is evaluated

This example performs some custom logging of project evaluation. Notice that the afterProject notification is received regardless of whether the project evaluates successfully or fails with an exception.

build.gradle

gradle.afterProject { project ->
    if (project.state.failure) {
        println "Evaluation of $project FAILED"
    } else {
        println "Evaluation of $project succeeded"
    }
}

Output of gradle -q test

gradle -q test
Evaluation of root project 'buildProjectEvaluateEvents' succeeded
Evaluation of project ':projectA' succeeded
Evaluation of project ':projectB' FAILED

FAILURE: Build failed with an exception.

* Where:
Build file '/home/user/gradle/samples/groovy/projectB.gradle' line: 1

* What went wrong:
A problem occurred evaluating project ':projectB'.
> broken

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 0s

4.6.2 Task creation

You can receive a notification immediately after a task is added to a project. This can be used to set some default values or add behaviour before the task is made available in the build file.
The following example sets the srcDir property of each task as it is created.
build.gradle

tasks.whenTaskAdded { task ->
    task.ext.srcDir = 'src/main/java'
}

task a

println "source dir is $a.srcDir"

Output of gradle -q a

gradle -q a
source dir is src/main/java

4.6.3 Task execution graph ready

You can receive a notification immediately after the task execution graph has been populated.
You can also add a TaskExecutionGraphListenerto the TaskExecutionGraphto receive these events.

4.6.4 Task execution

You can receive a notification immediately before and after any task is executed.

The following example logs the start and end of each task execution. Notice that the afterTask notification is received regardless of whether the task completes successfully or fails with an exception.

build.gradle

task ok

task broken(dependsOn: ok) {
    doLast {
        throw new RuntimeException('broken')
    }
}

gradle.taskGraph.beforeTask { Task task ->
    println "executing $task ..."
}

gradle.taskGraph.afterTask { Task task, TaskState state ->
    if (state.failure) {
        println "FAILED"
    }
    else {
        println "done"
    }
}

Output of gradle -q broken

> gradle -q broken
executing task ':ok' ...
done
executing task ':broken' ...
FAILED

FAILURE: Build failed with an exception.

* Where:
Build file '/home/user/gradle/samples/groovy/build.gradle' line: 5

* What went wrong:
Execution failed for task ':broken'.
> broken

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 0s

You can also use a TaskExecutionListenerto the TaskExecutionGraphto receive these events.

瞎看看

https://guides.gradle.org/creating-multi-project-builds/
In a multi-project you can use the top-level build script (also known as the root project) to configure as much commonality as possible, leaving sub-projects to customize only what is necessary for that subproject.
Gradle automatically detected that there is a build task in greeting-library and executed it. This is one of the powerful features of a Gradle multi-project build.

Execution failed for task ':app:mergeDebugResources'. > C:\Users\����Сͷ\.gradle\caches\transforms-2\files-2.1\7148643cdd30f7f62dbee74cd8e064bd\material-1.9.0\res\values\values.xml: Error: Can't determine type for tag '<macro name="m3_comp_assist_chip_container_shape">?attr/shapeAppearanceCornerSmall</macro>' * Try: Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Exception is: org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:mergeDebugResources'. at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:207) at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:263) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:205) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:186) at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:114) at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46) at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:62) at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57) at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56) at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecut
06-10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值