@@ -5,7 +5,11 @@ import UIKit
55import FlutterMacOS
66#endif
77
8+ import Foundation
9+ import Dispatch
10+ import Darwin
811import Python
12+ import os. log
913
1014public class SeriousPythonPlugin : NSObject , FlutterPlugin {
1115
@@ -110,6 +114,7 @@ public class SeriousPythonPlugin: NSObject, FlutterPlugin {
110114 }
111115
112116 @objc func runPythonFile( appPath: String ) {
117+ SeriousPythonLogForwarder . shared. beginCapturing ( )
113118 Py_Initialize ( )
114119
115120 // run app
@@ -120,9 +125,11 @@ public class SeriousPythonPlugin: NSObject, FlutterPlugin {
120125 }
121126
122127 Py_Finalize ( )
128+ SeriousPythonLogForwarder . shared. endCapturing ( )
123129 }
124130
125131 @objc func runPythonScript( script: String ) {
132+ SeriousPythonLogForwarder . shared. beginCapturing ( )
126133 Py_Initialize ( )
127134
128135 // run app
@@ -132,5 +139,144 @@ public class SeriousPythonPlugin: NSObject, FlutterPlugin {
132139 }
133140
134141 Py_Finalize ( )
142+ SeriousPythonLogForwarder . shared. endCapturing ( )
143+ }
144+ }
145+
146+ private final class SeriousPythonLogForwarder {
147+ static let shared = SeriousPythonLogForwarder ( )
148+
149+ private let lock = NSLock ( )
150+ private var captureCount = 0
151+ private let enableEnvVar = " SERIOUS_PYTHON_FORWARD_STDIO "
152+
153+ private var originalStdout : Int32 = - 1
154+ private var originalStderr : Int32 = - 1
155+ private var pipeReadFD : Int32 = - 1
156+ private var source : DispatchSourceRead ?
157+
158+ private let queue = DispatchQueue ( label: " serious_python.log_forwarder " )
159+ private var buffer = Data ( )
160+ private let log = OSLog ( subsystem: " dev.flet.serious_python " , category: " python " )
161+
162+ func beginCapturing( ) {
163+ lock. lock ( )
164+ defer { lock. unlock ( ) }
165+
166+ if let v = ProcessInfo . processInfo. environment [ enableEnvVar] ? . lowercased ( ) ,
167+ v == " 0 " || v == " false " || v == " no " {
168+ return
169+ }
170+
171+ captureCount += 1
172+ guard captureCount == 1 else { return }
173+
174+ var fds : [ Int32 ] = [ 0 , 0 ]
175+ guard pipe ( & fds) == 0 else {
176+ os_log ( " serious_python: failed to create pipe: errno=%d " , log: log, type: . error, errno)
177+ captureCount = 0
178+ return
179+ }
180+
181+ originalStdout = dup ( STDOUT_FILENO)
182+ originalStderr = dup ( STDERR_FILENO)
183+
184+ fflush ( stdout)
185+ fflush ( stderr)
186+ setbuf ( stdout, nil )
187+ setbuf ( stderr, nil )
188+
189+ _ = dup2 ( fds [ 1 ] , STDOUT_FILENO)
190+ _ = dup2 ( fds [ 1 ] , STDERR_FILENO)
191+ close ( fds [ 1 ] )
192+
193+ pipeReadFD = fds [ 0 ]
194+ _ = fcntl ( pipeReadFD, F_SETFL, O_NONBLOCK)
195+
196+ let src = DispatchSource . makeReadSource ( fileDescriptor: pipeReadFD, queue: queue)
197+ src. setEventHandler { [ weak self] in
198+ self ? . _drain ( )
199+ }
200+ src. setCancelHandler { [ weak self] in
201+ guard let self else { return }
202+ if self . pipeReadFD >= 0 {
203+ close ( self . pipeReadFD)
204+ self . pipeReadFD = - 1
205+ }
206+ }
207+ source = src
208+ src. resume ( )
209+ }
210+
211+ func endCapturing( ) {
212+ lock. lock ( )
213+ defer { lock. unlock ( ) }
214+
215+ guard captureCount > 0 else { return }
216+ captureCount -= 1
217+ guard captureCount == 0 else { return }
218+
219+ fflush ( stdout)
220+ fflush ( stderr)
221+ queue. sync { self . _drain ( ) }
222+
223+ if originalStdout >= 0 {
224+ _ = dup2 ( originalStdout, STDOUT_FILENO)
225+ close ( originalStdout)
226+ originalStdout = - 1
227+ }
228+
229+ if originalStderr >= 0 {
230+ _ = dup2 ( originalStderr, STDERR_FILENO)
231+ close ( originalStderr)
232+ originalStderr = - 1
233+ }
234+
235+ queue. sync { self . _drain ( ) }
236+
237+ source? . cancel ( )
238+ source = nil
239+
240+ if !buffer. isEmpty {
241+ _emit ( String ( decoding: buffer, as: UTF8 . self) )
242+ buffer. removeAll ( keepingCapacity: false )
243+ }
244+ }
245+
246+ private func _drain( ) {
247+ guard pipeReadFD >= 0 else { return }
248+
249+ var chunk = [ UInt8] ( repeating: 0 , count: 4096 )
250+ while true {
251+ let n = read ( pipeReadFD, & chunk, chunk. count)
252+ if n > 0 {
253+ buffer. append ( contentsOf: chunk [ 0 ..< n] )
254+ _flushLines ( )
255+ if buffer. count > 32 * 1024 {
256+ _emit ( String ( decoding: buffer, as: UTF8 . self) )
257+ buffer. removeAll ( keepingCapacity: true )
258+ }
259+ } else if n == 0 {
260+ break
261+ } else {
262+ if errno == EAGAIN || errno == EWOULDBLOCK { break }
263+ os_log ( " serious_python: read() failed: errno=%d " , log: log, type: . error, errno)
264+ break
265+ }
266+ }
267+ }
268+
269+ private func _flushLines( ) {
270+ while let newlineIndex = buffer. firstIndex ( of: 0x0A ) {
271+ let lineData = buffer. subdata ( in: 0 ..< newlineIndex)
272+ buffer. removeSubrange ( 0 ... newlineIndex)
273+ _emit ( String ( decoding: lineData, as: UTF8 . self) )
274+ }
275+ }
276+
277+ private func _emit( _ message: String ) {
278+ let trimmed = message. trimmingCharacters ( in: . newlines)
279+ guard !trimmed. isEmpty else { return }
280+ os_log ( " %{public}@ " , log: log, type: . default, trimmed)
135281 }
136282}
0 commit comments