vxx2 / vlib / v / builder / msvc_windows.v
828 lines · 792 sloc · 25.24 KB · 2085cbb45161b78712c3214add56edbb26aa7713
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 cfile := if os.exists('${path_without_o_postfix}.c') {
446 '${path_without_o_postfix}.c'
447 } else {
448 '${path_without_o_postfix}.cpp'
449 }
450 if os.exists(obj_path) {
451 // Reuse the cached object only when it is newer than its source and
452 // every header in the thirdparty module (config headers under include/
453 // included; see thirdparty_deps_mtime). Without this, a config/header
454 // change silently keeps a stale `.obj` — the long-standing `-cc msvc`
455 // "delete library/*.obj by hand" footgun. Source-less bundled objects
456 // (no sibling .c/.cpp) keep the previous reuse-if-present behavior.
457 src_exists := os.exists('${path_without_o_postfix}.c')
458 || os.exists('${path_without_o_postfix}.cpp')
459 if !src_exists || os.file_last_mod_unix(obj_path) >= v.thirdparty_deps_mtime(cfile) {
460 // println('${obj_path} already built.')
461 return
462 }
463 }
464 if trace_thirdparty_obj_files {
465 println('${obj_path} not found or stale, building it (with msvc)...')
466 }
467 flags := v.msvc_string_flags(moduleflags)
468 inc_dirs := flags.inc_paths.join(' ')
469 defines := flags.defines.join(' ')
470
471 mut oargs := []string{}
472 env_cflags := os.getenv('CFLAGS').replace('\r', ' ').replace('\n', ' ')
473 mut all_cflags := '${env_cflags} ${v.pref.cflags}'
474 if all_cflags != ' ' {
475 oargs << all_cflags
476 }
477 oargs << '/nologo' // NOTE: /NOLOGO is explicitly not recognised!
478 oargs << '/volatile:ms'
479
480 if v.pref.is_prod {
481 if !v.pref.no_prod_options {
482 oargs << '/O2'
483 }
484 }
485 if v.pref.is_debug {
486 oargs << '/MDd'
487 oargs << '/D_DEBUG'
488 } else {
489 oargs << '/MD'
490 oargs << '/DNDEBUG'
491 oargs << '/DNO_DEBUGGING'
492 }
493 oargs << defines
494 oargs << msvc.include_paths()
495 oargs << inc_dirs
496 oargs << '/c "${cfile}"'
497 oargs << '/Fo"${obj_path}"'
498 env_ldflags := os.getenv('LDFLAGS').replace('\r', ' ').replace('\n', ' ')
499 mut all_ldflags := '${env_ldflags} ${v.pref.ldflags}'
500 if all_ldflags != '' {
501 oargs << all_ldflags
502 }
503 v.dump_c_options(oargs)
504 str_oargs := oargs.join(' ')
505 cmd := '"${msvc.full_cl_exe_path}" ${str_oargs}'
506 // Note: the quotes above ARE balanced.
507 if trace_thirdparty_obj_files {
508 println('>>> build_thirdparty_obj_file_with_msvc cmd: ${cmd}')
509 }
510 // Note, that building object files with msvc can fail with permission denied errors,
511 // when the final .obj file, is locked by another msvc process for writing, or linker errors.
512 // Instead of failing, just retry several times in this case.
513 mut res := os.Result{}
514 mut i := 0
515 for i = 0; i < thirdparty_obj_build_max_retries; i++ {
516 res = os.execute(cmd)
517 if res.exit_code == 0 {
518 break
519 }
520 if !(res.output.contains('Permission denied') || res.output.contains('cannot open file')) {
521 break
522 }
523 eprintln('---------------------------------------------------------------------')
524 eprintln(' msvc: failed to build a thirdparty object, try: ${i}/${thirdparty_obj_build_max_retries}')
525 eprintln(' cmd: ${cmd}')
526 eprintln(' output:')
527 eprintln(res.output)
528 eprintln('---------------------------------------------------------------------')
529 time.sleep(thirdparty_obj_build_retry_delay)
530 }
531 if res.exit_code != 0 {
532 verror('msvc: failed to build a thirdparty object after ${i}/${thirdparty_obj_build_max_retries} retries
533 cmd:\n${cmd}
534 result:\n${res.output}')
535 }
536 if trace_thirdparty_obj_files {
537 println(res.output)
538 }
539}
540
541const thirdparty_obj_build_max_retries = 5
542const thirdparty_obj_build_retry_delay = 200 * time.millisecond
543
544struct MsvcStringFlags {
545mut:
546 real_libs []string
547 inc_paths []string
548 lib_paths []string
549 defines []string
550 other_flags []string
551}
552
553fn strip_quotes(value string) string {
554 if value.len >= 2 && value[0] == `"` && value[value.len - 1] == `"` {
555 return value[1..value.len - 1]
556 }
557 return value
558}
559
560fn apply_gnu_flag_to_msvc(value string, mut inc_paths []string, mut lib_paths []string, mut real_libs []string) bool {
561 v := strip_quotes(value)
562 if v.starts_with('-I') && v.len > 2 {
563 path := v[2..]
564 inc_paths << '/I"${os.real_path(path)}"'
565 return true
566 }
567 if v.starts_with('-L') && v.len > 2 {
568 path := v[2..]
569 lib_paths << path
570 lib_paths << path + os.path_separator + 'msvc'
571 return true
572 }
573 if v.starts_with('-l') && v.len > 2 {
574 mut lib := v[2..]
575 if lib.starts_with(':') && lib.len > 1 {
576 lib = lib[1..]
577 }
578 if !lib.ends_with('.lib') {
579 lib += '.lib'
580 }
581 real_libs << lib
582 return true
583 }
584 if v.starts_with('-Wl,') {
585 mut consumed := false
586 for part in v[4..].split(',') {
587 if apply_gnu_flag_to_msvc(part, mut inc_paths, mut lib_paths, mut real_libs) {
588 consumed = true
589 }
590 }
591 return consumed
592 }
593 return false
594}
595
596fn split_and_apply_gnu_flags(value string, mut inc_paths []string, mut lib_paths []string, mut real_libs []string) (bool, string) {
597 if !value.contains(' ') {
598 if apply_gnu_flag_to_msvc(value, mut inc_paths, mut lib_paths, mut real_libs) {
599 return true, ''
600 }
601 return false, value
602 }
603 parts := split_quoted_flags(value)
604 mut consumed := false
605 mut leftovers := []string{}
606 for part in parts {
607 if part == '' {
608 continue
609 }
610 if apply_gnu_flag_to_msvc(part, mut inc_paths, mut lib_paths, mut real_libs) {
611 consumed = true
612 } else {
613 leftovers << strip_quotes(part)
614 }
615 }
616 if consumed {
617 return true, leftovers.join(' ')
618 }
619 return false, value
620}
621
622fn split_quoted_flags(value string) []string {
623 mut parts := []string{}
624 mut buf := []u8{}
625 mut in_quote := false
626 for ch in value {
627 if ch == `"` {
628 in_quote = !in_quote
629 continue
630 }
631 if !in_quote && ch == ` ` {
632 if buf.len > 0 {
633 parts << buf.bytestr()
634 buf = []u8{}
635 }
636 continue
637 }
638 buf << ch
639 }
640 if buf.len > 0 {
641 parts << buf.bytestr()
642 }
643 return parts
644}
645
646pub fn (mut v Builder) msvc_string_flags(cflags []cflag.CFlag) MsvcStringFlags {
647 mut real_libs := []string{}
648 mut inc_paths := []string{}
649 mut lib_paths := []string{}
650 mut defines := []string{}
651 mut other_flags := []string{}
652 for flag in cflags {
653 if flag.name == '' {
654 consumed, leftover := split_and_apply_gnu_flags(flag.value, mut inc_paths, mut
655 lib_paths, mut real_libs)
656 if consumed {
657 if leftover != '' {
658 other_flags << strip_quotes(leftover)
659 }
660 continue
661 }
662 }
663 // println('fl: ${flag.name} | flag arg: ${flag.value}')
664 // We need to see if the flag contains -l
665 // -l isnt recognised and these libs will be passed straight to the linker
666 // by the compiler
667 if flag.name == '-l' {
668 if flag.value.ends_with('.dll') {
669 verror('MSVC cannot link against a dll (`#flag -l ${flag.value}`)')
670 }
671 // MSVC has no method of linking against a .dll
672 // TODO: we should look for .defs aswell
673 parts := split_quoted_flags(flag.value)
674 if parts.len > 0 {
675 mut lib := strip_quotes(parts[0])
676 if lib.starts_with(':') && lib.len > 1 {
677 lib = lib[1..]
678 }
679 if !lib.ends_with('.lib') {
680 lib += '.lib'
681 }
682 real_libs << lib
683 if parts.len > 1 {
684 _, leftover := split_and_apply_gnu_flags(parts[1..].join(' '), mut inc_paths, mut
685 lib_paths, mut real_libs)
686 if leftover != '' {
687 other_flags << strip_quotes(leftover)
688 }
689 }
690 }
691 } else if flag.name == '-I' {
692 should_split := flag.value.contains(' -I') || flag.value.contains(' -L')
693 || flag.value.contains(' -l') || flag.value.contains(' -Wl,')
694 || flag.value.contains(' "-I') || flag.value.contains(' "-L')
695 || flag.value.contains(' "-l') || flag.value.contains(' "-Wl,')
696 || flag.value.contains('\t-I') || flag.value.contains('\t-L')
697 || flag.value.contains('\t-l') || flag.value.contains('\t-Wl,')
698 || flag.value.contains('\t"-I') || flag.value.contains('\t"-L')
699 || flag.value.contains('\t"-l') || flag.value.contains('\t"-Wl,')
700 if !should_split {
701 inc_paths << '/I"${os.real_path(flag.value)}"'
702 } else {
703 parts := split_quoted_flags(flag.value)
704 if parts.len == 0 {
705 continue
706 }
707 for part in parts {
708 if part == '' {
709 continue
710 }
711 if apply_gnu_flag_to_msvc(part, mut inc_paths, mut lib_paths, mut real_libs) {
712 continue
713 }
714 if !part.starts_with('-') {
715 inc_paths << '/I"${os.real_path(strip_quotes(part))}"'
716 } else {
717 other_flags << strip_quotes(part)
718 }
719 }
720 }
721 } else if flag.name == '-D' {
722 defines << '/D${flag.value}'
723 } else if flag.name == '-L' {
724 should_split := flag.value.contains(' -I') || flag.value.contains(' -L')
725 || flag.value.contains(' -l') || flag.value.contains(' -Wl,')
726 || flag.value.contains(' "-I') || flag.value.contains(' "-L')
727 || flag.value.contains(' "-l') || flag.value.contains(' "-Wl,')
728 || flag.value.contains('\t-I') || flag.value.contains('\t-L')
729 || flag.value.contains('\t-l') || flag.value.contains('\t-Wl,')
730 || flag.value.contains('\t"-I') || flag.value.contains('\t"-L')
731 || flag.value.contains('\t"-l') || flag.value.contains('\t"-Wl,')
732 if !should_split {
733 lib_paths << flag.value
734 lib_paths << flag.value + os.path_separator + 'msvc'
735 } else {
736 parts := split_quoted_flags(flag.value)
737 if parts.len == 0 {
738 continue
739 }
740 for part in parts {
741 if part == '' {
742 continue
743 }
744 if apply_gnu_flag_to_msvc(part, mut inc_paths, mut lib_paths, mut real_libs) {
745 continue
746 }
747 if !part.starts_with('-') {
748 lib_paths << part
749 lib_paths << part + os.path_separator + 'msvc'
750 } else {
751 other_flags << strip_quotes(part)
752 }
753 }
754 }
755 // The above allows putting msvc specific .lib files in a subfolder msvc/ ,
756 // where gcc will NOT find them, but cl will do...
757 // Note: gcc is smart enough to not need .lib files at all in most cases, the .dll is enough.
758 // When both a msvc .lib file and .dll file are present in the same folder,
759 // as for example for glfw3, compilation with gcc would fail.
760 } else if flag.value.ends_with('.o') || flag.value.ends_with('.obj') {
761 other_flags << '"${v.msvc_thirdparty_obj_path(flag.mod, flag.value, flag.cached)}"'
762 } else if flag.value.starts_with('-D') {
763 defines << '/D${flag.value[2..]}'
764 } else {
765 consumed, leftover := split_and_apply_gnu_flags(flag.value, mut inc_paths, mut
766 lib_paths, mut real_libs)
767 if consumed {
768 if leftover != '' {
769 other_flags << strip_quotes(leftover)
770 }
771 continue
772 }
773 other_flags << strip_quotes(flag.value)
774 }
775 }
776 mut lpaths := []string{}
777 for l in lib_paths {
778 lpaths << '/LIBPATH:"${os.real_path(l)}"'
779 }
780 // Drop only stray quoted gcc-style -I/-L/-l tokens that leak into MSVC args.
781 mut filtered_other_flags := []string{cap: other_flags.len}
782 for of in other_flags {
783 ofs := of.trim_space()
784 if ofs.len >= 3 && ofs[0] == `"` && ofs[1] == `-` && ofs[2] in [`I`, `L`, `l`] {
785 continue
786 }
787 filtered_other_flags << of
788 }
789 other_flags = filtered_other_flags.clone()
790 return MsvcStringFlags{
791 real_libs: real_libs
792 inc_paths: inc_paths
793 lib_paths: lpaths
794 defines: defines
795 other_flags: other_flags
796 }
797}
798
799fn (r MsvcResult) include_paths() []string {
800 mut res := []string{cap: 4}
801 if r.ucrt_include_path != '' {
802 res << '-I "${r.ucrt_include_path}"'
803 }
804 if r.vs_include_path != '' {
805 res << '-I "${r.vs_include_path}"'
806 }
807 if r.um_include_path != '' {
808 res << '-I "${r.um_include_path}"'
809 }
810 if r.shared_include_path != '' {
811 res << '-I "${r.shared_include_path}"'
812 }
813 return res
814}
815
816fn (r MsvcResult) library_paths() []string {
817 mut res := []string{cap: 3}
818 if r.ucrt_lib_path != '' {
819 res << '/LIBPATH:"${r.ucrt_lib_path}"'
820 }
821 if r.um_lib_path != '' {
822 res << '/LIBPATH:"${r.um_lib_path}"'
823 }
824 if r.vs_lib_path != '' {
825 res << '/LIBPATH:"${r.vs_lib_path}"'
826 }
827 return res
828}
829