v/vlib/v/builder/msvc.v

523 lines
16 KiB
V

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
struct MsvcResult {
full_cl_exe_path string
exe_path string
um_lib_path string
ucrt_lib_path string
vs_lib_path string
um_include_path string
ucrt_include_path string
vs_include_path string
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 << '-I "$r.ucrt_include_path"'
a << '-I "$r.vs_include_path"'
a << '-I "$r.um_include_path"'
a << '-I "$r.shared_include_path"'
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 << '/LIBPATH:"$r.ucrt_lib_path"'
a << '/LIBPATH:"$r.um_lib_path"'
a << '/LIBPATH:"$r.vs_lib_path"'
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
}
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'
// println('cfile: $cfile')
flags := msvc_string_flags(moduleflags)
inc_dirs := flags.inc_paths.join(' ')
defines := flags.defines.join(' ')
include_string := '-I "$msvc.ucrt_include_path" -I "$msvc.vs_include_path" -I "$msvc.um_include_path" -I "$msvc.shared_include_path" $inc_dirs'
mut oargs := []string{}
env_cflags := os.getenv('CFLAGS')
mut all_cflags := '$env_cflags $v.pref.cflags'
if all_cflags != ' ' {
oargs << all_cflags
}
//
if v.pref.is_prod {
oargs << '/O2'
oargs << '/MD'
oargs << '/DNDEBUG'
} else {
oargs << '/MDd'
oargs << '/D_DEBUG'
}
env_ldflags := os.getenv('LDFLAGS')
if env_ldflags != '' {
oargs << env_ldflags
}
str_oargs := oargs.join(' ')
cmd := '"$msvc.full_cl_exe_path" /volatile:ms $str_oargs $defines $include_string /c "$cfile" /Fo"$obj_path"'
// 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
}
}