@@ -347,9 +347,14 @@ fn sanitized_utf8_str_visible_length_fn() string {
347347 ].join ('\n ' )
348348}
349349
350- fn (mut b Builder) compile_cleanc_executable (output_name string , cc string , cc_flags string , error_limit_flag string , mut sw time.StopWatch) {
350+ fn (mut b Builder) compile_cleanc_executable (output_name string , cc string , cc_flags string , cc_link_flags string , error_limit_flag string , mut sw time.StopWatch) {
351351 cc_start := sw.elapsed ()
352- cc_cmd := '${cc} ${cc_flags} -w "${staged_c_file} " -o "${output_name} "${error_limit_flag} '
352+ // Non-cached path: compile and link in one step, so include both compile and link flags.
353+ mut all_flags := cc_flags
354+ if cc_link_flags.len > 0 {
355+ all_flags + = ' ${cc_link_flags} '
356+ }
357+ cc_cmd := '${cc} ${all_flags} -w "${staged_c_file} " -o "${output_name} "${error_limit_flag} '
353358 run_cc_cmd_or_exit (cc_cmd, 'C compilation' , b.pref.show_cc)
354359 print_time ('CC' , time.Duration (sw.elapsed () - cc_start))
355360
@@ -477,19 +482,38 @@ fn (mut b Builder) gen_cleanc() {
477482
478483 cc := configured_cc (b.pref.vroot)
479484 directive_flags := b.collect_cflags_from_sources ()
485+ // Separate directive flags into compile-only and link-only flags.
486+ // -framework, -l, -L, .o/.a/.so/.dylib are linker flags and must NOT
487+ // be passed during -c compilation (they can trigger unwanted header
488+ // processing, e.g. MetalKit SIMD errors on macOS).
489+ directive_compile_flags , directive_link_flags := split_compile_and_link_flags (directive_flags)
480490 mut cc_flag_parts := []string {}
491+ mut cc_link_parts := []string {}
481492 env_flags := configured_cflags ()
482493 if env_flags.trim_space () != '' {
483494 cc_flag_parts << env_flags.trim_space ()
484495 }
485- if directive_flags.trim_space () != '' {
486- cc_flag_parts << directive_flags.trim_space ()
496+ if directive_compile_flags.trim_space () != '' {
497+ cc_flag_parts << directive_compile_flags.trim_space ()
498+ }
499+ if directive_link_flags.trim_space () != '' {
500+ cc_link_parts << directive_link_flags.trim_space ()
487501 }
488502 tcc_extra := tcc_flags (cc, b.pref.vroot)
489503 if tcc_extra.trim_space () != '' {
490504 cc_flag_parts << tcc_extra.trim_space ()
491505 }
506+ // macOS code can include Objective-C (.m) files via #include directives.
507+ // Tell the C compiler to treat the source as Objective-C.
508+ // Added unconditionally: if tcc is the compiler it will fail on other flags
509+ // anyway and the fallback to cc/clang will use this flag properly.
510+ $if macos {
511+ cc_flag_parts << '-x objective-c'
512+ }
513+ cc_flag_parts << '-std=gnu11'
514+ cc_flag_parts << '-fwrapv'
492515 cc_flags := cc_flag_parts.join (' ' )
516+ cc_link_flags := cc_link_parts.join (' ' )
493517 mut error_limit_flag := ''
494518 if ! cc.contains ('tcc' ) {
495519 version_res := os.execute ('${cc} --version' )
@@ -527,7 +551,7 @@ fn (mut b Builder) gen_cleanc() {
527551
528552 // Fast path: cache one core object (builtin+strconv), compile/link only the rest.
529553 if use_cache && ! b.pref.skip_builtin && b.has_module ('builtin' ) && b.has_module ('strconv' ) {
530- if b.gen_cleanc_with_cached_core (output_name, cc, cc_flags, error_limit_flag, mut
554+ if b.gen_cleanc_with_cached_core (output_name, cc, cc_flags, cc_link_flags, error_limit_flag, mut
531555 sw)
532556 {
533557 return
@@ -544,7 +568,8 @@ fn (mut b Builder) gen_cleanc() {
544568 }
545569 os.write_file (staged_c_file, c_source) or { panic (err) }
546570 println ('[*] Wrote ${staged_c_file} ' )
547- b.compile_cleanc_executable (output_name, cc, cc_flags, error_limit_flag, mut sw)
571+ b.compile_cleanc_executable (output_name, cc, cc_flags, cc_link_flags, error_limit_flag, mut
572+ sw)
548573}
549574
550575fn (b &Builder) is_cmd_v2_self_build () bool {
@@ -796,7 +821,7 @@ fn (mut b Builder) gen_cleanc_source_with_options(modules []string, export_const
796821 return source
797822}
798823
799- fn (mut b Builder) gen_cleanc_with_cached_core (output_name string , cc string , cc_flags string , error_limit_flag string , mut sw time.StopWatch) bool {
824+ fn (mut b Builder) gen_cleanc_with_cached_core (output_name string , cc string , cc_flags string , cc_link_flags string , error_limit_flag string , mut sw time.StopWatch) bool {
800825 main_modules := b.collect_modules_excluding (core_cached_module_names)
801826 if main_modules.len == 0 {
802827 if os.getenv ('V2_TRACE_CACHE' ) != '' {
@@ -846,8 +871,35 @@ fn (mut b Builder) gen_cleanc_with_cached_core(output_name string, cc string, cc
846871 if ! is_elf {
847872 // Cached .o was compiled by cc (via TCC fallback), not TCC.
848873 // Use cc for main compilation and linking to match formats.
874+ // Keep all flags (including directive -I paths) but strip
875+ // TCC-specific -I/-L paths that would conflict with system headers.
849876 main_cc = 'cc'
850- main_cc_flags = configured_cflags ()
877+ tcc_dir2 := cc.all_before_last ('/tcc' )
878+ if tcc_dir2 .len > 0 {
879+ mut parts := cc_flags.fields ()
880+ mut filtered := []string {cap: parts.len}
881+ mut j := 0
882+ for j < parts.len {
883+ p := parts[j]
884+ if (p == '-I' || p == '-L' ) && j + 1 < parts.len && parts[j + 1 ].contains ('tcc' ) {
885+ j + = 2
886+ continue
887+ }
888+ if (p.starts_with ('-I' ) || p.starts_with ('-L' )) && p.contains ('tcc' ) {
889+ j++
890+ continue
891+ }
892+ // Strip quoted -I/-L containing tcc
893+ if (p.starts_with ('-I"' ) || p.starts_with ('-L"' )
894+ || p.starts_with ("-I'" ) || p.starts_with ("-L'" )) && p.contains ('tcc' ) {
895+ j++
896+ continue
897+ }
898+ filtered << p
899+ j++
900+ }
901+ main_cc_flags = filtered.join (' ' )
902+ }
851903 }
852904 }
853905
@@ -889,6 +941,9 @@ fn (mut b Builder) gen_cleanc_with_cached_core(output_name string, cc string, cc
889941 link_cmd + = ' "${vlib_obj} "'
890942 }
891943 link_cmd + = ' -o "${output_name} "'
944+ if cc_link_flags.len > 0 {
945+ link_cmd + = ' ${cc_link_flags} '
946+ }
892947 run_cc_cmd_or_exit (link_cmd, 'Linking' , b.pref.show_cc)
893948 print_time ('CC' , time.Duration (sw.elapsed () - cc_start))
894949
@@ -1196,7 +1251,36 @@ fn (b &Builder) collect_cflags_from_sources() string {
11961251 continue
11971252 }
11981253 lines := os.read_lines (file.name) or { continue }
1254+ // Track $if nesting to skip flags inside non-matching comptime blocks.
1255+ // skip_depth > 0 means we are inside a non-matching $if block.
1256+ mut skip_depth := 0
11991257 for line in lines {
1258+ trimmed := line.trim_space ()
1259+ // Handle $if / $else / closing braces for comptime blocks
1260+ if trimmed.starts_with (r '$if ' ) {
1261+ cond := trimmed[4 ..].trim_right ('?{ ' ).trim_space ()
1262+ if skip_depth > 0 {
1263+ skip_depth++
1264+ } else if ! comptime_cond_matches (cond) {
1265+ skip_depth = 1
1266+ }
1267+ continue
1268+ }
1269+ if trimmed.starts_with (r '$else ' ) || trimmed == r '} $else {' {
1270+ if skip_depth == 1 {
1271+ skip_depth = 0
1272+ } else if skip_depth == 0 {
1273+ skip_depth = 1
1274+ }
1275+ continue
1276+ }
1277+ if trimmed == '}' && skip_depth > 0 {
1278+ skip_depth--
1279+ continue
1280+ }
1281+ if skip_depth > 0 {
1282+ continue
1283+ }
12001284 mut flag := parse_flag_directive_line (line, file.name) or { continue }
12011285 flag = flag.replace ('@VEXEROOT' , b.pref.vroot).replace ('VEXEROOT' , b.pref.vroot)
12021286 if flag_references_missing_file (flag) {
@@ -1212,6 +1296,64 @@ fn (b &Builder) collect_cflags_from_sources() string {
12121296 return flags.join (' ' )
12131297}
12141298
1299+ // split_compile_and_link_flags separates a flags string into compiler-only
1300+ // flags (for -c compilation) and linker-only flags (for the link step).
1301+ // Linker flags include: -l*, -L*, -framework, .o, .a, .so, .dylib files.
1302+ fn split_compile_and_link_flags (flags string ) (string , string ) {
1303+ tokens := flags.fields ()
1304+ mut compile := []string {}
1305+ mut link := []string {}
1306+ mut i := 0
1307+ for i < tokens.len {
1308+ tok := tokens[i]
1309+ if tok == '-framework' {
1310+ // -framework Name: two tokens, linker only
1311+ link << tok
1312+ if i + 1 < tokens.len {
1313+ i++
1314+ link << tokens[i]
1315+ }
1316+ } else if tok.starts_with ('-l' ) || tok.starts_with ('-L' ) {
1317+ link << tok
1318+ } else if tok.ends_with ('.o' ) || tok.ends_with ('.obj' ) || tok.ends_with ('.a' )
1319+ || tok.ends_with ('.so' ) || tok.ends_with ('.dylib' ) {
1320+ link << tok
1321+ } else {
1322+ compile << tok
1323+ }
1324+ i++
1325+ }
1326+ return compile.join (' ' ), link.join (' ' )
1327+ }
1328+
1329+ fn comptime_cond_matches (cond string ) bool {
1330+ // Handle negation: $if !platform
1331+ if cond.starts_with ('!' ) {
1332+ return ! comptime_cond_matches (cond[1 ..])
1333+ }
1334+ // Handle && conjunction
1335+ if and_idx := cond.index ('&&' ) {
1336+ left := cond[..and_idx].trim_space ()
1337+ right := cond[and_idx + 2 ..].trim_space ()
1338+ return comptime_cond_matches (left) && comptime_cond_matches (right)
1339+ }
1340+ current := os.user_os ().to_lower ()
1341+ return match cond.to_lower () {
1342+ 'macos' , 'darwin' , 'mac' { current == 'macos' || current == 'darwin' }
1343+ 'linux' { current == 'linux' }
1344+ 'windows' { current == 'windows' }
1345+ 'freebsd' { current == 'freebsd' }
1346+ 'openbsd' { current == 'openbsd' }
1347+ 'netbsd' { current == 'netbsd' }
1348+ 'dragonfly' { current == 'dragonfly' }
1349+ 'android' { current == 'android' }
1350+ 'native' { false }
1351+ 'emscripten' { false }
1352+ 'ios' { false }
1353+ else { false } // unknown user-defined flags default to false
1354+ }
1355+ }
1356+
12151357fn default_cc (vroot string ) string {
12161358 // Try to use tcc by default, like v1 does.
12171359 tcc_path := os.join_path (vroot, 'thirdparty' , 'tcc' , 'tcc.exe' )
@@ -1260,7 +1402,33 @@ fn run_cc_cmd_or_exit(cmd string, stage string, show_cc bool) bool {
12601402 eprintln ('Failed to compile with tcc, falling back to cc' )
12611403 eprintln ('tcc cmd: ${cmd} ' )
12621404 eprintln (result.output)
1263- fallback_cmd := cmd.replace_once (cc_binary, 'cc' )
1405+ // Replace TCC binary with cc and strip TCC-specific include/lib
1406+ // paths. TCC's tgmath.h conflicts with macOS system headers,
1407+ // causing SIMD ambiguity errors in MetalKit when compiling as
1408+ // Objective-C.
1409+ mut fallback_cmd := cmd.replace_once (cc_binary, 'cc' )
1410+ tcc_dir := cc_binary.all_before_last ('/tcc' )
1411+ if tcc_dir.len > 0 {
1412+ // Remove -I and -L flags pointing into the TCC directory.
1413+ mut parts := fallback_cmd.fields ()
1414+ mut filtered := []string {cap: parts.len}
1415+ mut i2 := 0
1416+ for i2 < parts.len {
1417+ p := parts[i2 ]
1418+ if (p == '-I' || p == '-L' ) && i2 + 1 < parts.len
1419+ && parts[i2 + 1 ].contains ('tcc' ) {
1420+ i2 + = 2
1421+ continue
1422+ }
1423+ if (p.starts_with ('-I' ) || p.starts_with ('-L' )) && p.contains ('tcc' ) {
1424+ i2 ++
1425+ continue
1426+ }
1427+ filtered << p
1428+ i2 ++
1429+ }
1430+ fallback_cmd = filtered.join (' ' )
1431+ }
12641432 run_cc_cmd_or_exit (fallback_cmd, stage, show_cc)
12651433 return true
12661434 }
0 commit comments