vxx2 / vlib / v / builder / cc.v
2749 lines · 2613 sloc · 90.11 KB · 2085cbb45161b78712c3214add56edbb26aa7713
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module builder
5
6import hash.fnv1a
7import os
8import v.ast
9import v.cflag
10import v.pref
11import v.util
12import v.vcache
13import term
14
15const c_std = 'c99'
16const cpp_std = 'c++17'
17
18const c_verror_message_marker = 'VERROR_MESSAGE '
19
20const current_os = os.user_os()
21
22const c_compilation_error_title = 'C compilation error'
23const missing_libatomic_markers = [
24 "library 'atomic' not found",
25 'cannot find -latomic',
26 'unable to find library -latomic',
27 'library not found for -latomic',
28 'cannot find libatomic',
29]!
30const max_cross_sysroot_git_symlink_depth = 32
31const max_cross_sysroot_git_symlink_placeholder_size = 256
32
33fn live_windows_import_lib_path(source_path string) string {
34 cache_dir := os.join_path(os.cache_dir(), 'v', 'live')
35 os.mkdir_all(cache_dir) or {}
36 key := fnv1a.sum64_string(os.real_path(source_path)).str()
37 return os.join_path(cache_dir, 'host_symbols_${key}.a')
38}
39
40fn extract_c_struct_name(line string) string {
41 start := line.index('struct ') or { return '' } + 'struct '.len
42 mut end := start
43 for end < line.len {
44 ch := line[end]
45 if !ch.is_letter() && !ch.is_digit() && ch != `_` {
46 break
47 }
48 end++
49 }
50 return if end > start { line[start..end] } else { '' }
51}
52
53fn extract_quoted_identifier(line string) string {
54 for quote in [u8(`"`), u8(`'`), u8(96)] {
55 start := line.index_u8(quote)
56 if start == -1 {
57 continue
58 }
59 end := line[start + 1..].index_u8(quote)
60 if end == -1 {
61 continue
62 }
63 return line[start + 1..start + 1 + end]
64 }
65 return ''
66}
67
68fn c_output_suggests_missing_header_for_typedef_c_struct(c_output string, known_typedef_c_structs map[string]bool, known_typedef_c_struct_aliases map[string]string) string {
69 if known_typedef_c_structs.len == 0 && known_typedef_c_struct_aliases.len == 0 {
70 return ''
71 }
72 for line in c_output.split_into_lines() {
73 lower_line := line.to_lower()
74 name := extract_quoted_identifier(line)
75 if name != '' {
76 if lower_line.contains('unknown type name') && name in known_typedef_c_structs {
77 return name
78 }
79 if name in known_typedef_c_struct_aliases
80 && (lower_line.contains('expected (got') || lower_line.contains('unknown type name')
81 || lower_line.contains('undeclared identifier')
82 || lower_line.contains('does not name a type')) {
83 return known_typedef_c_struct_aliases[name]
84 }
85 if name.contains('__')
86 && (lower_line.contains('expected (got') || lower_line.contains('unknown type name')
87 || lower_line.contains('undeclared identifier')
88 || lower_line.contains('does not name a type')) {
89 suffix := name.all_after_last('__')
90 if suffix in known_typedef_c_structs {
91 return suffix
92 }
93 }
94 }
95 }
96 return ''
97}
98
99fn c_output_suggests_missing_typedef_for_c_struct(c_output string, known_non_typedef_c_structs map[string]bool) string {
100 if known_non_typedef_c_structs.len == 0 {
101 return ''
102 }
103 mut forward_declared := map[string]bool{}
104 mut incomplete := map[string]bool{}
105 for line in c_output.split_into_lines() {
106 name := extract_c_struct_name(line)
107 if name == '' || name !in known_non_typedef_c_structs {
108 continue
109 }
110 lower_line := line.to_lower()
111 if lower_line.contains('has no member named') && (lower_line.contains("aka 'struct ")
112 || lower_line.contains('aka `struct ')
113 || lower_line.contains('aka "struct ')) {
114 return name
115 }
116 if lower_line.contains('forward declaration of') {
117 if name in incomplete {
118 return name
119 }
120 forward_declared[name] = true
121 continue
122 }
123 if lower_line.contains('incomplete result type')
124 || lower_line.contains('has incomplete type') || lower_line.contains('incomplete type')
125 || lower_line.contains('return type is an incomplete type') {
126 if name in forward_declared {
127 return name
128 }
129 incomplete[name] = true
130 }
131 }
132 return ''
133}
134
135fn c_output_suggests_missing_sokol_shader_symbol(c_output string) string {
136 for line in c_output.split_into_lines() {
137 lower_line := line.to_lower()
138 if !lower_line.contains('undeclared identifier')
139 && !lower_line.contains('undeclared (first use in this function)') {
140 continue
141 }
142 name := extract_quoted_identifier(line)
143 if name.starts_with('ATTR_') || name.starts_with('SLOT_') {
144 return name
145 }
146 }
147 return ''
148}
149
150fn is_c_identifier_start(ch u8) bool {
151 return ch.is_letter() || ch == `_`
152}
153
154fn is_c_identifier_continue(ch u8) bool {
155 return ch.is_letter() || ch.is_digit() || ch == `_`
156}
157
158fn extract_c_identifier_after_marker(line string, lower_line string, marker string) string {
159 marker_start := lower_line.index(marker) or { return '' }
160 start_search := marker_start + marker.len
161 mut start := start_search
162 for start < line.len {
163 if is_c_identifier_start(line[start]) {
164 break
165 }
166 start++
167 }
168 if start >= line.len {
169 return ''
170 }
171 mut end := start + 1
172 for end < line.len && is_c_identifier_continue(line[end]) {
173 end++
174 }
175 return line[start..end]
176}
177
178fn extract_undeclared_c_function_name(line string) string {
179 lower_line := line.to_lower()
180 for marker in ['call to undeclared function', 'implicit declaration of function'] {
181 name := extract_c_identifier_after_marker(line, lower_line, marker)
182 if name != '' {
183 return name
184 }
185 }
186 return ''
187}
188
189fn c_output_suggests_missing_c_function(c_output string, known_c_functions map[string]string) string {
190 if known_c_functions.len == 0 {
191 return ''
192 }
193 for line in c_output.split_into_lines() {
194 name := extract_undeclared_c_function_name(line)
195 if name in known_c_functions {
196 return known_c_functions[name]
197 }
198 }
199 return ''
200}
201
202fn (v &Builder) known_c_functions() map[string]string {
203 mut names := map[string]string{}
204 for _, func in v.table.fns {
205 if func.language != .c || !func.name.starts_with('C.') {
206 continue
207 }
208 c_name := util.no_dots(func.name[2..])
209 names[c_name] = func.name
210 if cattr := func.attrs.find_first('c') {
211 names[cattr.arg] = func.name
212 }
213 }
214 return names
215}
216
217fn (v &Builder) known_non_typedef_c_structs() map[string]bool {
218 mut names := map[string]bool{}
219 for sym in v.table.type_symbols {
220 if sym.language != .c || sym.kind != .struct || !sym.cname.starts_with('C__') {
221 continue
222 }
223 info := sym.info as ast.Struct
224 if info.is_typedef {
225 continue
226 }
227 names[sym.cname[3..]] = true
228 }
229 return names
230}
231
232fn (v &Builder) known_typedef_c_structs() map[string]bool {
233 mut names := map[string]bool{}
234 for sym in v.table.type_symbols {
235 if sym.language != .c || sym.kind != .struct || !sym.cname.starts_with('C__') {
236 continue
237 }
238 info := sym.info as ast.Struct
239 if !info.is_typedef {
240 continue
241 }
242 names[sym.cname[3..]] = true
243 }
244 return names
245}
246
247fn (v &Builder) known_typedef_c_struct_aliases() map[string]string {
248 mut aliases := map[string]string{}
249 for sym in v.table.type_symbols {
250 if sym.kind != .alias {
251 continue
252 }
253 alias_info := sym.info as ast.Alias
254 parent_sym := v.table.final_sym(alias_info.parent_type)
255 if parent_sym.language != .c || parent_sym.kind != .struct
256 || !parent_sym.cname.starts_with('C__') {
257 continue
258 }
259 parent_info := parent_sym.info as ast.Struct
260 if !parent_info.is_typedef {
261 continue
262 }
263 aliases[sym.cname] = parent_sym.cname[3..]
264 }
265 return aliases
266}
267
268fn c_error_looks_like_cpp_header(c_output string) bool {
269 lower_output := c_output.to_lower()
270 for marker in [
271 "unknown type name 'namespace'",
272 "unknown type name 'class'",
273 "unknown type name 'template'",
274 'unknown type name `namespace`',
275 'unknown type name `class`',
276 'unknown type name `template`',
277 'error: namespace',
278 'namespace does not name a type',
279 "'operator' declared as",
280 '`operator` declared as',
281 "before 'operator'",
282 'before `operator`',
283 'before "operator"',
284 ] {
285 if lower_output.contains(marker) {
286 return true
287 }
288 }
289 for line in lower_output.split_into_lines() {
290 trimmed_line := line.trim_space()
291 if trimmed_line.starts_with('namespace ') || trimmed_line.contains('| namespace ')
292 || trimmed_line.starts_with('class ') || trimmed_line.contains('| class ')
293 || trimmed_line.starts_with('public:') || trimmed_line.contains('| public:')
294 || trimmed_line.starts_with('private:') || trimmed_line.contains('| private:')
295 || trimmed_line.starts_with('protected:') || trimmed_line.contains('| protected:')
296 || trimmed_line.contains('template<') || trimmed_line.contains('template <')
297 || trimmed_line.contains('operator[]') || trimmed_line.contains('operator []')
298 || trimmed_line.contains('::') {
299 return true
300 }
301 }
302 return false
303}
304
305fn (v &Builder) ensure_imported_coroutines_runtime() ! {
306 if 'coroutines' !in v.table.imports {
307 return
308 }
309 pref.ensure_coroutines_runtime()!
310}
311
312fn c_error_missing_libatomic_marker(c_output string) string {
313 for line in c_output.split_into_lines() {
314 lower_line := line.to_lower()
315 for marker in missing_libatomic_markers {
316 if start := lower_line.index(marker) {
317 return line[start..start + marker.len]
318 }
319 }
320 }
321 return ''
322}
323
324fn c_error_looks_like_missing_libatomic(c_output string) bool {
325 return c_error_missing_libatomic_marker(c_output) != ''
326}
327
328fn normalized_linker_library_file_name(lib_name string) string {
329 for suffix in ['.dll.a', '.lib', '.a', '.dll'] {
330 if lib_name.ends_with(suffix) {
331 mut normalized := lib_name[..lib_name.len - suffix.len]
332 if normalized.starts_with('lib') {
333 normalized = normalized[3..]
334 }
335 return normalized
336 }
337 }
338 return lib_name
339}
340
341fn normalized_missing_library_name(raw_name string, allow_plain_name bool) string {
342 mut lib_name := raw_name.trim_space().trim('`\'"')
343 if lib_name.len > 1 && lib_name[1] == `:` {
344 return ''
345 }
346 for delimiter in ['`', "'", '"', ' ', ':'] {
347 lib_name = lib_name.all_before(delimiter)
348 }
349 lib_name = lib_name.trim_space().trim('`\'"')
350 if lib_name.starts_with('-l') {
351 return normalized_linker_library_file_name(lib_name[2..])
352 }
353 normalized := normalized_linker_library_file_name(lib_name)
354 if normalized != lib_name {
355 return normalized
356 }
357 if allow_plain_name {
358 if lib_name.contains('/') || lib_name.contains('\\') || lib_name.contains('.')
359 || lib_name.starts_with('-') {
360 return ''
361 }
362 return lib_name
363 }
364 return ''
365}
366
367fn c_error_missing_library_name(c_output string) string {
368 for line in c_output.split_into_lines() {
369 if line.contains("library '") && line.contains("' not found") {
370 return normalized_missing_library_name(line.all_after("library '").all_before("' not found"),
371 true)
372 }
373 lower_line := line.to_lower()
374 for marker in [
375 'cannot find -l',
376 'unable to find library -l',
377 'library not found for -l',
378 ] {
379 if start := lower_line.index(marker) {
380 return normalized_missing_library_name(line[start + marker.len..], true)
381 }
382 }
383 if start := lower_line.index('cannot find ') {
384 lib_name := normalized_missing_library_name(line[start + 'cannot find '.len..], true)
385 if lib_name != '' {
386 return lib_name
387 }
388 }
389 if line.contains(': error: ') && lower_line.contains(': no such file or directory') {
390 lib_name := normalized_missing_library_name(line.all_after(': error: ').all_before(': No such file or directory'),
391 false)
392 if lib_name != '' {
393 return lib_name
394 }
395 }
396 }
397 return ''
398}
399
400fn c_error_should_send_bug_report(c_output string) bool {
401 if c_error_missing_libatomic_marker(c_output) != '' {
402 return false
403 }
404 return c_error_missing_library_name(c_output) == ''
405}
406
407fn (mut v Builder) show_c_compiler_output(ccompiler string, res os.Result) {
408 header := '======== Output of the C Compiler (${ccompiler}) ========'
409 println(header)
410 if res.output.len > 0 {
411 println(res.output.trim_space())
412 }
413 println('='.repeat(header.len))
414}
415
416struct CCompilerFailureOutput {
417 display_ccompiler string
418 display_res os.Result
419 report_ccompiler string
420 report_res os.Result
421}
422
423fn c_compiler_failure_output(ccompiler string, res os.Result, tcc_output os.Result) CCompilerFailureOutput {
424 if res.exit_code != 0 && tcc_output.output != '' {
425 return CCompilerFailureOutput{
426 display_ccompiler: 'tcc'
427 display_res: tcc_output
428 report_ccompiler: ccompiler
429 report_res: res
430 }
431 }
432 return CCompilerFailureOutput{
433 display_ccompiler: ccompiler
434 display_res: res
435 report_ccompiler: ccompiler
436 report_res: res
437 }
438}
439
440fn (mut v Builder) post_process_c_compiler_output(ccompiler string, res os.Result) {
441 v.post_process_c_compiler_output_with_report(ccompiler, res, ccompiler, res)
442}
443
444fn (mut v Builder) post_process_c_compiler_output_with_report(ccompiler string, res os.Result, report_ccompiler string, report_res os.Result) {
445 if res.exit_code == 0 {
446 if v.pref.reuse_tmpc {
447 return
448 }
449 if os.getenv('V_NO_RM_CLEANUP_FILES') != '' {
450 return
451 }
452 for tmpfile in v.pref.cleanup_files {
453 if os.is_file(tmpfile) {
454 if v.pref.is_verbose {
455 eprintln('>> remove tmp file: ${tmpfile}')
456 }
457 os.rm(tmpfile) or {}
458 }
459 }
460 return
461 }
462 libatomic_marker := c_error_missing_libatomic_marker(res.output)
463 missing_library_name := if libatomic_marker == '' {
464 c_error_missing_library_name(res.output)
465 } else {
466 ''
467 }
468 for emsg_marker in [c_verror_message_marker, 'error: include file '] {
469 if res.output.contains(emsg_marker) {
470 emessage :=
471 res.output.all_after(emsg_marker).all_before('\n').all_before('\r').trim_right('\r\n')
472 verror(emessage)
473 }
474 }
475 if v.pref.is_debug {
476 eword := 'error:'
477 khighlight := highlight_word(eword)
478 println(res.output.trim_right('\r\n').replace(eword, khighlight))
479 } else {
480 if res.output.len < 30 {
481 println(res.output)
482 } else {
483 trimmed_output := res.output.trim_space()
484 original_elines := trimmed_output.split_into_lines()
485 mlines := 12
486 cut_off_limit := if original_elines.len > mlines + 3 { mlines } else { mlines + 3 }
487 mut error_keyword := 'error:'
488 mut error_context_before := 1
489 if libatomic_marker != '' && trimmed_output.contains(libatomic_marker) {
490 error_keyword = libatomic_marker
491 error_context_before = 0
492 }
493 elines := error_context_lines(trimmed_output, error_keyword, error_context_before,
494 cut_off_limit)
495 header := '================== ${c_compilation_error_title} (from ${ccompiler}): =============='
496 println(header)
497 for eline in elines {
498 println('cc: ${eline}')
499 }
500 if original_elines.len != elines.len {
501 println('...')
502 println('cc: ${original_elines#[-1..][0]}')
503 println('(note: the original output was ${original_elines.len} lines long; it was truncated to its first ${elines.len} lines + the last line)')
504 }
505 println('='.repeat(header.len))
506 // Check for TCC cross-compilation errors
507 if ccompiler == 'tcc' && res.output.starts_with('tcc: error: could not run') {
508 println('${highlight_word('Suggestion')}: try using a different C compiler with `-cc gcc` or `-cc clang`.')
509 println('${highlight_word('Suggestion')}: or build TCC for the target architecture yourself.')
510 println('${highlight_word('Note')}: you should build an 32bit version of `${@VEXEROOT}/thirdparty/tcc/lib/libgc.a` first or use `-gc none`.')
511 exit(1)
512 } else {
513 println('Try passing `-g` when compiling, to see a .v file:line information, that correlates more with the C error.')
514 println('(Alternatively, pass `-show-c-output`, to print the full C error message).')
515 }
516 }
517 }
518 if c_error_should_send_bug_report(report_res.output) {
519 known_c_functions := v.known_c_functions()
520 if c_output_suggests_missing_c_function(report_res.output, known_c_functions) == '' {
521 v.submit_c_error_bug_report(report_ccompiler, report_res.output)
522 }
523 }
524 if v.pref.is_quiet {
525 exit(1)
526 }
527 mut more_suggestions := ''
528 if res.output.contains('o: unrecognized file type')
529 || res.output.contains('.o: file not recognized') {
530 more_suggestions += '\n${highlight_word('Suggestion')}: try `v wipe-cache`, then repeat your compilation.'
531 }
532 missing_typedef_header_name := c_output_suggests_missing_header_for_typedef_c_struct(res.output,
533 v.known_typedef_c_structs(), v.known_typedef_c_struct_aliases())
534 if missing_typedef_header_name != '' {
535 more_suggestions += '\n${highlight_word('Suggestion')}: the C typedef `${missing_typedef_header_name}` backing `@[typedef] struct C.${missing_typedef_header_name} {}` was not found by the C compiler. Make sure the header that defines it is included on this platform and that its `#flag -I` path is correct. If the C API actually declares `struct ${missing_typedef_header_name}` without a typedef, remove `@[typedef]` from the V redeclaration.'
536 }
537 missing_typedef_name := c_output_suggests_missing_typedef_for_c_struct(res.output,
538 v.known_non_typedef_c_structs())
539 if missing_typedef_name != '' {
540 more_suggestions += '\n${highlight_word('Suggestion')}: if `${missing_typedef_name}` is declared in the C header with `typedef struct ... ${missing_typedef_name};`, add `@[typedef]` to the V redeclaration: `@[typedef] struct C.${missing_typedef_name} { ... }`.'
541 }
542 missing_shader_symbol := c_output_suggests_missing_sokol_shader_symbol(res.output)
543 if missing_shader_symbol != '' {
544 more_suggestions += '\n${highlight_word('Suggestion')}: `${missing_shader_symbol}` looks like a sokol shader symbol generated by `v shader`/`sokol-shdc`. If you renamed `C.${missing_shader_symbol}` in V, make the same change in the matching `.glsl` file and regenerate the header with `v shader .`.'
545 }
546 missing_c_function := c_output_suggests_missing_c_function(res.output, v.known_c_functions())
547 if missing_c_function != '' {
548 c_name := missing_c_function[2..]
549 verror('
550==================
551C function `${missing_c_function}` was declared in V, but the C compiler did not see a matching C declaration for `${c_name}`.
552A declaration like `fn ${missing_c_function}()` only tells V about an external C symbol; it does not define that symbol or include its header.
553Include the C header that declares `${c_name}` and add any needed `#flag -I`, `#flag -l`, or C source file.
554If there is no header but the symbol is provided by a linked library/object, mark the V declaration with `@[c_extern]`.${more_suggestions}')
555 }
556 if c_error_looks_like_cpp_header(res.output) {
557 verror('
558==================
559C error found while compiling generated C code.
560It looks like a C++ header was included with `#include` (for example one that contains `namespace`).
561Use a C-compatible header (for HDF5 use `hdf5.h` instead of `H5File.h`), or compile/link the C++ code separately.${more_suggestions}')
562 }
563 if libatomic_marker != '' {
564 verror('
565==================
566C error found while compiling generated C code.
567The C toolchain could not find `libatomic`, which V needs for `sync.stdatomic` with this compiler on this platform.
568Install the system package that provides `libatomic` and retry.
569On CentOS/RHEL, that is usually `libatomic` or `libatomic-devel`.${more_suggestions}')
570 }
571 if missing_library_name != '' {
572 verror('
573==================
574C library `${missing_library_name}` was not found while linking the generated program.
575Please install the corresponding development package/libraries and make sure the linker can find it.${more_suggestions}')
576 }
577 verror('
578==================
579C error found while compiling generated C code.
580This can be caused by invalid C interop code, C compiler flags, or a V compiler bug.
581If your code is pure V and this still happens, please report it using `v bug file.v`,
582or goto https://github.com/vlang/v/issues/new/choose .
583You can also use #help on Discord: https://discord.gg/vlang .${more_suggestions}')
584}
585
586fn (mut v Builder) show_cc(cmd string, response_file string, response_file_content string) {
587 if v.pref.is_verbose || v.pref.show_cc {
588 println('> C compiler cmd: ${cmd}')
589 if v.pref.show_cc && !v.pref.no_rsp && response_file != '' {
590 println('> C compiler response file "${response_file}":')
591 println(response_file_content)
592 }
593 }
594}
595
596pub enum CC {
597 tcc
598 gcc
599 icc
600 msvc
601 clang
602 emcc
603 unknown
604}
605
606pub struct CcompilerOptions {
607pub mut:
608 guessed_compiler string
609 shared_postfix string // .so, .dll
610
611 debug_mode bool
612 cc CC
613
614 env_cflags string // prepended *before* everything else
615 env_ldflags string // appended *after* everything else
616
617 args []string // ordinary C options like `-O2`
618 wargs []string // for `-Wxyz` *exclusively*
619 pre_args []string // options that should go before .o_args
620 o_args []string // for `-o target`
621 source_args []string // for `x.tmp.c`
622 post_args []string // options that should go after .o_args
623 linker_flags []string // `-lm`
624 ldflags []string // `-labcd' from `v -ldflags "-labcd"`
625}
626
627type WindowsPathResolver = fn (string) string
628
629fn ccompiler_type_from_name_with_ok(ccompiler string) (pref.CompilerType, bool) {
630 cc_file_name := os.file_name(ccompiler).to_lower_ascii()
631 if is_tinyc_compiler_label(cc_file_name) {
632 return pref.CompilerType.tinyc, true
633 }
634 if cc_file_name.contains('gcc') {
635 return pref.CompilerType.gcc, true
636 }
637 if cc_file_name.contains('clang') {
638 return pref.CompilerType.clang, true
639 }
640 if cc_file_name.contains('emcc') {
641 return pref.CompilerType.emcc, true
642 }
643 if cc_file_name == 'cl' || cc_file_name == 'cl.exe' || cc_file_name.contains('msvc') {
644 return pref.CompilerType.msvc, true
645 }
646 if cc_file_name.contains('mingw') {
647 return pref.CompilerType.mingw, true
648 }
649 if cc_file_name.contains('++') {
650 return pref.CompilerType.cplusplus, true
651 }
652 return pref.CompilerType.tinyc, false
653}
654
655fn ccompiler_type_from_name(ccompiler string) ?pref.CompilerType {
656 resolved, ok := ccompiler_type_from_name_with_ok(ccompiler)
657 return if ok { resolved } else { none }
658}
659
660fn ccompiler_type_from_resolved_path(ccompiler string) ?pref.CompilerType {
661 ccompiler_path := if os.exists(ccompiler) {
662 ccompiler
663 } else {
664 os.find_abs_path_of_executable(ccompiler) or { return none }
665 }
666 $if macos {
667 if ccompiler_path == '/usr/bin/cc' {
668 return pref.CompilerType.clang
669 }
670 }
671 resolved, ok := ccompiler_type_from_name_with_ok(os.real_path(ccompiler_path))
672 return if ok { resolved } else { none }
673}
674
675fn ccompiler_type_from_version_output_with_ok(output string) (pref.CompilerType, bool) {
676 if output == '' {
677 return pref.CompilerType.tinyc, false
678 }
679 lower_output := output.to_lower_ascii()
680 if is_tinyc_version_output(lower_output) {
681 return pref.CompilerType.tinyc, true
682 }
683 if lower_output.contains('clang') {
684 return pref.CompilerType.clang, true
685 }
686 if lower_output.contains('gcc version') || lower_output.contains('(gcc)')
687 || lower_output.contains('free software foundation') || lower_output.contains('gcc ') {
688 return pref.CompilerType.gcc, true
689 }
690 if lower_output.contains('emscripten') || lower_output.contains('emcc') {
691 return pref.CompilerType.emcc, true
692 }
693 if (lower_output.contains('microsoft') && lower_output.contains('c/c++'))
694 || lower_output.contains('msvc') {
695 return pref.CompilerType.msvc, true
696 }
697 return pref.CompilerType.tinyc, false
698}
699
700fn ccompiler_type_from_version_output(output string) ?pref.CompilerType {
701 resolved, ok := ccompiler_type_from_version_output_with_ok(output)
702 return if ok { resolved } else { none }
703}
704
705fn resolve_ccompiler_type(ccompiler string, fallback pref.CompilerType) pref.CompilerType {
706 resolved_by_name, name_ok := ccompiler_type_from_name_with_ok(ccompiler)
707 if name_ok {
708 return resolved_by_name
709 }
710 if resolved_by_path := ccompiler_type_from_resolved_path(ccompiler) {
711 return resolved_by_path
712 }
713 quoted_ccompiler := os.quoted_path(ccompiler)
714 for version_flag in ['--version', '-v'] {
715 res := os.execute('${quoted_ccompiler} ${version_flag} 2>&1')
716 resolved_by_version, version_ok := ccompiler_type_from_version_output_with_ok(res.output)
717 if version_ok {
718 return resolved_by_version
719 }
720 }
721 return fallback
722}
723
724fn darwin_target_arch_name(arch pref.Arch) string {
725 return match arch {
726 .amd64 { 'x86_64' }
727 .arm64 { 'arm64' }
728 .i386 { 'i386' }
729 .ppc { 'ppc' }
730 .ppc64 { 'ppc64' }
731 else { '' }
732 }
733}
734
735fn cc_from_pref_ccompiler_type(cc_type pref.CompilerType) CC {
736 return match cc_type {
737 .tinyc { .tcc }
738 .gcc, .mingw { .gcc }
739 .clang { .clang }
740 .emcc { .emcc }
741 .msvc { .msvc }
742 .cplusplus { .unknown }
743 }
744}
745
746fn (mut v Builder) setup_ccompiler_options(ccompiler string) {
747 mut ccoptions := CcompilerOptions{}
748
749 mut debug_options := ['-g']
750 mut optimization_options := ['-O2']
751 // arguments for the C compiler
752 ccoptions.args = [v.pref.cflags]
753 ccoptions.ldflags = [v.pref.ldflags]
754 ccoptions.wargs = [
755 '-Wall',
756 '-Wextra',
757 '-Werror',
758 // if anything, these should be a `v vet` warning instead:
759 '-Wno-unused-parameter',
760 '-Wno-unused',
761 '-Wno-type-limits',
762 '-Wno-tautological-compare',
763 // these cause various issues:
764 '-Wno-shadow', // the V compiler already catches this for user code, and enabling this causes issues with e.g. the `it` variable
765 '-Wno-int-to-pointer-cast', // gcc version of the above
766 '-Wno-trigraphs', // see stackoverflow.com/a/8435413
767 '-Wno-missing-braces', // see stackoverflow.com/q/13746033
768 '-Wno-enum-conversion', // silences `.dst_factor_rgb = sokol__gfx__BlendFactor__one_minus_src_alpha`
769 '-Wno-enum-compare', // silences `if (ev->mouse_button == sokol__sapp__MouseButton__left) {`
770 // enable additional warnings:
771 '-Wdate-time',
772 '-Winit-self',
773 '-Winvalid-pch',
774 '-Wmultichar',
775 '-Wnested-externs',
776 '-Wnull-dereference',
777 '-Wpacked',
778 '-Wpointer-arith',
779 ]
780 if v.pref.os == .ios {
781 ccoptions.args << '-fobjc-arc'
782 }
783 if v.pref.os == .macos && os.exists('/opt/procursus') {
784 ccoptions.linker_flags << '-Wl,-rpath,/opt/procursus/lib'
785 }
786 mut user_darwin_version := 999_999
787 mut user_darwin_ppc := false
788 $if macos {
789 user_darwin_version = os.uname().release.split('.')[0].int()
790 if os.uname().machine == 'Power Macintosh' {
791 user_darwin_ppc = true
792 }
793
794 // Mac OS 10.4 and older requires Macports legacy software to build programs
795 if user_darwin_version <= 8 {
796 ccoptions.args << '-I' + @VEXEROOT + '/thirdparty/legacy/include/LegacySupport/'
797 ccoptions.args << @VEXEROOT + '/thirdparty/legacy/lib/libMacportsLegacySupport.a'
798 }
799 }
800 ccoptions.debug_mode = v.pref.is_debug
801 ccoptions.guessed_compiler = v.pref.ccompiler
802 v.pref.ccompiler_type = resolve_ccompiler_type(ccompiler, v.pref.ccompiler_type)
803 cc_file_name := os.file_name(ccompiler).to_lower_ascii()
804 ccoptions.cc = if cc_file_name.contains('icc') || ccoptions.guessed_compiler == 'icc' {
805 .icc
806 } else {
807 cc_from_pref_ccompiler_type(v.pref.ccompiler_type)
808 }
809 if ccoptions.cc == .unknown {
810 eprintln('Compilation with unknown C compiler `${cc_file_name}`')
811 }
812 if v.pref.os == .macos && ccoptions.cc != .tcc {
813 // tcc does not understand -arch; it only targets the host arch.
814 darwin_target_arch := darwin_target_arch_name(v.pref.arch)
815 if darwin_target_arch != '' {
816 ccoptions.args << ['-arch', darwin_target_arch]
817 }
818 }
819
820 // Add -fwrapv to handle UB overflows
821 if ccoptions.cc in [.gcc, .clang, .tcc]
822 && v.pref.os in [.macos, .linux, .openbsd, .freebsd, .windows] {
823 ccoptions.args << '-fwrapv'
824 }
825
826 // For C++ we must be very tolerant
827 if ccoptions.guessed_compiler.contains('++') {
828 ccoptions.args << '-fpermissive'
829 ccoptions.args << '-w'
830 }
831 if ccoptions.cc == .clang {
832 if ccoptions.debug_mode {
833 debug_options = ['-g', '-O0']
834 }
835 optimization_options = ['-O3']
836 mut have_flto := true
837 $if windows {
838 have_flto = false
839 }
840 if v.pref.parallel_cc {
841 have_flto = false
842 }
843 if v.pref.is_shared || v.disable_flto {
844 // Keep shared libraries away from LTO to avoid runtime loader regressions.
845 have_flto = false
846 }
847 if have_flto {
848 optimization_options << '-flto'
849 }
850 ccoptions.wargs << [
851 '-Wno-tautological-bitwise-compare',
852 '-Wno-enum-conversion', // used in vlib/sokol, where C enums in C structs are typed as V structs instead
853 '-Wno-sometimes-uninitialized', // produced after exhaustive matches
854 '-Wno-int-to-void-pointer-cast',
855 '-Wno-excess-initializers', // vlib/v/tests/struct_init_with_complex_fields_test.v fails without that on macos clang 13
856 '-Wno-unknown-warning', // if a C compiler does not understand a certain flag, it should just ignore it
857 '-Wno-unknown-warning-option', // clang equivalent of the above
858 ]
859 // Apple clang >= 17 treats -Wincompatible-function-pointer-types as an error by default.
860 // V generates code with enum types (e.g. os.Signal) in callbacks where C expects int,
861 // and specific struct* returns where C expects void* (e.g. sync.pool.ThreadCB).
862 ccoptions.args << '-Wno-incompatible-function-pointer-types'
863 ccoptions.args << '-Wno-typedef-redefinition' // V re-typedefs bool after includes to undo stdbool.h
864 }
865 if ccoptions.cc == .gcc {
866 if ccoptions.debug_mode {
867 debug_options = ['-g']
868 if user_darwin_version > 9 {
869 debug_options << '-no-pie'
870 }
871 }
872 optimization_options = ['-O3']
873 mut have_flto := true
874 if v.pref.parallel_cc {
875 have_flto = false
876 }
877 if v.pref.is_shared || v.disable_flto {
878 // Keep shared libraries away from LTO to avoid runtime loader regressions.
879 have_flto = false
880 }
881 if have_flto {
882 optimization_options << '-flto'
883 }
884 // gcc versions newer than 10.2, produce buggy programs, usually triggered by optimising inlined small functions, when both -flto and -O3 are used.
885 // Using -fno-strict-aliasing prevents that. See https://github.com/vlang/v/issues/26512 .
886 optimization_options << '-fno-strict-aliasing'
887 ccoptions.wargs << [
888 '-Wduplicated-branches',
889 '-Wduplicated-cond',
890 '-Wjump-misses-init',
891 '-Wlogical-op',
892 '-Wno-incompatible-pointer-types', // V uses enum types (e.g. os.Signal) in callbacks where C expects int
893 '-Wno-missing-field-initializers', // @[typedef] C structs may have fields not present in V binding
894 ]
895 // On macOS, `gcc` is actually Apple clang, which splits -Wincompatible-pointer-types
896 // and -Wincompatible-function-pointer-types into separate warnings.
897 ccoptions.args << '-Wno-incompatible-function-pointer-types'
898 }
899 if ccoptions.cc == .icc {
900 if ccoptions.debug_mode {
901 debug_options = ['-g']
902 if user_darwin_version > 9 {
903 debug_options << '-no-pie'
904 }
905 }
906 optimization_options = ['-Ofast']
907 }
908
909 if ccoptions.debug_mode {
910 ccoptions.args << debug_options
911 }
912 if v.pref.is_prod {
913 // don't warn for vlib tests
914 if ccoptions.cc == .tcc && !(v.parsed_files.len > 0
915 && v.parsed_files.last().path.contains('vlib')) {
916 if !v.pref.is_quiet {
917 eprintln('Note: tcc is not recommended for -prod builds')
918 }
919 }
920 if !v.pref.no_prod_options {
921 ccoptions.args << optimization_options
922 }
923 }
924 if v.pref.is_prod && !ccoptions.debug_mode {
925 // sokol and other C libraries that use asserts
926 // have much better performance when NDEBUG is defined
927 // See also http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf
928 ccoptions.args << '-DNDEBUG'
929 ccoptions.args << '-DNO_DEBUGGING' // for BDWGC
930 }
931 if v.pref.sanitize {
932 ccoptions.args << '-fsanitize=leak'
933 }
934 if v.pref.is_o {
935 ccoptions.args << '-c'
936 }
937
938 ccoptions.shared_postfix = '.so'
939 if v.pref.os == .macos {
940 ccoptions.shared_postfix = '.dylib'
941 }
942 if v.pref.os == .windows {
943 ccoptions.shared_postfix = '.dll'
944 }
945 if v.pref.is_shared {
946 ccoptions.linker_flags << '-shared'
947 $if !windows {
948 ccoptions.args << '-fPIC' // -Wl,-z,defs'
949 }
950 // Default to hidden symbol visibility for shared libraries, so only
951 // functions/globals tagged with `@[export: '…']` (which emit `VV_EXP`
952 // = `__attribute__((visibility("default")))`) end up in the ABI.
953 // Without this, every C symbol from the V runtime + stdlib is exported.
954 // Windows uses `__declspec(dllexport)` and the linker only exports
955 // tagged symbols, so the flag is unnecessary there.
956 // `-sharedlive` is skipped: live reload resolves `impl_live_*` symbols
957 // via `dlsym` from the host process, and those are emitted without
958 // `VV_EXP` on non-Windows (see vlib/v/gen/c/fn.v).
959 if !v.pref.is_liveshared && v.pref.os !in [.windows, .wasm32] && ccoptions.cc != .msvc {
960 ccoptions.args << '-fvisibility=hidden'
961 }
962 if v.pref.os == .linux && 'gcboehm' in v.pref.compile_defines_all {
963 // Keep shared-library GC symbols bound to the shared object itself.
964 // This avoids cross-DSO symbol interposition between multiple V binaries
965 // in one process (for example, host executable + loaded V plugin).
966 ccoptions.linker_flags << '-Wl,-Bsymbolic'
967 if ccoptions.cc != .tcc {
968 // Do not leak symbols from the statically linked libgc archive into
969 // the shared library ABI. Only functions explicitly tagged with
970 // @[export] should be visible.
971 ccoptions.linker_flags << '-Wl,--exclude-libs,ALL'
972 }
973 }
974 }
975 if v.pref.is_bare && v.pref.os != .wasm32 {
976 ccoptions.args << '-fno-stack-protector'
977 ccoptions.args << '-ffreestanding'
978 ccoptions.linker_flags << '-static'
979 ccoptions.linker_flags << '-nostdlib'
980 } else if v.pref.os == .wasm32 {
981 ccoptions.args << '--no-standard-libraries'
982 ccoptions.args << '-target wasm32-unknown-unknown'
983 ccoptions.args << '-static'
984 ccoptions.args << '-nostdlib'
985 ccoptions.args << '-ffreestanding'
986 ccoptions.args << '-Wl,--export-all'
987 ccoptions.args << '-Wl,--no-entry'
988 }
989 if ccoptions.debug_mode && current_os != 'windows' && v.pref.build_mode != .build_module {
990 if ccoptions.cc != .tcc && current_os == 'macos' {
991 ccoptions.linker_flags << '-Wl,-export_dynamic' // clang for mac needs export_dynamic instead of -rdynamic
992 } else {
993 if v.pref.ccompiler != 'x86_64-w64-mingw32-gcc' {
994 // the mingw-w64-gcc cross compiler does not support -rdynamic, and windows/wine already does have nicer backtraces
995 ccoptions.linker_flags << '-rdynamic' // needed for nicer symbolic backtraces
996 }
997 }
998 }
999 if v.pref.os == .freebsd {
1000 // Needed for -usecache on FreeBSD 13, otherwise we get `ld: error: duplicate symbol: _const_math__bits__de_bruijn32` errors there
1001 if ccoptions.cc != .tcc {
1002 ccoptions.linker_flags << '-Wl,--allow-multiple-definition'
1003 } else {
1004 // tcc needs this, otherwise it fails to compile the runetype.h system header with:
1005 // /usr/include/runetype.h:94: error: ';' expected (got "const")
1006 ccoptions.args << '-D__RUNETYPE_INTERNAL'
1007 }
1008 }
1009
1010 // Fix 'braces around scalar initializer' errors
1011 // on OpenBSD with clang for cstrict mode
1012 if v.pref.os == .openbsd && ccoptions.cc == .clang {
1013 ccoptions.wargs << '-Wno-braced-scalar-init'
1014 }
1015
1016 if ccompiler != 'msvc' && v.pref.os != .freebsd {
1017 ccoptions.wargs << '-Werror=implicit-function-declaration'
1018 }
1019 if ccoptions.cc == .tcc {
1020 // tcc 806b3f98 needs this flag too:
1021 ccoptions.wargs << '-Wno-write-strings'
1022 }
1023 if v.pref.is_liveshared || v.pref.is_livemain {
1024 if v.pref.os in [.linux, .android, .termux] && v.pref.build_mode != .build_module {
1025 // The live reload shared library resolves symbols from the host executable.
1026 // Termux/Android need the same export behavior as Linux, otherwise plain
1027 // `-live` can crash while `-cg -live` happens to work via debug linker flags.
1028 ccoptions.linker_flags << '-rdynamic'
1029 }
1030 if v.pref.os == .macos {
1031 ccoptions.args << '-flat_namespace'
1032 if v.pref.is_liveshared {
1033 // Resolve sapp_* and similar host symbols when the live-reload dylib is loaded.
1034 ccoptions.args << '-undefined'
1035 ccoptions.args << 'dynamic_lookup'
1036 }
1037 }
1038 if v.pref.os == .windows && ccoptions.cc !in [.msvc, .tcc] {
1039 // tcc on Windows lacks `--out-implib` and `--export-all-symbols` support, so
1040 // hot-reload examples skip the host-symbol export plumbing under tcc.
1041 host_import_lib := v.tcc_quoted_path(live_windows_import_lib_path(v.pref.path))
1042 if v.pref.is_livemain {
1043 // Re-export host graphics/backend symbols so the live-reload DLL can reuse them.
1044 ccoptions.linker_flags << '-Wl,--export-all-symbols'
1045 ccoptions.linker_flags << '-Wl,--out-implib,${host_import_lib}'
1046 }
1047 if v.pref.is_liveshared {
1048 // Link the live-reload DLL against the host executable's import library.
1049 ccoptions.linker_flags << host_import_lib
1050 }
1051 }
1052 }
1053
1054 // macOS code can include objective C TODO remove once objective C is replaced with C
1055 if v.pref.os in [.macos, .ios] {
1056 if ccoptions.cc != .tcc && !user_darwin_ppc && !v.pref.is_bare && ccompiler != 'musl-gcc' {
1057 ccoptions.source_args << '-x objective-c'
1058 }
1059 }
1060 // Newer Windows runner images can surface short paths with an uppercase `.C` suffix,
1061 // which makes GCC/Clang compile the generated V C file as C++ unless we force C mode.
1062 force_generated_c_language := v.pref.os == .windows && !v.pref.parallel_cc
1063 && ccoptions.cc in [.gcc, .clang, .emcc]
1064 if force_generated_c_language {
1065 ccoptions.source_args << '-x c'
1066 }
1067 // The C file we are compiling
1068 if !v.pref.parallel_cc { // parallel_cc uses its own split up c files
1069 ccoptions.source_args << v.tcc_quoted_path(v.out_name_c)
1070 }
1071 // Min macos version is mandatory I think?
1072 if v.pref.os == .macos {
1073 if v.pref.macosx_version_min != '0' {
1074 ccoptions.post_args << '-mmacosx-version-min=${v.pref.macosx_version_min}'
1075 }
1076 }
1077 if v.pref.os == .ios {
1078 if v.pref.is_ios_simulator {
1079 ccoptions.post_args << '-miphonesimulator-version-min=10.0'
1080 } else {
1081 ccoptions.post_args << '-miphoneos-version-min=10.0'
1082 }
1083 }
1084 if v.pref.os == .windows {
1085 subsystem_flag := v.get_subsystem_flag()
1086 if subsystem_flag != '' {
1087 ccoptions.post_args << subsystem_flag
1088 }
1089 }
1090 ccoptions.env_cflags = os.getenv('CFLAGS').replace('\n', ' ')
1091 ccoptions.env_ldflags = os.getenv('LDFLAGS').replace('\n', ' ')
1092 // Set the cache salt before resolving cached thirdparty object paths,
1093 // so object building and final compilation agree on the same cache entry.
1094 v.pref.cache_manager.set_temporary_options(v.thirdparty_object_args(ccoptions, [
1095 ccoptions.guessed_compiler,
1096 ], false))
1097 cflags := v.get_os_cflags()
1098
1099 if v.pref.build_mode != .build_module && !v.pref.is_o {
1100 only_o_files := cflags.c_options_only_object_files()
1101 ccoptions.o_args << only_o_files
1102 }
1103
1104 defines, others, libs := cflags.defines_others_libs()
1105 ccoptions.pre_args << defines
1106 ccoptions.pre_args << others
1107 ccoptions.linker_flags << libs
1108 v.fixup_tcc_macos_comma_path_flags(mut ccoptions)
1109 if v.pref.use_cache && v.pref.build_mode != .build_module {
1110 if ccoptions.cc != .tcc {
1111 $if linux {
1112 ccoptions.linker_flags << '-Xlinker -z'
1113 ccoptions.linker_flags << '-Xlinker muldefs'
1114 }
1115 }
1116 }
1117 if ccoptions.cc == .tcc && 'no_backtrace' !in v.pref.compile_defines {
1118 ccoptions.post_args << '-bt25'
1119 }
1120 // Without these libs compilation will fail on Linux
1121 if !v.pref.is_bare && v.pref.build_mode != .build_module
1122 && v.pref.os in [.linux, .freebsd, .openbsd, .netbsd, .dragonfly, .solaris, .haiku] {
1123 if v.pref.os in [.freebsd, .netbsd] {
1124 // Free/NetBSD: backtrace needs execinfo library while linking, also execinfo depends on elf.
1125 ccoptions.linker_flags << '-lexecinfo'
1126 ccoptions.linker_flags << '-lelf'
1127 }
1128 }
1129 if v.pref.os == .macos {
1130 if v.pref.use_cache {
1131 ccoptions.source_args << '-x none'
1132 } else {
1133 for flag in ccoptions.linker_flags {
1134 if flag.starts_with('-') {
1135 continue
1136 }
1137 if os.is_file(flag) {
1138 ccoptions.source_args << '-x none'
1139 break
1140 }
1141 path := if flag.starts_with('"') && flag.ends_with('"') {
1142 flag[1..flag.len - 1]
1143 } else {
1144 flag
1145 }
1146 if os.is_dir(os.dir(path)) {
1147 ccoptions.source_args << '-x none'
1148 break
1149 }
1150 }
1151 }
1152 }
1153 if !v.pref.no_std {
1154 ccoptions.source_args << ['-std=${c_std}', '-D_DEFAULT_SOURCE']
1155 }
1156 $if trace_ccoptions ? {
1157 println('>>> setup_ccompiler_options ccompiler: ${ccompiler}')
1158 println('>>> setup_ccompiler_options ccoptions: ${ccoptions}')
1159 }
1160 v.ccoptions = ccoptions
1161}
1162
1163fn (v &Builder) all_args(ccoptions CcompilerOptions) []string {
1164 mut all := []string{}
1165 all << v.only_compile_args(ccoptions)
1166 all << v.only_linker_args(ccoptions)
1167 return all
1168}
1169
1170pub fn (v &Builder) get_compile_args() []string {
1171 return v.only_compile_args(v.ccoptions)
1172}
1173
1174fn (v &Builder) only_compile_args(ccoptions CcompilerOptions) []string {
1175 mut all := []string{}
1176 all << ccoptions.env_cflags
1177 if v.pref.is_cstrict {
1178 all << ccoptions.wargs
1179 }
1180 all << ccoptions.args
1181 all << ccoptions.o_args
1182 $if windows {
1183 // Adding default options for tcc, gcc and clang as done in msvc.v.
1184 // This is done before pre_args is added so that it can be overwritten if needed.
1185 // -Wl,-stack=33554432 == /F33554432
1186 // -Werror=implicit-function-declaration == /we4013
1187 // /volatile:ms - there seems to be no equivalent,
1188 // normally msvc should use /volatile:iso
1189 // but it could have an impact on vinix if it is created with msvc.
1190 if ccoptions.cc != .msvc {
1191 if v.pref.os != .wasm32_emscripten {
1192 all << '-Wl,-stack=33554432'
1193 }
1194 if !v.pref.is_cstrict {
1195 all << '-Werror=implicit-function-declaration'
1196 }
1197 }
1198 }
1199 all << ccoptions.pre_args
1200 all << ccoptions.source_args
1201 all << ccoptions.post_args
1202 return all
1203}
1204
1205pub fn (v &Builder) get_linker_args() []string {
1206 return v.only_linker_args(v.ccoptions)
1207}
1208
1209fn (v &Builder) only_linker_args(ccoptions CcompilerOptions) []string {
1210 mut all := []string{}
1211 // in `build-mode` or when producing a .o file, we do not need -lxyz flags,
1212 // since we are building an (.o) object file, that will be linked later.
1213 if v.pref.build_mode != .build_module && !v.pref.is_o {
1214 all << ccoptions.linker_flags
1215 all << ccoptions.env_ldflags
1216 all << ccoptions.ldflags
1217 }
1218 return all
1219}
1220
1221struct ThirdpartyCrossCompileConfig {
1222 target_args []string
1223 trailing_include_args []string
1224 sysroot string
1225}
1226
1227fn (v &Builder) thirdparty_cross_compile_config() ThirdpartyCrossCompileConfig {
1228 if v.pref.os == .linux && current_os != 'linux' {
1229 sysroot := os.join_path(os.vmodules_dir(), 'linuxroot')
1230 return ThirdpartyCrossCompileConfig{
1231 target_args: ['-target x86_64-linux-gnu']
1232 trailing_include_args: [
1233 '-I',
1234 os.quoted_path('${sysroot}/include'),
1235 ]
1236 sysroot: sysroot
1237 }
1238 }
1239 if v.pref.os == .freebsd && current_os != 'freebsd' {
1240 sysroot := os.join_path(os.vmodules_dir(), 'freebsdroot')
1241 return ThirdpartyCrossCompileConfig{
1242 target_args: ['-target x86_64-unknown-freebsd14.0']
1243 trailing_include_args: [
1244 '-I',
1245 os.quoted_path('${sysroot}/include'),
1246 '-I',
1247 os.quoted_path('${sysroot}/usr/include'),
1248 ]
1249 sysroot: sysroot
1250 }
1251 }
1252 return ThirdpartyCrossCompileConfig{}
1253}
1254
1255fn (mut v Builder) ensure_thirdparty_cross_compile_sysroot(cfg ThirdpartyCrossCompileConfig) {
1256 if cfg.sysroot == '' {
1257 return
1258 }
1259 if v.pref.os == .linux {
1260 v.ensure_linuxroot_exists(cfg.sysroot)
1261 return
1262 }
1263 if v.pref.os == .freebsd {
1264 v.ensure_freebsdroot_exists(cfg.sysroot)
1265 }
1266}
1267
1268fn (mut v Builder) thirdparty_object_args(ccoptions CcompilerOptions, middle []string, cpp_file bool) []string {
1269 mut all := []string{}
1270
1271 if !v.pref.no_std {
1272 if cpp_file {
1273 all << '-std=${cpp_std}'
1274 } else {
1275 all << '-std=${c_std}'
1276 }
1277 all << '-D_DEFAULT_SOURCE'
1278 }
1279
1280 cross_cfg := v.thirdparty_cross_compile_config()
1281 if cross_cfg.sysroot != '' {
1282 v.ensure_thirdparty_cross_compile_sysroot(cross_cfg)
1283 all << cross_cfg.target_args
1284 }
1285
1286 all << ccoptions.env_cflags
1287 all << ccoptions.args
1288 all << middle
1289 // NOTE do not append linker flags in .o build process,
1290 // compilers are inconsistent about how they handle:
1291 // all << ccoptions.env_ldflags
1292 // all << ccoptions.ldflags
1293 if cross_cfg.sysroot != '' {
1294 // add the system include/ folder after everything else,
1295 // so that local folders like thirdparty/mbedtls have a
1296 // chance to supply their own headers
1297 all << cross_cfg.trailing_include_args
1298 }
1299 return all
1300}
1301
1302fn (mut v Builder) setup_output_name() {
1303 if !v.pref.is_shared && v.pref.build_mode != .build_module && v.pref.os == .windows
1304 && !v.pref.is_o && !v.pref.out_name.ends_with('.exe') {
1305 v.pref.out_name += '.exe'
1306 }
1307 // Output executable name
1308 v.log('cc() isprod=${v.pref.is_prod} outname=${v.pref.out_name}')
1309 if v.pref.is_shared {
1310 if !v.pref.out_name.ends_with(v.ccoptions.shared_postfix) {
1311 v.pref.out_name += v.ccoptions.shared_postfix
1312 }
1313 }
1314 if v.pref.build_mode == .build_module {
1315 v.pref.out_name = v.pref.cache_manager.mod_postfix_with_key2cpath(v.pref.path, '.o',
1316 v.pref.path) // v.out_name
1317 if v.pref.is_verbose {
1318 println('Building ${v.pref.path} to ${v.pref.out_name} ...')
1319 }
1320 v.pref.cache_manager.mod_save(v.pref.path, '.output.description.txt', v.pref.path,
1321 get_dsc_content('PREF.PATH: ${v.pref.path}\nVOPTS: ${v.pref.cache_manager.vopts}\n')) or {
1322 panic(err)
1323 }
1324 }
1325 if os.is_dir(v.pref.out_name) {
1326 verror('${os.quoted_path(v.pref.out_name)} is a directory')
1327 }
1328 if !v.pref.parallel_cc {
1329 // parallel_cc sets its own `-o out_n.o`
1330 v.ccoptions.o_args << '-o ${v.tcc_quoted_path(v.pref.out_name)}'
1331 }
1332}
1333
1334pub fn (mut v Builder) tcc_quoted_path(p string) string {
1335 wp := v.tcc_windows_path(p)
1336 if v.ccoptions.cc == .tcc && !v.pref.no_rsp {
1337 // tcc has a bug that prevents it from parsing names quoted with `'` in .rsp files,
1338 // so force double-quoted, backslash-escaped paths for tcc rsp mode.
1339 mut escaped := wp.replace('\\', '\\\\')
1340 escaped = escaped.replace('"', '\\"')
1341 return '"${escaped}"'
1342 }
1343 return os.quoted_path(wp)
1344}
1345
1346fn looks_like_windows_path(value string) bool {
1347 return value.contains('\\') || value.contains('/') || (value.len > 1 && value[1] == `:`)
1348}
1349
1350fn rewrite_windows_path_arg(arg string, resolver WindowsPathResolver) string {
1351 if arg == '' {
1352 return ''
1353 }
1354 if start := arg.index('"') {
1355 end := arg.last_index('"') or { -1 }
1356 if end > start {
1357 path := arg[start + 1..end]
1358 if looks_like_windows_path(path) {
1359 return arg[..start] + '"${resolver(path)}"' + arg[end + 1..]
1360 }
1361 }
1362 }
1363 for prefix in ['-I', '-L', '-B', '-o ', '-c '] {
1364 if arg.starts_with(prefix) {
1365 path := arg[prefix.len..].trim_space().trim('"')
1366 if looks_like_windows_path(path) {
1367 return prefix + '"${resolver(path)}"'
1368 }
1369 }
1370 }
1371 trimmed := arg.trim_space().trim('"')
1372 if !arg.starts_with('-') && looks_like_windows_path(trimmed) {
1373 return '"${resolver(trimmed)}"'
1374 }
1375 return arg
1376}
1377
1378fn short_windows_path(path string) string {
1379 $if windows {
1380 return os.short_path(path)
1381 }
1382 return path
1383}
1384
1385fn (v &Builder) tcc_windows_path(p string) string {
1386 $if windows {
1387 if v.ccoptions.cc == .tcc {
1388 return short_windows_path(p)
1389 }
1390 }
1391 return p
1392}
1393
1394fn (v &Builder) tcc_windows_path_arg(arg string) string {
1395 $if windows {
1396 if v.ccoptions.cc == .tcc {
1397 return rewrite_windows_path_arg(arg, short_windows_path)
1398 }
1399 }
1400 return arg
1401}
1402
1403fn (v &Builder) rsp_safe_arg(arg string) string {
1404 if arg.starts_with('-B') && arg.len > 2 {
1405 path := arg[2..]
1406 if path.contains(' ') && !path.starts_with('"') {
1407 return '-B"${path}"'
1408 }
1409 }
1410 return arg
1411}
1412
1413fn (v &Builder) should_use_rsp(rsp_args []string) bool {
1414 if v.pref.no_rsp || v.pref.os == .termux {
1415 return false
1416 }
1417 for arg in rsp_args {
1418 if arg.contains("'\\''") || arg.contains('\n') || arg.contains('\r') {
1419 return false
1420 }
1421 }
1422 return true
1423}
1424
1425fn (v &Builder) msvc_should_use_rsp(args []string) bool {
1426 if !v.should_use_rsp(args) {
1427 return false
1428 }
1429 // Keep Unicode paths on the direct CreateProcessW command line. MSVC response
1430 // files still mis-handle non-ASCII file names on some Windows setups.
1431 for arg in args {
1432 if !arg.is_ascii() {
1433 return false
1434 }
1435 }
1436 return true
1437}
1438
1439fn (v &Builder) c_project_source_name() string {
1440 mut output_name := os.file_name(v.pref.out_name)
1441 if output_name == '' {
1442 output_name = 'main'
1443 }
1444 base_name := output_name.all_before_last('.')
1445 return if base_name == '' { '${output_name}.c' } else { '${base_name}.c' }
1446}
1447
1448fn (mut v Builder) c_project_output_name() string {
1449 mut output_name := os.file_name(v.pref.out_name)
1450 if output_name == '' {
1451 output_name = 'main'
1452 }
1453 if output_name.ends_with('.c') {
1454 output_name = output_name.trim_string_right('.c')
1455 }
1456 if output_name == '' {
1457 output_name = 'main'
1458 }
1459 if !v.pref.is_shared && v.pref.build_mode != .build_module && v.pref.os == .windows
1460 && !v.pref.is_o && !output_name.ends_with('.exe') {
1461 output_name += '.exe'
1462 }
1463 if v.pref.is_shared && !output_name.ends_with(v.ccoptions.shared_postfix) {
1464 output_name += v.ccoptions.shared_postfix
1465 }
1466 return output_name
1467}
1468
1469fn (mut v Builder) c_project_dependency_replacements() map[string]string {
1470 mut replacements := map[string]string{}
1471 for flag in v.get_os_cflags() {
1472 if !flag.value.ends_with('.o') && !flag.value.ends_with('.obj') {
1473 continue
1474 }
1475 cached_value := if flag.cached == '' { os.real_path(flag.value) } else { flag.cached }
1476 obj_path := os.real_path(flag.value)
1477 replacement_value := if source_path := c_project_source_from_object_path(obj_path) {
1478 os.quoted_path(source_path)
1479 } else if os.exists(obj_path) {
1480 os.quoted_path(obj_path)
1481 } else {
1482 os.quoted_path(cached_value)
1483 }
1484 for key in [
1485 cached_value,
1486 os.quoted_path(cached_value),
1487 '"${cached_value}"',
1488 flag.format() or { '' },
1489 ] {
1490 if key == '' {
1491 continue
1492 }
1493 replacements[key] = replacement_value
1494 }
1495 }
1496 return replacements
1497}
1498
1499fn (mut v Builder) generate_c_project() {
1500 if v.pref.backend != .c {
1501 verror('`-generate-c-project` is currently supported only for the C backend.')
1502 }
1503 mut project_dir := v.pref.generate_c_project
1504 if !os.is_abs_path(project_dir) {
1505 project_dir = os.real_path(project_dir)
1506 }
1507 if os.exists(project_dir) && !os.is_dir(project_dir) {
1508 verror('`-generate-c-project` expects a directory path, got file: ${os.quoted_path(project_dir)}')
1509 }
1510 os.mkdir_all(project_dir) or {
1511 verror('Cannot create `-generate-c-project` directory ${os.quoted_path(project_dir)}: ${err}')
1512 }
1513 c_source_path := os.join_path(project_dir, v.c_project_source_name())
1514 os.mv_by_cp(v.out_name_c, c_source_path) or {
1515 verror('Cannot write generated C source to ${os.quoted_path(c_source_path)}: ${err}')
1516 }
1517
1518 mut ccompiler := v.pref.ccompiler
1519 if v.pref.os == .wasm32 {
1520 ccompiler = 'clang'
1521 }
1522 v.setup_ccompiler_options(ccompiler)
1523 if v.pref.build_mode == .build_module {
1524 v.ccoptions.pre_args << '-c'
1525 }
1526 mut project_o_args := v.ccoptions.o_args.filter(!it.starts_with('-o '))
1527 project_o_args << [
1528 '-o ${v.tcc_quoted_path(os.join_path(project_dir, v.c_project_output_name()))}',
1529 ]
1530 v.ccoptions.o_args = project_o_args
1531 for idx, source_arg in v.ccoptions.source_args {
1532 if source_arg.contains(v.out_name_c) || source_arg.ends_with('.tmp.c')
1533 || source_arg.contains(".tmp.c'") || source_arg.contains('.tmp.c"') {
1534 v.ccoptions.source_args[idx] = v.tcc_quoted_path(c_source_path)
1535 }
1536 }
1537
1538 mut all_args := v.all_args(v.ccoptions)
1539 replacements := v.c_project_dependency_replacements()
1540 for idx, arg in all_args {
1541 if replacement := replacements[arg] {
1542 all_args[idx] = replacement
1543 }
1544 }
1545 v.dump_c_options(all_args)
1546 cc_cmd := '${v.quote_compiler_name(ccompiler)} ${all_args.join(' ')}'
1547 os.write_file(os.join_path(project_dir, 'build_command.txt'), cc_cmd + '\n') or {
1548 verror('Cannot write ${os.quoted_path(os.join_path(project_dir, 'build_command.txt'))}: ${err}')
1549 }
1550 os.write_file(os.join_path(project_dir, 'Makefile'), 'all:\n\t${cc_cmd}\n') or {
1551 verror('Cannot write ${os.quoted_path(os.join_path(project_dir, 'Makefile'))}: ${err}')
1552 }
1553 os.write_file(os.join_path(project_dir, 'build.sh'), '#!/bin/sh\nset -eu\n${cc_cmd}\n') or {
1554 verror('Cannot write ${os.quoted_path(os.join_path(project_dir, 'build.sh'))}: ${err}')
1555 }
1556 os.write_file(os.join_path(project_dir, 'build.bat'), '@echo off\r\n${cc_cmd}\r\n') or {
1557 verror('Cannot write ${os.quoted_path(os.join_path(project_dir, 'build.bat'))}: ${err}')
1558 }
1559 $if !windows {
1560 os.chmod(os.join_path(project_dir, 'build.sh'), 0o755) or {}
1561 }
1562 println('Generated C project in ${os.quoted_path(project_dir)}')
1563}
1564
1565pub fn (mut v Builder) cc() {
1566 if os.executable().contains('vfmt') {
1567 return
1568 }
1569 if v.pref.is_verbose {
1570 println('builder.cc() pref.out_name=${os.quoted_path(v.pref.out_name)}')
1571 }
1572 if v.pref.only_check_syntax {
1573 if v.pref.is_verbose {
1574 println('builder.cc returning early, since pref.only_check_syntax is true')
1575 }
1576 return
1577 }
1578 if v.pref.check_only {
1579 if v.pref.is_verbose {
1580 println('builder.cc returning early, since pref.check_only is true')
1581 }
1582 return
1583 }
1584 v.ensure_windows_icon_flag_is_valid()
1585 if v.pref.should_output_to_stdout() {
1586 // output to stdout
1587 content := os.read_file(v.out_name_c) or { panic(err) }
1588 println(content)
1589 os.rm(v.out_name_c) or {}
1590 return
1591 }
1592 if v.pref.generate_c_project != '' {
1593 v.pref.skip_running = true
1594 v.generate_c_project()
1595 return
1596 }
1597 // whether to just create a .c or .js file and exit, for example: `v -o v.c cmd.v`
1598 ends_with_c := v.pref.out_name.ends_with('.c')
1599 ends_with_js := v.pref.out_name.ends_with('.js')
1600 if ends_with_c || (ends_with_js && v.pref.os != .wasm32_emscripten) {
1601 v.pref.skip_running = true
1602 msg_mv := 'os.mv_by_cp ${os.quoted_path(v.out_name_c)} => ${os.quoted_path(v.pref.out_name)}'
1603 util.timing_start(msg_mv)
1604 // v.out_name_c may be on a different partition than v.out_name
1605 os.mv_by_cp(v.out_name_c, v.pref.out_name) or { panic(err) }
1606 util.timing_measure(msg_mv)
1607 return
1608 }
1609 v.ensure_imported_coroutines_runtime() or { verror(err.msg()) }
1610 // Cross compiling for Windows
1611 if v.pref.os == .windows && v.pref.ccompiler != 'msvc' {
1612 $if !windows {
1613 v.cc_windows_cross()
1614 return
1615 }
1616 }
1617 // Cross compiling for Linux
1618 if v.pref.os == .linux {
1619 $if !linux {
1620 v.cc_linux_cross()
1621 return
1622 }
1623 }
1624 // Cross compiling for FreeBSD
1625 if v.pref.os == .freebsd {
1626 $if !freebsd {
1627 v.cc_freebsd_cross()
1628 return
1629 }
1630 }
1631
1632 vexe := pref.vexe_path()
1633 vdir := os.dir(vexe)
1634 mut tried_compilation_commands := []string{}
1635 mut tcc_output := os.Result{}
1636 original_pwd := os.getwd()
1637 for {
1638 // try to compile with the chosen compiler
1639 // if compilation fails, retry again with another
1640 mut ccompiler := v.pref.ccompiler
1641 if v.pref.os == .wasm32 {
1642 ccompiler = 'clang'
1643 }
1644 v.setup_ccompiler_options(ccompiler)
1645 v.build_thirdparty_obj_files()
1646 v.setup_output_name()
1647
1648 if v.pref.os != .windows && ccompiler.contains('++') {
1649 cpp_atomic_h_path := '${@VEXEROOT}/thirdparty/stdatomic/nix/cpp/atomic.h'
1650 if !os.exists(cpp_atomic_h_path) {
1651 for file in v.parsed_files {
1652 if file.imports.any(it.mod.contains('sync')) {
1653 $if trace_stdatomic_gen ? {
1654 eprintln('> creating ${cpp_atomic_h_path} ...')
1655 }
1656 cppgenv := '${@VEXEROOT}/thirdparty/stdatomic/nix/cpp/gen.v'
1657 os.execute('${os.quoted_path(vexe)} run ${os.quoted_path(cppgenv)} ${os.quoted_path(ccompiler)}')
1658 break
1659 }
1660 }
1661 }
1662 }
1663 if v.pref.build_mode == .build_module {
1664 v.ccoptions.pre_args << '-c'
1665 }
1666 v.handle_usecache(vexe)
1667 $if windows {
1668 if v.ccoptions.cc == .msvc || v.pref.ccompiler_type == .msvc {
1669 v.cc_msvc()
1670 return
1671 }
1672 }
1673 //
1674 all_args := v.all_args(v.ccoptions)
1675 v.dump_c_options(all_args)
1676 mut rsp_args := all_args.map(v.rsp_safe_arg(it))
1677 rsp_args = rsp_args.map(v.tcc_windows_path_arg(it))
1678 shell_args := rsp_args.join(' ')
1679 mut should_use_rsp := v.should_use_rsp(rsp_args)
1680 mut str_args := if !should_use_rsp {
1681 shell_args.replace('\n', ' ')
1682 } else {
1683 shell_args
1684 }
1685 mut cmd := '${v.quote_compiler_name(ccompiler)} ${str_args}'
1686 if v.pref.parallel_cc {
1687 // In parallel cc mode, all we want in cc() is build the str_args.
1688 // Actual cc logic then happens in `parallel_cc()`
1689 v.str_args = str_args
1690 return
1691 }
1692 mut response_file := ''
1693 mut response_file_content := str_args
1694 if should_use_rsp {
1695 response_file = '${v.out_name_c}.rsp'
1696 response_file_content = str_args.replace('\\', '\\\\')
1697 write_response_file(response_file, response_file_content)
1698 rspexpr := '@${v.tcc_windows_path(response_file)}'
1699 cmd = '${v.quote_compiler_name(ccompiler)} ${os.quoted_path(rspexpr)}'
1700 if !v.ccoptions.debug_mode {
1701 v.pref.cleanup_files << response_file
1702 }
1703 }
1704 if !v.ccoptions.debug_mode {
1705 v.pref.cleanup_files << v.out_name_c
1706 }
1707 $if windows {
1708 if v.ccoptions.cc == .tcc {
1709 def_name := v.pref.out_name[0..v.pref.out_name.len - 4]
1710 v.pref.cleanup_files << '${def_name}.def'
1711 }
1712 }
1713 //
1714 os.chdir(vdir) or {}
1715 tried_compilation_commands << cmd
1716 v.last_cc_cmd = cmd
1717 v.show_cc(cmd, response_file, response_file_content)
1718 // Run
1719 ccompiler_label := 'C ${os.file_name(ccompiler):3}'
1720 util.timing_start(ccompiler_label)
1721 res := os.execute(cmd)
1722 util.timing_measure(ccompiler_label)
1723 if v.pref.show_c_output {
1724 v.show_c_compiler_output(ccompiler, res)
1725 }
1726 os.chdir(original_pwd) or {}
1727 vcache.dlog('| Builder.' + @FN,
1728 '> v.pref.use_cache: ${v.pref.use_cache} | v.pref.retry_compilation: ${v.pref.retry_compilation}')
1729 vcache.dlog('| Builder.' + @FN, '> cmd res.exit_code: ${res.exit_code} | cmd: ${cmd}')
1730 vcache.dlog('| Builder.' + @FN, '> response_file_content:\n${response_file_content}')
1731 if res.exit_code != 0 {
1732 // Some GCC+linker setups fail bootstrapping with `-flto` and then report a missing `main` symbol.
1733 // Retry once without `-flto`, while still keeping the remaining -prod options.
1734 if v.pref.building_v && v.pref.is_prod && !v.pref.no_prod_options && !v.disable_flto
1735 && v.ccoptions.cc == .gcc && response_file_content.contains('-flto')
1736 && (res.output.contains('undefined symbol: main')
1737 || res.output.contains('undefined reference to `main')) {
1738 v.disable_flto = true
1739 if !v.pref.is_quiet {
1740 eprintln('Retrying compiler build without `-flto` after a linker failure with missing `main`.')
1741 }
1742 continue
1743 }
1744 if is_tcc_compilation_failure(ccompiler, v.ccoptions.cc, res.output) {
1745 // A TCC problem? Retry with a non-tcc system compiler:
1746 if tried_compilation_commands.len > 1 {
1747 eprintln('Recompilation loop detected (ccompiler: ${ccompiler}):')
1748 for recompile_command in tried_compilation_commands {
1749 eprintln(' ${recompile_command}')
1750 }
1751 exit(101)
1752 }
1753 if v.pref.retry_compilation {
1754 tcc_output = res
1755 old_ccompiler := v.pref.ccompiler
1756 v.pref.default_c_compiler()
1757 if v.pref.ccompiler == ccompiler || is_tcc_compiler_name(v.pref.ccompiler)
1758 || is_tcc_alias_compiler(v.pref.ccompiler) {
1759 v.pref.ccompiler = first_available_ccompiler([old_ccompiler, ccompiler,
1760 v.pref.ccompiler])
1761 }
1762 if v.pref.ccompiler != '' && v.pref.ccompiler != ccompiler {
1763 if v.pref.is_verbose {
1764 eprintln('Compilation with tcc failed. Retrying with ${v.pref.ccompiler} ...')
1765 } else {
1766 $if macos {
1767 eprintln(term.red('warning: tcc compilation failed, falling back to ${v.pref.ccompiler} (this is much slower)'))
1768 }
1769 }
1770 continue
1771 }
1772 }
1773 }
1774 if res.exit_code == 127 {
1775 verror('C compiler error, while attempting to run: \n' +
1776 '-----------------------------------------------------------\n' + '${cmd}\n' +
1777 '-----------------------------------------------------------\n' +
1778 'Probably your C compiler is missing. \n' +
1779 'Please reinstall it, or make it available in your PATH.\n\n' +
1780 missing_compiler_info())
1781 }
1782 }
1783 // If tcc failed once, and the system C compiler has failed as well,
1784 // print the tcc error instead since it may contain more useful information.
1785 // Keep the uploaded bug report tied to the final compiler result.
1786 // See https://discord.com/channels/592103645835821068/592115457029308427/811956304314761228
1787 failure_output := c_compiler_failure_output(ccompiler, res, tcc_output)
1788 v.post_process_c_compiler_output_with_report(failure_output.display_ccompiler,
1789 failure_output.display_res, failure_output.report_ccompiler, failure_output.report_res)
1790 // Print the C command
1791 if v.pref.is_verbose {
1792 println('${ccompiler}')
1793 println('=========\n')
1794 }
1795 break
1796 }
1797 v.apply_windows_icon_to_executable() or { verror(err.msg()) }
1798 if v.pref.compress {
1799 ret := os.system('strip ${os.quoted_path(v.pref.out_name)}')
1800 if ret != 0 {
1801 println('strip failed')
1802 return
1803 }
1804 // Note: upx --lzma can sometimes fail with NotCompressibleException
1805 // See https://github.com/vlang/v/pull/3528
1806 mut ret2 := os.system('upx --lzma -qqq ${os.quoted_path(v.pref.out_name)}')
1807 if ret2 != 0 {
1808 ret2 = os.system('upx -qqq ${os.quoted_path(v.pref.out_name)}')
1809 }
1810 if ret2 != 0 {
1811 println('upx failed')
1812 $if macos {
1813 println('install upx with `brew install upx`')
1814 }
1815 $if linux {
1816 println('install upx\n' + 'for example, on Debian/Ubuntu run `sudo apt install upx`')
1817 }
1818 $if windows {
1819 println('install upx')
1820 }
1821 }
1822 }
1823 // if v.pref.os == .ios {
1824 // ret := os.system('ldid2 -S ${v.pref.out_name}')
1825 // if ret != 0 {
1826 // eprintln('failed to run ldid2, try: brew install ldid')
1827 // }
1828 // }
1829}
1830
1831fn (mut b Builder) ensure_linuxroot_exists(sysroot string) {
1832 crossrepo_url := 'https://github.com/vlang/linuxroot'
1833 sysroot_git_config_path := os.join_path(sysroot, '.git', 'config')
1834 if os.is_dir(sysroot) && !os.exists(sysroot_git_config_path) {
1835 // remove existing obsolete unarchived .zip file content
1836 os.rmdir_all(sysroot) or {}
1837 }
1838 if !os.is_dir(sysroot) {
1839 println('Downloading files for Linux cross compilation (~77MB) ...')
1840 os.system('git clone "${crossrepo_url}" ${os.quoted_path(sysroot)}')
1841 if !os.exists(sysroot_git_config_path) {
1842 verror('Failed to clone `${crossrepo_url}` to `${sysroot}`')
1843 }
1844 os.chmod(os.join_path(sysroot, 'ld.lld'), 0o755) or { panic(err) }
1845 }
1846 repaired := repair_cross_sysroot_git_symlink_placeholders(sysroot) or {
1847 verror('Failed to repair `${sysroot}` symlink placeholders: ${err}')
1848 return
1849 }
1850 if repaired > 0 {
1851 println('Materialized ${repaired} Git symlink placeholder files in ${os.quoted_path(sysroot)}.')
1852 }
1853}
1854
1855fn (mut b Builder) ensure_freebsdroot_exists(sysroot string) {
1856 crossrepo_url := 'https://github.com/spytheman/freebsd_base13.2'
1857 sysroot_git_config_path := os.join_path(sysroot, '.git', 'config')
1858 if os.is_dir(sysroot) && !os.exists(sysroot_git_config_path) {
1859 // remove existing obsolete unarchived .zip file content
1860 os.rmdir_all(sysroot) or {}
1861 }
1862 if !os.is_dir(sysroot) {
1863 println('Downloading files for FreeBSD cross compilation (~458MB) ...')
1864 os.system('git clone "${crossrepo_url}" ${os.quoted_path(sysroot)}')
1865 if !os.exists(sysroot_git_config_path) {
1866 verror('Failed to clone `${crossrepo_url}` to `${sysroot}`')
1867 }
1868 }
1869 repaired := repair_cross_sysroot_git_symlink_placeholders(sysroot) or {
1870 verror('Failed to repair `${sysroot}` symlink placeholders: ${err}')
1871 return
1872 }
1873 if repaired > 0 {
1874 println('Materialized ${repaired} Git symlink placeholder files in ${os.quoted_path(sysroot)}.')
1875 }
1876}
1877
1878fn git_repo_tracked_symlink_paths(repo string) ![]string {
1879 git_cmd := 'git -C ${os.quoted_path(repo)} ls-files -s'
1880 res := os.execute(git_cmd)
1881 if res.exit_code != 0 {
1882 return error('`${git_cmd}` failed: ${res.output.trim_space()}')
1883 }
1884 mut paths := []string{}
1885 for line in res.output.split_into_lines() {
1886 if !line.starts_with('120000 ') || line.index_u8(`\t`) == -1 {
1887 continue
1888 }
1889 paths << line.all_after('\t')
1890 }
1891 return paths
1892}
1893
1894fn normalize_git_symlink_target_path(path string, raw_target string) ?string {
1895 target := raw_target.trim_space()
1896 if target == '' || target.index_u8(`\n`) != -1 || target.index_u8(`\r`) != -1
1897 || target.index_u8(`\t`) != -1 {
1898 return none
1899 }
1900 resolved := if os.is_abs_path(target) {
1901 os.norm_path(target)
1902 } else {
1903 os.norm_path(os.join_path(os.dir(path), target))
1904 }
1905 if !os.exists(resolved) || os.is_dir(resolved) {
1906 return none
1907 }
1908 return resolved
1909}
1910
1911fn git_symlink_target_path(path string) ?string {
1912 if os.is_link(path) {
1913 raw_target := os.readlink(path) or { return none }
1914 return normalize_git_symlink_target_path(path, raw_target)
1915 }
1916 if !os.is_file(path) || os.file_size(path) > max_cross_sysroot_git_symlink_placeholder_size {
1917 return none
1918 }
1919 raw_target := os.read_file(path) or { return none }
1920 if raw_target.index_u8(0) != -1 {
1921 return none
1922 }
1923 return normalize_git_symlink_target_path(path, raw_target)
1924}
1925
1926fn git_symlink_materialization_source(path string) ?string {
1927 mut current := path
1928 mut seen := map[string]bool{}
1929 for _ in 0 .. max_cross_sysroot_git_symlink_depth {
1930 if current in seen {
1931 return none
1932 }
1933 seen[current] = true
1934 next := git_symlink_target_path(current) or {
1935 if current != path && os.is_file(current) {
1936 return current
1937 }
1938 return none
1939 }
1940 current = next
1941 }
1942 return none
1943}
1944
1945fn materialize_git_symlink_placeholder(path string, source string) ! {
1946 tmp_path := '${path}.v_symlink_fix_tmp'
1947 os.rm(tmp_path) or {}
1948 os.cp(source, tmp_path)!
1949 os.rm(path)!
1950 os.mv(tmp_path, path)!
1951}
1952
1953fn repair_cross_sysroot_git_symlink_placeholders_in_paths(paths []string, strict bool) !int {
1954 mut repaired := 0
1955 for path in paths {
1956 if os.is_link(path) || !os.is_file(path) {
1957 continue
1958 }
1959 source := git_symlink_materialization_source(path) or {
1960 if strict {
1961 return error('`${path}` is tracked as a symlink in the cross-compilation sysroot, but its target could not be resolved')
1962 }
1963 continue
1964 }
1965 if source == path {
1966 continue
1967 }
1968 materialize_git_symlink_placeholder(path, source)!
1969 repaired++
1970 }
1971 return repaired
1972}
1973
1974// Git on Windows can check symlinks out as tiny text files instead of real links.
1975fn repair_cross_sysroot_git_symlink_placeholders(sysroot string) !int {
1976 if !os.is_dir(sysroot) {
1977 return 0
1978 }
1979 if tracked_paths := git_repo_tracked_symlink_paths(sysroot) {
1980 mut candidates := []string{cap: tracked_paths.len}
1981 for rel_path in tracked_paths {
1982 candidates << os.join_path(sysroot, rel_path)
1983 }
1984 return repair_cross_sysroot_git_symlink_placeholders_in_paths(candidates, true)
1985 } else {
1986 mut fallback_candidates := []string{}
1987 for path in os.walk_ext(sysroot, '', hidden: false) {
1988 if !os.is_file(path) || os.is_link(path) {
1989 continue
1990 }
1991 if os.file_size(path) > max_cross_sysroot_git_symlink_placeholder_size {
1992 continue
1993 }
1994 fallback_candidates << path
1995 }
1996 return repair_cross_sysroot_git_symlink_placeholders_in_paths(fallback_candidates, false)
1997 }
1998}
1999
2000fn (mut b Builder) get_subsystem_flag() string {
2001 if b.pref.is_shared || b.pref.build_mode == .build_module || b.pref.is_o {
2002 return ''
2003 }
2004 return match b.pref.subsystem {
2005 .auto { '-municode' }
2006 .console { '-municode -mconsole' }
2007 .windows { '-municode -mwindows' }
2008 }
2009}
2010
2011struct LinuxCrossTarget {
2012 triple string
2013 lib_dir string
2014 dynamic_linker string
2015 linker_emulation string
2016}
2017
2018fn linux_cross_target_for_arch(arch pref.Arch) !LinuxCrossTarget {
2019 if arch != .amd64 {
2020 return error('Linux cross compilation currently supports only `-arch amd64`; the bundled linuxroot sysroot does not provide `${arch}` runtime files.')
2021 }
2022 return LinuxCrossTarget{
2023 triple: 'x86_64-linux-gnu'
2024 lib_dir: 'x86_64-linux-gnu'
2025 dynamic_linker: '/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2'
2026 linker_emulation: 'elf_x86_64'
2027 }
2028}
2029
2030fn (mut b Builder) cc_linux_cross() {
2031 linux_cross_target := linux_cross_target_for_arch(b.pref.arch) or {
2032 verror(err.msg())
2033 return
2034 }
2035 b.setup_ccompiler_options(b.pref.ccompiler)
2036 b.build_thirdparty_obj_files()
2037 b.setup_output_name()
2038 parent_dir := os.vmodules_dir()
2039 if !os.exists(parent_dir) {
2040 os.mkdir(parent_dir) or { panic(err) }
2041 }
2042 sysroot := os.join_path(os.vmodules_dir(), 'linuxroot')
2043 b.ensure_linuxroot_exists(sysroot)
2044 obj_file := if b.pref.build_mode == .build_module {
2045 b.pref.out_name
2046 } else {
2047 b.out_name_c + '.o'
2048 }
2049 cflags := b.get_os_cflags()
2050 defines, others, libs := cflags.defines_others_libs()
2051 // Some modules pass a raw `#flag /path/to/file.c` to add an additional
2052 // source file to the compile step (e.g. gitly's markdown module uses this
2053 // for md4c-lib.c). The native build line just appends them to the main
2054 // `clang ... main.c` invocation, but the cross-compile path uses
2055 // `clang -c <main.c> -o <main.o>`, and clang refuses to combine `-c` with
2056 // multiple inputs ("cannot specify -o when generating multiple output
2057 // files"). Pull those out, compile each separately into its own .o, and
2058 // hand the resulting objects to the linker step.
2059 mut other_flags := []string{cap: others.len}
2060 mut extra_sources := []string{}
2061 for opt in others {
2062 unq := opt.trim('"').trim("'")
2063 ext := os.file_ext(unq).to_lower()
2064 if ext in ['.c', '.cpp', '.cc', '.cxx', '.s'] && os.is_file(unq) {
2065 extra_sources << unq
2066 } else {
2067 other_flags << opt
2068 }
2069 }
2070 mut cc_name := b.pref.ccompiler
2071 mut out_name := b.pref.out_name
2072 $if windows {
2073 out_name = out_name.trim_string_right('.exe')
2074 }
2075 mut extra_objs := []string{cap: extra_sources.len}
2076 for src in extra_sources {
2077 src_obj := os.join_path(os.vtmp_dir(), os.file_name(src) + '.' +
2078 linux_cross_target.lib_dir + '.o')
2079 mut src_args := []string{cap: 16}
2080 src_args << '-w'
2081 src_args << '-fPIC'
2082 src_args << '-target ${linux_cross_target.triple}'
2083 src_args << defines
2084 src_args << '-I ${os.quoted_path('${sysroot}/include')}'
2085 src_args << other_flags
2086 src_args << '-o ${os.quoted_path(src_obj)}'
2087 src_args << '-c ${os.quoted_path(src)}'
2088 src_cmd := '${b.quote_compiler_name(cc_name)} ' + src_args.join(' ')
2089 if b.pref.show_cc {
2090 println(src_cmd)
2091 }
2092 src_res := os.execute(src_cmd)
2093 if src_res.exit_code != 0 {
2094 println('Cross compilation for Linux failed (extra source ${src}).')
2095 verror(src_res.output)
2096 return
2097 }
2098 extra_objs << src_obj
2099 }
2100 mut cc_args := []string{cap: 20}
2101 cc_args << '-w'
2102 cc_args << '-fPIC'
2103 cc_args << '-target ${linux_cross_target.triple}'
2104 cc_args << defines
2105 cc_args << '-I ${os.quoted_path('${sysroot}/include')} '
2106 cc_args << other_flags
2107 cc_args << '-o ${os.quoted_path(obj_file)}'
2108 cc_args << '-c ${os.quoted_path(b.out_name_c)}'
2109 cc_args << libs
2110 b.dump_c_options(cc_args)
2111 cc_cmd := '${b.quote_compiler_name(cc_name)} ' + cc_args.join(' ')
2112 if b.pref.show_cc {
2113 println(cc_cmd)
2114 }
2115 cc_res := os.execute(cc_cmd)
2116 if cc_res.exit_code != 0 {
2117 println('Cross compilation for Linux failed (first step, cc). Make sure you have clang installed.')
2118 verror(cc_res.output)
2119 return
2120 }
2121 if b.pref.build_mode == .build_module {
2122 return
2123 }
2124 // Compile compiler runtime builtins (provides __udivti3 etc. for 128-bit integer
2125 // operations used by thirdparty code like mbedtls bignum.c, since the linuxroot
2126 // sysroot doesn't include libgcc or compiler-rt).
2127 builtins_src := os.join_path(@VEXEROOT, 'thirdparty', 'builtins', 'compiler_builtins.c')
2128 builtins_obj := os.join_path(os.vtmp_dir(), 'compiler_builtins_${linux_cross_target.lib_dir}.o')
2129 if os.exists(builtins_src) {
2130 builtins_cmd := '${b.quote_compiler_name(cc_name)} -w -fPIC -target ${linux_cross_target.triple} -o ${os.quoted_path(builtins_obj)} -c ${os.quoted_path(builtins_src)}'
2131 builtins_res := os.execute(builtins_cmd)
2132 if builtins_res.exit_code != 0 {
2133 println('Warning: failed to compile compiler builtins for cross compilation.')
2134 }
2135 }
2136 mut linker_args := [
2137 '-L',
2138 os.quoted_path(os.join_path(sysroot, 'usr', 'lib', linux_cross_target.lib_dir)),
2139 '-L',
2140 os.quoted_path(os.join_path(sysroot, 'lib', linux_cross_target.lib_dir)),
2141 '--sysroot=' + os.quoted_path(sysroot),
2142 '-v',
2143 '-o',
2144 os.quoted_path(out_name),
2145 '-m ${linux_cross_target.linker_emulation}',
2146 '-dynamic-linker',
2147 os.quoted_path(linux_cross_target.dynamic_linker),
2148 os.quoted_path('${sysroot}/crt1.o'),
2149 os.quoted_path('${sysroot}/crti.o'),
2150 os.quoted_path(obj_file),
2151 ]
2152 for eobj in extra_objs {
2153 linker_args << os.quoted_path(eobj)
2154 }
2155 // User-defined libraries (e.g. `-lpq` from db.pg) and extra object files
2156 // must come before the system libraries they depend on. ld.lld resolves
2157 // references left-to-right, so libpq.a needs to be encountered before
2158 // -lssl/-lcrypto, otherwise its references to SSL_*/EVP_* stay unresolved.
2159 linker_args << cflags.c_options_only_object_files()
2160 linker_args << [
2161 '-lc',
2162 '-lcrypto',
2163 '-lssl',
2164 '-lpthread',
2165 os.quoted_path('${sysroot}/crtn.o'),
2166 '-lm',
2167 '-ldl',
2168 ]
2169 if os.exists(builtins_obj) {
2170 linker_args << os.quoted_path(builtins_obj)
2171 }
2172 // -ldl
2173 b.dump_c_options(linker_args)
2174 mut ldlld := '${sysroot}/ld.lld'
2175 $if windows {
2176 ldlld = 'ld.lld.exe'
2177 }
2178 linker_cmd := '${b.quote_compiler_name(ldlld)} ' + linker_args.join(' ')
2179 if b.pref.show_cc {
2180 println(linker_cmd)
2181 }
2182 res := os.execute(linker_cmd)
2183 if res.exit_code != 0 {
2184 println('Cross compilation for Linux failed (second step, lld).')
2185 verror(res.output)
2186 return
2187 }
2188 println(out_name + ' has been successfully cross compiled for linux.')
2189}
2190
2191fn (mut b Builder) cc_freebsd_cross() {
2192 b.setup_ccompiler_options(b.pref.ccompiler)
2193 b.build_thirdparty_obj_files()
2194 b.setup_output_name()
2195 parent_dir := os.vmodules_dir()
2196 if !os.exists(parent_dir) {
2197 os.mkdir(parent_dir) or { panic(err) }
2198 }
2199 sysroot := os.join_path(os.vmodules_dir(), 'freebsdroot')
2200 b.ensure_freebsdroot_exists(sysroot)
2201 obj_file := b.out_name_c + '.o'
2202 cflags := b.get_os_cflags()
2203 defines, others, libs := cflags.defines_others_libs()
2204 mut cc_args := []string{cap: 20}
2205 cc_args << '-w'
2206 cc_args << '-fPIC'
2207 cc_args << '-target x86_64-unknown-freebsd14.0' // TODO custom freebsd versions
2208 cc_args << defines
2209 cc_args << '-I'
2210 cc_args << os.quoted_path('${sysroot}/include')
2211 cc_args << '-I'
2212 cc_args << os.quoted_path('${sysroot}/usr/include')
2213 cc_args << others
2214 cc_args << '-o'
2215 cc_args << os.quoted_path(obj_file)
2216 cc_args << '-c'
2217 cc_args << os.quoted_path(b.out_name_c)
2218 cc_args << libs
2219 b.dump_c_options(cc_args)
2220 mut cc_name := b.pref.ccompiler
2221 mut out_name := b.pref.out_name
2222 $if windows {
2223 out_name = out_name.trim_string_right('.exe')
2224 }
2225 cc_cmd := '${b.quote_compiler_name(cc_name)} ' + cc_args.join(' ')
2226 if b.pref.show_cc {
2227 println(cc_cmd)
2228 }
2229 cc_res := os.execute(cc_cmd)
2230 if cc_res.exit_code != 0 {
2231 println('Cross compilation for FreeBSD failed (first step, cc). Make sure you have clang installed.')
2232 verror(cc_res.output)
2233 return
2234 }
2235 mut linker_args := [
2236 '-L',
2237 os.quoted_path('${sysroot}/lib/'),
2238 '-L',
2239 os.quoted_path('${sysroot}/usr/lib/'),
2240 '--sysroot=' + os.quoted_path(sysroot),
2241 '-v',
2242 '-o',
2243 os.quoted_path(out_name),
2244 '-m elf_x86_64',
2245 '-dynamic-linker /libexec/ld-elf.so.1',
2246 os.quoted_path('${sysroot}/usr/lib/crt1.o'),
2247 os.quoted_path('${sysroot}/usr/lib/crti.o'),
2248 os.quoted_path(obj_file),
2249 os.quoted_path('${sysroot}/usr/lib/crtn.o'),
2250 ]
2251 linker_args << '-lc' // needed for fwrite, strlen etc
2252 linker_args << '-lexecinfo' // needed for backtrace
2253 linker_args << cflags.c_options_only_object_files() // support custom module defined linker flags
2254 linker_args << libs
2255 // -ldl
2256 b.dump_c_options(linker_args)
2257 // mut ldlld := '${sysroot}/ld.lld'
2258 mut ldlld := b.pref.vcross_linker_name()
2259 linker_cmd := '${b.quote_compiler_name(ldlld)} ' + linker_args.join(' ')
2260 if b.pref.show_cc {
2261 println(linker_cmd)
2262 }
2263 res := os.execute(linker_cmd)
2264 if res.exit_code != 0 {
2265 println('Cross compilation for FreeBSD failed (second step, lld).')
2266 verror(res.output)
2267 return
2268 }
2269 println(out_name + ' has been successfully cross compiled for FreeBSD.')
2270}
2271
2272fn (mut c Builder) cc_windows_cross() {
2273 println('Cross compiling for Windows...')
2274 cross_compiler_name := c.pref.ccompiler
2275 cross_compiler_name_path := if cross_compiler_name.contains('/')
2276 || cross_compiler_name.contains('\\') {
2277 cross_compiler_name
2278 } else {
2279 os.find_abs_path_of_executable(cross_compiler_name) or {
2280 eprintln('Could not find `${cross_compiler_name}` in your PATH.')
2281 eprintln('Set `-cc` or `VCROSS_COMPILER_NAME` to a working cross compiler.')
2282 eprintln('See https://github.com/vlang/v/blob/master/doc/docs.md#cross-compilation for instructions on how to fix that.')
2283 exit(1)
2284 }
2285 }
2286
2287 c.setup_ccompiler_options(c.pref.ccompiler)
2288 c.build_thirdparty_obj_files()
2289 c.setup_output_name()
2290 icon_object := c.prepare_cross_windows_icon_resource() or { verror(err.msg()) }
2291
2292 if current_os !in ['macos', 'linux', 'termux'] {
2293 println(current_os)
2294 panic('your platform is not supported yet')
2295 }
2296
2297 all_args := c.windows_cross_compile_args(icon_object)
2298 c.dump_c_options(all_args)
2299 mut cmd := '${c.quote_compiler_name(cross_compiler_name_path)} ${all_args.join(' ')}'
2300 // cmd := 'clang -o ${obj_name} -w ${include} -m32 -c -target x86_64-win32 ${pref.default_module_path}/${c.out_name_c}'
2301 if c.pref.is_verbose || c.pref.show_cc {
2302 println(cmd)
2303 }
2304 if os.system(cmd) != 0 {
2305 println('Cross compilation for Windows failed. Make sure you have mingw-w64 installed.')
2306 $if macos {
2307 println('brew install mingw-w64')
2308 }
2309 $if linux {
2310 println('Try `sudo apt install -y mingw-w64` on Debian based distros, or `sudo pacman -S mingw-w64-gcc` on Arch, etc...')
2311 }
2312 exit(1)
2313 }
2314 println(c.pref.out_name + ' has been successfully cross compiled for windows.')
2315}
2316
2317fn (c &Builder) windows_cross_compile_args(icon_object string) []string {
2318 mut ccoptions := c.ccoptions
2319 if icon_object != '' {
2320 mut o_args := ccoptions.o_args.clone()
2321 o_args << os.quoted_path(icon_object)
2322 ccoptions.o_args = o_args
2323 }
2324 return c.all_args(ccoptions)
2325}
2326
2327fn (mut b Builder) build_thirdparty_obj_files() {
2328 b.log('build_thirdparty_obj_files: v.ast.cflags: ${b.table.cflags}')
2329 for flag in b.get_os_cflags() {
2330 if flag.value.ends_with('.o') || flag.value.ends_with('.obj') {
2331 rest_of_module_flags := b.get_rest_of_module_cflags(flag)
2332 $if windows {
2333 if b.pref.ccompiler == 'msvc' {
2334 b.build_thirdparty_obj_file_with_msvc(flag.mod, flag.value,
2335 rest_of_module_flags)
2336 continue
2337 }
2338 }
2339 b.build_thirdparty_obj_file(flag.mod, flag.value, rest_of_module_flags)
2340 }
2341 }
2342}
2343
2344enum SourceKind {
2345 c
2346 cpp
2347 asm
2348 unknown
2349}
2350
2351fn c_project_source_from_object_path(obj_path string) ?string {
2352 if !obj_path.ends_with('.o') && !obj_path.ends_with('.obj') {
2353 return none
2354 }
2355 base := obj_path.all_before_last('.')
2356 for ext in ['.c', '.cpp', '.S'] {
2357 source_file := base + ext
2358 if os.exists(source_file) {
2359 return source_file
2360 }
2361 }
2362 return none
2363}
2364
2365fn sqlite_thirdparty_validation_error(mod string, obj_path string, source_file string, source_kind SourceKind) string {
2366 if mod != 'db.sqlite' || os.file_name(obj_path) != 'sqlite3.o'
2367 || os.base(os.dir(obj_path)) != 'sqlite'
2368 || os.base(os.dir(os.dir(obj_path))) != 'thirdparty' {
2369 return ''
2370 }
2371 sqlite_dir := os.dir(obj_path)
2372 if source_kind == .cpp && os.file_name(source_file) == 'sqlite3.cpp' {
2373 return 'The `db.sqlite` module expects the SQLite amalgamation files `sqlite3.c` and `sqlite3.h` in `${sqlite_dir}`. Do not rename `sqlite3.c` to `sqlite3.cpp`; run `v vlib/db/sqlite/install_thirdparty_sqlite.vsh`, or download the SQLite amalgamation package and place those files there.'
2374 }
2375 if source_kind == .unknown {
2376 return 'The `db.sqlite` module expects the SQLite amalgamation files `sqlite3.c` and `sqlite3.h` in `${sqlite_dir}`. Run `v vlib/db/sqlite/install_thirdparty_sqlite.vsh`, or download the SQLite amalgamation package and place those files there.'
2377 }
2378 return ''
2379}
2380
2381// fixup_tcc_macos_comma_path_flags works around the fact that both tcc and
2382// clang split `-Wl,foo,bar` on every comma without an escape mechanism. When
2383// V's install path contains a literal comma (used in CI to stress
2384// space-paths), the `-Wl,-rpath,"@VEXEROOT/thirdparty/tcc/lib"` flag emitted
2385// by builtin_d_gcboehm.c.v under `$if tinyc` expands into a path with a comma
2386// and the linker rejects it. The bundled libgc.dylib also gets passed by
2387// absolute path; that itself is fine, but the dylib's `LC_ID_DYLIB` is
2388// `@rpath/libgc.dylib`, so the linked binary cannot find the library at
2389// runtime once we strip the rpath flag.
2390//
2391// This helper copies the bundled dylib into a non-comma cache directory,
2392// updates the copy's install_name to its own absolute path (so the linked
2393// binary records that path in `LC_LOAD_DYLIB`), then rewrites the dylib flag
2394// in the linker arguments to point at the copy and drops the now-broken rpath
2395// flag. The bundled libgc.dylib itself is left untouched, so binaries built
2396// before/elsewhere that rely on `@rpath/libgc.dylib` still work. The fixup is
2397// applied for any cc, since V may fall back from tcc to clang and reuse the
2398// already-emitted flags.
2399fn (v &Builder) fixup_tcc_macos_comma_path_flags(mut ccoptions CcompilerOptions) {
2400 $if !macos {
2401 return
2402 }
2403 if v.pref.os != .macos {
2404 return
2405 }
2406 tcc_lib_dir := os.real_path(os.join_path(v.pref.vroot, 'thirdparty', 'tcc', 'lib'))
2407 if !tcc_lib_dir.contains(',') {
2408 return
2409 }
2410 src_dylib := os.join_path(tcc_lib_dir, 'libgc.dylib')
2411 if !os.exists(src_dylib) {
2412 return
2413 }
2414 cache_dir := os.join_path(os.temp_dir(), 'v_tcc_libgc')
2415 if cache_dir.contains(',') {
2416 return
2417 }
2418 if !os.exists(cache_dir) {
2419 os.mkdir_all(cache_dir) or { return }
2420 }
2421 cache_dylib := os.join_path(cache_dir, 'libgc.dylib')
2422 src_mtime := os.file_last_mod_unix(src_dylib)
2423 if !os.exists(cache_dylib) || os.file_last_mod_unix(cache_dylib) < src_mtime {
2424 os.cp(src_dylib, cache_dylib) or { return }
2425 idres :=
2426 os.execute('install_name_tool -id ${os.quoted_path(cache_dylib)} ${os.quoted_path(cache_dylib)}')
2427 if idres.exit_code != 0 {
2428 if v.pref.is_verbose {
2429 eprintln('install_name_tool -id for ${cache_dylib} failed: ${idres.output.trim_space()}')
2430 }
2431 return
2432 }
2433 }
2434 cache_dylib_quoted := '"${cache_dylib}"'
2435 suffix := os.path_separator + 'tcc' + os.path_separator + 'lib' + os.path_separator +
2436 'libgc.dylib'
2437 ccoptions.linker_flags = ccoptions.linker_flags.map(if it.ends_with(suffix + '"')
2438 && it.starts_with('"') {
2439 cache_dylib_quoted
2440 } else {
2441 it
2442 })
2443 ccoptions.pre_args = ccoptions.pre_args.filter(!it.starts_with('-Wl,-rpath,'))
2444}
2445
2446fn (v &Builder) should_compile_bundled_thirdparty_object_from_source(obj_path string, source_file string, source_kind SourceKind) bool {
2447 if source_kind == .unknown {
2448 return false
2449 }
2450 if os.exists(obj_path) && os.file_last_mod_unix(obj_path) < os.file_last_mod_unix(source_file) {
2451 return true
2452 }
2453 return v.ccoptions.cc == .tcc && v.pref.os == .macos
2454}
2455
2456// thirdparty_module_root returns the `thirdparty/<module>` directory that
2457// `source_file` belongs to, or its own directory when the path is not under a
2458// `thirdparty/` tree (so the scan degrades to the old source-dir-only behavior).
2459fn thirdparty_module_root(source_file string) string {
2460 norm := source_file.replace('\\', '/')
2461 marker := '/thirdparty/'
2462 idx := norm.index(marker) or { return os.dir(source_file) }
2463 rest := norm[idx + marker.len..]
2464 mod_name := rest.all_before('/')
2465 if mod_name == '' || mod_name == rest {
2466 // No further path component after the module name: fall back.
2467 return os.dir(source_file)
2468 }
2469 return norm[..idx] + marker + mod_name
2470}
2471
2472// thirdparty_deps_mtime returns the newest mtime among `source_file` itself and
2473// every `.h`/`.hpp` header under its thirdparty module root. It is the shared
2474// cache key for third-party object compilation (both the C and MSVC paths), so
2475// that header-only edits anywhere in the module — including config headers under
2476// `include/`, e.g. mbedtls/include/mbedtls/mbedtls_config.h, not just siblings
2477// of the `.c` — reliably invalidate stale objects built before the change. The
2478// recursive header scan is memoized per module root.
2479fn (mut v Builder) thirdparty_deps_mtime(source_file string) i64 {
2480 if source_file == '' {
2481 return 0
2482 }
2483 src_mtime := os.file_last_mod_unix(source_file)
2484 root := thirdparty_module_root(source_file)
2485 hdr_mtime := v.thirdparty_header_mtimes[root] or {
2486 mut latest := i64(0)
2487 for f in os.walk_ext(root, '') {
2488 if !(f.ends_with('.h') || f.ends_with('.hpp')) {
2489 continue
2490 }
2491 m := os.file_last_mod_unix(f)
2492 if m > latest {
2493 latest = m
2494 }
2495 }
2496 v.thirdparty_header_mtimes[root] = latest
2497 latest
2498 }
2499 return if hdr_mtime > src_mtime { hdr_mtime } else { src_mtime }
2500}
2501
2502fn (mut v Builder) build_thirdparty_obj_file(mod string, path string, moduleflags []cflag.CFlag) {
2503 trace_thirdparty_obj_files := 'trace_thirdparty_obj_files' in v.pref.compile_defines
2504 obj_path := os.real_path(path)
2505 opath := v.pref.cache_manager.mod_postfix_with_key2cpath(mod, '.o', obj_path)
2506 thirdparty_desc_path := v.pref.cache_manager.mod_postfix_with_key2cpath(mod,
2507 '.thirdparty.description.txt', obj_path)
2508 mut source_file := c_project_source_from_object_path(obj_path) or { '' }
2509 source_kind := if source_file.ends_with('.c') {
2510 SourceKind.c
2511 } else if source_file.ends_with('.cpp') {
2512 SourceKind.cpp
2513 } else if source_file.ends_with('.S') {
2514 SourceKind.asm
2515 } else {
2516 SourceKind.unknown
2517 }
2518 sqlite_validation_message := sqlite_thirdparty_validation_error(mod, obj_path, source_file,
2519 source_kind)
2520 if sqlite_validation_message != '' {
2521 verror(sqlite_validation_message)
2522 }
2523 compile_bundled_source := v.should_compile_bundled_thirdparty_object_from_source(obj_path,
2524 source_file, source_kind)
2525 if os.exists(obj_path) && !compile_bundled_source {
2526 // Some .o files are distributed with no source
2527 // for example thirdparty\tcc\lib\openlibm.o
2528 // the best we can do for them is just copy them,
2529 // and hope that they work with any compiler...
2530 os.cp(obj_path, opath) or { panic(err) }
2531 return
2532 }
2533 if source_kind == .unknown {
2534 base := obj_path.all_before_last('.')
2535 eprintln('> File not found: ${base}{.c,.cpp,.S}')
2536 verror('build_thirdparty_obj_file only support .c, .cpp, and .S source file.')
2537 }
2538 bundled_object_is_stale := os.exists(obj_path)
2539 && os.file_last_mod_unix(obj_path) < os.file_last_mod_unix(source_file)
2540 cached_object_was_built_from_source := os.exists(thirdparty_desc_path)
2541 mut rebuild_reason_message := if bundled_object_is_stale {
2542 '${os.quoted_path(obj_path)} is older than ${os.quoted_path(source_file)}, rebuilding it in ${os.quoted_path(opath)} ...'
2543 } else if compile_bundled_source {
2544 '${os.quoted_path(obj_path)} is bundled for a different object format; rebuilding it in ${os.quoted_path(opath)} from ${os.quoted_path(source_file)} ...'
2545 } else {
2546 '${os.quoted_path(obj_path)} not found, building it in ${os.quoted_path(opath)} ...'
2547 }
2548 if os.exists(opath) {
2549 opath_mtime := os.file_last_mod_unix(opath)
2550 // Header-only edits (e.g. mbedtls's alignment.h, or a config header
2551 // under include/) leave every `.c` untouched, so a pure
2552 // `opath_mtime < src_mtime` test would silently reuse stale objects
2553 // that still reference the old headers. Fold the newest module header
2554 // into the cache key as well.
2555 deps_mtime := v.thirdparty_deps_mtime(source_file)
2556 if compile_bundled_source && !cached_object_was_built_from_source {
2557 rebuild_reason_message = '${os.quoted_path(opath)} was copied from a bundled object, rebuilding it from ${os.quoted_path(source_file)} ...'
2558 } else if opath_mtime < deps_mtime {
2559 rebuild_reason_message = '${os.quoted_path(opath)} is older than ${os.quoted_path(source_file)} or its sibling headers, rebuilding ...'
2560 } else {
2561 return
2562 }
2563 }
2564 if v.pref.is_verbose {
2565 println(rebuild_reason_message)
2566 }
2567 // prepare for tcc, it needs relative paths to thirdparty/tcc to work:
2568 current_folder := os.getwd()
2569 os.chdir(v.pref.vroot) or {}
2570
2571 cc_options := if source_kind == .asm {
2572 '-o ${v.tcc_quoted_path(opath)} -c ${v.tcc_quoted_path(source_file)}'
2573 } else {
2574 mut all_options := []string{cap: 4}
2575 all_options << v.pref.third_party_option
2576 all_options << moduleflags.c_options_before_target()
2577 all_options << '-o ${v.tcc_quoted_path(opath)}'
2578 all_options << '-c ${v.tcc_quoted_path(source_file)}'
2579 cpp_file := source_kind == .cpp
2580 v.thirdparty_object_args(v.ccoptions, all_options, cpp_file).map(v.tcc_windows_path_arg(it)).join(' ')
2581 }
2582
2583 // If the third party object file requires a CPP file compilation, switch to a CPP compiler
2584 mut ccompiler := v.pref.ccompiler
2585 if source_kind == .cpp {
2586 if trace_thirdparty_obj_files {
2587 println('>>> build_thirdparty_obj_files switched from compiler "${ccompiler}" to "${v.pref.cppcompiler}"')
2588 }
2589 ccompiler = v.pref.cppcompiler
2590 }
2591 cmd := '${v.quote_compiler_name(ccompiler)} ${cc_options}'
2592 if trace_thirdparty_obj_files {
2593 println('>>> build_thirdparty_obj_files cmd: ${cmd}')
2594 }
2595 res := os.execute(cmd)
2596 os.chdir(current_folder) or {}
2597 if res.exit_code != 0 {
2598 eprintln('> Failed build_thirdparty_obj_file cmd')
2599 eprintln('> mod: ${mod}')
2600 eprintln('> path: ${path}')
2601 eprintln('> source_file: ${source_file}')
2602 eprintln('> wd before cmd: ${current_folder}')
2603 eprintln('> getwd for cmd: ${v.pref.vroot}')
2604 eprintln('> cmd: ${cmd}')
2605 verror(res.output)
2606 return
2607 }
2608 v.pref.cache_manager.mod_save(mod, '.thirdparty.description.txt', obj_path,
2609 get_dsc_content('OBJ_PATH: ${obj_path}\nCMD: ${cmd}\n')) or { panic(err) }
2610 if v.pref.show_cc {
2611 println('>> OBJECT FILE compilation cmd: ${cmd}')
2612 }
2613 if trace_thirdparty_obj_files {
2614 if res.output != '' {
2615 println(res.output)
2616 }
2617 println('>>> build_thirdparty_obj_files done')
2618 }
2619}
2620
2621fn missing_compiler_info() string {
2622 $if windows {
2623 return 'https://github.com/vlang/v/wiki/Installing-a-C-compiler-on-Windows'
2624 }
2625 $if linux {
2626 return 'On Debian/Ubuntu, run `sudo apt install build-essential`'
2627 }
2628 $if macos {
2629 return 'Install command line XCode tools with `xcode-select --install`'
2630 }
2631 return 'Install a C compiler, like gcc or clang'
2632}
2633
2634fn is_tcc_compilation_failure(ccompiler string, cc_kind CC, output string) bool {
2635 return cc_kind == .tcc || is_tcc_compiler_name(ccompiler) || is_tcc_alias_compiler(ccompiler)
2636 || is_tcc_error_output(output)
2637}
2638
2639fn is_tinyc_compiler_label(label string) bool {
2640 return label.contains('tcc') || label.contains('tinyc') || label.contains('tinygcc')
2641 || label.contains('tiny_gcc') || label.contains('tiny-gcc')
2642}
2643
2644fn is_tinyc_version_output(output string) bool {
2645 return output.contains('tiny c compiler') || output.contains('tinycc')
2646 || output.contains('tinygcc') || output.contains('tiny_gcc') || output.contains('tiny-gcc')
2647 || output.contains('\ntcc') || output.starts_with('tcc')
2648}
2649
2650fn is_tcc_compiler_name(ccompiler string) bool {
2651 name := os.file_name(ccompiler).to_lower()
2652 return is_tinyc_compiler_label(name)
2653}
2654
2655fn is_tcc_error_output(output string) bool {
2656 trimmed_output := output.trim_space()
2657 return trimmed_output.starts_with('tcc: error:') || trimmed_output.contains('\ntcc: error:')
2658}
2659
2660fn is_tcc_alias_compiler(ccompiler string) bool {
2661 if ccompiler != 'cc' {
2662 return false
2663 }
2664 cc_version := os.execute('cc --version')
2665 if cc_version.exit_code != 0 {
2666 return false
2667 }
2668 lcc_version := cc_version.output.to_lower()
2669 return is_tinyc_version_output(lcc_version)
2670}
2671
2672fn ccompiler_is_available(ccompiler string) bool {
2673 if ccompiler.contains('/') || ccompiler.contains('\\') {
2674 return os.is_file(ccompiler) && os.is_executable(ccompiler)
2675 }
2676 os.find_abs_path_of_executable(ccompiler) or { return false }
2677 return true
2678}
2679
2680fn first_available_ccompiler(excluded []string) string {
2681 for candidate in ['cc', 'clang', 'gcc'] {
2682 if candidate in excluded {
2683 continue
2684 }
2685 if os.find_abs_path_of_executable(candidate) or { '' } != '' {
2686 return candidate
2687 }
2688 }
2689 return ''
2690}
2691
2692fn highlight_word(keyword string) string {
2693 return if term.can_show_color_on_stdout() { term.red(keyword) } else { keyword }
2694}
2695
2696fn error_context_lines(text string, keyword string, before int, after int) []string {
2697 khighlight := highlight_word(keyword)
2698 mut eline_idx := -1
2699 mut lines := text.split_into_lines()
2700 for idx, eline in lines {
2701 if eline.contains(keyword) {
2702 lines[idx] = lines[idx].replace(keyword, khighlight)
2703 if eline_idx == -1 {
2704 eline_idx = idx
2705 }
2706 }
2707 }
2708 idx_s := if eline_idx - before >= 0 { eline_idx - before } else { 0 }
2709 idx_e := if idx_s + after < lines.len { idx_s + after } else { lines.len }
2710 return lines[idx_s..idx_e]
2711}
2712
2713pub fn (mut v Builder) quote_compiler_name(name string) string {
2714 $if windows {
2715 // some compiler frontends on windows, like emcc, are a .bat file on windows.
2716 // Quoting the .bat file name here leads to problems with them, when they internally call python scripts for some reason.
2717 // Just emcc without quotes here does work, but:
2718 // |"emcc" -v| produces: python.exe: can't open file 'D:\programs\v\emcc.py': [Errno 2] No such file or directory
2719 if name.contains('/') || name.contains('\\') {
2720 return os.quoted_path(name)
2721 }
2722 return name
2723 }
2724 return os.quoted_path(name)
2725}
2726
2727fn write_response_file(response_file string, response_file_content string) {
2728 $if windows {
2729 os.write_file_array(response_file,
2730 string_to_ansi_not_null_terminated(response_file_content)) or {
2731 write_response_file_error(response_file_content, err)
2732 }
2733 } $else {
2734 os.write_file(response_file, response_file_content) or {
2735 write_response_file_error(response_file_content, err)
2736 }
2737 }
2738}
2739
2740fn write_response_file_error(response_file string, err IError) {
2741 verror('Unable to write to C response file "${response_file}", error: ${err}')
2742}
2743
2744fn get_dsc_content(suffix string) string {
2745 vargs := os.args.join(' ')
2746 vjobs := os.getenv('VJOBS')
2747 vflags := os.getenv('VFLAGS')
2748 return 'CLI: ${vargs}\nVFLAGS="${vflags}"\nVJOBS=${vjobs}\n${suffix}'
2749}
2750