Edit Page

配置编译任务

最终更新: 2024/03/21

Kotlin 跨平台项目使用编译任务来生成 artifact. 每个编译目标可以有一个或多个编译任务, 比如, 用于产品和测试的编译任务.

对每个编译目标, 默认的编译任务包括:

编译任务

如果需要编译除产品代码与单元测试之外的其他代码, 比如, 集成测试, 或性能测试, 你可以 创建自定义编译任务.

你可以在以下范围配置如何产生 artifact:

请参见对所有编译目标或特定编译目标可用的 编译任务参数列表编译器选项.

配置所有编译任务

kotlin {
    targets.all {
        compilations.all {
            compilerOptions.configure {
                allWarningsAsErrors.set(true)
            }
        }
    }
}

配置单个编译目标的编译任务

kotlin {
    jvm().compilations.all {
        compilerOptions.configure {
            jvmTarget.set(JvmTarget.JVM_1_8)
        }
    }
}
kotlin {
    jvm().compilations.all {
        compilerOptions.configure {
            jvmTarget.set(JvmTarget.JVM_1_8)
        }
    }
}

配置一个编译任务

kotlin {
    jvm {
        val main by compilations.getting {
            compilerOptions.configure {
                jvmTarget.set(JvmTarget.JVM_1_8)
            }
        }
    }
}
kotlin {
    jvm {
        compilations.main {
            compilerOptions.configure {
                jvmTarget.set(JvmTarget.JVM_1_8)
            }
        }
    }
}

创建自定义编译任务

如果需要编译除产品代码与单元测试之外的其他代码, 比如, 集成测试, 或性能测试, 请创建自定义编译任务.

比如, 要对 jvm() 编译目标的集成测试创建自定义编译任务, 请向 compilations 集合内添加新的元素.

对于自定义编译任务, 需要手动设置所有的依赖项. 自定义编译任务的默认源代码集不会依赖 commonMaincommonTest 源代码集.

kotlin {
    jvm() {
        compilations {
            val main by getting

            val integrationTest by compilations.creating {
                defaultSourceSet {
                    dependencies {
                        // 使用 main 编译任务的编译期类路径和输出进行编译:
                        implementation(main.compileDependencyFiles + main.output.classesDirs)
                        implementation(kotlin("test-junit"))
                        /* ... */
                    }
                }

                // 创建 test 任务来运行这个编译任务产生的测试:
                tasks.create<Test>("integrationTest") {
                    // 运行测试使用的类路径包含:
                    // 编译期依赖项(包括 'main'), 运行时依赖项, 以及这个编译任务的输出:
                    classpath = compileDependencyFiles + runtimeDependencyFiles + output.allOutputs

                    // 只运行这个编译任务的输出中包含的测试:
                    testClassesDirs = output.classesDirs
                }
            }
        }
    }
}
kotlin {
    jvm() {
        compilations.create('integrationTest') {
            defaultSourceSet {
                dependencies {
                    def main = compilations.main
                    // 使用 main 编译任务的编译期类路径和输出进行编译:
                    implementation(main.compileDependencyFiles + main.output.classesDirs)
                    implementation kotlin('test-junit')
                    /* ... */
                }
            }

            // 创建 test 任务来运行这个编译任务产生的测试:
            tasks.create('jvmIntegrationTest', Test) {
                // 运行测试使用的类路径包含:
                // 编译期依赖项(包括 'main'), 运行时依赖项, 以及这个编译任务的输出:
                classpath = compileDependencyFiles + runtimeDependencyFiles + output.allOutputs

                // 只运行这个编译任务的输出中包含的测试:
                testClassesDirs = output.classesDirs
            }
        }
    }
}

对于其他情况也需要创建自定义编译任务, 比如, 如果希望在你的最终 artifact 中对不同的 JVM 版本组合编译任务, 或者已经在 Gradle 中设置过源代码集, 希望迁移到跨平台项目.

在 JVM 编译任务中使用 Java 源代码

使用 项目向导 创建项目时, Java 源代码会包含在 JVM 编译任务内.

在构建脚本中, 以下代码会应用 Gradle java plugin, 并配置编译目标, 使其与 java plugin 协作:

kotlin {
    jvm {
        withJava()
    }
}

Java 源代码文件 放在 Kotlin 源代码根路径的子目录之内. 比如, 路径是:

Java 源代码文件

共通源代码集不可以包含 Java 源代码.

由于目前的限制, Kotlin plugin 会替换 Java plugin 配置的某些任务:

  • 使用编译目标的 JAR 任务, 而不是 jar 任务 (比如, jvmJar).
  • 使用编译目标的 test 任务, 而不是 test 任务 (比如, jvmTest).
  • 资源由编译任务中的同等任务处理, 而不是 *ProcessResources 任务.

这个编译目标的发布由 Kotlin plugin 处理, 而且不需要 Java plugin 的具体步骤.

配置与原生语言的交互

Kotlin 提供 与原生语言的交互能力, 以及对编译任务进行相关配置的 DSL.

