Skip to content

Commit 2b6df0a

Browse files
committed
When adding a macro target to a package, create a dependency on swift-syntax
When editing a package manifest to add a new macro target, also create a package dependency on swift-syntax and import CompilerPluginSupport. We need both so that the macro will build.
1 parent 47573ce commit 2b6df0a

File tree

4 files changed

+152
-31
lines changed

4 files changed

+152
-31
lines changed

Sources/PackageModelSyntax/AddPackageDependency.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,26 @@ public struct AddPackageDependency {
4444
throw ManifestEditError.cannotFindPackage
4545
}
4646

47-
let edits = try packageCall.appendingToArrayArgument(
47+
let newPackageCall = try addPackageDependencyLocal(
48+
dependency, to: packageCall
49+
)
50+
51+
return PackageEditResult(
52+
manifestEdits: [
53+
.replace(packageCall, with: newPackageCall.description)
54+
]
55+
)
56+
}
57+
58+
/// Implementation of adding a package dependency to an existing call.
59+
static func addPackageDependencyLocal(
60+
_ dependency: PackageDependency,
61+
to packageCall: FunctionCallExprSyntax
62+
) throws -> FunctionCallExprSyntax {
63+
try packageCall.appendingToArrayArgument(
4864
label: "dependencies",
4965
trailingLabels: Self.argumentLabelsAfterDependencies,
5066
newElement: dependency.asSyntax()
5167
)
52-
53-
return PackageEditResult(manifestEdits: edits)
5468
}
5569
}

Sources/PackageModelSyntax/AddTarget.swift

Lines changed: 88 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import PackageModel
1515
import SwiftParser
1616
import SwiftSyntax
1717
import SwiftSyntaxBuilder
18+
import struct TSCUtility.Version
1819

