mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
all: support v -watch run
(#9577)
This commit is contained in:
parent
82f3ca2d55
commit
c698fa1a58
288
cmd/tools/vwatch.v
Normal file
288
cmd/tools/vwatch.v
Normal file
@ -0,0 +1,288 @@
|
||||
module main
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
const scan_timeout_s = 5 * 60
|
||||
|
||||
const max_v_cycles = 1000
|
||||
|
||||
const scan_frequency_hz = 4
|
||||
|
||||
const scan_period_ms = 1000 / scan_frequency_hz
|
||||
|
||||
const max_scan_cycles = scan_timeout_s * scan_frequency_hz
|
||||
|
||||
//
|
||||
// Implements `v -watch file.v` , `v -watch run file.v` etc.
|
||||
// With this command, V will collect all .v files that are needed for the
|
||||
// compilation, then it will enter an infinite loop, monitoring them for
|
||||
// changes.
|
||||
//
|
||||
// When a change is detected, it will stop the current process, if it is
|
||||
// still running, then rerun/recompile/etc.
|
||||
//
|
||||
// In effect, this makes it easy to have an editor session and a separate
|
||||
// terminal, running just `v -watch run file.v`, and you will see your
|
||||
// changes right after you save your .v file in your editor.
|
||||
//
|
||||
//
|
||||
// Since -gc boehm is not available on all platforms yet,
|
||||
// and this program leaks ~8MB/minute without it, the implementation here
|
||||
// is done similarly to vfmt in 2 modes, in the same executable:
|
||||
//
|
||||
// a) A parent/manager process that only manages a single worker
|
||||
// process. The parent process does mostly nothing except restarting
|
||||
// workers, thus it does not leak much.
|
||||
//
|
||||
// b) A worker process, doing the actual monitoring/polling.
|
||||
// NB: *workers are started with the -vwatchworker option*
|
||||
//
|
||||
// Worker processes will run for a limited number of iterations, then
|
||||
// they will do exit(255), and then the parent will start a new worker.
|
||||
// Exiting by any other code will cause the parent to also exit with the
|
||||
// same error code. This limits the potential leak that a worker process
|
||||
// can do, even without using the garbage collection mode.
|
||||
//
|
||||
|
||||
struct VFileStat {
|
||||
path string
|
||||
mtime int
|
||||
}
|
||||
|
||||
[unsafe]
|
||||
fn (mut vfs VFileStat) free() {
|
||||
unsafe { vfs.path.free() }
|
||||
}
|
||||
|
||||
enum RerunCommand {
|
||||
restart
|
||||
quit
|
||||
}
|
||||
|
||||
struct Context {
|
||||
mut:
|
||||
pid int // the pid of the current process; useful while debugging manager/worker interactions
|
||||
is_worker bool // true in the workers, false in the manager process
|
||||
check_period_ms int = scan_period_ms
|
||||
vexe string
|
||||
affected_paths []string
|
||||
vfiles []VFileStat
|
||||
opts []string
|
||||
rerun_channel chan RerunCommand
|
||||
child_process &os.Process
|
||||
is_exiting bool // set by SIGINT/Ctrl-C
|
||||
v_cycles int // how many times the worker has restarted the V compiler
|
||||
scan_cycles int // how many times the worker has scanned for source file changes
|
||||
}
|
||||
|
||||
[if debug_vwatch]
|
||||
fn (mut context Context) elog(msg string) {
|
||||
eprintln('> vredo $context.pid, $msg')
|
||||
}
|
||||
|
||||
fn (context &Context) str() string {
|
||||
return 'Context{ pid: $context.pid, is_worker: $context.is_worker, check_period_ms: $context.check_period_ms, vexe: $context.vexe, opts: $context.opts, is_exiting: $context.is_exiting, vfiles: $context.vfiles'
|
||||
}
|
||||
|
||||
fn (mut context Context) get_stats_for_affected_vfiles() []VFileStat {
|
||||
if context.affected_paths.len == 0 {
|
||||
mut apaths := map[string]bool{}
|
||||
// The next command will make V parse the program, and print all .v files,
|
||||
// needed for its compilation, without actually compiling it.
|
||||
copts := context.opts.join(' ')
|
||||
cmd := '"$context.vexe" -silent -print-v-files $copts'
|
||||
// context.elog('> cmd: $cmd')
|
||||
mut vfiles := os.execute(cmd)
|
||||
if vfiles.exit_code == 0 {
|
||||
paths_trimmed := vfiles.output.trim_space()
|
||||
mut paths := paths_trimmed.split('\n')
|
||||
for vf in paths {
|
||||
apaths[os.real_path(os.dir(vf))] = true
|
||||
}
|
||||
}
|
||||
context.affected_paths = apaths.keys()
|
||||
// context.elog('vfiles paths to be scanned: $context.affected_paths')
|
||||
}
|
||||
// scan all files in the found folders
|
||||
mut newstats := []VFileStat{}
|
||||
for path in context.affected_paths {
|
||||
mut files := os.ls(path) or { []string{} }
|
||||
for pf in files {
|
||||
pf_ext := os.file_ext(pf).to_lower()
|
||||
if pf_ext in ['', '.bak', '.exe', '.dll', '.so', '.def'] {
|
||||
continue
|
||||
}
|
||||
if pf.starts_with('.#') {
|
||||
continue
|
||||
}
|
||||
if pf.ends_with('~') {
|
||||
continue
|
||||
}
|
||||
f := os.join_path(path, pf)
|
||||
fullpath := os.real_path(f)
|
||||
mtime := os.file_last_mod_unix(fullpath)
|
||||
newstats << VFileStat{fullpath, mtime}
|
||||
}
|
||||
}
|
||||
return newstats
|
||||
}
|
||||
|
||||
fn (mut context Context) get_changed_vfiles() int {
|
||||
mut changed := 0
|
||||
newfiles := context.get_stats_for_affected_vfiles()
|
||||
for vfs in newfiles {
|
||||
mut found := false
|
||||
for existing_vfs in context.vfiles {
|
||||
if existing_vfs.path == vfs.path {
|
||||
found = true
|
||||
if existing_vfs.mtime != vfs.mtime {
|
||||
context.elog('> new updates for file: $vfs')
|
||||
changed++
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
changed++
|
||||
continue
|
||||
}
|
||||
}
|
||||
context.vfiles = newfiles
|
||||
if changed > 0 {
|
||||
context.elog('> get_changed_vfiles: $changed')
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
fn change_detection_loop(ocontext &Context) {
|
||||
mut context := ocontext
|
||||
for {
|
||||
if context.v_cycles >= max_v_cycles || context.scan_cycles >= max_scan_cycles {
|
||||
context.is_exiting = true
|
||||
context.kill_pgroup()
|
||||
time.sleep(50 * time.millisecond)
|
||||
exit(255)
|
||||
}
|
||||
if context.is_exiting {
|
||||
return
|
||||
}
|
||||
changes := context.get_changed_vfiles()
|
||||
if changes > 0 {
|
||||
context.rerun_channel <- RerunCommand.restart
|
||||
}
|
||||
time.sleep(context.check_period_ms * time.millisecond)
|
||||
context.scan_cycles++
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut context Context) kill_pgroup() {
|
||||
if context.child_process == 0 {
|
||||
return
|
||||
}
|
||||
if context.child_process.is_alive() {
|
||||
context.child_process.signal_pgkill()
|
||||
}
|
||||
context.child_process.wait()
|
||||
}
|
||||
|
||||
fn (mut context Context) compilation_runner_loop() {
|
||||
cmd := '"$context.vexe" ${context.opts.join(' ')}'
|
||||
_ := <-context.rerun_channel
|
||||
for {
|
||||
context.elog('>> loop: v_cycles: $context.v_cycles')
|
||||
timestamp := time.now().format_ss_milli()
|
||||
context.child_process = os.new_process(context.vexe)
|
||||
context.child_process.use_pgroup = true
|
||||
context.child_process.set_args(context.opts)
|
||||
context.child_process.run()
|
||||
eprintln('$timestamp: $cmd | pid: ${context.child_process.pid:7d} | reload cycle: ${context.v_cycles:5d}')
|
||||
for {
|
||||
mut cmds := []RerunCommand{}
|
||||
for {
|
||||
if context.is_exiting {
|
||||
return
|
||||
}
|
||||
if !context.child_process.is_alive() {
|
||||
context.child_process.wait()
|
||||
}
|
||||
select {
|
||||
action := <-context.rerun_channel {
|
||||
cmds << action
|
||||
if action == .quit {
|
||||
context.kill_pgroup()
|
||||
return
|
||||
}
|
||||
}
|
||||
> 100 * time.millisecond {
|
||||
should_restart := RerunCommand.restart in cmds
|
||||
cmds = []
|
||||
if should_restart {
|
||||
// context.elog('>>>>>>>> KILLING $context.child_process.pid')
|
||||
context.kill_pgroup()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !context.child_process.is_alive() {
|
||||
context.child_process.wait()
|
||||
break
|
||||
}
|
||||
}
|
||||
context.v_cycles++
|
||||
}
|
||||
}
|
||||
|
||||
const ccontext = Context{
|
||||
child_process: 0
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut context := &ccontext
|
||||
context.pid = os.getpid()
|
||||
context.vexe = os.getenv('VEXE')
|
||||
context.is_worker = os.args.contains('-vwatchworker')
|
||||
context.opts = os.args[1..].filter(it != '-vwatchworker')
|
||||
context.elog('>>> context.pid: $context.pid')
|
||||
context.elog('>>> context.vexe: $context.vexe')
|
||||
context.elog('>>> context.opts: $context.opts')
|
||||
context.elog('>>> context.is_worker: $context.is_worker')
|
||||
if context.is_worker {
|
||||
context.worker_main()
|
||||
} else {
|
||||
context.manager_main()
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut context Context) manager_main() {
|
||||
myexecutable := os.executable()
|
||||
mut worker_opts := ['-vwatchworker']
|
||||
worker_opts << context.opts
|
||||
for {
|
||||
mut worker_process := os.new_process(myexecutable)
|
||||
worker_process.set_args(worker_opts)
|
||||
worker_process.run()
|
||||
for {
|
||||
if !worker_process.is_alive() {
|
||||
worker_process.wait()
|
||||
break
|
||||
}
|
||||
time.sleep(200 * time.millisecond)
|
||||
}
|
||||
if !(worker_process.code == 255 && worker_process.status == .exited) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut context Context) worker_main() {
|
||||
context.rerun_channel = chan RerunCommand{cap: 10}
|
||||
os.signal(C.SIGINT, fn () {
|
||||
mut context := &ccontext
|
||||
context.is_exiting = true
|
||||
context.kill_pgroup()
|
||||
})
|
||||
go context.compilation_runner_loop()
|
||||
change_detection_loop(context)
|
||||
}
|
@ -8,6 +8,9 @@ Examples:
|
||||
v run hello.v Same as above but also run the produced executable immediately after compilation.
|
||||
v -cg run hello.v Same as above, but make debugging easier (in case your program crashes).
|
||||
v -o h.c hello.v Translate `hello.v` to `h.c`. Do not compile further.
|
||||
|
||||
v -watch hello.v Re-compiles over and over the same compilation, when a source change is detected.
|
||||
v -watch run file.v Re-runs over and over the same file.v, when a source change is detected.
|
||||
|
||||
V supports the following commands:
|
||||
* New project scaffolding:
|
||||
|
@ -71,6 +71,15 @@ fn main() {
|
||||
}
|
||||
args_and_flags := util.join_env_vflags_and_os_args()[1..]
|
||||
prefs, command := pref.parse_args(external_tools, args_and_flags)
|
||||
if prefs.is_watch {
|
||||
util.launch_tool(prefs.is_verbose, 'vwatch', os.args[1..].filter(it != '-watch'))
|
||||
}
|
||||
if prefs.is_verbose {
|
||||
// println('args= ')
|
||||
// println(args) // QTODO
|
||||
// println('prefs= ')
|
||||
// println(prefs) // QTODO
|
||||
}
|
||||
if prefs.use_cache && os.user_os() == 'windows' {
|
||||
eprintln('-usecache is currently disabled on windows')
|
||||
exit(1)
|
||||
|
46
make.bat
46
make.bat
@ -162,15 +162,7 @@ if !flag_local! NEQ 1 (
|
||||
cd ..>>"!log_file!" 2>NUL
|
||||
)
|
||||
popd
|
||||
) || (
|
||||
echo Cloning vc...
|
||||
echo ^> Cloning from remote !vc_url!
|
||||
if !flag_verbose! EQU 1 (
|
||||
echo [Debug] git clone --depth 1 --quiet %vc_url%>>"!log_file!"
|
||||
echo git clone --depth 1 --quiet %vc_url%
|
||||
)
|
||||
git clone --depth 1 --quiet %vc_url%>>"!log_file!" 2>NUL
|
||||
)
|
||||
) || call :cloning_vc
|
||||
echo.
|
||||
)
|
||||
|
||||
@ -178,7 +170,6 @@ echo Building V...
|
||||
if not [!compiler!] == [] goto :!compiler!_strap
|
||||
|
||||
|
||||
|
||||
REM By default, use tcc, since we have it prebuilt:
|
||||
:tcc_strap
|
||||
:tcc32_strap
|
||||
@ -308,8 +299,6 @@ del %ObjFile%>>"!log_file!" 2>>&1
|
||||
if %ERRORLEVEL% NEQ 0 goto :compile_error
|
||||
goto :success
|
||||
|
||||
|
||||
|
||||
:download_tcc
|
||||
pushd %tcc_dir% 2>NUL && (
|
||||
echo Updating TCC
|
||||
@ -320,16 +309,8 @@ pushd %tcc_dir% 2>NUL && (
|
||||
)
|
||||
git pull --quiet>>"!log_file!" 2>NUL
|
||||
popd
|
||||
) || (
|
||||
echo Bootstraping TCC...
|
||||
echo ^> TCC not found
|
||||
if "!tcc_branch!" == "thirdparty-windows-i386" ( echo ^> Downloading TCC32 from !tcc_url! ) else ( echo ^> Downloading TCC64 from !tcc_url! )
|
||||
if !flag_verbose! EQU 1 (
|
||||
echo [Debug] git clone --depth 1 --quiet --single-branch --branch !tcc_branch! !tcc_url! "%tcc_dir%">>"!log_file!"
|
||||
echo git clone --depth 1 --quiet --single-branch --branch !tcc_branch! !tcc_url! "%tcc_dir%"
|
||||
)
|
||||
git clone --depth 1 --quiet --single-branch --branch !tcc_branch! !tcc_url! "%tcc_dir%">>"!log_file!" 2>NUL
|
||||
)
|
||||
) || call :bootstrap_tcc
|
||||
|
||||
for /f "usebackq delims=" %%i in (`dir "%tcc_dir%" /b /a /s tcc.exe`) do (
|
||||
set "attrib=%%~ai"
|
||||
set "dattrib=%attrib:~0,1%"
|
||||
@ -426,6 +407,27 @@ echo file
|
||||
echo --verbose Output compilation commands to stdout
|
||||
exit /b 0
|
||||
|
||||
:bootstrap_tcc
|
||||
echo Bootstraping TCC...
|
||||
echo ^> TCC not found
|
||||
if "!tcc_branch!" == "thirdparty-windows-i386" ( echo ^> Downloading TCC32 from !tcc_url! ) else ( echo ^> Downloading TCC64 from !tcc_url! )
|
||||
if !flag_verbose! EQU 1 (
|
||||
echo [Debug] git clone --depth 1 --quiet --single-branch --branch !tcc_branch! !tcc_url! "%tcc_dir%">>"!log_file!"
|
||||
echo git clone --depth 1 --quiet --single-branch --branch !tcc_branch! !tcc_url! "%tcc_dir%"
|
||||
)
|
||||
git clone --depth 1 --quiet --single-branch --branch !tcc_branch! !tcc_url! "%tcc_dir%">>"!log_file!" 2>NUL
|
||||
exit /b 0
|
||||
|
||||
:cloning_vc
|
||||
echo Cloning vc...
|
||||
echo ^> Cloning from remote !vc_url!
|
||||
if !flag_verbose! EQU 1 (
|
||||
echo [Debug] git clone --depth 1 --quiet %vc_url%>>"!log_file!"
|
||||
echo git clone --depth 1 --quiet %vc_url%
|
||||
)
|
||||
git clone --depth 1 --quiet %vc_url%>>"!log_file!" 2>NUL
|
||||
exit /b 0
|
||||
|
||||
:eof
|
||||
popd
|
||||
endlocal
|
||||
|
@ -177,7 +177,7 @@ pub fn malloc(n int) byteptr {
|
||||
}
|
||||
$if trace_malloc ? {
|
||||
total_m += n
|
||||
C.fprintf(C.stderr, c'v_malloc %d total %d\n', n, total_m)
|
||||
C.fprintf(C.stderr, c'v_malloc %6d total %10d\n', n, total_m)
|
||||
// print_backtrace()
|
||||
}
|
||||
mut res := byteptr(0)
|
||||
|
@ -72,6 +72,7 @@ fn C.fclose(stream &C.FILE) int
|
||||
fn C.pclose(stream &C.FILE) int
|
||||
|
||||
// process execution, os.process:
|
||||
[trusted]
|
||||
fn C.getpid() int
|
||||
|
||||
fn C.system(cmd charptr) int
|
||||
@ -82,6 +83,12 @@ fn C.posix_spawnp(child_pid &int, exefile charptr, file_actions voidptr, attrp v
|
||||
|
||||
fn C.execve(cmd_path charptr, args voidptr, envs voidptr) int
|
||||
|
||||
fn C.execvp(cmd_path charptr, args &charptr) int
|
||||
|
||||
fn C._execve(cmd_path charptr, args voidptr, envs voidptr) int
|
||||
|
||||
fn C._execvp(cmd_path charptr, args &charptr) int
|
||||
|
||||
[trusted]
|
||||
fn C.fork() int
|
||||
|
||||
@ -89,6 +96,7 @@ fn C.wait(status &int) int
|
||||
|
||||
fn C.waitpid(pid int, status &int, options int) int
|
||||
|
||||
[trusted]
|
||||
fn C.kill(pid int, sig int) int
|
||||
|
||||
fn C.setenv(charptr, charptr, int) int
|
||||
@ -115,12 +123,14 @@ fn C.fgets(str charptr, n int, stream &C.FILE) int
|
||||
|
||||
fn C.memset(str voidptr, c int, n size_t) int
|
||||
|
||||
[trusted]
|
||||
fn C.sigemptyset() int
|
||||
|
||||
fn C.getcwd(buf charptr, size size_t) charptr
|
||||
|
||||
fn C.signal(signal int, handlercb voidptr) voidptr
|
||||
|
||||
[trusted]
|
||||
fn C.mktime() int
|
||||
|
||||
fn C.gettimeofday(tv &C.timeval, tz &C.timezone) int
|
||||
@ -129,6 +139,7 @@ fn C.gettimeofday(tv &C.timeval, tz &C.timezone) int
|
||||
fn C.sleep(seconds u32) u32
|
||||
|
||||
// fn C.usleep(usec useconds_t) int
|
||||
[trusted]
|
||||
fn C.usleep(usec u32) int
|
||||
|
||||
fn C.opendir(charptr) voidptr
|
||||
@ -148,8 +159,10 @@ fn C.srand(seed u32)
|
||||
|
||||
fn C.atof(str charptr) f64
|
||||
|
||||
[trusted]
|
||||
fn C.tolower(c int) int
|
||||
|
||||
[trusted]
|
||||
fn C.toupper(c int) int
|
||||
|
||||
[trusted]
|
||||
@ -162,20 +175,26 @@ fn C.snprintf(str charptr, size size_t, format charptr, opt ...voidptr) int
|
||||
|
||||
fn C.fprintf(byteptr, ...byteptr)
|
||||
|
||||
[trusted]
|
||||
fn C.WIFEXITED(status int) bool
|
||||
|
||||
[trusted]
|
||||
fn C.WEXITSTATUS(status int) int
|
||||
|
||||
[trusted]
|
||||
fn C.WIFSIGNALED(status int) bool
|
||||
|
||||
[trusted]
|
||||
fn C.WTERMSIG(status int) int
|
||||
|
||||
[trusted]
|
||||
fn C.isatty(fd int) int
|
||||
|
||||
fn C.syscall(number int, va ...voidptr) int
|
||||
|
||||
fn C.sysctl(name &int, namelen u32, oldp voidptr, oldlenp voidptr, newp voidptr, newlen size_t) int
|
||||
|
||||
[trusted]
|
||||
fn C._fileno(int) int
|
||||
|
||||
fn C._get_osfhandle(fd int) C.intptr_t
|
||||
@ -196,6 +215,7 @@ fn C.SetHandleInformation(hObject voidptr, dwMask u32, dw_flags u32) bool
|
||||
|
||||
fn C.ExpandEnvironmentStringsW(lpSrc &u16, lpDst &u16, nSize u32) u32
|
||||
|
||||
[trusted]
|
||||
fn C.SendMessageTimeout() u32
|
||||
|
||||
fn C.SendMessageTimeoutW(hWnd voidptr, Msg u32, wParam &u16, lParam &u32, fuFlags u32, uTimeout u32, lpdwResult &u64) u32
|
||||
@ -231,6 +251,7 @@ fn C.SetConsoleMode(voidptr, u32) int
|
||||
// fn C.GetConsoleMode() int
|
||||
fn C.GetConsoleMode(voidptr, &u32) int
|
||||
|
||||
[trusted]
|
||||
fn C.GetCurrentProcessId() int
|
||||
|
||||
fn C.wprintf()
|
||||
@ -280,6 +301,7 @@ fn C._fullpath() int
|
||||
|
||||
fn C.GetFullPathName(voidptr, u32, voidptr, voidptr) u32
|
||||
|
||||
[trusted]
|
||||
fn C.GetCommandLine() voidptr
|
||||
|
||||
fn C.LocalFree()
|
||||
@ -301,8 +323,10 @@ fn C.CloseHandle(voidptr) int
|
||||
|
||||
fn C.GetExitCodeProcess(hProcess voidptr, lpExitCode &u32)
|
||||
|
||||
[trusted]
|
||||
fn C.GetTickCount() i64
|
||||
|
||||
[trusted]
|
||||
fn C.Sleep(dwMilliseconds u32)
|
||||
|
||||
fn C.WSAStartup(u16, &voidptr) int
|
||||
|
@ -4,10 +4,16 @@ module os
|
||||
|
||||
// close filedescriptor
|
||||
pub fn fd_close(fd int) int {
|
||||
if fd == -1 {
|
||||
return 0
|
||||
}
|
||||
return C.close(fd)
|
||||
}
|
||||
|
||||
pub fn fd_write(fd int, s string) {
|
||||
if fd == -1 {
|
||||
return
|
||||
}
|
||||
mut sp := s.str
|
||||
mut remaining := s.len
|
||||
for remaining > 0 {
|
||||
@ -23,6 +29,9 @@ pub fn fd_write(fd int, s string) {
|
||||
// read from filedescriptor, block until data
|
||||
pub fn fd_slurp(fd int) []string {
|
||||
mut res := []string{}
|
||||
if fd == -1 {
|
||||
return res
|
||||
}
|
||||
for {
|
||||
s, b := fd_read(fd, 4096)
|
||||
if b <= 0 {
|
||||
@ -36,6 +45,9 @@ pub fn fd_slurp(fd int) []string {
|
||||
// read from filedescriptor, don't block
|
||||
// return [bytestring,nrbytes]
|
||||
pub fn fd_read(fd int, maxbytes int) (string, int) {
|
||||
if fd == -1 {
|
||||
return '', 0
|
||||
}
|
||||
unsafe {
|
||||
mut buf := malloc(maxbytes)
|
||||
nbytes := C.read(fd, buf, maxbytes)
|
||||
|
19
vlib/os/os.v
19
vlib/os/os.v
@ -3,13 +3,6 @@
|
||||
// that can be found in the LICENSE file.
|
||||
module os
|
||||
|
||||
pub struct Result {
|
||||
pub:
|
||||
exit_code int
|
||||
output string
|
||||
// stderr string // TODO
|
||||
}
|
||||
|
||||
pub const (
|
||||
args = []string{}
|
||||
max_path_len = 4096
|
||||
@ -23,6 +16,18 @@ const (
|
||||
r_ok = 4
|
||||
)
|
||||
|
||||
pub struct Result {
|
||||
pub:
|
||||
exit_code int
|
||||
output string
|
||||
// stderr string // TODO
|
||||
}
|
||||
|
||||
[unsafe]
|
||||
pub fn (mut result Result) free() {
|
||||
unsafe { result.output.free() }
|
||||
}
|
||||
|
||||
// cp_all will recursively copy `src` to `dst`,
|
||||
// optionally overwriting files or dirs in `dst`.
|
||||
pub fn cp_all(src string, dst string, overwrite bool) ? {
|
||||
|
@ -15,7 +15,7 @@ fn C.getline(voidptr, voidptr, voidptr) int
|
||||
|
||||
fn C.ftell(fp voidptr) int
|
||||
|
||||
fn C.sigaction(int, voidptr, int)
|
||||
fn C.sigaction(int, voidptr, int) int
|
||||
|
||||
fn C.open(charptr, int, ...int) int
|
||||
|
||||
@ -23,7 +23,7 @@ fn C.fdopen(fd int, mode charptr) &C.FILE
|
||||
|
||||
fn C.CopyFile(&u32, &u32, int) int
|
||||
|
||||
fn C.execvp(file charptr, argv &charptr) int
|
||||
// fn C.lstat(charptr, voidptr) u64
|
||||
|
||||
fn C._wstat64(charptr, voidptr) u64
|
||||
|
||||
@ -43,11 +43,14 @@ struct C.__stat64 {
|
||||
struct C.DIR {
|
||||
}
|
||||
|
||||
type FN_SA_Handler = fn (sig int)
|
||||
|
||||
struct C.sigaction {
|
||||
mut:
|
||||
sa_mask int
|
||||
sa_sigaction int
|
||||
sa_flags int
|
||||
sa_handler FN_SA_Handler
|
||||
}
|
||||
|
||||
struct C.dirent {
|
||||
@ -758,9 +761,10 @@ fn normalize_drive_letter(path string) string {
|
||||
return path
|
||||
}
|
||||
|
||||
// signal will assign `handler` callback to be called when `signum` signal is recieved.
|
||||
pub fn signal(signum int, handler voidptr) {
|
||||
unsafe { C.signal(signum, handler) }
|
||||
// signal will assign `handler` callback to be called when `signum` signal is received.
|
||||
pub fn signal(signum int, handler voidptr) voidptr {
|
||||
res := unsafe { C.signal(signum, handler) }
|
||||
return res
|
||||
}
|
||||
|
||||
// fork will fork the current system process and return the pid of the fork.
|
||||
@ -843,10 +847,17 @@ pub fn execvp(cmdpath string, args []string) ? {
|
||||
cargs << charptr(args[i].str)
|
||||
}
|
||||
cargs << charptr(0)
|
||||
res := C.execvp(charptr(cmdpath.str), cargs.data)
|
||||
mut res := int(0)
|
||||
$if windows {
|
||||
res = C._execvp(charptr(cmdpath.str), cargs.data)
|
||||
} $else {
|
||||
res = C.execvp(charptr(cmdpath.str), cargs.data)
|
||||
}
|
||||
if res == -1 {
|
||||
return error_with_code(posix_get_error_msg(C.errno), C.errno)
|
||||
}
|
||||
// just in case C._execvp returned ... that happens on windows ...
|
||||
exit(res)
|
||||
}
|
||||
|
||||
// execve - loads and executes a new child process, *in place* of the current process.
|
||||
@ -867,7 +878,12 @@ pub fn execve(cmdpath string, args []string, envs []string) ? {
|
||||
}
|
||||
cargv << charptr(0)
|
||||
cenvs << charptr(0)
|
||||
res := C.execve(charptr(cmdpath.str), cargv.data, cenvs.data)
|
||||
mut res := int(0)
|
||||
$if windows {
|
||||
res = C._execve(charptr(cmdpath.str), cargv.data, cenvs.data)
|
||||
} $else {
|
||||
res = C.execve(charptr(cmdpath.str), cargv.data, cenvs.data)
|
||||
}
|
||||
// NB: normally execve does not return at all.
|
||||
// If it returns, then something went wrong...
|
||||
if res == -1 {
|
||||
|
@ -12,6 +12,7 @@ pub const (
|
||||
// Ref - https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types
|
||||
// A handle to an object.
|
||||
pub type HANDLE = voidptr
|
||||
pub type HMODULE = voidptr
|
||||
|
||||
// win: FILETIME
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
|
||||
|
@ -28,7 +28,9 @@ pub mut:
|
||||
env_is_custom bool // true, when the environment was customized with .set_environment
|
||||
env []string // the environment with which the process was started (list of 'var=val')
|
||||
use_stdio_ctl bool // when true, then you can use p.stdin_write(), p.stdout_slurp() and p.stderr_slurp()
|
||||
stdio_fd [3]int // the file descriptors
|
||||
use_pgroup bool // when true, the process will create a new process group, enabling .signal_pgkill()
|
||||
stdio_fd [3]int // the stdio file descriptors for the child process, used only by the nix implementation
|
||||
wdata voidptr // the WProcess; used only by the windows implementation
|
||||
}
|
||||
|
||||
// new_process - create a new process descriptor
|
||||
@ -39,6 +41,7 @@ pub mut:
|
||||
pub fn new_process(filename string) &Process {
|
||||
return &Process{
|
||||
filename: filename
|
||||
stdio_fd: [-1, -1, -1]!
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,6 +86,15 @@ pub fn (mut p Process) signal_kill() {
|
||||
return
|
||||
}
|
||||
|
||||
// signal_pgkill - kills the whole process group
|
||||
pub fn (mut p Process) signal_pgkill() {
|
||||
if p.status !in [.running, .stopped] {
|
||||
return
|
||||
}
|
||||
p._signal_pgkill()
|
||||
return
|
||||
}
|
||||
|
||||
// signal_stop - stops the process, you can resume it with p.signal_continue()
|
||||
pub fn (mut p Process) signal_stop() {
|
||||
if p.status != .running {
|
||||
@ -159,33 +171,55 @@ pub fn (mut p Process) set_redirect_stdio() {
|
||||
|
||||
pub fn (mut p Process) stdin_write(s string) {
|
||||
p._check_redirection_call('stdin_write')
|
||||
fd_write(p.stdio_fd[0], s)
|
||||
$if windows {
|
||||
p.win_write_string(0, s)
|
||||
} $else {
|
||||
fd_write(p.stdio_fd[0], s)
|
||||
}
|
||||
}
|
||||
|
||||
// will read from stdout pipe, will only return when EOF (end of file) or data
|
||||
// means this will block unless there is data
|
||||
pub fn (mut p Process) stdout_slurp() string {
|
||||
p._check_redirection_call('stdout_slurp')
|
||||
return fd_slurp(p.stdio_fd[1]).join('')
|
||||
$if windows {
|
||||
return p.win_slurp(1)
|
||||
} $else {
|
||||
return fd_slurp(p.stdio_fd[1]).join('')
|
||||
}
|
||||
}
|
||||
|
||||
// read from stderr pipe, wait for data or EOF
|
||||
pub fn (mut p Process) stderr_slurp() string {
|
||||
p._check_redirection_call('stderr_slurp')
|
||||
return fd_slurp(p.stdio_fd[2]).join('')
|
||||
$if windows {
|
||||
return p.win_slurp(2)
|
||||
} $else {
|
||||
return fd_slurp(p.stdio_fd[2]).join('')
|
||||
}
|
||||
}
|
||||
|
||||
// read from stdout, return if data or not
|
||||
pub fn (mut p Process) stdout_read() string {
|
||||
p._check_redirection_call('stdout_read')
|
||||
s, _ := fd_read(p.stdio_fd[1], 4096)
|
||||
return s
|
||||
$if windows {
|
||||
s, _ := p.win_read_string(1, 4096)
|
||||
return s
|
||||
} $else {
|
||||
s, _ := fd_read(p.stdio_fd[1], 4096)
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut p Process) stderr_read() string {
|
||||
p._check_redirection_call('stderr_read')
|
||||
s, _ := fd_read(p.stdio_fd[2], 4096)
|
||||
return s
|
||||
$if windows {
|
||||
s, _ := p.win_read_string(2, 4096)
|
||||
return s
|
||||
} $else {
|
||||
s, _ := fd_read(p.stdio_fd[2], 4096)
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// _check_redirection_call - should be called just by stdxxx methods
|
||||
@ -225,6 +259,15 @@ fn (mut p Process) _signal_kill() {
|
||||
}
|
||||
}
|
||||
|
||||
// _signal_pgkill - should not be called directly, except by p.signal_pgkill
|
||||
fn (mut p Process) _signal_pgkill() {
|
||||
$if windows {
|
||||
p.win_kill_pgroup()
|
||||
} $else {
|
||||
p.unix_kill_pgroup()
|
||||
}
|
||||
}
|
||||
|
||||
// _wait - should not be called directly, except by p.wait()
|
||||
fn (mut p Process) _wait() {
|
||||
$if windows {
|
||||
|
@ -1,5 +1,7 @@
|
||||
module os
|
||||
|
||||
fn C.setpgid(pid int, pgid int) int
|
||||
|
||||
fn (mut p Process) unix_spawn_process() int {
|
||||
mut pipeset := [6]int{}
|
||||
if p.use_stdio_ctl {
|
||||
@ -27,6 +29,10 @@ fn (mut p Process) unix_spawn_process() int {
|
||||
// It still shares file descriptors with the parent process,
|
||||
// but it is otherwise independant and can do stuff *without*
|
||||
// affecting the parent process.
|
||||
//
|
||||
if p.use_pgroup {
|
||||
C.setpgid(0, 0)
|
||||
}
|
||||
if p.use_stdio_ctl {
|
||||
// Redirect the child standart in/out/err to the pipes that
|
||||
// were created in the parent.
|
||||
@ -62,6 +68,10 @@ fn (mut p Process) unix_kill_process() {
|
||||
C.kill(p.pid, C.SIGKILL)
|
||||
}
|
||||
|
||||
fn (mut p Process) unix_kill_pgroup() {
|
||||
C.kill(-p.pid, C.SIGKILL)
|
||||
}
|
||||
|
||||
fn (mut p Process) unix_wait() {
|
||||
cstatus := 0
|
||||
ret := C.waitpid(p.pid, &cstatus, 0)
|
||||
@ -114,9 +124,23 @@ fn (mut p Process) win_resume_process() {
|
||||
fn (mut p Process) win_kill_process() {
|
||||
}
|
||||
|
||||
fn (mut p Process) win_kill_pgroup() {
|
||||
}
|
||||
|
||||
fn (mut p Process) win_wait() {
|
||||
}
|
||||
|
||||
fn (mut p Process) win_is_alive() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
fn (mut p Process) win_write_string(idx int, s string) {
|
||||
}
|
||||
|
||||
fn (mut p Process) win_read_string(idx int, maxbytes int) (string, int) {
|
||||
return '', 0
|
||||
}
|
||||
|
||||
fn (mut p Process) win_slurp(idx int) string {
|
||||
return ''
|
||||
}
|
||||
|
@ -1,17 +1,25 @@
|
||||
import os
|
||||
import time
|
||||
|
||||
const vexe = os.getenv('VEXE')
|
||||
const (
|
||||
vexe = os.getenv('VEXE')
|
||||
vroot = os.dir(vexe)
|
||||
test_os_process = os.join_path(os.temp_dir(), 'v', 'test_os_process.exe')
|
||||
test_os_process_source = os.join_path(vroot, 'cmd/tools/test_os_process.v')
|
||||
)
|
||||
|
||||
const vroot = os.dir(vexe)
|
||||
|
||||
const test_os_process = os.join_path(os.temp_dir(), 'v', 'test_os_process.exe')
|
||||
|
||||
const test_os_process_source = os.join_path(vroot, 'cmd/tools/test_os_process.v')
|
||||
|
||||
fn testsuite_begin() {
|
||||
fn testsuite_begin() ? {
|
||||
os.rm(test_os_process) or {}
|
||||
assert os.system('$vexe -o $test_os_process $test_os_process_source') == 0
|
||||
if os.getenv('WINE_TEST_OS_PROCESS_EXE') != '' {
|
||||
// Make it easier to run the test under wine emulation, by just
|
||||
// prebuilding the executable with:
|
||||
// v -os windows -o x.exe cmd/tools/test_os_process.v
|
||||
// WINE_TEST_OS_PROCESS_EXE=x.exe ./v -os windows vlib/os/process_test.v
|
||||
os.cp(os.getenv('WINE_TEST_OS_PROCESS_EXE'), test_os_process) ?
|
||||
} else {
|
||||
os.system('$vexe -o $test_os_process $test_os_process_source')
|
||||
}
|
||||
assert os.exists(test_os_process)
|
||||
}
|
||||
|
||||
fn test_getpid() {
|
||||
@ -21,10 +29,6 @@ fn test_getpid() {
|
||||
}
|
||||
|
||||
fn test_run() {
|
||||
if os.user_os() == 'windows' {
|
||||
return
|
||||
}
|
||||
//
|
||||
mut p := os.new_process(test_os_process)
|
||||
p.set_args(['-timeout_ms', '150', '-period_ms', '50'])
|
||||
p.run()
|
||||
@ -51,9 +55,6 @@ fn test_run() {
|
||||
}
|
||||
|
||||
fn test_wait() {
|
||||
if os.user_os() == 'windows' {
|
||||
return
|
||||
}
|
||||
mut p := os.new_process(test_os_process)
|
||||
assert p.status != .exited
|
||||
p.wait()
|
||||
@ -63,9 +64,6 @@ fn test_wait() {
|
||||
}
|
||||
|
||||
fn test_slurping_output() {
|
||||
if os.user_os() == 'windows' {
|
||||
return
|
||||
}
|
||||
mut p := os.new_process(test_os_process)
|
||||
p.set_args(['-timeout_ms', '500', '-period_ms', '50'])
|
||||
p.set_redirect_stdio()
|
||||
@ -81,10 +79,13 @@ fn test_slurping_output() {
|
||||
eprintln('p errors: "$errors"')
|
||||
eprintln('---------------------------')
|
||||
}
|
||||
// dump(output)
|
||||
assert output.contains('stdout, 1')
|
||||
assert output.contains('stdout, 2')
|
||||
assert output.contains('stdout, 3')
|
||||
assert output.contains('stdout, 4')
|
||||
//
|
||||
// dump(errors)
|
||||
assert errors.contains('stderr, 1')
|
||||
assert errors.contains('stderr, 2')
|
||||
assert errors.contains('stderr, 3')
|
||||
|
@ -1,33 +1,221 @@
|
||||
module os
|
||||
|
||||
import strings
|
||||
|
||||
fn C.GenerateConsoleCtrlEvent(event u32, pgid u32) bool
|
||||
fn C.GetModuleHandleA(name charptr) HMODULE
|
||||
fn C.GetProcAddress(handle voidptr, procname byteptr) voidptr
|
||||
fn C.TerminateProcess(process HANDLE, exit_code u32) bool
|
||||
|
||||
type FN_NTSuspendResume = fn (voidptr)
|
||||
|
||||
fn ntdll_fn(name charptr) FN_NTSuspendResume {
|
||||
ntdll := C.GetModuleHandleA(c'NTDLL')
|
||||
if ntdll == 0 {
|
||||
return FN_NTSuspendResume(0)
|
||||
}
|
||||
the_fn := FN_NTSuspendResume(C.GetProcAddress(ntdll, name))
|
||||
return the_fn
|
||||
}
|
||||
|
||||
fn failed_cfn_report_error(ok bool, label string) {
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
error_num := int(C.GetLastError())
|
||||
error_msg := get_error_msg(error_num)
|
||||
eprintln('failed $label: $error_msg')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
type PU32 = &u32
|
||||
|
||||
// TODO: the PU32 alias is used to compensate for the wrong number of &/*
|
||||
// that V does when doing: `h := &&u32(p)`, which should have casted
|
||||
// p to a double pointer.
|
||||
fn close_valid_handle(p voidptr) {
|
||||
h := &PU32(p)
|
||||
if *h != &u32(0) {
|
||||
C.CloseHandle(*h)
|
||||
unsafe {
|
||||
*h = &u32(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WProcess {
|
||||
pub mut:
|
||||
proc_info ProcessInformation
|
||||
command_line [65536]byte
|
||||
child_stdin &u32
|
||||
//
|
||||
child_stdout_read &u32
|
||||
child_stdout_write &u32
|
||||
//
|
||||
child_stderr_read &u32
|
||||
child_stderr_write &u32
|
||||
}
|
||||
|
||||
fn (mut p Process) win_spawn_process() int {
|
||||
eprintln('TODO implement waiting for a process on windows')
|
||||
return 12345
|
||||
mut wdata := &WProcess{
|
||||
child_stdin: 0
|
||||
child_stdout_read: 0
|
||||
child_stdout_write: 0
|
||||
child_stderr_read: 0
|
||||
child_stderr_write: 0
|
||||
}
|
||||
p.wdata = voidptr(wdata)
|
||||
mut start_info := StartupInfo{
|
||||
lp_reserved: 0
|
||||
lp_desktop: 0
|
||||
lp_title: 0
|
||||
cb: sizeof(C.PROCESS_INFORMATION)
|
||||
}
|
||||
if p.use_stdio_ctl {
|
||||
mut sa := SecurityAttributes{}
|
||||
sa.n_length = sizeof(C.SECURITY_ATTRIBUTES)
|
||||
sa.b_inherit_handle = true
|
||||
create_pipe_ok1 := C.CreatePipe(voidptr(&wdata.child_stdout_read), voidptr(&wdata.child_stdout_write),
|
||||
voidptr(&sa), 0)
|
||||
failed_cfn_report_error(create_pipe_ok1, 'CreatePipe stdout')
|
||||
set_handle_info_ok1 := C.SetHandleInformation(wdata.child_stdout_read, C.HANDLE_FLAG_INHERIT,
|
||||
0)
|
||||
failed_cfn_report_error(set_handle_info_ok1, 'SetHandleInformation')
|
||||
create_pipe_ok2 := C.CreatePipe(voidptr(&wdata.child_stderr_read), voidptr(&wdata.child_stderr_write),
|
||||
voidptr(&sa), 0)
|
||||
failed_cfn_report_error(create_pipe_ok2, 'CreatePipe stderr')
|
||||
set_handle_info_ok2 := C.SetHandleInformation(wdata.child_stderr_read, C.HANDLE_FLAG_INHERIT,
|
||||
0)
|
||||
failed_cfn_report_error(set_handle_info_ok2, 'SetHandleInformation stderr')
|
||||
start_info.h_std_input = wdata.child_stdin
|
||||
start_info.h_std_output = wdata.child_stdout_write
|
||||
start_info.h_std_error = wdata.child_stderr_write
|
||||
start_info.dw_flags = u32(C.STARTF_USESTDHANDLES)
|
||||
}
|
||||
cmd := '$p.filename ' + p.args.join(' ')
|
||||
C.ExpandEnvironmentStringsW(cmd.to_wide(), voidptr(&wdata.command_line[0]), 32768)
|
||||
|
||||
mut creation_flags := int(C.NORMAL_PRIORITY_CLASS)
|
||||
if p.use_pgroup {
|
||||
creation_flags |= C.CREATE_NEW_PROCESS_GROUP
|
||||
}
|
||||
create_process_ok := C.CreateProcessW(0, &wdata.command_line[0], 0, 0, C.TRUE, creation_flags,
|
||||
0, 0, voidptr(&start_info), voidptr(&wdata.proc_info))
|
||||
failed_cfn_report_error(create_process_ok, 'CreateProcess')
|
||||
if p.use_stdio_ctl {
|
||||
close_valid_handle(&wdata.child_stdout_write)
|
||||
close_valid_handle(&wdata.child_stderr_write)
|
||||
}
|
||||
p.pid = int(wdata.proc_info.dw_process_id)
|
||||
return p.pid
|
||||
}
|
||||
|
||||
fn (mut p Process) win_stop_process() {
|
||||
eprintln('TODO implement stopping a process on windows')
|
||||
the_fn := ntdll_fn(c'NtSuspendProcess')
|
||||
if voidptr(the_fn) == 0 {
|
||||
return
|
||||
}
|
||||
wdata := &WProcess(p.wdata)
|
||||
the_fn(wdata.proc_info.h_process)
|
||||
}
|
||||
|
||||
fn (mut p Process) win_resume_process() {
|
||||
eprintln('TODO implement resuming a process on windows')
|
||||
the_fn := ntdll_fn(c'NtResumeProcess')
|
||||
if voidptr(the_fn) == 0 {
|
||||
return
|
||||
}
|
||||
wdata := &WProcess(p.wdata)
|
||||
the_fn(wdata.proc_info.h_process)
|
||||
}
|
||||
|
||||
fn (mut p Process) win_kill_process() {
|
||||
eprintln('TODO implement killing a process on windows')
|
||||
wdata := &WProcess(p.wdata)
|
||||
C.TerminateProcess(wdata.proc_info.h_process, 3)
|
||||
}
|
||||
|
||||
fn (mut p Process) win_kill_pgroup() {
|
||||
wdata := &WProcess(p.wdata)
|
||||
C.GenerateConsoleCtrlEvent(C.CTRL_BREAK_EVENT, wdata.proc_info.dw_process_id)
|
||||
C.Sleep(20)
|
||||
C.TerminateProcess(wdata.proc_info.h_process, 3)
|
||||
}
|
||||
|
||||
fn (mut p Process) win_wait() {
|
||||
eprintln('TODO implement waiting for a process on windows')
|
||||
exit_code := u32(1)
|
||||
mut wdata := &WProcess(p.wdata)
|
||||
if p.wdata != 0 {
|
||||
C.WaitForSingleObject(wdata.proc_info.h_process, C.INFINITE)
|
||||
C.GetExitCodeProcess(wdata.proc_info.h_process, voidptr(&exit_code))
|
||||
close_valid_handle(&wdata.child_stdin)
|
||||
close_valid_handle(&wdata.child_stdout_write)
|
||||
close_valid_handle(&wdata.child_stderr_write)
|
||||
close_valid_handle(&wdata.proc_info.h_process)
|
||||
close_valid_handle(&wdata.proc_info.h_thread)
|
||||
}
|
||||
p.status = .exited
|
||||
p.code = 0
|
||||
p.code = int(exit_code)
|
||||
}
|
||||
|
||||
fn (mut p Process) win_is_alive() bool {
|
||||
eprintln('TODO implement checking whether the process is still alive on windows')
|
||||
exit_code := u32(0)
|
||||
wdata := &WProcess(p.wdata)
|
||||
C.GetExitCodeProcess(wdata.proc_info.h_process, voidptr(&exit_code))
|
||||
if exit_code == C.STILL_ACTIVE {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
///////////////
|
||||
|
||||
fn (mut p Process) win_write_string(idx int, s string) {
|
||||
panic('Process.write_string $idx is not implemented yet')
|
||||
}
|
||||
|
||||
fn (mut p Process) win_read_string(idx int, maxbytes int) (string, int) {
|
||||
panic('WProcess.read_string $idx is not implemented yet')
|
||||
return '', 0
|
||||
}
|
||||
|
||||
fn (mut p Process) win_slurp(idx int) string {
|
||||
mut wdata := &WProcess(p.wdata)
|
||||
if wdata == 0 {
|
||||
return ''
|
||||
}
|
||||
mut rhandle := &u32(0)
|
||||
if idx == 1 {
|
||||
rhandle = wdata.child_stdout_read
|
||||
}
|
||||
if idx == 2 {
|
||||
rhandle = wdata.child_stderr_read
|
||||
}
|
||||
if rhandle == 0 {
|
||||
return ''
|
||||
}
|
||||
mut bytes_read := u32(0)
|
||||
buf := [4096]byte{}
|
||||
mut read_data := strings.new_builder(1024)
|
||||
for {
|
||||
mut result := false
|
||||
unsafe {
|
||||
result = C.ReadFile(rhandle, &buf[0], 1000, voidptr(&bytes_read), 0)
|
||||
read_data.write_ptr(&buf[0], int(bytes_read))
|
||||
}
|
||||
if result == false || int(bytes_read) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
soutput := read_data.str()
|
||||
unsafe { read_data.free() }
|
||||
if idx == 1 {
|
||||
close_valid_handle(&wdata.child_stdout_read)
|
||||
}
|
||||
if idx == 2 {
|
||||
close_valid_handle(&wdata.child_stderr_read)
|
||||
}
|
||||
return soutput
|
||||
}
|
||||
|
||||
//
|
||||
// these are here to make v_win.c/v.c generation work in all cases:
|
||||
fn (mut p Process) unix_spawn_process() int {
|
||||
@ -43,6 +231,9 @@ fn (mut p Process) unix_resume_process() {
|
||||
fn (mut p Process) unix_kill_process() {
|
||||
}
|
||||
|
||||
fn (mut p Process) unix_kill_pgroup() {
|
||||
}
|
||||
|
||||
fn (mut p Process) unix_wait() {
|
||||
}
|
||||
|
||||
|
@ -76,6 +76,7 @@ pub mut:
|
||||
output_mode OutputMode = .stdout
|
||||
// verbosity VerboseLevel
|
||||
is_verbose bool
|
||||
is_watch bool // -watch mode, implemented by cmd/tools/watch.v
|
||||
// nofmt bool // disable vfmt
|
||||
is_test bool // `v test string_test.v`
|
||||
is_script bool // single file mode (`v program.v`), main function can be skipped
|
||||
@ -391,6 +392,9 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences
|
||||
'-w' {
|
||||
res.skip_warnings = true
|
||||
}
|
||||
'-watch' {
|
||||
res.is_watch = true
|
||||
}
|
||||
'-print-v-files' {
|
||||
res.print_v_files = true
|
||||
}
|
||||
|
@ -215,13 +215,12 @@ pub fn launch_tool(is_verbose bool, tool_name string, args []string) {
|
||||
tool_exe = path_of_executable(tool_basename)
|
||||
tool_source = tool_basename + '.v'
|
||||
}
|
||||
tool_command := '"$tool_exe" $tool_args'
|
||||
if is_verbose {
|
||||
println('launch_tool vexe : $vroot')
|
||||
println('launch_tool vroot : $vroot')
|
||||
println('launch_tool tool_args : $tool_args')
|
||||
println('launch_tool tool_source : $tool_source')
|
||||
println('launch_tool tool_command: $tool_command')
|
||||
println('launch_tool tool_exe : $tool_exe')
|
||||
println('launch_tool tool_args : $tool_args')
|
||||
}
|
||||
disabling_file := recompilation.disabling_file(vroot)
|
||||
is_recompilation_disabled := os.exists(disabling_file)
|
||||
@ -254,10 +253,11 @@ pub fn launch_tool(is_verbose bool, tool_name string, args []string) {
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
if is_verbose {
|
||||
println('launch_tool running tool command: $tool_command ...')
|
||||
$if windows {
|
||||
exit(os.system('"$tool_exe" $tool_args'))
|
||||
} $else {
|
||||
os.execvp(tool_exe, args) or { panic(err) }
|
||||
}
|
||||
exit(os.system(tool_command))
|
||||
}
|
||||
|
||||
// NB: should_recompile_tool/4 compares unix timestamps that have 1 second resolution
|
||||
|
Loading…
Reference in New Issue
Block a user