OkHttp之buildSrc模块分析

181 阅读8分钟

biz.aQute.bnd依赖介绍

1. biz.aQute.bnd简介

  • biz.aQute.bnd是一个用于创建和处理OSGi捆绑包的工具集

  • 它提供了一系列工具和插件,用于生成OSGi元数据、验证OSGi清单和解析OSGi依赖

2. 在OkHttp项目中的应用

  • OkHttp项目使用biz.aQute.bnd来生成OSGi兼容的JAR文件

  • 通过buildSrc模块中的Osgi.kt文件定义了应用OSGi配置的函数

  • 主要函数包括applyOsgi和applyOsgiMultiplatform,用于不同类型的项目

3. OSGi配置

  • 在Osgi.kt中定义了OSGi捆绑包的各种属性,如Bundle-SymbolicName、Bundle-Version等

  • 配置了导出包和导入包的规则

  • 处理了多平台项目的特殊需求

4. 测试

  • okhttp-osgi-tests模块专门用于测试OkHttp库在OSGi环境中的兼容性

  • 使用aQute.bnd.build和biz.aQute.resolve包来创建OSGi工作空间和解析依赖

  • 测试确保所有OkHttp模块都有正确的OSGi元数据

5. 依赖

  • buildSrc/build.gradle.kts中引入了biz.aQute.bnd.builder插件

  • 项目依赖了多个bnd相关库,如biz.aQute.bndlib和biz.aQute.resolve

6. OSGi的重要性

  • OSGi允许Java应用程序在运行时动态安装、启动、停止和更新模块

  • 对于像OkHttp这样的库,OSGi兼容性意味着它可以在OSGi容器(如Eclipse Equinox、Apache Felix等)中使用

  • 这增强了库的适用性,特别是在企业环境中

buildSrc模块与biz.aQute.bnd分析

buildSrc模块概述

buildSrc是OkHttp项目中的一个特殊Gradle模块,用于集中管理构建逻辑和自定义任务。这个模块中的代码会自动编译并添加到Gradle构建脚本的类路径中,使其可以在项目的所有build.gradle.kts文件中使用。

在OkHttp项目中,buildSrc模块主要包含:

  • 构建配置逻辑

  • 自定义Gradle任务

  • OSGi相关配置(通过Osgi.kt)

  • ALPN版本管理(通过AlpnVersions.kt)

biz.aQute.bnd详解

什么是biz.aQute.bnd?

biz.aQute.bnd是一个强大的OSGi工具集,由Peter Kriens创建,用于简化OSGi捆绑包的创建和管理。它提供了:

1. OSGi清单生成:自动从类文件和依赖中生成OSGi元数据

2. 版本管理:处理语义化版本和包版本

3. 依赖解析:分析和解析OSGi依赖

4. 构建工具集成:与Gradle、Maven等构建工具的集成

OkHttp中的biz.aQute.bnd配置

在OkHttp项目中,biz.aQute.bnd的配置主要通过buildSrc/src/main/kotlin/Osgi.kt文件实现:

// 为标准Java/Kotlin项目应用OSGi配置
fun Project.applyOsgi() {
  // 应用biz.aQute.bnd.builder插件
  apply(plugin = "biz.aQute.bnd.builder")
  
  // 配置OSGi捆绑包属性
  configure<BundleExtension> {
    bnd(
      mapOf(
        "Bundle-SymbolicName" to "com.squareup.${project.name}",
        "Bundle-Name" to project.name,
        "Bundle-Description" to "Square's ${project.name} library.",
        "Bundle-Vendor" to "Square, Inc.",
        "Bundle-Version" to project.version,
        "Export-Package" to "!*.internal.*,${project.name}.*",
        "Import-Package" to "!javax.annotation.*,*",
        "Automatic-Module-Name" to "com.squareup.${project.name.replace('-', '.')}"
      )
    )
  }
}

// 为多平台项目应用OSGi配置
fun Project.applyOsgiMultiplatform() {
  // 多平台项目的特殊处理逻辑
  // ...
}

OSGi测试实现

