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