Gradle总结

本篇参考Gradle官方文档,主要是从Android开发者的视角,介绍在使用Gradle进行构建的过程中涉及到的一些基础概念!不对之处,敬请指正。下面直接切入正题!

脚本语言
  • Groovy: 动态语言;脚本文件后缀为.gradle,eg:build.gradle
  • Kotlin: 静态语言;脚本文件后缀为.gradle.kts, eg:build.gradle.kts;使用kotlin书写gradle构建脚本,可以获得更好的IDE(IDEA、AndroidStudio)支持,自动补全、编译检查、重命名等。
脚本类型
  • init.gradle
  • settings.gradle
  • build.gradle
  • 其他脚本

gradle在构建时,会对脚本文件进行编译,生成对应的脚本对象。如将gradle脚本编译成一个实现Script接口的对象;将build.gradle.kts编译成KotlinBuildScript类型对象,将settings.gradle.kotlin编译成KotlinSettingsScript类型对象。因此,在脚本文件中,可以访问对应脚本对象中声明的属性与方法。

基本概念
  • Settings: 一个Settings对象对应settings.gradle脚本,由Gradle在配置阶段负责创建该对象,并执行settings.gradle脚本来对Settings对象进行配置。settings.gradle脚本使用include声明此次构建包含哪些工程(即包含哪些Project),并指定这些工程的路径、名称等。
  • Project:Gradle中抽象的概念,一般而言,一个Project与一个需要参与构建的软件工程或模块相对应,如Android工程中的App Module或者Library Module等。每一个Project对象与一个build.gradle脚本文件关联,Gradle在初始化阶段会为参与构建的每一个模块创建一个Project对象,并且在配置阶段,会执行对应的build.gradle脚本来对与之关联的Project对象进行配置。在build.gradle脚本中通过project顶层属性可以引用到关联的Project对象。
  • Task:很明显,代表构建过程中的一个子任务,负责完成构建过程中的某些操作

关于settings.gradle文件,有以下几点需要注意:

  1. 单项目构建该文件可选,但多项目构建这个文件是必须的,因为需要在这个文件中声明哪些子项目需要参与构建,也包括子项目路径、名称等
  2. Gradle允许在任意子项目中进行多项目的构建,那Gradle如何决定此次构建是多项目还是单项目构建呢?如果该构建目录中存在settings.gradle文件,那么就依据该文件来进行构建;如果不存在该文件,那么会向上一级目录查询文件是否存在(注意:只会向父目录查询,而不是一直向上级目录递归查询),如果存在就依据该文件进行构建,否则此次构建就是一个单项目的构建。因此,如果需要在多项目的一个工程目录结构中进行单项目的构建,我们可以在目标子项目的根目录下创建一个settings.gradle文件
构建流程
  • Initialize Phase:初始化阶段
    在该阶段主要是创建Settings对象,并执行settings.gralde脚本配置该对象,决定哪些Project参与构建,同时创建这些Project对象。settings.gradle脚本中使用include(projectpath)或者includeFlat(projectname)来声明包含的Project工程,并允许配置这些project的名称、工程路径等。下面对一个settings.gradle文件进行说明:
// include two projects, 'foo' and 'foo:bar'
// directories are inferred by replacing ':' with '/'
include 'foo:bar'  //(1)

// include one project whose project dir does not match the logical project path
include 'baz'  //(2)
project(':baz').name = ‘myBaz’
project(':baz').projectDir = file('foo/baz')

// include many projects whose project dirs do not match the logical project paths
file('subprojects').eachDir { dir ->
  include dir.name       //(3)
  project(":${dir.name}").projectDir = dir
}

//add a project with path :flatProject, name flatProject and project directory $rootDir/../flatProject
includeFlat('flatProject') //(4)

