vsymlink: real Windows symbolic link, fallback to batch, make.bat updates (#5841)

pull/5852/head
Ryan Willis 2020-07-16 09:33:26 -07:00 committed by GitHub
parent f3a505b558
commit f66967a88c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 244 additions and 166 deletions

View File

@ -1,11 +1,6 @@
import os import os
import v.pref import v.pref
const (
hkey_current_user = voidptr(0x80000001)
hwnd_broadcast = voidptr(0xffff)
)
$if windows { $if windows {
$if tinyc { $if tinyc {
#flag -lAdvapi32 #flag -lAdvapi32
@ -14,15 +9,15 @@ $if windows {
} }
fn main(){ fn main(){
vexe := pref.vexe_path()
$if windows { $if windows {
setup_symlink_on_windows() setup_symlink_windows(vexe)
} $else { } $else {
setup_symlink_on_unix() setup_symlink(vexe)
} }
} }
fn setup_symlink_on_unix(){ fn setup_symlink(vexe string){
vexe := pref.vexe_path()
mut link_path := '/usr/local/bin/v' mut link_path := '/usr/local/bin/v'
mut ret := os.exec('ln -sf $vexe $link_path') or { mut ret := os.exec('ln -sf $vexe $link_path') or {
panic(err) panic(err)
@ -45,40 +40,42 @@ fn setup_symlink_on_unix(){
} }
} }
fn setup_symlink_on_windows(){ fn setup_symlink_windows(vexe string){
$if windows { $if windows {
vexe := pref.vexe_path() // Create a symlink in a new local folder (.\.bin\.v.exe)
// NB: Putting $vdir directly into PATH will also result in // Puts `v` in %PATH% without polluting it with anything else (like make.bat).
// make.bat being global, which is NOT what we want. // This will make `v` available on cmd.exe, PowerShell, and MinGW(MSYS)/WSL/Cygwin
//
// 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)) vdir := os.real_path(os.dir(vexe))
vsymlinkdir := os.join_path(vdir, '.bin') vsymlinkdir := os.join_path(vdir, '.bin')
vsymlinkbat := os.join_path(vsymlinkdir, 'v.bat')
if os.exists(vsymlinkbat) { mut vsymlink := os.join_path(vsymlinkdir, 'v.exe')
print('Batch script $vsymlinkbat already exists, checking system %PATH%...')
if !os.exists(vsymlinkdir) {
os.mkdir_all(vsymlinkdir) // will panic if fails
} else {
os.rm(vsymlink)
} }
else {
os.rmdir_all(vsymlinkdir) // try to create a native symlink at .\.bin\v.exe
os.mkdir_all(vsymlinkdir) os.symlink(vsymlink, vexe) or {
os.write_file(vsymlinkbat, '@echo off\n${vexe} %*') // typically only fails if you're on a network drive (VirtualBox)
if !os.exists(vsymlinkbat) { // do batch file creation instead
eprintln('Could not create $vsymlinkbat') eprint('NOTE: Could not create a native symlink: $err')
exit(1) eprintln('Creating a batch file instead...')
vsymlink = os.join_path(vsymlinkdir, 'v.bat')
if os.exists(vsymlink) {
os.rm(vsymlink)
} }
else { os.write_file(vsymlink, '@echo off\n${vexe} %*')
print('Created $vsymlinkbat, checking system %PATH%...')
} }
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 { reg_sys_env_handle := get_reg_sys_env_handle() or {
warn_and_exit(err) warn_and_exit(err)
return return
@ -104,7 +101,7 @@ fn setup_symlink_on_windows(){
println('configured.') println('configured.')
} }
else { 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 { set_reg_value(reg_sys_env_handle, 'Path', new_sys_env_path) or {
warn_and_exit(err) warn_and_exit(err)
return return
@ -115,12 +112,12 @@ fn setup_symlink_on_windows(){
print('Letting running process know to update their Environment...') print('Letting running process know to update their Environment...')
send_setting_change_msg('Environment') or { send_setting_change_msg('Environment') or {
eprintln('\n' + err) 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 return
} }
println('finished.\n\nNote: restart your shell/IDE to load the new %PATH%.') 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 // get the system environment registry handle
fn get_reg_sys_env_handle() ?voidptr { fn get_reg_sys_env_handle() ?voidptr {
$if windows { $if windows { // wrap for cross-compile compat
// open the registry key // open the registry key
reg_key_path := 'Environment' reg_key_path := 'Environment'
reg_env_key := voidptr(0) // or HKEY (HANDLE) reg_env_key := voidptr(0) // or HKEY (HANDLE)
if C.RegOpenKeyEx(hkey_current_user, reg_key_path.to_wide(), 0, 1 | 2, &reg_env_key) != 0 { if C.RegOpenKeyEx(os.hkey_current_user, reg_key_path.to_wide(), 0, 1 | 2, &reg_env_key) != 0 {
return error('Could not open "$reg_key_path" in the registry') return error('Could not open "$reg_key_path" in the registry')
} }
return reg_env_key return reg_env_key
} }
return error('not on windows') 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') 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 // letting them know that the system environment has changed and should be reloaded
fn send_setting_change_msg(message_data string) ?bool { fn send_setting_change_msg(message_data string) ?bool {
$if windows { $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 error('Could not broadcast WM_SETTINGCHANGE')
} }
return true return true

View File

@ -2,8 +2,24 @@
echo Building V 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 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 pushd %~dp0
if "%~1"=="-local" goto :compile if "%~1"=="-local" goto :compile
@ -27,6 +43,8 @@ if "%~1"=="-msvc" set force_msvc=1 & goto :msvc_strap
if "%~2"=="-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 "%~1"=="-tcc" set force_tcc=1 & goto :tcc_strap
if "%~2"=="-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 :gcc_strap
echo. echo.
@ -39,13 +57,14 @@ if %ERRORLEVEL% NEQ 0 (
goto :msvc_strap 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 ( if %ERRORLEVEL% NEQ 0 (
rem In most cases, compile errors happen because the version of GCC installed is too old rem In most cases, compile errors happen because the version of GCC installed is too old
gcc --version>>%log_file% 2>>&1 gcc --version>>%log_file% 2>>&1
goto :compile_error goto :compile_error
) )
echo ^> Compiling with .\v.exe self
v.exe self>>%log_file% 2>>&1 v.exe self>>%log_file% 2>>&1
if %ERRORLEVEL% NEQ 0 goto :compile_error if %ERRORLEVEL% NEQ 0 goto :compile_error
goto :success goto :success
@ -79,16 +98,20 @@ if exist "%InstallDir%\Common7\Tools\vsdevcmd.bat" (
set ObjFile=.v.c.obj 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 if %ERRORLEVEL% NEQ 0 goto :compile_error
echo ^> Compiling with .\v.exe self
v.exe -cc msvc self>>%log_file% 2>>&1 v.exe -cc msvc self>>%log_file% 2>>&1
del %ObjFile% del %ObjFile%>>%log_file% 2>>&1
if %ERRORLEVEL% NEQ 0 goto :compile_error if %ERRORLEVEL% NEQ 0 goto :compile_error
goto :success goto :success
:fresh_tcc
rd /s /q %tcc_dir%
:clone_tcc :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 set cloned_tcc=1
goto :tcc_strap goto :tcc_strap
@ -98,31 +121,33 @@ echo Attempting to build v.c with TCC...
where /q tcc where /q tcc
if %ERRORLEVEL% NEQ 0 ( if %ERRORLEVEL% NEQ 0 (
if exist "%tcc_path%" ( if exist "%tcc_dir%" (
set tcc_exe=%tcc_path%tcc.exe set tcc_exe=%tcc_dir%\tcc.exe
) else if "%cloned_tcc%"=="" ( ) else if "%cloned_tcc%"=="" (
echo ^> TCC not found echo ^> TCC not found
echo ^> Downloading TCC from https://github.com/vlang/tccbin_win echo ^> Downloading TCC from %tcc_url%
goto :clone_tcc goto :clone_tcc
) else ( ) else (
echo ^> TCC not found, even after cloning echo ^> TCC not found, even after cloning %cloned_tcc%
goto :error goto :error
) )
) else ( ) else (
for /f "delims=" %%i in ('where tcc') do set tcc_exe=%%i for /f "delims=" %%i in ('where tcc') do set tcc_exe=%%i
) )
if exist "%tcc_path%" ( if exist "%tcc_dir%" (
if "%cloned_tcc%"=="" ( if "%cloned_tcc%"=="" (
echo ^> Updating prebuilt TCC... echo ^> Updating prebuilt TCC...
pushd "%tcc_path%" pushd "%tcc_dir%"\
git pull -q git pull -q
popd 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 if %ERRORLEVEL% NEQ 0 goto :compile_error
echo ^> Compiling with .\v.exe self
v.exe -cc "%tcc_exe%" self>>%log_file% 2>>&1 v.exe -cc "%tcc_exe%" self>>%log_file% 2>>&1
if %ERRORLEVEL% NEQ 0 goto :compile_error if %ERRORLEVEL% NEQ 0 goto :compile_error
goto :success goto :success
@ -130,8 +155,6 @@ goto :success
:compile_error :compile_error
echo. echo.
echo. echo.
echo Failed to compile - Create an issue at 'https://github.com/vlang' with the following info:
echo.
type %log_file% type %log_file%
del %log_file% del %log_file%
goto :error goto :error
@ -144,21 +167,23 @@ exit /b 1
:success :success
echo ^> V built successfully! echo ^> V built successfully!
echo ^> To add V to your PATH, run `.\v symlink`. echo ^> To add V to your PATH, run `.\v.exe symlink`.
del v_old.exe del v_old.exe >>%log_file% 2>>&1
del %log_file% del %log_file%
:version :version
echo. echo.
echo | set /p="V version: " echo | set /p="V version: "
v.exe version .\v.exe version
if "%cloned_tcc%" NEQ "" ( if "%cloned_tcc%" NEQ "" (
if "%force_tcc%" == "" (
echo. echo.
echo WARNING: No C compiler was detected in your PATH. `tcc` was used temporarily 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 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 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 https://github.com/vlang/v/wiki/Installing-a-C-compiler-on-Windows
echo. echo.
)
) )
popd popd

View File

@ -191,9 +191,17 @@ fn C._fileno(int) int
fn C._get_osfhandle(fd int) C.intptr_t 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.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 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.WriteFile() voidptr
fn C.GetModuleFileName() int
fn C._wchdir() fn C._wchdir()

View File

@ -2,63 +2,67 @@ module os
// Ref - winnt.h // Ref - winnt.h
const ( const (
success = 0 // ERROR_SUCCESS success = 0x0000 // ERROR_SUCCESS
error_insufficient_buffer = 130 error_insufficient_buffer = 0x0082
) )
const ( const (
file_share_read = 1 handle_generic_read = 0x80000000
file_share_write = 2 handle_open_existing = 0x00000003
file_share_delete = 4
) )
const ( const (
file_notify_change_file_name = 1 file_share_read = 0x01
file_notify_change_dir_name = 2 file_share_write = 0x02
file_notify_change_attributes = 4 file_share_delete = 0x04
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
) )
const ( const (
file_action_added = 1 file_notify_change_file_name = 0x01
file_action_removed = 2 file_notify_change_dir_name = 0x02
file_action_modified = 3 file_notify_change_attributes = 0x04
file_action_renamed_old_name = 4 file_notify_change_size = 0x08
file_action_renamed_new_name = 5 file_notify_change_last_write = 0x10
file_notify_change_last_access = 0x20
file_notify_change_creation = 0x40
file_notify_change_security = 0x80
) )
const ( const (
file_attr_readonly = 0x1 file_action_added = 0x01
file_attr_hidden = 0x2 file_action_removed = 0x02
file_attr_system = 0x4 file_action_modified = 0x03
file_attr_directory = 0x10 file_action_renamed_old_name = 0x04
file_attr_archive = 0x20 file_action_renamed_new_name = 0x05
file_attr_device = 0x40 )
file_attr_normal = 0x80
file_attr_temporary = 0x100 const (
file_attr_sparse_file = 0x200 file_attr_readonly = 0x00000001
file_attr_reparse_point = 0x400 file_attr_hidden = 0x00000002
file_attr_compressed = 0x800 file_attr_system = 0x00000004
file_attr_offline = 0x1000 file_attr_directory = 0x00000010
file_attr_not_content_indexed = 0x2000 file_attr_archive = 0x00000020
file_attr_encrypted = 0x4000 file_attr_device = 0x00000040
file_attr_integrity_stream = 0x8000 file_attr_normal = 0x00000080
file_attr_virtual = 0x10000 file_attr_temporary = 0x00000100
file_attr_no_scrub_data = 0x20000 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_open = u32(0x...)
// file_attr_recall_on_data_access = u32(0x...) // file_attr_recall_on_data_access = u32(0x...)
) )
const ( const (
file_type_disk = 0x1 file_type_unknown = 0x00
file_type_char = 0x2 file_type_disk = 0x01
file_type_pipe = 0x3 file_type_char = 0x02
file_type_pipe = 0x03
file_type_unknown = 0x0
) )
const ( const (
@ -82,25 +86,25 @@ const (
enable_window_input = 0x0008 enable_window_input = 0x0008
enable_virtual_terminal_input = 0x0200 enable_virtual_terminal_input = 0x0200
// Output Screen Buffer // Output Screen Buffer
enable_processed_output = 0x0001 enable_processed_output = 0x01
enable_wrap_at_eol_output = 0x0002 enable_wrap_at_eol_output = 0x02
enable_virtual_terminal_processing = 0x0004 enable_virtual_terminal_processing = 0x04
disable_newline_auto_return = 0x0008 disable_newline_auto_return = 0x08
enable_lvb_grid_worldwide = 0x0010 enable_lvb_grid_worldwide = 0x10
) )
// File modes // File modes
const ( const (
o_rdonly = 0 // open the file read-only. o_rdonly = 0x0000 // open the file read-only.
o_wronly = 1 // open the file write-only. o_wronly = 0x0001 // open the file write-only.
o_rdwr = 2 // open the file read-write. o_rdwr = 0x0002 // open the file read-write.
o_append = 0x0008 // append data to the file when writing. o_append = 0x0008 // append data to the file when writing.
o_create = 0x0100 // create a new file if none exists. o_create = 0x0100 // create a new file if none exists.
o_trunc = 0x0200 // truncate regular writable file when opened. o_trunc = 0x0200 // truncate regular writable file when opened.
o_excl = 0x0400 // used with o_create, file must not exist. o_excl = 0x0400 // used with o_create, file must not exist.
o_sync = 0 // open for synchronous I/O (ignored on Windows) o_sync = 0x0000 // open for synchronous I/O (ignored on Windows)
o_noctty = 0 // make file non-controlling tty (ignored on Windows) o_noctty = 0x0000 // make file non-controlling tty (ignored on Windows)
o_nonblock = 0 // don't block on opening file (ignored on Windows) o_nonblock = 0x0000 // don't block on opening file (ignored on Windows)
) )
const ( const (
@ -137,3 +141,24 @@ const (
status_invalid_cruntime_parameter = 0xC0000417 status_invalid_cruntime_parameter = 0xC0000417
status_assertion_failure = 0xC0000420 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
)

View File

@ -948,8 +948,30 @@ pub fn executable() string {
} }
$if windows { $if windows {
max := 512 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) 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) return string_from_wide2(result, len)
} }
$if macos { $if macos {
@ -1003,6 +1025,11 @@ fn executable_fallback() string {
return '' return ''
} }
mut exepath := os.args[0] mut exepath := os.args[0]
$if windows {
if !exepath.contains('.exe') {
exepath += '.exe'
}
}
if !os.is_abs_path(exepath) { if !os.is_abs_path(exepath) {
if exepath.contains( os.path_separator ) { if exepath.contains( os.path_separator ) {
exepath = os.join_path(os.wd_at_startup, exepath) exepath = os.join_path(os.wd_at_startup, exepath)