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