Skip to content

Commit

Permalink
New Needle dynamic codepath
Browse files Browse the repository at this point in the history
- 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 #432
  • Loading branch information
rudro committed Aug 19, 2022
1 parent 7ef56b4 commit 4b349ce
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 14 deletions.
18 changes: 9 additions & 9 deletions Generator/Package.swift
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -15,13 +15,13 @@ let swiftSyntaxVersion: Version = "0.50200.0"
#endif

var needleDependencies: Array<Target.Dependency> = [
"SwiftToolsSupport-auto",
"Concurrency",
"SourceParsingFramework",
"SwiftSyntax",
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
.product(name: "Concurrency", package: "swift-concurrency"),
.product(name: "SourceParsingFramework", package: "swift-common"),
.product(name: "SwiftSyntax", package: "swift-syntax"),
]
#if swift(>=5.6)
needleDependencies.append("SwiftSyntaxParser")
needleDependencies.append(.product(name: "SwiftSyntaxParser", package: "swift-syntax"))
#endif

let package = Package(
Expand All @@ -47,11 +47,11 @@ let package = Package(
exclude: [
"Fixtures",
]),
.target(
.executableTarget(
name: "needle",
dependencies: [
"NeedleFramework",
"CommandFramework",
.product(name: "CommandFramework", package: "swift-common"),
]),
],
swiftLanguageVersions: [.v5]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<SerializedProvider> {

/// 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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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]
}
Original file line number Diff line number Diff line change
@@ -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
}

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand All @@ -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
}
"""
Expand All @@ -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?
Expand Down
Loading

0 comments on commit 4b349ce

Please sign in to comment.