Skip to content

Commit 3ff45aa

Browse files
committed
Implement add-target support for macro targets
For macro targets, we need to add two new files: one with a macro definition, and another with the list of provided macros for the macro plugin. Reshuffle some code to make that easier.
1 parent 974128f commit 3ff45aa

File tree

3 files changed

+159
-9
lines changed

3 files changed

+159
-9
lines changed

Sources/PackageModel/Manifest/TargetDescription.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public struct TargetDescription: Hashable, Encodable, Sendable {
9292
}
9393

9494
/// The declared target dependencies.
95-
public let dependencies: [Dependency]
95+
public package(set) var dependencies: [Dependency]
9696

9797
/// The custom public headers path.
9898
public let publicHeadersPath: String?

Sources/PackageModelSyntax/AddTarget.swift

Lines changed: 102 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,24 +43,84 @@ public struct AddTarget {
4343
throw ManifestEditError.cannotFindPackage
4444
}
4545

46+
// Create a mutable version of target to which we can add more
47+
// content when needed.
48+
var target = target
49+
50+
// Macro targets need to depend on a couple of libraries from
51+
// SwiftSyntax.
52+
if target.type == .macro {
53+
target.dependencies.append(contentsOf: macroTargetDependencies)
54+
}
55+
4656
let manifestEdits = try packageCall.appendingToArrayArgument(
4757
label: "targets",
4858
trailingLabels: Self.argumentLabelsAfterTargets,
4959
newElement: target.asSyntax()
5060
)
5161

5262
let outerDirectory: String? = switch target.type {
53-
case .binary, .macro, .plugin, .system: nil
54-
case .executable, .regular: "Sources"
63+
case .binary, .plugin, .system: nil
64+
case .executable, .regular, .macro: "Sources"
5565
case .test: "Tests"
5666
}
5767

5868
guard let outerDirectory else {
5969
return PackageEditResult(manifestEdits: manifestEdits)
6070
}
6171

62-
let sourceFilePath = try RelativePath(validating: outerDirectory)
63-
.appending(components: [target.name, "\(target.name).swift"])
72+
let outerPath = try RelativePath(validating: outerDirectory)
73+
74+
/// The set of auxiliary files this refactoring will create.
75+
var auxiliaryFiles: AuxiliaryFiles = []
76+
77+
// Add the primary source file. Every target type has this.
78+
addPrimarySourceFile(
79+
outerPath: outerPath,
80+
target: target,
81+
to: &auxiliaryFiles
82+
)
83+
84+
// Perform any other actions that are needed for this target type.
85+
switch target.type {
86+
case .macro:
87+
// Macros need a file that introduces the main entrypoint
88+
// describing all of the macros.
89+
auxiliaryFiles.addSourceFile(
90+
path: outerPath.appending(
91+
components: [target.name, "ProvidedMacros.swift"]
92+
),
93+
sourceCode: """
94+
import SwiftCompilerPlugin
95+
96+
@main
97+
struct \(raw: target.name)Macros: CompilerPlugin {
98+
let providingMacros: [Macro.Type] = [
99+
\(raw: target.name).self,
100+
]
101+
}
102+
"""
103+
)
104+
105+
default: break;
106+
}
107+
108+
return PackageEditResult(
109+
manifestEdits: manifestEdits,
110+
auxiliaryFiles: auxiliaryFiles
111+
)
112+
}
113+
114+
/// Add the primary source file for a target to the list of auxiliary
115+
/// source files.
116+
fileprivate static func addPrimarySourceFile(
117+
outerPath: RelativePath,
118+
target: TargetDescription,
119+
to auxiliaryFiles: inout AuxiliaryFiles
120+
) {
121+
let sourceFilePath = outerPath.appending(
122+
components: [target.name, "\(target.name).swift"]
123+
)
64124

65125
// Introduce imports for each of the dependencies that were specified.
66126
var importModuleNames = target.dependencies.map {
@@ -83,9 +143,22 @@ public struct AddTarget {
83143
}
84144

85145
let sourceFileText: SourceFileSyntax = switch target.type {
86-
case .binary, .macro, .plugin, .system:
146+
case .binary, .plugin, .system:
87147
fatalError("should have exited above")
88148

149+
case .macro:
150+
"""
151+
\(imports)
152+
struct \(raw: target.name): Macro {
153+
/// TODO: Implement one or more of the protocols that inherit
154+
/// from Macro. The appropriate macro protocol is determined
155+
/// by the "macro" declaration that \(raw: target.name) implements.
156+
/// Examples include:
157+
/// @freestanding(expression) macro --> ExpressionMacro
158+
/// @attached(member) macro --> MemberMacro
159+
}
160+
"""
161+
89162
case .test:
90163
"""
91164
\(imports)
@@ -113,9 +186,9 @@ public struct AddTarget {
113186
"""
114187
}
115188

116-
return PackageEditResult(
117-
manifestEdits: manifestEdits,
118-
auxiliaryFiles: [(sourceFilePath, sourceFileText)]
189+
auxiliaryFiles.addSourceFile(
190+
path: sourceFilePath,
191+
sourceCode: sourceFileText
119192
)
120193
}
121194
}
@@ -131,3 +204,24 @@ fileprivate extension TargetDescription.Dependency {
131204
}
132205
}
133206
}
207+
208+
/// The array of auxiliary files that can be added by a package editing
209+
/// operation.
210+
fileprivate typealias AuxiliaryFiles = [(RelativePath, SourceFileSyntax)]
211+
212+
fileprivate extension AuxiliaryFiles {
213+
/// Add a source file to the list of auxiliary files.
214+
mutating func addSourceFile(
215+
path: RelativePath,
216+
sourceCode: SourceFileSyntax
217+
) {
218+
self.append((path, sourceCode))
219+
}
220+
}
221+
222+
/// The set of dependencies we need to introduce to a newly-created macro
223+
/// target.
224+
fileprivate let macroTargetDependencies: [TargetDescription.Dependency] = [
225+
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
226+
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
227+
]

