mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
203 lines
8.4 KiB
Swift
203 lines
8.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
|
|
@preconcurrency import PackageGraph
|
|
@preconcurrency import PackageModel
|
|
|
|
/// Utility responsible for discovering, sanitizing, and constructing header search paths
|
|
/// (`HEADER_SEARCH_PATHS`) for compiled C/Objective-C/Swift targets in the generated Xcode project.
|
|
public enum HeaderSearchPathBuilder: Sendable {
|
|
/// Default base search paths included for all generated project targets.
|
|
public static let defaultBaseSearchPaths: [String] = Config.Defaults.baseHeaderSearchPaths
|
|
|
|
/// Default directory names excluded from header search paths to prevent pollution.
|
|
public static let excludedFolderNames: Set<String> = [
|
|
".build",
|
|
"checkouts",
|
|
]
|
|
|
|
/// Validates an absolute path against excluded directory names and returns its string
|
|
/// representation.
|
|
///
|
|
/// - Parameter path: The candidate absolute directory path.
|
|
/// - Returns: The path string if allowed, or `nil` if the directory is excluded (e.g. `.build` or
|
|
/// `checkouts`).
|
|
public static func validateSearchPath(_ path: AbsolutePath) -> String? {
|
|
return !excludedFolderNames.contains(path.basename) ? path.pathString : nil
|
|
}
|
|
|
|
/// Validates and normalizes a candidate header search path or Xcode macro string.
|
|
///
|
|
/// Empty or whitespace-only strings are ignored and return `nil`. Xcode build setting macros
|
|
/// (starting with `$`, such as `$(inherited)` or `$(PROJECT_DIR)/GeneratedModules`) are allowed
|
|
/// verbatim. Concrete paths must be valid absolute paths and not end in an excluded directory
|
|
/// name.
|
|
///
|
|
/// - Parameter path: The candidate search path string or Xcode macro.
|
|
/// - Returns: The normalized path or macro string if valid and allowed, or `nil` if excluded or
|
|
/// empty.
|
|
/// - Throws: An error if the path cannot be validated as a valid absolute path.
|
|
public static func validateSearchPath(_ path: String) throws -> String? {
|
|
let trimmed = path.trimmingCharacters(in: .whitespaces)
|
|
guard !trimmed.isEmpty else { return nil }
|
|
|
|
// Xcode macros (e.g. $(inherited), $(PROJECT_DIR)/GeneratedModules) are always allowed.
|
|
if trimmed.hasPrefix("$") {
|
|
return trimmed
|
|
}
|
|
|
|
let absPath = try AbsolutePath(validating: trimmed)
|
|
return validateSearchPath(absPath)
|
|
}
|
|
|
|
/// Extracts the public header search paths that a Clang module exports to dependent targets.
|
|
///
|
|
/// Leverages SwiftPM's `includeDir` as the single source of truth and includes any immediate
|
|
/// module namespace subfolders (e.g. `Public/FirebaseCore` or `spm_headers/nanopb`) to support
|
|
/// both modular (`#import <FirebaseCore/FIRApp.h>`) and unqualified (`#import "FIRApp.h"`)
|
|
/// imports.
|
|
///
|
|
/// - Parameter clangModule: The Clang module to inspect.
|
|
/// - Returns: Sorted array of unique exported search paths.
|
|
public static func exportedSearchPaths(for clangModule: ClangModule) -> [String] {
|
|
let includeDir = clangModule.includeDir
|
|
var searchPaths = Set<String>()
|
|
|
|
if let path = validateSearchPath(includeDir) {
|
|
searchPaths.insert(path)
|
|
}
|
|
|
|
for header in clangModule.headers where header.isDescendantOfOrEqual(to: includeDir) {
|
|
let parent = header.parentDirectory
|
|
if parent != includeDir, let path = validateSearchPath(parent) {
|
|
searchPaths.insert(path)
|
|
}
|
|
}
|
|
|
|
return Array(searchPaths).sorted()
|
|
}
|
|
|
|
/// Extracts the exported public header search paths for a resolved module.
|
|
///
|
|
/// - Parameter module: The resolved module to inspect.
|
|
/// - Returns: Sorted array of unique exported search paths, or empty array if not a Clang module.
|
|
public static func exportedSearchPaths(for module: ResolvedModule) -> [String] {
|
|
guard let clangModule = module.underlying as? ClangModule else { return [] }
|
|
return exportedSearchPaths(for: clangModule)
|
|
}
|
|
|
|
/// Computes exported header search paths from a public include directory and optional header file
|
|
/// paths.
|
|
///
|
|
/// - Parameters:
|
|
/// - includeDir: The base include directory path.
|
|
/// - headerPaths: List of header file paths located within or under the include directory.
|
|
/// - Returns: Sorted array of unique exported search paths.
|
|
/// - Throws: An error if `includeDir` or any `headerPaths` entry cannot be validated as an
|
|
/// absolute path.
|
|
public static func exportedSearchPaths(includeDir: String,
|
|
headerPaths: [String] = []) throws -> [String] {
|
|
let includePath = try AbsolutePath(validating: includeDir)
|
|
guard let validatedIncludePath = validateSearchPath(includePath) else { return [] }
|
|
|
|
var searchPaths = Set<String>([validatedIncludePath])
|
|
for header in headerPaths {
|
|
let headerPath = try AbsolutePath(validating: header)
|
|
let parent = headerPath.parentDirectory
|
|
if parent != includePath,
|
|
parent.isDescendantOfOrEqual(to: includePath),
|
|
let path = validateSearchPath(parent) {
|
|
searchPaths.insert(path)
|
|
}
|
|
}
|
|
|
|
return Array(searchPaths).sorted()
|
|
}
|
|
|
|
/// Constructs the complete, sanitized header search paths for compiling a target.
|
|
///
|
|
/// Combines:
|
|
/// 1. Base project search paths (`$(inherited)`, `GeneratedModules`).
|
|
/// 2. Header search paths exported by direct and transitive dependencies.
|
|
/// 3. Explicit compiler search paths declared on the target (e.g. `cSettings:
|
|
/// [.headerSearchPath(...)]`).
|
|
/// 4. Directories containing the target's own header files (for internal includes).
|
|
///
|
|
/// - Parameters:
|
|
/// - ownHeaders: Header file paths belonging directly to the target.
|
|
/// - explicitSearchPaths: Search paths extracted from build settings assignments.
|
|
/// - dependencySearchPaths: Exported header search paths collected from dependencies.
|
|
/// - baseSearchPaths: Base header search paths applied to all targets.
|
|
/// - Returns: Sorted array of unique, cleaned header search paths.
|
|
/// - Throws: An error if any concrete search path or header path cannot be validated as an
|
|
/// absolute path.
|
|
public static func buildTargetHeaderSearchPaths(ownHeaders: [String] = [],
|
|
explicitSearchPaths: [String] = [],
|
|
dependencySearchPaths: [String] = [],
|
|
baseSearchPaths: [String] =
|
|
defaultBaseSearchPaths) throws -> [String] {
|
|
var searchPaths = Set<String>()
|
|
|
|
func addSearchPath(_ path: String) throws {
|
|
if let path = try validateSearchPath(path) {
|
|
searchPaths.insert(path)
|
|
}
|
|
}
|
|
|
|
for path in baseSearchPaths {
|
|
try addSearchPath(path)
|
|
}
|
|
|
|
for path in dependencySearchPaths {
|
|
try addSearchPath(path)
|
|
}
|
|
|
|
for path in explicitSearchPaths {
|
|
try addSearchPath(path)
|
|
}
|
|
|
|
for header in ownHeaders {
|
|
let trimmed = header.trimmingCharacters(in: .whitespaces)
|
|
guard !trimmed.isEmpty else { continue }
|
|
let headerPath = try AbsolutePath(validating: trimmed)
|
|
try addSearchPath(headerPath.parentDirectory.pathString)
|
|
}
|
|
|
|
return Array(searchPaths).sorted()
|
|
}
|
|
|
|
/// Returns a Boolean value indicating whether a path string represents an unresolved Xcode build
|
|
/// setting macro (e.g. `$(inherited)` or `$(SRCROOT)`).
|
|
///
|
|
/// - Parameter path: The path string to inspect.
|
|
/// - Returns: `true` if the path starts with `$`; otherwise, `false`.
|
|
public static func isXcodeMacro(_ path: String) -> Bool {
|
|
path.hasPrefix("$")
|
|
}
|
|
|
|
/// Filters an array of paths to exclude any Xcode build setting macros, returning only concrete
|
|
/// filesystem paths.
|
|
///
|
|
/// - Parameter paths: An array of path strings that may include Xcode build setting macros.
|
|
/// - Returns: An array containing only concrete filesystem paths.
|
|
public static func filterConcretePaths(_ paths: [String]) -> [String] {
|
|
paths.filter { !isXcodeMacro($0) }
|
|
}
|
|
}
|