OkHttp项目包含专门的okhttp-osgi-tests模块,用于验证库在OSGi环境中的兼容性:

1. 测试环境设置

  • 创建临时OSGi工作空间

  • 部署必要的OSGi捆绑包

  • 配置Eclipse Equinox作为OSGi框架

2. 依赖解析测试

@Test
fun testMainModuleWithSiblings() {
  createWorkspace().use { workspace ->
    createBndRun(workspace).use { bndRun ->
      bndRun.resolve(false, false)
    }
  }
}

此测试验证所有OkHttp模块都具有有效的OSGi元数据,并且可以在OSGi环境中正确解析。

3. 测试的捆绑包

private val REQUIRED_BUNDLES: List<String> = mutableListOf(
  "com.squareup.okhttp3",
  "com.squareup.okhttp3.brotli",
  "com.squareup.okhttp3.dnsoverhttps",
  "com.squareup.okhttp3.logging",
  "com.squareup.okhttp3.sse",
  "com.squareup.okhttp3.tls",
  "com.squareup.okhttp3.urlconnection"
)

biz.aQute.bnd的重要性

为什么OkHttp需要OSGi支持?

1. 广泛的兼容性

  • OSGi是Java生态系统中重要的模块化标准

  • 许多企业应用和中间件使用OSGi容器(如Eclipse Equinox、Apache Felix)

  • 通过支持OSGi,OkHttp可以在这些环境中无缝使用

2. 动态模块化

  • OSGi允许在运行时动态安装、启动、停止和更新模块

  • 这对于长时间运行的应用程序(如服务器)特别有价值

3. 版本管理

  • OSGi提供了严格的版本控制机制

  • 允许同一JVM中共存多个版本的库

biz.aQute.bnd的优势

1. 自动化

  • 自动分析类文件和依赖关系

  • 生成符合OSGi规范的清单文件

  • 减少手动配置错误

2. 构建集成

  • 与Gradle无缝集成

  • 作为构建过程的一部分生成OSGi元数据

3. 验证

  • 验证捆绑包的完整性

  • 确保所有依赖都正确声明

结论

biz.aQute.bnd在OkHttp项目中扮演着关键角色,它使OkHttp库能够在OSGi环境中正常工作,从而扩大了库的适用范围。通过buildSrc模块中的自定义配置,OkHttp团队简化了OSGi相关的构建逻辑,确保所有模块都具有一致的OSGi元数据。

这种方法不仅提高了OkHttp的兼容性,还展示了现代Java库如何通过适当的工具和配置来支持不同的运行环境和模块化系统。


从构建流程理解buildSrc代码逻辑

1. buildSrc 模块概述

在 Gradle 项目里,buildSrc 是一个特殊模块。Gradle 执行构建时,会自动编译并将 buildSrc 模块中的代码添加到构建脚本的类路径中。这使得开发者能在 buildSrc 里编写自定义的插件、扩展和工具类,供整个项目的构建脚本使用。

2. Osgi.kt 文件在 buildSrc 模块中的作用

Osgi.kt 文件位于 buildSrc/src/main/kotlin 目录下,其主要功能是为 Gradle 项目提供配置 OSGi(Open Service Gateway Initiative) 的能力。OSGi 是一种 Java 动态模块化系统标准,可让 Java 程序以模块化方式开发和部署,支持模块的动态加载、卸载和更新。Osgi.kt 通过扩展 Gradle 的 Project 类型,提供了将项目打包成符合 OSGi 规范的 Bundle 包的功能。

3. 核心功能分析

3.1 扩展函数

Osgi.kt 定义了两个扩展函数,分别用于不同类型项目的 OSGi 配置。

3.1.1 applyOsgi 函数
fun Project.applyOsgi(vararg bndProperties: String) {
  plugins.withId("org.jetbrains.kotlin.jvm") {
    applyOsgi("jar", "osgiApi", bndProperties)
  }
}

该函数适用于非 Kotlin/Multiplatform 项目。它会检查项目是否应用了 Kotlin JVM 插件,若应用了,则调用私有 applyOsgi 函数进行具体配置。

