Skip to content

Commit 4f00074

Browse files
authored
Restore Carthage (#8084)
1 parent e789676 commit 4f00074

File tree

4 files changed

+80
-258
lines changed

4 files changed

+80
-258
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{
2+
}

ReleaseTooling/Sources/ZipBuilder/CarthageUtils.swift

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -105,19 +105,6 @@ extension CarthageUtils {
105105
let fullPath = packagedDir.appendingPathComponent(product)
106106
guard FileManager.default.isDirectory(at: fullPath) else { continue }
107107

108-
// Abort Carthage generation if there are any xcframeworks.
109-
do {
110-
let files = try FileManager.default.contentsOfDirectory(at: fullPath,
111-
includingPropertiesForKeys: nil)
112-
let xcfFiles = files.filter { $0.pathExtension == "xcframework" }
113-
if xcfFiles.count > 0 {
114-
print("Skipping Carthage generation for \(product) since it includes xcframeworks.")
115-
continue
116-
}
117-
} catch {
118-
fatalError("Failed to get contents of \(fullPath).")
119-
}
120-
121108
// Parse the JSON file, ensure that we're not trying to overwrite a release.
122109
var jsonManifest = parseJSONFile(fromDir: jsonDir, product: product)
123110

@@ -138,7 +125,8 @@ extension CarthageUtils {
138125

139126
// Copy the NOTICES file from FirebaseCore.
140127
let noticesName = "NOTICES"
141-
let coreNotices = fullPath.appendingPathComponents(["FirebaseCore.framework", noticesName])
128+
let coreNotices = fullPath.appendingPathComponents(["FirebaseCore.xcframework",
129+
noticesName])
142130
let noticesPath = packagedDir.appendingPathComponent(noticesName)
143131
do {
144132
try FileManager.default.copyItem(at: noticesPath, to: coreNotices)

ReleaseTooling/Sources/ZipBuilder/FrameworkBuilder.swift

Lines changed: 38 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,15 @@ struct FrameworkBuilder {
2828
/// Flag for building dynamic frameworks instead of static frameworks.
2929
private let dynamicFrameworks: Bool
3030

31-
/// Flag for whether or not Carthage artifacts should be built as well.
32-
private let buildCarthage: Bool
33-
3431
/// The Pods directory for building the framework.
3532
private var podsDir: URL {
3633
return projectDir.appendingPathComponent("Pods", isDirectory: true)
3734
}
3835

3936
/// Default initializer.
40-
init(projectDir: URL, platform: Platform, includeCarthage: Bool,
41-
dynamicFrameworks: Bool) {
37+
init(projectDir: URL, platform: Platform, dynamicFrameworks: Bool) {
4238
self.projectDir = projectDir
4339
targetPlatforms = platform.platformTargets
44-
buildCarthage = includeCarthage && platform == .iOS
4540
self.dynamicFrameworks = dynamicFrameworks
4641
}
4742

@@ -52,11 +47,13 @@ struct FrameworkBuilder {
5247
///
5348
/// - Parameter framework: The name of the framework to be built.
5449
/// - Parameter logsOutputDir: The path to the directory to place build logs.
50+
/// - Parameter setCarthage: Set Carthage diagnostics flag in build.
5551
/// - Parameter moduleMapContents: Module map contents for all frameworks in this pod.
56-
/// - Returns: A path to the newly compiled frameworks, the Carthage frameworks, and Resources.
52+
/// - Returns: A path to the newly compiled frameworks, and Resources.
5753
func compileFrameworkAndResources(withName framework: String,
5854
logsOutputDir: URL? = nil,
59-
podInfo: CocoaPodUtils.PodInfo) -> ([URL], URL?, URL?) {
55+
setCarthage: Bool,
56+
podInfo: CocoaPodUtils.PodInfo) -> ([URL], URL?) {
6057
let fileManager = FileManager.default
6158
let outputDir = fileManager.temporaryDirectory(withName: "frameworks_being_built")
6259
let logsDir = logsOutputDir ?? fileManager.temporaryDirectory(withName: "build_logs")
@@ -78,10 +75,15 @@ struct FrameworkBuilder {
7875

7976
if dynamicFrameworks {
8077
return (buildDynamicFrameworks(withName: framework, logsDir: logsDir, outputDir: outputDir),
81-
nil, nil)
78+
nil)
8279
} else {
83-
return buildStaticFrameworks(withName: framework, logsDir: logsDir, outputDir: outputDir,
84-
podInfo: podInfo)
80+
return buildStaticFrameworks(
81+
withName: framework,
82+
logsDir: logsDir,
83+
outputDir: outputDir,
84+
setCarthage: setCarthage,
85+
podInfo: podInfo
86+
)
8587
}
8688
}
8789

@@ -149,7 +151,7 @@ struct FrameworkBuilder {
149151
/// - Returns: A dictionary of URLs to the built thin libraries keyed by platform.
150152
private func buildFrameworksForAllPlatforms(withName framework: String,
151153
logsDir: URL,
152-
setCarthage: Bool = false) -> [TargetPlatform: URL] {
154+
setCarthage: Bool) -> [TargetPlatform: URL] {
153155
// Build every architecture and save the locations in an array to be assembled.
154156
var slicedFrameworks = [TargetPlatform: URL]()
155157
for targetPlatform in targetPlatforms {
@@ -327,13 +329,15 @@ struct FrameworkBuilder {
327329
/// - Parameter framework: The name of the framework to be built.
328330
/// - Parameter logsDir: The path to the directory to place build logs.
329331
/// - Parameter moduleMapContents: Module map contents for all frameworks in this pod.
330-
/// - Returns: A path to the newly compiled framework, the Carthage version, and the Resource URL.
332+
/// - Returns: A path to the newly compiled framework, and the Resource URL.
331333
private func buildStaticFrameworks(withName framework: String,
332334
logsDir: URL,
333335
outputDir: URL,
334-
podInfo: CocoaPodUtils.PodInfo) -> ([URL], URL?, URL) {
336+
setCarthage: Bool,
337+
podInfo: CocoaPodUtils.PodInfo) -> ([URL], URL) {
335338
// Build every architecture and save the locations in an array to be assembled.
336-
let slicedFrameworks = buildFrameworksForAllPlatforms(withName: framework, logsDir: logsDir)
339+
let slicedFrameworks = buildFrameworksForAllPlatforms(withName: framework, logsDir: logsDir,
340+
setCarthage: setCarthage)
337341

338342
// Create the framework directory in the filesystem for the thin archives to go.
339343
let fileManager = FileManager.default
@@ -354,6 +358,17 @@ struct FrameworkBuilder {
354358
// Get the framework Headers directory. On macOS, it's a symbolic link.
355359
let headersDir = archivePath.appendingPathComponent("Headers").resolvingSymlinksInPath()
356360

361+
// The macOS Headers directory can have a Headers file in it symbolically linked to nowhere.
362+
// Delete it here to avoid putting it in the zip or crashing the Carthage hash generation.
363+
// For example,in the 8.0.0 zip distribution see
364+
// Firebase/FirebaseAnalytics/PromisesObjC.xcframework/macos-arm64_x86_64/PromisesObjc
365+
// .framework/Headers/Headers
366+
do {
367+
try fileManager.removeItem(at: headersDir.appendingPathComponent("Headers"))
368+
} catch {
369+
// Ignore
370+
}
371+
357372
// Find CocoaPods generated umbrella header.
358373
var umbrellaHeader = ""
359374
if framework == "gRPC-Core" || framework == "TensorFlowLiteObjC" {
@@ -427,27 +442,11 @@ struct FrameworkBuilder {
427442
}
428443
let moduleMapContents = moduleMapContentsTemplate.get(umbrellaHeader: umbrellaHeader)
429444
let frameworks = groupFrameworks(withName: framework,
445+
isCarthage: setCarthage,
430446
fromFolder: frameworkDir,
431447
slicedFrameworks: slicedFrameworks,
432448
moduleMapContents: moduleMapContents)
433449

434-
var carthageFramework: URL?
435-
if buildCarthage {
436-
var carthageThinArchives: [TargetPlatform: URL]
437-
if framework == "FirebaseCoreDiagnostics" {
438-
// FirebaseCoreDiagnostics needs to be built with a different ifdef for the Carthage distro.
439-
carthageThinArchives = buildFrameworksForAllPlatforms(withName: framework,
440-
logsDir: logsDir,
441-
setCarthage: true)
442-
} else {
443-
carthageThinArchives = slicedFrameworks
444-
}
445-
carthageFramework = packageCarthageFramework(withName: framework,
446-
fromFolder: frameworkDir,
447-
slicedFrameworks: carthageThinArchives,
448-
resourceContents: resourceContents,
449-
moduleMapContents: moduleMapContents)
450-
}
451450
// Remove the temporary thin archives.
452451
for slicedFramework in slicedFrameworks.values {
453452
do {
@@ -463,7 +462,7 @@ struct FrameworkBuilder {
463462
""")
464463
}
465464
}
466-
return (frameworks, carthageFramework, resourceContents)
465+
return (frameworks, resourceContents)
467466
}
468467

469468
/// Parses CocoaPods config files or uses the passed in `moduleMapContents` to write the
@@ -478,7 +477,7 @@ struct FrameworkBuilder {
478477
// Instead it use build options to specify them. For the zip build, we need the module maps to
479478
// include the dependent frameworks and libraries. Therefore we reconstruct them by parsing
480479
// the CocoaPods config files and add them here.
481-
// Currently we only to the construction for Objective C since Swift Module directories require
480+
// Currently we only do the construction for Objective C since Swift Module directories require
482481
// several other files. See https://github.com/firebase/firebase-ios-sdk/pull/5040.
483482
// Therefore, for Swift we do a simple copy of the Modules files from an Xcode build.
484483
// This is sufficient for the testing done so far, but more testing is required to determine
@@ -594,19 +593,22 @@ struct FrameworkBuilder {
594593

595594
/// Groups slices for each platform into a minimal set of frameworks.
596595
/// - Parameter withName: The framework name.
596+
/// - Parameter isCarthage: Name the temp directory differently for Carthage.
597597
/// - Parameter fromFolder: The almost complete framework folder. Includes Headers, Info.plist,
598598
/// and Resources.
599599
/// - Parameter slicedFrameworks: All the frameworks sliced by platform.
600600
/// - Parameter moduleMapContents: Module map contents for all frameworks in this pod.
601601
private func groupFrameworks(withName framework: String,
602+
isCarthage: Bool,
602603
fromFolder: URL,
603604
slicedFrameworks: [TargetPlatform: URL],
604605
moduleMapContents: String) -> ([URL]) {
605606
let fileManager = FileManager.default
606607

607608
// Create a `.framework` for each of the thinArchives using the `fromFolder` as the base.
608-
let platformFrameworksDir =
609-
fileManager.temporaryDirectory(withName: "platform_frameworks")
609+
let platformFrameworksDir = fileManager.temporaryDirectory(
610+
withName: isCarthage ? "carthage_frameworks" : "platform_frameworks"
611+
)
610612
if !fileManager.directoryExists(at: platformFrameworksDir) {
611613
do {
612614
try fileManager.createDirectory(at: platformFrameworksDir,
@@ -704,172 +706,4 @@ struct FrameworkBuilder {
704706
}
705707
return xcframework
706708
}
707-
708-
/// Packages a Carthage framework. Carthage does not yet support xcframeworks, so we exclude the
709-
/// Catalyst slice.
710-
/// - Parameter withName: The framework name.
711-
/// - Parameter fromFolder: The almost complete framework folder. Includes Headers, Info.plist,
712-
/// and Resources.
713-
/// - Parameter slicedFrameworks: All the frameworks sliced by platform.
714-
/// - Parameter resourceContents: Location of the resources for this Carthage framework.
715-
/// - Parameter moduleMapContents: Module map contents for all frameworks in this pod.
716-
private func packageCarthageFramework(withName framework: String,
717-
fromFolder: URL,
718-
slicedFrameworks: [TargetPlatform: URL],
719-
resourceContents: URL,
720-
moduleMapContents: String) -> URL? {
721-
let fileManager = FileManager.default
722-
723-
// Create a `.framework` for each of the thinArchives using the `fromFolder` as the base.
724-
let platformFrameworksDir = fileManager.temporaryDirectory(withName: "carthage_frameworks")
725-
if !fileManager.directoryExists(at: platformFrameworksDir) {
726-
do {
727-
try fileManager.createDirectory(at: platformFrameworksDir,
728-
withIntermediateDirectories: true)
729-
} catch {
730-
fatalError("Could not create a temp directory to store all thin frameworks: \(error)")
731-
}
732-
}
733-
734-
// The frameworks include the arm64 simulator slice which will conflict with the arm64 device
735-
// slice. Until Carthage can use XCFrameworks natively, extract the supported thin slices.
736-
let thinSlices: [Architecture: URL] =
737-
slicedBinariesForCarthage(fromFrameworks: slicedFrameworks,
738-
workingDir: platformFrameworksDir)
739-
740-
// Copy the framework in the appropriate directory structure.
741-
let frameworkDir = platformFrameworksDir.appendingPathComponent(fromFolder.lastPathComponent)
742-
do {
743-
try fileManager.copyItem(at: fromFolder, to: frameworkDir)
744-
} catch {
745-
fatalError("Could not create .framework needed to build \(framework) for Carthage: \(error)")
746-
}
747-
748-
// Build the fat archive using the `lipo` command to make one fat binary that Carthage can use
749-
// in the framework. We need the full archive path.
750-
let fatArchive = frameworkDir.appendingPathComponent(framework)
751-
let result = FrameworkBuilder.syncExec(
752-
command: "/usr/bin/lipo",
753-
args: ["-create", "-output", fatArchive.path] + thinSlices.map { $0.value.path }
754-
)
755-
switch result {
756-
case let .error(code, output):
757-
fatalError("""
758-
lipo command exited with \(code) when trying to build \(framework). Output:
759-
\(output)
760-
""")
761-
case .success:
762-
print("lipo command for \(framework) succeeded.")
763-
}
764-
765-
// Package the modulemaps. The build architecture does not support constructing Swift module
766-
// maps for the Carthage distribution, so skip this pod if there is any Swift.
767-
let foundSwift = packageModuleMaps(inFrameworks: slicedFrameworks.map { $0.value },
768-
moduleMapContents: moduleMapContents,
769-
destination: frameworkDir,
770-
buildingCarthage: true)
771-
if foundSwift {
772-
do {
773-
try fileManager.removeItem(at: frameworkDir)
774-
} catch {
775-
fatalError("Could not remove \(frameworkDir) \(error)")
776-
}
777-
return nil
778-
}
779-
780-
// Carthage Resources are packaged in the framework.
781-
// Copy them instead of moving them, since they'll still need to be copied into the xcframework.
782-
let resourceDir = frameworkDir.appendingPathComponent("Resources")
783-
do {
784-
try ResourcesManager.moveAllBundles(inDirectory: resourceContents,
785-
to: resourceDir,
786-
keepOriginal: true)
787-
} catch {
788-
fatalError("Could not move bundles into Resources directory while building \(framework): " +
789-
"\(error)")
790-
}
791-
return frameworkDir
792-
}
793-
794-
/// Takes existing fat frameworks (sliced per platform) and returns thin slices, excluding arm64
795-
/// simulator slices since Carthage can only create a regular framework.
796-
private func slicedBinariesForCarthage(fromFrameworks frameworks: [TargetPlatform: URL],
797-
workingDir: URL) -> [Architecture: URL] {
798-
// Exclude Catalyst.
799-
let platformsToInclude: [TargetPlatform] = frameworks.keys.filter { $0 != .catalyst }
800-
let builtSlices: [TargetPlatform: URL] = frameworks
801-
.filter { platformsToInclude.contains($0.key) }
802-
.mapValues { frameworkURL in
803-
// Get the path to the sliced binary instead of the framework.
804-
let frameworkName = frameworkURL.lastPathComponent
805-
let binaryName = frameworkName.replacingOccurrences(of: ".framework", with: "")
806-
return frameworkURL.appendingPathComponent(binaryName)
807-
}
808-
809-
let fileManager = FileManager.default
810-
let individualSlices = workingDir.appendingPathComponent("slices")
811-
if !fileManager.directoryExists(at: individualSlices) {
812-
do {
813-
try fileManager.createDirectory(at: individualSlices,
814-
withIntermediateDirectories: true)
815-
} catch {
816-
fatalError("Could not create a temp directory to store sliced binaries: \(error)")
817-
}
818-
}
819-
820-
// Loop through and extract the necessary architectures.
821-
var slices: [Architecture: URL] = [:]
822-
for (platform, binary) in builtSlices {
823-
var archs = platform.archs
824-
var forceLipoOnOneArch = false
825-
if platform == .iOSSimulator {
826-
// Exclude the arm64 slice for simulator since Carthage can't package as an XCFramework.
827-
archs.removeAll(where: { $0 == .arm64 })
828-
if binary.lastPathComponent == "FirebaseAppCheck" {
829-
// Exclude i386 slice for iOS 11+ frameworks.
830-
archs.removeAll(where: { $0 == .i386 })
831-
forceLipoOnOneArch = true // Still need to run lipo because .x86_64 and arm64 were built.
832-
}
833-
}
834-
if platform == .iOSDevice {
835-
if binary.lastPathComponent == "FirebaseAppCheck" {
836-
// Exclude armv7 slice for iOS 11+ frameworks.
837-
archs.removeAll(where: { $0 == .armv7 })
838-
}
839-
}
840-
841-
// lipo doesn't work if only one architecture.
842-
if archs.count == 1, !forceLipoOnOneArch {
843-
slices[archs.first!] = binary
844-
continue
845-
}
846-
847-
// Loop through the architectures and strip out each by using `lipo`.
848-
for arch in archs {
849-
// Create the path where the thin slice will reside, ensure it's non-existent.
850-
let destination = individualSlices.appendingPathComponent("\(arch.rawValue).a")
851-
fileManager.removeIfExists(at: destination)
852-
853-
// Use lipo to extract the architecture we're looking for.
854-
let result = FrameworkBuilder.syncExec(command: "/usr/bin/lipo",
855-
args: [binary.path,
856-
"-thin", arch.rawValue,
857-
"-output", destination.path])
858-
switch result {
859-
case let .error(code, output):
860-
fatalError("""
861-
lipo command exited with \(code) when trying to extract the \(arch.rawValue) slice \
862-
from \(binary.path). Output:
863-
\(output)
864-
""")
865-
case .success:
866-
print("lipo successfully extracted the \(arch.rawValue) slice from \(binary.path)")
867-
}
868-
869-
slices[arch] = destination
870-
}
871-
}
872-
873-
return slices
874-
}
875709
}

0 commit comments

Comments
 (0)