mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
127 lines
4.4 KiB
Swift
127 lines
4.4 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.
|
|
*/
|
|
|
|
@preconcurrency import Basics
|
|
import Common
|
|
import Foundation
|
|
import Logging
|
|
@preconcurrency import PackageGraph
|
|
@preconcurrency import PackageLoading
|
|
@preconcurrency import PackageModel
|
|
@preconcurrency import Workspace
|
|
|
|
/// Responsible for parsing `Package.swift` and loading the full SwiftPM `ModulesGraph`
|
|
/// using official `libSwiftPM` workspace APIs.
|
|
public struct PackageGraphLoader: Sendable {
|
|
private static let cache = GraphCache()
|
|
private let logger = Logger(label: "zip-builder.manifest-reader")
|
|
/// File system URL pointing to the root Swift package directory containing `Package.swift`.
|
|
public let packagePath: URL
|
|
|
|
/// Initializes the loader with a target package directory path.
|
|
/// - Parameter packagePath: File URL to the root package directory.
|
|
public init(packagePath: URL) {
|
|
self.packagePath = packagePath
|
|
}
|
|
|
|
/// Evaluates `Package.swift`, resolves package dependencies from `Package.resolved`,
|
|
/// and returns the complete in-memory `ModulesGraph`.
|
|
///
|
|
/// Concurrent or repeated calls for the same package path within the same process share
|
|
/// a single resolution task to prevent `libSwiftPM` workspace races on `.build/` state.
|
|
///
|
|
/// - Returns: Fully resolved `ModulesGraph` representing all packages, modules, and products.
|
|
/// - Throws: An error if manifest evaluation or package dependency resolution fails.
|
|
public func loadGraph() async throws -> ModulesGraph {
|
|
let absolutePath = try AbsolutePath(validating: packagePath.path)
|
|
let logger = self.logger
|
|
|
|
return try await Self.cache.load(at: absolutePath) {
|
|
logger.info("Loading package graph", metadata: ["path": "\(absolutePath.pathString)"])
|
|
|
|
let observability = ObservabilitySystem { scope, diagnostic in
|
|
let metadata: Logger.Metadata = [
|
|
"scope": "\(scope.description)",
|
|
"message": "\(diagnostic.message)",
|
|
]
|
|
|
|
switch diagnostic.severity {
|
|
case .error:
|
|
logger.error("Package diagnostic", metadata: metadata)
|
|
case .warning:
|
|
logger.warning("Package diagnostic", metadata: metadata)
|
|
case .info:
|
|
logger.info("Package diagnostic", metadata: metadata)
|
|
case .debug:
|
|
logger.debug("Package diagnostic", metadata: metadata)
|
|
@unknown default:
|
|
logger.info("Package diagnostic", metadata: metadata)
|
|
}
|
|
}
|
|
|
|
let workspace = try Workspace(
|
|
fileSystem: localFileSystem,
|
|
location: .init(forRootPackage: absolutePath, fileSystem: localFileSystem)
|
|
)
|
|
|
|
let rootInput = PackageGraphRootInput(packages: [absolutePath])
|
|
let graph = try await workspace.loadPackageGraph(
|
|
rootInput: rootInput,
|
|
observabilityScope: observability.topScope
|
|
)
|
|
let packageCount = graph.packages.count
|
|
let productCount = graph.allProducts.count
|
|
let targetCount = graph.allModules.count
|
|
logger.debug(
|
|
"Package graph loaded successfully",
|
|
metadata: [
|
|
"packageCount": "\(packageCount)",
|
|
"productCount": "\(productCount)",
|
|
"targetCount": "\(targetCount)",
|
|
]
|
|
)
|
|
|
|
return graph
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Actor that deduplicates and caches `ModulesGraph` loads per normalized package root path
|
|
/// so concurrent callers do not race on the same `.build/` workspace directory.
|
|
private actor GraphCache {
|
|
private var tasks: [AbsolutePath: Task<ModulesGraph, Error>] = [:]
|
|
|
|
func load(at path: AbsolutePath,
|
|
operation: @Sendable @escaping () async throws -> ModulesGraph) async throws
|
|
-> ModulesGraph {
|
|
if let existingTask = tasks[path] {
|
|
return try await existingTask.value
|
|
}
|
|
|
|
let task = Task {
|
|
try await operation()
|
|
}
|
|
tasks[path] = task
|
|
|
|
do {
|
|
return try await task.value
|
|
} catch {
|
|
tasks.removeValue(forKey: path)
|
|
throw error
|
|
}
|
|
}
|
|
}
|