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 ) 
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,23 @@ 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  result  =  try await  Subprocess . run ( . path( FilePath ( url. filePath. str) ) ,  arguments:  . init( arguments) ,  environment:  environment. map  {  . custom( . init( $0) )  }  ??  . inherit,  workingDirectory:  ( currentDirectoryURL? . filePath. str) . map  {  FilePath ( $0)  }  ??  nil ,  platformOptions:  platformOptions,  body:  {  execution,  inputWriter,  outputReader,  errorReader in 
94+             try await  inputWriter. finish ( ) 
95+             async  let  stdoutBytes  =  outputReader. collect ( ) . flatMap  {  $0. withUnsafeBytes ( Array . init)  } 
96+             async  let  stderrBytes  =  errorReader. collect ( ) . flatMap  {  $0. withUnsafeBytes ( Array . init)  } 
97+             return  try await  ( stdoutBytes,  stderrBytes) 
98+         } ) 
99+         return  Processes . ExecutionResult ( exitStatus:  . init( result. terminationStatus) ,  stdout:  Data ( result. value. 0 ) ,  stderr:  Data ( result. value. 1 ) ) 
100+         #else 
101+         throw  StubError . error ( " Process spawning is unavailable " ) 
102+         #endif 
103+         #else 
84104        if  #available( macOS 15 ,  iOS 18 ,  tvOS 18 ,  watchOS 11 ,  visionOS 2 ,  * )  { 
85105            // Extend the lifetime of the pipes to avoid file descriptors being closed until the AsyncStream is finished being consumed.
86106            return  try await  withExtendedLifetime ( ( Pipe ( ) ,  Pipe ( ) ) )  {  ( stdoutPipe,  stderrPipe)  in 
@@ -110,9 +130,32 @@ extension Process {
110130                return  Processes . ExecutionResult ( exitStatus:  exitStatus,  stdout:  Data ( output. stdoutData) ,  stderr:  Data ( output. stderrData) ) 
111131            } 
112132        } 
133+         #endif 
113134    } 
114135
115136    public  static  func  getMergedOutput( url:  URL ,  arguments:  [ String ] ,  currentDirectoryURL:  URL ? =  nil ,  environment:  Environment ? =  nil ,  interruptible:  Bool  =  true )  async  throws  ->  ( exitStatus:  Processes . ExitStatus ,  output:  Data )  { 
137+         #if canImport(Subprocess) 
138+         #if !canImport(Darwin) || os(macOS) 
139+         let  ( readEnd,  writeEnd)  =  try FileDescriptor . pipe ( ) 
140+         return  try await  readEnd. closeAfter  { 
141+             // 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).
142+             var  platformOptions  =  PlatformOptions ( ) 
143+             if  interruptible { 
144+                 platformOptions. teardownSequence =  [ . gracefulShutDown( allowedDurationToNextStep:  . seconds( 5 ) ) ] 
145+             } 
146+             let  result  =  try await  Subprocess . run ( . path( FilePath ( url. filePath. str) ) ,  arguments:  . init( arguments) ,  environment:  environment. map  {  . custom( . init( $0) )  }  ??  . inherit,  workingDirectory:  ( currentDirectoryURL? . filePath. str) . map  {  FilePath ( $0)  }  ??  nil ,  platformOptions:  platformOptions,  output:  . fileDescriptor( writeEnd,  closeAfterSpawningProcess:  true ) ,  error:  . fileDescriptor( writeEnd,  closeAfterSpawningProcess:  false ) ,  body:  {  execution in 
147+                 if  #available( macOS 15 ,  iOS 18 ,  tvOS 18 ,  watchOS 11 ,  visionOS 2 ,  * )  { 
148+                     try await  Array ( Data ( DispatchFD ( fileDescriptor:  readEnd) . dataStream ( ) . collect ( ) ) ) 
149+                 }  else  { 
150+                     try await  Array ( Data ( DispatchFD ( fileDescriptor:  readEnd) . _dataStream ( ) . collect ( ) ) ) 
151+                 } 
152+             } ) 
153+             return  ( . init( result. terminationStatus) ,  Data ( result. value) ) 
154+         } 
155+         #else 
156+         throw  StubError . error ( " Process spawning is unavailable " ) 
157+         #endif 
158+         #else 
116159        if  #available( macOS 15 ,  iOS 18 ,  tvOS 18 ,  watchOS 11 ,  visionOS 2 ,  * )  { 
117160            // Extend the lifetime of the pipe to avoid file descriptors being closed until the AsyncStream is finished being consumed.
118161            return  try await  withExtendedLifetime ( Pipe ( ) )  {  pipe in 
@@ -138,6 +181,7 @@ extension Process {
138181                return  ( exitStatus:  exitStatus,  output:  Data ( output) ) 
139182            } 
140183        } 
184+         #endif 
141185    } 
142186
143187    private  static  func  _getOutput< T,  U> ( url:  URL ,  arguments:  [ String ] ,  currentDirectoryURL:  URL ? ,  environment:  Environment ? ,  interruptible:  Bool ,  setup:  ( Process )  ->  T ,  collect:  ( T )  async  throws  ->  U )  async  throws  ->  ( exitStatus:  Processes . ExitStatus ,  output:  U )  { 
@@ -203,9 +247,8 @@ public enum Processes: Sendable {
203247        case  exit( _ code:  Int32 ) 
204248        case  uncaughtSignal( _ signal:  Int32 ) 
205249
206-         public  init ? ( rawValue:  Int32 )  { 
207-             #if os(Windows) 
208-             let  dwExitCode  =  DWORD ( bitPattern:  rawValue) 
250+         #if os(Windows) 
251+         public  init ( dwExitCode:  DWORD )  { 
209252            // Do the same thing as swift-corelibs-foundation (the input value is the GetExitCodeProcess return value)
210253            if  ( dwExitCode &  0xF0000000 )  ==  0x80000000      // HRESULT
211254                || ( dwExitCode &  0xF0000000 )  ==  0xC0000000  // NTSTATUS
@@ -215,6 +258,12 @@ public enum Processes: Sendable {
215258            }  else  { 
216259                self  =  . exit( Int32 ( bitPattern:  UInt32 ( dwExitCode) ) ) 
217260            } 
261+         } 
262+         #endif 
263+ 
264+         public  init ? ( rawValue:  Int32 )  { 
265+             #if os(Windows) 
266+             self  =  . init( dwExitCode:  DWORD ( bitPattern:  rawValue) ) 
218267            #else 
219268            func  WSTOPSIG( _ status:  Int32 )  ->  Int32  { 
220269                return  status >> 8 
@@ -294,6 +343,25 @@ public enum Processes: Sendable {
294343    } 
295344} 
296345
346+ #if canImport(Subprocess) 
347+ extension  Processes . ExitStatus  { 
348+     init ( _ terminationStatus:  TerminationStatus )  { 
349+         switch  terminationStatus { 
350+         case  let  . exited( code) : 
351+             self  =  . exit( numericCast ( code) ) 
352+         case  let  . unhandledException( code) : 
353+             #if os(Windows) 
354+             // Currently swift-subprocess returns the original raw GetExitCodeProcess value as uncaughtSignal for all values other than zero.
355+             // See also: https://github.com/swiftlang/swift-subprocess/issues/114
356+             self  =  . init( dwExitCode:  code) 
357+             #else 
358+             self  =  . uncaughtSignal( code) 
359+             #endif 
360+         } 
361+     } 
362+ } 
363+ #endif 
364+ 
297365extension  Processes . ExitStatus  { 
298366    public  init ( _ process:  Process )  throws  { 
299367        assert ( !process. isRunning) 
0 commit comments