Skip to content

Commit 17b5eb5

Browse files
committed
vpm: windows fix
1 parent 6430347 commit 17b5eb5

13 files changed

Lines changed: 379 additions & 71 deletions

File tree

cmd/tools/vpm/common.v

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,9 @@ fn get_ident_from_url(raw_url string) !(string, string) {
156156
// On Windows, absolute paths like `C:\...` are misinterpreted by urllib.parse
157157
// (the drive letter `C:` is treated as a URL scheme). Handle local paths first.
158158
if os.is_abs_path(raw_url) || raw_url.starts_with('./') || raw_url.starts_with('../')
159-
|| raw_url.starts_with('~/') || raw_url.starts_with('.\\') || raw_url.starts_with('..\\') {
160-
normalized := raw_url.replace('\\', '/')
159+
|| raw_url.starts_with('~/') || raw_url.starts_with('.\\') || raw_url.starts_with('..\\')
160+
|| raw_url.starts_with('file://') {
161+
normalized := raw_url.trim_string_left('file://').replace('\\', '/').trim_left('/')
161162
_, name := normalized.rsplit_once('/') or {
162163
return '', normalized.trim_string_right('.git')
163164
}

vlib/os/os.c.v

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -928,14 +928,16 @@ pub fn real_path(fpath string) string {
928928
mut res := ''
929929
$if windows {
930930
pu16_fullpath := unsafe { &u16(&fullpath[0]) }
931-
// gets handle with GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0
932-
// use C.CreateFile(fpath.to_wide(), 0x80000000, 1, 0, 3, 0x80, 0) instead of get_file_handle
931+
// gets handle with GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING,
932+
// FILE_FLAG_BACKUP_SEMANTICS, 0
933+
// FILE_FLAG_BACKUP_SEMANTICS (0x02000000) is needed to open directories
934+
// and resolve directory symlinks properly.
933935
// try to open the file to get symbolic link path
934936
fpath_wide := fpath.to_wide()
935937
defer {
936938
unsafe { free(voidptr(fpath_wide)) }
937939
}
938-
file := C.CreateFile(fpath_wide, 0x80000000, 1, 0, 3, 0x80, 0)
940+
file := C.CreateFile(fpath_wide, 0x80000000, 1, 0, 3, 0x02000000, 0)
939941
if file != voidptr(-1) {
940942
defer { C.CloseHandle(file) }
941943
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew

vlib/v2/builder/builder.v

Lines changed: 177 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

550575
fn (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+
12151357
fn 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
}

vlib/v2/gen/cleanc/array.v

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,25 @@ fn (mut g Gen) emit_deferred_fixed_array_aliases() {
149149
if !name.starts_with('Array_fixed_') {
150150
continue
151151
}
152+
// Skip if already emitted (safe to call this function multiple times).
153+
alias_key := 'alias_${name}'
154+
if alias_key in g.emitted_types {
155+
continue
156+
}
152157
if info := g.collected_fixed_array_types[name] {
153158
if info.elem_type in primitive_types
154159
|| info.elem_type in ['char', 'voidptr', 'charptr', 'byteptr', 'void*', 'char*'] {
155160
continue // already emitted in emit_runtime_aliases
156161
}
162+
// Only emit if the element type is already defined (struct body emitted).
163+
// For struct element types, check if the struct body has been emitted.
164+
elem_body_key := 'body_${info.elem_type}'
165+
elem_alias_key := 'alias_${info.elem_type}'
166+
if info.elem_type.contains('__') && elem_body_key !in g.emitted_types
167+
&& elem_alias_key !in g.emitted_types && !info.elem_type.starts_with('Array_fixed_') {
168+
continue // element struct not yet defined, defer
169+
}
157170
g.sb.writeln('typedef ${info.elem_type} ${name} [${info.size}];')
158-
alias_key := 'alias_${name}'
159171
body_key := 'body_${name}'
160172
g.emitted_types[alias_key] = true
161173
g.emitted_types[body_key] = true

0 commit comments

Comments
 (0)