跳转到内容
Skip
3.2k

依赖管理

Skip 项目的依赖有四种形式:

  1. 对双平台 Skip 包的依赖。
  2. 对纯 Swift Package Manager 包的依赖。
  3. 对仅 Android 代码使用的 Kotlin 或 Java 包库的依赖。
  4. 对由独立的 iOS 和 Android 库实现的通用功能的依赖。

请继续阅读,了解每种用例的详细说明。

使用 Package.swift 添加对外部 Skip SwiftPM 包的依赖,方式与标准 Swift 包相同。Skip 会检测依赖的 Skip/skip.yml 文件是否存在,并为该项目添加相应的 Gradle 依赖。换句话说,在 Swift 端添加 SwiftPM 依赖会自动在 Android 端创建 Gradle 依赖。因此,当你的项目仅依赖其他 Skip 项目时,通常不需要在自己的 skip.yml 文件中进行任何额外定制。


数千个纯 SwiftPM 包可以同时为 iOS 和 Android 构建。我们在 Swift Package Index 上追踪已知的 Android 兼容包。以标准方式将这些依赖添加到你的 Package.swift 中。

要将仅限 iOS 的 SwiftPM 包从 Android 构建的依赖中排除,请使用 Package.swift 的条件依赖语法:

let package = Package(
...
.target(name: "MyPackage", dependencies: [
.product(name: "Lottie", package: "lottie-ios", condition: .when(platforms: [.macOS, .iOS]))
], plugins: [.plugin(name: "skipstone", package: "skip")]),
...
)

有时项目的 Android 端可能需要使用 Skip 框架未提供的外部 Java 或 Kotlin 库。例如,一个 Skip 框架提供统一的双平台 API,但其底层实现依赖于外部项目产物。

SkipScript 框架为例,它在 iOS 上依赖内置的 JavaScriptCore 框架(因此 iOS 端没有外部依赖),但在 Android 上依赖外部的 jsc-android 库,其中包含脚本引擎和其他支持功能。

在这种情况下,你可以使用 skip.yml 将依赖添加到 Skip 项目的 Gradle 端。我们在这里讨论 skip.yml 和 Gradle。

例如,以下是 skip-script/Sources/SkipScript/Skip/skip.yml 文件,其中包含指定其对外部 jsc 库依赖的参数。这些参数会被聚合并包含在生成的 build.gradle.kts 文件中:

# 要添加到 build.gradle.kts 的块
build:
contents:
- block: 'dependencies'
contents:
- 'implementation("org.webkit:android-jsc-cppruntime:r245459@aar")'
- 'implementation("org.webkit:android-jsc:r245459@aar")'
# 要添加到 settings.gradle.kts 的块
settings:
contents:
- block: 'dependencyResolutionManagement'
contents:
- block: 'repositories'
contents:
- 'maven("https://github.com/jectivex/jsc-android/raw/main/releases")'

在许多情况下,依赖可以从内置的 mavenCentralgoogle 仓库获取。当依赖由这些内置仓库之一提供时,只需添加 build 块并列出要添加到项目的依赖,可以省略 settings 块。

生成的 build.gradle.kts 文件包含来自该模块和所有依赖模块的聚合块,大致如下:

// build.gradle.kts 由 Skip 为 SkipScript 模块生成。
// 此文件由 Skip 转译器插件生成,
// 来源于 SwiftPM 项目中聚合的 Skip/skip.yml 文件。
// 对此文件进行的直接编辑将被覆盖。
dependencies {
testImplementation("org.json:json:20180813")
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
testImplementation("org.robolectric:robolectric:4.10.3")
androidTestImplementation("androidx.test:runner:1.5.2")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
androidTestImplementation("org.jetbrains.kotlin:kotlin-test-junit")
testImplementation("androidx.test:core:1.5.0")
androidTestImplementation("androidx.test:core:1.5.0")
testImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
implementation("net.java.dev.jna:jna:5.13.0@aar")
testImplementation("net.java.dev.jna:jna:5.13.0")
implementation("org.webkit:android-jsc-cppruntime:r245459@aar")
implementation("org.webkit:android-jsc:r245459@aar")
implementation(project(":SkipFoundation"))
implementation(project(":SkipLib"))
testImplementation(project(":SkipUnit"))
androidTestImplementation(project(":SkipUnit"))
implementation(project(":SkipFFI"))
}
plugins {
kotlin("android") version "1.9.0"
id("com.android.library") version "8.1.0"
}
kotlin {
jvmToolchain(17)
}
android {
namespace = group as String
compileSdk = 34
defaultConfig {
minSdk = 29
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>() {
kotlinOptions {
suppressWarnings = true
}
}
tasks.withType<Test>().configureEach {
systemProperties.put("robolectric.logging", "stdout")
systemProperties.put("robolectric.graphicsMode", "NATIVE")
testLogging {
this.showStandardStreams = true
}
}

由于使用了 settings 块,生成的 settings.gradle.kts 文件将包含自定义的仓库配置:

// 这是项目的顶层 Gradle 设置。
// 其中包含的模块依赖可能是指向同级文件夹的符号链接。
//
// 此文件由 Skip 转译器插件生成,
// 来源于 SwiftPM 项目中聚合的 Skip/skip.yml 文件。
// 对此文件进行的直接编辑将被覆盖。
//
// 使用外部编辑器打开以在 IDE 中构建和运行此项目。
//
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
google()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
google()
maven("https://github.com/jectivex/jsc-android/raw/main/releases")
}
}
rootProject.name = "skip.script"
include(":SkipScript")
include(":SkipFoundation")
include(":SkipLib")
include(":SkipUnit")
include(":SkipFFI")