1920
/// Add a target to a manifest's source code.
2021
public struct AddTarget {
@@ -53,7 +54,7 @@ public struct AddTarget {
5354
target.dependencies.append(contentsOf: macroTargetDependencies)
5455
}
5556

56-
let manifestEdits = try packageCall.appendingToArrayArgument(
57+
var newPackageCall = try packageCall.appendingToArrayArgument(
5758
label: "targets",
5859
trailingLabels: Self.argumentLabelsAfterTargets,
5960
newElement: target.asSyntax()
@@ -66,7 +67,11 @@ public struct AddTarget {
6667
}
6768

6869
guard let outerDirectory else {
69-
return PackageEditResult(manifestEdits: manifestEdits)
70+
return PackageEditResult(
71+
manifestEdits: [
72+
.replace(packageCall, with: newPackageCall.description)
73+
]
74+
)
7075
}
7176

7277
let outerPath = try RelativePath(validating: outerDirectory)
@@ -82,31 +87,47 @@ public struct AddTarget {
8287
)
8388

8489
// Perform any other actions that are needed for this target type.
90+
var extraManifestEdits: [SourceEdit] = []
8591
switch target.type {
8692
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-
"""
93+
addProvidedMacrosSourceFile(
94+
outerPath: outerPath,
95+
target: target,
96+
to: &auxiliaryFiles
10397
)
10498

99+
if !manifest.description.contains("swift-syntax") {
100+
newPackageCall = try AddPackageDependency
101+
.addPackageDependencyLocal(
102+
.swiftSyntax,
103+
to: newPackageCall
104+
)
105+
106+
// Look for the first import declaration and insert an
107+
// import of `CompilerPluginSupport` there.
108+
let newImport = "import CompilerPluginSupport\n"
109+
for node in manifest.statements {
110+
if let importDecl = node.item.as(ImportDeclSyntax.self) {
111+
let insertPos = importDecl
112+
.positionAfterSkippingLeadingTrivia
113+
extraManifestEdits.append(
114+
SourceEdit(
115+
range: insertPos..<insertPos,
116+
replacement: newImport
117+
)
118+
)
119+
break
120+
}
121+
}
122+
}
123+
105124
default: break;
106125
}
107126

108127
return PackageEditResult(
109-
manifestEdits: manifestEdits,
128+
manifestEdits: [
129+
.replace(packageCall, with: newPackageCall.description)
130+
] + extraManifestEdits,
110131
auxiliaryFiles: auxiliaryFiles
111132
)
112133
}
@@ -191,6 +212,30 @@ public struct AddTarget {
191212
sourceCode: sourceFileText
192213
)
193214
}
215+
216+
/// Add a file that introduces the main entrypoint and provided macros
217+
/// for a macro target.
218+
fileprivate static func addProvidedMacrosSourceFile(
219+
outerPath: RelativePath,
220+
target: TargetDescription,
221+
to auxiliaryFiles: inout AuxiliaryFiles
222+
) {
223+
auxiliaryFiles.addSourceFile(
224+
path: outerPath.appending(
225+
components: [target.name, "ProvidedMacros.swift"]
226+
),
227+
sourceCode: """
228+
import SwiftCompilerPlugin
229+
230+
@main
231+
struct \(raw: target.name)Macros: CompilerPlugin {
232+
let providingMacros: [Macro.Type] = [
233+
\(raw: target.name).self,
234+
]
235+
}
236+
"""
237+
)
238+
}
194239
}
195240

196241
fileprivate extension TargetDescription.Dependency {
@@ -225,3 +270,27 @@ fileprivate let macroTargetDependencies: [TargetDescription.Dependency] = [
225270
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
226271
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
227272
]
273+
274+
/// The package dependency for swift-syntax, for use in macros.
275+
fileprivate extension PackageDependency {
276+
/// Source control URL for the swift-syntax package.
277+
static var swiftSyntaxURL: SourceControlURL {
278+
"https://github.com/apple/swift-syntax.git"
279+
}
280+
281+
/// Package dependency on the swift-syntax package.
282+
static var swiftSyntax: PackageDependency {
283+
let swiftSyntaxVersionDefault = InstalledSwiftPMConfiguration
284+
.default
285+
.swiftSyntaxVersionForMacroTemplate
286+
let swiftSyntaxVersion = Version(swiftSyntaxVersionDefault.description)!
287+
288+
return .sourceControl(
289+
identity: PackageIdentity(url: swiftSyntaxURL),
290+
nameForTargetDependencyResolutionOnly: nil,
291+
location: .remote(swiftSyntaxURL),
292+
requirement: .range(.upToNextMajor(from: swiftSyntaxVersion)),
293+
productFilter: .everything
294+
)
295+
}
296+
}

Sources/PackageModelSyntax/SyntaxEditUtils.swift

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ extension FunctionCallExprSyntax {
8181
func findArgument(labeled label: String) -> LabeledExprSyntax? {
8282
arguments.first { $0.label?.text == label }
8383
}
84+
85+
/// Find a call argument index based on its label.
86+
func findArgumentIndex(labeled label: String) -> LabeledExprListSyntax.Index? {
87+
arguments.firstIndex { $0.label?.text == label }
88+
}
8489
}
8590

8691
extension LabeledExprListSyntax {
@@ -358,6 +363,35 @@ extension Array<LabeledExprSyntax> {
358363
}
359364

360365
// MARK: Utilities for adding arguments into calls.
366+
fileprivate class ReplacingRewriter: SyntaxRewriter {
367+
let childNode: Syntax
368+
let newChildNode: Syntax
369+
370+
init(childNode: Syntax, newChildNode: Syntax) {
371+
self.childNode = childNode
372+
self.newChildNode = newChildNode
373+
super.init()
374+
}
375+
376+
override func visitAny(_ node: Syntax) -> Syntax? {
377+
if node == childNode {
378+
return newChildNode
379+
}
380+
381+
return nil
382+
}
383+
}
384+
385+
fileprivate extension SyntaxProtocol {
386+
/// Replace the given child with a new child node.
387+
func replacingChild(_ childNode: Syntax, with newChildNode: Syntax) -> Self {
388+
return ReplacingRewriter(
389+
childNode: childNode,
390+
newChildNode: newChildNode
391+
).rewrite(self).cast(Self.self)
392+
}
393+
}
394+
361395
extension FunctionCallExprSyntax {
362396
/// Produce source edits that will add the given new element to the
363397
/// array for an argument with the given label (if there is one), or
@@ -371,12 +405,12 @@ extension FunctionCallExprSyntax {
371405
/// which helps determine where the argument should be inserted if
372406
/// it doesn't exist yet.
373407
/// - newElement: The new element.
374-
/// - Returns: the resulting source edits to make this change.
408+
/// - Returns: the function call after making this change.
375409
func appendingToArrayArgument(
376410
label: String,
377411
trailingLabels: Set<String>,
378412
newElement: ExprSyntax
379-
) throws -> [SourceEdit] {
413+
) throws -> FunctionCallExprSyntax {
380414
// If there is already an argument with this name, append to the array
381415
// literal in there.
382416
if let arg = findArgument(labeled: label) {
@@ -402,7 +436,8 @@ extension FunctionCallExprSyntax {
402436
element: formattedElement,
403437
outerLeadingTrivia: arg.leadingTrivia
404438
)
405-
return [ .replace(argArray, with: updatedArgArray.description) ]
439+
440+
return replacingChild(Syntax(argArray), with: Syntax(updatedArgArray))
406441
}
407442

408443
// There was no argument, so we need to create one.
@@ -452,11 +487,6 @@ extension FunctionCallExprSyntax {
452487
)
453488
}
454489

455-
return [
456-
SourceEdit.replace(
457-
arguments,
458-
with: newArguments.description
459-
)
460-
]
490+
return with(\.arguments, newArguments)
461491
}
462492
}

Tests/PackageModelSyntaxTests/ManifestEditTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,14 +465,22 @@ class ManifestEditTests: XCTestCase {
465465
func testAddMacroTarget() throws {
466466
try assertManifestRefactor("""
467467
// swift-tools-version: 5.5
468+
import PackageDescription
469+
468470
let package = Package(
469471
name: "packages"
470472
)
471473
""",
472474
expectedManifest: """
473475
// swift-tools-version: 5.5
476+
import CompilerPluginSupport
477+
import PackageDescription
478+
474479
let package = Package(
475480
name: "packages",
481+
dependencies: [
482+
.package(url: "https://github.com/apple/swift-syntax.git", from: "600.0.0-latest"),
483+
],
476484
targets: [
477485
.macro(
478486
name: "MyMacro",

0 commit comments

Comments
 (0)