| 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 | vexe string |
| 12 | } |
| 13 | |
| 14 | fn (mut a App) println(s string) { |
| 15 | a.report_lines << s |
| 16 | } |
| 17 | |
| 18 | fn (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 | |
| 155 | struct CmdConfig { |
| 156 | line int |
| 157 | command string |
| 158 | } |
| 159 | |
| 160 | fn (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 | |
| 178 | fn (mut a App) line(label string, value string) { |
| 179 | a.println('|${label:-20}|${term.colorize(term.bold, value)}') |
| 180 | } |
| 181 | |
| 182 | fn (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 | |
| 187 | fn (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 | |
| 194 | fn (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 | |
| 211 | fn (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 | |
| 230 | fn (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 | |
| 242 | fn (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 | |
| 260 | fn (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 | |
| 281 | fn (mut a App) report_info() { |
| 282 | for x in a.report_lines { |
| 283 | println(x) |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | fn is_writable_dir(path string) bool { |
| 288 | os.ensure_folder_is_writable(path) or { return false } |
| 289 | return true |
| 290 | } |
| 291 | |
| 292 | fn 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 | |
| 310 | fn main() { |
| 311 | mut app := App{} |
| 312 | app.vexe = os.getenv('VEXE') |
| 313 | app.collect_info() |
| 314 | app.report_info() |
| 315 | } |
| 316 | |