3.1.2 applyOsgiMultiplatform 函数
fun Project.applyOsgiMultiplatform(vararg bndProperties: String) {
  // 处理 Kotlin/Multiplatform 项目的兼容性问题
  val jvmMainSourceSet = sourceSets.getByName("jvmMain")
  val mainSourceSet = object : SourceSet by jvmMainSourceSet {
    // 重写部分方法
  }
  extensions.getByType(JavaPluginExtension::class.java).sourceSets.add(mainSourceSet)
  tasks.named { it.endsWith("ForFakeMain") }.configureEach { onlyIf { false } }

  val osgiApi = configurations.create("osgiApi")
  project.dependencies {
    osgiApi(kotlinOsgi)
  }

  tasks.named<Jar>("jvmJar").configure {
    val bundleExtension = extensions.create(
      BundleTaskExtension.NAME,
      BundleTaskExtension::class.java,
      this
    ).apply {
      classpath(osgiApi.artifacts)
      classpath(tasks.named("jvmMainClasses").map { it.outputs })
      bnd(*bndProperties)
    }
    doLast {
      bundleExtension.buildAction().execute(this)
    }
  }
}

此函数专门为 Kotlin/Multiplatform 项目设计。由于 BND 工具假设 JVM 源集名称为 main,而 Kotlin/Multiplatform 项目的 JVM 源集名称是 jvmMain,所以该函数创建了一个名为 main 的源集,将其转发到 jvmMain 源集,解决兼容性问题。同时,它还创建 osgiApi 配置,添加 Kotlin OSGi 依赖,并在 jvmJar 任务完成后添加 OSGi 元数据。

3.2 私有 applyOsgi 函数
private fun Project.applyOsgi(
  jarTaskName: String,
  osgiApiConfigurationName: String,
  bndProperties: Array<out String>,
) {
  val osgi = project.sourceSets.create("osgi")
  val osgiApi = project.configurations.getByName(osgiApiConfigurationName)

  project.dependencies {
    osgiApi(kotlinOsgi)
  }

  val jarTask = tasks.getByName<Jar>(jarTaskName)
  val bundleExtension = jarTask.extensions.findByType<BundleTaskExtension>()
    ?: jarTask.extensions.create(
      BundleTaskExtension.NAME,
      BundleTaskExtension::class.java,
      jarTask
    )
  bundleExtension.run {
    setClasspath(osgi.compileClasspath + sourceSets["main"].compileClasspath)
    bnd(*bndProperties)
  }
  jarTask.doLast {
    bundleExtension.buildAction().execute(this)
  }
}

该私有函数负责具体的 OSGi 配置逻辑。它创建 osgi SourceSet,获取 osgiApi 配置并添加 Kotlin OSGi 依赖,然后为指定的 Jar 任务配置 BundleTaskExtension,设置类路径和 BND 配置属性,最后在 Jar 任务完成后添加 OSGi 元数据。

3.3 扩展属性
val Project.sourceSets: SourceSetContainer
  get() = (this as ExtensionAware).extensions["sourceSets"] as SourceSetContainer

private val Project.kotlinOsgi: MinimalExternalModuleDependency
  get() = extensions.getByType(VersionCatalogsExtension::class.java)
    .named("libs")
    .findLibrary("kotlin.stdlib.osgi")
    .get()
    .get()
  • sourceSets 扩展属性:方便获取项目的 SourceSetContainer
  • kotlinOsgi 扩展属性:从版本目录中获取 Kotlin OSGi 依赖。

4. 工作流程

4.1 项目构建触发

开发者通过命令行(如 ./gradlew build)或 IDE 触发 Gradle 构建。

4.2 buildSrc 模块编译

Gradle 会自动编译 buildSrc 模块中的代码,并将其添加到构建脚本的类路径中。这意味着 Osgi.kt 里定义的扩展函数和属性在构建脚本里可以直接使用。

4.3 调用扩展函数

在项目的 build.gradle.kts 脚本中调用 applyOsgi 或 applyOsgiMultiplatform 函数,根据项目类型触发相应的 OSGi 配置逻辑。例如:

// 非 Kotlin/Multiplatform 项目
applyOsgi(
    "Bundle-SymbolicName: com.example.myapp",
    "Bundle-Version: 1.0.0",
    "Export-Package: com.example.api"
)

// Kotlin/Multiplatform 项目
applyOsgiMultiplatform(
    "Bundle-SymbolicName: com.example.myapp.multiplatform",
    "Bundle-Version: 1.0.0",
    "Export-Package: com.example.shared.api"
)
4.4 配置 OSGi 相关信息

根据项目类型,执行相应的配置逻辑:

  • 非 Kotlin/Multiplatform 项目:创建 osgi SourceSet,获取 osgiApi 配置并添加 Kotlin OSGi 依赖,为 Jar 任务配置 BundleTaskExtension,设置类路径和 BND 配置属性。
  • Kotlin/Multiplatform 项目:创建名为 main 的源集转发到 jvmMain 源集,解决兼容性问题;创建 osgiApi 配置,添加 Kotlin OSGi 依赖;为 jvmJar 任务配置 BundleTaskExtension,设置类路径和 BND 配置属性。
4.5 生成 OSGi Bundle 包

在 Jar 任务(非 Kotlin/Multiplatform 项目)或 jvmJar 任务(Kotlin/Multiplatform 项目)完成后,调用 BundleTaskExtension 的 buildAction 方法,为生成的 JAR 文件添加 OSGi 元数据,最终生成符合 OSGi 规范的 Bundle 包。

5. 构建配置示例

5.1 非 Kotlin/Multiplatform 项目构建配置

在 build.gradle.kts 中调用 applyOsgi 函数:

plugins {
    kotlin("jvm") version "1.9.20"
}

applyOsgi(
    "Bundle-SymbolicName: com.example.myapp",
    "Bundle-Version: 1.0.0",
    "Export-Package: com.example.api"
)
5.2 Kotlin/Multiplatform 项目构建配置

在 build.gradle.kts 中调用 applyOsgiMultiplatform 函数:

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

kotlin {
    jvm()
}

applyOsgiMultiplatform(
    "Bundle-SymbolicName: com.example.myapp.multiplatform",
    "Bundle-Version: 1.0.0",
    "Export-Package: com.example.shared.api"
)

6. 生成文件附加的配置信息

构建完成后,生成的 OSGi Bundle 包(通常是 JAR 文件)会在 META-INF/MANIFEST.MF 中包含附加的 OSGi 配置信息。以非 Kotlin/Multiplatform 项目构建配置示例为例,MANIFEST.MF 可能如下:

Manifest-Version: 1.0
Bundle-SymbolicName: com.example.myapp
Bundle-Version: 1.0.0
Export-Package: com.example.api
Import-Package: ...

这些信息由 applyOsgi 或 applyOsgiMultiplatform 函数中的 bndProperties 参数配置。

7. OSGi 插入构建的切入点对应的代码

7.1 非 Kotlin/Multiplatform 项目切入点
jarTask.doLast {
    bundleExtension.buildAction().execute(this)
}

在 applyOsgi 函数中,jarTask.doLast 是插入点,在 Jar 任务完成后,调用 BundleTaskExtension 的 buildAction 方法添加 OSGi 元数据。

7.2 Kotlin/Multiplatform 项目切入点
tasks.named<Jar>("jvmJar").configure {
    // ...
    doLast {
        bundleExtension.buildAction().execute(this)
    }
}

在 applyOsgiMultiplatform 函数中,jvmJar 任务的 doLast 闭包是插入点,在 jvmJar 任务完成后添加 OSGi 元数据。

8. 总结

buildSrc 模块中的 Osgi.kt 文件通过扩展 Gradle 的 Project 类型,为项目提供了便捷的 OSGi 配置功能。开发者可以根据项目类型选择合适的扩展函数,在构建脚本中配置 BND 属性,最终将项目打包成符合 OSGi 规范的 Bundle 包。OSGi 配置通过在 Jar 任务完成后插入操作,为生成的 JAR 文件添加必要的元数据。