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