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,40 @@ 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+             // FIXME: Use new API from https://github.com/swiftlang/swift-subprocess/pull/180
169+             let  result  =  try await  Subprocess . run ( configuration,  output:  . fileDescriptor( writeEnd,  closeAfterSpawningProcess:  true ) ,  error:  . fileDescriptor( writeEnd,  closeAfterSpawningProcess:  false ) ,  body:  {  execution in 
170+                 if  #available( macOS 15 ,  iOS 18 ,  tvOS 18 ,  watchOS 11 ,  visionOS 2 ,  * )  { 
171+                     try await  Array ( Data ( DispatchFD ( fileDescriptor:  readEnd) . dataStream ( ) . collect ( ) ) ) 
172+                 }  else  { 
173+                     try await  Array ( Data ( DispatchFD ( fileDescriptor:  readEnd) . _dataStream ( ) . collect ( ) ) ) 
174+                 } 
175+             } ) 
176+             return  ( . init( result. terminationStatus) ,  Data ( result. value) ) 
177+         } 
178+         #else 
179+         throw  StubError . error ( " Process spawning is unavailable " ) 
180+         #endif 
181+         #else 
124182        if  #available( macOS 15 ,  iOS 18 ,  tvOS 18 ,  watchOS 11 ,  visionOS 2 ,  * )  { 
125183            let  pipe  =  Pipe ( ) 
126184
@@ -150,6 +208,7 @@ extension Process {
150208            } 
151209            return  ( exitStatus:  exitStatus,  output:  Data ( output) ) 
152210        } 
211+         #endif 
153212    } 
154213
155214    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 )  { 
@@ -221,9 +280,8 @@ public enum Processes: Sendable {
221280        case  exit( _ code:  Int32 ) 
222281        case  uncaughtSignal( _ signal:  Int32 ) 
223282
224-         public  init ? ( rawValue:  Int32 )  { 
225-             #if os(Windows) 
226-             let  dwExitCode  =  DWORD ( bitPattern:  rawValue) 
283+         #if os(Windows) 
284+         public  init ( dwExitCode:  DWORD )  { 
227285            // Do the same thing as swift-corelibs-foundation (the input value is the GetExitCodeProcess return value)
228286            if  ( dwExitCode &  0xF0000000 )  ==  0x80000000      // HRESULT
229287                || ( dwExitCode &  0xF0000000 )  ==  0xC0000000  // NTSTATUS
@@ -233,6 +291,12 @@ public enum Processes: Sendable {
233291            }  else  { 
234292                self  =  . exit( Int32 ( bitPattern:  UInt32 ( dwExitCode) ) ) 
235293            } 
294+         } 
295+         #endif 
296+ 
297+         public  init ? ( rawValue:  Int32 )  { 
298+             #if os(Windows) 
299+             self  =  . init( dwExitCode:  DWORD ( bitPattern:  rawValue) ) 
236300            #else 
237301            func  WSTOPSIG( _ status:  Int32 )  ->  Int32  { 
238302                return  status >> 8 
@@ -312,6 +376,37 @@ public enum Processes: Sendable {
312376    } 
313377} 
314378
379+ #if canImport(Subprocess) && (!canImport(Darwin) || os(macOS)) 
380+ extension  Processes . ExitStatus  { 
381+     init ( _ terminationStatus:  TerminationStatus )  { 
382+         switch  terminationStatus { 
383+         case  let  . exited( code) : 
384+             self  =  . exit( numericCast ( code) ) 
385+         case  let  . unhandledException( code) : 
386+             #if os(Windows) 
387+             // Currently swift-subprocess returns the original raw GetExitCodeProcess value as uncaughtSignal for all values other than zero.
388+             // See also: https://github.com/swiftlang/swift-subprocess/issues/114
389+             self  =  . init( dwExitCode:  code) 
390+             #else 
391+             self  =  . uncaughtSignal( code) 
392+             #endif 
393+         } 
394+     } 
395+ } 
396+ 
397+ extension  [ Subprocess . Environment . Key :  String ]  { 
398+     internal  init ( _ environment:  Environment )  { 
399+         self . init ( ) 
400+         let  sorted  =  environment. sorted  {  $0. key <  $1. key } 
401+         for  (key,  value)  in  sorted { 
402+             if  let  typedKey =  Subprocess . Environment. Key ( rawValue:  key. rawValue)  { 
403+                 self [ typedKey]  =  value
404+             } 
405+         } 
406+     } 
407+ } 
408+ #endif 
409+ 
315410extension  Processes . ExitStatus  { 
316411    public  init ( _ process:  Process )  throws  { 
317412        assert ( !process. isRunning) 
0 commit comments