mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
219 lines
7.7 KiB
Swift
219 lines
7.7 KiB
Swift
/*
|
|
* Copyright 2019 Google
|
|
*
|
|
* 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
|
|
|
|
// Extensions to FileManager that make scripting easier or cleaner for error reporting.
|
|
public extension FileManager {
|
|
// MARK: - Helper Enum Declarations
|
|
|
|
/// Describes a type of file to be searched for.
|
|
enum SearchFileType {
|
|
/// All files, not including folders.
|
|
case allFiles
|
|
|
|
/// All folders with a `.bundle` extension.
|
|
case bundles
|
|
|
|
/// A directory with an optional name. If name is `nil`, all directories will be matched.
|
|
case directories(name: String?)
|
|
|
|
/// All folders with a `.framework` extension.
|
|
case frameworks
|
|
|
|
/// All headers with a `.h` extension.
|
|
case headers
|
|
|
|
/// All files with the `.storyboard` extension.
|
|
case storyboards
|
|
}
|
|
|
|
// MARK: - Error Declarations
|
|
|
|
/// Errors that can be used to propagate up through the script related to files.
|
|
enum FileError: Error {
|
|
case directoryNotFound(path: String)
|
|
case failedToCreateDirectory(path: String, error: Error)
|
|
case writeToFileFailed(file: String, error: Error)
|
|
}
|
|
|
|
/// Errors that can occur during a recursive search operation.
|
|
enum RecursiveSearchError: Error {
|
|
case failedToCreateEnumerator(forDirectory: URL)
|
|
}
|
|
|
|
// MARK: - Directory Management
|
|
|
|
/// Convenience function to determine if there's a directory at the given file URL using existing
|
|
/// FileManager calls.
|
|
func directoryExists(at url: URL) -> Bool {
|
|
var isDir: ObjCBool = false
|
|
let exists = fileExists(atPath: url.path, isDirectory: &isDir)
|
|
return exists && isDir.boolValue
|
|
}
|
|
|
|
/// Convenience function to determine if a given file URL is a directory.
|
|
func isDirectory(at url: URL) -> Bool {
|
|
return directoryExists(at: url)
|
|
}
|
|
|
|
/// Returns the URL to the source Pod cache directory, and creates it if it doesn't exist.
|
|
func sourcePodCacheDirectory(withSubdir subdir: String = "") throws -> URL {
|
|
let cacheDir = FileManager.default.temporaryDirectory(withName: "cache")
|
|
let cacheRoot = cacheDir.appendingPathComponents([subdir])
|
|
if directoryExists(at: cacheRoot) {
|
|
return cacheRoot
|
|
}
|
|
|
|
// The cache root folder doesn't exist yet, create it.
|
|
try createDirectory(at: cacheRoot, withIntermediateDirectories: true)
|
|
|
|
return cacheRoot
|
|
}
|
|
|
|
/// Removes a directory or file if it exists. This is helpful to clean up error handling for
|
|
/// checks that
|
|
/// shouldn't fail. The only situation this could potentially fail is permission errors or if a
|
|
/// folder is open in Finder, and in either state the user needs to close the window or fix the
|
|
/// permissions. A fatal error will be thrown in those situations.
|
|
func removeIfExists(at url: URL) {
|
|
guard directoryExists(at: url) || fileExists(atPath: url.path) else { return }
|
|
|
|
do {
|
|
try removeItem(at: url)
|
|
} catch {
|
|
fatalError("""
|
|
Tried to remove directory \(url) but it failed - close any Finder windows and try again.
|
|
Error: \(error)
|
|
""")
|
|
}
|
|
}
|
|
|
|
/// Enable a single unique temporary workspace per execution with a sortable and readable
|
|
/// timestamp.
|
|
private static func timeStamp() -> String {
|
|
let formatter = DateFormatter()
|
|
formatter.dateFormat = "yyyy-MM-dd'T'HH-mm-ss"
|
|
return formatter.string(from: Date())
|
|
}
|
|
|
|
static let unique: String = timeStamp()
|
|
|
|
/// Allow clients to override default location for temporary directory creation
|
|
static var buildRoot: URL?
|
|
static func registerBuildRoot(buildRoot: URL) {
|
|
FileManager.buildRoot = buildRoot
|
|
}
|
|
|
|
/// Returns a deterministic path of a temporary directory for the given name. Note: This does
|
|
/// *not* create the directory if it doesn't exist, merely generates the name for creation.
|
|
func temporaryDirectory(withName name: String) -> URL {
|
|
// Get access to the temporary directory. This could be passed in via `LaunchArgs`, or use the
|
|
// default temporary directory.
|
|
let tempDir: URL
|
|
if let root = FileManager.buildRoot {
|
|
tempDir = root
|
|
} else
|
|
if #available(OSX 10.12, *) {
|
|
tempDir = temporaryDirectory
|
|
} else {
|
|
tempDir = URL(fileURLWithPath: NSTemporaryDirectory())
|
|
}
|
|
|
|
// Organize all temporary directories into a "ZipRelease" directory.
|
|
let unique = FileManager.unique
|
|
let zipDir = tempDir.appendingPathComponent("ZipRelease/" + unique, isDirectory: true)
|
|
return zipDir.appendingPathComponent(name, isDirectory: true)
|
|
}
|
|
|
|
// MARK: Searching
|
|
|
|
/// Recursively search for a set of items in a particular directory.
|
|
func recursivelySearch(for type: SearchFileType, in dir: URL) throws -> [URL] {
|
|
// Throw an error so an appropriate error can be logged from the caller.
|
|
|
|
guard directoryExists(at: dir) else {
|
|
throw FileError.directoryNotFound(path: dir.path)
|
|
}
|
|
|
|
// We have a directory, create an enumerator to do a recursive search.
|
|
let keys: [URLResourceKey] = [.nameKey, .isDirectoryKey]
|
|
guard let dirEnumerator = enumerator(at: dir, includingPropertiesForKeys: keys) else {
|
|
// Throw an error so an appropriate error can be logged from the caller.
|
|
throw RecursiveSearchError.failedToCreateEnumerator(forDirectory: dir)
|
|
}
|
|
|
|
// Recursively search using the enumerator, adding any matches to the array.
|
|
var matches: [URL] = []
|
|
var foundXcframework = false // Ignore .frameworks after finding an xcframework.
|
|
for case let fileURL as URL in dirEnumerator {
|
|
// Never mess with Privacy.bundles
|
|
if fileURL.lastPathComponent.hasSuffix("_Privacy.bundle") {
|
|
dirEnumerator.skipDescendants()
|
|
continue
|
|
}
|
|
|
|
switch type {
|
|
case .allFiles:
|
|
// Skip directories, include everything else.
|
|
guard !isDirectory(at: fileURL) else { continue }
|
|
|
|
matches.append(fileURL)
|
|
case let .directories(name):
|
|
// Skip any non-directories.
|
|
guard directoryExists(at: fileURL) else { continue }
|
|
|
|
// Get the name of the directory we're searching for. If there's not a specific name
|
|
// being searched for, add it as a match and move on.
|
|
guard let name = name else {
|
|
matches.append(fileURL)
|
|
continue
|
|
}
|
|
|
|
// If the last path component is a match, it's a directory we're looking for!
|
|
if fileURL.lastPathComponent == name {
|
|
matches.append(fileURL)
|
|
}
|
|
case .bundles:
|
|
// The only thing of interest is the path extension being ".bundle".
|
|
if fileURL.pathExtension == "bundle" {
|
|
matches.append(fileURL)
|
|
}
|
|
case .headers:
|
|
if fileURL.pathExtension == "h" {
|
|
matches.append(fileURL)
|
|
}
|
|
case .storyboards:
|
|
// The only thing of interest is the path extension being ".storyboard".
|
|
if fileURL.pathExtension == "storyboard" {
|
|
matches.append(fileURL)
|
|
}
|
|
case .frameworks:
|
|
// We care if it's a directory and has a .xcframework or .framework extension.
|
|
if directoryExists(at: fileURL) {
|
|
if fileURL.pathExtension == "xcframework" {
|
|
matches.append(fileURL)
|
|
foundXcframework = true
|
|
} else if !foundXcframework, fileURL.pathExtension == "framework" {
|
|
matches.append(fileURL)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return matches
|
|
}
|
|
}
|