Gradle基礎(chǔ)篇

項目和任務(wù)

Gradle中的所有內(nèi)容都基于兩個基本概念:項目任務(wù)

每個Gradle構(gòu)建都由一個或多個項目組成。項目代表什么取決于您使用Gradle做什么。例如,項目可能表示JAR或Web應(yīng)用程序。

每個項目由一個或多個任務(wù)組成。任務(wù)代表構(gòu)建執(zhí)行的一些原子工作。這可能是編譯某些類,創(chuàng)建JAR,生成Javadoc或?qū)⒁恍┐鏅n發(fā)布到存儲庫。

Hello,World

您使用gradle命令運行Gradle構(gòu)建。gradle命令查找當(dāng)前目錄中的build.gradle文件。我們稱這個build.gradle文件為構(gòu)建腳本,但嚴(yán)格來說它是一個構(gòu)建配置腳本

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

命令行shell中,執(zhí)行gradle -q hello

n$ gradle -q hello
Hello world!

任務(wù)依賴

您可以聲明依賴于其他任務(wù)的任務(wù)。

task hello {
    doLast {
        println 'Hello world!'
    }
}
task intro {
    dependsOn hello
    doLast {
        println "I'm Gradle"
    }
}

輸出

$ gradle -q intro
Hello world!
I'm Gradle

依賴尚不存在的任務(wù)

task taskX {
    dependsOn 'taskY'
    doLast {
        println 'taskX'
    }
}
task taskY {
    doLast {
        println 'taskY'
    }
}
$ gradle -q taskX
taskY
taskX

可以在定義之前聲明taskX 的依賴關(guān)系。這種自由對于多項目構(gòu)建非常重要。

動態(tài)任務(wù)

您還可以使用動態(tài)創(chuàng)建任務(wù)。

4.times { counter ->
    task "task$counter" {
        doLast {
            println "I'm task number $counter"
        }
    }
}
$ gradle -q task1
I'm task number 1

您可以在運行時動態(tài)地向任務(wù)添加依賴項

4.times { counter ->
    task "task$counter" {
        doLast {
            println "I'm task number $counter"
        }
    }
}
task0.dependsOn task2, task3
$ gradle -q task0
I'm task number 2
I'm task number 3
I'm task number 0

或者,您可以向現(xiàn)有任務(wù)添加行為。

task hello {
    doLast {
        println 'Hello Earth'
    }
}
hello.doFirst {
    println 'Hello Venus'
}
hello.configure {
    doLast {
        println 'Hello Mars'
    }
}
hello.configure {
    doLast {
        println 'Hello Jupiter'
    }
}
$ gradle -q hello
Hello Venus
Hello Earth
Hello Mars
Hello Jupiter

doFirstdoLast可以執(zhí)行多次。他們將操作添加到任務(wù)的操作列表的開頭或結(jié)尾。執(zhí)行任務(wù)時,將按順序執(zhí)行操作列表中的操作。

Groovy DSL快捷方式表示法

有一種方便的表示法來訪問現(xiàn)有任務(wù)。每個任務(wù)都可以作為構(gòu)建腳本的屬性使用:

將任務(wù)作為構(gòu)建腳本的屬性進行訪問

task hello {
    doLast {
        println 'Hello world!'
    }
}
hello.doLast {
    println "Greetings from the $it.name task."
}
$ gradle -q hello
Hello world!
Greetings from the hello task.

默認(rèn)任務(wù)

Gradle允許您定義在未指定其他任務(wù)時執(zhí)行的一個或多個默認(rèn)任務(wù)。

defaultTasks 'clean', 'run'

task clean {
    doLast {
        println 'Default Cleaning!'
    }
}

task run {
    doLast {
        println 'Default Running!'
    }
}

task other {
    doLast {
        println "I'm not a default task!"
    }
}
$ gradle -q
Default Cleaning!
Default Running!

gradle 項目分析

├── app
│   ├── app.iml
│   ├── build.gradle
│   ├── libs
│   ├── proguard-rules.pro
│   └── src
│       ├── main
│       │   ├── AndroidManifest.xml
│       │   ├── java
│       │   │   └── com
│       │   │       └── binzi
│       │   │           └── gradle
│       │   │               └── MainActivity.java
│       │   └── res
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.iml
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.properties
└── settings.gradle

