SPM构建公共头使用尖括号#import的Objective-C包失败如何解决
- 维护多年的开源Objective-C库
ORSSerialPort,原分发方式为可嵌入Xcode项目的framework,同时支持Carthage、CocoaPods集成。 - 新增SPM支持时编写了
Package.swift,最初无需修改源码或目录结构,不影响原有framework分发流程。 - Xcode 12默认开启framework公共头使用双引号
#import的警告,因此将公共头内的导入语句从双引号写法改为符合framework规范的尖括号<>写法后,SPM构建失败,报错信息如下:
In file included from /Users/andrewmadsen/Developer/ORSSerialPort/Source/ORSSerialPort.m:28: /Users/andrewmadsen/Developer/ORSSerialPort/Source/ORSSerialRequest.h:28:9: fatal error: 'ORSSerial/ORSSerialPacketDescriptor.h' file not found #import <ORSSerial/ORSSerialPacketDescriptor.h> ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 error generated. [0/1] Compiling ORSSerial ORSSerialPort.m
- 条件编译方案:通过
#ifdef对导入语句做分支判断,在Package.swift的target配置cSettings中传入对应宏定义区分SPM和framework构建场景。该方案在头文件数量较多时操作繁琐,可维护性差。 - 头文件搜索路径配置:所有
.h/.m文件均存放于同一Source目录下,在cSettings中将当前目录加入头文件搜索路径,配置如下,但仍报相同错误:
.target( name: "ORSSerial", path: "Source", exclude: ["ORSSerialBuffer.h"], publicHeadersPath: ".", cSettings: [ .headerSearchPath(".") ] )
SPM默认给Objective-C/Clang target生成的是普通静态模块的modulemap,而非framework类型模块。普通模块下编译器对#import <ModuleName/Header.h>的尖括号导入会做严格路径匹配,必须在头文件搜索路径下存在ORSSerial/同名子目录,才能找到对应头文件;而Xcode直接构建.framework时使用的是framework类型模块,编译器会遵循Apple framework的搜索规则,直接在framework的Headers目录下查找同码头文件,不需要同名子目录,这就是两种构建行为不一致的核心原因。
无需修改现有源码、无需调整目录结构、无需添加冗余条件编译,只需要自定义framework类型的modulemap给SPM使用,即可让SPM的构建行为和原生framework构建完全一致:
- 在存放源码的
Source目录下新建module.modulemap文件,写入以下内容:
framework module ORSSerial { umbrella "." export * module * { export * } }
如果库有统一的伞头文件(比如ORSSerialPort.h),也可以把umbrella "."替换为umbrella header "ORSSerialPort.h",匹配原有framework的modulemap配置即可。
2. 保持原有Package.swift的target配置基本不变即可,SPM检测到公共头路径下存在自定义module.modulemap时,会自动使用该文件而非自动生成普通模块的modulemap,此时编译器会按照framework规则解析尖括号导入的同码头文件,直接在Source目录下查找对应头文件,构建即可正常通过。
该方案不会影响原有Carthage、CocoaPods以及直接嵌入Xcode构建framework的流程,自定义的modulemap和原有framework的模块配置完全兼容,后续维护成本为0。
内容的提问来源于stack exchange,提问作者Andrew Madsen

