| 1 | module main |
| 2 | |
| 3 | import os |
| 4 | import os.cmdline |
| 5 | import v.util.recompilation |
| 6 | |
| 7 | const args_ = arguments() |
| 8 | const is_debug = args_.contains('-debug') |
| 9 | |
| 10 | // support a renamed `v` executable too: |
| 11 | const vexe = os.getenv_opt('VEXE') or { @VEXE } |
| 12 | |
| 13 | const vroot = os.dir(vexe) |
| 14 | const vself_flags_with_values = ['-o', '-os', '-cc', '-gc', '-cf', '-cflags', '-d', '-define'] |
| 15 | |
| 16 | fn main() { |
| 17 | // make testing `v up` easier, by providing a way to force `v self` to fail, |
| 18 | // to test the fallback logic: |
| 19 | if os.getenv('VSELF_SHOULD_FAIL') != '' { |
| 20 | eprintln('v self failed') |
| 21 | exit(1) |
| 22 | } |
| 23 | vexe_name := os.file_name(vexe) |
| 24 | short_v_name := vexe_name.all_before('.') |
| 25 | |
| 26 | recompilation.must_be_enabled(vroot, |
| 27 | 'Please install V from source, to use `${vexe_name} self` .') |
| 28 | os.chdir(vroot)! |
| 29 | os.setenv('VCOLORS', 'always', true) |
| 30 | repeat_count, mut args := extract_repeat_count(args_[1..].filter(it != 'self')) |
| 31 | if args.len == 0 || ('-cc' !in args && '-prod' !in args && '-parallel-cc' !in args) { |
| 32 | // compiling by default, i.e. `v self`: |
| 33 | uos := os.user_os() |
| 34 | uname := os.uname() |
| 35 | if uos == 'macos' && uname.machine == 'arm64' { |
| 36 | // Apple silicon, like m1, m2 etc |
| 37 | // Use tcc by default for V, since tinycc is much faster and also |
| 38 | // it already supports compiling many programs like V itself, that do not depend on inlined objective-C code |
| 39 | args << '-cc tcc' |
| 40 | } else if uos == 'linux' && uname.machine in ['arm64', 'aarch64'] { |
| 41 | // Bundled TCC can hang while bootstrapping V on Linux ARM64, so |
| 42 | // prefer the system compiler for self-builds there. |
| 43 | args << ['-cc', os.getenv_opt('CC') or { 'cc' }] |
| 44 | } |
| 45 | } |
| 46 | if !has_gc_arg(args) { |
| 47 | args << ['-gc', 'none'] |
| 48 | } |
| 49 | jargs := args.join(' ') |
| 50 | obinary := cmdline.option(args, '-o', '') |
| 51 | sargs := if obinary != '' { jargs } else { '${jargs} -o v2' } |
| 52 | options := if args.len > 0 { '(${sargs})' } else { '' } |
| 53 | final_binary := if obinary != '' { obinary } else { 'v2' } |
| 54 | pgo_cc_kind := pgo_compiler_kind(args) |
| 55 | for run_idx in 0 .. repeat_count { |
| 56 | run_label := if repeat_count > 1 { ' [${run_idx + 1}/${repeat_count}]' } else { '' } |
| 57 | println('V self compiling${run_label} ${options}...') |
| 58 | cmd := '${os.quoted_path(vexe)} ${sargs} ${os.quoted_path('cmd/v')}' |
| 59 | mut used_pgo := false |
| 60 | if pgo_cc_kind != '' { |
| 61 | used_pgo = compile_with_pgo(vroot, vexe, args, final_binary, pgo_cc_kind) |
| 62 | if !used_pgo { |
| 63 | eprintln('PGO self-build failed; falling back to a regular self-build.') |
| 64 | } |
| 65 | } |
| 66 | if !used_pgo { |
| 67 | if !try_compile(cmd) { |
| 68 | bootstrap_self_build(vroot, clone_args(args), final_binary) or { |
| 69 | eprintln('cannot compile to `${vroot}`: \n${err.msg()}') |
| 70 | exit(1) |
| 71 | } |
| 72 | } |
| 73 | } |
| 74 | if obinary == '' { |
| 75 | backup_old_version_and_rename_newer(short_v_name) or { panic(err.msg()) } |
| 76 | } |
| 77 | } |
| 78 | if obinary != '' { |
| 79 | return |
| 80 | } |
| 81 | println('V built successfully as executable "${vexe_name}".') |
| 82 | } |
| 83 | |
| 84 | fn repeat_count_arg(arg string) int { |
| 85 | if arg.len < 2 || arg[0] != `x` { |
| 86 | return 0 |
| 87 | } |
| 88 | for ch in arg[1..].bytes() { |
| 89 | if !ch.is_digit() { |
| 90 | return 0 |
| 91 | } |
| 92 | } |
| 93 | count := arg[1..].int() |
| 94 | return if count > 0 { count } else { 0 } |
| 95 | } |
| 96 | |
| 97 | fn extract_repeat_count(args []string) (int, []string) { |
| 98 | mut repeat_count := 1 |
| 99 | mut filtered := []string{cap: args.len} |
| 100 | mut should_skip_repeat_check := false |
| 101 | for arg in args { |
| 102 | if should_skip_repeat_check { |
| 103 | filtered << arg |
| 104 | should_skip_repeat_check = false |
| 105 | continue |
| 106 | } |
| 107 | if arg in vself_flags_with_values { |
| 108 | filtered << arg |
| 109 | should_skip_repeat_check = true |
| 110 | continue |
| 111 | } |
| 112 | if repeat_count == 1 { |
| 113 | count := repeat_count_arg(arg) |
| 114 | if count > 0 { |
| 115 | repeat_count = count |
| 116 | continue |
| 117 | } |
| 118 | } |
| 119 | filtered << arg |
| 120 | } |
| 121 | return repeat_count, filtered |
| 122 | } |
| 123 | |
| 124 | fn has_gc_arg(args []string) bool { |
| 125 | for arg in args { |
| 126 | if arg == '-gc' { |
| 127 | return true |
| 128 | } |
| 129 | if arg.starts_with('-gc=') { |
| 130 | return true |
| 131 | } |
| 132 | } |
| 133 | return false |
| 134 | } |
| 135 | |
| 136 | fn has_profile_cflag(args []string) bool { |
| 137 | mut skip_next := false |
| 138 | for i, arg in args { |
| 139 | if skip_next { |
| 140 | skip_next = false |
| 141 | continue |
| 142 | } |
| 143 | if arg in ['-cflags', '-cf'] { |
| 144 | if i + 1 < args.len { |
| 145 | next_arg := args[i + 1] |
| 146 | if next_arg.contains('-fprofile') { |
| 147 | return true |
| 148 | } |
| 149 | skip_next = true |
| 150 | } |
| 151 | continue |
| 152 | } |
| 153 | if (arg.starts_with('-cflags=') || arg.starts_with('-cf=')) && arg.contains('-fprofile') { |
| 154 | return true |
| 155 | } |
| 156 | } |
| 157 | return false |
| 158 | } |
| 159 | |
| 160 | fn pgo_compiler_kind(args []string) string { |
| 161 | if '-prod' !in args || '-no-prod-options' in args { |
| 162 | return '' |
| 163 | } |
| 164 | if os.user_os() == 'windows' { |
| 165 | return '' |
| 166 | } |
| 167 | if has_profile_cflag(args) { |
| 168 | return '' |
| 169 | } |
| 170 | mut ccompiler := cmdline.option(args, '-cc', '') |
| 171 | if ccompiler == '' { |
| 172 | ccompiler = os.getenv_opt('CC') or { 'cc' } |
| 173 | } |
| 174 | cc_file_name := os.file_name(ccompiler) |
| 175 | if cc_file_name.contains('clang') || cc_file_name.contains('gcc') |
| 176 | || cc_file_name.contains('g++') || ccompiler == 'cc' { |
| 177 | cc_ver := os.execute('${os.quoted_path(ccompiler)} --version').output |
| 178 | if cc_ver.contains('clang') { |
| 179 | _ := find_llvm_profdata() or { return '' } |
| 180 | return 'clang' |
| 181 | } |
| 182 | if cc_ver.contains('Free Software Foundation') || cc_ver.contains('GCC') { |
| 183 | return 'gcc' |
| 184 | } |
| 185 | } |
| 186 | if cc_file_name.contains('clang') { |
| 187 | _ := find_llvm_profdata() or { return '' } |
| 188 | return 'clang' |
| 189 | } |
| 190 | if cc_file_name.contains('gcc') || cc_file_name.contains('g++') { |
| 191 | return 'gcc' |
| 192 | } |
| 193 | return '' |
| 194 | } |
| 195 | |
| 196 | fn find_llvm_profdata() !string { |
| 197 | if profdata := os.find_abs_path_of_executable('llvm-profdata') { |
| 198 | return profdata |
| 199 | } |
| 200 | $if macos { |
| 201 | xcrun_result := os.execute('xcrun --find llvm-profdata') |
| 202 | if xcrun_result.exit_code == 0 { |
| 203 | xcrun_path := xcrun_result.output.trim_space() |
| 204 | if xcrun_path != '' && os.exists(xcrun_path) { |
| 205 | return xcrun_path |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | return error('can not find llvm-profdata in PATH') |
| 210 | } |
| 211 | |
| 212 | fn with_output_arg(args []string, output string) []string { |
| 213 | mut res := []string{cap: args.len + 2} |
| 214 | mut skip_next := false |
| 215 | for i, arg in args { |
| 216 | if skip_next { |
| 217 | skip_next = false |
| 218 | continue |
| 219 | } |
| 220 | if arg == '-o' { |
| 221 | if i + 1 < args.len { |
| 222 | skip_next = true |
| 223 | } |
| 224 | continue |
| 225 | } |
| 226 | if arg.starts_with('-o=') { |
| 227 | continue |
| 228 | } |
| 229 | res << arg |
| 230 | } |
| 231 | res << ['-o', output] |
| 232 | return res |
| 233 | } |
| 234 | |
| 235 | fn clone_args(args []string) []string { |
| 236 | mut cloned := []string{cap: args.len} |
| 237 | for arg in args { |
| 238 | cloned << arg.clone() |
| 239 | } |
| 240 | return cloned |
| 241 | } |
| 242 | |
| 243 | fn compose_v_cmd(vexe string, args []string, source string) string { |
| 244 | mut parts := []string{cap: args.len + 2} |
| 245 | parts << os.quoted_path(vexe) |
| 246 | for arg in args { |
| 247 | parts << os.quoted_path(arg) |
| 248 | } |
| 249 | parts << os.quoted_path(source) |
| 250 | return parts.join(' ') |
| 251 | } |
| 252 | |
| 253 | fn run_cmd(cmd string) ! { |
| 254 | result := os.execute(cmd) |
| 255 | if result.exit_code != 0 { |
| 256 | return error(result.output) |
| 257 | } |
| 258 | if result.output.len > 0 { |
| 259 | println(result.output.trim_space()) |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | fn try_compile(cmd string) bool { |
| 264 | result := os.execute(cmd) |
| 265 | if result.exit_code != 0 { |
| 266 | return false |
| 267 | } |
| 268 | if result.output.len > 0 { |
| 269 | println(result.output.trim_space()) |
| 270 | } |
| 271 | return true |
| 272 | } |
| 273 | |
| 274 | fn compile_with_pgo(vroot string, vexe string, args []string, out_binary string, cc_kind string) bool { |
| 275 | pgo_workspace := os.join_path(vroot, '.vself_pgo') |
| 276 | os.rmdir_all(pgo_workspace) or {} |
| 277 | os.mkdir_all(pgo_workspace) or { |
| 278 | eprintln('PGO disabled: can not create ${pgo_workspace}: ${err.msg()}') |
| 279 | return false |
| 280 | } |
| 281 | defer { |
| 282 | os.rmdir_all(pgo_workspace) or {} |
| 283 | } |
| 284 | profile_dir := os.join_path(pgo_workspace, 'profile') |
| 285 | os.mkdir_all(profile_dir) or { |
| 286 | eprintln('PGO disabled: can not create ${profile_dir}: ${err.msg()}') |
| 287 | return false |
| 288 | } |
| 289 | pgo_binary := os.join_path(pgo_workspace, 'v_pgo_gen') |
| 290 | training_output := os.join_path(pgo_workspace, 'cmd_v_training.c') |
| 291 | mut use_profile_flag := '-fprofile-use=${profile_dir}' |
| 292 | mut llvm_profdata := '' |
| 293 | mut profile_data := '' |
| 294 | if cc_kind == 'clang' { |
| 295 | llvm_profdata = find_llvm_profdata() or { |
| 296 | eprintln('PGO disabled: can not find `llvm-profdata`.') |
| 297 | return false |
| 298 | } |
| 299 | profile_data = os.join_path(pgo_workspace, 'code.profdata') |
| 300 | use_profile_flag = '-fprofile-use=${profile_data}' |
| 301 | } |
| 302 | mut generate_args := with_output_arg(args, pgo_binary) |
| 303 | generate_args << ['-cflags', '-fprofile-generate=${profile_dir}'] |
| 304 | generate_cmd := compose_v_cmd(vexe, generate_args, 'cmd/v') |
| 305 | run_cmd(generate_cmd) or { |
| 306 | eprintln('PGO step failed while building the instrumented compiler.') |
| 307 | eprintln(err.msg()) |
| 308 | return false |
| 309 | } |
| 310 | training_cmd := '${os.quoted_path(pgo_binary)} -o ${os.quoted_path(training_output)} ${os.quoted_path('cmd/v')}' |
| 311 | run_cmd(training_cmd) or { |
| 312 | eprintln('PGO step failed while generating the profiling data.') |
| 313 | eprintln(err.msg()) |
| 314 | return false |
| 315 | } |
| 316 | if cc_kind == 'clang' { |
| 317 | merge_cmd := '${os.quoted_path(llvm_profdata)} merge -output=${os.quoted_path(profile_data)} ${os.quoted_path(profile_dir)}' |
| 318 | run_cmd(merge_cmd) or { |
| 319 | eprintln('PGO step failed while merging the profiling data.') |
| 320 | eprintln(err.msg()) |
| 321 | return false |
| 322 | } |
| 323 | } |
| 324 | mut final_args := with_output_arg(args, out_binary) |
| 325 | final_args << ['-cflags', use_profile_flag] |
| 326 | if cc_kind == 'gcc' { |
| 327 | final_args << ['-cflags', '-fprofile-correction'] |
| 328 | } |
| 329 | final_cmd := compose_v_cmd(vexe, final_args, 'cmd/v') |
| 330 | run_cmd(final_cmd) or { |
| 331 | eprintln('PGO step failed while building the final compiler binary.') |
| 332 | eprintln(err.msg()) |
| 333 | return false |
| 334 | } |
| 335 | return true |
| 336 | } |
| 337 | |
| 338 | fn bootstrap_self_build(vroot string, args []string, final_binary string) ! { |
| 339 | bootstrap_prefix := '.vself_bootstrap' |
| 340 | mut bootstrap_v1 := '${bootstrap_prefix}_v1' |
| 341 | mut bootstrap_v2 := '${bootstrap_prefix}_v2' |
| 342 | exe_ext := if os.user_os() == 'windows' { '.exe' } else { '' } |
| 343 | bootstrap_v1 += exe_ext |
| 344 | bootstrap_v2 += exe_ext |
| 345 | os.rm(bootstrap_v1) or {} |
| 346 | os.rm(bootstrap_v2) or {} |
| 347 | defer { |
| 348 | os.rm(bootstrap_v1) or {} |
| 349 | os.rm(bootstrap_v2) or {} |
| 350 | } |
| 351 | vc_source := os.join_path(vroot, 'vc', |
| 352 | if os.user_os() == 'windows' { 'v_win.c' } else { 'v.c' }) |
| 353 | if !os.exists(vc_source) { |
| 354 | return error('bootstrap fallback failed: `${vc_source}` is missing') |
| 355 | } |
| 356 | cc := os.getenv_opt('CC') or { |
| 357 | if os.user_os() == 'windows' { 'gcc' } else { 'cc' } |
| 358 | } |
| 359 | bootstrap_v1_build_cmd := bootstrap_c_cmd(cc, bootstrap_v1, vc_source) |
| 360 | run_cmd(bootstrap_v1_build_cmd) or { |
| 361 | return error('bootstrap fallback failed while building v1.\n${err.msg()}') |
| 362 | } |
| 363 | mut bootstrap_args := ['-no-parallel'] |
| 364 | bootstrap_args << with_output_arg(args, bootstrap_v2) |
| 365 | bootstrap_v1_cmd := os.join_path('.', bootstrap_v1) |
| 366 | bootstrap_v2_cmd := '${os.quoted_path(bootstrap_v1_cmd)} ${bootstrap_args.join(' ')} ${os.quoted_path('cmd/v')}' |
| 367 | run_cmd(bootstrap_v2_cmd) or { |
| 368 | return error('bootstrap fallback failed while building v2.\n${err.msg()}') |
| 369 | } |
| 370 | final_args := with_output_arg(args, final_binary) |
| 371 | bootstrap_v2_cmd_path := os.join_path('.', bootstrap_v2) |
| 372 | final_cmd := '${os.quoted_path(bootstrap_v2_cmd_path)} ${final_args.join(' ')} ${os.quoted_path('cmd/v')}' |
| 373 | run_cmd(final_cmd) or { |
| 374 | return error('bootstrap fallback failed while building the final compiler.\n${err.msg()}') |
| 375 | } |
| 376 | } |
| 377 | |
| 378 | fn bootstrap_c_cmd(cc string, out_binary string, vc_source string) string { |
| 379 | mut parts := []string{cap: 8} |
| 380 | parts << os.quoted_path(cc) |
| 381 | if os.user_os() == 'windows' { |
| 382 | parts << ['-std=c99', '-municode', '-w', '-o', os.quoted_path(out_binary), |
| 383 | os.quoted_path(vc_source), '-lws2_32'] |
| 384 | } else { |
| 385 | parts << ['-std=c99', '-w', '-o', os.quoted_path(out_binary), |
| 386 | os.quoted_path(vc_source), '-lm', '-lpthread'] |
| 387 | } |
| 388 | return parts.join(' ') |
| 389 | } |
| 390 | |
| 391 | fn list_folder(short_v_name string, bmessage string, message string) { |
| 392 | if !is_debug { |
| 393 | return |
| 394 | } |
| 395 | if bmessage != '' { |
| 396 | println(bmessage) |
| 397 | } |
| 398 | if os.user_os() == 'windows' { |
| 399 | os.system('dir ${short_v_name}*.exe') |
| 400 | } else { |
| 401 | os.system('ls -lartd ${short_v_name}*') |
| 402 | } |
| 403 | println(message) |
| 404 | } |
| 405 | |
| 406 | fn backup_old_version_and_rename_newer(short_v_name string) !bool { |
| 407 | mut errors := []string{} |
| 408 | short_v_file := if os.user_os() == 'windows' { '${short_v_name}.exe' } else { '${short_v_name}' } |
| 409 | short_v2_file := if os.user_os() == 'windows' { 'v2.exe' } else { 'v2' } |
| 410 | short_bak_file := if os.user_os() == 'windows' { 'v_old.exe' } else { 'v_old' } |
| 411 | v_file := os.real_path(short_v_file) |
| 412 | v2_file := os.real_path(short_v2_file) |
| 413 | bak_file := os.real_path(short_bak_file) |
| 414 | |
| 415 | list_folder(short_v_name, 'before:', 'removing ${bak_file} ...') |
| 416 | if os.exists(bak_file) { |
| 417 | os.rm(bak_file) or { errors << 'failed removing ${bak_file}: ${err.msg()}' } |
| 418 | } |
| 419 | |
| 420 | list_folder(short_v_name, '', 'moving ${v_file} to ${bak_file} ...') |
| 421 | os.mv(v_file, bak_file) or { errors << err.msg() } |
| 422 | |
| 423 | list_folder(short_v_name, '', 'removing ${v_file} ...') |
| 424 | os.rm(v_file) or {} |
| 425 | |
| 426 | list_folder(short_v_name, '', 'moving ${v2_file} to ${v_file} ...') |
| 427 | os.mv_by_cp(v2_file, v_file) or { panic(err.msg()) } |
| 428 | |
| 429 | list_folder(short_v_name, 'after:', '') |
| 430 | |
| 431 | if errors.len > 0 { |
| 432 | eprintln('backup errors:\n >> ' + errors.join('\n >> ')) |
| 433 | } |
| 434 | return true |
| 435 | } |
| 436 | |