settings.gradle

settings.gradle 是負(fù)責(zé)配置項目的腳本
對應(yīng) Settings類,gradle 構(gòu)建過程中,會根據(jù) settings.gradle 生成 Settings 的對象
其中幾個主要的方法有:

  • include(projectPaths)
  • includeFlat(projectNames)
  • project(projectDir)

一般在項目里見到的引用子模塊的方法,就是使用 include

include ':app'

如果想指定子模塊的位置,可以使用 project 方法獲取 Project 對象,設(shè)置其 projectDir 參數(shù)

include ':app'
project(':app').projectDir = new File('./app')

rootproject/build.gradle

build.gradle 負(fù)責(zé)整體項目的一些配置,對應(yīng)的是 Project
gradle 構(gòu)建的時候,會根據(jù) build.gradle 生成 Project 對象,所以在 build.gradle 里寫的 dsl,其實都是 Project 接口的一些方法,Project 其實是一個接口,真正的實現(xiàn)類是 DefaultProject
其中幾個主要方法有:

  • buildscript // 配置腳本的 classpath
  • allprojects // 配置項目及其子項目
  • respositories // 配置倉庫地址,后面的依賴都會去這里配置的地址查找
  • dependencies // 配置項目的依賴

module/build.gradle

build.gradle 是子項目的配置,對應(yīng)的也是 Project 類
子項目和根項目的配置是差不多的,不過在子項目里可以看到有一個明顯的區(qū)別,就是引用了一個插件 apply plugin "com.android.application",后面的 android dsl 就是 application 插件的 extension

其中幾個主要方法有:

  • compileSdkVersion // 指定編譯需要的 sdk 版本
  • defaultConfig // 指定默認(rèn)的屬性,會運用到所有的 variants 上
  • buildTypes // 一些編譯屬性可以在這里配置,可配置的所有屬性在 這里
  • productFlavor // 配置項目的 flavor

依賴

在 gradle 3.4 里引入了新的依賴配置,如下:

新配置 棄用配置 行為 作用
implementation compile 依賴項在編譯時對模塊可用,并且僅在運行時對模塊的消費者可用。 對于大型多項目構(gòu)建,使用 implementation 而不是 api/compile 可以顯著縮短構(gòu)建時間,因為它可以減少構(gòu)建系統(tǒng)需要重新編譯的項目量。 大多數(shù)應(yīng)用和測試模塊都應(yīng)使用此配置。 api 只會暴露給直接依賴的模塊,使用此配置,在模塊修改以后,只會重新編譯直接依賴的模塊,間接依賴的模塊不需要改動
api compile 依賴項在編譯時對模塊可用,并且在編譯時和運行時還對模塊的消費者可用。 此配置的行為類似于 compile(現(xiàn)在已棄用),一般情況下,您應(yīng)當(dāng)僅在庫模塊中使用它。 應(yīng)用模塊應(yīng)使用 implementation,除非您想要將其 API 公開給單獨的測試模塊。 api 會暴露給間接依賴的模塊,使用此配置,在模塊修改以后,模塊的直接依賴和間接依賴的模塊都需要重新編譯
compileOnly provided 依賴項僅在編譯時對模塊可用,并且在編譯或運行時對其消費者不可用。 此配置的行為類似于 provided(現(xiàn)在已棄用)。 只在編譯期間依賴模塊,打包以后運行時不會依賴,可以用來解決一些庫沖突的問題
runtimeOnly apk 依賴項僅在運行時對模塊及其消費者可用。 此配置的行為類似于 apk(現(xiàn)在已棄用)。 只在運行時依賴模塊,編譯時不依賴

gradle wrapper

gradlew / gradlew.bat 這個文件用來下載特定版本的 gradle 然后執(zhí)行的,就不需要開發(fā)者在本地再安裝 gradle 了。這樣做有什么好處呢?開發(fā)者在本地安裝 gradle,會碰到的問題是不同項目使用不同版本的 gradle 怎么處理,用 wrapper 就很好的解決了這個問題,可以在不同項目里使用不同的 gradle 版本。gradle wrapper 一般下載在 GRADLE_CACHE/wrapper/dists 目錄下

