v / cmd / tools / vsymlink / vsymlink_windows.c.v
158 lines · 149 sloc · 5.58 KB · d444f9c25dea51ee70d1a8ec896b34484370e791
Raw
1import os
2
3$if tinyc {
4 #flag -ladvapi32
5 #flag -luser32
6}
7
8fn setup_symlink(custom_link_dir string) {
9 // Create a symlink in a local folder (by default .\.bin\v.exe).
10 // Puts `v` in %PATH% without polluting it with anything else (like makev.bat).
11 // This will make `v` available on cmd.exe, PowerShell, and MinGW(MSYS)/WSL/Cygwin
12 vsymlinkdir := normalized_link_dir(custom_link_dir)
13 mut vsymlink := symlink_path(vsymlinkdir)
14 // Remove old symlink first (v could have been moved, symlink rerun)
15 if !os.exists(vsymlinkdir) {
16 os.mkdir_all(vsymlinkdir) or { panic(err) }
17 } else {
18 if os.exists(vsymlink) {
19 os.rm(vsymlink) or { panic(err) }
20 } else {
21 vsymlink = os.join_path(vsymlinkdir, 'v.bat')
22 if os.exists(vsymlink) {
23 os.rm(vsymlink) or { panic(err) }
24 }
25 vsymlink = os.join_path(vsymlinkdir, 'v.exe')
26 }
27 }
28 // First, try to create a native symlink in the configured directory.
29 os.symlink(vexe, vsymlink) or {
30 // typically only fails if you're on a network drive (VirtualBox)
31 // do batch file creation instead
32 eprintln('Could not create a native symlink: ${err}')
33 eprintln('Creating a batch file instead...')
34 vsymlink = os.join_path(vsymlinkdir, 'v.bat')
35 if os.exists(vsymlink) {
36 os.rm(vsymlink) or { panic(err) }
37 }
38 os.write_file(vsymlink, '@echo off\n"${vexe}" %*') or { panic(err) }
39 eprintln('${vsymlink} file written.')
40 }
41 if !os.exists(vsymlink) {
42 warn_and_exit('Could not create ${vsymlink}')
43 }
44 println('Symlink ${vsymlink} to ${vexe} created.')
45 println('Checking system %PATH%...')
46 reg_sys_env_handle := get_reg_sys_env_handle() or {
47 warn_and_exit(err.msg())
48 return
49 }
50 // TODO: Fix defers inside ifs
51 // defer {
52 // C.RegCloseKey(reg_sys_env_handle)
53 // }
54 // if the above succeeded, and we cannot get the value, it may simply be empty
55 sys_env_path := get_reg_value(reg_sys_env_handle, 'Path') or { '' }
56 current_sys_paths := sys_env_path.split(os.path_delimiter).map(it.trim('/${os.path_separator}'))
57 mut new_paths := [vsymlinkdir]
58 for p in current_sys_paths {
59 if p == '' {
60 continue
61 }
62 if p !in new_paths {
63 new_paths << p
64 }
65 }
66 new_sys_env_path := new_paths.join(os.path_delimiter)
67 if new_sys_env_path == sys_env_path {
68 println('System %PATH% was already configured.')
69 } else {
70 println('System %PATH% was not configured.')
71 println('Adding symlink directory to system %PATH%...')
72 set_reg_value(reg_sys_env_handle, 'Path', new_sys_env_path) or {
73 C.RegCloseKey(reg_sys_env_handle)
74 warn_and_exit(err.msg())
75 }
76 println('Done.')
77 }
78 println('Notifying running processes to update their Environment...')
79 send_setting_change_msg('Environment') or {
80 eprintln(err)
81 C.RegCloseKey(reg_sys_env_handle)
82 warn_and_exit('You might need to run this again to have the `v` command in your %PATH%')
83 }
84 C.RegCloseKey(reg_sys_env_handle)
85 if os.getenv('GITHUB_JOB') != '' {
86 // Append V's install location to GITHUBs PATH environment variable.
87 // Resources:
88 // 1. https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files
89 // 2. https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-environment-variable
90 mut content := os.read_file(os.getenv('GITHUB_PATH')) or {
91 eprintln('The `GITHUB_PATH` env variable is not defined.')
92 exit(1)
93 }
94 content += '\n${new_sys_env_path}\n'
95 os.write_file(os.getenv('GITHUB_PATH'), content) or {
96 panic('Failed to write to GITHUB_PATH.')
97 }
98 }
99 println('Done.')
100 println('Note: Restart your shell/IDE to load the new %PATH%.')
101 println('After restarting your shell/IDE, give `v version` a try in another directory!')
102}
103
104fn default_link_dir() string {
105 vdir := os.real_path(os.dir(vexe))
106 return os.join_path(vdir, '.bin')
107}
108
109fn symlink_path(link_dir string) string {
110 return os.join_path(link_dir, 'v.exe')
111}
112
113fn warn_and_exit(err string) {
114 eprintln(err)
115 exit(1)
116}
117
118// get the system environment registry handle
119fn get_reg_sys_env_handle() !voidptr {
120 // open the registry key
121 reg_key_path := 'Environment'
122 mut reg_env_key := os.hkey_current_user // placeholder, overwritten by RegOpenKeyEx
123 if C.RegOpenKeyEx(os.hkey_current_user, reg_key_path.to_wide(), 0, 1 | 2, voidptr(®_env_key)) != 0 {
124 return error('Could not open "${reg_key_path}" in the registry')
125 }
126 return reg_env_key
127}
128
129// get a value from a given $key
130fn get_reg_value(reg_env_key voidptr, key string) !string {
131 // query the value (shortcut the sizing step)
132 reg_value_size := u32(4095) // this is the max length (not for the registry, but for the system %PATH%)
133 reg_value_cap := int(reg_value_size / u32(sizeof(u16))) + 1
134 mut reg_value := []u16{len: reg_value_cap}
135 if C.RegQueryValueExW(reg_env_key, key.to_wide(), 0, 0, &u8(reg_value.data), ®_value_size) != 0 {
136 return error('Unable to get registry value for "${key}".')
137 }
138 return unsafe { string_from_wide(reg_value.data) }
139}
140
141// sets the value for the given $key to the given $value
142fn set_reg_value(reg_key voidptr, key string, value string) !bool {
143 if C.RegSetValueExW(reg_key, key.to_wide(), 0, C.REG_EXPAND_SZ, value.to_wide(), value.len * 2) != 0 {
144 return error('Unable to set registry value for "${key}". %PATH% may be too long.')
145 }
146 return true
147}
148
149// Broadcasts a message to all listening windows (explorer.exe in particular)
150// letting them know that the system environment has changed and should be reloaded
151fn send_setting_change_msg(message_data string) !bool {
152 message_data_wide := message_data.to_wide()
153 if C.SendMessageTimeoutW(os.hwnd_broadcast, os.wm_settingchange, 0,
154 unsafe { &u32(message_data_wide) }, os.smto_abortifhung, 5000, 0) == 0 {
155 return error('Could not broadcast WM_SETTINGCHANGE')
156 }
157 return true
158}
159