(1)include方法参数为projectpath,非文件路径,因此不能包含传统的目录分隔符号'/',取而代之使用需要使用冒号:分隔(在project path中冒号代表的是root project);projectpath的最后一个节点作为project名称,且默认情况下gradle会将projectpath转为相对于rootProject的路径。如foo:barprojectpath,会对应两个project,分别为foofoo:bar,且工程根路径分别为$rootProjectDir/foo$rootProjectDir/foo/bar
(2) 添加了一个baz工程,名称自定义为myBaz,默认此工程的根目录为$rootProjectDir/baz,但此目录不存在,gradle允许我们修改此路径,这里我们修改为$rootProjectDir/foo/baz
(3) 遍历subproject目录下的所有子目录,添加为一个project,并设置projectdir;
(4) includeFlat方法也能添加一个project,但查找工程projectDir的策略与include不一致,它会查找相对于rootProject父目录下的文件路径。因此,这里flatProject的工程路径为$rootProject/../flatProject

  • Configure Phase:配置阶段
    在该阶段,gradle会执行build.gradle脚本文件对Project进行配置

  • Execute Phase:执行阶段
    配置阶段完成之后,Gradle会构建出一张基于task的有向无环图,执行阶段即负责执行这些task

软件模块:Module

这里的Module并不是指Android工程中的Module,而是指向一个依赖,是一个可以随着时间不断更新的软件模块,如Google Guava库。每一个Module都有一个名称module name,随着时间的推移,模块会不断改善并重新发布,而模块的每一次发布都会有一个版本号,使用module version来描述。Module一般会被组织到Reposity中,通过module name以及module version可以精确定位到该模块。

产物:Artifact

产物特指由一次build生成的一个文件或一个目录,如一个Jar、Zip、AAR等。产物的生成就是为了给其他用户或Project使用的。

伴随着Module的每一次发布,都会有相应的产物,称之为artifact,一个模块可以产出多个产物,如.jar包、.aar包等,而每一个artifact都可以有自己独立的Transitive Dependencies。这些产物都会有对应的描述信息,保存在module的metadata文件中(如maven repository中该文件为pom.xml)。在进行Module依赖解析时,Gradle会根据需要从repository中选择合适的产物下载使用,且默认情况下还会自动解析Transitive Dependencies。
此外,Configuration也可以有相应的产物,这样在声明依赖时,就可以指定具体使用哪个configuration artifact了。如:implementation(project(path = ":api", configuration = "spi"))

  • 定义产物
val myJar by tasks.registering(Jar::class)
val someFile = file("$buildDir/somefile.txt")
artifacts {
  add("taskArtifact", myJar) //taskArtifact指向myJar
  add("fileArtifact", someFile)
}

何以为家,Module的容器:Repository

通常情况下,依赖是以modules的方式存在和引用的,在声明依赖时,我们需要告知Gradle上哪去获取这些依赖。依赖保存的位置或路径称之为repository。 类型主要有:

  • Flat directory repository:flatDir()
  • Maven Central repository:mavenCentral()
  • JCenter Maven repository: jcenter()
  • Google Maven repository: google()
  • Local Maven repository: mavenLocal()
  • Custom Maven repositories: maven {url = uri("http://repo.mycompany.com/maven2")}

在构建脚本中,我们需要使用repositories{}DSL声明这些repository的位置,它可以指向本地或远程的仓库。如下所示:

buildscript {
  repositories {
    google() -- (1)
    jcenter() -- (2)
    flatDir("name" to "libs", "dirs" to "libs") -- (3)
  }
  dependencies {
    //dependency declaration
  }
}

脚本中我们声明了依赖查找的3个目标仓库,(1)是google的仓库,(2)是jcenter仓库、(3)则是通过flatDir方法传入本地文件系统的路径来指定本地仓库,这里创建了一个本地reposity,命名为libs,路径为$rootProject/libs。(Android工程中经常使用flatDir来包含本地的aar依赖包)

在构建过程中,Gradle会在声明的reposity仓库中查找定位我们声明的依赖,从远程下载或从本地目录、仓库获取这些依赖的产物用于构建,并保存在本地缓存中。这个过程称为依赖解析。Gradle根据声明的依赖,按Repository声明的顺序,从上到下依次查找。只要找到了一项就返回,不会继续往下查找!因此,需要注意Repository声明的顺序。

物以类聚,依赖的组织形式: Configuration