生成的构建文件会在下次运行 Skip 插件时被覆盖,因此不应直接编辑生成的输出。相反,对模块的 Skip/skip.yml 属性的任何更改都会导致 build.gradle.ktssettings.gradle.kts 被重新生成并作为构建的一部分包含进来。

要在转译的 Swift 中使用 Android 包,按常规方式导入即可:

#if SKIP
import com.xyz.SomeAndroidType
... 使用仅限 Android 的 API ...
#endif

要在编译的 Swift 中使用 Kotlin 或 Java 包,你必须使用桥接


Skip 框架通过 Gradle 版本目录 固定其底层 Android 库的特定版本,构建插件会将其聚合到 settings.gradle.kts 中。要使用不同版本的某个库——例如,在 Skip 自身采用之前拉取更新的 Jetpack Compose 版本——请在你自己模块的 Skip/skip.yml 中重新声明匹配的 version("…") 条目。你指定的值优先于框架默认值。

SkipModel 为例,它固定了 Jetpack Compose BOM(物料清单)版本:

settings:
contents:
- block: 'dependencyResolutionManagement'
contents:
- block: 'versionCatalogs'
contents:
- block: 'create("libs")'
contents:
- 'version("androidx-compose-bom", "2026.05.01")'
- 'library("androidx-compose-bom", "androidx.compose", "compose-bom").versionRef("androidx-compose-bom")'

要在你的应用中固定不同的 Compose BOM 版本,请在你的应用模块的 Skip/skip.yml 中添加匹配的 version("androidx-compose-bom", …) 条目:

settings:
contents:
- block: 'dependencyResolutionManagement'
contents:
- block: 'versionCatalogs'
contents:
- block: 'create("libs")'
contents:
- 'version("androidx-compose-bom", "2026.07.00")'

你在覆盖中指定的版本将优先生效:所有通过 libs.androidx.compose.bom 解析的 Compose 库都会使用新的 BOM。你只需要重新声明想要更改的 version(…);框架中匹配的 library(…) 条目仍然有效,因为它通过名称引用别名。

同样的模式适用于 Skip 框架声明的任何别名。例如,要提高或降低 SkipUnit 中声明的默认 minSdkcompileSdk 级别:

settings:
contents:
- block: 'dependencyResolutionManagement'
contents:
- block: 'versionCatalogs'
contents:
- block: 'create("libs")'
contents:
- 'version("android-sdk-min", "26")'
- 'version("android-sdk-compile", "35")'

浏览每个 Skip 框架的 Skip/skip.yml 文件(SkipUnitSkipModelSkipUI 等),找到可供覆盖的版本别名(例如 kotlinkotlin-coroutinesandroid-gradle-plugin 以及各种 AndroidX 库)。

你经常想使用在 iOS 和 Android 上都有现有库但尚无跨平台库可用的功能。大多数流行的第三方框架都发布了 iOS 和 Android 版本,大量其他通用功能也可在两个平台的独立开发者处获得。要使用这些功能,你有几个选择:

  1. 在每个使用处有条件地导入和调用 iOS 或 Android 库 API。这仅在你不在很多地方使用该功能时才可行。
  2. 在你的应用中编写一个简单的通用包装 API,内部委托给相应的平台 API。
  3. 创建你自己的 Skip 双平台库。这种方式的优势是你可以在其他项目中复用你的库,并将其贡献给开源社区,供其他 Skip 用户使用和改进。

无论你选择哪种方式,实现策略都是相同的:

  1. 如果你正在创建库,请遵循新双平台框架的入门指南。你可能需要与 Android Kotlin 或 Java 依赖紧密集成,因此考虑创建一个 Skip Lite 转译模块,将其 API 桥接到原生 Swift。
  2. 添加对 iOS 库的 Swift Package 依赖,并添加对你想使用的 Android 库的 Java/Kotlin 依赖
  3. 如果你在编写通用包装类或库,编写包装 API。
  4. 在实现代码中,使用 Skip 编译器指令有条件地导入和调用相应库的 API。使用跨平台主题中的技术来调用 Kotlin 和 Java API。

最终的实现模式大致如下:

#if !os(Android)
import SomeIOSLibrary
#else
import com.xyz.someandroidlibrary.__
#endif
public struct MyCommonAPI {
#if !os(Android)
private let libraryInstance = SomeIOSLibraryType()
#else
private let libraryInstance = SomeAndroidLibraryType()
#endif
public func myCommonAPIFunc() -> String {
#if !os(Android)
return libraryInstance.someIOSLibraryFunction()
#else
return libraryInstance.someAndroidLibraryFunction()
#endif
}
...
}

你可以在 Skip 框架的源码中清楚地看到这种模式,例如 SkipKeychain,它是一个很好的学习范例。

当编写一个提供标准或知名 iOS 框架功能的双平台库时,请考虑完全镜像该框架的 iOS API,就像 Skip 为转译代码镜像 Foundation 和其他框架一样。这遵循了 Skip 的透明采用理念。这样,用户无需编写针对你自定义 API 的代码,而是可以直接使用原来的 iOS API。在使用处,只有 import 会不同:

#if !os(Android)
import StandardIOSLibrary
#else
import SkipStandardIOSLibrary
#endif
// 使用 iOS 库 API...

这种模式还意味着你不必编写任何文档,也不必为 iOS 编写任何包装代码!Skip 应用的 iOS 端将直接针对现有的 iOS 库构建。Skip 的 FireStore 支持源码就是一个很好的例子。

同样,如果你创建了双平台库,请考虑将其贡献给开源 Skip 用户社区!