Skip to content

Commit 470b863

Browse files
DougGregorrintarocompnerd
authored
6.0: [SE-0301] Implement package manifest editing command-line options (#7494)
* **Explanation**: Implement package manifest editing commands (`swift package add-dependency`, `swift package add-target`, `swift package add-product`) described in [SE-0301](https://github.com/apple/swift-evolution/blob/main/proposals/0301-package-editing-commands.md), using swift-syntax under the hood to perform the edits. * **Original PR**: #7467, #7476, #7477 * **Risk**: Very low. All new code for new commands. * **Reviewed by**: @ahoppen , @bnbarham , @owenv , @MaxDesiatov * **Testing**: New tests. --------- Co-authored-by: Rintaro Ishizaki <[email protected]> Co-authored-by: Saleem Abdulrasool <[email protected]>
1 parent 25ad105 commit 470b863

29 files changed

+2723
-15
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
include(FetchContent)
2+
3+
set(BUILD_SHARED_LIBS OFF)
4+
5+
if(DEFINED SWIFTPM_PATH_TO_SWIFT_SYNTAX_SOURCE)
6+
file(TO_CMAKE_PATH "${SWIFTPM_PATH_TO_SWIFT_SYNTAX_SOURCE}" swift_syntax_path)
7+
FetchContent_Declare(SwiftSyntax
8+
SOURCE_DIR "${swift_syntax_path}")
9+
else()
10+
FetchContent_Declare(SwiftSyntax
11+
GIT_REPOSITORY https://github.com/apple/swift-syntax
12+
GIT_TAG main)
13+
endif()
14+
FetchContent_MakeAvailable(SwiftSyntax)

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,6 @@ find_package(SQLite3 REQUIRED)
5454
# Enable `package` modifier for the whole package.
5555
add_compile_options("$<$<COMPILE_LANGUAGE:Swift>:-package-name;SwiftPM>")
5656

57+
add_subdirectory(BuildSupport/SwiftSyntax)
5758
add_subdirectory(Sources)
5859
add_subdirectory(cmake/modules)

Package.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ let swiftPMDataModelProduct = (
4141
"PackageLoading",
4242
"PackageMetadata",
4343
"PackageModel",
44+
"PackageModelSyntax",
4445
"SourceControl",
4546
"Workspace",
4647
]
@@ -236,6 +237,23 @@ let package = Package(
236237
swiftSettings: packageModelResourcesSettings
237238
),
238239

240+
.target(
241+
/** Primary Package model objects relationship to SwiftSyntax */
242+
name: "PackageModelSyntax",
243+
dependencies: [
244+
"Basics",
245+
"PackageLoading",
246+
"PackageModel",
247+
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
248+
.product(name: "SwiftDiagnostics", package: "swift-syntax"),
249+
.product(name: "SwiftIDEUtils", package: "swift-syntax"),
250+
.product(name: "SwiftParser", package: "swift-syntax"),
251+
.product(name: "SwiftSyntax", package: "swift-syntax"),
252+
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
253+
],
254+
exclude: ["CMakeLists.txt"]
255+
),
256+
239257
.target(
240258
/** Package model conventions and loading support */
241259
name: "PackageLoading",
@@ -404,10 +422,12 @@ let package = Package(
404422
dependencies: [
405423
.product(name: "ArgumentParser", package: "swift-argument-parser"),
406424
.product(name: "OrderedCollections", package: "swift-collections"),
425+
.product(name: "SwiftIDEUtils", package: "swift-syntax"),
407426
"Basics",
408427
"Build",
409428
"CoreCommands",
410429
"PackageGraph",
430+
"PackageModelSyntax",
411431
"SourceControl",
412432
"Workspace",
413433
"XCBuildSupport",
@@ -613,6 +633,14 @@ let package = Package(
613633
name: "PackageModelTests",
614634
dependencies: ["PackageModel", "SPMTestSupport"]
615635
),
636+
.testTarget(
637+
name: "PackageModelSyntaxTests",
638+
dependencies: [
639+
"PackageModelSyntax",
640+
"SPMTestSupport",
641+
.product(name: "SwiftIDEUtils", package: "swift-syntax"),
642+
]
643+
),
616644
.testTarget(
617645
name: "PackageGraphTests",
618646
dependencies: ["PackageGraph", "SPMTestSupport"]
@@ -764,6 +792,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {
764792
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "1.2.2")),
765793
.package(url: "https://github.com/apple/swift-driver.git", branch: relatedDependenciesBranch),
766794
.package(url: "https://github.com/apple/swift-crypto.git", .upToNextMinor(from: "3.0.0")),
795+
.package(url: "https://github.com/apple/swift-syntax.git", branch: relatedDependenciesBranch),
767796
.package(url: "https://github.com/apple/swift-system.git", .upToNextMinor(from: "1.1.1")),
768797
.package(url: "https://github.com/apple/swift-collections.git", .upToNextMinor(from: "1.0.1")),
769798
.package(url: "https://github.com/apple/swift-certificates.git", .upToNextMinor(from: "1.0.1")),
@@ -774,6 +803,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {
774803
.package(path: "../swift-argument-parser"),
775804
.package(path: "../swift-driver"),
776805
.package(path: "../swift-crypto"),
806+
.package(path: "../swift-syntax"),
777807
.package(path: "../swift-system"),
778808
.package(path: "../swift-collections"),
779809
.package(path: "../swift-certificates"),

Sources/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ add_subdirectory(PackageFingerprint)
2121
add_subdirectory(PackageGraph)
2222
add_subdirectory(PackageLoading)
2323
add_subdirectory(PackageModel)
24+
add_subdirectory(PackageModelSyntax)
2425
add_subdirectory(PackagePlugin)
2526
add_subdirectory(PackageRegistry)
2627
add_subdirectory(PackageSigning)

Sources/Commands/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
add_library(Commands
10+
PackageCommands/AddDependency.swift
11+
PackageCommands/AddProduct.swift
12+
PackageCommands/AddTarget.swift
1013
PackageCommands/APIDiff.swift
1114
PackageCommands/ArchiveSource.swift
1215
PackageCommands/CompletionCommand.swift
@@ -56,6 +59,7 @@ target_link_libraries(Commands PUBLIC
5659
CoreCommands
5760
LLBuildManifest
5861
PackageGraph
62+
PackageModelSyntax
5963
SourceControl
6064
TSCBasic
6165
TSCUtility
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2014-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 ArgumentParser
14+
import Basics
15+
import CoreCommands
16+
import PackageModel
17+
import PackageModelSyntax
18+
import SwiftParser
19+
import SwiftSyntax
20+
import TSCBasic
21+
import TSCUtility
22+
import Workspace
23+
24+
extension SwiftPackageCommand {
25+
struct AddDependency: SwiftCommand {
26+
package static let configuration = CommandConfiguration(
27+
abstract: "Add a package dependency to the manifest")
28+
29+
@Argument(help: "The URL or directory of the package to add")
30+
var dependency: String
31+
32+
@OptionGroup(visibility: .hidden)
33+
var globalOptions: GlobalOptions
34+
35+
@Option(help: "The exact package version to depend on")
36+
var exact: Version?
37+
38+
@Option(help: "The specific package revision to depend on")
39+
var revision: String?
40+
41+
@Option(help: "The branch of the package to depend on")
42+
var branch: String?
43+
44+
@Option(help: "The package version to depend on (up to the next major version)")
45+
var from: Version?
46+
47+
@Option(help: "The package version to depend on (up to the next minor version)")
48+
var upToNextMinorFrom: Version?
49+
50+
@Option(help: "Specify upper bound on the package version range (exclusive)")
51+
var to: Version?
52+
53+
func run(_ swiftCommandState: SwiftCommandState) throws {
54+
let workspace = try swiftCommandState.getActiveWorkspace()
55+
56+
guard let packagePath = try swiftCommandState.getWorkspaceRoot().packages.first else {
57+
throw StringError("unknown package")
58+
}
59+
60+
// Load the manifest file
61+
let fileSystem = workspace.fileSystem
62+
let manifestPath = packagePath.appending("Package.swift")
63+
let manifestContents: ByteString
64+
do {
65+
manifestContents = try fileSystem.readFileContents(manifestPath)
66+
} catch {
67+
throw StringError("cannot find package manifest in \(manifestPath)")
68+
}
69+
70+
// Parse the manifest.
71+
let manifestSyntax = manifestContents.withData { data in
72+
data.withUnsafeBytes { buffer in
73+
buffer.withMemoryRebound(to: UInt8.self) { buffer in
74+
Parser.parse(source: buffer)
75+
}
76+
}
77+
}
78+
79+
let identity = PackageIdentity(url: .init(dependency))
80+
81+
// Collect all of the possible version requirements.
82+
var requirements: [PackageDependency.SourceControl.Requirement] = []
83+
if let exact {
84+
requirements.append(.exact(exact))
85+
}
86+
87+
if let branch {
88+
requirements.append(.branch(branch))
89+
}
90+
91+
if let revision {
92+
requirements.append(.revision(revision))
93+
}
94+
95+
if let from {
96+
requirements.append(.range(.upToNextMajor(from: from)))
97+
}
98+
99+
if let upToNextMinorFrom {
100+
requirements.append(.range(.upToNextMinor(from: upToNextMinorFrom)))
101+
}
102+
103+
if requirements.count > 1 {
104+
throw StringError("must specify at most one of --exact, --branch, --revision, --from, or --up-to-next-minor-from")
105+
}
106+
107+
guard let firstRequirement = requirements.first else {
108+
throw StringError("must specify one of --exact, --branch, --revision, --from, or --up-to-next-minor-from")
109+
}
110+
111+
let requirement: PackageDependency.SourceControl.Requirement
112+
if case .range(let range) = firstRequirement {
113+
if let to {
114+
requirement = .range(range.lowerBound..<to)
115+
} else {
116+
requirement = .range(range)
117+
}
118+
} else {
119+
requirement = firstRequirement
120+
121+
if to != nil {
122+
throw StringError("--to can only be specified with --from or --up-to-next-minor-from")
123+
}
124+
}
125+
126+
// Figure out the location of the package.
127+
let location: PackageDependency.SourceControl.Location
128+
if let path = try? Basics.AbsolutePath(validating: dependency) {
129+
location = .local(path)
130+
} else {
131+
location = .remote(.init(dependency))
132+
}
133+
134+
let packageDependency: PackageDependency = .sourceControl(
135+
identity: identity,
136+
nameForTargetDependencyResolutionOnly: nil,
137+
location: location,
138+
requirement: requirement,
139+
productFilter: .everything
140+
)
141+
142+
let editResult = try AddPackageDependency.addPackageDependency(
143+
packageDependency,
144+
to: manifestSyntax
145+
)
146+
147+
try editResult.applyEdits(
148+
to: fileSystem,
149+
manifest: manifestSyntax,
150+
manifestPath: manifestPath,
151+
verbose: !globalOptions.logging.quiet
152+
)
153+
}
154+
}
155+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2014-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 ArgumentParser
14+
import Basics
15+
import CoreCommands
16+
import PackageModel
17+
import PackageModelSyntax
18+
import SwiftParser
19+
import SwiftSyntax
20+
import TSCBasic
21+
import TSCUtility
22+
import Workspace
23+
24+
extension SwiftPackageCommand {
25+
struct AddProduct: SwiftCommand {
26+
/// The package product type used for the command-line. This is a
27+
/// subset of `ProductType` that expands out the library types.
28+
enum CommandProductType: String, Codable, ExpressibleByArgument {
29+
case executable
30+
case library
31+
case staticLibrary = "static-library"
32+
case dynamicLibrary = "dynamic-library"
33+
case plugin
34+
}
35+
36+
package static let configuration = CommandConfiguration(
37+
abstract: "Add a new product to the manifest")
38+
39+
@OptionGroup(visibility: .hidden)
40+
var globalOptions: GlobalOptions
41+
42+
@Argument(help: "The name of the new product")
43+
var name: String
44+
45+
@Option(help: "The type of target to add, which can be one of 'executable', 'library', 'static-library', 'dynamic-library', or 'plugin'")
46+
var type: CommandProductType = .library
47+
48+
@Option(
49+
parsing: .upToNextOption,
50+
help: "A list of targets that are part of this product"
51+
)
52+
var targets: [String] = []
53+
54+
@Option(help: "The URL for a remote binary target")
55+
var url: String?
56+
57+
@Option(help: "The path to a local binary target")
58+
var path: String?
59+
60+
@Option(help: "The checksum for a remote binary target")
61+
var checksum: String?
62+
63+
func run(_ swiftCommandState: SwiftCommandState) throws {
64+
let workspace = try swiftCommandState.getActiveWorkspace()
65+
66+
guard let packagePath = try swiftCommandState.getWorkspaceRoot().packages.first else {
67+
throw StringError("unknown package")
68+
}
69+
70+
// Load the manifest file
71+
let fileSystem = workspace.fileSystem
72+
let manifestPath = packagePath.appending("Package.swift")
73+
let manifestContents: ByteString
74+
do {
75+
manifestContents = try fileSystem.readFileContents(manifestPath)
76+
} catch {
77+
throw StringError("cannot find package manifest in \(manifestPath)")
78+
}
79+
80+
// Parse the manifest.
81+
let manifestSyntax = manifestContents.withData { data in
82+
data.withUnsafeBytes { buffer in
83+
buffer.withMemoryRebound(to: UInt8.self) { buffer in
84+
Parser.parse(source: buffer)
85+
}
86+
}
87+
}
88+
89+
// Map the product type.
90+
let type: ProductType = switch self.type {
91+
case .executable: .executable
92+
case .library: .library(.automatic)
93+
case .dynamicLibrary: .library(.dynamic)
94+
case .staticLibrary: .library(.static)
95+
case .plugin: .plugin
96+
}
97+
98+
let product = try ProductDescription(
99+
name: name,
100+
type: type,
101+
targets: targets
102+
)
103+
104+
let editResult = try PackageModelSyntax.AddProduct.addProduct(
105+
product,
106+
to: manifestSyntax
107+
)
108+
109+
try editResult.applyEdits(
110+
to: fileSystem,
111+
manifest: manifestSyntax,
112+
manifestPath: manifestPath,
113+
verbose: !globalOptions.logging.quiet
114+
)
115+
}
116+
}
117+
}
118+

0 commit comments

Comments
 (0)