Gradle允许针对不同的构建场景声明不同的依赖集合;如在编译打包发布时,我们不希望把测试用例的代码,以及跑测试用例需要依赖到的其他三方库与代码一起编译打包。因此,我们需要把这两种使用场景进行划分,不同的场景各自声明自己感兴趣的依赖,这样在不同场景下构建时,可以只解析和使用自己声明的依赖,避免了代码的混乱,也有利于代码的组织管理以及维护。在Gradle中,Configuration是一组有名称的依赖的集合,代表了该依赖组的一个使用场景(或作用域),如implementation configuration是编译project所需依赖的集合,testImplementation configuration是编译测试用例所需依赖的集合。Gradle要求每一个依赖都必须显示的指定其所属的Configuration。
Gradle框架本身以及我们日常引用的各类插件都会预先根据不同的使用场景定义出不同的configuration供使用,如Android插件中包含的implementationapicompileOnlyruntimeOnly等,当然我们也可以进行自定义。

  • 自定义Configuration:为某些特殊依赖场景自定义Configuration。
  • 继承Configuration:所有加入到父亲中的依赖,儿子都能直接继承和使用。
  • 一个Configuration可以包含一系列的Artifact供其他Project依赖使用!
val bar by configurations.registering //委托属性创建
configurations {
    create("baz") //直接创建
    bar.get().extendsFrom(getByName("implementation")) //继承

    providedCompile
    compile.extendsFrom providedCompile
}
dependencies {
  implementation "group:module:version"
  testImplementation "group:module1:version"
  bar "group:module2:version"
}

我们在日常开发中使用较多的mtl-publishmaven发布插件,其实也自定义了一个名为providedCompile的Configuration,并将compile从其继承,如上代码片段所示。
由于存在以上的继承关系,因此在构建过程中providedCompile与compile的作用其实是一样的,都能引用到依赖库aar包中的代码和资源文件
但在生成pom描述文件时(该文件中收集了该模块的所有三方依赖),会将所有providedCompile的依赖修改为provided依赖!
(注:如果一开始就使用provided,那么在开发阶段将无法引用到三方依赖库aar包中的资源,因此在引用依赖库资源时编译会失败)

你依赖我,我依赖它:Transitive Dependency

Module一般会提供额外的元数据,来详细描述该module的详细信息(如.module、.pom、ivy.xml文件)。如该module在repository中的坐标信息(group、name、version),作者author信息等。Module之间也可以相互依赖,因此在这些元数据中,包含一类特殊的数据,用于声明该module依赖的其他module,如JUnit 5 platform module需要依赖platform commons module。这些依赖称之为传递依赖(transitive dependencies),默认情况下,Gradle会自动解析和使用该module声明的其他module依赖,当然我们也可以针对性的配置这些依赖的解析规则。

依赖类型

  • Module Dependency:最常用的依赖类型,指向一个reposity中的目标模块,通过group、name、version来定位。依赖声明方式如下:configurationname(dependencyNotion, dependencyConfiguration)。configurationname指定依赖所属的Configuration(Gradle要求每个依赖都必须显示指定其宿主Configuration)、dependencyNotion是依赖的声明,如字符串形式或map形式,格式一般为{group}:{name}:{version}[{:classifier}@{extension}]。classifier代表同一个module的不同Artifact变种,extension指定Artifact的扩展名。最后还可以传入一个dependencyConfiguration配置Action,用于配置该依赖的一些属性,如change属性或transitive属性、force属性及exclude规则等等,详情可查阅该Action的运行上下文ExternalModuleDependency提供的接口。栗子:
dependencies {
  runtimeOnly(group: 'org.springframework', name: 'spring-  core', version:'2.5') {
      because("demenstrate the reason we pick this version")
      isTransitive = true //是否解析传递依赖,默认为true
      isChanging = true //是否为可变版本,过期后会重新获取
      isForce = false //当有依赖冲突时,是否强制使用该版本依赖
      exclude(group="name", name="name") //解析该依赖的transitive dependency时,不解析被exclude的部分
  }
}
  • File Dependency:本地文件依赖。可以直接依赖本地的jar、aar包等,而不需要上传至repository中。文件依赖使用FileCollection来指定。如下:
dependencies {
  //files构建一个FileCollection对象,包含了相对于当前脚本目录的 libs/a.jar和libs/b.jar两个依赖文件
  runtimeOnly(files("libs/a.jar", "libs/b.jar"))
  //通过libs路径构建一个目录树对象FileTree,该类继承于FileCollection,通过include匹配该目录下的所有.jar文件作为依赖
  runtimeOnly(fileTree("libs") { include("*.jar") })
  implementation files("$buildDir/classes") {
  //通过builtBy指定文件依赖的产物是由compile这个task生成的
  //因此,会优先执行该task生成文件依赖
      builtBy 'compile'
  }
}
  • Project Dependency:本地工程依赖。通过project(projectPath)方法来引用一个本地工程,projectPath参数为工程路径。如project(":A:A1:A2")

依赖声明

  • 直接指定具体的版本号进行依赖
dependencies {
  implementation 'group:name:5.0.2.RELEASE'
}
  • 不指定版本,而是通过Dependency Constraint来统一限定版本,可以指定在脚本中声明过的依赖版本,也可以指定Transitive Dependency依赖的版本。这种方式允许我们在一个地方集中进行声明
dependencies {
  implementation 'group:name'
}
dependencies {
  constraints {
    implementation 'group:name:5.0.2.RELEASE'
  }
}
  • 动态版本依赖:解析至符合要求的最新的版本进行依赖
dependencies {
  implementation 'group:name:5.+'
}
  • Rich version declaration: 使用strictly、require、prefer、reject规则进行声明。很少使用
dependencies {
  implementation("org.slf4j:slf4j-api") {
    version {
      strictly("[1.7, 1.8[")
      prefer("1.7.25")
    }
  }
}
  • 使用classifier@符号指定Artifact的种类与扩展名:一般而言,在进行依赖解析时,Gradle默认查找的依赖产物是JAR包,如果无法获取将解析失败。当JAR包类型的Artifact不存在(如为ZIP包、AAR包等),或者存在多个类型不同的Artifact、亦或是我们不想解析Transitive Dependency时,可以使用@符号指定依赖产物的扩展名。同一个模块也可能会输出不同变种的Artifact,比如经过混淆或者未经过混淆的,可以使用classifier进行声明。
dependencies {
  //min指定变种为已混淆;@js指定扩展名
  js 'jquery:jquery:3.2.1:min@js'
}
  • 在声明依赖时,可以额外的传递一个依赖配置lambda表达式Action,该Action在ExternalModuleDependency的上下文中执行,可以通过调用该类中的接口对依赖进行更细粒度的配置;具体查阅API文档。

依赖解析

Gradle允许我们自定义依赖解析的规则,可以帮助我们解决依赖冲突。说明如下:

  • Dependency Resolve Rules:依赖解析规则
dependencies {
    //version scheme
    implementation("group:name:default")
}

configurations.all { //this: Configuration
    resolutionStrategy.eachDependency { //this: DependencyResolveDetails
        //1. change dependency version
        if (requested.group == "group") {
            useVersion("1.2")
            because("why")
        }
        //2.deal with version scheme
        if (requested.group == "group" && requested.version == "default") {
            useVersion("1.1")
            because("why")
        }
        //3.change group/name
        if (requested.group == "group") {
            useTarget("group2:name2:1.1")
            because("why")
        }
    }
}
  • Dependency Substitution Rules: 依赖替换规则
configurations.all { //this: Configuration
    resolutionStrategy.dependencySubstitution { //this: DependencySubstitutions
        //1. substitute module dependency with project dependency
        substitute(module("org.utils:api")).apply {
            with(project(":projectA"))
            because("tell me why")
        }
        //2. substitute project dependency with module dependency
        substitute(project(":projectC")).apply {
            with(module("org.utils:api:1.3"))
            because("tell me why")
        }
    }
}
  • Changing configuration dependencies prior to resolution:依赖配置
configurations {
    implementation {
        withDependencies {//this:DependencySet
            val dep = find { it.name == "to-modify" } as ExternalModuleDependency
            dep.version {
                strictly("1.2")
            }
            dep.isChanging = false
            dep.isTransitive = true
            dep.exclude("group", "module")
        }
    }
}

依赖检查与构建检查

