| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- /*
- * 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
- }
- }
|