From 0c5d95a80ad9b80fa09bf7515043cf458333ed3c Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sun, 24 May 2026 19:10:47 +0300 Subject: [PATCH] pref: fall back to bundled tcc on Windows when gcc is not in PATH (fix #27119) (#27233) --- vlib/v/pref/default.v | 26 ++++++++++- vlib/v/pref/default_tcc_compiler_test.v | 61 +++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/vlib/v/pref/default.v b/vlib/v/pref/default.v index 6b99e48fc..f1394df37 100644 --- a/vlib/v/pref/default.v +++ b/vlib/v/pref/default.v @@ -468,6 +468,23 @@ pub fn default_tcc_compiler() string { return usable_system_tcc_compiler() } +// windows_default_c_compiler picks the host C compiler to default to on Windows. +// `gcc` is the historical default, but plenty of Windows users only have the +// bundled tcc that `makev.bat` provisions under `thirdparty/tcc/tcc.exe`. +// Falling back to the bundled tcc when `gcc` isn't on PATH avoids confusing +// "'gcc' is not recognized as an internal or external command" errors during +// `v install`, `v outdated`, etc. (https://github.com/vlang/v/issues/27119). +fn windows_default_c_compiler(vroot string) string { + if os.find_abs_path_of_executable('gcc') or { '' } != '' { + return 'gcc' + } + bundled_tcc := usable_bundled_tcc_compiler(vroot) + if bundled_tcc != '' { + return bundled_tcc + } + return 'gcc' +} + fn (mut p Preferences) clear_gc_options() { p.compile_defines = p.compile_defines.filter(it !in windows_default_gc_defines) p.compile_defines_all = p.compile_defines_all.filter(it !in windows_default_gc_defines) @@ -503,7 +520,14 @@ fn (mut p Preferences) clear_gc_options() { pub fn (mut p Preferences) default_c_compiler() { // TODO: fix $if after 'string' $if windows { - p.ccompiler = 'gcc' + // -prealloc and -prod intentionally avoid the bundled tcc (see + // try_to_use_tcc_by_default); preserve that here so the Windows fallback + // does not silently re-select an incompatible compiler. + if p.prealloc || p.is_prod { + p.ccompiler = 'gcc' + return + } + p.ccompiler = windows_default_c_compiler(os.dir(vexe_path())) return } if p.os == .ios { diff --git a/vlib/v/pref/default_tcc_compiler_test.v b/vlib/v/pref/default_tcc_compiler_test.v index bf107ca58..e74aeb18f 100644 --- a/vlib/v/pref/default_tcc_compiler_test.v +++ b/vlib/v/pref/default_tcc_compiler_test.v @@ -220,6 +220,67 @@ fn test_try_to_use_tcc_by_default_resolves_explicit_tcc_to_system_tcc_on_termux( assert prefs.ccompiler == system_tcc } +fn test_windows_default_c_compiler_prefers_gcc_when_available() { + $if windows { + return + } + test_root := os.join_path(os.vtmp_dir(), 'v_pref_default_c_compiler_test') + prepare_test_tcc_binary(test_root, 'exit 0') + fake_gcc := prepare_test_executable(test_root, 'bin/gcc', 'exit 0') + old_path := os.getenv('PATH') + os.setenv('PATH', os.dir(fake_gcc), true) + defer { + os.setenv('PATH', old_path, true) + os.rmdir_all(test_root) or {} + } + assert windows_default_c_compiler(test_root) == 'gcc' +} + +fn test_windows_default_c_compiler_falls_back_to_bundled_tcc_when_no_gcc() { + $if windows { + return + } + test_root := os.join_path(os.vtmp_dir(), 'v_pref_default_c_compiler_test') + tcc_path := prepare_test_tcc_binary(test_root, 'exit 0') + old_path := os.getenv('PATH') + os.setenv('PATH', os.join_path(test_root, 'no_such_path'), true) + defer { + os.setenv('PATH', old_path, true) + os.rmdir_all(test_root) or {} + } + assert windows_default_c_compiler(test_root) == tcc_path +} + +fn test_windows_default_c_compiler_keeps_gcc_when_bundled_tcc_is_broken() { + $if windows { + return + } + test_root := os.join_path(os.vtmp_dir(), 'v_pref_default_c_compiler_test') + prepare_test_tcc_binary(test_root, 'exit 1') + old_path := os.getenv('PATH') + os.setenv('PATH', os.join_path(test_root, 'no_such_path'), true) + defer { + os.setenv('PATH', old_path, true) + os.rmdir_all(test_root) or {} + } + assert windows_default_c_compiler(test_root) == 'gcc' +} + +fn test_windows_default_c_compiler_keeps_gcc_when_neither_is_available() { + $if windows { + return + } + test_root := os.join_path(os.vtmp_dir(), 'v_pref_default_c_compiler_test') + os.rmdir_all(test_root) or {} + old_path := os.getenv('PATH') + os.setenv('PATH', os.join_path(test_root, 'no_such_path'), true) + defer { + os.setenv('PATH', old_path, true) + os.rmdir_all(test_root) or {} + } + assert windows_default_c_compiler(test_root) == 'gcc' +} + fn prepare_test_executable(test_root string, relative_path string, exit_line string) string { path := os.join_path(test_root, relative_path) os.mkdir_all(os.dir(path)) or { panic(err) } -- 2.39.5