Skip to content

Commit 50c22b5

Browse files
authored
os: implement os.fd_is_pending/1, os.Process.pipe_read/1, os.Process.is_pending/1 (vlang#19787)
1 parent a92700e commit 50c22b5

File tree

15 files changed

+288
-136
lines changed

15 files changed

+288
-136
lines changed

.github/workflows/cross_ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ jobs:
7676
- name: v_win.c can be compiled and run with -os windows
7777
run: |
7878
./v -cc msvc -os windows -o /tmp/v_win.c cmd/v
79-
x86_64-w64-mingw32-gcc /tmp/v_win.c -std=c99 -w -municode -o v_from_vc.exe
79+
x86_64-w64-mingw32-gcc /tmp/v_win.c -std=c99 -w -municode -o v_from_vc.exe -lws2_32
8080
ls -lart v_from_vc.exe
8181
wine64 ./v_from_vc.exe version
8282

.github/workflows/windows_ci.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- name: Test new v.c
2121
run: |
2222
.\v.exe -o v.c cmd/v
23-
gcc -Werror -municode -w v.c
23+
gcc -Werror -municode -w v.c -lws2_32
2424
- name: Install dependencies
2525
run: |
2626
.\v.exe setup-freetype
@@ -119,7 +119,7 @@ jobs:
119119
- name: Test new v.c
120120
run: |
121121
.\v.exe -o v.c cmd/v
122-
.\thirdparty\tcc\tcc.exe -Werror -w -ladvapi32 -bt10 v.c
122+
.\thirdparty\tcc\tcc.exe -Werror -w -ladvapi32 -lws2_32 -bt10 v.c
123123
- name: Install dependencies
124124
run: |
125125
.\v.exe setup-freetype
@@ -163,7 +163,7 @@ jobs:
163163
# .\v.exe wipe-cache
164164
# .\make.bat -tcc32
165165
# - name: Test new v.c
166-
# run: .\v.exe -o v.c cmd/v && .\thirdparty\tcc\tcc.exe -Werror -g -w -ladvapi32 -bt10 v.c
166+
# run: .\v.exe -o v.c cmd/v && .\thirdparty\tcc\tcc.exe -Werror -g -w -ladvapi32 -lws2_32 -bt10 v.c
167167
# - name: v doctor
168168
# run: ./v doctor
169169
#

Dockerfile.cross

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ LABEL maintainer="Delyan Angelov <delian66@gmail.com>"
44
COPY . .
55
RUN make
66
RUN ./v -os windows -o v.c cmd/v
7-
RUN x86_64-w64-mingw32-gcc v.c -std=c99 -w -municode -o v.exe
7+
RUN x86_64-w64-mingw32-gcc v.c -std=c99 -w -municode -o v.exe -lws2_32
88
RUN file v.exe
99

1010
CMD [ "bash" ]

GNUmakefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ endif
9898

9999
all: latest_vc latest_tcc latest_legacy
100100
ifdef WIN32
101-
$(CC) $(CFLAGS) -std=c99 -municode -w -o v1.exe $(VC)/$(VCFILE) $(LDFLAGS)
101+
$(CC) $(CFLAGS) -std=c99 -municode -w -o v1.exe $(VC)/$(VCFILE) $(LDFLAGS) -lws2_32
102102
v1.exe -no-parallel -o v2.exe $(VFLAGS) cmd/v
103103
v2.exe -o $(VEXE) $(VFLAGS) cmd/v
104104
del v1.exe

cmd/tools/modules/vgit/vgit.v

+4
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,10 @@ pub fn (mut vgit_context VGitContext) compile_oldv_if_needed() {
198198
// after 53ffee1 2020-05-18, gcc builds on windows do need `-municode`
199199
c_flags += '-municode'
200200
}
201+
// after 2023-11-07, windows builds need linking to ws2_32:
202+
if vgit_context.commit_v__ts >= 1699341818 && !vgit_context.cc.contains('msvc') {
203+
c_flags += '-lws2_32'
204+
}
201205
command_for_building_v_from_c_source = '${vgit_context.cc} ${c_flags} -o cv.exe "${vc_source_file_location}" ${c_ldflags}'
202206
command_for_selfbuilding = '.\\cv.exe -o ${vgit_context.vexename} {SOURCE}'
203207
} else {

examples/process/process_stdin_trick.v

-74
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
module main
2+
3+
// This example shows how to communicate with a child process (`bash` in this case), by sending
4+
// commands to its stdin pipe, and reading responses from its stdout and stderr pipes.
5+
// Note, you can use `if p.is_pending(.stdout) {` and `if p.is_pending(.stderr) {`, to check if
6+
// there is available data in the pipes, without having to block in your main loop, if the data
7+
// is missing or just not available yet.
8+
import os
9+
import time
10+
11+
const tmp_folder = os.join_path(os.temp_dir(), 'process_folder')
12+
13+
const max_txt_files = 20
14+
15+
fn exec(cmd string) (string, int, string) {
16+
mut out := []string{}
17+
mut er := []string{}
18+
mut rc := 0
19+
20+
mut p := os.new_process('/bin/bash')
21+
p.set_redirect_stdio()
22+
p.run()
23+
24+
p.stdin_write('echo "START " && sleep 0.1 && ${cmd};\n')
25+
p.stdin_write('ECODE=\$?;\n')
26+
p.stdin_write('sleep 0.1;\n')
27+
p.stdin_write('exit \$ECODE;\n')
28+
29+
// Note, that you can also ensure that `bash` will exit, when the command finishes,
30+
// by closing its stdin pipe. In the above example, that is not needed however, since
31+
// the last `exit` command, will make it quit as well.
32+
// os.fd_close(p.stdio_fd[0])
33+
34+
for p.is_alive() {
35+
if data := p.pipe_read(.stderr) {
36+
eprintln('p.pipe_read .stderr, len: ${data.len:4} | data: `${data#[0..10]}`...')
37+
er << data
38+
}
39+
if data := p.pipe_read(.stdout) {
40+
eprintln('p.pipe_read .stdout, len: ${data.len:4} | data: `${data#[0..10]}`...')
41+
out << data
42+
}
43+
// avoid a busy loop, by sleeping a bit between each iteration
44+
time.sleep(2 * time.millisecond)
45+
}
46+
47+
// the process finished, slurp all the remaining data in the pipes:
48+
out << p.stdout_slurp()
49+
er << p.stderr_slurp()
50+
p.close()
51+
p.wait()
52+
53+
if p.code > 0 {
54+
eprintln('----------------------------------------------------------')
55+
eprintln('COMMAND: ${cmd}')
56+
eprintln('STDOUT:\n${out}')
57+
eprintln('STDERR:\n${er}')
58+
eprintln('----------------------------------------------------------')
59+
rc = 1
60+
}
61+
62+
return out.join(''), rc, er.join('')
63+
}
64+
65+
fn main() {
66+
mut out := ''
67+
mut er := ''
68+
mut ecode := 0
69+
70+
// prepare some files in a temporary folder
71+
defer {
72+
os.rmdir_all(tmp_folder) or {}
73+
}
74+
os.mkdir_all(tmp_folder) or {}
75+
for i in 0 .. max_txt_files {
76+
os.write_file(os.join_path(tmp_folder, '${i}.txt'), '${i}\n${i}\n')!
77+
}
78+
79+
out, ecode, er = exec("find ${os.quoted_path(tmp_folder)} ; sleep 0.1; find ${os.quoted_path(tmp_folder)} ; echo '******'")
80+
assert out.ends_with('******\n')
81+
assert er == ''
82+
83+
out, ecode, er = exec('echo to stdout')
84+
assert out.contains('to stdout')
85+
assert er == ''
86+
87+
out, ecode, er = exec('echo to stderr 1>&2')
88+
assert out.starts_with('START')
89+
assert er.contains('to stderr')
90+
91+
out, ecode, er = exec('ls /sssss')
92+
assert out.starts_with('START')
93+
assert er != ''
94+
assert ecode > 0 // THIS STILL GIVES AN ERROR !
95+
96+
println('test ok stderr & stdout is indeed redirected, ecode: ${ecode}')
97+
}

make.bat

+4-4
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ REM By default, use tcc, since we have it prebuilt:
134134
:tcc_strap
135135
:tcc32_strap
136136
echo ^> Attempting to build "%V_BOOTSTRAP%" (from v_win.c) with "!tcc_exe!"
137-
"!tcc_exe!" -Bthirdparty/tcc -bt10 -g -w -o "%V_BOOTSTRAP%" ./vc/v_win.c -ladvapi32
137+
"!tcc_exe!" -Bthirdparty/tcc -bt10 -g -w -o "%V_BOOTSTRAP%" ./vc/v_win.c -ladvapi32 -lws2_32
138138
if %ERRORLEVEL% NEQ 0 goto :compile_error
139139
echo ^> Compiling "%V_EXE%" with "%V_BOOTSTRAP%"
140140
"%V_BOOTSTRAP%" -keepc -g -showcc -cc "!tcc_exe!" -cflags -Bthirdparty/tcc -o "%V_UPDATED%" cmd/v
@@ -151,7 +151,7 @@ if %ERRORLEVEL% NEQ 0 (
151151
)
152152

153153
echo ^> Attempting to build "%V_BOOTSTRAP%" (from v_win.c) with Clang
154-
clang -std=c99 -municode -g -w -o "%V_BOOTSTRAP%" ./vc/v_win.c -ladvapi32
154+
clang -std=c99 -municode -g -w -o "%V_BOOTSTRAP%" ./vc/v_win.c -ladvapi32 -lws2_32
155155
if %ERRORLEVEL% NEQ 0 (
156156
echo In most cases, compile errors happen because the version of Clang installed is too old
157157
clang --version
@@ -173,7 +173,7 @@ if %ERRORLEVEL% NEQ 0 (
173173
)
174174

175175
echo ^> Attempting to build "%V_BOOTSTRAP%" (from v_win.c) with GCC
176-
gcc -std=c99 -municode -g -w -o "%V_BOOTSTRAP%" ./vc/v_win.c -ladvapi32
176+
gcc -std=c99 -municode -g -w -o "%V_BOOTSTRAP%" ./vc/v_win.c -ladvapi32 -lws2_32
177177
if %ERRORLEVEL% NEQ 0 (
178178
echo In most cases, compile errors happen because the version of GCC installed is too old
179179
gcc --version
@@ -214,7 +214,7 @@ if exist "%InstallDir%/Common7/Tools/vsdevcmd.bat" (
214214
set ObjFile=.v.c.obj
215215

216216
echo ^> Attempting to build "%V_BOOTSTRAP%" (from v_win.c) with MSVC
217-
cl.exe /volatile:ms /Fo%ObjFile% /W0 /MD /D_VBOOTSTRAP "vc/v_win.c" user32.lib kernel32.lib advapi32.lib shell32.lib /link /nologo /out:"%V_BOOTSTRAP%" /incremental:no
217+
cl.exe /volatile:ms /Fo%ObjFile% /W0 /MD /D_VBOOTSTRAP "vc/v_win.c" user32.lib kernel32.lib advapi32.lib shell32.lib ws2_32.lib /link /nologo /out:"%V_BOOTSTRAP%" /incremental:no
218218
if %ERRORLEVEL% NEQ 0 (
219219
echo In some cases, compile errors happen because of the MSVC compiler version
220220
cl.exe

vlib/os/fd.c.v

+50-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
11
module os
22

3-
// file descriptor based operations:
3+
// low level operations with file descriptors/handles
44

5-
// close filedescriptor
5+
$if !windows {
6+
#include <sys/select.h>
7+
}
8+
9+
$if windows {
10+
#include <winsock2.h>
11+
}
12+
13+
#flag windows -lws2_32
14+
15+
// fd_close closes the file descriptor. It returns 0 on success.
616
pub fn fd_close(fd int) int {
717
if fd == -1 {
818
return 0
919
}
1020
return C.close(fd)
1121
}
1222

23+
// fd_write writes the given string to the file descriptor.
24+
// It blocks until all the data in the string is written.
1325
pub fn fd_write(fd int, s string) {
1426
if fd == -1 {
1527
return
@@ -26,7 +38,7 @@ pub fn fd_write(fd int, s string) {
2638
}
2739
}
2840

29-
// read from filedescriptor, block until data
41+
// fd_slurp reads all the remaining data from the file descriptor.
3042
pub fn fd_slurp(fd int) []string {
3143
mut res := []string{}
3244
if fd == -1 {
@@ -42,8 +54,7 @@ pub fn fd_slurp(fd int) []string {
4254
return res
4355
}
4456

45-
// read from filedescriptor, don't block
46-
// return [bytestring,nrbytes]
57+
// fd_read reads data from the file descriptor. It returns the read data, and how many bytes were read.
4758
pub fn fd_read(fd int, maxbytes int) (string, int) {
4859
if fd == -1 {
4960
return '', 0
@@ -59,3 +70,37 @@ pub fn fd_read(fd int, maxbytes int) (string, int) {
5970
return tos(buf, nbytes), nbytes
6071
}
6172
}
73+
74+
[typedef]
75+
pub struct C.fd_set {}
76+
77+
pub struct C.timeval {
78+
tv_sec u64
79+
tv_usec u64
80+
}
81+
82+
fn C.@select(ndfs int, readfds &C.fd_set, writefds &C.fd_set, exceptfds &C.fd_set, timeout &C.timeval) int
83+
84+
// These are C macros, but from the V's point of view, can be treated as C functions:
85+
fn C.FD_ZERO(fdset &C.fd_set)
86+
fn C.FD_SET(fd int, fdset &C.fd_set)
87+
fn C.FD_ISSET(fd int, fdset &C.fd_set) int
88+
89+
// fd_is_pending returns true, when there is pending data, waiting to be read from file descriptor `fd`.
90+
// If the file descriptor is closed, or if reading from it, will block (there is no data), fd_is_pending returns false.
91+
pub fn fd_is_pending(fd int) bool {
92+
read_set := C.fd_set{}
93+
C.FD_ZERO(&read_set)
94+
C.FD_SET(fd, &read_set)
95+
mut ts := C.timeval{
96+
tv_sec: 0
97+
tv_usec: 0
98+
}
99+
res := C.@select(fd + 1, &read_set, C.NULL, C.NULL, &ts)
100+
if res > 0 {
101+
if C.FD_ISSET(fd, &read_set) != 0 {
102+
return true
103+
}
104+
}
105+
return false
106+
}

vlib/os/file_test.v

+3-3
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ fn test_read_bytes_into_newline_binary() {
9393
bw[9] = 0xff
9494
bw[12] = 10 // newline
9595

96-
n0_bytes := bw[0..10]
97-
n1_bytes := bw[10..13]
98-
n2_bytes := bw[13..]
96+
n0_bytes := unsafe { bw[0..10] }
97+
n1_bytes := unsafe { bw[10..13] }
98+
n2_bytes := unsafe { bw[13..] }
9999

100100
mut f := os.open_file(tfile, 'w')!
101101
f.write(bw)!

0 commit comments

Comments
 (0)