Generator/Sources/NeedleFramework/Generating/DependencyGraphExporter.swift (89 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 generation phase entry class that executes tasks to process dependency /// graph components into the necessary dependency providers and their /// registrations, then exports the contents to the destination path. class DependencyGraphExporter { /// Initializer. init() {} /// Given an array of components to create dependency providers for, for /// each one, traverse it's list of parents looking for all the required /// dependencies. Then turn this data into the source code for the dependency /// providers. /// /// - parameter components: Array of Components to export. /// - parameter imports: The import statements. /// - parameter to: Path to file where we want the results written to. /// - parameter using: The executor to use for concurrent computation of /// the dependency provider bodies. /// - parameter timeout: The timeout value, in seconds, to use for /// waiting on exporting tasks. /// - parameter headerDocPath: The path to custom header doc file to be /// included at the top of the generated file. /// - throws: `DependencyGraphExporterError.timeout` if computation times out. /// - throws: `DependencyGraphExporterError.unableToWriteFile` if the file /// write fails. func export(_ components: [Component], with imports: [String], to path: String, using executor: SequenceExecutor, withTimeout timeout: TimeInterval, include headerDocPath: String?) throws { // Enqueue tasks. let taskHandleTuples = enqueueExportDependencyProviders(for: components, using: executor) let dynamicTtaskHandleTuples = enqueueExportDynamicDependencyProviders(for: components, using: executor) let headerDocContentHandle = try enqueueLoadHeaderDoc(from: headerDocPath, using: executor) // Wait for execution to complete. let providers = try awaitSerialization(using: taskHandleTuples, withTimeout: timeout) let dynamicProviders = try awaitSerialization(using: dynamicTtaskHandleTuples, withTimeout: timeout) let headerDocContent = try headerDocContentHandle?.await(withTimeout: timeout) ?? "" let fileContents = OutputSerializer(providers: providers, dynamicProviders: dynamicProviders, imports: imports, headerDocContent: headerDocContent).serialize() let currentFileContents = try? String(contentsOfFile: path, encoding: .utf8) guard currentFileContents != fileContents else { info("Not writing the file as content is unchanged") return } try fileContents.write(toFile: path, atomically: true, encoding: .utf8) } // MARK: - Private private func enqueueLoadHeaderDoc(from filePath: String?, using executor: SequenceExecutor) throws -> SequenceExecutionHandle<String>? { guard let filePath = filePath else { return nil } let loaderTask = FileContentLoaderTask(filePath: filePath) return executor.executeSequence(from: loaderTask) { (_, result: Any) -> SequenceExecution<String> in // Cannot throw error here. Also the force cast is safe since that's // the return type of the task. return .endOfSequence(result as! String) } } private func enqueueExportDependencyProviders(for components: [Component], using executor: SequenceExecutor) -> [(SequenceExecutionHandle<[SerializedProvider]>, String)] { var taskHandleTuples = [(handle: SequenceExecutionHandle<[SerializedProvider]>, componentName: String)]() for component in components { let initialTask = DependencyProviderDeclarerTask(component: component) let taskHandle = executor.executeSequence(from: initialTask) { (currentTask: Task, currentResult: Any) -> SequenceExecution<[SerializedProvider]> in if currentTask is DependencyProviderDeclarerTask, let providers = currentResult as? [DependencyProvider] { return .continueSequence(DependencyProviderContentTask(providers: providers)) } else if currentTask is DependencyProviderContentTask, let processedProviders = currentResult as? [ProcessedDependencyProvider] { return .continueSequence(DependencyProviderSerializerTask(providers: processedProviders)) } else if currentTask is DependencyProviderSerializerTask, let serializedProviders = currentResult as? [SerializedProvider] { return .endOfSequence(serializedProviders) } else { error("Unhandled task \(currentTask) with result \(currentResult)") } } taskHandleTuples.append((taskHandle, component.name)) } return taskHandleTuples } private func enqueueExportDynamicDependencyProviders(for components: [Component], using executor: SequenceExecutor) -> [(SequenceExecutionHandle<[SerializedProvider]>, String)] { var taskHandleTuples = [(handle: SequenceExecutionHandle<[SerializedProvider]>, componentName: String)]() for component in components { let initialTask = DependencyProviderDeclarerTask(component: component) let taskHandle = executor.executeSequence(from: initialTask) { (currentTask: Task, currentResult: Any) -> SequenceExecution<[SerializedProvider]> in if currentTask is DependencyProviderDeclarerTask, let providers = currentResult as? [DependencyProvider] { return .continueSequence(PluginizedDependencyProviderContentTask(providers: providers, pluginizedComponents: [])) } else if currentTask is PluginizedDependencyProviderContentTask, let processedProviders = currentResult as? [PluginizedProcessedDependencyProvider] { return .continueSequence(PluginizedDynamicDependencyProviderSerializerTask(component: component, providers: processedProviders)) } else if currentTask is PluginizedDynamicDependencyProviderSerializerTask, let serializedProviders = currentResult as? [SerializedProvider] { return .endOfSequence(serializedProviders) } else { error("Unhandled task \(currentTask) with result \(currentResult)") } } taskHandleTuples.append((taskHandle, component.name)) } return taskHandleTuples } private func awaitSerialization(using taskHandleTuples: [(SequenceExecutionHandle<[SerializedProvider]>, String)], withTimeout timeout: TimeInterval) throws -> [SerializedProvider] { // Wait for all the generation to complete so we can write all the output into a single file var providers = [SerializedProvider]() var isMissingDependencies = false for (taskHandle, componentName) in taskHandleTuples { do { let provider = try taskHandle.await(withTimeout: timeout) providers.append(contentsOf: provider) } catch DependencyProviderContentError.missingDependency(let message) { warning(message) isMissingDependencies = true } catch SequenceExecutionError.awaitTimeout { throw GenericError.withMessage("Generating dependency provider for \(componentName) timed out.") } catch { throw error } } if isMissingDependencies { throw GenericError.withMessage("Some dependencies are missing, please look at the warnings above for the list.") } return providers } }