原生语言 支持的平台 备注
C 所有平台, WebAssembly 除外  
Objective-C Apple 平台 (macOS, iOS, watchOS, tvOS)  
经由 Objective-C 的 Swift Apple 平台 (macOS, iOS, watchOS, tvOS) Kotlin 只能使用 @objc 属性标注过的 Swift 声明.

编译任务可以与几个原生库交互. 在编译任务的 cinterops 代码段中, 可以使用 可用的参数 来配置交互能力.

kotlin {
    linuxX64 { // 请替换为你需要的编译目标.
        compilations.getByName("main") {
            val myInterop by cinterops.creating {
                // 描述原生 API 的 def 文件.
                // 默认路径是 src/nativeInterop/cinterop/<interop-name>.def
                defFile(project.file("def-file.def"))

                // 用来放置生成的 Kotlin API 的包.
                packageName("org.sample")

                // 通过 cinterop 工具传递给编译器的参数.
                compilerOpts("-Ipath/to/headers")

                // 用来查找头文件的目录.
                includeDirs.apply {
                    // 用来查找头文件的目录 (等价于编译器的 -I<path> 参数).
                    allHeaders("path1", "path2")

                    // 用来查找 def 文件的 'headerFilter' 参数中指定的头文件时使用的额外的目录.
                    // 等价于 -headerFilterAdditionalSearchPrefix 命令行参数.
                    headerFilterOnly("path1", "path2")
                }
                // includeDirs.allHeaders 的缩写方式.
                includeDirs("include/directory", "another/directory")
            }

            val anotherInterop by cinterops.creating { /* ... */ }
        }
    }
}
kotlin {
    linuxX64 { // 请替换为你需要的编译目标.
        compilations.main {
            cinterops {
                myInterop {
                    // 描述原生 API 的 def 文件.
                    // 默认路径是 src/nativeInterop/cinterop/<interop-name>.def
                    defFile project.file("def-file.def")

                    // 用来放置生成的 Kotlin API 的包.
                    packageName 'org.sample'

                    // 通过 cinterop 工具传递给编译器的参数.
                    compilerOpts '-Ipath/to/headers'

                    // 用来查找头文件的目录 (等价于编译器的 -I<path> 参数).
                    includeDirs.allHeaders("path1", "path2")

                    // 用来查找 def 文件的 'headerFilter' 参数中指定的头文件时使用的额外的目录.
                    // 等价于 -headerFilterAdditionalSearchPrefix 命令行参数.
                    includeDirs.headerFilterOnly("path1", "path2")

                    // includeDirs.allHeaders 的缩写方式.
                    includeDirs("include/directory", "another/directory")
                }

                anotherInterop { /* ... */ }
            }
        }
    }
}

Android 编译任务

对 Android 编译目标默认创建的编译任务会与 Android 构建变体(build variant) 绑定: 对每个构建变体, 会创建一个相同名称的 Kotlin 编译任务.

然后, 对每个构建变体编译的每个 Android 源代码集, 会创建 Kotlin 源代码集, 名称是 Android 源代码集名前面加上编译目标名, 比如, 对于 Kotlin 编译目标 android, 以及 Android 源代码集 debug, 对应的 Kotlin 源代码集名为 androidDebug. 这些 Kotlin 源代码集会被添加到对应的构建变体的编译任务中.

默认的源代码集 commonMain 会被添加到所有的产品构建变体(无论是应用程序还是库) 的编译任务中. 类似的, 源代码集 commonTest, 会被添加到单元测试(Unit Test)和设备测试(Instrumented Test)的构建变体中.

也支持使用 kapt 的注解处理, 但是由于目前的限制, 要求在配置 kapt 依赖项之前创建 Android 编译目标, 因此需要使用顶级的 dependencies { ... } 代码段, 而不是使用 Kotlin 源代码集的依赖项配置语法.

kotlin {
    android { /* ... */ }
}

dependencies {
    kapt("com.my.annotation:processor:1.0.0")
}

源代码集层级结构的编译任务

Kotlin 可以使用 dependsOn 关系来创建 源代码集层级结构.

源代码集层级结构

如果源代码集 jvmMain 依赖源代码集 commonMain, 那么:

  • 对某个编译目标, 当 jvmMain 被编译时, commonMain 也会参与这个编译任务, 而且也会被编译称为同一个编译目标的二进制形式, 比如 JVM class 文件.
  • jvmMain 的源代码可以 '看见' commonMain 的声明, 包括内部声明, 也能看见 commonMain依赖项, 即使是标记为 implementation 的依赖项.
  • jvmMain 可以包含 commonMain预期声明(expected declaration) 的平台相关的实现.
  • commonMain 的资源会与 jvmMain 的资源一起处理, 复制.
  • jvmMaincommonMain语言设置 应该保持一致.

语言设置的一致性检查规则是:

  • jvmMainlanguageVersion 设置应该高于或等于 commonMain 的设置.
  • commonMain 启用的所有非稳定的语言特性, jvmMain 都应该启用 (对于 bugfix 特性没有这个要求).
  • commonMain 使用的所有实验性注解, jvmMain 都应该使用.
  • apiVersion, bugfix 语言特性, 以及 progressiveMode 可以任意设定.