通过不同方式,可以输出Project的依赖列表、依赖关系以及依赖冲突如何解决,以及最终所解析至的依赖版本。

  • Build时使用--scan生成详细的报告,可以查看详细的各个阶段的耗时、task运行情况、依赖解析情况等。
  • 运行插件提供的task输出依赖关系,如Android插件提供的androidDependenciesTask。
  • 运行Gradle提供的dependenciesTask,通过--configuration选项指定要输出哪个Configuration的依赖关系
./gradlew dependencies --configuration implementation
  • 运行Gradle提供的dependencyInsightTask,可以跟踪某一个自定义Configuration下某个具体的依赖。追踪依赖的引用链,以及依赖最终解析的结果和原因(为什么解析到这个版本);
configurations {
    create("scm")
}
dependencies {
    "scm"("junit:junit:4.12")
}
./gradlew dependencyInsight --configuration scm --dependency junit:junit 

如何解决依赖冲突

  • 检查依赖冲突。当出现依赖冲突时,Gradle可能会帮我们解决,但结果可能不是我们想要的。这里可以使用failOnVersionConflict(),当出现冲突时,构建直接失败,需要手动解决
  • 在一些情况下,我们可能会同时依赖仓库中的版本以及依赖本地的Project工程,我们想使用本地工程,方便修改和调试!这种情况下可以通过配置preferProjectModules(),在出现上述冲突时,选择编译本地工程版本(前提是Project的name需要跟modulename一致)
configurations.all {
  resolutionStrategy {
    failOnVersionConflict()
    preferProjectModules()
  }
}

考虑如下场景:A 模块依赖 B1 和 C, D 模块依赖了 B2,其中 B1 和 B1 是同一个Module B的两个不同版本;同时,工程中我们同时依赖了 A 和 D。这种情况下,会存在针对Module B的依赖冲突!默认情况下Gradle会尝试帮我们解决依赖冲突,解决的方式是使用最新的版本;这里的最新不是判断版本号大小,应该是根据发布时间来决定!现在我们自己手动进行依赖冲突的解决:

  • 这里是由于 A 或者 D 的Transitive Dependency引发了冲突,那么可以配置 transitive 属性为false,即不进行传递依赖的解析,这种情况可能需要自己手动添加部分依赖。
(1) 方案一:针对 A 或 D 配置 transitive。这里针对A配置,不解析A模块的传递依赖,因此依赖中不再包含 B1 和 C,这里需要手动添加依赖 C
dependencies {
    implementation A {
      transitive = false
    }
    implementation C 
    implementation D {
      //transitive = false
    }
}
(2) 方案二:针对 A 或 D 配置 exclude规则
dependencies {
    implementation A {
      exclude  B1
    }
    implementation D {
      //exclude  B2
    }
}
  • 使用force强制依赖某个版本,如强制依赖 B1 或者 B2
configurations.all {
    resolutionStrategy {
       force B1
       // force B2 
    }
}
  • 根据上一节,配置依赖解析规则。进行版本声明、依赖替换等。不再赘述
启用插件
  • Script Plugin:指包含额外构建逻辑的一个脚本文件,可以是一个本地文件系统的脚本文件或一个远程http地址指向的脚本文件,通过apply方法指定from(脚本文件的路径或远程地址)来启用,引入后Gradle自动解析并执行该脚本文件;
//groovy dsl
apply from: 'other.gradle'
apply from: 'http://www.example.com/other.gradle'

//kotlin dsl
apply(from="other.gradle.kts")
apply(from="http://www.example.com/other.gradle.kts")
  • Binary Plugin: 通常为一个类或一个Jar包,插件类需要实现gradle提供的Plugin接口,当插件启用时,其实是调用了Plugin.apply(org.gradle.Project)方法。每一个插件对应一个唯一ID,通过plugin id来启用插件,如java, com.android.application, com.android.libary;插件实现可以直接定义在脚本文件中的一个实现类,或从远程仓库repositories中拉取。如以下代码,我们从google仓库中拉取了android插件并启用:
//rootProject: build.gradle.kts
buildscript {
    repositories {
      google()  // (1)
    }
    dependencies {
      classpath ‘com.android.tools.build:gradle:3.4.0’ //(2)
    }
}

//appProject: app/build.gradle.kts
plugins {
  id("com.android.application")  //(3)
}
//apply(plugin="com.android.application")

