1111//===----------------------------------------------------------------------===//
1212
1313public  import  Foundation
14- import  SWBLibc
14+ public  import  SWBLibc
15+ import  Synchronization
1516
16- #if os(Windows ) 
17- public   typealias   pid_t   =   Int32 
17+ #if canImport(Subprocess) && (!canImport(Darwin) ||  os(macOS) ) 
18+ import  Subprocess 
1819#endif 
1920
20- #if !canImport(Darwin) 
21- extension  ProcessInfo  { 
22-     public  var  isMacCatalystApp :  Bool  { 
23-         false 
24-     } 
25- } 
21+ #if canImport(System) 
22+ public  import  System
23+ #else 
24+ public  import  SystemPackage
25+ #endif 
26+ 
27+ #if os(Windows) 
28+ public  typealias  pid_t  =  Int32 
2629#endif 
2730
2831#if (!canImport(Foundation.NSTask) || targetEnvironment(macCatalyst)) && canImport(Darwin) 
@@ -64,7 +67,7 @@ public typealias Process = Foundation.Process
6467#endif 
6568
6669extension  Process  { 
67-     public  static  var  hasUnsafeWorkingDirectorySupport :  Bool  { 
70+     fileprivate  static  var  hasUnsafeWorkingDirectorySupport :  Bool  { 
6871        get throws   { 
6972            switch  try ProcessInfo . processInfo. hostOperatingSystem ( )  { 
7073            case  . linux: 
@@ -81,6 +84,30 @@ extension Process {
8184
8285extension  Process  { 
8386    public  static  func  getOutput( url:  URL ,  arguments:  [ String ] ,  currentDirectoryURL:  URL ? =  nil ,  environment:  Environment ? =  nil ,  interruptible:  Bool  =  true )  async  throws  ->  Processes . ExecutionResult  { 
87+         #if canImport(Subprocess) 
88+         #if !canImport(Darwin) || os(macOS) 
89+         var  platformOptions  =  PlatformOptions ( ) 
90+         if  interruptible { 
91+             platformOptions. teardownSequence =  [ . gracefulShutDown( allowedDurationToNextStep:  . seconds( 5 ) ) ] 
92+         } 
93+         let  configuration  =  try Subprocess . Configuration ( 
94+             . path( FilePath ( url. filePath. str) ) , 
95+             arguments:  . init( arguments) , 
96+             environment:  environment. map  {  . custom( . init( $0) )  }  ??  . inherit, 
97+             workingDirectory:  ( currentDirectoryURL? . filePath. str) . map  {  FilePath ( $0)  }  ??  nil , 
98+             platformOptions:  platformOptions
99+         ) 
100+         let  result  =  try await  Subprocess . run ( configuration,  body:  {  execution,  inputWriter,  outputReader,  errorReader in 
101+             async  let  stdoutBytes  =  outputReader. collect ( ) . flatMap  {  $0. withUnsafeBytes ( Array . init)  } 
102+             async  let  stderrBytes  =  errorReader. collect ( ) . flatMap  {  $0. withUnsafeBytes ( Array . init)  } 
103+             try await  inputWriter. finish ( ) 
104+             return  try await  ( stdoutBytes,  stderrBytes) 
105+         } ) 
106+         return  Processes . ExecutionResult ( exitStatus:  . init( result. terminationStatus) ,  stdout:  Data ( result. value. 0 ) ,  stderr:  Data ( result. value. 1 ) ) 
107+         #else 
108+         throw  StubError . error ( " Process spawning is unavailable " ) 
109+         #endif 
110+         #else 
84111        if  #available( macOS 15 ,  iOS 18 ,  tvOS 18 ,  watchOS 11 ,  visionOS 2 ,  * )  { 
85112            let  stdoutPipe  =  Pipe ( ) 
86113            let  stderrPipe  =  Pipe ( ) 
@@ -118,9 +145,39 @@ extension Process {
118145            } 
119146            return  Processes . ExecutionResult ( exitStatus:  exitStatus,  stdout:  Data ( output. stdoutData) ,  stderr:  Data ( output. stderrData) ) 
120147        } 
148+         #endif 
121149    } 
122150
123151    public  static  func  getMergedOutput( url:  URL ,  arguments:  [ String ] ,  currentDirectoryURL:  URL ? =  nil ,  environment:  Environment ? =  nil ,  interruptible:  Bool  =  true )  async  throws  ->  ( exitStatus:  Processes . ExitStatus ,  output:  Data )  { 
152+         #if canImport(Subprocess) 
153+         #if !canImport(Darwin) || os(macOS) 
154+         let  ( readEnd,  writeEnd)  =  try FileDescriptor . pipe ( ) 
155+         return  try await  readEnd. closeAfter  { 
156+             // Direct both stdout and stderr to the same fd. Only set `closeAfterSpawningProcess` on one of the outputs so it isn't double-closed (similarly avoid using closeAfter for the same reason).
157+             var  platformOptions  =  PlatformOptions ( ) 
158+             if  interruptible { 
159+                 platformOptions. teardownSequence =  [ . gracefulShutDown( allowedDurationToNextStep:  . seconds( 5 ) ) ] 
160+             } 
161+             let  configuration  =  try Subprocess . Configuration ( 
162+                 . path( FilePath ( url. filePath. str) ) , 
163+                 arguments:  . init( arguments) , 
164+                 environment:  environment. map  {  . custom( . init( $0) )  }  ??  . inherit, 
165+                 workingDirectory:  ( currentDirectoryURL? . filePath. str) . map  {  FilePath ( $0)  }  ??  nil , 
166+                 platformOptions:  platformOptions
167+             ) 
168+             let  result  =  try await  Subprocess . run ( configuration,  output:  . fileDescriptor( writeEnd,  closeAfterSpawningProcess:  true ) ,  error:  . fileDescriptor( writeEnd,  closeAfterSpawningProcess:  false ) ,  body:  {  execution in 
169+                 if  #available( macOS 15 ,  iOS 18 ,  tvOS 18 ,  watchOS 11 ,  visionOS 2 ,  * )  { 
170+                     try await  Array ( Data ( DispatchFD ( fileDescriptor:  readEnd) . dataStream ( ) . collect ( ) ) ) 
171+                 }  else  { 
172+                     try await  Array ( Data ( DispatchFD ( fileDescriptor:  readEnd) . _dataStream ( ) . collect ( ) ) ) 
173+                 } 
174+             } ) 
175+             return  ( . init( result. terminationStatus) ,  Data ( result. value) ) 
176+         } 
177+         #else 
178+         throw  StubError . error ( " Process spawning is unavailable " ) 
179+         #endif 
180+         #else 
124181        if  #available( macOS 15 ,  iOS 18 ,  tvOS 18 ,  watchOS 11 ,  visionOS 2 ,  * )  { 
125182            let  pipe  =  Pipe ( ) 
126183
@@ -150,6 +207,7 @@ extension Process {
150207            } 
151208            return  ( exitStatus:  exitStatus,  output:  Data ( output) ) 
152209        } 
210+         #endif 
153211    } 
154212
155213    private  static  func  _getOutput< T,  U> ( url:  URL ,  arguments:  [ String ] ,  currentDirectoryURL:  URL ? ,  environment:  Environment ? ,  interruptible:  Bool ,  setup:  ( Process )  ->  T ,  collect:  @Sendable  ( T)   async throws  ->  U )  async  throws  ->  ( exitStatus:  Processes . ExitStatus ,  output:  U )  { 
@@ -215,9 +273,8 @@ public enum Processes: Sendable {
215273        case  exit( _ code:  Int32 ) 
216274        case  uncaughtSignal( _ signal:  Int32 ) 
217275
218-         public  init ? ( rawValue:  Int32 )  { 
219-             #if os(Windows) 
220-             let  dwExitCode  =  DWORD ( bitPattern:  rawValue) 
276+         #if os(Windows) 
277+         public  init ( dwExitCode:  DWORD )  { 
221278            // Do the same thing as swift-corelibs-foundation (the input value is the GetExitCodeProcess return value)
222279            if  ( dwExitCode &  0xF0000000 )  ==  0x80000000      // HRESULT
223280                || ( dwExitCode &  0xF0000000 )  ==  0xC0000000  // NTSTATUS
@@ -227,6 +284,12 @@ public enum Processes: Sendable {
227284            }  else  { 
228285                self  =  . exit( Int32 ( bitPattern:  UInt32 ( dwExitCode) ) ) 
229286            } 
287+         } 
288+         #endif 
289+ 
290+         public  init ? ( rawValue:  Int32 )  { 
291+             #if os(Windows) 
292+             self  =  . init( dwExitCode:  DWORD ( bitPattern:  rawValue) ) 
230293            #else 
231294            func  WSTOPSIG( _ status:  Int32 )  ->  Int32  { 
232295                return  status >> 8 
@@ -306,6 +369,25 @@ public enum Processes: Sendable {
306369    } 
307370} 
308371
372+ #if canImport(Subprocess) && (!canImport(Darwin) || os(macOS)) 
373+ extension  Processes . ExitStatus  { 
374+     init ( _ terminationStatus:  TerminationStatus )  { 
375+         switch  terminationStatus { 
376+         case  let  . exited( code) : 
377+             self  =  . exit( numericCast ( code) ) 
378+         case  let  . unhandledException( code) : 
379+             #if os(Windows) 
380+             // Currently swift-subprocess returns the original raw GetExitCodeProcess value as uncaughtSignal for all values other than zero.
381+             // See also: https://github.com/swiftlang/swift-subprocess/issues/114
382+             self  =  . init( dwExitCode:  code) 
383+             #else 
384+             self  =  . uncaughtSignal( code) 
385+             #endif 
386+         } 
387+     } 
388+ } 
389+ #endif 
390+ 
309391extension  Processes . ExitStatus  { 
310392    public  init ( _ process:  Process )  throws  { 
311393        assert ( !process. isRunning) 
0 commit comments