Generator/Sources/NeedleFramework/Entry/Generator.swift (113 lines of code) (raw):

// // Copyright (c) 2018. Uber Technologies // // 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 Concurrency import Foundation import SourceParsingFramework /// The entry point to Needle's code generator. public class Generator { /// Initializer. public init() {} /// Parse Swift source files by recurively scanning the given directories /// or source files included in the given source list files, excluding /// files with specified suffixes. Then generate the necessary dependency /// provider code and export to the specified destination path. /// /// - parameter sourceRootUrls: The directories or text files that contain /// a set of Swift source files to parse. /// - parameter sourcesListFormatValue: The optional `String` value of the /// format used by the sources list file. Use `nil` if the given /// `sourceRootPaths` is not a file containing a list of Swift source paths. /// - parameter exclusionSuffixes: The list of file name suffixes to /// check from. If a filename's suffix matches any in the this list, /// the file will not be parsed. /// - parameter exclusionPaths: The list of path components to check. /// If a file's URL path contains any elements in this list, the file /// will not be parsed. /// - parameter additionalImports: The additional import statements to add /// to the ones parsed from source files. /// - parameter headerDocPath: The path to custom header doc file to be /// included at the top of the generated file. /// - parameter destinationPath: The path to export generated code to. /// - parameter shouldCollectParsingInfo: `true` if dependency graph /// parsing information should be collected as tasks are executed. `false` /// otherwise. By collecting execution information, if waiting on the /// completion of a task sequence in the dependency parsing phase times out, /// the reported error contains the relevant information when the timeout /// occurred. The tracking does incur a minor performance cost. This value /// defaults to `false`. /// - parameter parsingTimeout: The timeout value, in seconds, to use for /// waiting on parsing tasks. /// - parameter exportingTimeout: The timeout value, in seconds, to use for /// waiting on exporting tasks. /// - parameter retryParsingOnTimeoutLimit: The maximum number of times /// parsing Swift source files should be retried in case of timeouts. /// - parameter concurrencyLimit: The maximum number of tasks to execute /// concurrently. `nil` if no limit is set. /// - throws: `GenericError`. public final func generate(from sourceRootPaths: [String], withSourcesListFormat sourcesListFormatValue: String? = nil, excludingFilesEndingWith exclusionSuffixes: [String], excludingFilesWithPaths exclusionPaths: [String], with additionalImports: [String], _ headerDocPath: String?, to destinationPath: String, shouldCollectParsingInfo: Bool, parsingTimeout: TimeInterval, exportingTimeout: TimeInterval, retryParsingOnTimeoutLimit: Int, concurrencyLimit: Int?, emitInputsDepsFile: Bool) throws { let processor: ProcessorType = .generateSource(additionalImports: additionalImports, headerDocPath: headerDocPath, destinationPath: destinationPath, exportingTimeout: exportingTimeout) try processSourceCode(from: sourceRootPaths, withSourcesListFormat: sourcesListFormatValue, excludingFilesEndingWith: exclusionSuffixes, excludingFilesWithPaths: exclusionPaths, shouldCollectParsingInfo: shouldCollectParsingInfo, parsingTimeout: parsingTimeout, retryParsingOnTimeoutLimit: retryParsingOnTimeoutLimit, concurrencyLimit: concurrencyLimit, processorType: processor, emitInputsDepsFile: emitInputsDepsFile) } /// Parse Swift source files by recurively scanning the given directories /// or source files included in the given source list files, excluding /// files with specified suffixes. Then print the static dependency tree starting at RootComponent. /// /// - parameter sourceRootUrls: The directories or text files that contain /// a set of Swift source files to parse. /// - parameter sourcesListFormatValue: The optional `String` value of the /// format used by the sources list file. Use `nil` if the given /// `sourceRootPaths` is not a file containing a list of Swift source paths. /// - parameter exclusionSuffixes: The list of file name suffixes to /// check from. If a filename's suffix matches any in the this list, /// the file will not be parsed. /// - parameter exclusionPaths: The list of path components to check. /// If a file's URL path contains any elements in this list, the file /// will not be parsed. /// - parameter shouldCollectParsingInfo: `true` if dependency graph /// parsing information should be collected as tasks are executed. `false` /// otherwise. By collecting execution information, if waiting on the /// completion of a task sequence in the dependency parsing phase times out, /// the reported error contains the relevant information when the timeout /// occurred. The tracking does incur a minor performance cost. This value /// defaults to `false`. /// - parameter parsingTimeout: The timeout value, in seconds, to use for /// waiting on parsing tasks. /// - parameter retryParsingOnTimeoutLimit: The maximum number of times /// parsing Swift source files should be retried in case of timeouts. /// - parameter concurrencyLimit: The maximum number of tasks to execute /// concurrently. `nil` if no limit is set. /// - throws: `GenericError`. public final func printDependencyTree(from sourceRootPaths: [String], withSourcesListFormat sourcesListFormatValue: String? = nil, excludingFilesEndingWith exclusionSuffixes: [String], excludingFilesWithPaths exclusionPaths: [String], shouldCollectParsingInfo: Bool, parsingTimeout: TimeInterval, retryParsingOnTimeoutLimit: Int, concurrencyLimit: Int?, rootComponentName: String) throws { let processor: ProcessorType = .printDIStructure(rootComponentName: rootComponentName) try processSourceCode(from: sourceRootPaths, withSourcesListFormat: sourcesListFormatValue, excludingFilesEndingWith: exclusionSuffixes, excludingFilesWithPaths: exclusionPaths, shouldCollectParsingInfo: shouldCollectParsingInfo, parsingTimeout: parsingTimeout, retryParsingOnTimeoutLimit: retryParsingOnTimeoutLimit, concurrencyLimit: concurrencyLimit, processorType: processor, emitInputsDepsFile: false) } // MARK: - Internal func generate(from sourceRootUrls: [URL], withSourcesListFormat sourcesListFormatValue: String?, excludingFilesEndingWith exclusionSuffixes: [String], excludingFilesWithPaths exclusionPaths: [String], with additionalImports: [String], _ headerDocPath: String?, to destinationPath: String, using executor: SequenceExecutor, withParsingTimeout parsingTimeout: TimeInterval, exportingTimeout: TimeInterval, emitInputsDepsFile: Bool) throws { let parser = DependencyGraphParser() let (components, imports) = try parser.parse(from: sourceRootUrls, withSourcesListFormat: sourcesListFormatValue, excludingFilesEndingWith: exclusionSuffixes, excludingFilesWithPaths: exclusionPaths, using: executor, withTimeout: parsingTimeout) let exporter = DependencyGraphExporter() try exporter.export(components, with: imports + additionalImports, to: destinationPath, using: executor, withTimeout: exportingTimeout, include: headerDocPath) } // MARK: - Private private enum ProcessorType { case generateSource(additionalImports: [String], headerDocPath: String?, destinationPath: String, exportingTimeout: TimeInterval) case printDIStructure(rootComponentName: String) } private func createExecutor(withName name: String, shouldTrackTaskId: Bool, concurrencyLimit: Int?) -> SequenceExecutor { #if DEBUG return ProcessInfo().environment["SINGLE_THREADED"] != nil ? ImmediateSerialSequenceExecutor() : ConcurrentSequenceExecutor(name: name, qos: .userInteractive, shouldTrackTaskId: shouldTrackTaskId, maxConcurrentTasks: concurrencyLimit) #else return ConcurrentSequenceExecutor(name: name, qos: .userInteractive, shouldTrackTaskId: shouldTrackTaskId, maxConcurrentTasks: concurrencyLimit) #endif } private func processSourceCode(from sourceRootPaths: [String], withSourcesListFormat sourcesListFormatValue: String? = nil, excludingFilesEndingWith exclusionSuffixes: [String], excludingFilesWithPaths exclusionPaths: [String], shouldCollectParsingInfo: Bool, parsingTimeout: TimeInterval, retryParsingOnTimeoutLimit: Int, concurrencyLimit: Int?, processorType: ProcessorType, emitInputsDepsFile: Bool) throws { let sourceRootUrls = sourceRootPaths.map { (path: String) -> URL in URL(path: path) } let executor = createExecutor(withName: "Needle.generate", shouldTrackTaskId: shouldCollectParsingInfo, concurrencyLimit: concurrencyLimit) var retryParsingCount = 0 while true { do { switch processorType { case .generateSource(let additionalImports, let headerDocPath, let destinationPath, let exportingTimeout): try generate(from: sourceRootUrls, withSourcesListFormat: sourcesListFormatValue, excludingFilesEndingWith: exclusionSuffixes, excludingFilesWithPaths: exclusionPaths, with: additionalImports, headerDocPath, to: destinationPath, using: executor, withParsingTimeout: parsingTimeout, exportingTimeout: exportingTimeout, emitInputsDepsFile: emitInputsDepsFile) case .printDIStructure(let rootComponentName): try printDIStructure(from : sourceRootUrls, withSourcesListFormat: sourcesListFormatValue, excludingFilesEndingWith: exclusionSuffixes, excludingFilesWithPaths: exclusionPaths, withExecutor: executor, withParsingTimeout: parsingTimeout, withRootComponentName: rootComponentName) } break } catch DependencyGraphParserError.timeout(let sourcePath, let taskId) { retryParsingCount += 1 let message = "Parsing Swift source file at \(sourcePath) timed out when executing task with ID \(taskId)." if retryParsingCount >= retryParsingOnTimeoutLimit { throw GenericError.withMessage(message) } } catch { throw error } } } private func printDIStructure(from sourceRootUrls: [URL], withSourcesListFormat sourcesListFormatValue: String? = nil, excludingFilesEndingWith exclusionSuffixes: [String], excludingFilesWithPaths exclusionPaths: [String], withExecutor executor: SequenceExecutor, withParsingTimeout parsingTimeout: TimeInterval, withRootComponentName rootComponentName: String) throws { let parser = DependencyGraphParser() let (components, _) = try parser.parse(from: sourceRootUrls, withSourcesListFormat: sourcesListFormatValue, excludingFilesEndingWith: exclusionSuffixes, excludingFilesWithPaths: exclusionPaths, using: executor, withTimeout: parsingTimeout) let printer = DependencyGraphPrinter(components: components) printer.printDIStructure(withRootComponentName: rootComponentName) } }