[ new ] popen/pclose for the NodeJS backend. (#2857)

* implement popen and pclose (to an extent) for NodeJS

* bring node020 back into tests.

* ah, I see what was being done here. Fix the idris for the test.

* fix test's unreachable clause warning

* fix expectation

* Add note to CHANGELOG

* small tweaks to get popen into merge-ready state.
This commit is contained in:
Mathew Polzin 2023-01-30 09:38:42 -06:00 committed by GitHub
parent 62811c565c
commit ad12f8335c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 85 additions and 11 deletions

View File

@ -30,6 +30,7 @@
#### Node.js
* Generated JavaScript files now include a shebang when using the Node.js backend
* NodeJS now supports `popen`/`pclose` for the `Read` mode.
### Compiler changes

View File

@ -9,8 +9,10 @@ import public System.File.Types
%foreign "C:fflush,libc 6"
prim__flush : FilePtr -> PrimIO Int
%foreign supportC "idris2_popen"
supportNode "popen"
prim__popen : String -> String -> PrimIO FilePtr
%foreign supportC "idris2_pclose"
supportNode "pclose"
prim__pclose : FilePtr -> PrimIO Int
||| Force a write of all user-space buffered data for the given `File`.
@ -25,6 +27,11 @@ fflush (FHandle f)
||| given command-string using the '-c' flag, in a new process. The pipe is
||| opened with the given mode.
|||
||| IMPORTANT: The NodeJS backend only currently supports the Read mode. Also with
||| the NodeJS backend, the opened process will finish execution before
||| popen returns (it blocks on open) which is different than other
||| backends which will block on close.
|||
||| @ cmd the command to pass to the shell
||| @ m the mode the pipe should have
export

View File

@ -30,7 +30,7 @@ prim__readChar : FilePtr -> PrimIO Int
prim__writeLine : FilePtr -> String -> PrimIO Int
%foreign supportC "idris2_eof"
"node:lambda:x=>(x.eof?1:0)"
"node:lambda:f=>(f.eof?1:0)"
prim__eof : FilePtr -> PrimIO Int
%foreign supportC "idris2_removeFile"

View File

@ -1,5 +1,5 @@
const support_system_file_fs = require('fs')
const support_system_file_child_process = require('child_process')
function support_system_file_fileErrno(){
const n = process.__lasterr===undefined?0:process.__lasterr.errno || 0
@ -62,9 +62,13 @@ function support_system_file_getStr () {
return support_system_file_readLine({ fd: 0, buffer: Buffer.alloc(0), name: '<stdin>', eof: false })
}
function support_system_file_parseMode(mode) {
return mode.replace('b', '')
}
function support_system_file_openFile (n, m) {
try {
const fd = support_system_file_fs.openSync(n, m.replace('b', ''))
const fd = support_system_file_fs.openSync(n, support_system_file_parseMode(m))
return { fd: fd, buffer: Buffer.alloc(0), name: n, eof: false }
} catch (e) {
process.__lasterr = e
@ -91,3 +95,65 @@ function support_system_file_removeFile (filename) {
return 1
}
}
// IMPLEMENTATION NOTE:
// If in the future Idris's NodeJS backend supports executing async code, the
// far superior and more true-to-C way to implement popen/pclose would be to
// spawn in popen (instead of spawnSync) and then in pclose await the processes
// completion.
//
// Note doing the above makes it impossible to support the use-case for popen of
// writing to the child process's stdin between popen and pclose.
function support_system_file_popen (cmd, m) {
const mode = support_system_file_parseMode(m)
if (mode != 'r') {
process.__lasterr = 'The NodeJS popen FFI only supports opening for reading currently.'
return null
}
const tmp_file = require('os').tmpdir() + "/" + require('crypto').randomBytes(15).toString('hex')
const write_fd = support_system_file_fs.openSync(
tmp_file,
'w'
)
var io_setting
switch (mode) {
case "r":
io_setting = ['ignore', write_fd, 2]
break
case "w", "a":
io_setting = [write_fd, 'ignore', 2]
break
default:
process.__lasterr = 'The popen function cannot be used for reading and writing simultaneously.'
return null
}
const { status, error } = support_system_file_child_process.spawnSync(
cmd,
[],
{ stdio: io_setting, shell: true }
)
support_system_file_fs.closeSync(write_fd)
if (error) {
process.__lasterr = error
return null
}
const read_ptr = support_system_file_openFile(
tmp_file,
'r'
)
return { ...read_ptr, exit_code: status }
}
function support_system_file_pclose (file_ptr) {
const { fd, name, exit_code } = file_ptr
support_system_file_fs.closeSync(fd)
support_system_file_removeFile(name)
return exit_code
}

View File

@ -334,10 +334,10 @@ nodeTests : TestPool
nodeTests = MkTestPool "Node backend" [] (Just Node)
[ "node001", "node002", "node003", "node004", "node005", "node006"
, "node007", "node008", "node009", "node011", "node012", "node015"
, "node017", "node018", "node019", "node021", "node022", "node023"
, "node024", "node025", "node026", "node027"
, "node017", "node018", "node019", "node020", "node021", "node022"
, "node023", "node024", "node025", "node026", "node027"
, "perf001"
-- , "node14", "node020"
-- , "node14"
, "args"
, "bitops"
, "casts"

View File

@ -2,6 +2,7 @@ import System
import System.File
import System.Info
import Data.String
import Data.List1
windowsPath : String -> String
windowsPath path =
@ -24,6 +25,5 @@ main = do
| Left err => printLn err
ignore $ pclose fh
putStrLn "closed"
let [idris2, _] = split ((==) ',') output
| _ => printLn "Unexpected result"
let (idris2 ::: _) = split ((==) ',') output
putStrLn idris2

View File

@ -1,5 +1,5 @@
opened
1/1: Building Popen (Popen.idr)
Main> opened
closed
Idris 2
1/1: Building Popen (Popen.idr)
Main> Main> Bye for now!
Main> Bye for now!