v / cmd / tools / vdoctor.v
315 lines · 293 sloc · 8.62 KB · 6c7c2ce75c6004889285cad0ebe19bb4d4e3be0c
Raw
1import os
2import time
3import term
4import v.util.version
5import runtime
6
7struct App {
8mut:
9 report_lines []string
10 cached_cpuinfo map[string]string
11 vexe string
12}
13
14fn (mut a App) println(s string) {
15 a.report_lines << s
16}
17
18fn (mut a App) collect_info() {
19 a.line('V full version', version.full_v_version(true))
20 a.line(':-------------------', ':-------------------')
21
22 mut os_kind := os.user_os()
23 mut arch_details := []string{}
24 arch_details << '${runtime.nr_cpus()} cpus'
25 if runtime.is_32bit() {
26 arch_details << '32bit'
27 }
28 if runtime.is_64bit() {
29 arch_details << '64bit'
30 }
31 if runtime.is_big_endian() {
32 arch_details << 'big endian'
33 }
34 if runtime.is_little_endian() {
35 arch_details << 'little endian'
36 }
37 if os_kind == 'macos' {
38 arch_details << a.cmd(command: 'sysctl -n machdep.cpu.brand_string')
39 }
40 if os_kind == 'linux' {
41 mut cpu_details := ''
42 if cpu_details == '' {
43 cpu_details = a.cpu_info('model name')
44 }
45 if cpu_details == '' {
46 cpu_details = a.cpu_info('hardware')
47 }
48 if cpu_details == '' {
49 cpu_details = os.uname().machine
50 }
51 arch_details << cpu_details
52 }
53 if os_kind == 'windows' {
54 arch_details << a.cmd(
55 command: 'wmic cpu get name /format:table'
56 line: 2
57 )
58 }
59
60 mut os_details := ''
61 wsl_check := a.cmd(command: 'cat /proc/sys/kernel/osrelease')
62 if os_kind == 'linux' {
63 os_details = a.get_linux_os_name()
64 if a.cpu_info('flags').contains('hypervisor') {
65 if wsl_check.contains('microsoft') {
66 // WSL 2 is a Managed VM and Full Linux Kernel
67 // See https://docs.microsoft.com/en-us/windows/wsl/compare-versions
68 os_details += ' (WSL 2)'
69 } else {
70 os_details += ' (VM)'
71 }
72 }
73 // WSL 1 is NOT a Managed VM and Full Linux Kernel
74 // See https://docs.microsoft.com/en-us/windows/wsl/compare-versions
75 if wsl_check.contains('Microsoft') {
76 os_details += ' (WSL)'
77 }
78 // From https://unix.stackexchange.com/a/14346
79 awk_cmd := '[ "$(awk \'\$5=="/" {print \$1}\' </proc/1/mountinfo)" != "$(awk \'\$5=="/" {print \$1}\' </proc/$$/mountinfo)" ] ; echo \$?'
80 if a.cmd(command: awk_cmd) == '0' {
81 os_details += ' (chroot)'
82 }
83 } else if os_kind == 'macos' {
84 mut details := []string{}
85 details << a.cmd(command: 'sw_vers -productName')
86 details << a.cmd(command: 'sw_vers -productVersion')
87 details << a.cmd(command: 'sw_vers -buildVersion')
88 os_details = details.join(', ')
89 } else if os_kind == 'windows' {
90 wmic_info := a.cmd(
91 command: 'wmic os get * /format:value'
92 line: -1
93 )
94 p := a.parse(wmic_info, '=')
95 mut caption, mut build_number, mut os_arch := p['caption'], p['buildnumber'], p['osarchitecture']
96 os_details = '${caption} ${build_number} ${os_arch}'
97 } else {
98 ouname := os.uname()
99 os_details = '${ouname.release}, ${ouname.version}'
100 }
101 a.line('OS', '${os_kind}, ${os_details}')
102 a.line('Processor', arch_details.join(', '))
103 total_memory := f32(runtime.total_memory() or { 0 }) / (1024.0 * 1024.0 * 1024.0)
104 free_memory := f32(runtime.free_memory() or { 0 }) / (1024.0 * 1024.0 * 1024.0)
105 if total_memory != 0 && free_memory != 0 {
106 a.line('Memory', '${free_memory:.2}GB/${total_memory:.2}GB')
107 } else {
108 a.line('Memory', 'N/A')
109 }
110
111 a.line('', '')
112 vexe := a.vexe
113 vroot := os.dir(vexe)
114 vmodules := os.vmodules_dir()
115 vtmp_dir := os.vtmp_dir()
116 getwd := os.getwd()
117 os.chdir(vroot) or {}
118 a.line('V executable', vexe)
119 a.line('V last modified time', time.unix(os.file_last_mod_unix(vexe)).str())
120 a.line('', '')
121 a.line2('V home dir', diagnose_dir(vroot), vroot)
122 a.line2('VMODULES', diagnose_dir(vmodules), vmodules)
123 a.line2('VTMP', diagnose_dir(vtmp_dir), vtmp_dir)
124 a.line2('Current working dir', diagnose_dir(getwd), getwd)
125 a.line('', '')
126
127 a.line_env('VFLAGS')
128 a.line_env('CFLAGS')
129 a.line_env('LDFLAGS')
130
131 a.line('Git version', a.cmd(command: 'git --version'))
132 a.line('V git status', a.git_info())
133 a.line('.git/config present', os.is_file('.git/config').str())
134 a.line('', '')
135 a.line('cc version', a.cmd(command: 'cc --version'))
136 if os_kind == 'openbsd' {
137 a.line('gcc version', a.cmd(command: 'egcc --version'))
138 } else {
139 a.line('gcc version', a.cmd(command: 'gcc --version'))
140 }
141 a.line('clang version', a.cmd(command: 'clang --version'))
142 if os_kind == 'windows' {
143 // Check for MSVC on windows
144 a.line('msvc version', a.cmd(command: 'cl'))
145 }
146 a.report_tcc_version('thirdparty/tcc')
147 a.line('emcc version', a.cmd(command: 'emcc --version'))
148 if os_kind != 'openbsd' && os_kind != 'freebsd' {
149 a.line('glibc version', a.cmd(command: 'ldd --version'))
150 } else {
151 a.line('glibc version', 'N/A')
152 }
153}
154
155struct CmdConfig {
156 line int
157 command string
158}
159
160fn (mut a App) cmd(c CmdConfig) string {
161 x := os.execute(c.command)
162 os_kind := os.user_os()
163 if x.exit_code < 0 || x.exit_code == 127 || (os_kind == 'windows' && x.exit_code == 1) {
164 return 'N/A'
165 }
166 if x.exit_code == 0 {
167 if c.line < 0 {
168 return x.output
169 }
170 output := x.output.split_into_lines()
171 if output.len > 0 && output.len > c.line {
172 return output[c.line]
173 }
174 }
175 return 'Error: ${x.output}'
176}
177
178fn (mut a App) line(label string, value string) {
179 a.println('|${label:-20}|${term.colorize(term.bold, value)}')
180}
181
182fn (mut a App) line2(label string, value string, value2 string) {
183 a.println('|${label:-20}|${term.colorize(term.bold, value)}, value: ${term.colorize(term.bold,
184 value2)}')
185}
186
187fn (mut a App) line_env(env_var string) {
188 value := os.getenv(env_var)
189 if value != '' {
190 a.line('env ${env_var}', '"${value}"')
191 }
192}
193
194fn (app &App) parse(config string, sep string) map[string]string {
195 mut m := map[string]string{}
196 lines := config.split_into_lines()
197 for line in lines {
198 sline := line.trim_space()
199 if sline.len == 0 || sline[0] == `#` {
200 continue
201 }
202 x := sline.split(sep)
203 if x.len < 2 {
204 continue
205 }
206 m[x[0].trim_space().to_lower()] = x[1].trim_space().trim('"')
207 }
208 return m
209}
210
211fn (mut a App) get_linux_os_name() string {
212 if os.is_file('/etc/os-release') {
213 if lines := os.read_file('/etc/os-release') {
214 vals := a.parse(lines, '=')
215 if vals['PRETTY_NAME'] != '' {
216 return vals['PRETTY_NAME']
217 }
218 }
219 }
220 if !a.cmd(command: 'type lsb_release').starts_with('Error') {
221 return a.cmd(command: 'lsb_release -d -s')
222 }
223 if os.is_file('/proc/version') {
224 return a.cmd(command: 'cat /proc/version')
225 }
226 ouname := os.uname()
227 return '${ouname.release}, ${ouname.version}'
228}
229
230fn (mut a App) cpu_info(key string) string {
231 if a.cached_cpuinfo.len > 0 {
232 return a.cached_cpuinfo[key]
233 }
234 info := os.execute('cat /proc/cpuinfo')
235 if info.exit_code != 0 {
236 return '`cat /proc/cpuinfo` could not run'
237 }
238 a.cached_cpuinfo = a.parse(info.output, ':')
239 return a.cached_cpuinfo[key]
240}
241
242fn (mut a App) git_info() string {
243 // Check if in a Git repository
244 x := os.execute('git rev-parse --is-inside-work-tree')
245 if x.exit_code != 0 || x.output.trim_space() != 'true' {
246 return 'N/A'
247 }
248 mut out := a.cmd(command: 'git -C . describe --abbrev=8 --dirty --always --tags').trim_space()
249 os.execute('git -C . remote add V_REPO https://github.com/vlang/v') // ignore failure (i.e. remote exists)
250 if '-skip-github' !in os.args {
251 os.execute('${os.quoted_path(a.vexe)} timeout 5.1 "git -C . fetch V_REPO"') // usually takes ~0.6s; 5 seconds should be enough for even the slowest networks
252 }
253 commit_count := a.cmd(command: 'git rev-list @{0}...V_REPO/master --right-only --count').int()
254 if commit_count > 0 {
255 out += ' (${commit_count} commit(s) behind V master)'
256 }
257 return out
258}
259
260fn (mut a App) report_tcc_version(tccfolder string) {
261 cmd := os.join_path(tccfolder, 'tcc.exe') + ' -v'
262 x := os.execute(cmd)
263 if x.exit_code == 0 {
264 a.line('tcc version', '${x.output.trim_space()}')
265 } else {
266 a.line('tcc version', 'N/A')
267 }
268 if !os.is_file(os.join_path(tccfolder, '.git', 'config')) {
269 a.line('tcc git status', 'N/A')
270 } else {
271 tcc_branch_name := a.cmd(
272 command: 'git -C ${os.quoted_path(tccfolder)} rev-parse --abbrev-ref HEAD'
273 )
274 tcc_commit := a.cmd(
275 command: 'git -C ${os.quoted_path(tccfolder)} describe --abbrev=8 --dirty --always --tags'
276 )
277 a.line('tcc git status', '${tcc_branch_name} ${tcc_commit}')
278 }
279}
280
281fn (mut a App) report_info() {
282 for x in a.report_lines {
283 println(x)
284 }
285}
286
287fn is_writable_dir(path string) bool {
288 os.ensure_folder_is_writable(path) or { return false }
289 return true
290}
291
292fn diagnose_dir(path string) string {
293 mut diagnostics := []string{}
294 if !is_writable_dir(path) {
295 diagnostics << 'NOT writable'
296 }
297 if path.contains(' ') {
298 diagnostics << 'contains spaces'
299 }
300 path_non_ascii_runes := path.runes().filter(it > 255)
301 if path_non_ascii_runes.len > 0 {
302 diagnostics << 'contains these non ASCII characters: ${path_non_ascii_runes}'
303 }
304 if diagnostics.len == 0 {
305 diagnostics << 'OK'
306 }
307 return diagnostics.join(', ')
308}
309
310fn main() {
311 mut app := App{}
312 app.vexe = os.getenv('VEXE')
313 app.collect_info()
314 app.report_info()
315}
316