From 49a01776ccd525f00da5569ebb9458b8fe0a42f4 Mon Sep 17 00:00:00 2001 From: Rudro Samanta Date: Tue, 28 Jun 2022 23:01:20 -0700 Subject: [PATCH 1/2] New Needle dynamic codepath - Code generated is guarded so current behavior is not affected - Both paths are generated in the same file, the parsing of the `#if` should not slow things down - Addresses https://github.com/uber/needle/issues/432 --- Generator/Package.swift | 6 +- .../Generating/DependencyGraphExporter.swift | 26 ++++++- ...PluginExtensionDynamicSerializerTask.swift | 35 ++++++++++ .../PluginizedDependencyGraphExporter.swift | 45 ++++++++++++- ...amicDependencyProviderSerializerTask.swift | 51 ++++++++++++++ .../DependencyPropsSerializer.swift | 67 +++++++++++++++++++ .../Serializers/OutputSerializer.swift | 23 ++++++- ...ginExtensionDynamicContentSerializer.swift | 57 ++++++++++++++++ 8 files changed, 302 insertions(+), 8 deletions(-) create mode 100644 Generator/Sources/NeedleFramework/Generating/Pluginized/PluginExtensionDynamicSerializerTask.swift create mode 100644 Generator/Sources/NeedleFramework/Generating/Pluginized/PluginizedDynamicDependencyProviderSerializerTask.swift create mode 100644 Generator/Sources/NeedleFramework/Generating/Serializers/DependencyPropsSerializer.swift create mode 100644 Generator/Sources/NeedleFramework/Generating/Serializers/Pluginized/PluginExtensionDynamicContentSerializer.swift diff --git a/Generator/Package.swift b/Generator/Package.swift index f85e6cc4..915889b1 100644 --- a/Generator/Package.swift +++ b/Generator/Package.swift @@ -1,8 +1,8 @@ -// swift-tools-version:5.1 +// swift-tools-version:5.5 import PackageDescription // Based on https://github.com/apple/swift-syntax#readme -#if swift(>=5.6) && swift(<5.7) +#if swift(>=5.6) && swift(<5.8) let swiftSyntaxVersion: Version = "0.50600.1" #elseif swift(>=5.5) let swiftSyntaxVersion: Version = "0.50500.0" @@ -47,7 +47,7 @@ let package = Package( exclude: [ "Fixtures", ]), - .target( + .executableTarget( name: "needle", dependencies: [ "NeedleFramework", diff --git a/Generator/Sources/NeedleFramework/Generating/DependencyGraphExporter.swift b/Generator/Sources/NeedleFramework/Generating/DependencyGraphExporter.swift index 9283bc58..2a5b524f 100644 --- a/Generator/Sources/NeedleFramework/Generating/DependencyGraphExporter.swift +++ b/Generator/Sources/NeedleFramework/Generating/DependencyGraphExporter.swift @@ -46,13 +46,15 @@ class DependencyGraphExporter { 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, imports: imports, headerDocContent: headerDocContent).serialize() + 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") @@ -96,6 +98,28 @@ class DependencyGraphExporter { 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 diff --git a/Generator/Sources/NeedleFramework/Generating/Pluginized/PluginExtensionDynamicSerializerTask.swift b/Generator/Sources/NeedleFramework/Generating/Pluginized/PluginExtensionDynamicSerializerTask.swift new file mode 100644 index 00000000..b740e011 --- /dev/null +++ b/Generator/Sources/NeedleFramework/Generating/Pluginized/PluginExtensionDynamicSerializerTask.swift @@ -0,0 +1,35 @@ +// +// PluginExtensionDynamicSerializerTask.swift +// +// +// Created by Rudro Samanta on 7/1/22. +// + +import Concurrency +import Foundation + +/// The task that generates the declaration and registration of the +/// plugin extension provider for a specific pluginized component. +class PluginExtensionDynamicSerializerTask : AbstractTask { + + /// Initializer. + /// + /// - parameter component: The pluginized component that requires the + /// plugin extension provider. + init(component: PluginizedComponent) { + self.component = component + super.init(id: TaskIds.pluginExtensionSerializerTask.rawValue) + } + + /// Execute the task and returns the data model. + /// + /// - returns: The `SerializedProvider`. + override func execute() -> SerializedProvider { + let content = PluginExtensionDynamicContentSerializer(component: component).serialize() + return SerializedProvider(content: content, registration: "", attributes: ProviderAttributes()) + } + + // MARK: - Private + + private let component: PluginizedComponent +} diff --git a/Generator/Sources/NeedleFramework/Generating/Pluginized/PluginizedDependencyGraphExporter.swift b/Generator/Sources/NeedleFramework/Generating/Pluginized/PluginizedDependencyGraphExporter.swift index c94705b6..104c8ba2 100644 --- a/Generator/Sources/NeedleFramework/Generating/Pluginized/PluginizedDependencyGraphExporter.swift +++ b/Generator/Sources/NeedleFramework/Generating/Pluginized/PluginizedDependencyGraphExporter.swift @@ -48,14 +48,17 @@ class PluginizedDependencyGraphExporter { 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, imports: imports, headerDocContent: headerDocContent, needleVersionHash: needleVersionHash).serialize() + 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") @@ -104,6 +107,33 @@ class PluginizedDependencyGraphExporter { 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 { @@ -117,6 +147,19 @@ class PluginizedDependencyGraphExporter { 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 diff --git a/Generator/Sources/NeedleFramework/Generating/Pluginized/PluginizedDynamicDependencyProviderSerializerTask.swift b/Generator/Sources/NeedleFramework/Generating/Pluginized/PluginizedDynamicDependencyProviderSerializerTask.swift new file mode 100644 index 00000000..40ac4e22 --- /dev/null +++ b/Generator/Sources/NeedleFramework/Generating/Pluginized/PluginizedDynamicDependencyProviderSerializerTask.swift @@ -0,0 +1,51 @@ +// +// 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 + +/// The task that serializes a list of pluginized processed dependency +/// providers into exportable foramt. +class PluginizedDynamicDependencyProviderSerializerTask: AbstractTask<[SerializedProvider]> { + + /// Initializer. + /// + /// - parameter providers: The pluginized processed dependency provider + /// to serialize. + init(component: Component, providers: [PluginizedProcessedDependencyProvider]) { + self.component = component + self.providers = providers + super.init(id: TaskIds.pluginizedDependencyProviderSerializerTask.rawValue) + } + + /// Execute the task and returns the in-memory serialized dependency + /// provider data models. + /// + /// - returns: The list of `SerializedProvider`. + override func execute() -> [SerializedProvider] { + guard !providers.isEmpty else { + return [] + } + let serilizer = DependencyPropsSerializer(component: component) + let result = SerializedProvider(content: serilizer.serialize(), registration: "", attributes: ProviderAttributes()) + return [result] + } + + // MARK: - Private + + private let component: Component + private let providers: [PluginizedProcessedDependencyProvider] +} diff --git a/Generator/Sources/NeedleFramework/Generating/Serializers/DependencyPropsSerializer.swift b/Generator/Sources/NeedleFramework/Generating/Serializers/DependencyPropsSerializer.swift new file mode 100644 index 00000000..7d2d3ed3 --- /dev/null +++ b/Generator/Sources/NeedleFramework/Generating/Serializers/DependencyPropsSerializer.swift @@ -0,0 +1,67 @@ +// +// 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 Foundation + +class DependencyPropsSerializer: Serializer { + + init(component: Component) { + self.component = component + } + + func serialize() -> String { + if component.isLeaf { + return """ +extension \(component.name): Registration { + public func registerItems() { +\(serialize(component.dependency)) + } +} + +""" + } else { + return """ +extension \(component.name): Registration { + public func registerItems() { +\(serialize(component.dependency)) +\(serialize(component.properties)) + } +} + +""" + } + } + + // MARK: - Private + + private func serialize(_ dependency: Dependency) -> String { + let dependencyName = dependency.name + return dependency.properties.map { property in + return " keyPathToName[\\\(dependencyName).\(property.name)] = \"\(property.name)-\(property.type)\"" + }.joined(separator: "\n") + } + + private func serialize(_ properties: [Property]) -> String { + return properties.filter { property in + !property.isInternal + }.map { property in + return " localTable[\"\(property.name)-\(property.type)\"] = { self.\(property.name) as Any }" + }.joined(separator: "\n") + } + + private let component: Component +} + diff --git a/Generator/Sources/NeedleFramework/Generating/Serializers/OutputSerializer.swift b/Generator/Sources/NeedleFramework/Generating/Serializers/OutputSerializer.swift index fb051a88..7f8e5393 100644 --- a/Generator/Sources/NeedleFramework/Generating/Serializers/OutputSerializer.swift +++ b/Generator/Sources/NeedleFramework/Generating/Serializers/OutputSerializer.swift @@ -26,8 +26,9 @@ class OutputSerializer: Serializer { /// - parameter imports: The list of import statements to include. /// - parameter headerDocContent: The content of the header doc to /// include at the top of the output file. - init(providers: [SerializedProvider], imports: [String], headerDocContent: String, needleVersionHash: String? = nil) { + init(providers: [SerializedProvider], dynamicProviders: [SerializedProvider], imports: [String], headerDocContent: String, needleVersionHash: String? = nil) { self.providers = providers + self.dynamicProviders = dynamicProviders self.imports = imports self.headerDocContent = headerDocContent self.needleVersionHash = needleVersionHash @@ -61,6 +62,10 @@ class OutputSerializer: Serializer { } .joined() + let dynamicProvidersSection = dynamicProviders + .map { (provider: SerializedProvider) in provider.content } + .joined() + let traversalHelpers = (1...maxLevel).map { num in return """ private func parent\(num)(_ component: NeedleFoundation.Scope) -> NeedleFoundation.Scope { @@ -119,8 +124,14 @@ class OutputSerializer: Serializer { // MARK: - Providers + #if !NEEDLE_DYNAMIC + \(providersSection) - + #else + \(dynamicProvidersSection) + + #endif + private func factoryEmptyDependencyProvider(_ component: NeedleFoundation.Scope) -> AnyObject { return EmptyDependencyProvider(component: component) } @@ -130,10 +141,15 @@ class OutputSerializer: Serializer { __DependencyProviderRegistry.instance.registerDependencyProviderFactory(for: componentPath, factory) } + #if !NEEDLE_DYNAMIC + \(registrationHelpers) - + #endif + public func registerProviderFactories() { + #if !NEEDLE_DYNAMIC \(registrationBody) + #endif } """ @@ -142,6 +158,7 @@ class OutputSerializer: Serializer { // MARK: - Private private let providers: [SerializedProvider] + private let dynamicProviders: [SerializedProvider] private let imports: [String] private let headerDocContent: String private let needleVersionHash: String? diff --git a/Generator/Sources/NeedleFramework/Generating/Serializers/Pluginized/PluginExtensionDynamicContentSerializer.swift b/Generator/Sources/NeedleFramework/Generating/Serializers/Pluginized/PluginExtensionDynamicContentSerializer.swift new file mode 100644 index 00000000..e0ee0714 --- /dev/null +++ b/Generator/Sources/NeedleFramework/Generating/Serializers/Pluginized/PluginExtensionDynamicContentSerializer.swift @@ -0,0 +1,57 @@ +// +// 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 Foundation + +/// A serializer that produces the class definitation source code for the +/// plugin extension provider. +class PluginExtensionDynamicContentSerializer: Serializer { + + /// Initializer. + /// + /// - parameter component: The pluginized component for which is associated + /// with this plugin extension + init(component: PluginizedComponent) { + self.component = component + } + + /// Serialize the data model and produce the source code. + /// + /// - returns: The plugin extension class implemention source code. + func serialize() -> String { + let properties = serialize(properties: component.pluginExtension.properties) + + return """ + /// \(component.data.name) plugin extension + extension \(component.data.name): ExtensionRegistration { + public func registerExtensionItems() { + \(properties) + } + } + + """ + } + + func serialize(properties: [Property]) -> String { + return properties.map { property in + return " extensionToName[\\\(component.pluginExtension.name).\(property.name)] = \"\(property.name)-\(property.type)\"" + }.joined(separator: "\n") + } + + // MARK: - Private + + private let component: PluginizedComponent +} From 10f58869da9f3c72bc5d1d0c1a128e9364804c5f Mon Sep 17 00:00:00 2001 From: Rudro Samanta Date: Sun, 28 Aug 2022 22:48:23 -0700 Subject: [PATCH 2/2] Fix header for one file --- .../PluginExtensionDynamicSerializerTask.swift | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Generator/Sources/NeedleFramework/Generating/Pluginized/PluginExtensionDynamicSerializerTask.swift b/Generator/Sources/NeedleFramework/Generating/Pluginized/PluginExtensionDynamicSerializerTask.swift index b740e011..a7a25c20 100644 --- a/Generator/Sources/NeedleFramework/Generating/Pluginized/PluginExtensionDynamicSerializerTask.swift +++ b/Generator/Sources/NeedleFramework/Generating/Pluginized/PluginExtensionDynamicSerializerTask.swift @@ -1,8 +1,17 @@ // -// PluginExtensionDynamicSerializerTask.swift -// +// Copyright (c) 2018. Uber Technologies // -// Created by Rudro Samanta on 7/1/22. +// 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