依赖管理
Skip 项目的依赖有四种形式:
- 对双平台 Skip 包的依赖。
- 对纯 Swift Package Manager 包的依赖。
- 对仅 Android 代码使用的 Kotlin 或 Java 包库的依赖。
- 对由独立的 iOS 和 Android 库实现的通用功能的依赖。
请继续阅读,了解每种用例的详细说明。
添加 Skip 包依赖
Section titled “添加 Skip 包依赖”使用 Package.swift 添加对外部 Skip SwiftPM 包的依赖,方式与标准 Swift 包相同。Skip 会检测依赖的 Skip/skip.yml 文件是否存在,并为该项目添加相应的 Gradle 依赖。换句话说,在 Swift 端添加 SwiftPM 依赖会自动在 Android 端创建 Gradle 依赖。因此,当你的项目仅依赖其他 Skip 项目时,通常不需要在自己的 skip.yml 文件中进行任何额外定制。
添加纯 Swift Package Manager 依赖
Section titled “添加纯 Swift Package Manager 依赖”数千个纯 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")]),
...)添加 Java/Kotlin 依赖
Section titled “添加 Java/Kotlin 依赖”有时项目的 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")'在许多情况下,依赖可以从内置的 mavenCentral 和 google 仓库获取。当依赖由这些内置仓库之一提供时,只需添加 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.kts 和 settings.gradle.kts 被重新生成并作为构建的一部分包含进来。
要在转译的 Swift 中使用 Android 包,按常规方式导入即可:
#if SKIPimport com.xyz.SomeAndroidType... 使用仅限 Android 的 API ...#endif要在编译的 Swift 中使用 Kotlin 或 Java 包,你必须使用桥接。
覆盖依赖版本
Section titled “覆盖依赖版本”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 ↗ 中声明的默认 minSdk 和 compileSdk 级别:
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 文件(SkipUnit ↗、SkipModel ↗、SkipUI ↗ 等),找到可供覆盖的版本别名(例如 kotlin、kotlin-coroutines、android-gradle-plugin 以及各种 AndroidX 库)。
独立的 iOS 和 Android 库依赖
Section titled “独立的 iOS 和 Android 库依赖”你经常想使用在 iOS 和 Android 上都有现有库但尚无跨平台库可用的功能。大多数流行的第三方框架都发布了 iOS 和 Android 版本,大量其他通用功能也可在两个平台的独立开发者处获得。要使用这些功能,你有几个选择:
- 在每个使用处有条件地导入和调用 iOS 或 Android 库 API。这仅在你不在很多地方使用该功能时才可行。
- 在你的应用中编写一个简单的通用包装 API,内部委托给相应的平台 API。
- 创建你自己的 Skip 双平台库。这种方式的优势是你可以在其他项目中复用你的库,并将其贡献给开源社区,供其他 Skip 用户使用和改进。
无论你选择哪种方式,实现策略都是相同的:
- 如果你正在创建库,请遵循新双平台框架的入门指南。你可能需要与 Android Kotlin 或 Java 依赖紧密集成,因此考虑创建一个 Skip Lite 转译模块,将其 API 桥接到原生 Swift。
- 添加对 iOS 库的 Swift Package 依赖,并添加对你想使用的 Android 库的 Java/Kotlin 依赖。
- 如果你在编写通用包装类或库,编写包装 API。
- 在实现代码中,使用 Skip 编译器指令有条件地导入和调用相应库的 API。使用跨平台主题中的技术来调用 Kotlin 和 Java API。
最终的实现模式大致如下:
#if !os(Android)import SomeIOSLibrary#elseimport 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#elseimport SkipStandardIOSLibrary#endif
// 使用 iOS 库 API...这种模式还意味着你不必编写任何文档,也不必为 iOS 编写任何包装代码!Skip 应用的 iOS 端将直接针对现有的 iOS 库构建。Skip 的 FireStore 支持源码 ↗就是一个很好的例子。
同样,如果你创建了双平台库,请考虑将其贡献给开源 Skip 用户社区!