Skip to content

Commit

Permalink
Setup plugin structure
Browse files Browse the repository at this point in the history
  • Loading branch information
dfed committed Dec 3, 2023
1 parent d018fdf commit 2b4f264
Show file tree
Hide file tree
Showing 10 changed files with 441 additions and 33 deletions.
36 changes: 36 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
{
"pins" : [
{
"identity" : "jjliso8601dateformatter",
"kind" : "remoteSourceControl",
"location" : "https://github.com/michaeleisel/JJLISO8601DateFormatter",
"state" : {
"revision" : "de422afd9a47b72703c30a81423c478337191390",
"version" : "0.1.6"
}
},
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser.git",
"state" : {
"revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531",
"version" : "1.2.3"
}
},
{
"identity" : "swift-macro-testing",
"kind" : "remoteSourceControl",
Expand All @@ -26,6 +44,24 @@
"revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036",
"version" : "509.0.2"
}
},
{
"identity" : "zippyjson",
"kind" : "remoteSourceControl",
"location" : "https://github.com/michaeleisel/ZippyJSON.git",
"state" : {
"revision" : "c4ab804780b64979f19268619dfa563b6be58f7d",
"version" : "1.2.10"
}
},
{
"identity" : "zippyjsoncfamily",
"kind" : "remoteSourceControl",
"location" : "https://github.com/michaeleisel/ZippyJSONCFamily",
"state" : {
"revision" : "8abdd7a5e943afe68e7b03fdaa63b21c042a3893",
"version" : "1.2.9"
}
}
],
"version" : 2
Expand Down
49 changes: 44 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,34 @@ let package = Package(
.macCatalyst(.v13)
],
products: [
/// A library containing SafeDI macros, property wrappers, and types.
.library(
name: "SafeDI",
targets: ["SafeDI"]
),
/// A SafeDI plugin that must be run on non-root source modules in a project.
.plugin(
name: "SafeDICollectInstantiables",
targets: ["SafeDICollectInstantiables"]
),
/// A SafeDI plugin that must be run on the root source module in a project.
.plugin(
name: "SafeDIGenerateDependencyTree",
targets: ["SafeDIGenerateDependencyTree"]
)
],
dependencies: [
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0"),
.package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.2.0"),
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0"),
.package(url: "https://github.com/michaeleisel/ZippyJSON.git", from: "1.2.0"),
.package(url: "https://github.com/pointfreeco/swift-macro-testing.git", from: "0.2.0"),
],
targets: [
.target(name: "SafeDI", dependencies: ["SafeDIMacros"]),
// Macros
.target(
name: "SafeDI",
dependencies: ["SafeDIMacros"]
),
.testTarget(
name: "SafeDITests",
dependencies: [
Expand Down Expand Up @@ -52,6 +69,30 @@ let package = Package(
.product(name: "MacroTesting", package: "swift-macro-testing"),
]
),

// Plugins
.plugin(
name: "SafeDICollectInstantiables",
capability: .buildTool(),
dependencies: ["SafeDIPlugin"]
),
.plugin(
name: "SafeDIGenerateDependencyTree",
capability: .buildTool(),
dependencies: ["SafeDIPlugin"]
),
.executableTarget(
name: "SafeDIPlugin",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftParser", package: "swift-syntax"),
"ZippyJSON",
"SafeDICore",
]
),

// Core
.target(
name: "SafeDICore",
dependencies: [
Expand All @@ -62,9 +103,7 @@ let package = Package(
),
.testTarget(
name: "SafeDICoreTests",
dependencies: [
"SafeDICore"
]
dependencies: ["SafeDICore"]
),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Foundation
import PackagePlugin

@main
struct SafeDICollectInstantiables: BuildToolPlugin {
func createBuildCommands(
context: PluginContext,
target: Target)
async throws -> [Command]
{
guard let sourceTarget = target as? SourceModuleTarget else {
return []
}

let outputSafeDIFile = context.pluginWorkDirectory.appending(subpath: "\(sourceTarget.moduleName).safedi")
let inputSwiftFiles = sourceTarget.sourceFiles(withSuffix: ".swift").map(\.path)
let arguments = inputSwiftFiles
.map(\.string)
.compactMap { $0.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) }
+ [
"--instantiables-output",
outputSafeDIFile
.string
.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)
].compactMap { $0 }

return [
.buildCommand(
displayName: "SafeDIPlugin",
executable: try context.tool(named: "SafeDIPlugin").path,
arguments: arguments,
environment: [:],
inputFiles: inputSwiftFiles,
outputFiles: [outputSafeDIFile])
]
}
}

#if canImport(XcodeProjectPlugin)
import XcodeProjectPlugin

extension SafeDICollectInstantiables: XcodeBuildToolPlugin {
func createBuildCommands(
context: XcodeProjectPlugin.XcodePluginContext,
target: XcodeProjectPlugin.XcodeTarget)
throws -> [PackagePlugin.Command]
{
[] // showstopper TODO: Support Xcode project plugin!
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import Foundation
import PackagePlugin

@main
struct SafeDIGenerateDependencyTree: BuildToolPlugin {
func createBuildCommands(
context: PluginContext,
target: Target)
async throws -> [Command]
{
guard let sourceTarget = target as? SourceModuleTarget else {
return []
}

let inputSwiftFiles = sourceTarget.sourceFiles(withSuffix: ".swift").map(\.path)
let outputSwiftFile = context.pluginWorkDirectory.appending(subpath: "SafeDI.swift")
let targetDependencySafeDIOutputFiles = sourceTarget
.sourceModuleRecursiveDependencies
.map {
context
.pluginWorkDirectory
.removingLastComponent() // Remove `SafeDIGenerateDependencyTree` from path.
.removingLastComponent() // Remove current module name from path.
.appending([
$0.name, // Dependency module name.
"SafeDICollectInstantiables", // SafeDICollectInstantiables working directory
"\($0.name).safedi" // SafeDICollectInstantiables output file.
])
}
.filter { FileManager.default.fileExists(atPath: $0.string) }

let arguments = inputSwiftFiles
.map(\.string)
.compactMap { $0.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) }
+ ["--instantiables-paths"]
+ targetDependencySafeDIOutputFiles
.map(\.string)
.compactMap { $0.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) }
+ [
"--dependency-tree-output",
outputSwiftFile
.string
.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)
].compactMap { $0 }

return [
.buildCommand(
displayName: "SafeDIGenerateDependencyTree",
executable: try context.tool(named: "GenerateDependencyTree").path,
arguments: arguments,
environment: [:],
inputFiles: inputSwiftFiles + targetDependencySafeDIOutputFiles,
outputFiles: [outputSwiftFile])
]
}
}

