# 构建最终的原生二进制文件 Kotlin/Native 编译目标默认会被编译输出为 `*.klib` 库文件, 这种库文件可以被 Kotlin/Native 用作依赖项, 但它不能执行, 也不能被用作一个原生的库. 如果要编译为最终的原生二进制文件, 比如可执行文件, 或共享库, 可以使用原生编译目标的 `binaries` 属性. 这个属性值是原生二进制文件的列表, 表示除默认的 `*.klib` 库文件之外, 这个编译目标还需要编译为哪些类型, 这个属性还提供了一组方法, 用来声明和配置这些原生二进制文件. > **Note:** > `kotlin-multiplatform` plugin 默认不会创建任何产品版(production)的二进制文件. 默认情况下, 只会产生一个调试版(debug)的测试可执行文件, 你可以通过 `test` 编译任务来运行这个可执行文件内的单元测试. Kotlin/Native 编译器生成的二进制文件可能包含第三方代码, 数据, 或衍生作品. 也就是说, 如果你发布 Kotlin/Native 编译的最终二进制文件, 那么你始终需要在你的二进制分发版中包含必要的 [许可证文件](native-binary-licenses.html). ## 声明二进制文件 请使用以下工厂方法来声明 `binaries` 列表中的元素. | 工厂方法 | 二进制文件类型 | 可用于 | | --- | --- | --- | | `executable` | 产品版的可执行文件 | 所有的原生编译目标 | | `test` | 测试程序的可执行文件 | 所有的原生编译目标 | | `sharedLib` | Shared 原生库 | 所有的原生编译目标 | | `staticLib` | Static 原生库 | 所有的原生编译目标 | | `framework` | Objective-C 框架 | 仅限于 macOS, iOS, watchOS, 和 tvOS 编译目标 | 最简单的版本不需要任何额外参数, 并对每一个构建类型创建一个二进制文件. 现在有 2 种构建类型: * `DEBUG` – 产生一个未经优化的二进制文件, 带有额外的 metadata, 供 [调试工具](native-debugging.html) 使用 * `RELEASE` – 产生优化过的二进制文件, 没有调试信息 下面的代码会创建 2 个可执行的二进制文件, debug 和 release: ```KOTLIN kotlin { linuxX64 { // 这里请改为你的编译目标. binaries { executable { // 这里指定二进制文件的配置信息. } } } } ``` 如果不需要[额外的配置](multiplatform-dsl-reference.html#native-targets), 那么可以省略这个 Lambda 表达式: ```KOTLIN binaries { executable() } ``` 还可以指定对哪些构建类型创建二进制文件. 下面的示例只创建 `debug` 版的二进制文件: Kotlin: ```KOTLIN binaries { executable(listOf(DEBUG)) { // 这里指定二进制文件的配置信息. } } ``` Groovy: ```GROOVY binaries { executable([DEBUG]) { // 这里指定二进制文件的配置信息. } } ``` 还可以使用自定义的名称来声明二进制文件: Kotlin: ```KOTLIN binaries { executable("foo", listOf(DEBUG)) { // 这里指定二进制文件的配置信息. } // 可以省略构建类型 // (这时会使用所有可用的构建类型). executable("bar") { // 这里指定二进制文件的配置信息. } } ``` Groovy: ```GROOVY binaries { executable('foo', [DEBUG]) { // 这里指定二进制文件的配置信息. } // 可以省略构建类型 // (这时会使用所有可用的构建类型). executable('bar') { // 这里指定二进制文件的配置信息. } } ``` 这个示例中的第一个参数指定一个名称前缀, 它会是二进制文件的默认名称. 比如, 在 Windows 平台, 这个示例会输出 `foo.exe` 和 `bar.exe`. 还可以使用这个名称前缀 [在构建脚本中访问二进制文件](#access-binaries). ## 访问二进制文件 可以访问二进制文件来 [对其进行配置](multiplatform-dsl-reference.html#native-targets), 或者得到它们的属性 (比如, 得到输出文件的路径). 可以通过二进制文件的唯一名称来得到它. 这个名称由名称前缀(如果有指定), 构建类型, 以及二进制文件类型组成, 使用以下命名方式: ``, 比如, `releaseFramework` 或 `testDebugExecutable`. > **Note:** > 静态库和共享库分别带有 static 和 shared 后缀, 比如, `fooDebugStatic` 或 `barReleaseShared`. Kotlin: ```KOTLIN // 如果二进制文件不存在, 这个函数会失败. binaries["fooDebugExecutable"] binaries.getByName("fooDebugExecutable") // 如果二进制文件不存在, 这个函数会返回 null. binaries.findByName("fooDebugExecutable") ``` Groovy: ```GROOVY // 如果二进制文件不存在, 这个函数会失败. binaries['fooDebugExecutable'] binaries.fooDebugExecutable binaries.getByName('fooDebugExecutable') // 如果二进制文件不存在, 这个函数会返回 null. binaries.findByName('fooDebugExecutable') ``` 另一种方法是, 可以使用名称前缀和构建类型, 通过有类型的 get 方法访问二进制文件. Kotlin: ```KOTLIN // 如果二进制文件不存在, 这个函数会失败. binaries.getExecutable("foo", DEBUG) binaries.getExecutable(DEBUG) // 如果没有设置名称前缀, 可以省略第一个参数. binaries.getExecutable("bar", "DEBUG") // 对于构建类型, 也可以使用字符串. // 对其他二进制文件类型, 可以使用类似的 get 方法: // getFramework, getStaticLib 以及 getSharedLib. // 如果二进制文件不存在, 这个函数会返回 null. binaries.findExecutable("foo", DEBUG) // 对其他二进制文件类型, 可以使用类似的 get 方法: // findFramework, findStaticLib 以及 findSharedLib. ``` Groovy: ```GROOVY // 如果二进制文件不存在, 这个函数会失败. binaries.getExecutable('foo', DEBUG) binaries.getExecutable(DEBUG) // 如果没有设置名称前缀, 可以省略第一个参数. binaries.getExecutable('bar', 'DEBUG') // 对于构建类型, 也可以使用字符串. // 对其他二进制文件类型, 可以使用类似的 get 方法: // getFramework, getStaticLib 以及 getSharedLib. // 如果二进制文件不存在, 这个函数会返回 null. binaries.findExecutable('foo', DEBUG) // 对其他二进制文件类型, 可以使用类似的 get 方法: // findFramework, findStaticLib 以及 findSharedLib. ``` ## 将依赖项目导出到二进制文件 编译 Objective-C 框架, 或原生库(共享库或静态库)时, 经常会出现一种需要, 不仅要打包当前项目的类文件, 同时还要打包它的依赖项的类. 我们可以用 `export` 方法, 指定需要导出哪些依赖项到二进制文件中. Kotlin: ```KOTLIN kotlin { // ... sourceSets { macosMain.dependencies { // 这些依赖项会被导出. api(project(":dependency")) api("org.example:exported-library:1.0") // 这个依赖项不会被导出. api("org.example:not-exported-library:1.0") } } macosArm64("macos").binaries { framework { export(project(":dependency")) export("org.example:exported-library:1.0") } sharedLib { // 可以对不同的二进制文件导出不同的依赖项目. export(project(':dependency')) } } } ``` Groovy: ```GROOVY kotlin { // ... sourceSets { macosMain.dependencies { // 这些依赖项会被导出. api project(':dependency') api 'org.example:exported-library:1.0' // 这个依赖项不会被导出. api 'org.example:not-exported-library:1.0' } } macosArm64("macos").binaries { framework { export project(':dependency') export 'org.example:exported-library:1.0' } sharedLib { // 可以对不同的二进制文件导出不同的依赖项目. export project(':dependency') } } } ``` 比如, 你用 Kotlin 实现了几个模块, 并且想要在 Swift 中访问这些模块. 在一个 Swift 应用程序中无法使用多个 Kotlin/Native 框架, 但你可以创建一个 umbrella 框架, 把所有这些模块都导出到这个框架. > **Note:** > 只能导出对应的源代码集的 [api 依赖项](gradle-configure-project.html#dependency-types). 当你导出一个依赖项, 它的所有 API 到会包含框架 API 中. 编译器会向框架添加这个依赖项的代码, 即使你只使用了它的一小部分. 这就使得对导出的依赖项 (以及某种程度上对它的依赖项) 死代码消除功能不再有效. 默认情况下, 导出是非传递性的(non-transitively). 也就是说, 如果你导出的库 `foo` 依赖于库 `bar`, 只有 `foo` 中的方法会被添加到输出的框架中. 这种行为可以通过 `transitiveExport` 选项来修改. 如果设置为 `true`, 库 `bar` 中的声明也会被导出. > **Warning:** > 不推荐使用 `transitiveExport`: 它会将导出的依赖项的所有传递依赖项添加到框架. 这会增加编译时间, 并增大输出的二进制文件大小. > > > > 大多数情况下, 你不需要将所有这些依赖项添加到 framework API. 应该只对你需要在 Swift 或 Objective-C 代码中直接访问的依赖项, 明确使用 `export`. Kotlin: ```KOTLIN binaries { framework { export(project(":dependency")) // 传递性导出. transitiveExport = true } } ``` Groovy: ```GROOVY binaries { framework { export project(':dependency') // 传递性导出. transitiveExport = true } } ``` ## 构建通用框架(Universal Framework) 默认情况下, Kotlin/Native 编译产生的 Objective-C 框架只支持单个平台. 但是, 使用 [lipo 工具程序](https://llvm.org/docs/CommandGuide/llvm-lipo.html), 可以将多个框架合并为单个通用的(fat) 二进制文件. 对 32 位和 64 位 iOS 框架来说, 这种操作尤其合理. 这种情况下, 最终产生的通用框架可以同时运行在 32 位和 64 位设备上. > **Warning:** > fat 框架必须使用与原框架相同的基本名称(base name). 否则会发生错误. Kotlin: ```KOTLIN import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask kotlin { // 创建并配置编译目标. val watchos32 = watchosArm32("watchos32") val watchos64 = watchosArm64("watchos64") configure(listOf(watchos32, watchos64)) { binaries.framework { baseName = "MyFramework" } } // 创建 fat 框架的构建任务. tasks.register("debugFatFramework") { // fat 框架必须使用与原框架相同的基本名称(base name). baseName = "MyFramework" // 默认的输出目录是 "/fat-framework". destinationDirProperty.set(layout.buildDirectory.dir("fat-framework/debug")) // 指定需要合并的框架. from( watchos32.binaries.getFramework("DEBUG"), watchos64.binaries.getFramework("DEBUG") ) } } ``` Groovy: ```GROOVY import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask kotlin { // 创建并配置编译目标. targets { watchosArm32("watchos32") watchosArm64("watchos64") configure([watchos32, watchos64]) { binaries.framework { baseName = "MyFramework" } } } // 创建 fat 框架的构建任务. tasks.register("debugFatFramework", FatFrameworkTask) { // fat 框架必须使用与原框架相同的基本名称(base name). baseName = "MyFramework" // 默认的输出目录是 "/fat-framework". destinationDirProperty.set(layout.buildDirectory.dir("fat-framework/debug")) // 指定需要合并的框架. from( targets.watchos32.binaries.getFramework("DEBUG"), targets.watchos64.binaries.getFramework("DEBUG") ) } } ``` ## 构建 XCFramework 所有的 Kotlin 跨平台项目都可以使用 XCFramework 作为输出, 将用于所有目标平台和架构的逻辑收集在单个 bundle 之内. 与 [单个通用的(fat)框架](#build-universal-frameworks) 不同, 在将应用程序发布到 App Store 之前, 你不需要删除所有不必要的架构. Kotlin: ```KOTLIN import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework plugins { kotlin("multiplatform") version "2.4.20" } kotlin { val xcf = XCFramework() val iosTargets = listOf(iosArm64(), iosSimulatorArm64()) iosTargets.forEach { it.binaries.framework { baseName = "shared" xcf.add(this) } } } ``` Groovy: ```GROOVY import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkConfig plugins { id 'org.jetbrains.kotlin.multiplatform' version '2.4.20' } kotlin { def xcf = new XCFrameworkConfig(project) def iosTargets = [iosArm64(), iosSimulatorArm64()] iosTargets.forEach { it.binaries.framework { baseName = 'shared' xcf.add(it) } } } ``` 在你声明 XCFramework 时, Kotlin Gradle plugin 会注册几个 Gradle task: * `assembleXCFramework` * `assembleDebugXCFramework` * `assembleReleaseXCFramework` 如果在你的项目中使用 [CocoaPods 集成](multiplatform-cocoapods-overview.html), 那么可以使用 Kotlin CocoaPods Gradle plugin 构建 XCFramework. 它包含以下 task, 使用所有已注册的编译目标构建 XCFramework, 并生成 podspec 文件: * `podPublishReleaseXCFramework`, 生成 release 版 XCFramework 以及一个 podspec 文件. * `podPublishDebugXCFramework`, 生成 debug 版 XCFramework 以及一个 podspec 文件. * `podPublishXCFramework`, 生成 debug 版和 release 版 XCFramework 以及一个 podspec 文件. 通过这些 task, 可以帮助你将你的项目的共用部分从移动应用程序中分离出来, 单独通过 CocoaPod 发布. 你也可以使用 XCFramework 来发布到私有的或公共的 podspec 仓库. > **Warning:** > 如果 Kotlin 框架使用不同的 Kotlin 版本构建, 那么不推荐发布这些框架到公共仓库. 这样做可能导致在最终使用者的项目中发生冲突. ## 定制 Info.plist 文件 输出框架时, Kotlin/Native 编译器会生成信息属性列表文件, `Info.plist`. 你可以使用相应的二进制选项来定制其中的属性: | 属性 | 二进制 | | --- | --- | | `CFBundleIdentifier` | `bundleId` | | `CFBundleShortVersionString` | `bundleShortVersionString` | | `CFBundleVersion` | `bundleVersion` | 要启用这个功能, 请对指定的框架使用 `-Xbinary=$option=$value` 编译器选项, 或通过 Gradle DSL 设置 `binaryOption("option", "value")`: ```KOTLIN binaries { framework { binaryOption("bundleId", "com.example.app") binaryOption("bundleVersion", "2") } } ```