blob: 9f9d13af696d2ce70238a01853e1cad5d0c5e579 [file] [log] [blame]
#!/usr/bin/env python
"""Ninja toolchain abstraction for Microsoft compiler suite"""
import os
import subprocess
import toolchain
import vslocate
class MSVCToolchain(toolchain.Toolchain):
def initialize(self, project, archs, configs, includepaths, dependlibs, libpaths, variables, subninja):
#Local variable defaults
self.sdkpath = ''
self.toolchain = ''
self.includepaths = []
self.libpaths = libpaths
self.ccompiler = 'cl'
self.cxxcompiler = 'cl'
self.archiver = 'lib'
self.linker = 'link'
self.dller = 'dll'
#Command definitions (to generate assembly, add "/FAs /Fa$out.asm")
self.cccmd = '$toolchain$cc /showIncludes /I. $includepaths $moreincludepaths $cflags $carchflags $cconfigflags $cmoreflags /c $in /Fo$out /Fd$pdbpath /FS /nologo'
self.cxxcmd = '$toolchain$cxx /showIncludes /I. $includepaths $moreincludepaths $cxxflags $carchflags $cconfigflags $cmoreflags /c $in /Fo$out /Fd$pdbpath /FS /nologo'
self.ccdepfile = None
self.ccdeps = 'msvc'
self.arcmd = '$toolchain$ar $arflags $ararchflags $arconfigflags /NOLOGO /OUT:$out $in'
self.linkcmd = '$toolchain$link $libpaths $configlibpaths $linkflags $linkarchflags $linkconfigflags /DEBUG /NOLOGO /SUBSYSTEM:CONSOLE /DYNAMICBASE /NXCOMPAT /MANIFEST /MANIFESTUAC:\"level=\'asInvoker\' uiAccess=\'false\'\" /TLBID:1 /PDB:$pdbpath /OUT:$out $in $libs $archlibs $oslibs'
self.dllcmd = self.linkcmd + ' /DLL'
self.cflags = ['/D', '"' + project.upper() + '_COMPILE=1"', '/D', '"_UNICODE"', '/D', '"UNICODE"', '/std:c17', '/Zi', '/Oi', '/Oy-', '/GS-', '/Gy-', '/Qpar-', '/fp:fast', '/fp:except-', '/Zc:forScope', '/Zc:wchar_t', '/GR-', '/openmp-']
self.cwarnflags = ['/W4', '/WX', '/wd4201'] #Ignore nameless union/struct which is allowed in C11
self.cmoreflags = []
self.arflags = ['/ignore:4221'] #Ignore empty object file warning]
self.linkflags = ['/DEBUG']
self.oslibs = ['kernel32', 'user32', 'shell32', 'advapi32']
self.initialize_subninja(subninja)
self.initialize_archs(archs)
self.initialize_configs(configs)
self.initialize_project(project)
self.initialize_toolchain()
self.initialize_depends(dependlibs)
self.parse_default_variables(variables)
self.read_build_prefs()
self.includepaths = self.prefix_includepaths((includepaths or []) + ['.'])
if self.is_monolithic():
self.cflags += ['/D', '"BUILD_MONOLITHIC=1"']
if not 'nowarning' in variables or not variables['nowarning']:
self.cflags += self.cwarnflags
self.cxxflags = list(self.cflags)
#Overrides
self.objext = '.obj'
#Builders
self.builders['c'] = self.builder_cc
self.builders['cc'] = self.builder_cxx
self.builders['cpp'] = self.builder_cxx
self.builders['lib'] = self.builder_lib
self.builders['multilib'] = self.builder_multicopy
self.builders['sharedlib'] = self.builder_sharedlib
self.builders['multisharedlib'] = self.builder_multicopy
self.builders['bin'] = self.builder_bin
self.builders['multibin'] = self.builder_multicopy
#Setup toolchain
self.build_toolchain()
def name(self):
return 'msvc'
def parse_prefs(self, prefs):
super(MSVCToolchain, self).parse_prefs(prefs)
if 'msvc' in prefs:
msvcprefs = prefs['msvc']
if 'sdkpath' in msvcprefs:
self.sdkpath = msvcprefs['sdkpath']
if 'toolchain' in msvcprefs:
self.toolchain = msvcprefs['toolchain']
if 'toolchain_version' in msvcprefs:
self.toolchain_version = msvcprefs['toolchain_version']
def write_variables(self, writer):
super(MSVCToolchain, self).write_variables(writer)
writer.variable('cc', self.ccompiler)
writer.variable('cxx', self.cxxcompiler)
writer.variable('ar', self.archiver)
writer.variable('link', self.linker)
writer.variable('dll', self.dller)
writer.variable('toolchain', self.toolchain)
writer.variable('includepaths', self.make_includepaths(self.includepaths))
writer.variable('moreincludepaths', '')
writer.variable('pdbpath', 'ninja.pdb')
writer.variable('cflags', self.cflags)
writer.variable('carchflags', '')
writer.variable('cconfigflags', '')
writer.variable('cxxflags', self.cxxflags)
writer.variable('cmoreflags', self.cmoreflags)
writer.variable('arflags', self.arflags)
writer.variable('ararchflags', '')
writer.variable('arconfigflags', '')
writer.variable('linkflags', self.linkflags)
writer.variable('linkarchflags', '')
writer.variable('linkconfigflags', '')
writer.variable('libs', '')
writer.variable('libpaths', self.make_libpaths(self.libpaths))
writer.variable('configlibpaths', '')
writer.variable('archlibs', '')
writer.variable('oslibs', self.make_libs(self.oslibs))
writer.newline()
def write_rules(self, writer):
super(MSVCToolchain, self).write_rules(writer)
writer.rule('cc', command = self.cccmd, depfile = self.ccdepfile, deps = self.ccdeps, description = 'CC $in')
writer.rule('cxx', command = self.cxxcmd, depfile = self.ccdepfile, deps = self.ccdeps, description = 'CXX $in')
writer.rule('ar', command = self.arcmd, description = 'LIB $out')
writer.rule('link', command = self.linkcmd, description = 'LINK $out')
writer.rule('dll', command = self.dllcmd, description = 'DLL $out')
writer.newline()
def build_toolchain(self):
if self.toolchain == '':
installed_versions = vslocate.get_vs_installations()
for versionstr, installpath in installed_versions:
major_version = versionstr.split('.')[0]
if int(major_version) >= 15:
tools_basepath = os.path.join(installpath, 'VC', 'Tools', 'MSVC')
tools_list = [item for item in os.listdir(tools_basepath) if os.path.isdir(os.path.join(tools_basepath, item))]
from distutils.version import StrictVersion
tools_list.sort(key=StrictVersion)
self.toolchain = os.path.join(tools_basepath, tools_list[-1])
self.toolchain_version = major_version + ".0"
break
if self.toolchain == '':
toolchain = ''
versions = ['17.0', '16.0', '15.0']
keys = [
'HKLM\\SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VC7',
'HKCU\\SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VC7',
'HKLM\\SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7',
'HKCU\\SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7',
'HKLM\\SOFTWARE\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VC7',
'HKCU\\SOFTWARE\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VC7',
'HKLM\\SOFTWARE\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VS7',
'HKCU\\SOFTWARE\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VS7'
]
for version in versions:
for key in keys:
try:
query = subprocess.check_output(['reg', 'query', key, '/v', version ], stderr = subprocess.STDOUT).strip().splitlines()
if len(query) == 2:
toolchain = str(query[1]).split('REG_SZ')[-1].strip(" '\"\n\r\t")
except:
continue
if not toolchain == '':
tools_basepath = os.path.join(toolchain, 'VC', 'Tools', 'MSVC')
tools_list = [item for item in os.listdir(tools_basepath) if os.path.isdir(os.path.join(tools_basepath, item))]
from distutils.version import StrictVersion
tools_list.sort(key=StrictVersion)
toolchain = os.path.join(tools_basepath, tools_list[-1])
self.toolchain = toolchain
self.toolchain_version = version
break
if not self.toolchain == '':
break
if self.toolchain == '':
raise Exception("Unable to locate any installed Visual Studio toolchain")
self.includepaths += [os.path.join(self.toolchain, 'include')]
if self.sdkpath == '':
versions = ['v10.0', 'v8.1']
keys = [
'HKLM\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows',
'HKCU\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows',
'HKLM\\SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SDKs\\Windows',
'HKCU\\SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SDKs\\Windows'
]
include_path = 'include'
for version in versions:
for key in keys:
sdkpath = ''
try:
query = subprocess.check_output(['reg', 'query', key + '\\' + version, '/v', 'InstallationFolder'], stderr = subprocess.STDOUT).strip().splitlines()
if len(query) == 2:
sdkpath = str(query[1]).split('REG_SZ')[-1].strip(" '\"\n\r\t")
if not sdkpath == '' and version == 'v10.0':
base_path = sdkpath
sdkpath = ''
query = subprocess.check_output(['reg', 'query', key + '\\' + version, '/v', 'ProductVersion'], stderr = subprocess.STDOUT).strip().splitlines()
if len(query) == 2:
version_path = str(query[1]).split('REG_SZ')[-1].strip(" '\"\n\r\t")
if not version_path == '':
sdkpath = base_path
self.sdkversionpath = version_path
versioned_include_path = os.path.join(include_path, self.sdkversionpath)
if not os.path.exists(os.path.join(sdkpath, versioned_include_path)) and os.path.exists(os.path.join(sdkpath, versioned_include_path + '.0')):
self.sdkversionpath = self.sdkversionpath + '.0'
versioned_include_path = os.path.join(include_path, self.sdkversionpath)
include_path = versioned_include_path
except subprocess.CalledProcessError as e:
continue
if not sdkpath == '':
self.includepaths += [
os.path.join(sdkpath, include_path, 'shared'),
os.path.join(sdkpath, include_path, 'um'),
os.path.join(sdkpath, include_path, 'winrt')
]
if version == 'v10.0':
self.includepaths += [
os.path.join(sdkpath, include_path, 'ucrt')
]
self.sdkpath = sdkpath
self.sdkversion = version
break
if not sdkpath == '':
break
if self.toolchain != '' and not self.toolchain.endswith('/') and not self.toolchain.endswith('\\'):
self.toolchain += os.sep
def make_includepaths(self, includepaths):
if not includepaths is None:
return ['/I' + self.path_escape(path) for path in list(includepaths)]
return []
def make_libpath(self, path):
return self.path_escape(path)
def make_libpaths(self, libpaths):
if not libpaths is None:
return ['/LIBPATH:' + self.make_libpath(path) for path in libpaths]
return []
def make_arch_toolchain_path(self, arch):
if arch == 'x86-64':
return os.path.join(self.toolchain, 'bin', 'HostX64', 'x64\\')
elif arch == 'x86':
return os.path.join(self.toolchain, 'bin', 'HostX64', 'x86\\')
return os.path.join(self.toolchain, 'bin\\')
def make_carchflags(self, arch, targettype):
flags = []
if targettype == 'sharedlib':
flags += ['/MD', '/D', '"BUILD_DYNAMIC_LINK=1"']
else:
flags += ['/MT']
if arch == 'x86':
flags += ['/arch:SSE2']
elif arch == 'x86-64':
pass
return flags
def make_cconfigflags(self, config, targettype):
flags = ['/Gm-']
if config == 'debug':
flags += ['/Od', '/D', '"BUILD_DEBUG=1"', '/GF-']
else:
flags += ['/Ob2', '/Ot', '/GT', '/GL', '/GF']
if config == 'release':
flags += ['/O2', '/D' '"BUILD_RELEASE=1"']
elif config == 'profile':
flags += ['/Ox', '/D', '"BUILD_PROFILE=1"']
elif config == 'deploy':
flags += ['/Ox', '/D', '"BUILD_DEPLOY=1"']
return flags
def make_ararchflags(self, arch, targettype):
flags = []
if arch == 'x86':
flags += ['/MACHINE:X86']
elif arch == 'x86-64':
flags += ['/MACHINE:X64']
return flags
def make_arconfigflags(self, config, targettype):
flags = []
if config != 'debug':
flags += ['/LTCG']
return flags
def make_linkarchflags(self, arch, targettype):
flags = []
if arch == 'x86':
flags += ['/MACHINE:X86']
elif arch == 'x86-64':
flags += ['/MACHINE:X64']
return flags
def make_linkconfigflags(self, config, targettype):
flags = []
if config == 'debug':
flags += ['/INCREMENTAL']
else:
flags += ['/LTCG', '/INCREMENTAL:NO', '/OPT:REF', '/OPT:ICF']
return flags
def make_libs(self, libs):
if libs != None:
return [lib + '.lib' for lib in libs]
return []
def make_configlibpaths(self, config, arch, extralibpaths):
libpaths = [
self.libpath,
os.path.join(self.libpath, arch),
os.path.join(self.libpath, config),
os.path.join(self.libpath, config, arch)
]
if extralibpaths != None:
libpaths += [os.path.join(libpath, self.libpath) for libpath in extralibpaths]
libpaths += [os.path.join(libpath, self.libpath, arch) for libpath in extralibpaths]
libpaths += [os.path.join(libpath, self.libpath, config) for libpath in extralibpaths]
libpaths += [os.path.join(libpath, self.libpath, config, arch) for libpath in extralibpaths]
if self.sdkpath != '':
if arch == 'x86':
libpaths += [os.path.join(self.toolchain, 'lib', 'x86')]
if self.sdkversion == 'v8.1':
libpaths += [os.path.join( self.sdkpath, 'lib', 'winv6.3', 'um', 'x86')]
if self.sdkversion == 'v10.0':
libpaths += [os.path.join(self.sdkpath, 'lib', self.sdkversionpath, 'um', 'x86')]
libpaths += [os.path.join(self.sdkpath, 'lib', self.sdkversionpath, 'ucrt', 'x86')]
else:
libpaths += [os.path.join( self.toolchain, 'lib', 'x64')]
if self.sdkversion == 'v8.1':
libpaths += [os.path.join( self.sdkpath, 'lib', 'winv6.3', 'um', 'x64')]
if self.sdkversion == 'v10.0':
libpaths += [os.path.join( self.sdkpath, 'lib', self.sdkversionpath, 'um', 'x64')]
libpaths += [os.path.join( self.sdkpath, 'lib', self.sdkversionpath, 'ucrt', 'x64')]
return self.make_libpaths(libpaths)
def cc_variables(self, config, arch, targettype, variables):
localvariables = [('toolchain', self.make_arch_toolchain_path(arch))]
if 'includepaths' in variables:
moreincludepaths = self.make_includepaths(variables['includepaths'])
if not moreincludepaths == []:
localvariables += [('moreincludepaths', moreincludepaths)]
if 'modulepath' in variables:
localvariables += [('pdbpath', os.path.join(variables['modulepath'], 'ninja.pdb'))]
carchflags = self.make_carchflags(arch, targettype)
if carchflags != []:
localvariables += [('carchflags', carchflags)]
cconfigflags = self.make_cconfigflags(config, targettype)
if cconfigflags != []:
localvariables += [('cconfigflags', cconfigflags)]
if 'defines' in variables:
definelist = []
for define in variables['defines']:
definelist += ['/D', '"' + define + '"']
localvariables += [('cmoreflags', definelist)]
return localvariables
def ar_variables(self, config, arch, targettype, variables):
localvariables = [('toolchain', self.make_arch_toolchain_path(arch))]
ararchflags = self.make_ararchflags(arch, targettype)
if ararchflags != []:
localvariables += [('ararchflags', ararchflags)]
arconfigflags = self.make_arconfigflags(config, targettype)
if arconfigflags != []:
localvariables += [('arconfigflags', arconfigflags)]
return localvariables
def link_variables(self, config, arch, targettype, variables):
localvariables = [('toolchain', self.make_arch_toolchain_path(arch))]
linkarchflags = self.make_linkarchflags(arch, targettype)
if linkarchflags != []:
localvariables += [('linkarchflags', linkarchflags)]
linkconfigflags = self.make_linkconfigflags(config, targettype)
if linkconfigflags != []:
localvariables += [('linkconfigflags', linkconfigflags)]
if 'modulepath' in variables:
localvariables += [('pdbpath', os.path.join(variables['modulepath'], 'ninja.pdb'))]
if 'libs' in variables:
libvar = self.make_libs(variables['libs'])
if libvar != []:
localvariables += [('libs', libvar)]
libpaths = []
if 'libpaths' in variables:
libpaths = variables['libpaths']
localvariables += [('configlibpaths', self.make_configlibpaths(config, arch, libpaths))]
return localvariables
def builder_cc(self, writer, config, arch, targettype, infile, outfile, variables):
return writer.build(outfile, 'cc', infile, implicit = self.implicit_deps(config, variables), variables = self.cc_variables(config, arch, targettype, variables))
def builder_cxx(self, writer, config, arch, targettype, infile, outfile, variables):
return writer.build(outfile, 'cxx', infile, implicit = self.implicit_deps(config, variables), variables = self.cc_variables(config, arch, targettype, variables))
def builder_lib(self, writer, config, arch, targettype, infiles, outfile, variables):
return writer.build(outfile, 'ar', infiles, implicit = self.implicit_deps(config, variables), variables = self.ar_variables(config, arch, targettype, variables))
def builder_sharedlib(self, writer, config, arch, targettype, infiles, outfile, variables):
return writer.build(outfile, 'dll', infiles, implicit = self.implicit_deps(config, variables), variables = self.link_variables(config, arch, targettype, variables))
def builder_bin(self, writer, config, arch, targettype, infiles, outfile, variables):
return writer.build(outfile, 'link', infiles, implicit = self.implicit_deps(config, variables), variables = self.link_variables(config, arch, targettype, variables))
def create(host, target, toolchain):
return MSVCToolchain(host, target, toolchain)