@@ -16,17 +16,17 @@ import CoreCommands
16
16
import Foundation
17
17
import PackageGraph
18
18
import PackageModel
19
- import PackageModelSyntax
20
19
import SwiftParser
20
+ @_spi ( PackageRefactor) import SwiftRefactor
21
21
import SwiftSyntax
22
22
import TSCBasic
23
23
import TSCUtility
24
24
import Workspace
25
25
26
- extension AddTarget . TestHarness : ExpressibleByArgument { }
26
+ extension AddPackageTarget . TestHarness : @ retroactive ExpressibleByArgument { }
27
27
28
28
extension SwiftPackageCommand {
29
- struct AddTarget : SwiftCommand {
29
+ struct AddTarget : AsyncSwiftCommand {
30
30
/// The type of target that can be specified on the command line.
31
31
enum TargetType : String , Codable , ExpressibleByArgument , CaseIterable {
32
32
case library
@@ -63,9 +63,9 @@ extension SwiftPackageCommand {
63
63
var checksum : String ?
64
64
65
65
@Option ( help: " The testing library to use when generating test targets, which can be one of 'xctest', 'swift-testing', or 'none'. " )
66
- var testingLibrary : PackageModelSyntax . AddTarget . TestHarness = . default
66
+ var testingLibrary : AddPackageTarget . TestHarness = . default
67
67
68
- func run( _ swiftCommandState: SwiftCommandState ) throws {
68
+ func run( _ swiftCommandState: SwiftCommandState ) async throws {
69
69
let workspace = try swiftCommandState. getActiveWorkspace ( )
70
70
71
71
guard let packagePath = try swiftCommandState. getWorkspaceRoot ( ) . packages. first else {
@@ -92,43 +92,39 @@ extension SwiftPackageCommand {
92
92
}
93
93
94
94
// Move sources into their own folder if they're directly in `./Sources`.
95
- try PackageModelSyntax . AddTarget. moveSingleTargetSources (
95
+ try await moveSingleTargetSources (
96
+ workspace: workspace,
96
97
packagePath: packagePath,
97
- manifest: manifestSyntax,
98
- fileSystem: fileSystem,
99
- verbose: !globalOptions. logging. quiet
98
+ verbose: !globalOptions. logging. quiet,
99
+ observabilityScope: swiftCommandState. observabilityScope
100
100
)
101
101
102
102
// Map the target type.
103
- let type : TargetDescription . TargetKind = switch self . type {
104
- case . library: . regular
103
+ let type : PackageTarget . TargetKind = switch self . type {
104
+ case . library: . library
105
105
case . executable: . executable
106
106
case . test: . test
107
107
case . macro: . macro
108
108
}
109
109
110
110
// Map dependencies
111
- let dependencies : [ TargetDescription . Dependency ] =
112
- self . dependencies. map {
113
- . byName( name: $0, condition: nil )
114
- }
115
-
116
- let target = try TargetDescription (
117
- name: name,
118
- dependencies: dependencies,
119
- path: path,
120
- url: url,
121
- type: type,
122
- checksum: checksum
123
- )
111
+ let dependencies : [ PackageTarget . Dependency ] = self . dependencies. map {
112
+ . byName( name: $0)
113
+ }
124
114
125
- let editResult = try PackageModelSyntax . AddTarget. addTarget (
126
- target,
127
- to: manifestSyntax,
128
- configuration: . init( testHarness: testingLibrary) ,
129
- installedSwiftPMConfiguration: swiftCommandState
130
- . getHostToolchain ( )
131
- . installedSwiftPMConfiguration
115
+ let editResult = try AddPackageTarget . manifestRefactor (
116
+ syntax: manifestSyntax,
117
+ in: . init(
118
+ target: . init(
119
+ name: name,
120
+ type: type,
121
+ dependencies: dependencies,
122
+ path: path,
123
+ url: url,
124
+ checksum: checksum
125
+ ) ,
126
+ testHarness: testingLibrary
127
+ )
132
128
)
133
129
134
130
try editResult. applyEdits (
@@ -138,6 +134,57 @@ extension SwiftPackageCommand {
138
134
verbose: !globalOptions. logging. quiet
139
135
)
140
136
}
137
+
138
+ // Check if the package has a single target with that target's sources located
139
+ // directly in `./Sources`. If so, move the sources into a folder named after
140
+ // the target before adding a new target.
141
+ private func moveSingleTargetSources(
142
+ workspace: Workspace ,
143
+ packagePath: Basics . AbsolutePath ,
144
+ verbose: Bool = false ,
145
+ observabilityScope: ObservabilityScope
146
+ ) async throws {
147
+ let manifest = try await workspace. loadRootManifest (
148
+ at: packagePath,
149
+ observabilityScope: observabilityScope
150
+ )
151
+
152
+ guard let target = manifest. targets. first, manifest. targets. count == 1 else {
153
+ return
154
+ }
155
+
156
+ let sourcesFolder = packagePath. appending ( " Sources " )
157
+ let expectedTargetFolder = sourcesFolder. appending ( target. name)
158
+
159
+ let fileSystem = workspace. fileSystem
160
+ // If there is one target then pull its name out and use that to look for a folder in `Sources/TargetName`.
161
+ // If the folder doesn't exist then we know we have a single target package and we need to migrate files
162
+ // into this folder before we can add a new target.
163
+ if !fileSystem. isDirectory ( expectedTargetFolder) {
164
+ if verbose {
165
+ print (
166
+ """
167
+ Moving existing files from \(
168
+ sourcesFolder. relative ( to: packagePath)
169
+ ) to \(
170
+ expectedTargetFolder. relative ( to: packagePath)
171
+ ) ...
172
+ """ ,
173
+ terminator: " "
174
+ )
175
+ }
176
+ let contentsToMove = try fileSystem. getDirectoryContents ( sourcesFolder)
177
+ try fileSystem. createDirectory ( expectedTargetFolder)
178
+ for file in contentsToMove {
179
+ let source = sourcesFolder. appending ( file)
180
+ let destination = expectedTargetFolder. appending ( file)
181
+ try fileSystem. move ( from: source, to: destination)
182
+ }
183
+ if verbose {
184
+ print ( " done. " )
185
+ }
186
+ }
187
+ }
141
188
}
142
189
}
143
190
0 commit comments