Tests/PackageModelSyntaxTests/ManifestEditTests.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,62 @@ class ManifestEditTests: XCTestCase {
461461
)
462462
}
463463
}
464+
465+
func testAddMacroTarget() throws {
466+
try assertManifestRefactor("""
467+
// swift-tools-version: 5.5
468+
let package = Package(
469+
name: "packages"
470+
)
471+
""",
472+
expectedManifest: """
473+
// swift-tools-version: 5.5
474+
let package = Package(
475+
name: "packages",
476+
targets: [
477+
.macro(
478+
name: "MyMacro",
479+
dependencies: [
480+
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
481+
.product(name: "SwiftSyntaxMacros", package: "swift-syntax")
482+
]
483+
),
484+
]
485+
)
486+
""",
487+
expectedAuxiliarySources: [
488+
RelativePath("Sources/MyMacro/MyMacro.swift") : """
489+
import SwiftCompilerPlugin
490+
import SwiftSyntaxMacros
491+
492+
struct MyMacro: Macro {
493+
/// TODO: Implement one or more of the protocols that inherit
494+
/// from Macro. The appropriate macro protocol is determined
495+
/// by the "macro" declaration that MyMacro implements.
496+
/// Examples include:
497+
/// @freestanding(expression) macro --> ExpressionMacro
498+
/// @attached(member) macro --> MemberMacro
499+
}
500+
""",
501+
RelativePath("Sources/MyMacro/ProvidedMacros.swift") : """
502+
import SwiftCompilerPlugin
503+
504+
@main
505+
struct MyMacroMacros: CompilerPlugin {
506+
let providingMacros: [Macro.Type] = [
507+
MyMacro.self,
508+
]
509+
}
510+
"""
511+
]
512+
) { manifest in
513+
try AddTarget.addTarget(
514+
TargetDescription(name: "MyMacro", type: .macro),
515+
to: manifest
516+
)
517+
}
518+
}
519+
464520
}
465521

466522

0 commit comments

Comments
 (0)