From 86447c1301b9a7865d5a10f1dfc8a1eaddc658c6 Mon Sep 17 00:00:00 2001 From: vitalyster Date: Thu, 7 Nov 2019 16:01:17 +0300 Subject: [PATCH] windows: use CreateProcess for os.exec --- tools/vtest.v | 4 +- vlib/compiler/msvc.v | 6 +-- vlib/os/os.v | 26 ------------ vlib/os/os_nix.v | 26 +++++++++++- vlib/os/os_windows.v | 97 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 126 insertions(+), 33 deletions(-) diff --git a/tools/vtest.v b/tools/vtest.v index 9da1284de1..74a4ce252c 100644 --- a/tools/vtest.v +++ b/tools/vtest.v @@ -82,7 +82,6 @@ pub fn main() { pub fn (ts mut TestSession) test() { ok := term.ok_message('OK') fail := term.fail_message('FAIL') - cmd_needs_quoting := (os.user_os() == 'windows') show_stats := '-stats' in ts.vargs.split(' ') ts.benchmark = benchmark.new_benchmark() for dot_relative_file in ts.files { @@ -93,8 +92,7 @@ pub fn (ts mut TestSession) test() { } tmpc_filepath := file.replace('.v', '.tmp.c') - mut cmd := '"$ts.vexe" $ts.vargs "$file"' - if cmd_needs_quoting { cmd = '"$cmd"' } + cmd := '"$ts.vexe" $ts.vargs "$file"' ts.benchmark.step() if show_stats { diff --git a/vlib/compiler/msvc.v b/vlib/compiler/msvc.v index b6128ac721..155c60f34f 100644 --- a/vlib/compiler/msvc.v +++ b/vlib/compiler/msvc.v @@ -145,7 +145,7 @@ fn find_vs(vswhere_dir string, host_arch string) ?VsInstallation { // If its not there then end user needs to update their visual studio // installation! - res := os.exec('""$vswhere_dir\\Microsoft Visual Studio\\Installer\\vswhere.exe" -latest -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath"') or { + res := os.exec('"$vswhere_dir\\Microsoft Visual Studio\\Installer\\vswhere.exe" -latest -prerelease -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath') or { return error(err) } // println('res: "$res"') @@ -348,7 +348,7 @@ pub fn (v mut V) cc_msvc() { args := a.join(' ') - cmd := '""$r.full_cl_exe_path" $args"' + cmd := '"$r.full_cl_exe_path" $args' // It is hard to see it at first, but the quotes above ARE balanced :-| ... // Also the double quotes at the start ARE needed. if v.pref.show_c_cmd || v.pref.is_verbose { @@ -410,7 +410,7 @@ fn build_thirdparty_obj_file_with_msvc(path string, moduleflags []CFlag) { btarget := moduleflags.c_options_before_target_msvc() atarget := moduleflags.c_options_after_target_msvc() - cmd := '""$msvc.full_cl_exe_path" /volatile:ms /Zi /DNDEBUG $include_string /c $btarget $cfiles $atarget /Fo"$obj_path""' + cmd := '"$msvc.full_cl_exe_path" /volatile:ms /Zi /DNDEBUG $include_string /c $btarget $cfiles $atarget /Fo"$obj_path"' //NB: the quotes above ARE balanced. println('thirdparty cmd line: $cmd') res := os.exec(cmd) or { diff --git a/vlib/os/os.v b/vlib/os/os.v index 85bc76e6f2..8b420cd39a 100644 --- a/vlib/os/os.v +++ b/vlib/os/os.v @@ -388,32 +388,6 @@ pub: //stderr string // TODO } -// exec starts the specified command, waits for it to complete, and returns its output. -pub fn exec(cmd string) ?Result { - if cmd.contains(';') || cmd.contains('&&') || cmd.contains('||') || cmd.contains('\n') { - return error(';, &&, || and \\n are not allowed in shell commands') - } - pcmd := '$cmd 2>&1' - f := vpopen(pcmd) - if isnil(f) { - return error('exec("$cmd") failed') - } - buf := [1000]byte - mut res := '' - for C.fgets(*char(buf), 1000, f) != 0 { - res += tos(buf, vstrlen(buf)) - } - res = res.trim_space() - exit_code := vpclose(f) - //if exit_code != 0 { - //return error(res) - //} - return Result { - output: res - exit_code: exit_code - } -} - // `system` works like `exec()`, but only returns a return code. pub fn system(cmd string) int { if cmd.contains(';') || cmd.contains('&&') || cmd.contains('||') || cmd.contains('\n') { diff --git a/vlib/os/os_nix.v b/vlib/os/os_nix.v index 47c2949428..18c10d9c09 100644 --- a/vlib/os/os_nix.v +++ b/vlib/os/os_nix.v @@ -60,4 +60,28 @@ pub fn mkdir(path string) { C.mkdir(path.str, 511)// S_IRWXU | S_IRWXG | S_IRWXO } - +// exec starts the specified command, waits for it to complete, and returns its output. +pub fn exec(cmd string) ?Result { + if cmd.contains(';') || cmd.contains('&&') || cmd.contains('||') || cmd.contains('\n') { + return error(';, &&, || and \\n are not allowed in shell commands') + } + pcmd := '$cmd 2>&1' + f := vpopen(pcmd) + if isnil(f) { + return error('exec("$cmd") failed') + } + buf := [1000]byte + mut res := '' + for C.fgets(*char(buf), 1000, f) != 0 { + res += tos(buf, vstrlen(buf)) + } + res = res.trim_space() + exit_code := vpclose(f) + //if exit_code != 0 { + //return error(res) + //} + return Result { + output: res + exit_code: exit_code + } +} diff --git a/vlib/os/os_windows.v b/vlib/os/os_windows.v index a06d04b1b1..ba7a4efe90 100644 --- a/vlib/os/os_windows.v +++ b/vlib/os/os_windows.v @@ -37,6 +37,42 @@ mut: wFinderFlags u16 } +struct ProcessInformation { +mut: + hProcess voidptr + hThread voidptr + dwProcessId u32 + dwThreadId u32 +} + +struct StartupInfo { +mut: + cb u32 + lpReserved &u16 + lpDesktop &u16 + lpTitle &u16 + dwX u32 + dwY u32 + dwXSize u32 + dwYSize u32 + dwXCountChars u32 + dwYCountChars u32 + dwFillAttribute u32 + dwFlags u32 + wShowWindow u16 + cbReserved2 u16 + lpReserved2 byteptr + hStdInput voidptr + hStdOutput voidptr + hStdError voidptr +} + +struct SecurityAttributes { +mut: + nLength u32 + lpSecurityDescriptor voidptr + bInheritHandle bool +} fn init_os_args(argc int, argv &byteptr) []string { mut args := []string @@ -189,3 +225,64 @@ pub fn get_error_msg(code int) string { } return string_from_wide(_ptr_text) } + +// exec starts the specified command, waits for it to complete, and returns its output. +pub fn exec(cmd string) ?Result { + if cmd.contains(';') || cmd.contains('&&') || cmd.contains('||') || cmd.contains('\n') { + return error(';, &&, || and \\n are not allowed in shell commands') + } + mut child_stdin := &u32(0) + mut child_stdout_read := &u32(0) + mut child_stdout_write := &u32(0) + mut sa := SecurityAttributes {} + sa.nLength = sizeof(C.SECURITY_ATTRIBUTES) + sa.bInheritHandle = true + + create_pipe_result := C.CreatePipe(&child_stdout_read, &child_stdout_write, &sa, 0) + if create_pipe_result == 0 { + error_msg := get_error_msg(int(C.GetLastError())) + return error('exec failed (CreatePipe): $error_msg') + } + set_handle_info_result := C.SetHandleInformation(child_stdout_read, C.HANDLE_FLAG_INHERIT, 0) + if set_handle_info_result == 0 { + error_msg := get_error_msg(int(C.GetLastError())) + panic('exec failed (SetHandleInformation): $error_msg') + } + + proc_info := ProcessInformation{} + mut start_info := StartupInfo{} + start_info.cb = sizeof(C.PROCESS_INFORMATION) + start_info.hStdInput = child_stdin + start_info.hStdOutput = child_stdout_write + start_info.hStdError = child_stdout_write + start_info.dwFlags = u32(C.STARTF_USESTDHANDLES) + command_line := [32768]u16 + C.ExpandEnvironmentStrings(cmd.to_wide(), &command_line, 32768) + create_process_result := C.CreateProcess(0, command_line, 0, 0, C.TRUE, 0, 0, 0, &start_info, &proc_info) + if create_process_result == 0 { + error_msg := get_error_msg(int(C.GetLastError())) + return error('exec failed (CreateProcess): $error_msg') + } + C.CloseHandle(child_stdin) + C.CloseHandle(child_stdout_write) + buf := [1000]byte + mut bytes_read := 0 + mut read_data := '' + for { + readfile_result := C.ReadFile(child_stdout_read, buf, 1000, &bytes_read, 0) + read_data += tos(buf, bytes_read) + if (readfile_result == 0 || bytes_read == 0) { + break + } + } + read_data = read_data.trim_space() + exit_code := 0 + C.WaitForSingleObject(proc_info.hProcess, C.INFINITE) + C.GetExitCodeProcess(proc_info.hProcess, &exit_code) + C.CloseHandle(proc_info.hProcess) + C.CloseHandle(proc_info.hThread) + return Result { + output: read_data + exit_code: exit_code + } +} \ No newline at end of file