#if canImport(XcodeProjectPlugin)
import XcodeProjectPlugin

extension SafeDIGenerateDependencyTree: XcodeBuildToolPlugin {
func createBuildCommands(
context: XcodeProjectPlugin.XcodePluginContext,
target: XcodeProjectPlugin.XcodeTarget)
throws -> [PackagePlugin.Command]
{
[] // showstopper TODO: Support Xcode project plugin!
}
}
#endif

extension Target {

var sourceModuleRecursiveDependencies: [SourceModuleTarget] {
recursiveTargetDependencies.compactMap {
$0 as? SourceModuleTarget
}
}

}
38 changes: 38 additions & 0 deletions Sources/SafeDICore/Generators/DependencyTreeGenerator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Distributed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

public final class DependencyTreeGenerator {

// MARK: Initialization

public init(typeDescriptionToFulfillingInstantiable: [TypeDescription : Instantiable]) {
self.typeDescriptionToFulfillingInstantiable = typeDescriptionToFulfillingInstantiable
}

// MARK: Public

public func generate() async throws -> String {
"" // TODO: actually generate the type
}

// MARK: Private

private let typeDescriptionToFulfillingInstantiable: [TypeDescription : Instantiable]
}
3 changes: 3 additions & 0 deletions Sources/SafeDICore/Models/Dependency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import SwiftSyntax
/// A representation of a dependency.
/// e.g. `@Singleton let mySingleton: MySingleton`
public struct Dependency: Codable, Hashable {

// MARK: Public

public let property: Property
public let source: Source

Expand Down
10 changes: 5 additions & 5 deletions Sources/SafeDICore/Models/Instantiable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

struct Instantiable: Codable, Hashable {
public struct Instantiable: Codable, Hashable {

// MARK: Initialization

init(
public init(
instantiableType: TypeDescription,
additionalInstantiableTypes: [TypeDescription]?,
dependencies: [Dependency])
Expand All @@ -34,11 +34,11 @@ struct Instantiable: Codable, Hashable {
// MARK: Public

/// The types that can be fulfilled with this Instantiable.
let instantiableTypes: [TypeDescription]
public let instantiableTypes: [TypeDescription]
/// The concrete type that fulfills `instantiableTypes`.
var concreteInstantiableType: TypeDescription {
public var concreteInstantiableType: TypeDescription {
instantiableTypes[0]
}
/// The ordered dependencies of this Instantiable.
let dependencies: [Dependency]
public let dependencies: [Dependency]
}
Loading

0 comments on commit 2b4f264

Please sign in to comment.