Skip to content

Introduce manifest editing API for "add target dependency" #7552

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
merged 2 commits into from
May 14, 2024
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
103 changes: 103 additions & 0 deletions Sources/PackageModelSyntax/AddTargetDependency.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Basics
import PackageLoading
import PackageModel
import SwiftParser
import SwiftSyntax
import SwiftSyntaxBuilder

/// Add a target dependency to a manifest's source code.
public struct AddTargetDependency {
/// The set of argument labels that can occur after the "dependencies"
/// argument in the various target initializers.
///
/// TODO: Could we generate this from the the PackageDescription module, so
/// we don't have keep it up-to-date manually?
private static let argumentLabelsAfterDependencies: Set<String> = [
"path",
"exclude",
"sources",
"resources",
"publicHeadersPath",
"packageAccess",
"cSettings",
"cxxSettings",
"swiftSettings",
"linkerSettings",
"plugins",
]

/// Produce the set of source edits needed to add the given target
/// dependency to the given manifest file.
public static func addTargetDependency(
_ dependency: TargetDescription.Dependency,
targetName: String,
to manifest: SourceFileSyntax
) throws -> PackageEditResult {
// Make sure we have a suitable tools version in the manifest.
try manifest.checkEditManifestToolsVersion()

guard let packageCall = manifest.findCall(calleeName: "Package") else {
throw ManifestEditError.cannotFindPackage
}

// Dig out the array of targets.
guard let targetsArgument = packageCall.findArgument(labeled: "targets"),
let targetArray = targetsArgument.expression.findArrayArgument() else {
throw ManifestEditError.cannotFindTargets
}

// Look for a call whose name is a string literal matching the
// requested target name.
func matchesTargetCall(call: FunctionCallExprSyntax) -> Bool {
guard let nameArgument = call.findArgument(labeled: "name") else {
return false
}

guard let stringLiteral = nameArgument.expression.as(StringLiteralExprSyntax.self),
let literalValue = stringLiteral.representedLiteralValue else {
return false
}

return literalValue == targetName
}

guard let targetCall = FunctionCallExprSyntax.findFirst(in: targetArray, matching: matchesTargetCall) else {
throw ManifestEditError.cannotFindTarget(targetName: targetName)
}

let newTargetCall = try addTargetDependencyLocal(
dependency, to: targetCall
)

return PackageEditResult(
manifestEdits: [
.replace(targetCall, with: newTargetCall.description)
]
)
}

/// Implementation of adding a target dependency to an existing call.
static func addTargetDependencyLocal(
_ dependency: TargetDescription.Dependency,
to targetCall: FunctionCallExprSyntax
) throws -> FunctionCallExprSyntax {
try targetCall.appendingToArrayArgument(
label: "dependencies",
trailingLabels: Self.argumentLabelsAfterDependencies,
newElement: dependency.asSyntax()
)
}
}

1 change: 1 addition & 0 deletions Sources/PackageModelSyntax/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ add_library(PackageModelSyntax
AddPackageDependency.swift
AddProduct.swift
AddTarget.swift
AddTargetDependency.swift
ManifestEditError.swift
ManifestSyntaxRepresentable.swift
PackageDependency+Syntax.swift
Expand Down
6 changes: 6 additions & 0 deletions Sources/PackageModelSyntax/ManifestEditError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import SwiftSyntax
/// package manifest programattically.
package enum ManifestEditError: Error {
case cannotFindPackage
case cannotFindTargets
case cannotFindTarget(targetName: String)
case cannotFindArrayLiteralArgument(argumentName: String, node: Syntax)
case oldManifest(ToolsVersion)
}
Expand All @@ -33,6 +35,10 @@ extension ManifestEditError: CustomStringConvertible {
switch self {
case .cannotFindPackage:
"invalid manifest: unable to find 'Package' declaration"
case .cannotFindTargets:
"unable to find package targets in manifest"
case .cannotFindTarget(targetName: let name):
"unable to find target named '\(name)' in package"
case .cannotFindArrayLiteralArgument(argumentName: let name, node: _):
"unable to find array literal for '\(name)' argument"
case .oldManifest(let version):
Expand Down
40 changes: 40 additions & 0 deletions Tests/PackageModelSyntaxTests/ManifestEditTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,46 @@ class ManifestEditTests: XCTestCase {
)
}
}

func testAddTargetDependency() throws {
try assertManifestRefactor("""
// swift-tools-version: 5.5
let package = Package(
name: "packages",
dependencies: [
.package(url: "https://github.com/apple/swift-testing.git", from: "0.8.0"),
],
targets: [
.testTarget(
name: "MyTest"
),
]
)
""",
expectedManifest: """
// swift-tools-version: 5.5
let package = Package(
name: "packages",
dependencies: [
.package(url: "https://github.com/apple/swift-testing.git", from: "0.8.0"),
],
targets: [
.testTarget(
name: "MyTest",
dependencies: [
.product(name: "Testing", package: "swift-testing"),
]
),
]
)
""") { manifest in
try AddTargetDependency.addTargetDependency(
.product(name: "Testing", package: "swift-testing"),
targetName: "MyTest",
to: manifest
)
}
}
}


Expand Down