@@ -221,7 +221,12 @@ struct FrameworkBuilder {
221221 " ARCHS= \( cleanArch) " ,
222222 " VALID_ARCHS= \( cleanArch) " ,
223223 " ONLY_ACTIVE_ARCH=NO " ,
224- " BUILD_LIBRARIES_FOR_DISTRIBUTION=YES " ,
224+ // BUILD_LIBRARY_FOR_DISTRIBUTION=YES is necessary for Swift libraries.
225+ // See https://forums.developer.apple.com/thread/125646.
226+ // Unlike the comment there, the option here is sufficient to cause .swiftinterface
227+ // files to be generated in the .swiftmodule directory. The .swiftinterface files
228+ // are required for xcodebuild to successfully generate an xcframework.
229+ " BUILD_LIBRARY_FOR_DISTRIBUTION=YES " ,
225230 " SUPPORTS_MACCATALYST= \( isMacCatalystString) " ,
226231 " BUILD_DIR= \( buildDir. path) " ,
227232 " -sdk " , platform. rawValue,
@@ -267,7 +272,7 @@ struct FrameworkBuilder {
267272 var actualFramework : String
268273 do {
269274 let files = try FileManager . default. contentsOfDirectory ( at: frameworkPath,
270- includingPropertiesForKeys: nil ) . compactMap { $0. absoluteString }
275+ includingPropertiesForKeys: nil ) . compactMap { $0. path }
271276 let frameworkDir = files. filter { $0. contains ( " .framework " ) }
272277 actualFramework = URL ( fileURLWithPath: frameworkDir [ 0 ] ) . lastPathComponent
273278 } catch {
@@ -427,21 +432,21 @@ struct FrameworkBuilder {
427432
428433 // Find CocoaPods generated umbrella header.
429434 var umbrellaHeader = " "
430- if framework == " gRPC-Core " {
435+ if framework == " gRPC-Core " || framework == " TensorFlowLiteObjC " {
431436 // TODO: Proper handling of podspec-specified module.modulemap files with customized umbrella
432437 // headers. This is good enough for Firebase since it doesn't need these modules.
433438 umbrellaHeader = " \( framework) -umbrella.h "
434439 } else {
435440 var umbrellaHeaderURL : URL
436441 do {
437442 let files = try fileManager. contentsOfDirectory ( at: headersDir,
438- includingPropertiesForKeys: nil ) . compactMap { $0. absoluteString }
439- let umbrellas = files. filter { $0. contains ( " umbrella.h " ) }
443+ includingPropertiesForKeys: nil ) . compactMap { $0. path }
444+ let umbrellas = files. filter { $0. hasSuffix ( " umbrella.h " ) }
440445 if umbrellas. count != 1 {
441446 fatalError ( " Did not find exactly one umbrella header in \( headersDir) . " )
442447 }
443448 guard let firstUmbrella = umbrellas. first,
444- let foundHeader = URL ( string: firstUmbrella) else { /* error */
449+ let foundHeader = URL ( string: firstUmbrella) else {
445450 fatalError ( " Failed to get umbrella header in \( headersDir) . " )
446451 }
447452 umbrellaHeaderURL = foundHeader
@@ -527,10 +532,6 @@ struct FrameworkBuilder {
527532 thinArchives: [ Architecture : URL ] ,
528533 destination: URL ,
529534 moduleMapContents: String ) {
530- // Build the fat archive using the `lipo` command. We need the full archive path and the list of
531- // thin paths (as Strings, not URLs).
532- let thinPaths = thinArchives. map { $0. value. path }
533-
534535 // Store all fat archives in a temporary directory that includes all architectures included as
535536 // the parent folder.
536537 let fatArchivesDir : URL = {
@@ -552,8 +553,10 @@ struct FrameworkBuilder {
552553 fatalError ( " Could not create directories needed to build \( framework) : \( error) " )
553554 }
554555
556+ // Build the fat archive using the `lipo` command. We need the full archive path.
555557 let fatArchive = fatArchivesDir. appendingPathComponent ( framework)
556- let result = syncExec ( command: " /usr/bin/lipo " , args: [ " -create " , " -output " , fatArchive. path] + thinPaths)
558+ let result = syncExec ( command: " /usr/bin/lipo " , args: [ " -create " , " -output " , fatArchive. path] +
559+ thinArchives. map { $0. value. path } )
557560 switch result {
558561 case let . error( code, output) :
559562 fatalError ( """
@@ -571,19 +574,84 @@ struct FrameworkBuilder {
571574 } catch {
572575 fatalError ( " Could not copy \( framework) to destination: \( error) " )
573576 }
574- // Copy the module map to the destination.
575- let moduleDir = destination. appendingPathComponent ( " Modules " )
576- do {
577- try FileManager . default. createDirectory ( at: moduleDir, withIntermediateDirectories: true )
578- } catch {
579- fatalError ( " Could not create Modules directory for framework: \( framework) . \( error) " )
577+
578+ // CocoaPods does not put dependent frameworks and libraries into the module maps it generates.
579+ // Instead it use build options to specify them. For the zip build, we need the module maps to
580+ // include the dependent frameworks and libraries. Therefore we reconstruct them by parsing
581+ // the CocoaPods config files and add them here.
582+ // Currently we only to the construction for Objective C since Swift Module directories require
583+ // several other files. See https://github.com/firebase/firebase-ios-sdk/pull/5040.
584+ // Therefore, for Swift we do a simple copy of the Modules files from an Xcode build.
585+ // This is sufficient for the testing done so far, but more testing is required to determine
586+ // if dependent libraries and frameworks also may need to be added to the Swift module maps in
587+ // some cases.
588+ let builtSwiftModules = makeSwiftModuleMap ( thinArchives: thinArchives, destination: destination)
589+ if !builtSwiftModules {
590+ // Copy the module map to the destination.
591+ let moduleDir = destination. appendingPathComponent ( " Modules " )
592+ do {
593+ try FileManager . default. createDirectory ( at: moduleDir, withIntermediateDirectories: true )
594+ } catch {
595+ fatalError ( " Could not create Modules directory for framework: \( framework) . \( error) " )
596+ }
597+ let modulemap = moduleDir. appendingPathComponent ( " module.modulemap " )
598+ do {
599+ try moduleMapContents. write ( to: modulemap, atomically: true , encoding: . utf8)
600+ } catch {
601+ fatalError ( " Could not write modulemap to disk for \( framework) : \( error) " )
602+ }
580603 }
581- let modulemap = moduleDir. appendingPathComponent ( " module.modulemap " )
582- do {
583- try moduleMapContents. write ( to: modulemap, atomically: true , encoding: . utf8)
584- } catch {
585- fatalError ( " Could not write modulemap to disk for \( framework) : \( error) " )
604+ }
605+
606+ private func makeSwiftModuleMap( thinArchives: [ Architecture : URL ] , destination: URL ) -> Bool {
607+ let fileManager = FileManager . default
608+ for archive in thinArchives {
609+ let frameworkDir = archive. value. deletingLastPathComponent ( )
610+ // Get the Modules directory. The Catalyst one is a symbolic link.
611+ let moduleDir = frameworkDir. appendingPathComponent ( " Modules " ) . resolvingSymlinksInPath ( )
612+ do {
613+ let files = try fileManager. contentsOfDirectory ( at: moduleDir,
614+ includingPropertiesForKeys: nil ) . compactMap { $0. path }
615+ let swiftModules = files. filter { $0. hasSuffix ( " .swiftmodule " ) }
616+ if swiftModules. isEmpty {
617+ return false
618+ }
619+ guard let first = swiftModules. first,
620+ let swiftModule = URL ( string: first) else {
621+ fatalError ( " Failed to get swiftmodule in \( moduleDir) . " )
622+ }
623+ let destModuleDir = destination. appendingPathComponent ( " Modules " )
624+ if !fileManager. directoryExists ( at: destModuleDir) {
625+ do {
626+ try fileManager. copyItem ( at: moduleDir, to: destModuleDir)
627+ } catch {
628+ fatalError ( " Could not copy Modules from \( moduleDir) to " + " \( destModuleDir) : \( error) " )
629+ }
630+ } else {
631+ // If the Modules directory is already there, only copy in the architecture specific files
632+ // from the *.swiftmodule subdirectory.
633+ do {
634+ let files = try fileManager. contentsOfDirectory ( at: swiftModule,
635+ includingPropertiesForKeys: nil ) . compactMap { $0. path }
636+ let destSwiftModuleDir = destModuleDir. appendingPathComponent ( swiftModule. lastPathComponent)
637+ for file in files {
638+ let fileURL = URL ( fileURLWithPath: file)
639+ do {
640+ try fileManager. copyItem ( at: fileURL, to:
641+ destSwiftModuleDir. appendingPathComponent ( fileURL. lastPathComponent) )
642+ } catch {
643+ fatalError ( " Could not copy Swift module file from \( fileURL) to " + " \( destSwiftModuleDir) : \( error) " )
644+ }
645+ }
646+ } catch {
647+ fatalError ( " Failed to get Modules directory contents - \( moduleDir) : \( error. localizedDescription) " )
648+ }
649+ }
650+ } catch {
651+ fatalError ( " Error while enumerating files \( moduleDir) : \( error. localizedDescription) " )
652+ }
586653 }
654+ return true
587655 }
588656
589657 /// Packages an XCFramework based on an almost complete framework folder (missing the binary but includes everything else needed)
@@ -619,7 +687,7 @@ struct FrameworkBuilder {
619687 for platform in Architecture . TargetPlatform. allCases {
620688 // Get all the slices that belong to the specific platform in order to lipo them together.
621689 let slices = thinArchives. filter { $0. key. platform == platform }
622- if slices. count == 0 {
690+ if slices. isEmpty {
623691 continue
624692 }
625693 let platformDir = platformFrameworksDir. appendingPathComponent ( platform. rawValue)
0 commit comments