diff --git a/cmd/tools/vsymlink.v b/cmd/tools/vsymlink.v index 54c2ad2073..caf29f4ecf 100644 --- a/cmd/tools/vsymlink.v +++ b/cmd/tools/vsymlink.v @@ -1,11 +1,6 @@ import os import v.pref -const ( - hkey_current_user = voidptr(0x80000001) - hwnd_broadcast = voidptr(0xffff) -) - $if windows { $if tinyc { #flag -lAdvapi32 @@ -14,15 +9,15 @@ $if windows { } fn main(){ + vexe := pref.vexe_path() $if windows { - setup_symlink_on_windows() + setup_symlink_windows(vexe) } $else { - setup_symlink_on_unix() + setup_symlink(vexe) } } -fn setup_symlink_on_unix(){ - vexe := pref.vexe_path() +fn setup_symlink(vexe string){ mut link_path := '/usr/local/bin/v' mut ret := os.exec('ln -sf $vexe $link_path') or { panic(err) @@ -45,40 +40,42 @@ fn setup_symlink_on_unix(){ } } -fn setup_symlink_on_windows(){ +fn setup_symlink_windows(vexe string){ $if windows { - vexe := pref.vexe_path() - // NB: Putting $vdir directly into PATH will also result in - // make.bat being global, which is NOT what we want. - // - // Instead, we create a small launcher v.bat, in a new local - // folder .bin/ . That .bin/ folder can then be put in PATH - // without poluting it with anything else - just a `v` - // command will be available, similar to unix. - // - // Creating a real NTFS symlink to the real executable was also - // tried, but then os.real_path( os.executable() ) returns the - // path to the symlink, unfortunately, unlike on posix systems - // ¯\_(ツ)_/¯ - vdir := os.real_path(os.dir(vexe)) - vsymlinkdir := os.join_path(vdir, '.bin') - vsymlinkbat := os.join_path(vsymlinkdir, 'v.bat') - if os.exists(vsymlinkbat) { - print('Batch script $vsymlinkbat already exists, checking system %PATH%...') + // Create a symlink in a new local folder (.\.bin\.v.exe) + // Puts `v` in %PATH% without polluting it with anything else (like make.bat). + // This will make `v` available on cmd.exe, PowerShell, and MinGW(MSYS)/WSL/Cygwin + + vdir := os.real_path(os.dir(vexe)) + vsymlinkdir := os.join_path(vdir, '.bin') + + mut vsymlink := os.join_path(vsymlinkdir, 'v.exe') + + if !os.exists(vsymlinkdir) { + os.mkdir_all(vsymlinkdir) // will panic if fails + } else { + os.rm(vsymlink) } - else { - os.rmdir_all(vsymlinkdir) - os.mkdir_all(vsymlinkdir) - os.write_file(vsymlinkbat, '@echo off\n${vexe} %*') - if !os.exists(vsymlinkbat) { - eprintln('Could not create $vsymlinkbat') - exit(1) - } - else { - print('Created $vsymlinkbat, checking system %PATH%...') + + // try to create a native symlink at .\.bin\v.exe + os.symlink(vsymlink, vexe) or { + // typically only fails if you're on a network drive (VirtualBox) + // do batch file creation instead + eprint('NOTE: Could not create a native symlink: $err') + eprintln('Creating a batch file instead...') + vsymlink = os.join_path(vsymlinkdir, 'v.bat') + if os.exists(vsymlink) { + os.rm(vsymlink) } + os.write_file(vsymlink, '@echo off\n${vexe} %*') } + if !os.exists(vsymlink) { + warn_and_exit('Could not create $vsymlink') + } + + print('Symlink $vsymlink to $vexe created.\n\nChecking system %PATH%...') + reg_sys_env_handle := get_reg_sys_env_handle() or { warn_and_exit(err) return @@ -104,7 +101,7 @@ fn setup_symlink_on_windows(){ println('configured.') } else { - print('not configured.\nSetting system %PATH%...') + print('not configured.\nAdding symlink directory to system %PATH%...') set_reg_value(reg_sys_env_handle, 'Path', new_sys_env_path) or { warn_and_exit(err) return @@ -115,12 +112,12 @@ fn setup_symlink_on_windows(){ print('Letting running process know to update their Environment...') send_setting_change_msg('Environment') or { eprintln('\n' + err) - warn_and_exit('You might need to run this again to have `v` in your %PATH%') + warn_and_exit('You might need to run this again to have the `v` command in your %PATH%') return } println('finished.\n\nNote: restart your shell/IDE to load the new %PATH%.') - println('\nAfter restarting your shell/IDE, give `v version` a try in another dir!') + println('After restarting your shell/IDE, give `v version` a try in another dir!') } } @@ -131,14 +128,13 @@ fn warn_and_exit(err string) { // get the system environment registry handle fn get_reg_sys_env_handle() ?voidptr { - $if windows { + $if windows { // wrap for cross-compile compat // open the registry key reg_key_path := 'Environment' reg_env_key := voidptr(0) // or HKEY (HANDLE) - if C.RegOpenKeyEx(hkey_current_user, reg_key_path.to_wide(), 0, 1 | 2, ®_env_key) != 0 { + if C.RegOpenKeyEx(os.hkey_current_user, reg_key_path.to_wide(), 0, 1 | 2, ®_env_key) != 0 { return error('Could not open "$reg_key_path" in the registry') } - return reg_env_key } return error('not on windows') @@ -171,11 +167,11 @@ fn set_reg_value(reg_key voidptr, key string, value string) ?bool { return error('not on windows') } -// broadcasts a message to all listening windows (explorer.exe in particular) +// Broadcasts a message to all listening windows (explorer.exe in particular) // letting them know that the system environment has changed and should be reloaded fn send_setting_change_msg(message_data string) ?bool { $if windows { - if C.SendMessageTimeout(hwnd_broadcast, 0x001A, 0, message_data.to_wide(), 2, 5000, 0) == 0 { + if C.SendMessageTimeout(os.hwnd_broadcast, os.wm_settingchange, 0, message_data.to_wide(), os.smto_abortifhung, 5000, 0) == 0 { return error('Could not broadcast WM_SETTINGCHANGE') } return true diff --git a/make.bat b/make.bat index 869a5eb0a9..6c124487f7 100644 --- a/make.bat +++ b/make.bat @@ -2,8 +2,24 @@ echo Building V +REM default tcc +set tcc_url=https://github.com/vlang/tccbin_win +set tcc_dir=%~dp0thirdparty\tcc + +REM let a particular environment specify their own tcc +if "%TCC_GIT%" =="" goto :init +set tcc_url="%TCC_GIT%" + +:init +REM initialize the log file with the failure message set log_file=%TEMP%\v_make.bat.log -set tcc_path=%~dp0thirdparty\tcc\ +echo Failed to compile - Create an issue at 'https://github.com/vlang/v' with the following info:>%log_file% +echo.>>%log_file% + +REM alleviate weird issues with this var +set cloned_tcc= +set ERRORLEVEL= + pushd %~dp0 if "%~1"=="-local" goto :compile @@ -21,12 +37,14 @@ if exist "vc" ( :compile REM option to force msvc, gcc or tcc -if "%~1"=="-gcc" set force_gcc=1 & goto :gcc_strap -if "%~2"=="-gcc" set force_gcc=1 & goto :gcc_strap -if "%~1"=="-msvc" set force_msvc=1 & goto :msvc_strap -if "%~2"=="-msvc" set force_msvc=1 & goto :msvc_strap -if "%~1"=="-tcc" set force_tcc=1 & goto :tcc_strap -if "%~2"=="-tcc" set force_tcc=1 & goto :tcc_strap +if "%~1"=="-gcc" set force_gcc=1 & goto :gcc_strap +if "%~2"=="-gcc" set force_gcc=1 & goto :gcc_strap +if "%~1"=="-msvc" set force_msvc=1 & goto :msvc_strap +if "%~2"=="-msvc" set force_msvc=1 & goto :msvc_strap +if "%~1"=="-tcc" set force_tcc=1 & goto :tcc_strap +if "%~2"=="-tcc" set force_tcc=1 & goto :tcc_strap +if "%~1"=="-fresh_tcc" set force_tcc=1 & goto :fresh_tcc +if "%~2"=="-fresh_tcc" set force_tcc=1 & goto :fresh_tcc :gcc_strap echo. @@ -39,13 +57,14 @@ if %ERRORLEVEL% NEQ 0 ( goto :msvc_strap ) -gcc -std=c99 -municode -w -o v.exe vc\v_win.c>>%log_file% 2>>&1 +gcc -std=c99 -municode -w -o v.exe .\vc\v_win.c>>%log_file% 2>>&1 if %ERRORLEVEL% NEQ 0 ( rem In most cases, compile errors happen because the version of GCC installed is too old gcc --version>>%log_file% 2>>&1 goto :compile_error ) +echo ^> Compiling with .\v.exe self v.exe self>>%log_file% 2>>&1 if %ERRORLEVEL% NEQ 0 goto :compile_error goto :success @@ -79,16 +98,20 @@ if exist "%InstallDir%\Common7\Tools\vsdevcmd.bat" ( set ObjFile=.v.c.obj -cl.exe /nologo /w /volatile:ms /Fo%ObjFile% /O2 /MD /D_VBOOTSTRAP vc\v_win.c user32.lib kernel32.lib advapi32.lib shell32.lib /link /NOLOGO /OUT:v.exe /INCREMENTAL:NO>>%log_file% 2>>&1 +cl.exe /volatile:ms /Fo%ObjFile% /O2 /MD /D_VBOOTSTRAP vc\v_win.c user32.lib kernel32.lib advapi32.lib shell32.lib /link /nologo /out:v.exe /incremental:no>>%log_file% 2>>&1 if %ERRORLEVEL% NEQ 0 goto :compile_error +echo ^> Compiling with .\v.exe self v.exe -cc msvc self>>%log_file% 2>>&1 -del %ObjFile% +del %ObjFile%>>%log_file% 2>>&1 if %ERRORLEVEL% NEQ 0 goto :compile_error goto :success +:fresh_tcc +rd /s /q %tcc_dir% + :clone_tcc -git clone --depth 1 --quiet https://github.com/vlang/tccbin_win %tcc_path% +git clone --depth 1 --quiet %tcc_url% %tcc_dir% set cloned_tcc=1 goto :tcc_strap @@ -98,31 +121,33 @@ echo Attempting to build v.c with TCC... where /q tcc if %ERRORLEVEL% NEQ 0 ( - if exist "%tcc_path%" ( - set tcc_exe=%tcc_path%tcc.exe + if exist "%tcc_dir%" ( + set tcc_exe=%tcc_dir%\tcc.exe ) else if "%cloned_tcc%"=="" ( echo ^> TCC not found - echo ^> Downloading TCC from https://github.com/vlang/tccbin_win + echo ^> Downloading TCC from %tcc_url% goto :clone_tcc ) else ( - echo ^> TCC not found, even after cloning + echo ^> TCC not found, even after cloning %cloned_tcc% goto :error ) ) else ( for /f "delims=" %%i in ('where tcc') do set tcc_exe=%%i ) -if exist "%tcc_path%" ( +if exist "%tcc_dir%" ( if "%cloned_tcc%"=="" ( echo ^> Updating prebuilt TCC... - pushd "%tcc_path%" + pushd "%tcc_dir%"\ git pull -q popd ) ) -call "%tcc_exe%" -std=c99 -municode -lws2_32 -lshell32 -ladvapi32 -bt10 -w -o v.exe vc\v_win.c + +%tcc_exe% -std=c99 -municode -lws2_32 -lshell32 -ladvapi32 -bt10 -w -o v.exe vc\v_win.c if %ERRORLEVEL% NEQ 0 goto :compile_error +echo ^> Compiling with .\v.exe self v.exe -cc "%tcc_exe%" self>>%log_file% 2>>&1 if %ERRORLEVEL% NEQ 0 goto :compile_error goto :success @@ -130,8 +155,6 @@ goto :success :compile_error echo. echo. -echo Failed to compile - Create an issue at 'https://github.com/vlang' with the following info: -echo. type %log_file% del %log_file% goto :error @@ -144,21 +167,23 @@ exit /b 1 :success echo ^> V built successfully! -echo ^> To add V to your PATH, run `.\v symlink`. -del v_old.exe +echo ^> To add V to your PATH, run `.\v.exe symlink`. +del v_old.exe >>%log_file% 2>>&1 del %log_file% :version echo. echo | set /p="V version: " -v.exe version +.\v.exe version if "%cloned_tcc%" NEQ "" ( - echo. - echo WARNING: No C compiler was detected in your PATH. `tcc` was used temporarily - echo to build V, but it may have some bugs and may not work in all cases. - echo A more advanced C compiler like GCC or MSVC is recommended. - echo https://github.com/vlang/v/wiki/Installing-a-C-compiler-on-Windows - echo. + if "%force_tcc%" == "" ( + echo. + echo WARNING: No C compiler was detected in your PATH. `tcc` was used temporarily + echo to build V, but it may have some bugs and may not work in all cases. + echo A more advanced C compiler like GCC or MSVC is recommended. + echo https://github.com/vlang/v/wiki/Installing-a-C-compiler-on-Windows + echo. + ) ) popd diff --git a/vlib/builtin/cfns.c.v b/vlib/builtin/cfns.c.v index 8b1abad953..496c8dd0fa 100644 --- a/vlib/builtin/cfns.c.v +++ b/vlib/builtin/cfns.c.v @@ -191,9 +191,17 @@ fn C._fileno(int) int fn C._get_osfhandle(fd int) C.intptr_t +fn C.GetModuleFileName() int fn C.GetModuleFileNameW(hModule voidptr, lpFilename &u16, nSize u32) u32 +fn C.CreateFile() voidptr +fn C.CreateFileW(lpFilename &u16, dwDesiredAccess u32, dwShareMode u32, lpSecurityAttributes &u16, dwCreationDisposition u32, dwFlagsAndAttributes u32, hTemplateFile voidptr) u32 + + +fn C.GetFinalPathNameByHandleW(hFile voidptr, lpFilePath &u16, nSize u32, dwFlags u32) int + + fn C.CreatePipe(hReadPipe &voidptr, hWritePipe &voidptr, lpPipeAttributes voidptr, nSize u32) bool @@ -304,9 +312,6 @@ fn C.WriteConsole() voidptr fn C.WriteFile() voidptr -fn C.GetModuleFileName() int - - fn C._wchdir() diff --git a/vlib/os/const_windows.c.v b/vlib/os/const_windows.c.v index 8bb704a2dc..d7b2436ee8 100644 --- a/vlib/os/const_windows.c.v +++ b/vlib/os/const_windows.c.v @@ -2,63 +2,67 @@ module os // Ref - winnt.h const ( - success = 0 // ERROR_SUCCESS - error_insufficient_buffer = 130 + success = 0x0000 // ERROR_SUCCESS + error_insufficient_buffer = 0x0082 ) const ( - file_share_read = 1 - file_share_write = 2 - file_share_delete = 4 + handle_generic_read = 0x80000000 + handle_open_existing = 0x00000003 ) const ( - file_notify_change_file_name = 1 - file_notify_change_dir_name = 2 - file_notify_change_attributes = 4 - file_notify_change_size = 8 - file_notify_change_last_write = 16 - file_notify_change_last_access = 32 - file_notify_change_creation = 64 - file_notify_change_security = 128 + file_share_read = 0x01 + file_share_write = 0x02 + file_share_delete = 0x04 ) const ( - file_action_added = 1 - file_action_removed = 2 - file_action_modified = 3 - file_action_renamed_old_name = 4 - file_action_renamed_new_name = 5 + file_notify_change_file_name = 0x01 + file_notify_change_dir_name = 0x02 + file_notify_change_attributes = 0x04 + file_notify_change_size = 0x08 + file_notify_change_last_write = 0x10 + file_notify_change_last_access = 0x20 + file_notify_change_creation = 0x40 + file_notify_change_security = 0x80 ) const ( - file_attr_readonly = 0x1 - file_attr_hidden = 0x2 - file_attr_system = 0x4 - file_attr_directory = 0x10 - file_attr_archive = 0x20 - file_attr_device = 0x40 - file_attr_normal = 0x80 - file_attr_temporary = 0x100 - file_attr_sparse_file = 0x200 - file_attr_reparse_point = 0x400 - file_attr_compressed = 0x800 - file_attr_offline = 0x1000 - file_attr_not_content_indexed = 0x2000 - file_attr_encrypted = 0x4000 - file_attr_integrity_stream = 0x8000 - file_attr_virtual = 0x10000 - file_attr_no_scrub_data = 0x20000 + file_action_added = 0x01 + file_action_removed = 0x02 + file_action_modified = 0x03 + file_action_renamed_old_name = 0x04 + file_action_renamed_new_name = 0x05 +) + +const ( + file_attr_readonly = 0x00000001 + file_attr_hidden = 0x00000002 + file_attr_system = 0x00000004 + file_attr_directory = 0x00000010 + file_attr_archive = 0x00000020 + file_attr_device = 0x00000040 + file_attr_normal = 0x00000080 + file_attr_temporary = 0x00000100 + file_attr_sparse_file = 0x00000200 + file_attr_reparse_point = 0x00000400 + file_attr_compressed = 0x00000800 + file_attr_offline = 0x00001000 + file_attr_not_content_indexed = 0x00002000 + file_attr_encrypted = 0x00004000 + file_attr_integrity_stream = 0x00008000 + file_attr_virtual = 0x00010000 + file_attr_no_scrub_data = 0x00020000 // file_attr_recall_on_open = u32(0x...) // file_attr_recall_on_data_access = u32(0x...) ) const ( - file_type_disk = 0x1 - file_type_char = 0x2 - file_type_pipe = 0x3 - - file_type_unknown = 0x0 + file_type_unknown = 0x00 + file_type_disk = 0x01 + file_type_char = 0x02 + file_type_pipe = 0x03 ) const ( @@ -82,58 +86,79 @@ const ( enable_window_input = 0x0008 enable_virtual_terminal_input = 0x0200 // Output Screen Buffer - enable_processed_output = 0x0001 - enable_wrap_at_eol_output = 0x0002 - enable_virtual_terminal_processing = 0x0004 - disable_newline_auto_return = 0x0008 - enable_lvb_grid_worldwide = 0x0010 + enable_processed_output = 0x01 + enable_wrap_at_eol_output = 0x02 + enable_virtual_terminal_processing = 0x04 + disable_newline_auto_return = 0x08 + enable_lvb_grid_worldwide = 0x10 ) // File modes const ( - o_rdonly = 0 // open the file read-only. - o_wronly = 1 // open the file write-only. - o_rdwr = 2 // open the file read-write. - o_append = 0x0008 // append data to the file when writing. - o_create = 0x0100 // create a new file if none exists. - o_trunc = 0x0200 // truncate regular writable file when opened. - o_excl = 0x0400 // used with o_create, file must not exist. - o_sync = 0 // open for synchronous I/O (ignored on Windows) - o_noctty = 0 // make file non-controlling tty (ignored on Windows) - o_nonblock = 0 // don't block on opening file (ignored on Windows) + o_rdonly = 0x0000 // open the file read-only. + o_wronly = 0x0001 // open the file write-only. + o_rdwr = 0x0002 // open the file read-write. + o_append = 0x0008 // append data to the file when writing. + o_create = 0x0100 // create a new file if none exists. + o_trunc = 0x0200 // truncate regular writable file when opened. + o_excl = 0x0400 // used with o_create, file must not exist. + o_sync = 0x0000 // open for synchronous I/O (ignored on Windows) + o_noctty = 0x0000 // make file non-controlling tty (ignored on Windows) + o_nonblock = 0x0000 // don't block on opening file (ignored on Windows) ) const ( - status_access_violation = 0xC0000005 - status_in_page_error = 0xC0000006 - status_invalid_handle = 0xC0000008 - status_invalid_parameter = 0xC000000D - status_no_memory = 0xC0000017 - status_illegal_instruction = 0xC000001D - status_noncontinuable_exception = 0xC0000025 - status_invalid_disposition = 0xC0000026 - status_array_bounds_exceeded = 0xC000008C - status_float_denormal_operand = 0xC000008D - status_float_divide_by_zero = 0xC000008E - status_float_inexact_result = 0xC000008F - status_float_invalid_operation = 0xC0000090 - status_float_overflow = 0xC0000091 - status_float_stack_check = 0xC0000092 - status_float_underflow = 0xC0000093 - status_integer_divide_by_zero = 0xC0000094 - status_integer_overflow = 0xC0000095 - status_privileged_instruction = 0xC0000096 - status_stack_overflow = 0xC00000FD - status_dll_not_found = 0xC0000135 - status_ordinal_not_found = 0xC0000138 - status_entrypoint_not_found = 0xC0000139 - status_control_c_exit = 0xC000013A - status_dll_init_failed = 0xC0000142 - status_float_multiple_faults = 0xC00002B4 - status_float_multiple_traps = 0xC00002B5 - status_reg_nat_consumption = 0xC00002C9 - status_heap_corruption = 0xC0000374 - status_stack_buffer_overrun = 0xC0000409 - status_invalid_cruntime_parameter = 0xC0000417 - status_assertion_failure = 0xC0000420 + status_access_violation = 0xC0000005 + status_in_page_error = 0xC0000006 + status_invalid_handle = 0xC0000008 + status_invalid_parameter = 0xC000000D + status_no_memory = 0xC0000017 + status_illegal_instruction = 0xC000001D + status_noncontinuable_exception = 0xC0000025 + status_invalid_disposition = 0xC0000026 + status_array_bounds_exceeded = 0xC000008C + status_float_denormal_operand = 0xC000008D + status_float_divide_by_zero = 0xC000008E + status_float_inexact_result = 0xC000008F + status_float_invalid_operation = 0xC0000090 + status_float_overflow = 0xC0000091 + status_float_stack_check = 0xC0000092 + status_float_underflow = 0xC0000093 + status_integer_divide_by_zero = 0xC0000094 + status_integer_overflow = 0xC0000095 + status_privileged_instruction = 0xC0000096 + status_stack_overflow = 0xC00000FD + status_dll_not_found = 0xC0000135 + status_ordinal_not_found = 0xC0000138 + status_entrypoint_not_found = 0xC0000139 + status_control_c_exit = 0xC000013A + status_dll_init_failed = 0xC0000142 + status_float_multiple_faults = 0xC00002B4 + status_float_multiple_traps = 0xC00002B5 + status_reg_nat_consumption = 0xC00002C9 + status_heap_corruption = 0xC0000374 + status_stack_buffer_overrun = 0xC0000409 + status_invalid_cruntime_parameter = 0xC0000417 + status_assertion_failure = 0xC0000420 ) + +// Windows Registry Constants + +pub const ( + hkey_local_machine = voidptr(0x80000002) + hkey_current_user = voidptr(0x80000001) + + key_query_value = 0x0001 + key_set_value = 0x0002 + key_enumerate_sub_keys = 0x0008 + key_wow64_32key = 0x0200 +) + +// Windows Messages + +pub const ( + hwnd_broadcast = voidptr(0xFFFF) + + wm_settingchange = 0x001A + smto_abortifhung = 0x0002 +) \ No newline at end of file diff --git a/vlib/os/os.v b/vlib/os/os.v index da8d05642f..83ccc3e237 100644 --- a/vlib/os/os.v +++ b/vlib/os/os.v @@ -948,8 +948,30 @@ pub fn executable() string { } $if windows { max := 512 - mut result := &u16(vcalloc(max * 2)) // max_path_len * sizeof(wchar_t) + size := max * 2 // max_path_len * sizeof(wchar_t) + mut result := &u16(vcalloc(size)) len := C.GetModuleFileName(0, result, max) + // determine if the file is a windows symlink + attrs := C.GetFileAttributesW(result) + is_set := attrs & 0x400 // FILE_ATTRIBUTE_REPARSE_POINT + if is_set != 0 { // it's a windows symlink + // gets handle with GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 + file := C.CreateFile(result, 0x80000000, 1, 0, 3, 0x80, 0) + if file != -1 { + final_path := &u16(vcalloc(size)) + // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew + final_len := C.GetFinalPathNameByHandleW(file, final_path, size, 0) + if final_len < size { + ret := string_from_wide2(final_path, final_len) + // remove '\\?\' from beginning (see link above) + return ret[4..] + } + else { + eprintln('os.executable() saw that the executable file path was too long') + } + } + C.CloseHandle(file) + } return string_from_wide2(result, len) } $if macos { @@ -1003,6 +1025,11 @@ fn executable_fallback() string { return '' } mut exepath := os.args[0] + $if windows { + if !exepath.contains('.exe') { + exepath += '.exe' + } + } if !os.is_abs_path(exepath) { if exepath.contains( os.path_separator ) { exepath = os.join_path(os.wd_at_startup, exepath)