gradle/wrapper/gradle-wrapper.properties 是一些 gradlewrapper 的配置,其中用的比較多的就是 distributionUrl,可以執(zhí)行 gradle 的下載地址和版本
gradle/wrapper/gradle-wrapper.jar 是 gradlewrapper 運行需要的依賴包

gradle 生命周期

gradle 構(gòu)建分為三個階段
初始化階段

在初始化階段,Gradle判斷需要參與編譯的工程,為每個工程創(chuàng)建一個Project對象。在這個階段,Gradle會創(chuàng)建Settings對象,并在其上執(zhí)行settings.gradle腳本,建立工程之間的層次關(guān)系。

配置階段

在這個階段,Gradle會分別在每個Project對象上執(zhí)行對應(yīng)的build.gradle腳本,對Project進行配置。

執(zhí)行階段

在執(zhí)行階段,Gradle會判斷配置階段創(chuàng)建的哪些Task需要被執(zhí)行,然后執(zhí)行選中的每個Task。

gradle 在構(gòu)建過程中,會提供一些列回調(diào)接口,方便在不同的階段做一些事情,主要的接口有下面幾個:

gradle.addBuildListener(new BuildListener() {
    @Override
    void buildStarted(Gradle gradle) {
        println('構(gòu)建開始')
        // 這個回調(diào)一般不會調(diào)用,因為我們注冊的時機太晚,注冊的時候構(gòu)建已經(jīng)開始了,是 gradle 內(nèi)部使用的
    }

    @Override
    void settingsEvaluated(Settings settings) {
        println('settings 文件解析完成')
    }

    @Override
    void projectsLoaded(Gradle gradle) {
        println('項目加載完成')
        gradle.rootProject.subprojects.each { pro ->
            pro.beforeEvaluate {
                println("${pro.name} 項目配置之前調(diào)用")
            }
            pro.afterEvaluate{
                println("${pro.name} 項目配置之后調(diào)用")
            }
        }
    }

    @Override
    void projectsEvaluated(Gradle gradle) {
        println('項目解析完成')
    }

    @Override
    void buildFinished(BuildResult result) {
        println('構(gòu)建完成')
    }
})

gradle.taskGraph.whenReady {
    println("task 圖構(gòu)建完成")
}
gradle.taskGraph.beforeTask {
    println("每個 task 執(zhí)行前會調(diào)這個接口")
}
gradle.taskGraph.afterTask {
    println("每個 task 執(zhí)行完成會調(diào)這個接口")
}

常用API

org.gradle.api.Project

Project對象是Gradle中最核心的API,通過Project對象可以訪問所有Gradle特性。

Project與build.gradle

Project對象和build.gradle文件一一對應(yīng)。在Gradle構(gòu)建時,會先創(chuàng)建Settings實例并在其上執(zhí)行settings.gradle;再通過Settings對象定義的Project層級,創(chuàng)建若干個Project實例,并分別在其上執(zhí)行對應(yīng)的build.gradle

Extra屬性

Project有一個Extra屬性,可通過ext前綴在其中定義屬性,定義好后可以不加ext前綴直接訪問。

project.ext.prop1 = "foo"
task doStuff {
    ext.prop2 = "bar"
}
ext.isSnapshot = version.endsWith("-SNAPSHOT")
if (isSnapshot) {
    // do snapshot stuff
}

Project的屬性/方法調(diào)用

build.gradle中調(diào)用屬性,或調(diào)用Project.property(java.lang.String)方法時,會按順序從以下范圍查找:

  1. Project自身定義的屬性
  2. Project的Extra屬性
  3. 插件添加的Extension屬性
  4. 插件添加的Convension屬性
  5. Project中Task的名字
  6. 從父Project繼承的屬性,一直遞歸到RootProject

build.gradle中調(diào)用方法時,會按順序從以下范圍查找:

  1. Project自身定義的方法
  2. build.gradle腳本定義的方法
  3. 插件添加類型為Action或Closure的Extension
  4. 插件添加的Convension方法
  5. Project中Task的名字都會創(chuàng)建一個對應(yīng)方法
  6. 從父Project繼承的方法,一直遞歸到RootProject
  7. Project中為Closure類型的屬性可以作為方法調(diào)用

Project繼承了PluginAware、ExtensionAware,分別用于支持Plugin和Extension方法。部分常用API如下。

