mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
106 lines
5.0 KiB
Swift
106 lines
5.0 KiB
Swift
/*
|
|
* Copyright 2026 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import Foundation
|
|
|
|
/// Applies `layout` to every slice of the `.xcframework` at `xcframework`.
|
|
/// See docs/how-it-works.md#post-archive-layout.
|
|
func applyLayout(_ layout: Layout, to xcframework: Path, name: String) throws {
|
|
let root = URL(fileURLWithPath: xcframework)
|
|
for slice in try FileManager.default.contentsOfDirectory(atPath: xcframework).sorted() {
|
|
let framework = root.appendingPathComponent("\(slice)/\(name).framework")
|
|
if FileManager.default.fileExists(atPath: framework.path) {
|
|
try applyLayout(layout, toSlice: framework, name: name)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// One slice: the privacy manifest and the other resources move into their bundles, and the
|
|
/// `Info.plist` values are set. macOS-style (versioned) frameworks keep their resources in
|
|
/// `Versions/A/Resources`, and their bundles use `Contents/`.
|
|
private func applyLayout(_ layout: Layout, toSlice framework: URL, name: String) throws {
|
|
let manager = FileManager.default
|
|
let versionedResources = framework.appendingPathComponent("Versions/A/Resources")
|
|
let isVersioned = manager.fileExists(atPath: versionedResources.path)
|
|
let resources = isVersioned ? versionedResources : framework
|
|
let infoURL = resources.appendingPathComponent("Info.plist")
|
|
var format = PropertyListSerialization.PropertyListFormat.binary
|
|
let plist: Any
|
|
do {
|
|
plist = try PropertyListSerialization.propertyList(
|
|
from: Data(contentsOf: infoURL), options: [], format: &format
|
|
)
|
|
} catch {
|
|
throw ZipBuilderError("Couldn't read \(infoURL.path): \(error.localizedDescription)")
|
|
}
|
|
guard let info = plist as? [String: Any] else {
|
|
throw ZipBuilderError("\(infoURL.path) isn't a dictionary.")
|
|
}
|
|
|
|
let manifest = resources.appendingPathComponent(privacyManifestName)
|
|
if let bundle = layout.privacyBundle, manager.fileExists(atPath: manifest.path) {
|
|
try move([manifest], intoBundle: bundle, in: resources, isVersioned: isVersioned, info: info)
|
|
}
|
|
if let bundle = layout.resourceBundle {
|
|
// What makes up the framework itself; everything else here is a resource to move. Without a
|
|
// privacy bundle, the last entry is a bare `.bundle`, which is kept as well.
|
|
let frameworkItems: Set = ["Info.plist", name, "Headers", "PrivateHeaders", "Modules",
|
|
"_CodeSignature", "Versions", "Resources", privacyManifestName,
|
|
"\(bundle).bundle", "\(layout.privacyBundle ?? "").bundle"]
|
|
let items = try manager.contentsOfDirectory(atPath: resources.path).sorted()
|
|
.filter { !frameworkItems.contains($0) }.map { resources.appendingPathComponent($0) }
|
|
if !items.isEmpty {
|
|
try move(items, intoBundle: bundle, in: resources, isVersioned: isVersioned, info: info)
|
|
}
|
|
}
|
|
if !layout.infoPlist.isEmpty {
|
|
let updated = info.merging(layout.infoPlist) { _, new in new }
|
|
try PropertyListSerialization.data(fromPropertyList: updated, format: format, options: 0)
|
|
.write(to: infoURL)
|
|
}
|
|
}
|
|
|
|
/// Moves `items` into `<resources>/<name>.bundle`, creating the bundle with an `Info.plist`
|
|
/// derived from the framework's, because a bundle without one risks failing App Store validation.
|
|
private func move(_ items: [URL], intoBundle name: String, in resources: URL, isVersioned: Bool,
|
|
info: [String: Any]) throws {
|
|
let manager = FileManager.default
|
|
let bundle = resources.appendingPathComponent("\(name).bundle")
|
|
let contents = isVersioned ? bundle.appendingPathComponent("Contents") : bundle
|
|
let destination = isVersioned ? contents.appendingPathComponent("Resources") : contents
|
|
try manager.createDirectory(at: destination, withIntermediateDirectories: true)
|
|
for item in items {
|
|
let target = destination.appendingPathComponent(item.lastPathComponent)
|
|
try? manager.removeItem(at: target)
|
|
try manager.moveItem(at: item, to: target)
|
|
}
|
|
let bundleInfo = contents.appendingPathComponent("Info.plist")
|
|
if !manager.fileExists(atPath: bundleInfo.path) {
|
|
try PropertyListSerialization.data(fromPropertyList: bundleInfoPlist(info, name: name),
|
|
format: .binary, options: 0).write(to: bundleInfo)
|
|
}
|
|
}
|
|
|
|
/// A resource bundle's `Info.plist`: the framework's, without an executable, as a `BNDL` named
|
|
/// after the bundle. The reference zip's bundles have the same keys.
|
|
func bundleInfoPlist(_ frameworkInfo: [String: Any], name: String) -> [String: Any] {
|
|
var info = frameworkInfo
|
|
info["CFBundleExecutable"] = nil
|
|
info["CFBundleName"] = name
|
|
info["CFBundlePackageType"] = "BNDL"
|
|
return info
|
|
}
|