ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SwiftPM 混合语言 Target 支持(SE-0403)深度解析:让 Swift 与 C/Objective-C/C++ 共处一个模块

SwiftPM 混合语言 Target 支持(SE-0403)深度解析:让 Swift 与 C/Objective-C/C++ 共处一个模块 文档【免费下载链接】swift-evolutionThis maintains proposals for changes and user-visible enhancements to the Swift Programming Language.项目地址https://gitcode.com/gh_mirrors/sw/swift-evolution点击查看免费下载SE-0403Package Manager Mixed Language Target Support是 Swift Evolution 针对 Swift Package Manager 的一项提案目标是在单个 target 内同时容纳 Swift 与 C/Objective-C/C 源码并让这些源码编译为一个对外暴露的统一模块。本文将完整继承提案的技术细节目录组织约定、导入方式、编译标志、模块映射与构建产物并结合本仓库内 SE-0038、SE-0541 等关联提案从源码与构建机制层面深入讲解其设计与实现原理帮助包作者理解如何组织混合语言包以及 SwiftPM 内部如何驱动 Swift 与 Clang 两套编译器协同完成构建。背景与动机为何需要混合语言 Target在 SE-0403 之前SwiftPM 遵循 SE-0038Package Manager C Language Target Support确立的约定一个 target 的源码要么全部是 Swift要么全部是 C 系语言C、C、Objective-C、Objective-C不能两者共存。SE-0038 明确写道This proposal is limited in scope to only supporting targets consisting entirely of C languages; there is no provision for supporting targets which include both C and Swift sources.这给需要同时使用两种语言开发包的开发者带来了困境。对于维护混合语言包的开发者提案指出了两条现有变通方案但都存在显著缺陷通过 binary target 分发二进制框架。缺点包括包的移植性下降只能支持二进制所覆盖的平台二进制依赖仅适用于 Apple 平台使用方无法在工作区中查看或调试源码发布流程需要额外的二进制生成工具链。按语言把 target 拆分成多个子 target如 Swift 的Foo调用 Clang 的FooObjc再通过依赖关系串联。缺点包括必须在 target 之间设计公共 API 表面显著增加 Package.swift 清单与包组织结构的复杂度无论对维护者还是使用者阻碍包作者在 target 内部渐进式地把实现从一种语言迁移到另一种语言例如 Objective-C 迁移到 Swift因为语言间仍存在 target 级分隔。混合语言 target 支持正是为了同时解决这两类问题让开发者可以在单个 target 内自由混合受支持语言的源码而无需复杂化包结构或牺牲开发体验。解决方案总览一个模块、两套编译器包作者只需在 target 的源目录中混放不同语言的源码即可创建混合 target。对于 C 等语言还可以通过在 manifest 中配置SwiftSetting.InteroperabilityMode来选择是否启用高级互操作性特性。混合 target 构建完成后其公共 API 会被打包为单一模块供客户端使用。从高层看构建过程按源码语言划分为两部分Swift 源码由 Swift 编译器构建C/Objective-C/C 源码由 Clang 编译器构建。两者的协作遵循两条关键约束构建 Swift 源码生成swiftmodule时Swift 编译器需要感知到该 target 的 Clang 部分Clang 部分在构建时需要知道互操作 Swift headerinterop header且该 header 的内容取决于 target 配置了何种语言互操作模式如果配置了的话。该互操作 header 会被模块化为混合 target 公共接口的一部分。目录结构约定提案给出的示例包MixedPackage展示了典型的混合语言目录组织方式MixedPackage ├── Package.swift ├── Sources │ └── MixedPackage │ ├── Jedi.swift ⎤-- Swift sources │ ├── Lightsaber.swift ⎦ │ ├── Sith.m ⎤-- Implementations internal headers │ ├── SithRegistry.h ⎟ │ ├── SithRegistry.m ⎟ │ ├── droid_debug.c ⎦ │ ├── hello_there.txt ]-- Resources │ └── include ⎤-- Public headers │ ├── MixedPackage.h ⎟ │ ├── Sith.h ⎟ │ └── droid_debug.h ⎦ └── Tests └── MixedPackageTests ├── JediTests.swift ]-- Swift tests ├── SithTests.m ]-- Objective-C tests ├── ObjcTestConstants.h ⎤-- Mixed language test utils ├── ObjcTestConstants.m ⎟ └── SwiftTestConstants.swift ⎦这一布局延续了 SE-0038 的include目录约定include或Includes目录中的头文件被视为 target 的导出公共接口而 target 根目录下散落的.h文件如SithRegistry.h属于实现内部头文件。资源文件如hello_there.txt与源码混放。在该布局下混合 target 支持以下能力跨混合语言源码导出公共 API如果有target 内的 C/Objective-C/C 源码可以使用该 target Swift 源码中兼容 C/Objective-C/C 的 Swift APItarget 内的 Swift 源码可以使用该 target C/Objective-C/C 源码中兼容 Swift 的 API从 Swift 与 Objective-C 两种上下文访问 target 资源。初始限制SE-0403 明确列出的首批支持限制target 类型必须是 library 或 test target。对其他 target 类型如 executable的支持延后直到用例清晰若 target 包含自定义 module map则不能包含形如$(ModuleName).Swift的子模块。原因是 SwiftPM 会合成一个扩展extendedmodule map其中包含一个将生成的 Swift 互操作 header 模块化的子模块二者会产生冲突。导入混合 target 的方式以包含混合语言 target 的包MixedPackage为例客户端 target 可按语言上下文选择不同导入方式。在 Swift 上下文中导入// MyClientTarget.swift import MixedPackage测试 target 则可通过testable import MixedPackage导入。与纯 Swift target 一致这会暴露模块内的 internal Swift 类型但不会暴露任何非 public 的 C 语言类型。在 C/Objective-C/C 上下文中导入导入方式取决于目标语言。当支持 Clang modules 时客户端可以直接进行模块导入import同时也支持文本导入#import。考虑如下目录组织的MixedPackageMixedPackage ├── Package.swift └── Sources ├── NewCar.swift └── include ]-- Public headers directory ├── OldCar.h └── MixedPackage-Swift.h ]-- This header is generated during the build.与普通 Clang target 一样MixedPackage的公共头目录上例中的include会被加入客户端 target 的头文件搜索路径。下面展示了可以从MixedPackage导入的全部公共头文件// MyClientTarget.m // 若支持模块导入公共 API含生成 Swift header 中的 API可通过模块导入方式引入。 import MixedPackage; // 导入 OldCar.h 中定义的类型。 #import OldCar.h // 导入 MixedPackage 中兼容 Objective-C 的 Swift 类型。 #import MixedPackage-Swift.h其中MixedPackage-Swift.h是构建期间由 Swift 编译器生成的互操作 header对应-emit-objc-header产物import MixedPackage与#import MixedPackage-Swift.h两条路径都能拿到 Swift 侧暴露给 Objective-C 的 API。插件支持新增MixedSourceModuleTarget类型为让 SwiftPM 插件能够处理混合语言 targetPackagePlugin模块将新增一个类型MixedSourceModuleTarget。该类型由既有SwiftSourceModuleTarget与ClangSourceModuleTarget两者的属性合并而成两个既有类型定义可参见仓库中的 SE-0541 提案 PackagePlugin Changes 小节/// Represents a target consisting of a source code module compiled using both the Clang and Swift compiler. public struct MixedSourceModuleTarget: SourceModuleTarget { /// Unique identifier for the target. public let id: ID /// The name of the target, as defined in the package manifest. This name /// is unique among the targets of the package in which it is defined. public let name: String /// The kind of module, describing whether it contains unit tests, contains /// the main entry point of an executable, or neither. public let kind: ModuleKind /// The absolute path of the target directory in the local file system. public let directory: Path /// Any other targets on which this target depends, in the same order as /// they are specified in the package manifest. Conditional dependencies /// that do not apply have already been filtered out. public let dependencies: [TargetDependency] /// The name of the module produced by the target (derived from the target /// name, though future SwiftPM versions may allow this to be customized). public let moduleName: String /// The source files that are associated with this target (any files that /// have been excluded in the manifest have already been filtered out). public let sourceFiles: FileList /// Any custom compilation conditions specified for the targets Swift sources. public let swiftCompilationConditions: [String] /// Any preprocessor definitions specified for the targets Clang sources. public let clangPreprocessorDefinitions: [String] /// Any custom header search paths specified for the Clang target. public let headerSearchPaths: [String] /// The directory containing public C headers, if applicable. This will /// only be set for targets that have a directory of a public headers. public let publicHeadersDirectory: Path? /// Any custom linked libraries required by the module, as specified in the /// package manifest. public let linkedLibraries: [String] /// Any custom linked frameworks required by the module, as specified in the /// package manifest. public let linkedFrameworks: [String] }插件的后续演进方向可见 SE-0541其最终将SwiftSourceModuleTarget/ClangSourceModuleTarget标记为 deprecated并把swiftCompilationConditions、clangPreprocessorDefinitions、headerSearchPaths、publicHeadersDirectoryURL等要求上移到SourceModuleTarget协议本身使插件无需强转具体类型即可处理混合源 target。详细设计混合 target 在 SwiftPM 中的建模双阶段类型加载期的MixedTarget与构建期的MixedTargetDescription在本提案之前包加载时每个 target 在程序上被表示为SwiftTarget或ClangTarget之一由 target 内的源码类型决定若发现混合语言源码SwiftPM 会抛出错误并反馈给客户端。构建阶段这两类类型分别映射到SwiftTargetBuildDescription与ClangTargetBuildDescription描述 target 应如何构建。SE-0403 新增两个类型分别覆盖包加载与构建两个阶段MixedTarget—— 表示混合语言 target 的加载期模型MixedTargetDescription—— 表示混合语言 target 的构建期描述。值得强调的实现细节是MixedTarget是对底层SwiftTarget与ClangTarget的包装类型。初始化MixedTarget时会内部地从给定 Swift 源码初始化一个SwiftTarget、从给定 Clang 源码初始化一个ClangTargetMixedTargetDescription同理包装了一个SwiftTargetDescription与一个ClangTargetDescription。这种包装而非改造的路线带来了两个收益更高的代码复用率以及降低因改动既有SwiftTarget/ClangTarget而引入回归的风险。MixedTargetBuildDescription的角色是生成构建所需的辅助产物并向底层的SwiftTargetBuildDescription与ClangTargetBuildDescription传递特定的构建标志。类型关系如下构建顺序Swift 部分先行混合 target 构建时Swift 部分必须先于 Clang 部分构建。原因在于C 语言源码可能需要以文本导入textual import方式解析生成的互操作 header而该 header 是在 Swift 部分构建时与 Swift module 一起产出的。这一依赖关系被显式固化在 llbuild 清单即包.build目录下的debug.yaml中——生成的互操作 header 被列为该 target 各 C 语言源码编译命令的输入。编译 Swift 子 target 的附加标志构建 Swift 子 target 时额外使用以下标志-import-underlying-module该标志会在构建 Swift module 时触发对底层 C 语言源码的部分构建。这是让 Swift 源码能够使用 target Clang 部分中 C 语言类型的关键标志。-I /path/to/modulemap_dir上面的-import-underlying-module会在给定的头文件搜索路径中查找 module map。此处使用的 module map不能模块化生成的互操作 header因为它尚不存在要等 Swift 子 target 构建完成后才会生成。若提供了自定义 module map则使用公共头目录自定义 module map 被强制要求位于该目录同时强制要求该 module map 不暴露互操作 header。若未提供自定义 module mapSwiftPM 会传入 target 的构建目录——那里会合成一个 module map且该 module map 是un-extended不模块化生成的互操作 header的。未提供自定义 module map 时合成两个 module map VFS OverlaySwiftPM 会合成两个 module map——extended版本模块化生成的互操作 header与un-extended版本不模块化。随后创建一个 VFS Overlay 文件在构建时把 extended 版本名为module.modulemap与 un-extended 版本unextended-module.modulemap进行交换从而对两阶段构建分别呈现合适的 module map 视图。-Xcc -I -Xcc $(TARGET_SRC_PATH)把 target 源码根路径加入搜索路径允许以 target 根为基准的路径导入头文件。因为-import-underlying-module会触发 Clang 源码的部分构建该标志用于解析可能的头文件导入。-Xcc -I -Xcc $(TARGET_PUBLIC_HDRS)把 target 公共头目录加入搜索路径允许以公共头目录为基准的路径导入头文件作用同上。编译 Clang 子 target 的附加标志编译 Clang 子 target 时额外使用以下标志-I $(targets path)加入 target 源码根路径允许以 target 根为基准导入头文件。-I /path/to/generated_swift_header_dir/生成的 Swift header 在编译 Clang 源码时可能被需要例如通过文本导入方式引用互操作 header。执行构建llbuild 清单的生成实际构建包时SwiftPM 会创建一份 llbuild manifest 并交给 llbuild 系统执行。混合 target 支持的核心改动在于LLBuildManifestBuilder.swiftproposals/0403-swiftpm-mixed-language-targets.md 引用——它负责把MixedTargetBuildDescription转换为 llbuild 构建节点。由于MixedTargetBuildDescription有意包装并配置了底层的SwiftTargetBuildDescription与ClangTargetBuildDescription为混合 target 创建 llbuild 构建节点本质上就是分别为其SwiftTargetBuildDescription与ClangTargetBuildDescription创建构建节点。面向客户端的构建产物客户端可见的模块映射Module Map客户端面向的 module map 目的是定义混合语言模块的公共 API。它由两部分构成一个主模块声明primary module declaration与一个次级子模块声明secondary submodule declaration。前者暴露公共 C 语言头文件后者暴露生成的互操作 header。创建客户端 module map 时存在两种情况若 target 中存在自定义 module map其内容会被复制并扩展以模块化生成的互操作 header写入构建目录命名为extended-custom-module.modulemap。由于公共头目录与构建目录都作为导入路径传入构建调用此命名是为了避免与-import-underlying-module在导入路径中查找到多个module.modulemap文件。否则module map 内容按照 SE-0038 确立的生成规则生成并额外增加一步生成.Swift子模块。该文件名为module.modulemap位于构建目录。客户端的最终编译使用的是包含模块化互操作 header 的extendedmodule map而构建 target 自身则使用un-extended版本。注意target 的 Clang 部分可能完全不导出公共 API。例如公共 API 表面全部用 Swift 编写、实现用 Objective-C 编写的 target此时主模块声明不会暴露任何头文件。以下是一个公共头目录include中含有 umbrella header 的 target 的 module map 示例// extended-custom-module.modulemap // 该声明要么从自定义 module map 复制而来要么按 SE-0038 的规则生成。 module MixedTarget { umbrella header /Users/crusty/Developer/MixedTarget/Sources/MixedTarget/include/MixedTarget.h export * } // 以下部分由 SwiftPM 依据本提案添加。 module MixedTarget.Swift { header MixedTarget-Swift.h requires objc }MixedTarget.Swift子模块正是限制一节中禁止自定义 module map 包含$(ModuleName).Swift子模块的原因——该子模块名由 SwiftPM 保留用于模块化生成的互操作 header。all-product-headers.yamlVFS Overlay 调整all-product-headers.yaml是一个 VFS Overlay 文件用于对公共头目录做两项调整把互操作 header 以相对路径形式暴露给客户端若存在自定义 module map则将其替换为扩展版模块化互操作 header 的那个。无论哪种情况它都会与 module map 一起作为编译参数传给客户端-fmodule-map-file/Users/crusty/Developer/MixedTarget/Sources/MixedTarget/include/module.modulemap -ivfsoverlay /Users/crusty/Developer/MixedTarget/.build/.../MixedTarget.build/Product/all-product-headers.yaml对 SwiftPM 的配套改动发出互操作 header提案的目标是让混合语言 target 在 SwiftPM 支持的所有平台上可用。实现路上的一个障碍是本提案提出时SwiftPM 在调用构建系统时没有传入发出互操作 header 所需的标志对应SwiftTargetBuildDescription.swift中should-emit-header相关的限制代码见 proposals/0403-swiftpm-mixed-language-targets.md。该限制已过时将作为本提案的一部分移除。对 Swift 编译器的配套改动互操作 header 的 umbrella 导入逻辑当 Swift 编译器通过-emit-objc-header生成互操作 header 时Swift API 中引用的任何无法前向声明forward declare的 Objective-C 符号如 superclass、protocol 等会尝试通过 umbrella header 导入。由于编译器按framework而非 app来评估该 target编译器假设公共头目录中存在一个以模块命名的子目录其中包含 umbrella header#import $(ModuleName)/$(ModuleName).h即编译器假定上述路径可相对于公共头目录解析。为避免强迫包作者围绕该约束组织包结构Swift 编译器的互操作 header 生成逻辑将针对公共头目录不具备 xcframework 目录结构的情况做出修正若存在被 Clang module 模块化的 umbrella header则互操作 header 改为直接引用该 umbrella header否则互操作 header 将导入 Clang module map 中的全部文本包含textual includes。这一配套改动使混合 target 不必刻意复制 framework 风格的头文件布局$(ModuleName)/$(ModuleName).h大幅降低了对包作者目录组织的约束。该问题在 SE-0541 中进一步收尾混合源 package target 的生成 header 将改为通过相对公共头目录的引号包含quoted include引用底层模块头文件确保依赖方总能成功消费它见 proposals/0541-flexible-swift-c-interoperability-for-packages.md。混合语言测试 Target为配合混合语言库 target本提案同样支持混合测试 target。沿用前面的示例包布局MixedPackage ├── ... └── Tests └── MixedPackageTests ├── JediTests.swift ]-- Swift tests ├── SithTests.m ]-- Objective-C tests ├── ObjcTestConstants.h ⎤-- Mixed language test utils ├── ObjcTestConstants.m ⎟ └── TestConstants.swift ⎦其中ObjcTestConstants.h中定义的类型在SithTests.m中可见通过导入该头文件TestConstants.swift中兼容 Objective-C 的类型在JediTests.swift中可见通过导入生成的互操作 header。这一设计赋予包作者设计混合 target 测试套件时充分的灵活性——例如把测试工具常量分别用两种语言编写并在对应语言的测试文件中直接使用。失败场景与面向用户的错误信息混合语言 target 存在若干会直接暴露给终端用户的失败场景使用不包含本提案实现的工具版本构建混合 targettarget at \(path) contains mixed language source files; feature not supported构建既非 library 也非 test的混合 targetTarget with mixed sources at \(path) is a \(type) target; targets with mixed language sources are only supported for library and test targets.构建包含$(MixedTargetName).Swift子模块的自定义 module map 的混合 targetThe targets module map may not contain a Swift submodule for the module \(target name).这些错误在 SwiftPM 的TargetSourcesBuilder等加载阶段被检测并抛给客户端详见提案中引用的 mixed-target-error 对应实现位置。安全影响与既有包兼容性安全本提案对安全性、安全性与隐私性无影响。既有包兼容性本提案不会改变既有包的行为。混合语言包的构建代码路径与既有 Swift 源码包、C 语言源码包的构建路径相互独立、互不干扰。工具版本门控该特性按 tools minor version 更新进行门控因此在使用不支持该特性的旧工具链上构建混合语言 target 时会继续抛出上述feature not supported错误而不会产生静默行为差异。未来方向提案为后续演进预留了明确空间允许包作者向混合 target 的 Swift 实现暴露非公共头文件将混合语言 target 支持扩展到当前不支持的 target 类型如 executable让所有 target 默认都是混合语言 target从而把ClangTarget、SwiftTarget、MixedTarget等语言相关类型合并为单一类型简化实现。初次实现中刻意回避了这一方案以降低引入回归的风险。与后续提案的衔接从 SE-0403 到 SE-0541值得注意的是SE-0403 的初始状态为Returned for Revision返回修订其核心构想——Swift 与 C 系源码共存于单一 target——在后续的 SE-0541: Flexible Swift/C Interoperability for Packages 中得到了系统性落地与扩展混合源 target 不再局限于 library/test而是扩展到 regular、executable、macro 等全部带源码的 target 类型include公共头目录约定、module map 生成规则优先使用自定义module.modulemap、模块名同名 umbrella header、umbrella directory被完整继承同时新增了 bridging header 配置能力并修复了混合源模块生成 header 的消费健全性问题。因此阅读 SE-0403 是理解 SwiftPM 混合语言支持设计动机与底层机制的最佳起点而 SE-0541 则呈现了该机制在生产环境中的最终形态。两者共同构成了 SwiftPM 从单一语言 target走向自由混合源 target的完整演进路径。赞分享文档【免费下载链接】swift-evolutionThis maintains proposals for changes and user-visible enhancements to the Swift Programming Language.项目地址https://gitcode.com/gh_mirrors/sw/swift-evolution点击查看免费下载相关推荐如何用Dagger 2在Kotlin Android应用中实现依赖注入基于PhotoAffix的完整教程如何用Dagger 2在Kotlin Android应用中实现依赖注入基于PhotoAffix的完整教程 PhotoAffix 是一款用 Kotlin 编写的如何在 React 项目中通过 Vite 集成 Monaco Editor 并配置自定义 Worker如何在 React 项目中通过 Vite 集成 Monaco Editor 并配置自定义 Worker 在 React 项目中使用 Vite 打包 Monac文档智能视频编辑架构革新基于Lucy Edit Dev的零掩码生产级方案智能视频编辑架构革新基于Lucy Edit Dev的零掩码生产级方案 在视频内容创作和后期制作领域传统编辑方法往往需要复杂的掩码标注、繁琐的人工操作和专业的人工智能深度学习大模型音视频创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表