1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
|
//===----------------------------------------------------------------------===//
//
// 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 package dependency to a manifest's source code.
public enum AddPackageDependency {
/// The set of argument labels that can occur after the "dependencies"
/// argument in the Package 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> = [
"targets",
"swiftLanguageVersions",
"cLanguageStandard",
"cxxLanguageStandard",
]
/// Produce the set of source edits needed to add the given package
/// dependency to the given manifest file.
public static func addPackageDependency(
_ dependency: MappablePackageDependency.Kind,
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
}
guard try !dependencyAlreadyAdded(
dependency,
in: packageCall
) else {
return PackageEditResult(manifestEdits: [])
}
let newPackageCall = try addPackageDependencyLocal(
dependency, to: packageCall
)
return PackageEditResult(
manifestEdits: [
.replace(packageCall, with: newPackageCall.description),
]
)
}
/// Return `true` if the dependency already exists in the manifest, otherwise return `false`.
/// Throws an error if a dependency already exists with the same id or url, but different arguments.
private static func dependencyAlreadyAdded(
_ dependency: MappablePackageDependency.Kind,
in packageCall: FunctionCallExprSyntax
) throws -> Bool {
let dependencySyntax = dependency.asSyntax()
guard let dependenctFnSyntax = dependencySyntax.as(FunctionCallExprSyntax.self) else {
throw ManifestEditError.cannotFindPackage
}
guard let id = dependenctFnSyntax.arguments.first(where: {
$0.label?.text == "url" || $0.label?.text == "id" || $0.label?.text == "path"
}) else {
throw InternalError("Missing id or url argument in dependency syntax")
}
if let existingDependencies = packageCall.findArgument(labeled: "dependencies") {
// If we have an existing dependencies array, we need to check if
if let expr = existingDependencies.expression.as(ArrayExprSyntax.self) {
// Iterate through existing dependencies and look for an argument that matches
// either the `id` or `url` argument of the new dependency.
let existingArgument = expr.elements.first { elem in
if let funcExpr = elem.expression.as(FunctionCallExprSyntax.self) {
return funcExpr.arguments.contains {
$0.trimmedDescription == id.trimmedDescription
}
}
return true
}
if let existingArgument {
let normalizedExistingArgument = existingArgument.detached.with(\.trailingComma, nil)
// This exact dependency already exists, return false to indicate we should do nothing.
if normalizedExistingArgument.trimmedDescription == dependencySyntax.trimmedDescription {
return true
}
throw ManifestEditError.existingDependency(dependencyName: dependency.identifier)
}
}
}
return false
}
/// Implementation of adding a package dependency to an existing call.
static func addPackageDependencyLocal(
_ dependency: MappablePackageDependency.Kind,
to packageCall: FunctionCallExprSyntax
) throws -> FunctionCallExprSyntax {
try packageCall.appendingToArrayArgument(
label: "dependencies",
trailingLabels: self.argumentLabelsAfterDependencies,
newElement: dependency.asSyntax()
)
}
}
fileprivate extension MappablePackageDependency.Kind {
var identifier: String {
switch self {
case .sourceControl(let name, let path, _):
return name ?? path
case .fileSystem(let name, let location):
return name ?? location
case .registry(let id, _):
return id
}
}
}
|