Skip to content

Commit 973f8df

Browse files
authored
[Release Tooling] Update XCFramework structure (#12595)
1 parent 9b661b0 commit 973f8df

File tree

4 files changed

+94
-134
lines changed

4 files changed

+94
-134
lines changed

FirebaseCore/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Unreleased
2+
- Fix validation issue for macOS and macCatalyst XCFrameworks related to
3+
framework directory structure. (#12557)
4+
15
# Firebase 10.23.0
26
- Fix validation issue for macOS and macCatalyst XCFrameworks. (#12505)
37

ReleaseTooling/Sources/ZipBuilder/FrameworkBuilder.swift

+68-130
Original file line numberDiff line numberDiff line change
@@ -456,17 +456,13 @@ struct FrameworkBuilder {
456456
buildingCarthage: buildingCarthage) {
457457
return buildingCarthage
458458
}
459-
// Copy the module map to the destination.
460-
let moduleDir = destination.appendingPathComponent("Modules")
461-
do {
462-
try FileManager.default.createDirectory(at: moduleDir, withIntermediateDirectories: true)
463-
} catch {
464-
let frameworkName: String = frameworks.first?.lastPathComponent ?? "<UNKNOWN"
465-
fatalError("Could not create Modules directory for framework: \(frameworkName). \(error)")
466-
}
467-
let modulemap = moduleDir.appendingPathComponent("module.modulemap")
459+
460+
let modulemapURL = destination
461+
.appendingPathComponent("Modules")
462+
.appendingPathComponent("module.modulemap")
463+
.resolvingSymlinksInPath()
468464
do {
469-
try moduleMapContents.write(to: modulemap, atomically: true, encoding: .utf8)
465+
try moduleMapContents.write(to: modulemapURL, atomically: true, encoding: .utf8)
470466
} catch {
471467
let frameworkName: String = frameworks.first?.lastPathComponent ?? "<UNKNOWN"
472468
fatalError("Could not write modulemap to disk for \(frameworkName): \(error)")
@@ -496,64 +492,7 @@ struct FrameworkBuilder {
496492
} else if buildingCarthage {
497493
return true
498494
}
499-
guard let first = swiftModules.first,
500-
let swiftModule = URL(string: first) else {
501-
fatalError("Failed to get swiftmodule in \(moduleDir).")
502-
}
503-
let destModuleDir = destination.appendingPathComponent("Modules")
504-
if !fileManager.directoryExists(at: destModuleDir) {
505-
do {
506-
try fileManager.copyItem(at: moduleDir, to: destModuleDir)
507-
} catch {
508-
fatalError("Could not copy Modules from \(moduleDir) to " +
509-
"\(destModuleDir): \(error)")
510-
}
511-
} else {
512-
// If the Modules directory is already there, only copy in the architecture specific files
513-
// from the *.swiftmodule subdirectory.
514-
do {
515-
let files = try fileManager.contentsOfDirectory(at: swiftModule,
516-
includingPropertiesForKeys: nil)
517-
.compactMap { $0.path }
518-
let destSwiftModuleDir = destModuleDir
519-
.appendingPathComponent(swiftModule.lastPathComponent)
520-
for file in files {
521-
let fileURL = URL(fileURLWithPath: file)
522-
let projectDir = swiftModule.appendingPathComponent("Project")
523-
if fileURL.lastPathComponent == "Project",
524-
fileManager.directoryExists(at: projectDir) {
525-
// The Project directory (introduced with Xcode 11.4) already exists, only copy in
526-
// new contents.
527-
let projectFiles = try fileManager.contentsOfDirectory(at: projectDir,
528-
includingPropertiesForKeys: nil)
529-
.compactMap { $0.path }
530-
let destProjectDir = destSwiftModuleDir.appendingPathComponent("Project")
531-
for projectFile in projectFiles {
532-
let projectFileURL = URL(fileURLWithPath: projectFile)
533-
do {
534-
try fileManager.copyItem(at: projectFileURL, to:
535-
destProjectDir.appendingPathComponent(projectFileURL.lastPathComponent))
536-
} catch {
537-
fatalError("Could not copy Project file from \(projectFileURL) to " +
538-
"\(destProjectDir): \(error)")
539-
}
540-
}
541-
} else {
542-
do {
543-
try fileManager.copyItem(at: fileURL, to:
544-
destSwiftModuleDir
545-
.appendingPathComponent(fileURL.lastPathComponent))
546-
} catch {
547-
fatalError("Could not copy Swift module file from \(fileURL) to " +
548-
"\(destSwiftModuleDir): \(error)")
549-
}
550-
}
551-
}
552-
} catch {
553-
fatalError("Failed to get Modules directory contents - \(moduleDir):" +
554-
"\(error.localizedDescription)")
555-
}
556-
}
495+
557496
do {
558497
// If this point is reached, the framework contains a Swift module,
559498
// so it's built from either Swift sources or Swift & C Family
@@ -564,7 +503,7 @@ struct FrameworkBuilder {
564503
// that the framework was built from mixed language sources because
565504
// those additional headers are public headers for the C Family
566505
// Language sources.
567-
let headersDir = destination.appendingPathComponent("Headers")
506+
let headersDir = destination.appendingPathComponent("Headers").resolvingSymlinksInPath()
568507
let headers = try fileManager.contentsOfDirectory(
569508
at: headersDir,
570509
includingPropertiesForKeys: nil
@@ -585,6 +524,7 @@ struct FrameworkBuilder {
585524
}
586525
"""
587526
let modulemapURL = destination.appendingPathComponents(["Modules", "module.modulemap"])
527+
.resolvingSymlinksInPath()
588528
try newModuleMapContents.write(to: modulemapURL, atomically: true, encoding: .utf8)
589529
}
590530
} catch {
@@ -626,78 +566,52 @@ struct FrameworkBuilder {
626566
}
627567
}
628568

629-
// Group the thin frameworks into three groups: device, simulator, and Catalyst (all represented
630-
// by the `TargetPlatform` enum. The slices need to be packaged that way with lipo before
631-
// creating a .framework that works for similar grouped architectures. If built separately,
632-
// `-create-xcframework` will return an error and fail:
633-
// `Both ios-arm64 and ios-armv7 represent two equivalent library definitions`
634-
var frameworksBuilt: [URL] = []
635-
for (platform, frameworkPath) in slicedFrameworks {
569+
return slicedFrameworks.map { platform, frameworkPath in
636570
// Create the following structure in the platform frameworks directory:
637571
// - platform_frameworks
638572
// └── $(PLATFORM)
639573
// └── $(FRAMEWORK).framework
640574
let platformFrameworkDir = platformFrameworksDir
641575
.appendingPathComponent(platform.buildName)
642-
.appendingPathComponent(fromFolder.lastPathComponent)
576+
.appendingPathComponent(framework + ".framework")
577+
643578
do {
644-
try fileManager.createDirectory(at: platformFrameworkDir, withIntermediateDirectories: true)
579+
// Create `platform_frameworks/$(PLATFORM)` subdirectory.
580+
try fileManager.createDirectory(
581+
at: platformFrameworkDir.deletingLastPathComponent(),
582+
withIntermediateDirectories: true
583+
)
584+
585+
// Copy the built framework to the `platform_frameworks/$(PLATFORM)/$(FRAMEWORK).framework`.
586+
try fileManager.copyItem(at: frameworkPath, to: platformFrameworkDir)
645587
} catch {
646-
fatalError("Could not create directory for architecture slices on \(platform) for " +
588+
fatalError("Could not copy directory for architecture slices on \(platform) for " +
647589
"\(framework): \(error)")
648590
}
649591

650-
processPrivacyManifests(fileManager, frameworkPath, platformFrameworkDir)
651-
652-
// Headers from slice
653-
do {
654-
let headersSrc: URL = frameworkPath.appendingPathComponent("Headers")
655-
.resolvingSymlinksInPath()
656-
// The macOS slice's `Headers` directory may have a `Headers` file in
657-
// it that symbolically links to nowhere. For example, in the 8.0.0
658-
// zip distribution, see the `Headers` directory in the macOS slice
659-
// of the `PromisesObjC.xcframework`. Delete it here to avoid putting
660-
// it in the zip or crashing the Carthage hash generation. Because
661-
// this will throw an error for cases where the file does not exist,
662-
// the error is ignored.
663-
try? fileManager.removeItem(at: headersSrc.appendingPathComponent("Headers"))
664-
665-
try fileManager.copyItem(
666-
at: headersSrc,
667-
to: platformFrameworkDir.appendingPathComponent("Headers")
592+
// CocoaPods creates a `_CodeSignature` directory. Delete it.
593+
// Note that the build only produces a `_CodeSignature` directory for
594+
// macOS and macCatalyst, but we try to delete it for other platforms
595+
// just in case it were to appear.
596+
let codeSignatureDir = platformFrameworkDir
597+
.appendingPathComponent(
598+
platform == .catalyst || platform == .macOS ? "Versions/A/" : ""
668599
)
669-
} catch {
670-
fatalError("Could not create framework directory needed to build \(framework): \(error)")
671-
}
600+
.appendingPathComponent("_CodeSignature")
601+
try? fileManager.removeItem(at: codeSignatureDir)
672602

673-
// Copy the binary and Info.plist to the right location.
674-
let binaryName = frameworkPath.lastPathComponent.replacingOccurrences(of: ".framework",
675-
with: "")
676-
let fatBinary = frameworkPath.appendingPathComponent(binaryName).resolvingSymlinksInPath()
677-
let plistPathComponents = {
678-
if platform == .catalyst || platform == .macOS {
679-
// Frameworks for macOS and macCatalyst have a different directory
680-
// structure so the framework-level `Info.plist` is found in a
681-
// different spot.
682-
return ["Versions", "A", "Resources", "Info.plist"]
683-
} else {
684-
return ["Info.plist"]
685-
}
686-
}()
687-
let infoPlist = frameworkPath.appendingPathComponents(plistPathComponents)
688-
.resolvingSymlinksInPath()
689-
let infoPlistDestination = platformFrameworkDir.appendingPathComponent("Info.plist")
690-
let fatBinaryDestination = platformFrameworkDir.appendingPathComponent(framework)
691-
do {
692-
try fileManager.copyItem(at: fatBinary, to: fatBinaryDestination)
693-
} catch {
694-
fatalError("Could not copy fat binary to framework directory for \(framework): \(error)")
695-
}
603+
// The minimum OS version is set to 100.0 to work around b/327020913.
604+
// TODO(ncooke3): Revert this logic once b/327020913 is fixed.
605+
// TODO(ncooke3): Does this need to happen on macOS?
696606
do {
697-
// The minimum OS version is set to 100.0 to work around b/327020913.
698-
// TODO(ncooke3): Revert this logic once b/327020913 is fixed.
607+
let frameworkInfoPlistURL = platformFrameworkDir
608+
.appendingPathComponent(
609+
platform == .catalyst || platform == .macOS ? "Resources" : ""
610+
)
611+
.resolvingSymlinksInPath()
612+
.appendingPathComponent("Info.plist")
699613
var plistDictionary = try PropertyListSerialization.propertyList(
700-
from: Data(contentsOf: infoPlist), format: nil
614+
from: Data(contentsOf: frameworkInfoPlistURL), format: nil
701615
) as! [AnyHashable: Any]
702616
plistDictionary["MinimumOSVersion"] = "100.0"
703617

@@ -707,22 +621,46 @@ struct FrameworkBuilder {
707621
options: 0
708622
)
709623

710-
try updatedPlistData.write(to: infoPlistDestination)
624+
try updatedPlistData.write(to: frameworkInfoPlistURL)
711625
} catch {
712626
fatalError(
713-
"Could not copy framework-level plist to framework directory for \(framework): \(error)"
627+
"Could not modify framework-level plist for b/327020913 in framework directory \(framework): \(error)"
714628
)
715629
}
716630

631+
// The macOS slice's `PrivateHeaders` directory may have a
632+
// `PrivateHeaders` file in it that symbolically links to nowhere. Delete
633+
// it here to avoid putting it in the zip or crashing the Carthage hash
634+
// generation. Because this will throw an error for cases where the file
635+
// does not exist, the error is ignored.
636+
let privateHeadersDir = platformFrameworkDir.appendingPathComponent("PrivateHeaders")
637+
if fileManager.directoryExists(at: privateHeadersDir.resolvingSymlinksInPath()) {
638+
try? fileManager
639+
.removeItem(at: privateHeadersDir.resolvingSymlinksInPath()
640+
.appendingPathComponent("PrivateHeaders"))
641+
} else {
642+
try? fileManager.removeItem(at: privateHeadersDir)
643+
}
644+
let headersDir = platformFrameworkDir.appendingPathComponent("Headers")
645+
.resolvingSymlinksInPath()
646+
try? fileManager.removeItem(at: headersDir.appendingPathComponent("Headers"))
647+
648+
// Move privacy manifest containing resource bundles into the framework.
649+
let resourceDir = platformFrameworkDir
650+
.appendingPathComponent(
651+
platform == .catalyst || platform == .macOS ? "Resources" : ""
652+
)
653+
.resolvingSymlinksInPath()
654+
processPrivacyManifests(fileManager, frameworkPath, resourceDir)
655+
717656
// Use the appropriate moduleMaps
718657
packageModuleMaps(inFrameworks: [frameworkPath],
719658
frameworkName: framework,
720659
moduleMapContents: moduleMapContents,
721660
destination: platformFrameworkDir)
722661

723-
frameworksBuilt.append(platformFrameworkDir)
662+
return platformFrameworkDir
724663
}
725-
return frameworksBuilt
726664
}
727665

728666
/// Process privacy manifests.

ReleaseTooling/Sources/ZipBuilder/ZipBuilder.swift

+20-2
Original file line numberDiff line numberDiff line change
@@ -502,14 +502,32 @@ struct ZipBuilder {
502502
guard fileManager.isDirectory(at: frameworkPath),
503503
frameworkPath.lastPathComponent.hasSuffix("framework") else { continue }
504504
let resourcesDir = frameworkPath.appendingPathComponent("Resources")
505-
try fileManager.copyItem(at: xcResourceDir, to: resourcesDir)
505+
.resolvingSymlinksInPath()
506+
// On macOS platforms, this directory will already be there, so
507+
// ignore error that it already exists.
508+
try? fileManager.createDirectory(
509+
at: resourcesDir,
510+
withIntermediateDirectories: true
511+
)
512+
513+
let xcResourceDirContents = try fileManager.contentsOfDirectory(
514+
at: xcResourceDir,
515+
includingPropertiesForKeys: nil
516+
)
517+
518+
for file in xcResourceDirContents {
519+
try fileManager.copyItem(
520+
at: file,
521+
to: resourcesDir.appendingPathComponent(file.lastPathComponent)
522+
)
523+
}
506524
}
507525
}
508526
try fileManager.removeItem(at: xcResourceDir)
509527
}
510528
}
511529
} catch {
512-
fatalError("Could not setup Resources for \(pod) for \(packageKind) \(error)")
530+
fatalError("Could not setup Resources for \(pod.key) for \(packageKind) \(error)")
513531
}
514532

515533
// Special case for Crashlytics:

scripts/setup_quickstart_framework.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ if [ ! -f "Firebase/module.modulemap" ]; then
4343
fi
4444
for file in "$@"
4545
do
46-
if [ ! -f "Firebase/$(basename ${file})" ]; then
47-
cp -R ${file} Firebase/
46+
if [ ! -d "Firebase/$(basename ${file})" ]; then
47+
rsync -a ${file} Firebase/
4848
fi
4949
done
5050

0 commit comments

Comments
 (0)