Kotlin 多平台:访问代码中的构建变量

ama*_*eus 10 gradle kotlin-multiplatform

我正在开发一个 Kotlin 多平台项目,该项目是一个为 iOS 和 Android 应用程序提供功能的 SDK。

在我们的代码中,build.gradle.kts我们希望在 iOS 和 Android 之间的公共代码共享代码中访问几个变量。

作为一名 Android 开发人员,我在 Android 项目中通常会这样做:

android {
    ...
    defaultConfig {
        ...
        buildConfigField "String", "SOME_VARIABLE", '"' + SOME_VARIABLE_IN_GRADLE_FILES + '"'
        ...
    }
    ...
}

Run Code Online (Sandbox Code Playgroud)

然后我可以在代码中访问它:

val someVariable = BuildConfig.SOME_VARIABLE
Run Code Online (Sandbox Code Playgroud)

如何才能使类似的东西在 Kotlin Mulitplatform 项目中工作,因为BuildConfig这不是公共共享代码库中认可的东西。

在搜索这个主题的解决方案后,我还没有找到任何相关的答案,但是我的 googlefoo 技能可能还不够......

aSe*_*emy 15

@Evgeny 提供的答案将解决问题,但我认为这是学习如何在纯 Gradle 中解决此问题的一个很好的例子。即使您想使用插件,了解 Gradle 的工作原理也可以帮助您了解如何自定义任何插件或 Gradle 行为。

生成源代码

有几个 Gradle 实用程序可用于从构建属性生成代码。

  1. TextResourceFactory可以动态创建文件。
  2. 创建输出文件的任务可以用作输入示例)。

注意惰性配置也很重要。应在必要时才生成该文件。

BuildConfig 生成器任务

任务是在 Gradle 中定义工作的方式,因此让我们创建一个将生成文件作为输出的任务。Sync是一个很好的任务类型。Sync会将任意数量的文件复制到一个目录中。

// build.gradle.kts

val buildConfigGenerator by tasks.registering(Sync::class) {
  // from(...) // no input files yet

  // the target directory
  into(layout.buildDirectory.dir("generated-src/kotlin"))
}
Run Code Online (Sandbox Code Playgroud)

如果您运行./gradlew buildConfigGenerator,则任务将运行,但唯一发生的事情是./build/generated-src/kotlin/创建一个空目录。因此,让我们添加一个文件作为输入。

定义生成的文件

TextResouceFactory是一个鲜为人知的工具,可以动态创建文本文件。(包括从 URL 下载文本文件,这可以派上用场!)

让我们使用它创建一个 Kotlin 文件。

// build.gradle.kts

val buildConfigGenerator by tasks.registering(Sync::class) {

  from(
    resources.text.fromString(
      """
        |package my.project
        |
        |object BuildConfig {
        |  const val PROJECT_VERSION = "${project.version}"
        |}
        |
      """.trimMargin()
    )
  ) {
    rename { "BuildConfig.kt" } // set the file name
    into("my/project/") // change the directory to match the package
  }

  into(layout.buildDirectory.dir("generated-src/kotlin/"))
}
Run Code Online (Sandbox Code Playgroud)

请注意,我必须重命名该文件(TextResourceFactory将生成一个随机名称),并将目录设置为匹配package my.project

现在如果你运行./gradlew buildConfigGenerator它实际上会生成一个文件!

在此输入图像描述

// ./build/generated-src/kotlin/BuildConfig.kt

package my.project

object BuildConfig {
  const val PROJECT_VERSION = "0.0.1"
}
Run Code Online (Sandbox Code Playgroud)

然而,还有两件事需要解决。

  1. 我不想手动运行此任务。我怎样才能让Gradle自动运行它?
  2. Gradle 无法识别./build/generated-src/kotlin/源目录。

我们可以一次性解决这两个问题!

将任务链接到源集

您可以通过 Kotlin Multiplatform 插件 DSL 将新的源目录添加到 Kotlin SourceSetkotlin.srcDir()

// build.gradle.kts

plugins {
  kotlin("multiplatform") version "1.7.22"
}

kotlin { 
  sourceSets {
    val commonMain by getting {
      kotlin.srcDir(/* add the generate source directory here... */)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我们可以硬编码kotlin.srcDir(layout.buildDirectory.dir("generated-src/kotlin/"))- 但现在我们还没有告诉 Gradle 我们的任务!我们仍然需要手动运行它。

幸运的是,我们可以两全其美。感谢 Gradle 的 Provider API,任务可以转换为 file-provider

// build.gradle.kts

kotlin { 
  sourceSets {
    val commonMain by getting {
      kotlin.srcDir(
        // convert the task to a file-provider
        buildConfigGenerator.map { it.destinationDir }
      )
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

(请注意,由于buildConfigGenerator是使用tasks.registering()其类型 is创建的TaskProvider,因此使用 类型创建的任务则不然tasks.creating()。)

generatedSourceDirProvider将延迟提供生成的目录,并且因为它是从任务映射的,所以 Gradle 知道每当使用源集进行编译时,它都需要运行连接的任务。commonMain

要对此进行测试,请运行./gradlew clean. 构建目录消失了,包括生成的文件。现在运行./gradlew assemble- Gradle 将自动运行generatedSourceDirProvider。魔法!

未来的改进

在 IDEA 同步期间生成源

当我第一次在 IntelliJ 中打开项目时,没有生成源代码,这有点烦人。我可以添加gradle-idea-ext-plugin,并使 IntelliJbuildConfigGenerator在 Gradle 同步上触发。

动态属性

在此示例中,我硬编码了project.version. 但是如果项目版本是动态的怎么办?

在这种情况下,我们需要使用一个提供者,它可以映射到一个文件。

// build.gradle.kts

val buildConfigGenerator by tasks.registering(Sync::class) {

  // create a provider for the project version
  val projectVersionProvider: Provider<String> = provider { project.version.toString() }

  // map the project version into a file
  val buildConfigFileContents: Provider<TextResource> =
    projectVersionProvider.map { version ->
      resources.text.fromString(
        """
          |package my.project
          |
          |object BuildConfig {
          |  const val PROJECT_VERSION = "$version"
          |}
          |
        """.trimMargin()
      )
    }

  // Gradle accepts file providers as Sync inputs
  from(buildConfigFileContents) {
    rename { "BuildConfig.kt" }
    into("my/project/")
  }

  into(layout.buildDirectory.dir("generated-src/kotlin/"))
}
Run Code Online (Sandbox Code Playgroud)
动态特性

如果您想从多个属性生成一个文件怎么办?在这种情况下,我要么

  • 谢谢@aSemy,无法要求更明确的答案。效果非常好! (2认同)

Evg*_*y K 5

您可以使用com.github.gmazzo.buildconfiggradle 插件来实现此目的(github)。

您可以在里面找到 KMM 项目的示例。

基本上你应该添加:

plugins {
    kotlin("multiplatform")
    // other plugins
    id("com.github.gmazzo.buildconfig") version "<latest>"
}

// ...

buildConfig {
    // common config
    buildConfigField("String", "COMMON_VALUE", "\"aCommonValue\"")

    // for specific source set
    sourceSets.named<BuildConfigSourceSet>("jvmMain") {
        buildConfigField("String", "PLATFORM", "\"jvm\"")
        buildConfigField("String", "JVM_VALUE", "\"JvmValue\"")
    }
}

Run Code Online (Sandbox Code Playgroud)

你也可以看看com.codingfeline.buildkonfiggithub)它有类似的API