v / cmd / tools
Raw file | 205 loc (191 sloc) | 6.43 KB | Latest commit hash 017ace6ea
1import os
2
3$if windows {
4 $if tinyc {
5 #flag -ladvapi32
6 #flag -luser32
7 }
8}
9
10fn main() {
11 C.atexit(cleanup_vtmp_folder)
12
13 if os.args.len > 3 {
14 print('usage: v symlink [OPTIONS]')
15 exit(1)
16 }
17 vexe := os.real_path(os.getenv_opt('VEXE') or { @VEXE })
18
19 if '-githubci' in os.args {
20 setup_symlink_github()
21 } else {
22 $if windows {
23 setup_symlink_windows(vexe)
24 } $else {
25 setup_symlink_unix(vexe)
26 }
27 }
28}
29
30fn cleanup_vtmp_folder() {
31 os.rmdir_all(os.vtmp_dir()) or {}
32}
33
34fn setup_symlink_github() {
35 // We append V's install location (which should
36 // be the current directory) to the PATH environment variable.
37
38 // Resources:
39 // 1. https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files
40 // 2. https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-environment-variable
41 mut content := os.read_file(os.getenv('GITHUB_PATH')) or {
42 panic('Failed to read GITHUB_PATH.')
43 }
44 content += '\n${os.getwd()}\n'
45 os.write_file(os.getenv('GITHUB_PATH'), content) or { panic('Failed to write to GITHUB_PATH.') }
46}
47
48fn setup_symlink_unix(vexe string) {
49 mut link_path := '/data/data/com.termux/files/usr/bin/v'
50 if !os.is_dir('/data/data/com.termux/files') {
51 link_dir := '/usr/local/bin'
52 if !os.exists(link_dir) {
53 os.mkdir_all(link_dir) or { panic(err) }
54 }
55 link_path = link_dir + '/v'
56 }
57 os.rm(link_path) or {}
58 os.symlink(vexe, link_path) or {
59 eprintln('Failed to create symlink "${link_path}". Try again with sudo.')
60 exit(1)
61 }
62}
63
64fn setup_symlink_windows(vexe string) {
65 $if windows {
66 // Create a symlink in a new local folder (.\.bin\.v.exe)
67 // Puts `v` in %PATH% without polluting it with anything else (like make.bat).
68 // This will make `v` available on cmd.exe, PowerShell, and MinGW(MSYS)/WSL/Cygwin
69 vdir := os.real_path(os.dir(vexe))
70 vsymlinkdir := os.join_path(vdir, '.bin')
71 mut vsymlink := os.join_path(vsymlinkdir, 'v.exe')
72 // Remove old symlink first (v could have been moved, symlink rerun)
73 if !os.exists(vsymlinkdir) {
74 os.mkdir(vsymlinkdir) or { panic(err) }
75 } else {
76 if os.exists(vsymlink) {
77 os.rm(vsymlink) or { panic(err) }
78 } else {
79 vsymlink = os.join_path(vsymlinkdir, 'v.bat')
80 if os.exists(vsymlink) {
81 os.rm(vsymlink) or { panic(err) }
82 }
83 vsymlink = os.join_path(vsymlinkdir, 'v.exe')
84 }
85 }
86 // First, try to create a native symlink at .\.bin\v.exe
87 os.symlink(vexe, vsymlink) or {
88 // typically only fails if you're on a network drive (VirtualBox)
89 // do batch file creation instead
90 eprintln('Could not create a native symlink: ${err}')
91 eprintln('Creating a batch file instead...')
92 vsymlink = os.join_path(vsymlinkdir, 'v.bat')
93 if os.exists(vsymlink) {
94 os.rm(vsymlink) or { panic(err) }
95 }
96 os.write_file(vsymlink, '@echo off\n"${vexe}" %*') or { panic(err) }
97 eprintln('${vsymlink} file written.')
98 }
99 if !os.exists(vsymlink) {
100 warn_and_exit('Could not create ${vsymlink}')
101 }
102 println('Symlink ${vsymlink} to ${vexe} created.')
103 println('Checking system %PATH%...')
104 reg_sys_env_handle := get_reg_sys_env_handle() or {
105 warn_and_exit(err.msg())
106 return
107 }
108 // TODO: Fix defers inside ifs
109 // defer {
110 // C.RegCloseKey(reg_sys_env_handle)
111 // }
112 // if the above succeeded, and we cannot get the value, it may simply be empty
113 sys_env_path := get_reg_value(reg_sys_env_handle, 'Path') or { '' }
114 current_sys_paths := sys_env_path.split(os.path_delimiter).map(it.trim('/${os.path_separator}'))
115 mut new_paths := [vsymlinkdir]
116 for p in current_sys_paths {
117 if p == '' {
118 continue
119 }
120 if p !in new_paths {
121 new_paths << p
122 }
123 }
124 new_sys_env_path := new_paths.join(os.path_delimiter)
125 if new_sys_env_path == sys_env_path {
126 println('System %PATH% was already configured.')
127 } else {
128 println('System %PATH% was not configured.')
129 println('Adding symlink directory to system %PATH%...')
130 set_reg_value(reg_sys_env_handle, 'Path', new_sys_env_path) or {
131 C.RegCloseKey(reg_sys_env_handle)
132 warn_and_exit(err.msg())
133 }
134 println('Done.')
135 }
136 println('Notifying running processes to update their Environment...')
137 send_setting_change_msg('Environment') or {
138 eprintln(err)
139 C.RegCloseKey(reg_sys_env_handle)
140 warn_and_exit('You might need to run this again to have the `v` command in your %PATH%')
141 }
142 C.RegCloseKey(reg_sys_env_handle)
143 println('Done.')
144 println('Note: Restart your shell/IDE to load the new %PATH%.')
145 println('After restarting your shell/IDE, give `v version` a try in another directory!')
146 }
147}
148
149fn warn_and_exit(err string) {
150 eprintln(err)
151 exit(1)
152}
153
154// get the system environment registry handle
155fn get_reg_sys_env_handle() !voidptr {
156 $if windows { // wrap for cross-compile compat
157 // open the registry key
158 reg_key_path := 'Environment'
159 reg_env_key := unsafe { nil } // or HKEY (HANDLE)
160 if C.RegOpenKeyEx(os.hkey_current_user, reg_key_path.to_wide(), 0, 1 | 2, ®_env_key) != 0 {
161 return error('Could not open "${reg_key_path}" in the registry')
162 }
163 return reg_env_key
164 }
165 return error('not on windows')
166}
167
168// get a value from a given $key
169fn get_reg_value(reg_env_key voidptr, key string) !string {
170 $if windows {
171 // query the value (shortcut the sizing step)
172 reg_value_size := u32(4095) // this is the max length (not for the registry, but for the system %PATH%)
173 mut reg_value := unsafe { &u16(malloc(int(reg_value_size))) }
174 if C.RegQueryValueExW(reg_env_key, key.to_wide(), 0, 0, reg_value, ®_value_size) != 0 {
175 return error('Unable to get registry value for "${key}".')
176 }
177 return unsafe { string_from_wide(reg_value) }
178 }
179 return error('not on windows')
180}
181
182// sets the value for the given $key to the given $value
183fn set_reg_value(reg_key voidptr, key string, value string) !bool {
184 $if windows {
185 if C.RegSetValueExW(reg_key, key.to_wide(), 0, C.REG_EXPAND_SZ, value.to_wide(),
186 value.len * 2) != 0 {
187 return error('Unable to set registry value for "${key}". %PATH% may be too long.')
188 }
189 return true
190 }
191 return error('not on windows')
192}
193
194// Broadcasts a message to all listening windows (explorer.exe in particular)
195// letting them know that the system environment has changed and should be reloaded
196fn send_setting_change_msg(message_data string) !bool {
197 $if windows {
198 if C.SendMessageTimeoutW(os.hwnd_broadcast, os.wm_settingchange, 0, unsafe { &u32(message_data.to_wide()) },
199 os.smto_abortifhung, 5000, 0) == 0 {
200 return error('Could not broadcast WM_SETTINGCHANGE')
201 }
202 return true
203 }
204 return error('not on windows')
205}