Skip to content

When adding a macro target to a package, create a dependency on swift-syntax #7476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Sources/Commands/PackageCommands/AddTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ extension SwiftPackageCommand {

let editResult = try PackageModelSyntax.AddTarget.addTarget(
target,
to: manifestSyntax
to: manifestSyntax,
installedSwiftPMConfiguration: swiftCommandState
.getHostToolchain()
.installedSwiftPMConfiguration
)

try editResult.applyEdits(
Expand Down
20 changes: 17 additions & 3 deletions Sources/PackageModelSyntax/AddPackageDependency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,26 @@ public struct AddPackageDependency {
throw ManifestEditError.cannotFindPackage
}

let edits = try packageCall.appendingToArrayArgument(
let newPackageCall = try addPackageDependencyLocal(
dependency, to: packageCall
)

return PackageEditResult(
manifestEdits: [
.replace(packageCall, with: newPackageCall.description)
]
)
}

/// Implementation of adding a package dependency to an existing call.
static func addPackageDependencyLocal(
_ dependency: PackageDependency,
to packageCall: FunctionCallExprSyntax
) throws -> FunctionCallExprSyntax {
try packageCall.appendingToArrayArgument(
label: "dependencies",
trailingLabels: Self.argumentLabelsAfterDependencies,
newElement: dependency.asSyntax()
)

return PackageEditResult(manifestEdits: edits)
}
}
113 changes: 93 additions & 20 deletions Sources/PackageModelSyntax/AddTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import PackageModel
import SwiftParser
import SwiftSyntax
import SwiftSyntaxBuilder
import struct TSCUtility.Version

