Skip to content

Commit d30647a

Browse files
authored
When adding a macro target to a package, create a dependency on swift-syntax (swiftlang#7476)
1 parent bc62af9 commit d30647a

File tree

5 files changed

+161
-33
lines changed

5 files changed

+161
-33
lines changed

Sources/Commands/PackageCommands/AddTarget.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,10 @@ extension SwiftPackageCommand {
113113

114114
let editResult = try PackageModelSyntax.AddTarget.addTarget(
115115
target,
116-
to: manifestSyntax
116+
to: manifestSyntax,
117+
installedSwiftPMConfiguration: swiftCommandState
118+
.getHostToolchain()
119+
.installedSwiftPMConfiguration
117120
)
118121

119122
try editResult.applyEdits(

Sources/PackageModelSyntax/AddPackageDependency.swift

+17-3
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

+93-20
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 {
@@ -34,7 +35,8 @@ public struct AddTarget {
3435
/// new target.
3536
public static func addTarget(
3637
_ target: TargetDescription,
37-
to manifest: SourceFileSyntax
38+
to manifest: SourceFileSyntax,
39+
installedSwiftPMConfiguration: InstalledSwiftPMConfiguration = .default
3840
) throws -> PackageEditResult {
3941
// Make sure we have a suitable tools version in the manifest.
4042
try manifest.checkEditManifestToolsVersion()
@@ -53,7 +55,7 @@ public struct AddTarget {
5355
target.dependencies.append(contentsOf: macroTargetDependencies)
5456
}
5557

56-
let manifestEdits = try packageCall.appendingToArrayArgument(
58+
var newPackageCall = try packageCall.appendingToArrayArgument(
5759
label: "targets",
5860
trailingLabels: Self.argumentLabelsAfterTargets,
5961
newElement: target.asSyntax()
@@ -66,7 +68,11 @@ public struct AddTarget {
6668
}
6769

6870
guard let outerDirectory else {
69-
return PackageEditResult(manifestEdits: manifestEdits)
71+
return PackageEditResult(
72+
manifestEdits: [
73+
.replace(packageCall, with: newPackageCall.description)
74+
]
75+
)
7076
}
7177

7278
let outerPath = try RelativePath(validating: outerDirectory)
@@ -82,31 +88,49 @@ public struct AddTarget {
8288
)
8389

8490
// Perform any other actions that are needed for this target type.
91+
var extraManifestEdits: [SourceEdit] = []
8592
switch target.type {
8693
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-
"""
94+
addProvidedMacrosSourceFile(
95+
outerPath: outerPath,
96+
target: target,
97+
to: &auxiliaryFiles
10398
)
10499

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

108130
return PackageEditResult(
109-
manifestEdits: manifestEdits,
131+
manifestEdits: [
132+
.replace(packageCall, with: newPackageCall.description)
133+
] + extraManifestEdits,
110134
auxiliaryFiles: auxiliaryFiles
111135
)
112136
}
@@ -191,6 +215,30 @@ public struct AddTarget {
191215
sourceCode: sourceFileText
192216
)
193217
}
218+
219+
/// Add a file that introduces the main entrypoint and provided macros
220+
/// for a macro target.
221+
fileprivate static func addProvidedMacrosSourceFile(
222+
outerPath: RelativePath,
223+
target: TargetDescription,
224+
to auxiliaryFiles: inout AuxiliaryFiles
225+
) {
226+
auxiliaryFiles.addSourceFile(
227+
path: outerPath.appending(
228+
components: [target.name, "ProvidedMacros.swift"]
229+
),
230+
sourceCode: """
231+
import SwiftCompilerPlugin
232+
233+
@main
234+
struct \(raw: target.name)Macros: CompilerPlugin {
235+
let providingMacros: [Macro.Type] = [
236+
\(raw: target.name).self,
237+
]
238+
}
239+
"""
240+
)
241+
}
194242
}
195243

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

Sources/PackageModelSyntax/SyntaxEditUtils.swift

+39-9
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

+8
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)