vxx / vlib / v / builder / cc_test.v
867 lines · 767 sloc · 32.49 KB · 2085cbb45161b78712c3214add56edbb26aa7713
Raw
1module builder
2
3import os
4import time
5import v.pref
6
7fn test_ccompiler_is_available_with_existing_absolute_path() {
8 assert ccompiler_is_available(@VEXE)
9}
10
11fn test_ccompiler_is_available_with_missing_compiler() {
12 assert !ccompiler_is_available('missing_compiler_17126_for_builder_test')
13}
14
15fn test_c_error_looks_like_cpp_header_with_clang_style_output() {
16 clang_output := "error: unknown type name 'namespace'\nerror: expected ';' after top level declarator"
17 assert c_error_looks_like_cpp_header(clang_output)
18}
19
20fn test_c_error_looks_like_cpp_header_with_source_excerpt() {
21 gcc_output := '/usr/include/H5File.h:18: error: \';\' expected (got "H5")\n| namespace H5 {\n'
22 assert c_error_looks_like_cpp_header(gcc_output)
23}
24
25fn test_c_error_looks_like_cpp_header_with_imgui_style_operator_overload() {
26 imgui_output := "/tmp/fake_imgui.h:3:16: error: 'operator' declared as array of functions of type 'float (unsigned long)'\n 3 | float operator[](unsigned long idx) const { return (&x)[idx]; }\n"
27 assert c_error_looks_like_cpp_header(imgui_output)
28}
29
30fn test_c_error_looks_like_cpp_header_with_class_keyword() {
31 class_output := "/usr/include/foo.hpp:4:1: error: unknown type name 'class'\n 4 | class Foo {\n"
32 assert c_error_looks_like_cpp_header(class_output)
33}
34
35fn test_c_error_looks_like_cpp_header_with_regular_c_error() {
36 c_output := "error: unknown type name 'my_missing_type'"
37 assert !c_error_looks_like_cpp_header(c_output)
38}
39
40fn test_c_output_suggests_missing_sokol_shader_symbol_with_clang_style_output() {
41 c_output := [
42 "/tmp/v_501/simple_shader.tmp.c:21250:43: error: use of undeclared identifier 'ATTR_vs_aposition'",
43 ' pipeline_desc.layout.attrs[v_fixed_index(ATTR_vs_aposition, 16)].format = 1;',
44 ].join('\n')
45 assert c_output_suggests_missing_sokol_shader_symbol(c_output) == 'ATTR_vs_aposition'
46}
47
48fn test_c_output_suggests_missing_sokol_shader_symbol_with_gcc_style_output() {
49 c_output := [
50 "/tmp/v_501/simple_shader.tmp.c:21250:43: error: 'SLOT_fs_params' undeclared (first use in this function)",
51 ' gfx_apply_uniforms(SLOT_fs_params);',
52 ].join('\n')
53 assert c_output_suggests_missing_sokol_shader_symbol(c_output) == 'SLOT_fs_params'
54}
55
56fn test_c_output_suggests_missing_sokol_shader_symbol_ignores_regular_c_errors() {
57 c_output := "error: use of undeclared identifier 'my_missing_type'"
58 assert c_output_suggests_missing_sokol_shader_symbol(c_output) == ''
59}
60
61fn test_c_output_suggests_missing_c_function_with_clang_style_output() {
62 c_output := "/tmp/v_501/main.tmp.c:4111:2: error: call to undeclared function 'bad_fn'; ISO C99 and later do not support implicit function declarations"
63 known_c_functions := {
64 'bad_fn': 'C.bad_fn'
65 }
66 assert c_output_suggests_missing_c_function(c_output, known_c_functions) == 'C.bad_fn'
67}
68
69fn test_c_output_suggests_missing_c_function_with_tcc_style_output() {
70 c_output := "/tmp/v_501/main.tmp.c:4111: warning: implicit declaration of function 'bad_fn'"
71 known_c_functions := {
72 'bad_fn': 'C.bad_fn'
73 }
74 assert c_output_suggests_missing_c_function(c_output, known_c_functions) == 'C.bad_fn'
75}
76
77fn test_c_output_suggests_missing_c_function_ignores_unknown_symbols() {
78 c_output := "/tmp/v_501/main.tmp.c:4111:2: error: call to undeclared function 'bad_fn'"
79 known_c_functions := {
80 'other_fn': 'C.other_fn'
81 }
82 assert c_output_suggests_missing_c_function(c_output, known_c_functions) == ''
83}
84
85fn test_macos_compile_args_do_not_force_version_min_by_default() {
86 compile_args := macos_compile_args(['-os', 'macos', '-cc', 'clang', hello_world_example()])
87 assert macos_version_min_flags(compile_args) == []string{}
88}
89
90fn test_macos_compile_args_keep_explicit_cflag_version_min() {
91 compile_args := macos_compile_args([
92 '-os',
93 'macos',
94 '-cc',
95 'clang',
96 '-cflags',
97 '-mmacosx-version-min=11.0',
98 hello_world_example(),
99 ])
100 assert macos_version_min_flags(compile_args) == ['-mmacosx-version-min=11.0']
101}
102
103fn test_macos_compile_args_append_macosx_version_min_after_cflags() {
104 compile_args := macos_compile_args([
105 '-os',
106 'macos',
107 '-cc',
108 'clang',
109 '-cflags',
110 '-mmacosx-version-min=10.7',
111 '-macosx-version-min',
112 '11.0',
113 hello_world_example(),
114 ])
115 assert macos_version_min_flags(compile_args) == [
116 '-mmacosx-version-min=10.7',
117 '-mmacosx-version-min=11.0',
118 ]
119}
120
121fn test_cc_from_string_detects_cl_as_msvc() {
122 assert pref.cc_from_string('cl') == .msvc
123 assert pref.cc_from_string('C:/Program Files/Microsoft Visual Studio/cl.exe') == .msvc
124}
125
126fn test_cc_from_string_detects_tiny_gcc_as_tinyc() {
127 assert pref.cc_from_string('tiny_gcc') == .tinyc
128 assert pref.cc_from_string('/usr/local/bin/tiny_gcc') == .tinyc
129}
130
131fn test_ccompiler_type_from_version_output_detects_openbsd_clang() {
132 detected := ccompiler_type_from_version_output('OpenBSD clang version 16.0.6') or { panic(err) }
133 assert detected == .clang
134}
135
136fn test_ccompiler_type_from_version_output_detects_tcc() {
137 detected := ccompiler_type_from_version_output('tcc version 0.9.27 (x86_64 Linux)') or {
138 panic(err)
139 }
140 assert detected == .tinyc
141}
142
143fn test_ccompiler_type_from_name_detects_tiny_gcc() {
144 detected := ccompiler_type_from_name('/opt/tiny_gcc') or { panic(err) }
145 assert detected == .tinyc
146}
147
148fn test_ccompiler_type_from_version_output_detects_tiny_gcc() {
149 detected := ccompiler_type_from_version_output('tiny_gcc version 0.9.27') or { panic(err) }
150 assert detected == .tinyc
151}
152
153fn test_is_tcc_compilation_failure_detects_tiny_gcc_compiler_name() {
154 assert is_tcc_compilation_failure('/opt/bin/tiny_gcc', .unknown, '')
155}
156
157fn test_resolve_ccompiler_type_detects_cc_alias_path_as_clang() {
158 $if windows {
159 return
160 }
161 test_root := os.join_path(os.vtmp_dir(), 'v_builder_cc_alias_${os.getpid()}')
162 alias_cc := prepare_test_ccompiler_alias(test_root, 'cc', 'OpenBSD clang version 16.0.6')
163 defer {
164 os.rmdir_all(test_root) or {}
165 }
166 assert resolve_ccompiler_type(alias_cc, pref.cc_from_string(alias_cc)) == .clang
167}
168
169fn test_resolve_ccompiler_type_detects_real_path_without_running_alias() {
170 $if windows {
171 return
172 }
173 test_root := os.join_path(os.vtmp_dir(), 'v_builder_cc_symlink_${os.getpid()}')
174 os.rmdir_all(test_root) or {}
175 os.mkdir_all(test_root) or { panic(err) }
176 defer {
177 os.rmdir_all(test_root) or {}
178 }
179 clang_path := os.join_path(test_root, 'clang')
180 os.write_file(clang_path, '#!/bin/sh
181exit 1
182') or { panic(err) }
183 os.chmod(clang_path, 0o700) or { panic(err) }
184 link_path := os.join_path(test_root, 'cc')
185 os.symlink(clang_path, link_path) or { panic(err) }
186 assert resolve_ccompiler_type(link_path, pref.cc_from_string(link_path)) == .clang
187}
188
189fn test_ccompiler_type_from_resolved_path_detects_macos_cc_wrapper_as_clang() {
190 $if macos {
191 detected := ccompiler_type_from_resolved_path('/usr/bin/cc') or { panic(err) }
192 assert detected == .clang
193 }
194}
195
196fn test_setup_ccompiler_options_detects_cl_path_as_msvc() {
197 mut full_args := ['']
198 full_args << hello_world_example()
199 mut prefs, _ := pref.parse_args_and_show_errors([], full_args, false)
200 prefs.ccompiler = 'C:/Program Files/Microsoft Visual Studio/cl.exe'
201 prefs.ccompiler_type = pref.cc_from_string(prefs.ccompiler)
202 mut builder := new_builder(prefs)
203 builder.out_name_c = os.join_path(os.vtmp_dir(), 'builder_cc_test.tmp.c')
204 builder.setup_ccompiler_options(prefs.ccompiler)
205 assert builder.ccoptions.cc == .msvc
206}
207
208fn test_msvc_thirdparty_obj_path_uses_cached_location_for_target_arch() {
209 obj_file := os.join_path(@VEXEROOT, 'thirdparty', 'mbedtls', 'library', 'bignum.o')
210 mut builder64 := new_builder_for_args(['-cc', 'msvc', '-m64', hello_world_example()])
211 mut builder32 := new_builder_for_args(['-cc', 'msvc', '-m32', hello_world_example()])
212 obj64 := builder64.msvc_thirdparty_obj_path('mbedtls', obj_file, '')
213 obj32 := builder32.msvc_thirdparty_obj_path('mbedtls', obj_file, '')
214 source_obj := os.real_path(obj_file.all_before_last('.o') + '.obj')
215 assert obj64 != obj32
216 assert obj64 != source_obj
217 assert obj32 != source_obj
218 assert obj64.ends_with('.obj')
219 assert obj32.ends_with('.obj')
220}
221
222fn test_msvc_thirdparty_obj_path_keeps_debug_objects_separate() {
223 obj_file := os.join_path(@VEXEROOT, 'thirdparty', 'mbedtls', 'library', 'bignum.o')
224 mut release_builder := new_builder_for_args(['-cc', 'msvc', '-m64', hello_world_example()])
225 mut debug_builder := new_builder_for_args(['-cc', 'msvc', '-m64', '-g', hello_world_example()])
226 release_obj := release_builder.msvc_thirdparty_obj_path('mbedtls', obj_file, '')
227 debug_obj := debug_builder.msvc_thirdparty_obj_path('mbedtls', obj_file, '')
228 assert debug_obj.ends_with('.debug.obj')
229 assert release_obj != debug_obj
230}
231
232fn test_sqlite_thirdparty_validation_error_for_missing_amalgamation() {
233 obj_path := os.join_path(@VEXEROOT, 'thirdparty', 'sqlite', 'sqlite3.o')
234 msg := sqlite_thirdparty_validation_error('db.sqlite', obj_path, '', .unknown)
235 assert msg.contains('sqlite3.c')
236 assert msg.contains('sqlite3.h')
237 assert msg.contains('install_thirdparty_sqlite.vsh')
238}
239
240fn test_sqlite_thirdparty_validation_error_for_sqlite3_cpp() {
241 obj_path := os.join_path(@VEXEROOT, 'thirdparty', 'sqlite', 'sqlite3.o')
242 source_path := obj_path.all_before_last('.o') + '.cpp'
243 msg := sqlite_thirdparty_validation_error('db.sqlite', obj_path, source_path, .cpp)
244 assert msg.contains('Do not rename `sqlite3.c` to `sqlite3.cpp`')
245 assert msg.contains('SQLite amalgamation package')
246}
247
248fn test_sqlite_thirdparty_validation_error_ignores_sqlite3_c() {
249 obj_path := os.join_path(@VEXEROOT, 'thirdparty', 'sqlite', 'sqlite3.o')
250 source_path := obj_path.all_before_last('.o') + '.c'
251 assert sqlite_thirdparty_validation_error('db.sqlite', obj_path, source_path, .c) == ''
252}
253
254fn test_sqlite_thirdparty_validation_error_ignores_other_modules() {
255 obj_path := os.join_path(@VEXEROOT, 'thirdparty', 'sqlite', 'sqlite3.o')
256 source_path := obj_path.all_before_last('.o') + '.cpp'
257 assert sqlite_thirdparty_validation_error('json.cjson', obj_path, source_path, .cpp) == ''
258}
259
260fn test_linux_cross_target_for_amd64() {
261 target := linux_cross_target_for_arch(.amd64) or { panic(err) }
262 assert target.triple == 'x86_64-linux-gnu'
263 assert target.lib_dir == 'x86_64-linux-gnu'
264 assert target.dynamic_linker == '/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2'
265 assert target.linker_emulation == 'elf_x86_64'
266}
267
268fn test_linux_cross_target_for_arm64_errors() {
269 if target := linux_cross_target_for_arch(.arm64) {
270 assert false, 'unexpected target: ${target}'
271 } else {
272 assert err.msg().contains('only `-arch amd64`')
273 assert err.msg().contains('linuxroot')
274 }
275}
276
277fn test_git_symlink_target_path_detects_placeholder_file() {
278 test_root := os.join_path(os.vtmp_dir(), 'v_builder_git_symlink_target_${os.getpid()}')
279 os.rmdir_all(test_root) or {}
280 defer {
281 os.rmdir_all(test_root) or {}
282 }
283 lib_dir := os.join_path(test_root, 'lib', 'x86_64-linux-gnu')
284 os.mkdir_all(lib_dir)!
285 target_file := os.join_path(lib_dir, 'libm-2.31.so')
286 placeholder := os.join_path(lib_dir, 'libm.so.6')
287 os.write_file(target_file, 'ELF PLACEHOLDER')!
288 os.write_file(placeholder, 'libm-2.31.so\n')!
289 assert git_symlink_target_path(placeholder) or { panic(err) } == target_file
290}
291
292fn test_git_symlink_target_path_ignores_linker_script() {
293 test_root := os.join_path(os.vtmp_dir(), 'v_builder_git_symlink_script_${os.getpid()}')
294 os.rmdir_all(test_root) or {}
295 defer {
296 os.rmdir_all(test_root) or {}
297 }
298 lib_dir := os.join_path(test_root, 'lib', 'x86_64-linux-gnu')
299 os.mkdir_all(lib_dir)!
300 linker_script := os.join_path(lib_dir, 'libm.so')
301 os.write_file(linker_script, '/* GNU ld script */\nGROUP ( /lib/x86_64-linux-gnu/libm.so.6 )\n')!
302 assert git_symlink_target_path(linker_script) == none
303}
304
305fn test_git_symlink_materialization_source_follows_placeholder_chain() {
306 test_root := os.join_path(os.vtmp_dir(), 'v_builder_git_symlink_chain_${os.getpid()}')
307 os.rmdir_all(test_root) or {}
308 defer {
309 os.rmdir_all(test_root) or {}
310 }
311 lib_dir := os.join_path(test_root, 'lib', 'x86_64-linux-gnu')
312 os.mkdir_all(lib_dir)!
313 final_target := os.join_path(lib_dir, 'libm-2.31.so')
314 first_link := os.join_path(lib_dir, 'libm.so.6')
315 second_link := os.join_path(lib_dir, 'libm.so')
316 os.write_file(final_target, 'ELF FINAL')!
317 os.write_file(first_link, 'libm-2.31.so\n')!
318 os.write_file(second_link, 'libm.so.6\n')!
319 assert git_symlink_materialization_source(second_link) or { panic(err) } == final_target
320}
321
322fn test_repair_cross_sysroot_git_symlink_placeholders_in_paths_materializes_target_copy() {
323 test_root := os.join_path(os.vtmp_dir(), 'v_builder_git_symlink_repair_${os.getpid()}')
324 os.rmdir_all(test_root) or {}
325 defer {
326 os.rmdir_all(test_root) or {}
327 }
328 lib_dir := os.join_path(test_root, 'lib', 'x86_64-linux-gnu')
329 os.mkdir_all(lib_dir)!
330 final_target := os.join_path(lib_dir, 'libm-2.31.so')
331 placeholder := os.join_path(lib_dir, 'libm.so.6')
332 final_bytes := 'ELF DATA BINARY'
333 os.write_file(final_target, final_bytes)!
334 os.write_file(placeholder, 'libm-2.31.so\n')!
335 repaired := repair_cross_sysroot_git_symlink_placeholders_in_paths([placeholder], true) or {
336 panic(err)
337 }
338 assert repaired == 1
339 assert os.read_file(placeholder)! == final_bytes
340}
341
342fn test_msvc_should_use_rsp_for_ascii_args() {
343 builder := new_builder_for_args(['-cc', 'msvc', hello_world_example()])
344 assert builder.msvc_should_use_rsp(['/OUT:"C:\\Users\\russo\\Desktop\\main.exe"'])
345}
346
347fn test_msvc_should_not_use_rsp_for_non_ascii_args() {
348 builder := new_builder_for_args(['-cc', 'msvc', hello_world_example()])
349 assert !builder.msvc_should_use_rsp([
350 '/OUT:"C:\\Users\\russo\\OneDrive\\Рабочий стол\\main.exe"',
351 ])
352}
353
354fn test_msvc_should_not_use_rsp_when_no_rsp_is_requested() {
355 builder := new_builder_for_args(['-cc', 'msvc', '-no-rsp', hello_world_example()])
356 assert !builder.msvc_should_use_rsp(['/OUT:"C:\\Users\\russo\\Desktop\\main.exe"'])
357}
358
359fn test_live_termux_linker_args_include_rdynamic_without_debug() {
360 linker_args := builder_linker_args([
361 '-os',
362 'termux',
363 '-cc',
364 'clang',
365 '-live',
366 hello_world_example(),
367 ])
368 assert linker_args.contains('-rdynamic')
369}
370
371fn test_thirdparty_cross_compile_config_for_linux_matches_target() {
372 builder := new_builder_for_args(['-os', 'linux', hello_world_example()])
373 cfg := builder.thirdparty_cross_compile_config()
374 if current_os == 'linux' {
375 assert cfg.sysroot == ''
376 assert cfg.target_args == []string{}
377 assert cfg.trailing_include_args == []string{}
378 return
379 }
380 assert cfg.target_args == ['-target x86_64-linux-gnu']
381 assert normalized_test_path(cfg.sysroot).ends_with('/linuxroot')
382 assert cfg.trailing_include_args == [
383 '-I',
384 os.quoted_path('${cfg.sysroot}/include'),
385 ]
386}
387
388fn test_thirdparty_cross_compile_config_for_freebsd_matches_target() {
389 builder := new_builder_for_args(['-os', 'freebsd', hello_world_example()])
390 cfg := builder.thirdparty_cross_compile_config()
391 if current_os == 'freebsd' {
392 assert cfg.sysroot == ''
393 assert cfg.target_args == []string{}
394 assert cfg.trailing_include_args == []string{}
395 return
396 }
397 assert cfg.target_args == ['-target x86_64-unknown-freebsd14.0']
398 assert normalized_test_path(cfg.sysroot).ends_with('/freebsdroot')
399 assert cfg.trailing_include_args == [
400 '-I',
401 os.quoted_path('${cfg.sysroot}/include'),
402 '-I',
403 os.quoted_path('${cfg.sysroot}/usr/include'),
404 ]
405}
406
407fn test_linux_tcc_arm64_stdatomic_does_not_add_atomic_s() {
408 $if !linux {
409 return
410 }
411 test_dir := os.join_path(os.vtmp_dir(), 'v_builder_stdatomic_tcc_arm64_${os.getpid()}')
412 os.mkdir_all(test_dir) or { panic(err) }
413 defer {
414 os.rmdir_all(test_dir) or {}
415 }
416 src_file := os.join_path(test_dir, 'main.v')
417 os.write_file(src_file, 'import sync.stdatomic
418fn main() {
419 mut x := u64(0)
420 stdatomic.store_u64(&x, 1)
421}
422') or {
423 panic(err)
424 }
425 res :=
426 os.execute('${os.quoted_path(@VEXE)} -dump-c-flags - -os linux -cc tcc -arch arm64 ${os.quoted_path(src_file)}')
427 assert res.exit_code == 0, res.output
428 assert !res.output.contains('thirdparty/stdatomic/nix/atomic.S')
429 // libatomic.so is only emitted when the host has the aarch64 cross-compile gcc toolchain installed.
430 matches := os.glob('/usr/lib/gcc/aarch64-linux-gnu/*/libatomic.so') or { []string{} }
431 if matches.len > 0 {
432 assert res.output.contains('libatomic.so')
433 }
434}
435
436fn test_live_windows_main_linker_args_export_host_symbols() {
437 linker_args := builder_linker_args_with_cc([
438 '-os',
439 'windows',
440 '-cc',
441 'gcc',
442 '-live',
443 hot_reload_graph_example(),
444 ], .gcc)
445 assert linker_args.contains('-Wl,--export-all-symbols')
446 assert linker_args.contains('-Wl,--out-implib,')
447 expected_import_lib := os.file_name(live_windows_import_lib_path(hot_reload_graph_example()))
448 assert linker_args.contains(expected_import_lib), 'linker_args should contain ${expected_import_lib}'
449}
450
451fn test_live_windows_shared_linker_args_include_host_import_lib() {
452 linker_args := builder_linker_args_with_cc([
453 '-os',
454 'windows',
455 '-cc',
456 'gcc',
457 '-sharedlive',
458 '-shared',
459 hot_reload_graph_example(),
460 ], .gcc)
461 expected_import_lib := os.file_name(live_windows_import_lib_path(hot_reload_graph_example()))
462 assert linker_args.contains(expected_import_lib), 'linker_args should contain ${expected_import_lib}'
463}
464
465fn test_windows_cross_compile_args_match_shared_prod_args() {
466 $if windows {
467 return
468 }
469 mut builder := new_test_builder(['-os', 'windows', '-prod', hello_world_example()])
470 all_args := builder.windows_cross_compile_args('')
471 assert all_args == builder.all_args(builder.ccoptions)
472 assert all_args.contains('-O3')
473 assert all_args.contains('-fwrapv')
474 assert all_args.contains('-fno-strict-aliasing')
475 assert all_args.contains('-DNDEBUG')
476 assert all_args.contains('-DNO_DEBUGGING')
477}
478
479fn test_shared_windows_builds_do_not_add_subsystem_flags() {
480 mut builder := new_test_builder(['-os', 'windows', '-shared', hello_world_example()])
481 assert builder.get_subsystem_flag() == ''
482 compile_args := builder.get_compile_args().join(' ')
483 assert !compile_args.contains('-municode')
484 assert !compile_args.contains('-mwindows')
485 assert !compile_args.contains('-mconsole')
486}
487
488fn test_windows_gcc_compile_args_force_generated_source_to_c_mode() {
489 compile_args := builder_compile_args(['-os', 'windows', '-cc', 'gcc', hello_world_example()])
490 assert compile_args.contains('-x c')
491}
492
493fn test_shared_tcc_compile_args_skip_bt25_after_late_compiler_resolution() {
494 mut full_args := ['']
495 full_args << ['-shared', hello_world_example()]
496 mut prefs, _ := pref.parse_args_and_show_errors([], full_args, false)
497 prefs.ccompiler = 'tcc'
498 prefs.ccompiler_type = .tinyc
499 prefs.normalize_gc_defaults_for_resolved_ccompiler()
500 assert 'no_backtrace' in prefs.compile_defines_all
501
502 mut builder := new_builder(prefs)
503 builder.out_name_c = os.join_path(os.vtmp_dir(), 'builder_cc_test.tmp.c')
504 builder.setup_ccompiler_options(prefs.ccompiler)
505
506 assert !builder.get_compile_args().contains('-bt25')
507}
508
509fn test_linux_shared_boehm_linker_args_hide_static_gc_archive_symbols() {
510 linker_args := builder_linker_args_with_cc([
511 '-os',
512 'linux',
513 '-shared',
514 '-gc',
515 'boehm',
516 hello_world_example(),
517 ], .gcc)
518 assert linker_args.contains('-Wl,-Bsymbolic')
519 assert linker_args.contains('-Wl,--exclude-libs,ALL')
520}
521
522fn test_linux_shared_boehm_tcc_linker_args_skip_gnu_exclude_libs() {
523 linker_args := builder_linker_args_with_cc([
524 '-os',
525 'linux',
526 '-shared',
527 '-gc',
528 'boehm',
529 hello_world_example(),
530 ], .tcc)
531 assert linker_args.contains('-Wl,-Bsymbolic')
532 assert !linker_args.contains('-Wl,--exclude-libs,ALL')
533}
534
535fn test_should_use_rsp_for_linux_by_default() {
536 builder := new_test_builder([hello_world_example()])
537 assert builder.should_use_rsp(['-o', builder.out_name_c])
538}
539
540fn test_should_not_use_rsp_for_termux() {
541 builder := new_test_builder(['-os', 'termux', hello_world_example()])
542 assert !builder.should_use_rsp(['-o', builder.out_name_c])
543}
544
545fn test_should_not_use_rsp_for_args_with_embedded_single_quotes() {
546 builder := new_test_builder([hello_world_example()])
547 assert !builder.should_use_rsp(["'\\''"])
548}
549
550fn test_setup_ccompiler_options_detects_cc_alias_path_as_clang() {
551 $if windows {
552 return
553 }
554 test_root := os.join_path(os.vtmp_dir(), 'v_builder_cc_setup_${os.getpid()}')
555 alias_cc := prepare_test_ccompiler_alias(test_root, 'cc', 'OpenBSD clang version 16.0.6')
556 defer {
557 os.rmdir_all(test_root) or {}
558 }
559 mut full_args := ['']
560 full_args << hello_world_example()
561 mut prefs, _ := pref.parse_args_and_show_errors([], full_args, false)
562 prefs.ccompiler = alias_cc
563 prefs.ccompiler_type = pref.cc_from_string(prefs.ccompiler)
564 mut builder := new_builder(prefs)
565 builder.out_name_c = os.join_path(os.vtmp_dir(), 'builder_cc_test.tmp.c')
566 builder.setup_ccompiler_options(prefs.ccompiler)
567 assert builder.pref.ccompiler_type == .clang
568 assert builder.ccoptions.cc == .clang
569}
570
571fn macos_compile_args(args []string) string {
572 return builder_compile_args(args)
573}
574
575fn builder_compile_args(args []string) string {
576 builder := new_test_builder(args)
577 return builder.get_compile_args().join(' ')
578}
579
580fn builder_linker_args(args []string) string {
581 builder := new_test_builder(args)
582 return builder.get_linker_args().join(' ')
583}
584
585fn builder_linker_args_with_cc(args []string, cc CC) string {
586 mut builder := new_test_builder_without_cc_setup(args)
587 ccompiler := ccompiler_name_for_test_cc(cc)
588 builder.pref.ccompiler = ccompiler
589 builder.pref.ccompiler_type = pref.cc_from_string(ccompiler)
590 builder.setup_ccompiler_options(ccompiler)
591 builder.setup_output_name()
592 return builder.get_linker_args().join(' ')
593}
594
595fn new_test_builder(args []string) Builder {
596 mut builder := new_test_builder_without_cc_setup(args)
597 builder.setup_ccompiler_options(builder.pref.ccompiler)
598 builder.setup_output_name()
599 return builder
600}
601
602fn new_test_builder_without_cc_setup(args []string) Builder {
603 mut full_args := ['']
604 full_args << args
605 prefs, _ := pref.parse_args_and_show_errors([], full_args, false)
606 mut builder := new_builder(prefs)
607 builder.out_name_c = os.join_path(os.vtmp_dir(), 'builder_cc_test.tmp.c')
608 return builder
609}
610
611fn ccompiler_name_for_test_cc(cc CC) string {
612 return match cc {
613 .tcc { 'tcc' }
614 .gcc { 'gcc' }
615 .icc { 'icc' }
616 .msvc { 'msvc' }
617 .clang { 'clang' }
618 .emcc { 'emcc' }
619 .unknown { '' }
620 }
621}
622
623fn new_builder_for_args(args []string) Builder {
624 mut full_args := ['']
625 full_args << args
626 prefs, _ := pref.parse_args_and_show_errors([], full_args, false)
627 return new_builder(prefs)
628}
629
630fn macos_version_min_flags(compile_args string) []string {
631 return compile_args.split(' ').filter(it.starts_with('-mmacosx-version-min='))
632}
633
634fn hello_world_example() string {
635 return os.join_path(@VEXEROOT, 'examples', 'hello_world.v')
636}
637
638fn hot_reload_graph_example() string {
639 return os.join_path(@VEXEROOT, 'examples', 'hot_reload', 'graph.v')
640}
641
642fn normalized_test_path(path string) string {
643 mut normalized := path.replace('\\', '/')
644 for normalized.contains('//') {
645 normalized = normalized.replace('//', '/')
646 }
647 return normalized
648}
649
650fn test_c_output_suggests_missing_typedef_for_c_struct_with_issue_19050_output() {
651 c_output := [
652 "/tmp/v_501/c_struct.6580681062929530137.tmp.c:12966:17: error: incomplete result type 'struct string_c' in function definition",
653 'struct string_c main__convert(string s) {',
654 ' ^',
655 "/tmp/v_501/c_struct.6580681062929530137.tmp.c:1962:8: note: forward declaration of 'struct string_c'",
656 'struct string_c main__convert(string s);',
657 ' ^',
658 "/tmp/v_501/c_struct.6580681062929530137.tmp.c:12967:25: error: variable has incomplete type 'struct string_c'",
659 ' struct string_c _t1 = ((struct string_c){.content = s.str,.len = ((u32)(s.len)),});',
660 ' ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
661 ].join('\n')
662 assert c_output_suggests_missing_typedef_for_c_struct(c_output, {
663 'string_c': true
664 }) == 'string_c'
665}
666
667fn test_c_output_suggests_missing_typedef_for_c_struct_requires_matching_redeclaration() {
668 c_output := [
669 "/tmp/v_501/c_struct.tmp.c:1:1: error: incomplete result type 'struct string_c' in function definition",
670 "/tmp/v_501/c_struct.tmp.c:2:1: note: forward declaration of 'struct string_c'",
671 ].join('\n')
672 assert c_output_suggests_missing_typedef_for_c_struct(c_output, {
673 'other_c_struct': true
674 }) == ''
675}
676
677fn test_c_output_suggests_missing_typedef_for_c_struct_with_issue_17101_output() {
678 c_output := [
679 "C:\\Users\\USER\\AppData\\Local\\Temp\\v_0\\main2.3589850650208729523.tmp.c:12338:99: error: 'tilengine__TLN_Affine' {aka 'struct TLN_Affine'} has no member named 'angle'",
680 '12338 | tilengine__TLN_Affine* affine = ((tilengine__TLN_Affine*)memdup(&(tilengine__TLN_Affine){.angle = 10,.dx = 1,.dy = 1,.sx = 1,.sy = 1,}, sizeof(tilengine__TLN_Affine)));',
681 ' | ^~~~~',
682 ].join('\n')
683 assert c_output_suggests_missing_typedef_for_c_struct(c_output, {
684 'TLN_Affine': true
685 }) == 'TLN_Affine'
686}
687
688fn test_extract_quoted_identifier_supports_double_quotes() {
689 assert extract_quoted_identifier('error: \';\' expected (got "glfw__GLFWwindow")') == 'glfw__GLFWwindow'
690}
691
692fn test_c_output_suggests_missing_header_for_typedef_c_struct_with_issue_25384_tcc_output() {
693 c_output := 'C:/Users/si_z_/AppData/Local/Temp/v_0/TestV.tmp.c:904: error: \';\' expected (got "glfw__GLFWwindow")'
694 assert c_output_suggests_missing_header_for_typedef_c_struct(c_output, {
695 'GLFWwindow': true
696 }, {
697 'glfw__GLFWwindow': 'GLFWwindow'
698 }) == 'GLFWwindow'
699}
700
701fn test_c_output_suggests_missing_header_for_typedef_c_struct_with_issue_25384_gcc_output() {
702 c_output := [
703 "/tmp/v_502/issue25384_windows.exe.tmp.c:1198:9: error: unknown type name 'GLFWwindow'",
704 ' 1198 | typedef GLFWwindow glfw__GLFWwindow;',
705 ].join('\n')
706 assert c_output_suggests_missing_header_for_typedef_c_struct(c_output, {
707 'GLFWwindow': true
708 }, {
709 'glfw__GLFWwindow': 'GLFWwindow'
710 }) == 'GLFWwindow'
711}
712
713fn test_c_output_suggests_missing_header_for_typedef_c_struct_requires_known_type() {
714 c_output := 'C:/Users/si_z_/AppData/Local/Temp/v_0/TestV.tmp.c:904: error: \';\' expected (got "glfw__GLFWwindow")'
715 assert c_output_suggests_missing_header_for_typedef_c_struct(c_output, {}, {}) == ''
716}
717
718fn test_c_output_suggests_missing_header_for_typedef_c_struct_with_issue_23648_tcc_output() {
719 c_output := 'D:/Temp/Temp/v_0/main.tmp.c:1028: error: \';\' expected (got "duarteroso__glfw__GLFWmonitor")'
720 assert c_output_suggests_missing_header_for_typedef_c_struct(c_output, {
721 'GLFWmonitor': true
722 }, {}) == 'GLFWmonitor'
723}
724
725fn test_c_error_missing_library_name_detects_tcc_output() {
726 tcc_output := "tcc: error: library 'pq' not found"
727 lib_name := c_error_missing_library_name(tcc_output)
728 assert lib_name == 'pq'
729}
730
731fn test_c_error_missing_libatomic_marker_with_tcc_output() {
732 c_output := "/tmp/v/vdoc.tmp.c:24184: warning: assignment makes pointer from integer without a cast\ntcc: error: library 'atomic' not found\n"
733 assert c_error_missing_libatomic_marker(c_output) == "library 'atomic' not found"
734 assert c_error_looks_like_missing_libatomic(c_output)
735}
736
737fn test_c_error_missing_libatomic_marker_with_ld_output() {
738 c_output := '/usr/bin/ld: cannot find -latomic\ncollect2: error: ld returned 1 exit status\n'
739 assert c_error_missing_libatomic_marker(c_output) == 'cannot find -latomic'
740 assert c_error_looks_like_missing_libatomic(c_output)
741}
742
743fn test_c_error_missing_libatomic_marker_with_regular_c_error() {
744 c_output := "error: unknown type name 'my_missing_type'"
745 assert c_error_missing_libatomic_marker(c_output) == ''
746 assert !c_error_looks_like_missing_libatomic(c_output)
747}
748
749fn test_c_error_missing_library_name_with_macos_ld_output() {
750 c_output := "ld: library 'mbedtls' not found\nclang: error: linker command failed with exit code 1\n"
751 assert c_error_missing_library_name(c_output) == 'mbedtls'
752}
753
754fn test_c_error_missing_library_name_with_gnu_ld_output() {
755 c_output := '/usr/bin/ld: cannot find -lssl\ncollect2: error: ld returned 1 exit status\n'
756 assert c_error_missing_library_name(c_output) == 'ssl'
757}
758
759fn test_c_error_missing_library_name_with_mingw_ld_output() {
760 c_output := 'C:/msys64/ucrt64/bin/ld.exe: cannot find libv_missing_lib_25499.dll.a: No such file or directory\ncollect2.exe: error: ld returned 1 exit status\n'
761 assert c_error_missing_library_name(c_output) == 'v_missing_lib_25499'
762}
763
764fn test_c_error_missing_library_name_with_mingw_lflag_suffix_output() {
765 c_output := 'C:/msys64/ucrt64/bin/ld.exe: cannot find -lv_missing_lib_25499.dll.a: No such file or directory\ncollect2.exe: error: ld returned 1 exit status\n'
766 assert c_error_missing_library_name(c_output) == 'v_missing_lib_25499'
767}
768
769fn test_c_error_missing_library_name_with_mingw_plain_library_output() {
770 c_output := 'C:/msys64/ucrt64/bin/ld.exe: cannot find v_missing_lib_25499: No such file or directory\ncollect2.exe: error: ld returned 1 exit status\n'
771 assert c_error_missing_library_name(c_output) == 'v_missing_lib_25499'
772}
773
774fn test_c_error_missing_library_name_with_mingw_driver_missing_file_output() {
775 c_output := 'x86_64-w64-mingw32-gcc.exe: error: libv_missing_lib_25499.dll.a: No such file or directory\n'
776 assert c_error_missing_library_name(c_output) == 'v_missing_lib_25499'
777}
778
779fn test_c_error_missing_library_name_ignores_missing_object_file() {
780 c_output := '/usr/bin/ld: cannot find crt1.o: No such file or directory\ncollect2: error: ld returned 1 exit status\n'
781 assert c_error_missing_library_name(c_output) == ''
782}
783
784fn test_c_error_missing_library_name_with_regular_c_error() {
785 c_output := "error: unknown type name 'my_missing_type'"
786 assert c_error_missing_library_name(c_output) == ''
787}
788
789fn test_c_error_should_send_bug_report_skips_libatomic() {
790 c_output := '/usr/bin/ld: cannot find -latomic\ncollect2: error: ld returned 1 exit status\n'
791 assert !c_error_should_send_bug_report(c_output)
792}
793
794fn test_c_error_should_send_bug_report_skips_missing_library() {
795 c_output := '/usr/bin/ld: cannot find -lllvm\ncollect2: error: ld returned 1 exit status\n'
796 assert !c_error_should_send_bug_report(c_output)
797}
798
799fn test_c_error_should_send_bug_report_for_regular_c_error() {
800 c_output := "error: unknown type name 'my_missing_type'"
801 assert c_error_should_send_bug_report(c_output)
802}
803
804fn test_thirdparty_module_root_spans_library_and_include() {
805 // A source under library/ resolves to the whole `thirdparty/<mod>` root, so a
806 // sibling include/ tree (e.g. mbedtls config headers) is in scope when
807 // computing the object's header-dependency mtime.
808 assert thirdparty_module_root('/x/y/thirdparty/mbedtls/library/aes.c') == '/x/y/thirdparty/mbedtls'
809 // Windows-style separators are normalized to forward slashes.
810 assert thirdparty_module_root('S:\\repo\\thirdparty\\mbedtls\\include\\mbedtls\\cfg.h') == 'S:/repo/thirdparty/mbedtls'
811 // A path that is not under a thirdparty/ tree falls back to the source dir.
812 assert thirdparty_module_root('/tmp/foo/bar.c') == os.dir('/tmp/foo/bar.c')
813}
814
815// Regression guard for vlang/v#27437: thirdparty objects must rebuild when a
816// header anywhere in the module changes, including config headers under
817// include/ (not just siblings of the .c). thirdparty_deps_mtime is the shared
818// cache key for both the C and MSVC object paths, so this protects both.
819fn test_thirdparty_deps_mtime_includes_module_include_headers() {
820 test_root := os.join_path(os.vtmp_dir(), 'v_builder_thirdparty_deps_${os.getpid()}')
821 os.rmdir_all(test_root) or {}
822 defer {
823 os.rmdir_all(test_root) or {}
824 }
825 lib_dir := os.join_path(test_root, 'thirdparty', 'mymod', 'library')
826 inc_dir := os.join_path(test_root, 'thirdparty', 'mymod', 'include', 'mymod')
827 os.mkdir_all(lib_dir) or { panic(err) }
828 os.mkdir_all(inc_dir) or { panic(err) }
829 source := os.join_path(lib_dir, 'foo.c')
830 sibling := os.join_path(lib_dir, 'foo.h')
831 os.write_file(source, 'int foo(void){return 0;}') or { panic(err) }
832 os.write_file(sibling, '/* sibling header */') or { panic(err) }
833 // file_last_mod_unix is second-resolution, so a >1s gap is needed for the
834 // config header to be observably newer than the source.
835 time.sleep(1100 * time.millisecond)
836 config := os.join_path(inc_dir, 'mymod_config.h')
837 os.write_file(config, '#define MYMOD 1') or { panic(err) }
838
839 mut b := new_builder_for_args([hello_world_example()])
840 deps := b.thirdparty_deps_mtime(source)
841 config_mtime := os.file_last_mod_unix(config)
842 source_mtime := os.file_last_mod_unix(source)
843 assert config_mtime > source_mtime, 'precondition: the include/ config header must be newer than the source'
844 // The config header lives under include/, not in the source directory, yet
845 // the dependency mtime must still reflect it — otherwise stale objects are
846 // reused after a config-header change (the bug this fix addresses).
847 assert deps == config_mtime, 'thirdparty_deps_mtime must fold in include/ headers; got ${deps}, want ${config_mtime}'
848 // Memoized per module root: a second call returns the same value.
849 assert b.thirdparty_deps_mtime(source) == config_mtime
850}
851
852fn prepare_test_ccompiler_alias(test_root string, compiler_name string, version_output string) string {
853 os.rmdir_all(test_root) or {}
854 os.mkdir_all(test_root) or { panic(err) }
855 compiler_path := os.join_path(test_root, compiler_name)
856 os.write_file(compiler_path, '#!/bin/sh
857if [ "\$1" = "--version" ] || [ "\$1" = "-v" ]; then
858 echo "${version_output}"
859 exit 0
860fi
861exit 1
862') or {
863 panic(err)
864 }
865 os.chmod(compiler_path, 0o700) or { panic(err) }
866 return compiler_path
867}
868