public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
    Project getRootProject();
    File getRootDir();
    File getBuildDir();
    void allprojects(Closure configureClosure);
    ScriptHandler getBuildscript();
    void buildscript(Closure configureClosure);
    RepositoryHandler getRepositories();
    void repositories(Closure configureClosure);
    ConfigurationContainer getConfigurations();
    void configurations(Closure configureClosure);
    DependencyHandler getDependencies();
    void dependencies(Closure configureClosure);
    ConfigurableFileCollection files(Object... paths);
    ConfigurableFileTree fileTree(Object baseDir);
    Convention getConvention();
    ExtensionContainer getExtensions();
    Task task(String name) throws InvalidUserDataException;
    Task task(String name, Closure configureClosure);
    void afterEvaluate(Closure closure);
    // ...
}

常用API示例(以下腳本均寫在build.gradle中):

// 配置Gradle插件,閉包參數(shù)會在ScriptHandler上執(zhí)行
buildscript {
// ...
}
// 配置所有工程,閉包參數(shù)會分別在每個Project上執(zhí)行
allprojects {
// ...
}
// 配置使用的倉庫,閉包參數(shù)會在RepositoryHandler上執(zhí)行
repositories {
// ...
}
// 配置依賴項,閉包參數(shù)會在DependencyHandler上執(zhí)行。
// files和fileTree也是Project提供的API,
// 而project則是DependencyHandler提供的API。
dependencies {
compile files('hibernate.jar', 'libs/spring.jar')
compile fileTree('libs')
    compile project(path: ':library')
    // ...
}
// 在當(dāng)前Project配置完成后,閉包會被執(zhí)行
afterEvaluate {
    println "Project '$name' has been evaluated!"
}
// 在RootProject配置完成后,閉包會被執(zhí)行
rootProject.afterEvaluate {
    println "RootProject '$name' has been evaluated!"
}

org.gradle.api.invocation.Gradle

Gradle對象表示一次Gradle調(diào)用,通過Project.getGradle()可以獲取這個對象。在一次構(gòu)建過程中只有一個Gradle對象。

public interface Gradle extends PluginAware {
    String getGradleVersion();
    File getGradleUserHomeDir();
    File getGradleHomeDir();
    Gradle getParent();
    Project getRootProject() throws IllegalStateException;
    void rootProject(Action< ? super Project> action);
    void allprojects(Action< ? super Project> action);
    TaskExecutionGraph getTaskGraph();
    StartParameter getStartParameter();
    ProjectEvaluationListener addProjectEvaluationListener(ProjectEvaluationListener listener);
    void removeProjectEvaluationListener(ProjectEvaluationListener listener);
    void beforeProject(Closure closure);
    void afterProject(Closure closure);
    void buildStarted(Closure closure);
    void settingsEvaluated(Closure closure);
    void projectsLoaded(Closure closure);
    void projectsEvaluated(Closure closure);
    void buildFinished(Closure closure);
    void addBuildListener(BuildListener buildListener);
    public void addListener(Object listener);
    public void removeListener(Object listener);
    public void useLogger(Object logger);
    Gradle getGradle();
}

org.gradle.api.initialization.Settings

Settings對象主要用于配置Project的層級結(jié)構(gòu)。

Settings對象和settings.gradle文件一一對應(yīng)。Gradle構(gòu)建的第一步,就是創(chuàng)建Settings對象并其上執(zhí)行settings.gradle腳本。

public interface Settings extends PluginAware {
    String DEFAULT_SETTINGS_FILE = "settings.gradle";
void include(String[] projectPaths);
void includeFlat(String[] projectNames);
    Settings getSettings();
File getSettingsDir();
    File getRootDir();
    ProjectDescriptor getRootProject();
    ProjectDescriptor project(String path) throws UnknownProjectException;
    ProjectDescriptor findProject(String path);
    ProjectDescriptor project(File projectDir) throws UnknownProjectException;
    ProjectDescriptor findProject(File projectDir);
    StartParameter getStartParameter();
    Gradle getGradle();
}

常用API示例:

  1. include()可以配置包含Project,例如include ':app', ':library'

  2. project()可獲取ProjectDescriptor從而做一些配置,例如經(jīng)常會配置Gradle依賴本地Library工程的路徑:

    include ':img:library'project(':img:library').projectDir = new File('../../img/library')
    

