module builder import os import time import v.util import v.cflag #flag windows -l shell32 #flag windows -l dbghelp #flag windows -l advapi32 // shell32 for RegOpenKeyExW etc // Mimics a HKEY type RegKey = voidptr // Taken from the windows SDK const hkey_local_machine = RegKey(0x80000002) const key_query_value = (0x0001) const key_wow64_32key = (0x0200) const key_enumerate_sub_keys = (0x0008) // Given a root key look for one of the subkeys in 'versions' and get the path fn find_windows_kit_internal(key RegKey, versions []string) !string { $if windows { unsafe { for version in versions { required_bytes := u32(0) // TODO: mut result := C.RegQueryValueEx(key, version.to_wide(), 0, 0, 0, voidptr(&required_bytes)) length := required_bytes / 2 if result != 0 { continue } alloc_length := (required_bytes + 2) mut value := &u16(malloc_noscan(int(alloc_length))) if value == nil { continue } // else { } result2 := C.RegQueryValueEx(key, version.to_wide(), 0, 0, voidptr(value), voidptr(&alloc_length)) if result2 != 0 { continue } // We might need to manually null terminate this thing // So just make sure that we do that if value[length - 1] != u16(0) { value[length] = u16(0) } res := string_from_wide(value) return res } } } return error('windows kit not found') } struct WindowsKit { um_lib_path string ucrt_lib_path string um_include_path string ucrt_include_path string shared_include_path string } // Try and find the root key for installed windows kits fn find_windows_kit_root(target_arch string) !WindowsKit { $if windows { wkroot := find_windows_kit_root_by_reg(target_arch) or { if wkroot := find_windows_kit_root_by_env(target_arch) { return wkroot } return err } return wkroot } $else { return error('Host OS does not support finding a windows kit') } } // Try to find the root key for installed windows kits from registry fn find_windows_kit_root_by_reg(target_arch string) !WindowsKit { $if windows { root_key := RegKey(0) path := 'SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots' rc := C.RegOpenKeyEx(hkey_local_machine, path.to_wide(), 0, key_query_value | key_wow64_32key | key_enumerate_sub_keys, voidptr(&root_key)) if rc != 0 { return error('Unable to open root key') } // Try and find win10 kit kit_root := find_windows_kit_internal(root_key, ['KitsRoot10', 'KitsRoot81']) or { C.RegCloseKey(root_key) return error('Unable to find a windows kit') } C.RegCloseKey(root_key) return new_windows_kit(kit_root, target_arch) } $else { return error('Host OS does not support finding a windows kit') } } fn new_windows_kit(kit_root string, target_arch string) !WindowsKit { kit_lib := kit_root + 'Lib' files := os.ls(kit_lib)! mut highest_path := '' mut highest_int := 0 for f in files { no_dot := f.replace('.', '') v_int := no_dot.int() if v_int > highest_int { highest_int = v_int highest_path = f } } kit_lib_highest := kit_lib + '\\${highest_path}' kit_include_highest := kit_lib_highest.replace('Lib', 'Include') return WindowsKit{ um_lib_path: kit_lib_highest + '\\um\\${target_arch}' ucrt_lib_path: kit_lib_highest + '\\ucrt\\${target_arch}' um_include_path: kit_include_highest + '\\um' ucrt_include_path: kit_include_highest + '\\ucrt' shared_include_path: kit_include_highest + '\\shared' } } fn find_windows_kit_root_by_env(target_arch string) !WindowsKit { kit_root := os.getenv('WindowsSdkDir') if kit_root == '' { return error('empty WindowsSdkDir') } return new_windows_kit(kit_root, target_arch) } struct VsInstallation { include_path string lib_path string exe_path string } fn find_vs(vswhere_dir string, host_arch string, target_arch string) !VsInstallation { $if windows { vsinst := find_vs_by_reg(vswhere_dir, host_arch, target_arch) or { if vsinst := find_vs_by_env(host_arch, target_arch) { return vsinst } return err } return vsinst } $else { return error('Host OS does not support finding a Visual Studio installation') } } fn find_vs_by_reg(vswhere_dir string, host_arch string, target_arch string) !VsInstallation { $if windows { // Emily: // VSWhere is guaranteed to be installed at this location now // If its not there then end user needs to update their visual studio // installation! res := os.execute('"${vswhere_dir}\\Microsoft Visual Studio\\Installer\\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath') // println('res: "${res}"') if res.exit_code != 0 { return error_with_code(res.output, res.exit_code) } res_output := res.output.trim_space() version := os.read_file('${res_output}\\VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt') or { // println('Unable to find msvc version') return error('Unable to find vs installation') } // println('version: ${version}') v := version.trim_space() lib_path := '${res_output}\\VC\\Tools\\MSVC\\${v}\\lib\\${target_arch}' include_path := '${res_output}\\VC\\Tools\\MSVC\\${v}\\include' if os.exists('${lib_path}\\vcruntime.lib') { p := '${res_output}\\VC\\Tools\\MSVC\\${v}\\bin\\Host${host_arch}\\${target_arch}' // println('${lib_path} ${include_path}') return VsInstallation{ exe_path: p lib_path: lib_path include_path: include_path } } println('Unable to find vs installation (attempted to use lib path "${lib_path}")') return error('Unable to find vs exe folder') } $else { return error('Host OS does not support finding a Visual Studio installation') } } fn find_vs_by_env(host_arch string, target_arch string) !VsInstallation { vs_dir := os.getenv('VSINSTALLDIR') if vs_dir == '' { return error('empty VSINSTALLDIR') } vc_tools_dir := os.getenv('VCToolsInstallDir') if vc_tools_dir == '' { return error('empty VCToolsInstallDir') } bin_dir := '${vc_tools_dir}bin\\Host${host_arch}\\${target_arch}' lib_path := '${vc_tools_dir}lib\\${target_arch}' include_path := '${vc_tools_dir}include' return VsInstallation{ exe_path: bin_dir lib_path: lib_path include_path: include_path } } fn find_msvc(m64_target bool) !MsvcResult { $if windows { processor_architecture := os.getenv('PROCESSOR_ARCHITECTURE') vswhere_dir := if processor_architecture == 'x86' { '%ProgramFiles%' } else { '%ProgramFiles(x86)%' } host_arch := if processor_architecture == 'x86' { 'X86' } else { 'X64' } target_arch := if !m64_target { 'X86' } else { 'X64' } wk := find_windows_kit_root(target_arch) or { return error('Unable to find windows sdk') } vs := find_vs(vswhere_dir, host_arch, target_arch) or { return error('Unable to find visual studio') } return MsvcResult{ full_cl_exe_path: os.real_path(vs.exe_path + os.path_separator + 'cl.exe') exe_path: vs.exe_path um_lib_path: wk.um_lib_path ucrt_lib_path: wk.ucrt_lib_path vs_lib_path: vs.lib_path um_include_path: wk.um_include_path ucrt_include_path: wk.ucrt_include_path vs_include_path: vs.include_path shared_include_path: wk.shared_include_path valid: true } } $else { // This hack allows to at least see the generated .c file with `-os windows -cc msvc -o x.c` // Please do not remove it, unless you also check that the above continues to work. return MsvcResult{ full_cl_exe_path: '/usr/bin/true' valid: true } } } pub fn (mut v Builder) cc_msvc() { r := v.cached_msvc if r.valid == false { verror('cannot find MSVC on this OS') } out_name_pdb := os.real_path(v.out_name_c + '.pdb') out_name_cmd_line := os.real_path(v.out_name_c + '.rsp') // testdll.01JNX9W7JAV4FKMZ6KDXT67QYV.tmp.so.c app_dir_out_name_c := (v.pref.out_name.all_before_last('\\') + '\\' + v.pref.out_name_c.all_after_last('\\')).all_before_last('.') // testdll.dll app_dir_out_name := if v.pref.out_name.ends_with('.dll') || v.pref.out_name.ends_with('.exe') { v.pref.out_name[0..v.pref.out_name.len - 4] } else { v.pref.out_name } mut a := []string{} env_cflags := os.getenv('CFLAGS') mut all_cflags := '${env_cflags} ${v.pref.cflags}' if all_cflags != ' ' { a << all_cflags } // Default arguments // `-w` no warnings // `/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 // `/volatile:ms` enables atomic volatile (gcc _Atomic) // `/F33554432` changes the stack size to 32MB, see https://docs.microsoft.com/en-us/cpp/build/reference/f-set-stack-size?view=msvc-170 // 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, // if there is an F: drive in the system (they map c: as /c/, d: as /d/ etc) a << ['-w', '/we4013', '/volatile:ms', '/F33554432'] if v.pref.is_prod && !v.pref.no_prod_options { a << '/O2' } if v.pref.is_debug { a << '/MDd' a << '/D_DEBUG' // /Zi generates a .pdb // /Fd sets the pdb file name (so its not just vc140 all the time) a << ['/Zi', '/Fd"${out_name_pdb}"'] } else { a << '/MD' a << '/DNDEBUG' a << '/DNO_DEBUGGING' if !v.ccoptions.debug_mode { v.pref.cleanup_files << out_name_pdb v.pref.cleanup_files << app_dir_out_name + '.pdb' } } if v.pref.is_shared { if !v.pref.out_name.ends_with('.dll') { v.pref.out_name += '.dll' } // Build dll a << '/LD' } else if !v.pref.out_name.ends_with('.exe') { v.pref.out_name += '.exe' } v.pref.out_name = os.real_path(v.pref.out_name) // alibs := []string{} // builtin.o os.o http.o etc if v.pref.build_mode == .build_module { // Compile only a << '/c' } if v.pref.sanitize { eprintln('Sanitize not supported on msvc.') } // The C file we are compiling // Use /Tc instead of /TC, otherwise .lib/.obj linker inputs are also // treated as C sources. a << '/Tc' + os.quoted_path(os.real_path(v.out_name_c)) if !v.ccoptions.debug_mode { v.pref.cleanup_files << os.real_path(v.out_name_c) } // Emily: // Not all of these are needed (but the compiler should discard them if they are not used) // these are the defaults used by msbuild and visual studio mut real_libs := ['kernel32.lib', 'user32.lib', 'advapi32.lib', 'ws2_32.lib'] sflags := v.msvc_string_flags(v.get_os_cflags()) real_libs << sflags.real_libs inc_paths := sflags.inc_paths lib_paths := sflags.lib_paths defines := sflags.defines other_flags := sflags.other_flags // Include the base paths a << r.include_paths() a << defines a << inc_paths a << other_flags // Libs are passed to cl.exe which passes them to the linker a << real_libs.join(' ') a << '/link' if v.pref.is_shared { // generate a .def for export function names, avoid function name mangle // must put after the /link flag! def_name := app_dir_out_name + '.def' a << '/DEF:' + os.quoted_path(def_name) if !v.ccoptions.debug_mode { v.pref.cleanup_files << def_name v.pref.cleanup_files << app_dir_out_name_c + '.exp' v.pref.cleanup_files << app_dir_out_name_c + '.lib' } } a << '/nologo' // NOTE: /NOLOGO is explicitly not recognised! a << '/OUT:${os.quoted_path(v.pref.out_name)}' if v.pref.is_livemain { a << '/IMPLIB:${os.quoted_path(v.pref.out_name[..v.pref.out_name.len - 4] + '.lib')}' } a << r.library_paths() if !all_cflags.contains('/DEBUG') { // only use /DEBUG, if the user *did not* provide its own: a << '/DEBUG:FULL' // required for prod builds to generate a PDB file } if v.pref.is_prod && !v.pref.no_prod_options { a << '/INCREMENTAL:NO' // Disable incremental linking a << '/OPT:REF' a << '/OPT:ICF' } a << lib_paths env_ldflags := os.getenv('LDFLAGS') if env_ldflags != '' { a << env_ldflags } if v.pref.ldflags != '' { a << v.pref.ldflags.trim_space() } // Remove stray quoted gcc-style -I/-L/-l tokens (e.g. "-I...") that can leak into MSVC args. mut filtered_args := []string{cap: a.len} for arg in a { mut s := arg.trim_space() if s.len >= 3 && s[0] == `"` && s[1] == `-` && s[2] in [`I`, `L`, `l`] { continue } filtered_args << arg } a = filtered_args.clone() v.dump_c_options(a) raw_args := a.join(' ') mut args := raw_args mut response_file := '' mut cmd := '"${r.full_cl_exe_path}" ${raw_args.replace('\n', ' ')}' if v.msvc_should_use_rsp(a) { args = '\xEF\xBB\xBF' + raw_args // write a BOM to indicate the utf8 encoding of the file // write args to a file so that we dont smash createprocess os.write_file(out_name_cmd_line, args) or { verror('Unable to write response file to "${out_name_cmd_line}"') } response_file = out_name_cmd_line cmd = '"${r.full_cl_exe_path}" "@${out_name_cmd_line}"' } if !v.ccoptions.debug_mode { if response_file != '' { v.pref.cleanup_files << out_name_cmd_line } v.pref.cleanup_files << app_dir_out_name_c + '.obj' v.pref.cleanup_files << app_dir_out_name + '.ilk' } // It is hard to see it at first, but the quotes above ARE balanced :-| ... // Also the double quotes at the start ARE needed. v.show_cc(cmd, response_file, args) if os.user_os() != 'windows' && !v.pref.out_name.ends_with('.c') { verror('cannot build with msvc on ${os.user_os()}') } util.timing_start('C msvc') res := os.execute(cmd) if res.exit_code != 0 { eprintln('================== ${c_compilation_error_title} (from msvc): ==============') eprintln(res.output) verror('msvc error') } util.timing_measure('C msvc') if v.pref.show_c_output { v.show_c_compiler_output(r.full_cl_exe_path, res) } else { v.post_process_c_compiler_output(r.full_cl_exe_path, res) } v.apply_windows_icon_to_executable() or { verror(err.msg()) } // println(res) // println('C OUTPUT:') } fn (mut v Builder) build_thirdparty_obj_file_with_msvc(mod string, path string, moduleflags []cflag.CFlag) { if v.cached_msvc.valid == false { verror('cannot find MSVC on this OS') } msvc := v.cached_msvc trace_thirdparty_obj_files := 'trace_thirdparty_obj_files' in v.pref.compile_defines path_without_o_postfix := path.all_before_last('.') obj_path := v.msvc_thirdparty_obj_path(mod, path, '') if os.exists(obj_path) { // println('${obj_path} already built.') return } if trace_thirdparty_obj_files { println('${obj_path} not found, building it (with msvc)...') } cfile := if os.exists('${path_without_o_postfix}.c') { '${path_without_o_postfix}.c' } else { '${path_without_o_postfix}.cpp' } flags := v.msvc_string_flags(moduleflags) inc_dirs := flags.inc_paths.join(' ') defines := flags.defines.join(' ') mut oargs := []string{} env_cflags := os.getenv('CFLAGS').replace('\r', ' ').replace('\n', ' ') mut all_cflags := '${env_cflags} ${v.pref.cflags}' if all_cflags != ' ' { oargs << all_cflags } oargs << '/nologo' // NOTE: /NOLOGO is explicitly not recognised! oargs << '/volatile:ms' if v.pref.is_prod { if !v.pref.no_prod_options { oargs << '/O2' } } if v.pref.is_debug { oargs << '/MDd' oargs << '/D_DEBUG' } else { oargs << '/MD' oargs << '/DNDEBUG' oargs << '/DNO_DEBUGGING' } oargs << defines oargs << msvc.include_paths() oargs << inc_dirs oargs << '/c "${cfile}"' oargs << '/Fo"${obj_path}"' env_ldflags := os.getenv('LDFLAGS').replace('\r', ' ').replace('\n', ' ') mut all_ldflags := '${env_ldflags} ${v.pref.ldflags}' if all_ldflags != '' { oargs << all_ldflags } v.dump_c_options(oargs) str_oargs := oargs.join(' ') cmd := '"${msvc.full_cl_exe_path}" ${str_oargs}' // Note: the quotes above ARE balanced. if trace_thirdparty_obj_files { println('>>> build_thirdparty_obj_file_with_msvc cmd: ${cmd}') } // Note, that building object files with msvc can fail with permission denied errors, // when the final .obj file, is locked by another msvc process for writing, or linker errors. // Instead of failing, just retry several times in this case. mut res := os.Result{} mut i := 0 for i = 0; i < thirdparty_obj_build_max_retries; i++ { res = os.execute(cmd) if res.exit_code == 0 { break } if !(res.output.contains('Permission denied') || res.output.contains('cannot open file')) { break } eprintln('---------------------------------------------------------------------') eprintln(' msvc: failed to build a thirdparty object, try: ${i}/${thirdparty_obj_build_max_retries}') eprintln(' cmd: ${cmd}') eprintln(' output:') eprintln(res.output) eprintln('---------------------------------------------------------------------') time.sleep(thirdparty_obj_build_retry_delay) } if res.exit_code != 0 { verror('msvc: failed to build a thirdparty object after ${i}/${thirdparty_obj_build_max_retries} retries cmd:\n${cmd} result:\n${res.output}') } if trace_thirdparty_obj_files { println(res.output) } } const thirdparty_obj_build_max_retries = 5 const thirdparty_obj_build_retry_delay = 200 * time.millisecond struct MsvcStringFlags { mut: real_libs []string inc_paths []string lib_paths []string defines []string other_flags []string } fn strip_quotes(value string) string { if value.len >= 2 && value[0] == `"` && value[value.len - 1] == `"` { return value[1..value.len - 1] } return value } fn apply_gnu_flag_to_msvc(value string, mut inc_paths []string, mut lib_paths []string, mut real_libs []string) bool { v := strip_quotes(value) if v.starts_with('-I') && v.len > 2 { path := v[2..] inc_paths << '/I"${os.real_path(path)}"' return true } if v.starts_with('-L') && v.len > 2 { path := v[2..] lib_paths << path lib_paths << path + os.path_separator + 'msvc' return true } if v.starts_with('-l') && v.len > 2 { mut lib := v[2..] if lib.starts_with(':') && lib.len > 1 { lib = lib[1..] } if !lib.ends_with('.lib') { lib += '.lib' } real_libs << lib return true } if v.starts_with('-Wl,') { mut consumed := false for part in v[4..].split(',') { if apply_gnu_flag_to_msvc(part, mut inc_paths, mut lib_paths, mut real_libs) { consumed = true } } return consumed } return false } fn split_and_apply_gnu_flags(value string, mut inc_paths []string, mut lib_paths []string, mut real_libs []string) (bool, string) { if !value.contains(' ') { if apply_gnu_flag_to_msvc(value, mut inc_paths, mut lib_paths, mut real_libs) { return true, '' } return false, value } parts := split_quoted_flags(value) mut consumed := false mut leftovers := []string{} for part in parts { if part == '' { continue } if apply_gnu_flag_to_msvc(part, mut inc_paths, mut lib_paths, mut real_libs) { consumed = true } else { leftovers << strip_quotes(part) } } if consumed { return true, leftovers.join(' ') } return false, value } fn split_quoted_flags(value string) []string { mut parts := []string{} mut buf := []u8{} mut in_quote := false for ch in value { if ch == `"` { in_quote = !in_quote continue } if !in_quote && ch == ` ` { if buf.len > 0 { parts << buf.bytestr() buf = []u8{} } continue } buf << ch } if buf.len > 0 { parts << buf.bytestr() } return parts } pub fn (mut v Builder) msvc_string_flags(cflags []cflag.CFlag) MsvcStringFlags { mut real_libs := []string{} mut inc_paths := []string{} mut lib_paths := []string{} mut defines := []string{} mut other_flags := []string{} for flag in cflags { if flag.name == '' { consumed, leftover := split_and_apply_gnu_flags(flag.value, mut inc_paths, mut lib_paths, mut real_libs) if consumed { if leftover != '' { other_flags << strip_quotes(leftover) } continue } } // println('fl: ${flag.name} | flag arg: ${flag.value}') // We need to see if the flag contains -l // -l isnt recognised and these libs will be passed straight to the linker // by the compiler if flag.name == '-l' { if flag.value.ends_with('.dll') { verror('MSVC cannot link against a dll (`#flag -l ${flag.value}`)') } // MSVC has no method of linking against a .dll // TODO: we should look for .defs aswell parts := split_quoted_flags(flag.value) if parts.len > 0 { mut lib := strip_quotes(parts[0]) if lib.starts_with(':') && lib.len > 1 { lib = lib[1..] } if !lib.ends_with('.lib') { lib += '.lib' } real_libs << lib if parts.len > 1 { _, leftover := split_and_apply_gnu_flags(parts[1..].join(' '), mut inc_paths, mut lib_paths, mut real_libs) if leftover != '' { other_flags << strip_quotes(leftover) } } } } else if flag.name == '-I' { should_split := flag.value.contains(' -I') || flag.value.contains(' -L') || flag.value.contains(' -l') || flag.value.contains(' -Wl,') || flag.value.contains(' "-I') || flag.value.contains(' "-L') || flag.value.contains(' "-l') || flag.value.contains(' "-Wl,') || flag.value.contains('\t-I') || flag.value.contains('\t-L') || flag.value.contains('\t-l') || flag.value.contains('\t-Wl,') || flag.value.contains('\t"-I') || flag.value.contains('\t"-L') || flag.value.contains('\t"-l') || flag.value.contains('\t"-Wl,') if !should_split { inc_paths << '/I"${os.real_path(flag.value)}"' } else { parts := split_quoted_flags(flag.value) if parts.len == 0 { continue } for part in parts { if part == '' { continue } if apply_gnu_flag_to_msvc(part, mut inc_paths, mut lib_paths, mut real_libs) { continue } if !part.starts_with('-') { inc_paths << '/I"${os.real_path(strip_quotes(part))}"' } else { other_flags << strip_quotes(part) } } } } else if flag.name == '-D' { defines << '/D${flag.value}' } else if flag.name == '-L' { should_split := flag.value.contains(' -I') || flag.value.contains(' -L') || flag.value.contains(' -l') || flag.value.contains(' -Wl,') || flag.value.contains(' "-I') || flag.value.contains(' "-L') || flag.value.contains(' "-l') || flag.value.contains(' "-Wl,') || flag.value.contains('\t-I') || flag.value.contains('\t-L') || flag.value.contains('\t-l') || flag.value.contains('\t-Wl,') || flag.value.contains('\t"-I') || flag.value.contains('\t"-L') || flag.value.contains('\t"-l') || flag.value.contains('\t"-Wl,') if !should_split { lib_paths << flag.value lib_paths << flag.value + os.path_separator + 'msvc' } else { parts := split_quoted_flags(flag.value) if parts.len == 0 { continue } for part in parts { if part == '' { continue } if apply_gnu_flag_to_msvc(part, mut inc_paths, mut lib_paths, mut real_libs) { continue } if !part.starts_with('-') { lib_paths << part lib_paths << part + os.path_separator + 'msvc' } else { other_flags << strip_quotes(part) } } } // The above allows putting msvc specific .lib files in a subfolder msvc/ , // where gcc will NOT find them, but cl will do... // Note: gcc is smart enough to not need .lib files at all in most cases, the .dll is enough. // When both a msvc .lib file and .dll file are present in the same folder, // as for example for glfw3, compilation with gcc would fail. } else if flag.value.ends_with('.o') || flag.value.ends_with('.obj') { other_flags << '"${v.msvc_thirdparty_obj_path(flag.mod, flag.value, flag.cached)}"' } else if flag.value.starts_with('-D') { defines << '/D${flag.value[2..]}' } else { consumed, leftover := split_and_apply_gnu_flags(flag.value, mut inc_paths, mut lib_paths, mut real_libs) if consumed { if leftover != '' { other_flags << strip_quotes(leftover) } continue } other_flags << strip_quotes(flag.value) } } mut lpaths := []string{} for l in lib_paths { lpaths << '/LIBPATH:"${os.real_path(l)}"' } // Drop only stray quoted gcc-style -I/-L/-l tokens that leak into MSVC args. mut filtered_other_flags := []string{cap: other_flags.len} for of in other_flags { ofs := of.trim_space() if ofs.len >= 3 && ofs[0] == `"` && ofs[1] == `-` && ofs[2] in [`I`, `L`, `l`] { continue } filtered_other_flags << of } other_flags = filtered_other_flags.clone() return MsvcStringFlags{ real_libs: real_libs inc_paths: inc_paths lib_paths: lpaths defines: defines other_flags: other_flags } } fn (r MsvcResult) include_paths() []string { mut res := []string{cap: 4} if r.ucrt_include_path != '' { res << '-I "${r.ucrt_include_path}"' } if r.vs_include_path != '' { res << '-I "${r.vs_include_path}"' } if r.um_include_path != '' { res << '-I "${r.um_include_path}"' } if r.shared_include_path != '' { res << '-I "${r.shared_include_path}"' } return res } fn (r MsvcResult) library_paths() []string { mut res := []string{cap: 3} if r.ucrt_lib_path != '' { res << '/LIBPATH:"${r.ucrt_lib_path}"' } if r.um_lib_path != '' { res << '/LIBPATH:"${r.um_lib_path}"' } if r.vs_lib_path != '' { res << '/LIBPATH:"${r.vs_lib_path}"' } return res }