在主工程目录下的build.gradle.kts文件中,一般都会包含如上的片段代码:使用buildscript{}块来声明脚本运行时的依赖。repositories{}块声明工程中依赖查找的仓库,这里声明了google()仓库,脚本中使用的依赖都将首先尝试从该reposity中进行解析获取;在(2)中又声明了classpath依赖,指向了com.android.tools.build:gradle:3.4.0。意思就是将这个依赖包(从google仓库解析并下载jar包)添加到脚本文件编译执行的classpath中去。这样一来,在脚本文件中我们就可以import以及使用这个依赖包(jar包)中包含的相关类和方法、属性了。比如在(3)的位置,我们启用了com.android.application这个插件,这个插件的代码其实就是包含在com.android.tools.build:gradle:3.4.0这个依赖声明中的。

project and task path
  • project path: 以一个可选的冒号开头(代表RootProject,而不能以projectname来指定),其余的部分是以冒号分隔的project names,后一个project是前一个project的子project;如::projectA:projectB
    包含rootProject,projectA和projectB,A和B都是root的子工程,A是第一级儿子工程,B是第二级;同时B也是A的儿子工程。
  • task path: ${projectPath:taskName}
apply方法解析

apply方法的作用主要有:

  • 启动某个插件
  • 应用某个脚本文件到某个目标对象(委托对象)。脚本文件可以为本地路径或远程http协议的gradle脚本,由Gradle负责下载解析

如下面的示例:

task configure {
  doLast {
  def pos = new java.text.FieldPosition(10)
  // Apply the script
  apply from: 'other.gradle', to: pos
  println pos.beginIndex
  println pos.endIndex
  }
}
// Set properties.
beginIndex = 1
endIndex = 5

apply from: 'other.gradle', to: pos语句中,apply允许我们传递一个map对象。在这个map中,我们使用from这个key声明被应用的原始脚本文件为other.gradle,使用to这个key声明委托对象为pos。这样一来在other.gradle中的方法或属性都将以pos这个类型为FieldPosition的对象作为委托对象,脚本中访问的未声明方法和属性会在delegate对象中寻找,如beginIndex和endIndex其实是访问了委托对象的属性。该Feature在KotlinDSL中尚未得到支持。

SourceSet

Gradle Java Support引入了SourceSet的概念来构建基于源代码的Project,因为我们一般都是以类型来对源代码以及资源文件进行分类和组织,如应用代码、单元测试代码、集成测试代码,它们通常被分开并放在不同的目录下。每一个逻辑分组可以有自己的一组源代码文件依赖、classpath。SourceSet通常涉及以下几个和编译相关的方面:

  • 源代码文件所在的路径
  • 编译所需的其他classpath或者一系列依赖(在Dependency块中通过{sourceSet}Configuration引入依赖,如testCompileOnly、testImplementation)。其中sourceSet为名称(如test、androidTest),而Configuration为依赖类型(如compileOnly、implementation)。
  • 编译结果输出的目录

Gradle会自动为定义的每一个SourceSet生成一些编译Task以及Dependency Configuration。如compile<SourceSet>Java 、process<SourceSet>Resources、<SourceSet>Implementation等,除了main这个SourceSet外。main是一个特殊的SourceSet,用于工程的主生产代码(used for the project’s production code)。

The main source set

Most language plugins, Java included, automatically create a source set called main,
which is used for the project’s production code. This source set is special in that its
name is not included in the names of the configurations and tasks, hence why you
have just a compileJava task and compileOnly and implementation configurations
rather than compileMainJava, mainCompileOnly and mainImplementationrespectively。

除了main这个SourceSet外,一般插件还会生成其他的SourceSet,如Android的插件还提供了testandroidTest,分别用于跑测试用例和android的测试用例。我们可以配置sourceSet配置额外的代码、资源路径、exclude规则等

sourceSets {
  main {
    java {
      srcDir 'thirdParty/src/main/java'
    }
  }
}

