Skip to content

Commit 0602982

Browse files
DougGregorfurby-tm
authored andcommitted
Introduce manifest editing API for "add target dependency" (swiftlang#7552)
As a convenience for when we want to be able to add new target dependencies, introduce a manifest editing API that adds a new target dependency to an existing target in the manifest.
1 parent f2290e2 commit 0602982

File tree

4 files changed

+150
-0
lines changed

4 files changed

+150
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Basics
14+
import PackageLoading
15+
import PackageModel
16+
import SwiftParser
17+
import SwiftSyntax
18+
import SwiftSyntaxBuilder
19+
20+
/// Add a target dependency to a manifest's source code.
21+
public struct AddTargetDependency {
22+
/// The set of argument labels that can occur after the "dependencies"
23+
/// argument in the various target initializers.
24+
///
25+
/// TODO: Could we generate this from the the PackageDescription module, so
26+
/// we don't have keep it up-to-date manually?
27+
private static let argumentLabelsAfterDependencies: Set<String> = [
28+
"path",
29+
"exclude",
30+
"sources",
31+
"resources",
32+
"publicHeadersPath",
33+
"packageAccess",
34+
"cSettings",
35+
"cxxSettings",
36+
"swiftSettings",
37+
"linkerSettings",
38+
"plugins",
39+
]
40+
41+
/// Produce the set of source edits needed to add the given target
42+
/// dependency to the given manifest file.
43+
public static func addTargetDependency(
44+
_ dependency: TargetDescription.Dependency,
45+
targetName: String,
46+
to manifest: SourceFileSyntax
47+
) throws -> PackageEditResult {
48+
// Make sure we have a suitable tools version in the manifest.
49+
try manifest.checkEditManifestToolsVersion()
50+
51+
guard let packageCall = manifest.findCall(calleeName: "Package") else {
52+
throw ManifestEditError.cannotFindPackage
53+
}
54+
55+
// Dig out the array of targets.
56+
guard let targetsArgument = packageCall.findArgument(labeled: "targets"),
57+
let targetArray = targetsArgument.expression.findArrayArgument() else {
58+
throw ManifestEditError.cannotFindTargets
59+
}
60+
61+
// Look for a call whose name is a string literal matching the
62+
// requested target name.
63+
func matchesTargetCall(call: FunctionCallExprSyntax) -> Bool {
64+
guard let nameArgument = call.findArgument(labeled: "name") else {
65+
return false
66+
}
67+
68+
guard let stringLiteral = nameArgument.expression.as(StringLiteralExprSyntax.self),
69+
let literalValue = stringLiteral.representedLiteralValue else {
70+
return false
71+
}
72+
73+
return literalValue == targetName
74+
}
75+
76+
guard let targetCall = FunctionCallExprSyntax.findFirst(in: targetArray, matching: matchesTargetCall) else {
77+
throw ManifestEditError.cannotFindTarget(targetName: targetName)
78+
}
79+
80+
let newTargetCall = try addTargetDependencyLocal(
81+
dependency, to: targetCall
82+
)
83+
84+
return PackageEditResult(
85+
manifestEdits: [
86+
.replace(targetCall, with: newTargetCall.description)
87+
]
88+
)
89+
}
90+
91+
/// Implementation of adding a target dependency to an existing call.
92+
static func addTargetDependencyLocal(
93+
_ dependency: TargetDescription.Dependency,
94+
to targetCall: FunctionCallExprSyntax
95+
) throws -> FunctionCallExprSyntax {
96+
try targetCall.appendingToArrayArgument(
97+
label: "dependencies",
98+
trailingLabels: Self.argumentLabelsAfterDependencies,
99+
newElement: dependency.asSyntax()
100+
)
101+
}
102+
}
103+

Sources/PackageModelSyntax/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_library(PackageModelSyntax
1010
AddPackageDependency.swift
1111
AddProduct.swift
1212
AddTarget.swift
13+
AddTargetDependency.swift
1314
ManifestEditError.swift
1415
ManifestSyntaxRepresentable.swift
1516
PackageDependency+Syntax.swift

Sources/PackageModelSyntax/ManifestEditError.swift

+6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import SwiftSyntax
1818
/// package manifest programattically.
1919
package enum ManifestEditError: Error {
2020
case cannotFindPackage
21+
case cannotFindTargets
22+
case cannotFindTarget(targetName: String)
2123
case cannotFindArrayLiteralArgument(argumentName: String, node: Syntax)
2224
case oldManifest(ToolsVersion)
2325
}
@@ -33,6 +35,10 @@ extension ManifestEditError: CustomStringConvertible {
3335
switch self {
3436
case .cannotFindPackage:
3537
"invalid manifest: unable to find 'Package' declaration"
38+
case .cannotFindTargets:
39+
"unable to find package targets in manifest"
40+
case .cannotFindTarget(targetName: let name):
41+
"unable to find target named '\(name)' in package"
3642
case .cannotFindArrayLiteralArgument(argumentName: let name, node: _):
3743
"unable to find array literal for '\(name)' argument"
3844
case .oldManifest(let version):

Tests/PackageModelSyntaxTests/ManifestEditTests.swift

+40
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,46 @@ class ManifestEditTests: XCTestCase {
611611
)
612612
}
613613
}
614+
615+
func testAddTargetDependency() throws {
616+
try assertManifestRefactor("""
617+
// swift-tools-version: 5.5
618+
let package = Package(
619+
name: "packages",
620+
dependencies: [
621+
.package(url: "https://github.com/apple/swift-testing.git", from: "0.8.0"),
622+
],
623+
targets: [
624+
.testTarget(
625+
name: "MyTest"
626+
),
627+
]
628+
)
629+
""",
630+
expectedManifest: """
631+
// swift-tools-version: 5.5
632+
let package = Package(
633+
name: "packages",
634+
dependencies: [
635+
.package(url: "https://github.com/apple/swift-testing.git", from: "0.8.0"),
636+
],
637+
targets: [
638+
.testTarget(
639+
name: "MyTest",
640+
dependencies: [
641+
.product(name: "Testing", package: "swift-testing"),
642+
]
643+
),
644+
]
645+
)
646+
""") { manifest in
647+
try AddTargetDependency.addTargetDependency(
648+
.product(name: "Testing", package: "swift-testing"),
649+
targetName: "MyTest",
650+
to: manifest
651+
)
652+
}
653+
}
614654
}
615655

616656

0 commit comments

Comments
 (0)