@@ -53,6 +53,7 @@ import enum TSCBasic.ProcessLockError
53
53
import var TSCBasic. stderrStream
54
54
import class TSCBasic. TerminalController
55
55
import class TSCBasic. ThreadSafeOutputByteStream
56
+ import enum TSCBasic. SystemError
56
57
57
58
import var TSCUtility. verbosity
58
59
@@ -90,18 +91,25 @@ public protocol _SwiftCommand {
90
91
var workspaceDelegateProvider : WorkspaceDelegateProvider { get }
91
92
var workspaceLoaderProvider : WorkspaceLoaderProvider { get }
92
93
func buildSystemProvider( _ swiftCommandState: SwiftCommandState ) throws -> BuildSystemProvider
94
+
95
+ // If a packagePath is specificed, this indicates that the command allows
96
+ // creating the directory if it doesn't exist.
97
+ var createPackagePath : Bool { get }
93
98
}
94
99
95
100
extension _SwiftCommand {
96
101
public var toolWorkspaceConfiguration : ToolWorkspaceConfiguration {
97
102
. init( )
98
103
}
104
+
105
+ public var createPackagePath : Bool {
106
+ return false
107
+ }
99
108
}
100
109
101
110
public protocol SwiftCommand : ParsableCommand , _SwiftCommand {
102
111
func run( _ swiftCommandState: SwiftCommandState ) throws
103
112
}
104
-
105
113
extension SwiftCommand {
106
114
public static var _errorLabel : String { " error " }
107
115
@@ -110,7 +118,8 @@ extension SwiftCommand {
110
118
options: globalOptions,
111
119
toolWorkspaceConfiguration: self . toolWorkspaceConfiguration,
112
120
workspaceDelegateProvider: self . workspaceDelegateProvider,
113
- workspaceLoaderProvider: self . workspaceLoaderProvider
121
+ workspaceLoaderProvider: self . workspaceLoaderProvider,
122
+ createPackagePath: self . createPackagePath
114
123
)
115
124
116
125
// We use this to attempt to catch misuse of the locking APIs since we only release the lock from here.
@@ -151,7 +160,8 @@ extension AsyncSwiftCommand {
151
160
options: globalOptions,
152
161
toolWorkspaceConfiguration: self . toolWorkspaceConfiguration,
153
162
workspaceDelegateProvider: self . workspaceDelegateProvider,
154
- workspaceLoaderProvider: self . workspaceLoaderProvider
163
+ workspaceLoaderProvider: self . workspaceLoaderProvider,
164
+ createPackagePath: self . createPackagePath
155
165
)
156
166
157
167
// We use this to attempt to catch misuse of the locking APIs since we only release the lock from here.
@@ -283,7 +293,8 @@ public final class SwiftCommandState {
283
293
options: GlobalOptions ,
284
294
toolWorkspaceConfiguration: ToolWorkspaceConfiguration = . init( ) ,
285
295
workspaceDelegateProvider: @escaping WorkspaceDelegateProvider ,
286
- workspaceLoaderProvider: @escaping WorkspaceLoaderProvider
296
+ workspaceLoaderProvider: @escaping WorkspaceLoaderProvider ,
297
+ createPackagePath: Bool
287
298
) throws {
288
299
// output from background activities goes to stderr, this includes diagnostics and output from build operations,
289
300
// package resolution that take place as part of another action
@@ -295,7 +306,8 @@ public final class SwiftCommandState {
295
306
options: options,
296
307
toolWorkspaceConfiguration: toolWorkspaceConfiguration,
297
308
workspaceDelegateProvider: workspaceDelegateProvider,
298
- workspaceLoaderProvider: workspaceLoaderProvider
309
+ workspaceLoaderProvider: workspaceLoaderProvider,
310
+ createPackagePath: createPackagePath
299
311
)
300
312
}
301
313
@@ -306,6 +318,7 @@ public final class SwiftCommandState {
306
318
toolWorkspaceConfiguration: ToolWorkspaceConfiguration ,
307
319
workspaceDelegateProvider: @escaping WorkspaceDelegateProvider ,
308
320
workspaceLoaderProvider: @escaping WorkspaceLoaderProvider ,
321
+ createPackagePath: Bool ,
309
322
hostTriple: Basics . Triple ? = nil ,
310
323
fileSystem: any FileSystem = localFileSystem,
311
324
environment: Environment = . current
@@ -341,19 +354,20 @@ public final class SwiftCommandState {
341
354
self . options = options
342
355
343
356
// Honor package-path option is provided.
344
- if let packagePath = options. locations. packageDirectory {
345
- try ProcessEnv . chdir ( packagePath)
346
- }
347
-
348
- if toolWorkspaceConfiguration. shouldInstallSignalHandlers {
349
- cancellator. installSignalHandlers ( )
350
- }
351
- self . cancellator = cancellator
357
+ try Self . chdirIfNeeded (
358
+ packageDirectory: self . options. locations. packageDirectory,
359
+ createPackagePath: createPackagePath
360
+ )
352
361
} catch {
353
362
self . observabilityScope. emit ( error)
354
363
throw ExitCode . failure
355
364
}
356
365
366
+ if toolWorkspaceConfiguration. shouldInstallSignalHandlers {
367
+ cancellator. installSignalHandlers ( )
368
+ }
369
+ self . cancellator = cancellator
370
+
357
371
// Create local variables to use while finding build path to avoid capture self before init error.
358
372
let packageRoot = findPackageRoot ( fileSystem: fileSystem)
359
373
@@ -529,6 +543,23 @@ public final class SwiftCommandState {
529
543
return ( identities, targets)
530
544
}
531
545
546
+ private static func chdirIfNeeded( packageDirectory: AbsolutePath ? , createPackagePath: Bool ) throws {
547
+ if let packagePath = packageDirectory {
548
+ do {
549
+ try ProcessEnv . chdir ( packagePath)
550
+ } catch let SystemError . chdir( errorCode, path) {
551
+ // If the command allows for the directory at the package path
552
+ // to not be present then attempt to create it and chdir again.
553
+ if createPackagePath {
554
+ try makeDirectories ( packagePath)
555
+ try ProcessEnv . chdir ( packagePath)
556
+ } else {
557
+ throw SystemError . chdir ( errorCode, path)
558
+ }
559
+ }
560
+ }
561
+ }
562
+
532
563
private func getEditsDirectory( ) throws -> AbsolutePath {
533
564
// TODO: replace multiroot-data-file with explicit overrides
534
565
if let multiRootPackageDataFile = options. locations. multirootPackageDataFile {
0 commit comments