SourceSet Properties

  • name — (RO) String
    The name of the source set, used to identify it.
  • output — (RO) SourceSetOutput
    The output files of the source set, containing its compiled classes and resources.
  • output.classesDirs — FileCollection
    Default value: buildDir/classes/java/name, e.g. build/classes/java/main
    The directories to generate the classes of this source set into. May contain directories for other
    JVM languages, e.g. build/classes/kotlin/main.
  • output.resourcesDir — File
    Default value: buildDir/resources/name, e.g. build/resources/main
    The directory to generate the resources of this source set into.
  • compileClasspath — FileCollection
    Default value: ${name}CompileClasspath configuration.The classpath to use whencompiling the source files of this source set.
  • java — (read-only) SourceDirectorySet
    The Java source files of this source set. Contains only .java files found in the Java source
    directories, and excludes all other files
  • java.srcDirs — Set<File>
    Default value: src/$name/java, e.g. src/main/java
    The source directories containing the Java source files of this source set
  • java.outputDir — File
    Default value: buildDir/classes/java/name, e.g. build/classes/java/main
    The directory to generate compiled Java sources into
  • resources — SourceDirectorySet
    The resources of this source set. Contains only resources, and excludes any .java files found in the resource directories.
  • resources.srcDirs — Set<File>
    Default value: [src/$name/resources]
    The directories containing the resources of this source set.
  • allJava — SourceDirectorySet
    Default value: Same as java property
    All Java files of this source set. Some plugins, such as the Groovy Plugin, add additional Java
    source files to this collection
  • allSource — SourceDirectorySet
    Default value: Sum of everything in the resources and java properties
    All source files of this source set of any language. This includes all resource files and all Java source files. Some plugins, such as the Groovy Plugin, add additional source files to this
    collection
configuration与artifacts
subprojects {
  apply(plugin = "java")
  group = "org.gradle.sample"
  version = "1.0"
}
project(":api") {
  configurations {
    create("spi") -----------(1)
  }
  dependencies {
    "implementation"(project(":shared"))
  }
  tasks.register<Jar>("spiJar") {
    archiveBaseName.set("api-spi")
    from(project.the<SourceSetContainer>()["main"].output)
    include("org/gradle/sample/api/**")
  }
  artifacts {
    add("spi", tasks["spiJar"]) -------(2)
  }
}
project(":services:personService") {
  dependencies {
    "implementation"(project(":shared"))
    "implementation"(project(path = ":api", configuration = "spi")) ---------(3)
    "testImplementation"("junit:junit:4.12")
    "testImplementation"(project(":api"))
  }
}

关于FlatDir

flatDir可以指定一个本地的目录作为一个备选的仓库,Gradle在搜索该仓库,查找对应的Module;在下面的示例中,我们指定aars目录作为一个本地仓库,其中包含名为'moduleA.aar'文件以及'moduleB.aar'文件。在(1)的依赖声明中,我们指定依赖的name为moduleA、扩展名为'aar', Gradle最终会解析到aars目录下的'moduleA.aar'文件。在(2)的依赖声明中,我们使用常规的'group:name:version@ext'声明,最终Gradle也会解析到aars目录下的'moduleB.aar'文件。因为,使用flatDir指定本地仓库时,Gradle会忽略group或者version部分,只要aars目录中存在'moduleB.aar'或者'moduleB-1.0.0.aar'这样的文件,Gradle都会成功解析到该依赖。

  repositories {
    flatDir {
            dirs 'aars'
        }
  }
  dependencies {
    implementation(name: 'moduleA', ext: 'aar') // (1)
    implementation('groupname.fake:moduleB:1.0.0') //(2)
  }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 0x00 前言 Android studio默认使用Gradle构建Android工程。我之前有花一段时间研究Gr...
    坚坚老师阅读 2,730评论 2 11
  • 参考资料:http://gold.xitu.io/post/580c85768ac247005b5472f9htt...
    zhaoyubetter阅读 10,959评论 0 6
  • 原文地址 作为Android开发你必须明白的Gradle基础 作为一个Android开发程序员,如果你的build...
    Android_冯星阅读 668评论 0 1
  • 一、健 康 本周跑步3次,最近2周雾霾和下雨,跑步次数减少,跑步速度比刚开始还慢,而且比较累,看样子跑步还得...
    小凡_7ec8阅读 191评论 0 0
  • 这几天,微博上有个标题一直吸引着我,90后的我们正在失去,一个时代已经过去了。90后的我们在长大,意味着有...
    政月阅读 343评论 0 0