v2 / vlib / v / builder / msvc_windows.v
818 lines · 782 sloc · 24.61 KB · da7e85cbec7fd73d9d26db033850648c49120c9f
Raw
1module builder
2
3import os
4import time
5import v.util
6import v.cflag
7
8#flag windows -l shell32
9#flag windows -l dbghelp
10#flag windows -l advapi32
11
12// shell32 for RegOpenKeyExW etc
13// Mimics a HKEY
14type RegKey = voidptr
15
16// Taken from the windows SDK
17const hkey_local_machine = RegKey(0x80000002)
18const key_query_value = (0x0001)
19const key_wow64_32key = (0x0200)
20const key_enumerate_sub_keys = (0x0008)
21
22// Given a root key look for one of the subkeys in 'versions' and get the path
23fn find_windows_kit_internal(key RegKey, versions []string) !string {
24 $if windows {
25 unsafe {
26 for version in versions {
27 required_bytes := u32(0) // TODO: mut
28 result := C.RegQueryValueEx(key, version.to_wide(), 0, 0, 0,
29 voidptr(&required_bytes))
30 length := required_bytes / 2
31 if result != 0 {
32 continue
33 }
34 alloc_length := (required_bytes + 2)
35 mut value := &u16(malloc_noscan(int(alloc_length)))
36 if value == nil {
37 continue
38 }
39 //
40 else {
41 }
42 result2 := C.RegQueryValueEx(key, version.to_wide(), 0, 0, voidptr(value),
43 voidptr(&alloc_length))
44 if result2 != 0 {
45 continue
46 }
47 // We might need to manually null terminate this thing
48 // So just make sure that we do that
49 if value[length - 1] != u16(0) {
50 value[length] = u16(0)
51 }
52 res := string_from_wide(value)
53 return res
54 }
55 }
56 }
57 return error('windows kit not found')
58}
59
60struct WindowsKit {
61 um_lib_path string
62 ucrt_lib_path string
63 um_include_path string
64 ucrt_include_path string
65 shared_include_path string
66}
67
68// Try and find the root key for installed windows kits
69fn find_windows_kit_root(target_arch string) !WindowsKit {
70 $if windows {
71 wkroot := find_windows_kit_root_by_reg(target_arch) or {
72 if wkroot := find_windows_kit_root_by_env(target_arch) {
73 return wkroot
74 }
75 return err
76 }
77
78 return wkroot
79 } $else {
80 return error('Host OS does not support finding a windows kit')
81 }
82}
83
84// Try to find the root key for installed windows kits from registry
85fn find_windows_kit_root_by_reg(target_arch string) !WindowsKit {
86 $if windows {
87 root_key := RegKey(0)
88 path := 'SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots'
89 rc := C.RegOpenKeyEx(hkey_local_machine, path.to_wide(), 0,
90 key_query_value | key_wow64_32key | key_enumerate_sub_keys, voidptr(&root_key))
91
92 if rc != 0 {
93 return error('Unable to open root key')
94 }
95 // Try and find win10 kit
96 kit_root := find_windows_kit_internal(root_key, ['KitsRoot10', 'KitsRoot81']) or {
97 C.RegCloseKey(root_key)
98 return error('Unable to find a windows kit')
99 }
100 C.RegCloseKey(root_key)
101 return new_windows_kit(kit_root, target_arch)
102 } $else {
103 return error('Host OS does not support finding a windows kit')
104 }
105}
106
107fn new_windows_kit(kit_root string, target_arch string) !WindowsKit {
108 kit_lib := kit_root + 'Lib'
109 files := os.ls(kit_lib)!
110 mut highest_path := ''
111 mut highest_int := 0
112 for f in files {
113 no_dot := f.replace('.', '')
114 v_int := no_dot.int()
115 if v_int > highest_int {
116 highest_int = v_int
117 highest_path = f
118 }
119 }
120 kit_lib_highest := kit_lib + '\\${highest_path}'
121 kit_include_highest := kit_lib_highest.replace('Lib', 'Include')
122 return WindowsKit{
123 um_lib_path: kit_lib_highest + '\\um\\${target_arch}'
124 ucrt_lib_path: kit_lib_highest + '\\ucrt\\${target_arch}'
125 um_include_path: kit_include_highest + '\\um'
126 ucrt_include_path: kit_include_highest + '\\ucrt'
127 shared_include_path: kit_include_highest + '\\shared'
128 }
129}
130
131fn find_windows_kit_root_by_env(target_arch string) !WindowsKit {
132 kit_root := os.getenv('WindowsSdkDir')
133 if kit_root == '' {
134 return error('empty WindowsSdkDir')
135 }
136 return new_windows_kit(kit_root, target_arch)
137}
138
139struct VsInstallation {
140 include_path string
141 lib_path string
142 exe_path string
143}
144
145fn find_vs(vswhere_dir string, host_arch string, target_arch string) !VsInstallation {
146 $if windows {
147 vsinst := find_vs_by_reg(vswhere_dir, host_arch, target_arch) or {
148 if vsinst := find_vs_by_env(host_arch, target_arch) {
149 return vsinst
150 }
151 return err
152 }
153 return vsinst
154 } $else {
155 return error('Host OS does not support finding a Visual Studio installation')
156 }
157}
158
159fn find_vs_by_reg(vswhere_dir string, host_arch string, target_arch string) !VsInstallation {
160 $if windows {
161 // Emily:
162 // VSWhere is guaranteed to be installed at this location now
163 // If its not there then end user needs to update their visual studio
164 // installation!
165 res :=
166 os.execute('"${vswhere_dir}\\Microsoft Visual Studio\\Installer\\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath')
167 // println('res: "${res}"')
168 if res.exit_code != 0 {
169 return error_with_code(res.output, res.exit_code)
170 }
171 res_output := res.output.trim_space()
172 version := os.read_file('${res_output}\\VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt') or {
173 // println('Unable to find msvc version')
174 return error('Unable to find vs installation')
175 }
176 // println('version: ${version}')
177 v := version.trim_space()
178 lib_path := '${res_output}\\VC\\Tools\\MSVC\\${v}\\lib\\${target_arch}'
179 include_path := '${res_output}\\VC\\Tools\\MSVC\\${v}\\include'
180 if os.exists('${lib_path}\\vcruntime.lib') {
181 p := '${res_output}\\VC\\Tools\\MSVC\\${v}\\bin\\Host${host_arch}\\${target_arch}'
182 // println('${lib_path} ${include_path}')
183 return VsInstallation{
184 exe_path: p
185 lib_path: lib_path
186 include_path: include_path
187 }
188 }
189 println('Unable to find vs installation (attempted to use lib path "${lib_path}")')
190 return error('Unable to find vs exe folder')
191 } $else {
192 return error('Host OS does not support finding a Visual Studio installation')
193 }
194}
195
196fn find_vs_by_env(host_arch string, target_arch string) !VsInstallation {
197 vs_dir := os.getenv('VSINSTALLDIR')
198 if vs_dir == '' {
199 return error('empty VSINSTALLDIR')
200 }
201
202 vc_tools_dir := os.getenv('VCToolsInstallDir')
203 if vc_tools_dir == '' {
204 return error('empty VCToolsInstallDir')
205 }
206
207 bin_dir := '${vc_tools_dir}bin\\Host${host_arch}\\${target_arch}'
208 lib_path := '${vc_tools_dir}lib\\${target_arch}'
209 include_path := '${vc_tools_dir}include'
210
211 return VsInstallation{
212 exe_path: bin_dir
213 lib_path: lib_path
214 include_path: include_path
215 }
216}
217
218fn find_msvc(m64_target bool) !MsvcResult {
219 $if windows {
220 processor_architecture := os.getenv('PROCESSOR_ARCHITECTURE')
221 vswhere_dir := if processor_architecture == 'x86' {
222 '%ProgramFiles%'
223 } else {
224 '%ProgramFiles(x86)%'
225 }
226 host_arch := if processor_architecture == 'x86' { 'X86' } else { 'X64' }
227 target_arch := if !m64_target { 'X86' } else { 'X64' }
228 wk := find_windows_kit_root(target_arch) or { return error('Unable to find windows sdk') }
229 vs := find_vs(vswhere_dir, host_arch, target_arch) or {
230 return error('Unable to find visual studio')
231 }
232 return MsvcResult{
233 full_cl_exe_path: os.real_path(vs.exe_path + os.path_separator + 'cl.exe')
234 exe_path: vs.exe_path
235 um_lib_path: wk.um_lib_path
236 ucrt_lib_path: wk.ucrt_lib_path
237 vs_lib_path: vs.lib_path
238 um_include_path: wk.um_include_path
239 ucrt_include_path: wk.ucrt_include_path
240 vs_include_path: vs.include_path
241 shared_include_path: wk.shared_include_path
242 valid: true
243 }
244 } $else {
245 // This hack allows to at least see the generated .c file with `-os windows -cc msvc -o x.c`
246 // Please do not remove it, unless you also check that the above continues to work.
247 return MsvcResult{
248 full_cl_exe_path: '/usr/bin/true'
249 valid: true
250 }
251 }
252}
253
254pub fn (mut v Builder) cc_msvc() {
255 r := v.cached_msvc
256 if r.valid == false {
257 verror('cannot find MSVC on this OS')
258 }
259 out_name_pdb := os.real_path(v.out_name_c + '.pdb')
260 out_name_cmd_line := os.real_path(v.out_name_c + '.rsp')
261 // testdll.01JNX9W7JAV4FKMZ6KDXT67QYV.tmp.so.c
262 app_dir_out_name_c := (v.pref.out_name.all_before_last('\\') + '\\' +
263 v.pref.out_name_c.all_after_last('\\')).all_before_last('.')
264 // testdll.dll
265 app_dir_out_name := if v.pref.out_name.ends_with('.dll') || v.pref.out_name.ends_with('.exe') {
266 v.pref.out_name[0..v.pref.out_name.len - 4]
267 } else {
268 v.pref.out_name
269 }
270 mut a := []string{}
271
272 env_cflags := os.getenv('CFLAGS')
273 mut all_cflags := '${env_cflags} ${v.pref.cflags}'
274 if all_cflags != ' ' {
275 a << all_cflags
276 }
277
278 // Default arguments
279 // `-w` no warnings
280 // `/we4013` 2 unicode defines, see https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-3-c4013?redirectedfrom=MSDN&view=msvc-170
281 // `/volatile:ms` enables atomic volatile (gcc _Atomic)
282 // `/F33554432` changes the stack size to 32MB, see https://docs.microsoft.com/en-us/cpp/build/reference/f-set-stack-size?view=msvc-170
283 // Note: passing `/FNUMBER` is preferable to `/F NUMBER` for unix shells like bash or in cygwin, that otherwise may treat the `/F` as a folder,
284 // if there is an F: drive in the system (they map c: as /c/, d: as /d/ etc)
285 a << ['-w', '/we4013', '/volatile:ms', '/F33554432']
286 if v.pref.is_prod && !v.pref.no_prod_options {
287 a << '/O2'
288 }
289 if v.pref.is_debug {
290 a << '/MDd'
291 a << '/D_DEBUG'
292 // /Zi generates a .pdb
293 // /Fd sets the pdb file name (so its not just vc140 all the time)
294 a << ['/Zi', '/Fd"${out_name_pdb}"']
295 } else {
296 a << '/MD'
297 a << '/DNDEBUG'
298 a << '/DNO_DEBUGGING'
299 if !v.ccoptions.debug_mode {
300 v.pref.cleanup_files << out_name_pdb
301 v.pref.cleanup_files << app_dir_out_name + '.pdb'
302 }
303 }
304 if v.pref.is_shared {
305 if !v.pref.out_name.ends_with('.dll') {
306 v.pref.out_name += '.dll'
307 }
308 // Build dll
309 a << '/LD'
310 } else if !v.pref.out_name.ends_with('.exe') {
311 v.pref.out_name += '.exe'
312 }
313 v.pref.out_name = os.real_path(v.pref.out_name)
314 // alibs := []string{} // builtin.o os.o http.o etc
315 if v.pref.build_mode == .build_module {
316 // Compile only
317 a << '/c'
318 }
319 if v.pref.sanitize {
320 eprintln('Sanitize not supported on msvc.')
321 }
322 // The C file we are compiling
323 // Use /Tc<file> instead of /TC, otherwise .lib/.obj linker inputs are also
324 // treated as C sources.
325 a << '/Tc' + os.quoted_path(os.real_path(v.out_name_c))
326 if !v.ccoptions.debug_mode {
327 v.pref.cleanup_files << os.real_path(v.out_name_c)
328 }
329 // Emily:
330 // Not all of these are needed (but the compiler should discard them if they are not used)
331 // these are the defaults used by msbuild and visual studio
332 mut real_libs := ['kernel32.lib', 'user32.lib', 'advapi32.lib', 'ws2_32.lib']
333 sflags := v.msvc_string_flags(v.get_os_cflags())
334 real_libs << sflags.real_libs
335 inc_paths := sflags.inc_paths
336 lib_paths := sflags.lib_paths
337 defines := sflags.defines
338 other_flags := sflags.other_flags
339 // Include the base paths
340 a << r.include_paths()
341 a << defines
342 a << inc_paths
343 a << other_flags
344 // Libs are passed to cl.exe which passes them to the linker
345 a << real_libs.join(' ')
346 a << '/link'
347 if v.pref.is_shared {
348 // generate a .def for export function names, avoid function name mangle
349 // must put after the /link flag!
350 def_name := app_dir_out_name + '.def'
351 a << '/DEF:' + os.quoted_path(def_name)
352 if !v.ccoptions.debug_mode {
353 v.pref.cleanup_files << def_name
354 v.pref.cleanup_files << app_dir_out_name_c + '.exp'
355 v.pref.cleanup_files << app_dir_out_name_c + '.lib'
356 }
357 }
358
359 a << '/nologo' // NOTE: /NOLOGO is explicitly not recognised!
360 a << '/OUT:${os.quoted_path(v.pref.out_name)}'
361 if v.pref.is_livemain {
362 a << '/IMPLIB:${os.quoted_path(v.pref.out_name[..v.pref.out_name.len - 4] + '.lib')}'
363 }
364 a << r.library_paths()
365 if !all_cflags.contains('/DEBUG') {
366 // only use /DEBUG, if the user *did not* provide its own:
367 a << '/DEBUG:FULL' // required for prod builds to generate a PDB file
368 }
369 if v.pref.is_prod && !v.pref.no_prod_options {
370 a << '/INCREMENTAL:NO' // Disable incremental linking
371 a << '/OPT:REF'
372 a << '/OPT:ICF'
373 }
374 a << lib_paths
375 env_ldflags := os.getenv('LDFLAGS')
376 if env_ldflags != '' {
377 a << env_ldflags
378 }
379 if v.pref.ldflags != '' {
380 a << v.pref.ldflags.trim_space()
381 }
382 // Remove stray quoted gcc-style -I/-L/-l tokens (e.g. "-I...") that can leak into MSVC args.
383 mut filtered_args := []string{cap: a.len}
384 for arg in a {
385 mut s := arg.trim_space()
386 if s.len >= 3 && s[0] == `"` && s[1] == `-` && s[2] in [`I`, `L`, `l`] {
387 continue
388 }
389 filtered_args << arg
390 }
391 a = filtered_args.clone()
392 v.dump_c_options(a)
393 raw_args := a.join(' ')
394 mut args := raw_args
395 mut response_file := ''
396 mut cmd := '"${r.full_cl_exe_path}" ${raw_args.replace('\n', ' ')}'
397 if v.msvc_should_use_rsp(a) {
398 args = '\xEF\xBB\xBF' + raw_args // write a BOM to indicate the utf8 encoding of the file
399 // write args to a file so that we dont smash createprocess
400 os.write_file(out_name_cmd_line, args) or {
401 verror('Unable to write response file to "${out_name_cmd_line}"')
402 }
403 response_file = out_name_cmd_line
404 cmd = '"${r.full_cl_exe_path}" "@${out_name_cmd_line}"'
405 }
406 if !v.ccoptions.debug_mode {
407 if response_file != '' {
408 v.pref.cleanup_files << out_name_cmd_line
409 }
410 v.pref.cleanup_files << app_dir_out_name_c + '.obj'
411 v.pref.cleanup_files << app_dir_out_name + '.ilk'
412 }
413 // It is hard to see it at first, but the quotes above ARE balanced :-| ...
414 // Also the double quotes at the start ARE needed.
415 v.show_cc(cmd, response_file, args)
416 if os.user_os() != 'windows' && !v.pref.out_name.ends_with('.c') {
417 verror('cannot build with msvc on ${os.user_os()}')
418 }
419 util.timing_start('C msvc')
420 res := os.execute(cmd)
421 if res.exit_code != 0 {
422 eprintln('================== ${c_compilation_error_title} (from msvc): ==============')
423 eprintln(res.output)
424 verror('msvc error')
425 }
426 util.timing_measure('C msvc')
427 if v.pref.show_c_output {
428 v.show_c_compiler_output(r.full_cl_exe_path, res)
429 } else {
430 v.post_process_c_compiler_output(r.full_cl_exe_path, res)
431 }
432 v.apply_windows_icon_to_executable() or { verror(err.msg()) }
433 // println(res)
434 // println('C OUTPUT:')
435}
436
437fn (mut v Builder) build_thirdparty_obj_file_with_msvc(mod string, path string, moduleflags []cflag.CFlag) {
438 if v.cached_msvc.valid == false {
439 verror('cannot find MSVC on this OS')
440 }
441 msvc := v.cached_msvc
442 trace_thirdparty_obj_files := 'trace_thirdparty_obj_files' in v.pref.compile_defines
443 path_without_o_postfix := path.all_before_last('.')
444 obj_path := v.msvc_thirdparty_obj_path(mod, path, '')
445 if os.exists(obj_path) {
446 // println('${obj_path} already built.')
447 return
448 }
449 if trace_thirdparty_obj_files {
450 println('${obj_path} not found, building it (with msvc)...')
451 }
452 cfile := if os.exists('${path_without_o_postfix}.c') {
453 '${path_without_o_postfix}.c'
454 } else {
455 '${path_without_o_postfix}.cpp'
456 }
457 flags := v.msvc_string_flags(moduleflags)
458 inc_dirs := flags.inc_paths.join(' ')
459 defines := flags.defines.join(' ')
460
461 mut oargs := []string{}
462 env_cflags := os.getenv('CFLAGS').replace('\r', ' ').replace('\n', ' ')
463 mut all_cflags := '${env_cflags} ${v.pref.cflags}'
464 if all_cflags != ' ' {
465 oargs << all_cflags
466 }
467 oargs << '/nologo' // NOTE: /NOLOGO is explicitly not recognised!
468 oargs << '/volatile:ms'
469
470 if v.pref.is_prod {
471 if !v.pref.no_prod_options {
472 oargs << '/O2'
473 }
474 }
475 if v.pref.is_debug {
476 oargs << '/MDd'
477 oargs << '/D_DEBUG'
478 } else {
479 oargs << '/MD'
480 oargs << '/DNDEBUG'
481 oargs << '/DNO_DEBUGGING'
482 }
483 oargs << defines
484 oargs << msvc.include_paths()
485 oargs << inc_dirs
486 oargs << '/c "${cfile}"'
487 oargs << '/Fo"${obj_path}"'
488 env_ldflags := os.getenv('LDFLAGS').replace('\r', ' ').replace('\n', ' ')
489 mut all_ldflags := '${env_ldflags} ${v.pref.ldflags}'
490 if all_ldflags != '' {
491 oargs << all_ldflags
492 }
493 v.dump_c_options(oargs)
494 str_oargs := oargs.join(' ')
495 cmd := '"${msvc.full_cl_exe_path}" ${str_oargs}'
496 // Note: the quotes above ARE balanced.
497 if trace_thirdparty_obj_files {
498 println('>>> build_thirdparty_obj_file_with_msvc cmd: ${cmd}')
499 }
500 // Note, that building object files with msvc can fail with permission denied errors,
501 // when the final .obj file, is locked by another msvc process for writing, or linker errors.
502 // Instead of failing, just retry several times in this case.
503 mut res := os.Result{}
504 mut i := 0
505 for i = 0; i < thirdparty_obj_build_max_retries; i++ {
506 res = os.execute(cmd)
507 if res.exit_code == 0 {
508 break
509 }
510 if !(res.output.contains('Permission denied') || res.output.contains('cannot open file')) {
511 break
512 }
513 eprintln('---------------------------------------------------------------------')
514 eprintln(' msvc: failed to build a thirdparty object, try: ${i}/${thirdparty_obj_build_max_retries}')
515 eprintln(' cmd: ${cmd}')
516 eprintln(' output:')
517 eprintln(res.output)
518 eprintln('---------------------------------------------------------------------')
519 time.sleep(thirdparty_obj_build_retry_delay)
520 }
521 if res.exit_code != 0 {
522 verror('msvc: failed to build a thirdparty object after ${i}/${thirdparty_obj_build_max_retries} retries
523 cmd:\n${cmd}
524 result:\n${res.output}')
525 }
526 if trace_thirdparty_obj_files {
527 println(res.output)
528 }
529}
530
531const thirdparty_obj_build_max_retries = 5
532const thirdparty_obj_build_retry_delay = 200 * time.millisecond
533
534struct MsvcStringFlags {
535mut:
536 real_libs []string
537 inc_paths []string
538 lib_paths []string
539 defines []string
540 other_flags []string
541}
542
543fn strip_quotes(value string) string {
544 if value.len >= 2 && value[0] == `"` && value[value.len - 1] == `"` {
545 return value[1..value.len - 1]
546 }
547 return value
548}
549
550fn apply_gnu_flag_to_msvc(value string, mut inc_paths []string, mut lib_paths []string, mut real_libs []string) bool {
551 v := strip_quotes(value)
552 if v.starts_with('-I') && v.len > 2 {
553 path := v[2..]
554 inc_paths << '/I"${os.real_path(path)}"'
555 return true
556 }
557 if v.starts_with('-L') && v.len > 2 {
558 path := v[2..]
559 lib_paths << path
560 lib_paths << path + os.path_separator + 'msvc'
561 return true
562 }
563 if v.starts_with('-l') && v.len > 2 {
564 mut lib := v[2..]
565 if lib.starts_with(':') && lib.len > 1 {
566 lib = lib[1..]
567 }
568 if !lib.ends_with('.lib') {
569 lib += '.lib'
570 }
571 real_libs << lib
572 return true
573 }
574 if v.starts_with('-Wl,') {
575 mut consumed := false
576 for part in v[4..].split(',') {
577 if apply_gnu_flag_to_msvc(part, mut inc_paths, mut lib_paths, mut real_libs) {
578 consumed = true
579 }
580 }
581 return consumed
582 }
583 return false
584}
585
586fn split_and_apply_gnu_flags(value string, mut inc_paths []string, mut lib_paths []string, mut real_libs []string) (bool, string) {
587 if !value.contains(' ') {
588 if apply_gnu_flag_to_msvc(value, mut inc_paths, mut lib_paths, mut real_libs) {
589 return true, ''
590 }
591 return false, value
592 }
593 parts := split_quoted_flags(value)
594 mut consumed := false
595 mut leftovers := []string{}
596 for part in parts {
597 if part == '' {
598 continue
599 }
600 if apply_gnu_flag_to_msvc(part, mut inc_paths, mut lib_paths, mut real_libs) {
601 consumed = true
602 } else {
603 leftovers << strip_quotes(part)
604 }
605 }
606 if consumed {
607 return true, leftovers.join(' ')
608 }
609 return false, value
610}
611
612fn split_quoted_flags(value string) []string {
613 mut parts := []string{}
614 mut buf := []u8{}
615 mut in_quote := false
616 for ch in value {
617 if ch == `"` {
618 in_quote = !in_quote
619 continue
620 }
621 if !in_quote && ch == ` ` {
622 if buf.len > 0 {
623 parts << buf.bytestr()
624 buf = []u8{}
625 }
626 continue
627 }
628 buf << ch
629 }
630 if buf.len > 0 {
631 parts << buf.bytestr()
632 }
633 return parts
634}
635
636pub fn (mut v Builder) msvc_string_flags(cflags []cflag.CFlag) MsvcStringFlags {
637 mut real_libs := []string{}
638 mut inc_paths := []string{}
639 mut lib_paths := []string{}
640 mut defines := []string{}
641 mut other_flags := []string{}
642 for flag in cflags {
643 if flag.name == '' {
644 consumed, leftover := split_and_apply_gnu_flags(flag.value, mut inc_paths, mut
645 lib_paths, mut real_libs)
646 if consumed {
647 if leftover != '' {
648 other_flags << strip_quotes(leftover)
649 }
650 continue
651 }
652 }
653 // println('fl: ${flag.name} | flag arg: ${flag.value}')
654 // We need to see if the flag contains -l
655 // -l isnt recognised and these libs will be passed straight to the linker
656 // by the compiler
657 if flag.name == '-l' {
658 if flag.value.ends_with('.dll') {
659 verror('MSVC cannot link against a dll (`#flag -l ${flag.value}`)')
660 }
661 // MSVC has no method of linking against a .dll
662 // TODO: we should look for .defs aswell
663 parts := split_quoted_flags(flag.value)
664 if parts.len > 0 {
665 mut lib := strip_quotes(parts[0])
666 if lib.starts_with(':') && lib.len > 1 {
667 lib = lib[1..]
668 }
669 if !lib.ends_with('.lib') {
670 lib += '.lib'
671 }
672 real_libs << lib
673 if parts.len > 1 {
674 _, leftover := split_and_apply_gnu_flags(parts[1..].join(' '), mut inc_paths, mut
675 lib_paths, mut real_libs)
676 if leftover != '' {
677 other_flags << strip_quotes(leftover)
678 }
679 }
680 }
681 } else if flag.name == '-I' {
682 should_split := flag.value.contains(' -I') || flag.value.contains(' -L')
683 || flag.value.contains(' -l') || flag.value.contains(' -Wl,')
684 || flag.value.contains(' "-I') || flag.value.contains(' "-L')
685 || flag.value.contains(' "-l') || flag.value.contains(' "-Wl,')
686 || flag.value.contains('\t-I') || flag.value.contains('\t-L')
687 || flag.value.contains('\t-l') || flag.value.contains('\t-Wl,')
688 || flag.value.contains('\t"-I') || flag.value.contains('\t"-L')
689 || flag.value.contains('\t"-l') || flag.value.contains('\t"-Wl,')
690 if !should_split {
691 inc_paths << '/I"${os.real_path(flag.value)}"'
692 } else {
693 parts := split_quoted_flags(flag.value)
694 if parts.len == 0 {
695 continue
696 }
697 for part in parts {
698 if part == '' {
699 continue
700 }
701 if apply_gnu_flag_to_msvc(part, mut inc_paths, mut lib_paths, mut real_libs) {
702 continue
703 }
704 if !part.starts_with('-') {
705 inc_paths << '/I"${os.real_path(strip_quotes(part))}"'
706 } else {
707 other_flags << strip_quotes(part)
708 }
709 }
710 }
711 } else if flag.name == '-D' {
712 defines << '/D${flag.value}'
713 } else if flag.name == '-L' {
714 should_split := flag.value.contains(' -I') || flag.value.contains(' -L')
715 || flag.value.contains(' -l') || flag.value.contains(' -Wl,')
716 || flag.value.contains(' "-I') || flag.value.contains(' "-L')
717 || flag.value.contains(' "-l') || flag.value.contains(' "-Wl,')
718 || flag.value.contains('\t-I') || flag.value.contains('\t-L')
719 || flag.value.contains('\t-l') || flag.value.contains('\t-Wl,')
720 || flag.value.contains('\t"-I') || flag.value.contains('\t"-L')
721 || flag.value.contains('\t"-l') || flag.value.contains('\t"-Wl,')
722 if !should_split {
723 lib_paths << flag.value
724 lib_paths << flag.value + os.path_separator + 'msvc'
725 } else {
726 parts := split_quoted_flags(flag.value)
727 if parts.len == 0 {
728 continue
729 }
730 for part in parts {
731 if part == '' {
732 continue
733 }
734 if apply_gnu_flag_to_msvc(part, mut inc_paths, mut lib_paths, mut real_libs) {
735 continue
736 }
737 if !part.starts_with('-') {
738 lib_paths << part
739 lib_paths << part + os.path_separator + 'msvc'
740 } else {
741 other_flags << strip_quotes(part)
742 }
743 }
744 }
745 // The above allows putting msvc specific .lib files in a subfolder msvc/ ,
746 // where gcc will NOT find them, but cl will do...
747 // Note: gcc is smart enough to not need .lib files at all in most cases, the .dll is enough.
748 // When both a msvc .lib file and .dll file are present in the same folder,
749 // as for example for glfw3, compilation with gcc would fail.
750 } else if flag.value.ends_with('.o') || flag.value.ends_with('.obj') {
751 other_flags << '"${v.msvc_thirdparty_obj_path(flag.mod, flag.value, flag.cached)}"'
752 } else if flag.value.starts_with('-D') {
753 defines << '/D${flag.value[2..]}'
754 } else {
755 consumed, leftover := split_and_apply_gnu_flags(flag.value, mut inc_paths, mut
756 lib_paths, mut real_libs)
757 if consumed {
758 if leftover != '' {
759 other_flags << strip_quotes(leftover)
760 }
761 continue
762 }
763 other_flags << strip_quotes(flag.value)
764 }
765 }
766 mut lpaths := []string{}
767 for l in lib_paths {
768 lpaths << '/LIBPATH:"${os.real_path(l)}"'
769 }
770 // Drop only stray quoted gcc-style -I/-L/-l tokens that leak into MSVC args.
771 mut filtered_other_flags := []string{cap: other_flags.len}
772 for of in other_flags {
773 ofs := of.trim_space()
774 if ofs.len >= 3 && ofs[0] == `"` && ofs[1] == `-` && ofs[2] in [`I`, `L`, `l`] {
775 continue
776 }
777 filtered_other_flags << of
778 }
779 other_flags = filtered_other_flags.clone()
780 return MsvcStringFlags{
781 real_libs: real_libs
782 inc_paths: inc_paths
783 lib_paths: lpaths
784 defines: defines
785 other_flags: other_flags
786 }
787}
788
789fn (r MsvcResult) include_paths() []string {
790 mut res := []string{cap: 4}
791 if r.ucrt_include_path != '' {
792 res << '-I "${r.ucrt_include_path}"'
793 }
794 if r.vs_include_path != '' {
795 res << '-I "${r.vs_include_path}"'
796 }
797 if r.um_include_path != '' {
798 res << '-I "${r.um_include_path}"'
799 }
800 if r.shared_include_path != '' {
801 res << '-I "${r.shared_include_path}"'
802 }
803 return res
804}
805
806fn (r MsvcResult) library_paths() []string {
807 mut res := []string{cap: 3}
808 if r.ucrt_lib_path != '' {
809 res << '/LIBPATH:"${r.ucrt_lib_path}"'
810 }
811 if r.um_lib_path != '' {
812 res << '/LIBPATH:"${r.um_lib_path}"'
813 }
814 if r.vs_lib_path != '' {
815 res << '/LIBPATH:"${r.vs_lib_path}"'
816 }
817 return res
818}
819