From a069577e9c53777881c1a6dd26bb9f4276f57df9 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sun, 21 Aug 2022 04:36:25 +0300 Subject: [PATCH] builder: remove msvc code from non windows systems --- ROADMAP.md | 3 +- vlib/v/builder/builder.v | 23 +- vlib/v/builder/cc.v | 51 +--- vlib/v/builder/cc_windows.v | 40 +++ vlib/v/builder/msvc.v | 543 +--------------------------------- vlib/v/builder/msvc_windows.v | 543 ++++++++++++++++++++++++++++++++++ vlib/v/gen/c/assign.v | 2 +- 7 files changed, 611 insertions(+), 594 deletions(-) create mode 100644 vlib/v/builder/cc_windows.v create mode 100644 vlib/v/builder/msvc_windows.v diff --git a/ROADMAP.md b/ROADMAP.md index 9ac396adc..b4f41f07d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6,7 +6,6 @@ - [ ] Parallel C compilation - [ ] `recover()` from panics - [ ] vfmt: add missing imports (like goimports) -- [ ] merge v.c and v_win.c - [ ] Recursive structs via optionals: `struct Node { next ?Node }` - [ ] Optional function struct fields - [ ] Handle function pointers safely, remove `if function == 0 {` @@ -15,4 +14,4 @@ - [ ] -usecache on by default - [ ] -skip-unused on by default - [ ] `any` type -- [ ] `copy()` builtin function for easier conversion from `[]Foo` to `[4]Foo` +- [ ] `copy()` builtin function (e.g. for easier conversion from `[]Foo` to `[4]Foo`) diff --git a/vlib/v/builder/builder.v b/vlib/v/builder/builder.v index 85db9e736..b17b98b14 100644 --- a/vlib/v/builder/builder.v +++ b/vlib/v/builder/builder.v @@ -32,9 +32,11 @@ pub mut: pref &pref.Preferences module_search_paths []string parsed_files []&ast.File - cached_msvc MsvcResult - table &ast.Table - ccoptions CcompilerOptions + //$if windows { + cached_msvc MsvcResult + //} + table &ast.Table + ccoptions CcompilerOptions // // Note: changes in mod `builtin` force invalidation of every other .v file mod_invalidates_paths map[string][]string // changes in mod `os`, invalidate only .v files, that do `import os` @@ -56,12 +58,15 @@ pub fn new_builder(pref &pref.Preferences) Builder { util.emanager.set_support_color(false) } table.pointer_size = if pref.m64 { 8 } else { 4 } - msvc := find_msvc(pref.m64) or { - if pref.ccompiler == 'msvc' { - // verror('Cannot find MSVC on this OS') - } - MsvcResult{ - valid: false + mut msvc := MsvcResult{} + $if windows { + msvc = find_msvc(pref.m64) or { + if pref.ccompiler == 'msvc' { + // verror('Cannot find MSVC on this OS') + } + MsvcResult{ + valid: false + } } } util.timing_set_should_print(pref.show_timings || pref.is_verbose) diff --git a/vlib/v/builder/cc.v b/vlib/v/builder/cc.v index e96e9b54d..714b49bb9 100644 --- a/vlib/v/builder/cc.v +++ b/vlib/v/builder/cc.v @@ -39,39 +39,6 @@ You can also use `v doctor`, to see what V knows about your current environment. You can also seek #help on Discord: https://discord.gg/vlang ' -pub fn (mut v Builder) find_win_cc() ? { - $if !windows { - return - } - ccompiler_version_res := os.execute('${os.quoted_path(v.pref.ccompiler)} -v') - if ccompiler_version_res.exit_code != 0 { - if v.pref.is_verbose { - println('$v.pref.ccompiler not found, looking for msvc...') - } - find_msvc(v.pref.m64) or { - if v.pref.is_verbose { - println('msvc not found, looking for thirdparty/tcc...') - } - vpath := os.dir(pref.vexe_path()) - thirdparty_tcc := os.join_path(vpath, 'thirdparty', 'tcc', 'tcc.exe') - tcc_version_res := os.execute('${os.quoted_path(thirdparty_tcc)} -v') - if tcc_version_res.exit_code != 0 { - if v.pref.is_verbose { - println('tcc not found') - } - return error('tcc not found') - } - v.pref.ccompiler = thirdparty_tcc - v.pref.ccompiler_type = .tinyc - return - } - v.pref.ccompiler = 'msvc' - v.pref.ccompiler_type = .msvc - return - } - v.pref.ccompiler_type = pref.cc_from_string(v.pref.ccompiler) -} - fn (mut v Builder) show_c_compiler_output(res os.Result) { println('======== C Compiler output ========') println(res.output) @@ -593,9 +560,11 @@ pub fn (mut v Builder) cc() { v.ccoptions.pre_args << '-c' } v.handle_usecache(vexe) - if ccompiler == 'msvc' { - v.cc_msvc() - return + $if windows { + if ccompiler == 'msvc' { + v.cc_msvc() + return + } } // all_args := v.all_args(v.ccoptions) @@ -918,11 +887,13 @@ fn (mut b Builder) build_thirdparty_obj_files() { for flag in b.get_os_cflags() { if flag.value.ends_with('.o') { rest_of_module_flags := b.get_rest_of_module_cflags(flag) - if b.pref.ccompiler == 'msvc' { - b.build_thirdparty_obj_file_with_msvc(flag.value, rest_of_module_flags) - } else { - b.build_thirdparty_obj_file(flag.value, rest_of_module_flags) + $if windows { + if b.pref.ccompiler == 'msvc' { + b.build_thirdparty_obj_file_with_msvc(flag.value, rest_of_module_flags) + } + continue } + b.build_thirdparty_obj_file(flag.value, rest_of_module_flags) } } } diff --git a/vlib/v/builder/cc_windows.v b/vlib/v/builder/cc_windows.v new file mode 100644 index 000000000..b9b472fa0 --- /dev/null +++ b/vlib/v/builder/cc_windows.v @@ -0,0 +1,40 @@ +// Copyright (c) 2019-2022 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module builder + +import os +import v.pref + +pub fn (mut v Builder) find_win_cc() ? { + $if !windows { + return + } + ccompiler_version_res := os.execute('${os.quoted_path(v.pref.ccompiler)} -v') + if ccompiler_version_res.exit_code != 0 { + if v.pref.is_verbose { + println('$v.pref.ccompiler not found, looking for msvc...') + } + find_msvc(v.pref.m64) or { + if v.pref.is_verbose { + println('msvc not found, looking for thirdparty/tcc...') + } + vpath := os.dir(pref.vexe_path()) + thirdparty_tcc := os.join_path(vpath, 'thirdparty', 'tcc', 'tcc.exe') + tcc_version_res := os.execute('${os.quoted_path(thirdparty_tcc)} -v') + if tcc_version_res.exit_code != 0 { + if v.pref.is_verbose { + println('tcc not found') + } + return error('tcc not found') + } + v.pref.ccompiler = thirdparty_tcc + v.pref.ccompiler_type = .tinyc + return + } + v.pref.ccompiler = 'msvc' + v.pref.ccompiler_type = .msvc + return + } + v.pref.ccompiler_type = pref.cc_from_string(v.pref.ccompiler) +} diff --git a/vlib/v/builder/msvc.v b/vlib/v/builder/msvc.v index 03f27aa65..25424b84c 100644 --- a/vlib/v/builder/msvc.v +++ b/vlib/v/builder/msvc.v @@ -1,14 +1,6 @@ module builder -import os -import v.pref -import v.util -import v.cflag - -#flag windows -l shell32 -#flag windows -l dbghelp -#flag windows -l advapi32 - +// TODO move this to msvc_default.v struct MsvcResult { full_cl_exe_path string exe_path string @@ -21,536 +13,3 @@ struct MsvcResult { shared_include_path string valid bool } - -// shell32 for RegOpenKeyExW etc -// Mimics a HKEY -type RegKey = voidptr - -// Taken from the windows SDK -const ( - hkey_local_machine = RegKey(0x80000002) - key_query_value = (0x0001) - key_wow64_32key = (0x0200) - 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, &required_bytes) - length := required_bytes / 2 - if result != 0 { - continue - } - alloc_length := (required_bytes + 2) - mut value := &u16(malloc_noscan(int(alloc_length))) - if isnil(value) { - continue - } - // - else { - } - result2 := C.RegQueryValueEx(key, version.to_wide(), 0, 0, value, &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(builder.hkey_local_machine, path.to_wide(), 0, builder.key_query_value | builder.key_wow64_32key | builder.key_enumerate_sub_keys, - &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_obj := os.real_path(v.out_name_c + '.obj') - out_name_pdb := os.real_path(v.out_name_c + '.pdb') - out_name_cmd_line := os.real_path(v.out_name_c + '.rsp') - 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) - // `/Fo` sets the object file name - needed so we can clean up after ourselves properly - // `/F 16777216` changes the stack size to 16MB, see https://docs.microsoft.com/en-us/cpp/build/reference/f-set-stack-size?view=msvc-170 - a << ['-w', '/we4013', '/volatile:ms', '/Fo"$out_name_obj"', '/F 16777216'] - if v.pref.is_prod { - 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' - } - 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' - } else if v.pref.build_mode == .default_mode { - /* - b := os.real_path( '${pref.default_module_path}/vlib/builtin.obj' ) - alibs << '"$b"' - if !os.exists(b) { - println('`builtin.obj` not found') - exit(1) - } - for imp in v.ast.imports { - if imp == 'webview' { - continue - } - alibs << '"' + os.real_path( '${pref.default_module_path}/vlib/${imp}.obj' ) + '"' - } - */ - } - if v.pref.sanitize { - eprintln('Sanitize not supported on msvc.') - } - // The C file we are compiling - // a << '"$TmpPath/$v.out_name_c"' - a << '"' + 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'] - sflags := 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' - a << '/NOLOGO' - a << '/OUT:"$v.pref.out_name"' - 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 { - 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 - } - v.dump_c_options(a) - args := a.join(' ') - // 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"') - } - cmd := '"$r.full_cl_exe_path" "@$out_name_cmd_line"' - // 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, out_name_cmd_line, 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(res.output) - verror('msvc error') - } - util.timing_measure('C msvc') - if v.pref.show_c_output { - v.show_c_compiler_output(res) - } else { - v.post_process_c_compiler_output(res) - } - // println(res) - // println('C OUTPUT:') - // Always remove the object file - it is completely unnecessary - os.rm(out_name_obj) or {} -} - -fn (mut v Builder) build_thirdparty_obj_file_with_msvc(path string, moduleflags []cflag.CFlag) { - msvc := v.cached_msvc - if msvc.valid == false { - verror('Cannot find MSVC on this OS') - } - // msvc expects .obj not .o - path_without_o_postfix := path[..path.len - 2] // remove .o - mut obj_path := '${path_without_o_postfix}.obj' - obj_path = os.real_path(obj_path) - if os.exists(obj_path) { - // println('$obj_path already built.') - return - } - println('$obj_path not found, building it (with msvc)...') - cfile := '${path_without_o_postfix}.c' - flags := msvc_string_flags(moduleflags) - inc_dirs := flags.inc_paths.join(' ') - defines := flags.defines.join(' ') - // - mut oargs := []string{} - env_cflags := os.getenv('CFLAGS') - mut all_cflags := '$env_cflags $v.pref.cflags' - if all_cflags != ' ' { - oargs << all_cflags - } - oargs << '/NOLOGO' - oargs << '/volatile:ms' - // - if v.pref.is_prod { - oargs << '/O2' - oargs << '/MD' - oargs << '/DNDEBUG' - } else { - oargs << '/MDd' - oargs << '/D_DEBUG' - } - oargs << defines - oargs << msvc.include_paths() - oargs << inc_dirs - oargs << '/c "$cfile"' - oargs << '/Fo"$obj_path"' - env_ldflags := os.getenv('LDFLAGS') - if env_ldflags != '' { - oargs << env_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') - } - res := os.execute(cmd) - if res.exit_code != 0 { - println('msvc: failed to build a thirdparty object; cmd: $cmd') - verror(res.output) - } - println(res.output) -} - -struct MsvcStringFlags { -mut: - real_libs []string - inc_paths []string - lib_paths []string - defines []string - other_flags []string -} - -// pub fn (cflags []CFlag) msvc_string_flags() MsvcStringFlags { -pub fn 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 { - // 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 - lib_lib := flag.value + '.lib' - real_libs << lib_lib - } else if flag.name == '-I' { - inc_paths << flag.format() - } else if flag.name == '-D' { - defines << '/D$flag.value' - } else if flag.name == '-L' { - lib_paths << flag.value - lib_paths << flag.value + os.path_separator + 'msvc' - // 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') { - // msvc expects .obj not .o - other_flags << '"${flag.value}bj"' - } else if flag.value.starts_with('-D') { - defines << '/D${flag.value[2..]}' - } else { - other_flags << flag.value - } - } - mut lpaths := []string{} - for l in lib_paths { - lpaths << '/LIBPATH:"${os.real_path(l)}"' - } - 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 -} diff --git a/vlib/v/builder/msvc_windows.v b/vlib/v/builder/msvc_windows.v new file mode 100644 index 000000000..53c854060 --- /dev/null +++ b/vlib/v/builder/msvc_windows.v @@ -0,0 +1,543 @@ +module builder + +import os +import v.pref +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) + key_query_value = (0x0001) + key_wow64_32key = (0x0200) + 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, &required_bytes) + length := required_bytes / 2 + if result != 0 { + continue + } + alloc_length := (required_bytes + 2) + mut value := &u16(malloc_noscan(int(alloc_length))) + if isnil(value) { + continue + } + // + else { + } + result2 := C.RegQueryValueEx(key, version.to_wide(), 0, 0, value, &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(builder.hkey_local_machine, path.to_wide(), 0, builder.key_query_value | builder.key_wow64_32key | builder.key_enumerate_sub_keys, + &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_obj := os.real_path(v.out_name_c + '.obj') + out_name_pdb := os.real_path(v.out_name_c + '.pdb') + out_name_cmd_line := os.real_path(v.out_name_c + '.rsp') + 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) + // `/Fo` sets the object file name - needed so we can clean up after ourselves properly + // `/F 16777216` changes the stack size to 16MB, see https://docs.microsoft.com/en-us/cpp/build/reference/f-set-stack-size?view=msvc-170 + a << ['-w', '/we4013', '/volatile:ms', '/Fo"$out_name_obj"', '/F 16777216'] + if v.pref.is_prod { + 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' + } + 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' + } else if v.pref.build_mode == .default_mode { + /* + b := os.real_path( '${pref.default_module_path}/vlib/builtin.obj' ) + alibs << '"$b"' + if !os.exists(b) { + println('`builtin.obj` not found') + exit(1) + } + for imp in v.ast.imports { + if imp == 'webview' { + continue + } + alibs << '"' + os.real_path( '${pref.default_module_path}/vlib/${imp}.obj' ) + '"' + } + */ + } + if v.pref.sanitize { + eprintln('Sanitize not supported on msvc.') + } + // The C file we are compiling + // a << '"$TmpPath/$v.out_name_c"' + a << '"' + 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'] + sflags := 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' + a << '/NOLOGO' + a << '/OUT:"$v.pref.out_name"' + 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 { + 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 + } + v.dump_c_options(a) + args := a.join(' ') + // 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"') + } + cmd := '"$r.full_cl_exe_path" "@$out_name_cmd_line"' + // 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, out_name_cmd_line, 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(res.output) + verror('msvc error') + } + util.timing_measure('C msvc') + if v.pref.show_c_output { + v.show_c_compiler_output(res) + } else { + v.post_process_c_compiler_output(res) + } + // println(res) + // println('C OUTPUT:') + // Always remove the object file - it is completely unnecessary + os.rm(out_name_obj) or {} +} + +fn (mut v Builder) build_thirdparty_obj_file_with_msvc(path string, moduleflags []cflag.CFlag) { + msvc := v.cached_msvc + if msvc.valid == false { + verror('Cannot find MSVC on this OS') + } + // msvc expects .obj not .o + path_without_o_postfix := path[..path.len - 2] // remove .o + mut obj_path := '${path_without_o_postfix}.obj' + obj_path = os.real_path(obj_path) + if os.exists(obj_path) { + // println('$obj_path already built.') + return + } + println('$obj_path not found, building it (with msvc)...') + cfile := '${path_without_o_postfix}.c' + flags := msvc_string_flags(moduleflags) + inc_dirs := flags.inc_paths.join(' ') + defines := flags.defines.join(' ') + // + mut oargs := []string{} + env_cflags := os.getenv('CFLAGS') + mut all_cflags := '$env_cflags $v.pref.cflags' + if all_cflags != ' ' { + oargs << all_cflags + } + oargs << '/NOLOGO' + oargs << '/volatile:ms' + // + if v.pref.is_prod { + oargs << '/O2' + oargs << '/MD' + oargs << '/DNDEBUG' + } else { + oargs << '/MDd' + oargs << '/D_DEBUG' + } + oargs << defines + oargs << msvc.include_paths() + oargs << inc_dirs + oargs << '/c "$cfile"' + oargs << '/Fo"$obj_path"' + env_ldflags := os.getenv('LDFLAGS') + if env_ldflags != '' { + oargs << env_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') + } + res := os.execute(cmd) + if res.exit_code != 0 { + println('msvc: failed to build a thirdparty object; cmd: $cmd') + verror(res.output) + } + println(res.output) +} + +struct MsvcStringFlags { +mut: + real_libs []string + inc_paths []string + lib_paths []string + defines []string + other_flags []string +} + +// pub fn (cflags []CFlag) msvc_string_flags() MsvcStringFlags { +pub fn 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 { + // 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 + lib_lib := flag.value + '.lib' + real_libs << lib_lib + } else if flag.name == '-I' { + inc_paths << flag.format() + } else if flag.name == '-D' { + defines << '/D$flag.value' + } else if flag.name == '-L' { + lib_paths << flag.value + lib_paths << flag.value + os.path_separator + 'msvc' + // 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') { + // msvc expects .obj not .o + other_flags << '"${flag.value}bj"' + } else if flag.value.starts_with('-D') { + defines << '/D${flag.value[2..]}' + } else { + other_flags << flag.value + } + } + mut lpaths := []string{} + for l in lib_paths { + lpaths << '/LIBPATH:"${os.real_path(l)}"' + } + 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 +} diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index d8ef3cc08..720a14a3a 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -270,7 +270,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { } else { // str += str2 => `str = string__plus(str, str2)` g.expr(left) - g.write(' = /*f*/string__plus(') + g.write(' = string__plus(') } g.is_assign_lhs = false str_add = true -- 2.30.2