@@ -39,9 +39,11 @@ import Musl
39
39
#endif
40
40
41
41
import func TSCBasic. exec
42
+ import class TSCBasic. FileLock
42
43
import protocol TSCBasic. OutputByteStream
43
44
import class TSCBasic. Process
44
45
import enum TSCBasic. ProcessEnv
46
+ import enum TSCBasic. ProcessLockError
45
47
import var TSCBasic. stderrStream
46
48
import class TSCBasic. TerminalController
47
49
import class TSCBasic. ThreadSafeOutputByteStream
@@ -109,6 +111,7 @@ extension SwiftCommand {
109
111
workspaceLoaderProvider: self . workspaceLoaderProvider
110
112
)
111
113
swiftTool. buildSystemProvider = try buildSystemProvider ( swiftTool)
114
+
112
115
var toolError : Error ? = . none
113
116
do {
114
117
try self . run ( swiftTool)
@@ -118,6 +121,7 @@ extension SwiftCommand {
118
121
} catch {
119
122
toolError = error
120
123
}
124
+ try swiftTool. releaseLockIfNeeded ( )
121
125
122
126
// wait for all observability items to process
123
127
swiftTool. waitForObservabilityEvents ( timeout: . now( ) + 5 )
@@ -396,6 +400,9 @@ public final class SwiftTool {
396
400
return workspace
397
401
}
398
402
403
+ // Before creating the workspace, we need to acquire a lock on the build directory.
404
+ try self . acquireLockIfNeeded ( )
405
+
399
406
if options. resolver. skipDependencyUpdate {
400
407
self . observabilityScope. emit ( warning: " '--skip-update' option is deprecated and will be removed in a future release " )
401
408
}
@@ -866,6 +873,35 @@ public final class SwiftTool {
866
873
case success
867
874
case failure
868
875
}
876
+
877
+ // MARK: - Locking
878
+
879
+ private var workspaceLock : FileLock ?
880
+
881
+ fileprivate func acquireLockIfNeeded( ) throws {
882
+ guard workspaceLock == nil else {
883
+ fatalError ( )
884
+ }
885
+ let workspaceLock = try FileLock . prepareLock ( fileToLock: self . scratchDirectory)
886
+
887
+ // Try a non-blocking lock first so that we can inform the user about an already running SwiftPM.
888
+ do {
889
+ try workspaceLock. lock ( type: . exclusive, blocking: false )
890
+ } catch let ProcessLockError . unableToAquireLock( errno) {
891
+ if errno == EWOULDBLOCK {
892
+ self . outputStream. write ( " Another instance of SwiftPM is already running using ' \( self . scratchDirectory) ', waiting until that process has finished execution... " . utf8)
893
+ self . outputStream. flush ( )
894
+ }
895
+ }
896
+
897
+ let interactive = isatty ( STDOUT_FILENO) == 1
898
+ try workspaceLock. lock ( type: . exclusive, blocking: interactive ? true : false )
899
+ self . workspaceLock = workspaceLock
900
+ }
901
+
902
+ fileprivate func releaseLockIfNeeded( ) {
903
+ workspaceLock? . unlock ( )
904
+ }
869
905
}
870
906
871
907
/// Returns path of the nearest directory containing the manifest file w.r.t
0 commit comments