Generator/Sources/NeedleFramework/Generating/Pluginized/PluginizedDependencyGraphExporter.swift (120 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, inlcuding pluginized and non-core ones, into necessary /// dependency providers and their registrations, then exports the contents to /// the destination path. class PluginizedDependencyGraphExporter { /// Generate the necessary dependency provider and plugin extension source /// code for the given components and pluginized components, and export /// the source code to the given destination path. /// /// - parameter components: Array of Components to generate dependnecy /// providers for /// - parameter pluginizedComponents: Array of pluginized components to /// generate plugin extensions and dependnecy providers for. /// - parameter imports: The import statements. /// - parameter path: Path to file where we want the results written to. /// - parameter executor: 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. /// - parameter needleVersionHash: The needleVersionHash so that we /// can recompile when upstream-dependency files change /// - throws: `DependencyGraphExporterError.timeout` if computation times out. /// - throws: `DependencyGraphExporterError.unableToWriteFile` if the file /// write fails. func export(_ components: [Component], _ pluginizedComponents: [PluginizedComponent], with imports: [String], to path: String, using executor: SequenceExecutor, withTimeout timeout: TimeInterval, include headerDocPath: String?, needleVersionHash: String?) throws { // Enqueue tasks. let dependencyProviderHandleTuples = enqueueExportDependencyProviders(for: components, pluginizedComponents, using: executor) let dynamicDependencyProviderHandleTuples = enqueueExportDynamicDependencyProviders(for: components, pluginizedComponents, using: executor) let pluginExtensionHandleTuples = enqueueExportPluginExtensions(for: pluginizedComponents, using: executor) let dynamicpluginExtensionHandleTuples = enqueueExportDynamicPluginExtensions(for: pluginizedComponents, using: executor) let headerDocContentHandle = enqueueLoadHeaderDoc(from: headerDocPath, using: executor) // Wait for execution to complete. let serializedProviders = try awaitSerialization(using: dependencyProviderHandleTuples + pluginExtensionHandleTuples, withTimeout: timeout) let serializedDynamicProviders = try awaitSerialization(using: dynamicDependencyProviderHandleTuples + dynamicpluginExtensionHandleTuples, withTimeout: timeout) let headerDocContent = try headerDocContentHandle?.await(withTimeout: timeout) ?? "" let fileContents = OutputSerializer(providers: serializedProviders, dynamicProviders: serializedDynamicProviders, imports: imports, headerDocContent: headerDocContent, needleVersionHash: needleVersionHash).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) -> 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], _ pluginizedComponents: [PluginizedComponent], using executor: SequenceExecutor) -> [(SequenceExecutionHandle<[SerializedProvider]>, String)] { let pluginizedData = pluginizedComponents.map { (component: PluginizedComponent) -> Component in component.data } let allComponents = components + pluginizedData var taskHandleTuples = [(handle: SequenceExecutionHandle<[SerializedProvider]>, componentName: String)]() for component in allComponents { 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: pluginizedComponents)) } else if currentTask is PluginizedDependencyProviderContentTask, let processedProviders = currentResult as? [PluginizedProcessedDependencyProvider] { return .continueSequence(PluginizedDependencyProviderSerializerTask(providers: processedProviders)) } else if currentTask is PluginizedDependencyProviderSerializerTask, 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], _ pluginizedComponents: [PluginizedComponent], using executor: SequenceExecutor) -> [(SequenceExecutionHandle<[SerializedProvider]>, String)] { let pluginizedData = pluginizedComponents.map { (component: PluginizedComponent) -> Component in component.data } let allComponents = components + pluginizedData var taskHandleTuples = [(handle: SequenceExecutionHandle<[SerializedProvider]>, componentName: String)]() for component in allComponents { 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: 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 enqueueExportPluginExtensions(for pluginizedComponents: [PluginizedComponent], using executor: SequenceExecutor) -> [(SequenceExecutionHandle<[SerializedProvider]>, String)] { var taskHandleTuples = [(handle: SequenceExecutionHandle<[SerializedProvider]>, pluginExtensionName: String)]() for component in pluginizedComponents { let task = PluginExtensionSerializerTask(component: component) let taskHandle = executor.executeSequence(from: task) { (currentTask: Task, currentResult: Any) -> SequenceExecution<[SerializedProvider]> in return .endOfSequence([currentResult as! SerializedProvider]) } taskHandleTuples.append((taskHandle, component.pluginExtension.name)) } return taskHandleTuples } private func enqueueExportDynamicPluginExtensions(for pluginizedComponents: [PluginizedComponent], using executor: SequenceExecutor) -> [(SequenceExecutionHandle<[SerializedProvider]>, String)] { var taskHandleTuples = [(handle: SequenceExecutionHandle<[SerializedProvider]>, pluginExtensionName: String)]() for component in pluginizedComponents { let task = PluginExtensionDynamicSerializerTask(component: component) let taskHandle = executor.executeSequence(from: task) { (currentTask: Task, currentResult: Any) -> SequenceExecution<[SerializedProvider]> in return .endOfSequence([currentResult as! SerializedProvider]) } taskHandleTuples.append((taskHandle, component.pluginExtension.name)) } return taskHandleTuples } private func awaitSerialization(using taskHandleTuples: [(SequenceExecutionHandle<[SerializedProvider]>, String)], withTimeout timeout: TimeInterval) throws -> [SerializedProvider] { 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 } }