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 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
// ¯\_(ツ)_/¯
// 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')
vsymlinkbat := os.join_path(vsymlinkdir, 'v.bat')
if os.exists(vsymlinkbat) {
print('Batch script $vsymlinkbat already exists, checking system %PATH%...')
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)
// 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)
}
else {
print('Created $vsymlinkbat, checking system %PATH%...')
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, &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 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

View File

@ -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
@ -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 "%~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,15 +167,16 @@ 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 "" (
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.
@ -160,5 +184,6 @@ if "%cloned_tcc%" NEQ "" (
echo https://github.com/vlang/v/wiki/Installing-a-C-compiler-on-Windows
echo.
)
)
popd

View File

@ -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()

View File

@ -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,25 +86,25 @@ 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_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 = 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_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 (
@ -137,3 +141,24 @@ const (
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
)

View File

@ -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)