org.gradle.api.Task

Task

Task也是Gradle中很重要的API。Task代表構(gòu)建過程中的一個原子操作,例如編譯classes文件或生成JavaDoc。

每個Task屬于一個Project。每個Task都有一個名字。所屬Project名+Task名可組成唯一的完整名(fully qualified path),例如:app:assemble。

Action

每個Task包含一個Action序列,并在Task執(zhí)行時按先后順序執(zhí)行。通過Task的doFirst/doLast方法可以往Action序列的頭部/末尾添加Action,支持Action或閉包(閉包會被轉(zhuǎn)換成Action對象)。

Task依賴和排序

每個Task可以依賴其他Task,執(zhí)行Task時會先執(zhí)行其依賴的Task,通過dependsOn可設(shè)置依賴。每個Task還可以設(shè)置在其他Task之前、之后執(zhí)行,一般可通過mustRunAfter設(shè)置。

例如下面的配置,執(zhí)行A時一定會先執(zhí)行B;執(zhí)行A不一定會執(zhí)行C;當(dāng)A、C都要執(zhí)行時一定先執(zhí)行C。

taskA.dependsOn(taskB)
taskA.mustRunAfter(taskC)

Task的部分常用API如下:

public interface Task extends Comparable<Task>, ExtensionAware {
    String getName();
    Project getProject();
    TaskDependency getTaskDependencies();
    Task dependsOn(Object... paths);
    String getPath();
    Task doFirst(Action< ? super Task> action);
    Task doFirst(Closure action);
    Task doLast(Action< ? super Task> action);
    Task doLast(Closure action);
    Task configure(Closure configureClosure);
    Task mustRunAfter(Object... paths);
    TaskDependency shouldRunAfter(Object... paths);
    // ...
}

Task 的一些重要方法分類如下:

  • Task 行為
    Task.doFirst
    Task.doLast
  • Task 依賴順序
    Task.dependsOn
    Task.mustRunAfter
    Task.shouldRunAfter
    Task.finalizedBy
  • Task 的分組描述
    Task.group
    Task.description
  • Task 是否可用
    Task.enabled
  • Task 輸入輸出
    gradle 會比較 task 的 inputs 和 outputs 來決定 task 是否是最新的,如果 inputs 和 outputs 沒有變化,則認(rèn)為 task 是最新的,task 就會跳過不執(zhí)行
    Task.inputs
    Task.outputs
  • Task 是否執(zhí)行
    可以通過指定 Task.upToDateWhen = false 來強制 task 執(zhí)行
    Task.upToDateWhen

Task創(chuàng)建

注:Gradle不推薦使用task hello << { ... }的方式定義Task,并會在后續(xù)版本刪除,因此這里不做介紹。

build.gradle中創(chuàng)建Task,最常見寫法如下。task(xxx)是Project提供的API,最終調(diào)用了TaskContainer的create方法。可接收參數(shù)包括:

  • Task名稱(必選)
  • Map<String, ?>類型配置(可選)
  • 閉包配置(可選)
task hello(dependsOn: clean) {
doLast {
    println 'hello'
    }
}

也可以直接調(diào)用TaskContainer創(chuàng)建Task,Project中的tasks屬性即為TaskContainer對象。

tasks.create('hello')

Task創(chuàng)建后會在Project上添加一個同名方法,調(diào)用這個方法可以配置Task。

task hello
hello {
doLast {
    println 'hello'
    }
}

Task的type屬性,帶參數(shù)的Task

還可以用類實現(xiàn)Task,創(chuàng)建Task時指定type為這個class即可,定義Task的類通常繼承自DefaultTask。下列示例代碼中給Task定義了一個名為name的參數(shù)。

import org.gradle.api.internal.tasks.options.Option
class HelloTask extends DefaultTask {
    String personName = '';
    HelloTask() {
        doLast {
            println "Hello " + personName
        }
    }
    @Option(description = "set person name", option = "name")
    def setMessage(String name) {
        this.personName = name;
    }
}
task hello(type: HelloTask)

命令行中執(zhí)行效果:

$ ./gradlew hello --name Tom
:hello
Hello Tom
BUILD SUCCESSFUL
Total time: 0.889 secs
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容