mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
tools/vsymlink: use the win32 api to update the system environment
This commit is contained in:
parent
0058b8253d
commit
08814d6de4
@ -1,6 +1,11 @@
|
||||
import os
|
||||
import v.pref
|
||||
|
||||
const (
|
||||
hkey_local_machine = voidptr(0x80000002)
|
||||
hwnd_broadcast = voidptr(0xffff)
|
||||
)
|
||||
|
||||
fn main(){
|
||||
$if windows {
|
||||
setup_symlink_on_windows()
|
||||
@ -34,56 +39,141 @@ fn setup_symlink_on_unix(){
|
||||
}
|
||||
|
||||
fn setup_symlink_on_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 .symlink/ . That .symlink/ folder can then be put
|
||||
// in PATH without poluting it with anything else - just a
|
||||
// `v` command will be available, simillar 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, '.symlink')
|
||||
vsymlinkbat := os.join_path(vsymlinkdir, 'v.bat')
|
||||
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)
|
||||
}
|
||||
println('Created $vsymlinkbat .')
|
||||
current_paths := os.getenv('PATH').split(';').map(it.trim('/\\'))
|
||||
if vsymlinkdir in current_paths {
|
||||
println('$vsymlinkdir is already on your PATH')
|
||||
println('Try running `v version`')
|
||||
exit(0)
|
||||
}
|
||||
// put vsymlinkdir first, prevent duplicates:
|
||||
mut new_paths := [ vsymlinkdir ]
|
||||
for p in current_paths {
|
||||
if p !in new_paths {
|
||||
new_paths << p
|
||||
$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 .symlink/ . That .symlink/ 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, '.symlink')
|
||||
vsymlinkbat := os.join_path(vsymlinkdir, 'v.bat')
|
||||
if os.exists(vsymlinkbat) {
|
||||
print('Batch script $vsymlinkbat already exists, checking system %PATH%...')
|
||||
}
|
||||
}
|
||||
//
|
||||
change_path_cmd := 'setx /M PATH "' + new_paths.join(';') +'"'
|
||||
println('Changing global PATH with:')
|
||||
println(change_path_cmd)
|
||||
res := os.system(change_path_cmd)
|
||||
if res == 0 {
|
||||
println('')
|
||||
println('$vsymlinkdir has been prepended to PATH.')
|
||||
println('Try running `v version`.')
|
||||
exit(0)
|
||||
} else {
|
||||
println('Could not run `setx`, probably you are not an administrator.')
|
||||
println('`v symlink` should be launched with admin privileges.')
|
||||
exit(1)
|
||||
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%...')
|
||||
}
|
||||
}
|
||||
|
||||
reg_sys_env_handle := get_reg_sys_env_handle() or {
|
||||
warn_and_exit(err)
|
||||
return
|
||||
}
|
||||
defer {
|
||||
C.RegCloseKey(reg_sys_env_handle)
|
||||
}
|
||||
|
||||
sys_env_path := get_reg_value(reg_sys_env_handle, 'Path') or {
|
||||
warn_and_exit(err)
|
||||
return
|
||||
}
|
||||
|
||||
current_sys_paths := sys_env_path.split(os.path_delimiter).map(it.trim('/$os.path_separator'))
|
||||
mut new_paths := [ vsymlinkdir ]
|
||||
for p in current_sys_paths {
|
||||
if p !in new_paths {
|
||||
new_paths << p
|
||||
}
|
||||
}
|
||||
|
||||
new_sys_env_path := new_paths.join(';')
|
||||
|
||||
if new_sys_env_path == sys_env_path {
|
||||
println('configured.')
|
||||
}
|
||||
else {
|
||||
print('not configured.\nSetting system %PATH%...')
|
||||
set_reg_value(reg_sys_env_handle, 'Path', new_sys_env_path) or {
|
||||
warn_and_exit(err)
|
||||
return
|
||||
}
|
||||
println('done.')
|
||||
}
|
||||
|
||||
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%')
|
||||
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!')
|
||||
}
|
||||
}
|
||||
|
||||
fn warn_and_exit(err string) {
|
||||
eprintln(err)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// get the system environment registry handle
|
||||
fn get_reg_sys_env_handle() ?voidptr {
|
||||
$if windows {
|
||||
// open the registry key
|
||||
reg_key_path := 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment'
|
||||
reg_env_key := voidptr(0) // or HKEY (HANDLE)
|
||||
if C.RegOpenKeyEx(hkey_local_machine, 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')
|
||||
}
|
||||
|
||||
// get a value from a given $key
|
||||
fn get_reg_value(reg_env_key voidptr, key string) ?string {
|
||||
$if windows {
|
||||
// query the value (shortcut the sizing step)
|
||||
reg_value_size := 4095 // this is the max length (not for the registry, but for the system %PATH%)
|
||||
mut reg_value := &u16(malloc(reg_value_size))
|
||||
if C.RegQueryValueEx(reg_env_key, key.to_wide(), 0, 0, reg_value, ®_value_size) != 0 {
|
||||
return error('Unable to get registry value for "$key", are you running as an Administrator?')
|
||||
}
|
||||
|
||||
return string_from_wide(reg_value)
|
||||
}
|
||||
return error('not on windows')
|
||||
}
|
||||
|
||||
// sets the value for the given $key to the given $value
|
||||
fn set_reg_value(reg_key voidptr, key string, value string) ?bool {
|
||||
$if windows {
|
||||
if C.RegSetValueEx(reg_key, key.to_wide(), 0, 1, value.to_wide(), 4095) != 0 {
|
||||
return error('Unable to set registry value for "$key", are you running as an Administrator?')
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
return error('not on windows')
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return error('Could not broadcast WM_SETTINGCHANGE')
|
||||
}
|
||||
return true
|
||||
}
|
||||
return error('not on windows')
|
||||
}
|
||||
|
@ -202,6 +202,10 @@ fn C.SetHandleInformation(hObject voidptr, dwMask u32, dw_flags u32) bool
|
||||
fn C.ExpandEnvironmentStringsW(lpSrc &u16, lpDst &u16, nSize u32) u32
|
||||
|
||||
|
||||
fn C.SendMessageTimeout() u32
|
||||
fn C.SendMessageTimeoutW(hWnd voidptr, Msg u32, wParam &u16, lParam &u32, fuFlags u32, uTimeout u32, lpdwResult &u64) u32
|
||||
|
||||
|
||||
fn C.CreateProcessW(lpApplicationName &u16, lpCommandLine &u16, lpProcessAttributes voidptr, lpThreadAttributes voidptr, bInheritHandles bool, dwCreationFlags u32, lpEnvironment voidptr, lpCurrentDirectory &u16, lpStartupInfo voidptr, lpProcessInformation voidptr) bool
|
||||
|
||||
|
||||
@ -211,18 +215,21 @@ fn C.ReadFile(hFile voidptr, lpBuffer voidptr, nNumberOfBytesToRead u32, lpNumbe
|
||||
fn C.GetFileAttributesW(lpFileName byteptr) u32
|
||||
|
||||
|
||||
fn C.RegQueryValueEx() voidptr
|
||||
fn C.RegQueryValueExW(hKey voidptr, lpValueName &u16, lp_reserved &u32, lpType &u32, lpData byteptr, lpcbData &u32) int
|
||||
|
||||
|
||||
fn C.RegOpenKeyEx() voidptr
|
||||
fn C.RegOpenKeyExW(hKey voidptr, lpSubKey &u16, ulOptions u32, samDesired u32, phkResult voidptr) int
|
||||
|
||||
|
||||
fn C.RegSetValueEx() voidptr
|
||||
fn C.RegSetValueExW(hKey voidptr, lpValueName &u16, Reserved u32, dwType u32, lpData byteptr, lpcbData u32) int
|
||||
|
||||
|
||||
fn C.RegCloseKey()
|
||||
|
||||
|
||||
fn C.RegQueryValueEx() voidptr
|
||||
|
||||
|
||||
fn C.RemoveDirectory() int
|
||||
|
||||
|
||||
@ -338,7 +345,6 @@ fn C.CloseHandle()
|
||||
fn C.GetExitCodeProcess()
|
||||
|
||||
|
||||
fn C.RegOpenKeyEx() voidptr
|
||||
|
||||
|
||||
fn C.GetTickCount() i64
|
||||
|
@ -118,7 +118,7 @@ fn find_windows_kit_root(host_arch string) ?WindowsKit {
|
||||
shared_include_path: kit_include_highest + '\\shared'
|
||||
}
|
||||
}
|
||||
return error('Host OS does not support funding a windows kit')
|
||||
return error('Host OS does not support finding a windows kit')
|
||||
}
|
||||
|
||||
struct VsInstallation {
|
||||
@ -129,7 +129,7 @@ struct VsInstallation {
|
||||
|
||||
fn find_vs(vswhere_dir, host_arch string) ?VsInstallation {
|
||||
$if !windows {
|
||||
return error('Host OS does not support finding a Vs installation')
|
||||
return error('Host OS does not support finding a Visual Studio installation')
|
||||
}
|
||||
// Emily:
|
||||
// VSWhere is guaranteed to be installed at this location now
|
||||
|
Loading…
Reference in New Issue
Block a user