/// Add a target to a manifest's source code.
public struct AddTarget {
Expand All @@ -34,7 +35,8 @@ public struct AddTarget {
/// new target.
public static func addTarget(
_ target: TargetDescription,
to manifest: SourceFileSyntax
to manifest: SourceFileSyntax,
installedSwiftPMConfiguration: InstalledSwiftPMConfiguration = .default
) throws -> PackageEditResult {
// Make sure we have a suitable tools version in the manifest.
try manifest.checkEditManifestToolsVersion()
Expand All @@ -53,7 +55,7 @@ public struct AddTarget {
target.dependencies.append(contentsOf: macroTargetDependencies)
}

let manifestEdits = try packageCall.appendingToArrayArgument(
var newPackageCall = try packageCall.appendingToArrayArgument(
label: "targets",
trailingLabels: Self.argumentLabelsAfterTargets,
newElement: target.asSyntax()
Expand All @@ -66,7 +68,11 @@ public struct AddTarget {
}

guard let outerDirectory else {
return PackageEditResult(manifestEdits: manifestEdits)
return PackageEditResult(
manifestEdits: [
.replace(packageCall, with: newPackageCall.description)
]
)
}

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

// Perform any other actions that are needed for this target type.
var extraManifestEdits: [SourceEdit] = []
switch target.type {
case .macro:
// Macros need a file that introduces the main entrypoint
// describing all of the macros.
auxiliaryFiles.addSourceFile(
path: outerPath.appending(
components: [target.name, "ProvidedMacros.swift"]
),
sourceCode: """
import SwiftCompilerPlugin

@main
struct \(raw: target.name)Macros: CompilerPlugin {
let providingMacros: [Macro.Type] = [
\(raw: target.name).self,
]
}
"""
addProvidedMacrosSourceFile(
outerPath: outerPath,
target: target,
to: &auxiliaryFiles
)

if !manifest.description.contains("swift-syntax") {
newPackageCall = try AddPackageDependency
.addPackageDependencyLocal(
.swiftSyntax(
configuration: installedSwiftPMConfiguration
),
to: newPackageCall
)

// Look for the first import declaration and insert an
// import of `CompilerPluginSupport` there.
let newImport = "import CompilerPluginSupport\n"
for node in manifest.statements {
if let importDecl = node.item.as(ImportDeclSyntax.self) {
let insertPos = importDecl
.positionAfterSkippingLeadingTrivia
extraManifestEdits.append(
SourceEdit(
range: insertPos..<insertPos,
replacement: newImport
)
)
break
}
}
}

default: break;
}

return PackageEditResult(
manifestEdits: manifestEdits,
manifestEdits: [
.replace(packageCall, with: newPackageCall.description)
] + extraManifestEdits,
auxiliaryFiles: auxiliaryFiles
)
}
Expand Down Expand Up @@ -191,6 +215,30 @@ public struct AddTarget {
sourceCode: sourceFileText
)
}

/// Add a file that introduces the main entrypoint and provided macros
/// for a macro target.
fileprivate static func addProvidedMacrosSourceFile(
outerPath: RelativePath,
target: TargetDescription,
to auxiliaryFiles: inout AuxiliaryFiles
) {
auxiliaryFiles.addSourceFile(
path: outerPath.appending(
components: [target.name, "ProvidedMacros.swift"]
),
sourceCode: """
import SwiftCompilerPlugin

@main
struct \(raw: target.name)Macros: CompilerPlugin {
let providingMacros: [Macro.Type] = [
\(raw: target.name).self,
]
}
"""
)
}
}

fileprivate extension TargetDescription.Dependency {
Expand Down Expand Up @@ -225,3 +273,28 @@ fileprivate let macroTargetDependencies: [TargetDescription.Dependency] = [
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
]

/// The package dependency for swift-syntax, for use in macros.
fileprivate extension PackageDependency {
/// Source control URL for the swift-syntax package.
static var swiftSyntaxURL: SourceControlURL {
"https://github.com/apple/swift-syntax.git"
}

/// Package dependency on the swift-syntax package.
static func swiftSyntax(
configuration: InstalledSwiftPMConfiguration
) -> PackageDependency {
let swiftSyntaxVersionDefault = configuration
.swiftSyntaxVersionForMacroTemplate
let swiftSyntaxVersion = Version(swiftSyntaxVersionDefault.description)!

return .sourceControl(
identity: PackageIdentity(url: swiftSyntaxURL),
nameForTargetDependencyResolutionOnly: nil,
location: .remote(swiftSyntaxURL),
requirement: .range(.upToNextMajor(from: swiftSyntaxVersion)),
productFilter: .everything
)
}
}
48 changes: 39 additions & 9 deletions Sources/PackageModelSyntax/SyntaxEditUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ extension FunctionCallExprSyntax {
func findArgument(labeled label: String) -> LabeledExprSyntax? {
arguments.first { $0.label?.text == label }
}

/// Find a call argument index based on its label.
func findArgumentIndex(labeled label: String) -> LabeledExprListSyntax.Index? {
arguments.firstIndex { $0.label?.text == label }
}
}

extension LabeledExprListSyntax {
Expand Down Expand Up @@ -358,6 +363,35 @@ extension Array<LabeledExprSyntax> {
}

// MARK: Utilities for adding arguments into calls.
fileprivate class ReplacingRewriter: SyntaxRewriter {
let childNode: Syntax
let newChildNode: Syntax

init(childNode: Syntax, newChildNode: Syntax) {
self.childNode = childNode
self.newChildNode = newChildNode
super.init()
}

override func visitAny(_ node: Syntax) -> Syntax? {
if node == childNode {
return newChildNode
}

return nil
}
}

fileprivate extension SyntaxProtocol {
/// Replace the given child with a new child node.
func replacingChild(_ childNode: Syntax, with newChildNode: Syntax) -> Self {
return ReplacingRewriter(
childNode: childNode,
newChildNode: newChildNode
).rewrite(self).cast(Self.self)
}
}

extension FunctionCallExprSyntax {
/// Produce source edits that will add the given new element to the
/// array for an argument with the given label (if there is one), or
Expand All @@ -371,12 +405,12 @@ extension FunctionCallExprSyntax {
/// which helps determine where the argument should be inserted if
/// it doesn't exist yet.
/// - newElement: The new element.
/// - Returns: the resulting source edits to make this change.
/// - Returns: the function call after making this change.
func appendingToArrayArgument(
label: String,
trailingLabels: Set<String>,
newElement: ExprSyntax
) throws -> [SourceEdit] {
) throws -> FunctionCallExprSyntax {
// If there is already an argument with this name, append to the array
// literal in there.
if let arg = findArgument(labeled: label) {
Expand All @@ -402,7 +436,8 @@ extension FunctionCallExprSyntax {
element: formattedElement,
outerLeadingTrivia: arg.leadingTrivia
)
return [ .replace(argArray, with: updatedArgArray.description) ]

return replacingChild(Syntax(argArray), with: Syntax(updatedArgArray))
}

// There was no argument, so we need to create one.
Expand Down Expand Up @@ -452,11 +487,6 @@ extension FunctionCallExprSyntax {
)
}

return [
SourceEdit.replace(
arguments,
with: newArguments.description
)
]
return with(\.arguments, newArguments)
}
}
8 changes: 8 additions & 0 deletions Tests/PackageModelSyntaxTests/ManifestEditTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -465,14 +465,22 @@ class ManifestEditTests: XCTestCase {
func testAddMacroTarget() throws {
try assertManifestRefactor("""
// swift-tools-version: 5.5
import PackageDescription

let package = Package(
name: "packages"
)
""",
expectedManifest: """
// swift-tools-version: 5.5
import CompilerPluginSupport
import PackageDescription

let package = Package(
name: "packages",
dependencies: [
.package(url: "https://github.com/apple/swift-syntax.git", from: "600.0.0-latest"),
],
targets: [
.macro(
name: "MyMacro",
Expand Down