1--
2-- vs2010_vcxproj.lua
3-- Generate a Visual Studio 201x C/C++ project.
4-- Copyright (c) Jason Perkins and the Premake project
5--
6
7	local p = premake
8	p.vstudio.vc2010 = {}
9
10	local vstudio = p.vstudio
11	local project = p.project
12	local config = p.config
13	local fileconfig = p.fileconfig
14	local tree = p.tree
15
16	local m = p.vstudio.vc2010
17
18
19---
20-- Add namespace for element definition lists for p.callArray()
21---
22
23	m.elements = {}
24	m.conditionalElements = {}
25
26--
27-- Generate a Visual Studio 201x C++ project, with support for the new platforms API.
28--
29
30	m.elements.project = function(prj)
31		return {
32			m.xmlDeclaration,
33			m.project,
34			m.projectConfigurations,
35			m.globals,
36			m.importDefaultProps,
37			m.configurationPropertiesGroup,
38			m.importLanguageSettings,
39			m.importExtensionSettings,
40			m.propertySheetGroup,
41			m.userMacros,
42			m.outputPropertiesGroup,
43			m.itemDefinitionGroups,
44			m.assemblyReferences,
45			m.files,
46			m.projectReferences,
47			m.importLanguageTargets,
48			m.importExtensionTargets,
49			m.ensureNuGetPackageBuildImports,
50		}
51	end
52
53	function m.generate(prj)
54		p.utf8()
55		p.callArray(m.elements.project, prj)
56		p.out('</Project>')
57	end
58
59
60--
61-- Output the XML declaration and opening <Project> tag.
62--
63
64	function m.project(prj)
65		local action = p.action.current()
66		if _ACTION >= "vs2019" then
67			p.push('<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">')
68		else
69			p.push('<Project DefaultTargets="Build" ToolsVersion="%s" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">',
70				   action.vstudio.toolsVersion)
71		end
72	end
73
74
75--
76-- Write out the list of project configurations, which pairs build
77-- configurations with architectures.
78--
79
80	function m.projectConfigurations(prj)
81
82		-- build a list of all architectures used in this project
83		local platforms = {}
84		for cfg in project.eachconfig(prj) do
85			local arch = vstudio.archFromConfig(cfg, true)
86			if not table.contains(platforms, arch) then
87				table.insert(platforms, arch)
88			end
89		end
90
91		local configs = {}
92		p.push('<ItemGroup Label="ProjectConfigurations">')
93		for cfg in project.eachconfig(prj) do
94			for _, arch in ipairs(platforms) do
95				local prjcfg = vstudio.projectConfig(cfg, arch)
96				if not configs[prjcfg] then
97					configs[prjcfg] = prjcfg
98					p.push('<ProjectConfiguration Include="%s">', vstudio.projectConfig(cfg, arch))
99					p.x('<Configuration>%s</Configuration>', vstudio.projectPlatform(cfg))
100					p.w('<Platform>%s</Platform>', arch)
101					p.pop('</ProjectConfiguration>')
102				end
103			end
104		end
105		p.pop('</ItemGroup>')
106	end
107
108
109--
110-- Write out the TargetFrameworkVersion property.
111--
112
113	function m.targetFramework(prj)
114		local action = p.action.current()
115		local tools = string.format(' ToolsVersion="%s"', action.vstudio.toolsVersion)
116
117		local framework = prj.dotnetframework or action.vstudio.targetFramework or "4.0"
118		p.w('<TargetFrameworkVersion>v%s</TargetFrameworkVersion>', framework)
119	end
120
121
122
123--
124-- Write out the Globals property group.
125--
126
127	m.elements.globals = function(prj)
128		return {
129			m.projectGuid,
130			m.ignoreWarnDuplicateFilename,
131			m.keyword,
132			m.projectName,
133			m.preferredToolArchitecture,
134			m.latestTargetPlatformVersion,
135			m.windowsTargetPlatformVersion,
136		}
137	end
138
139	m.elements.globalsCondition = function(prj, cfg)
140		return {
141			m.windowsTargetPlatformVersion,
142			m.xpDeprecationWarning,
143		}
144	end
145
146	function m.globals(prj)
147
148		-- Write out the project-level globals
149		m.propertyGroup(nil, "Globals")
150		p.callArray(m.elements.globals, prj)
151		p.pop('</PropertyGroup>')
152
153		-- Write out the configurable globals
154		for cfg in project.eachconfig(prj) do
155
156			-- Find out whether we're going to actually write a property out
157			local captured = p.capture(	function()
158										p.push()
159										p.callArray(m.elements.globalsCondition, prj, cfg)
160										p.pop()
161										end)
162
163			-- If we do have something, create the entry, skip otherwise
164			if captured ~= '' then
165				m.propertyGroup(cfg, "Globals")
166				p.callArray(m.elements.globalsCondition, prj, cfg)
167				p.pop('</PropertyGroup>')
168			end
169
170		end
171
172	end
173
174
175--
176-- Write out the configuration property group: what kind of binary it
177-- produces, and some global settings.
178--
179
180	m.elements.configurationProperties = function(cfg)
181		if cfg.kind == p.UTILITY then
182			return {
183				m.configurationType,
184				m.platformToolset,
185			}
186		else
187			return {
188				m.configurationType,
189				m.useDebugLibraries,
190				m.useOfMfc,
191				m.useOfAtl,
192				m.clrSupport,
193				m.characterSet,
194				m.platformToolset,
195				m.wholeProgramOptimization,
196				m.nmakeOutDirs,
197				m.windowsSDKDesktopARMSupport,
198			}
199		end
200	end
201
202	function m.configurationProperties(cfg)
203		m.propertyGroup(cfg, "Configuration")
204		p.callArray(m.elements.configurationProperties, cfg)
205		p.pop('</PropertyGroup>')
206	end
207
208	function m.configurationPropertiesGroup(prj)
209		for cfg in project.eachconfig(prj) do
210			m.configurationProperties(cfg)
211		end
212	end
213
214
215
216--
217-- Write the output property group, which includes the output and intermediate
218-- directories, manifest, etc.
219--
220
221	m.elements.outputProperties = function(cfg)
222		if cfg.kind == p.UTILITY then
223			return {
224				m.outDir,
225				m.intDir,
226				m.extensionsToDeleteOnClean,
227				m.executablePath,
228			}
229		else
230			return {
231				m.linkIncremental,
232				m.ignoreImportLibrary,
233				m.outDir,
234				m.intDir,
235				m.targetName,
236				m.targetExt,
237				m.includePath,
238				m.libraryPath,
239				m.generateManifest,
240				m.extensionsToDeleteOnClean,
241				m.executablePath,
242			}
243		end
244	end
245
246	function m.outputProperties(cfg)
247		if not vstudio.isMakefile(cfg) then
248			m.propertyGroup(cfg)
249			p.callArray(m.elements.outputProperties, cfg)
250			p.pop('</PropertyGroup>')
251		end
252	end
253
254
255--
256-- Write the NMake property group for Makefile projects, which includes the custom
257-- build commands, output file location, etc.
258--
259
260	m.elements.nmakeProperties = function(cfg)
261		return {
262			m.executablePath,
263			m.includePath,
264			m.libraryPath,
265			m.nmakeOutput,
266			m.nmakeBuildCommands,
267			m.nmakeRebuildCommands,
268			m.nmakeCleanCommands,
269			m.nmakePreprocessorDefinitions,
270			m.nmakeIncludeDirs,
271			m.additionalCompileOptions
272		}
273	end
274
275	function m.nmakeProperties(cfg)
276		if vstudio.isMakefile(cfg) then
277			m.propertyGroup(cfg)
278			p.callArray(m.elements.nmakeProperties, cfg)
279			p.pop('</PropertyGroup>')
280		end
281	end
282
283
284--
285-- Output properties and NMake properties should appear side-by-side
286-- for each configuration.
287--
288
289	function m.outputPropertiesGroup(prj)
290		for cfg in project.eachconfig(prj) do
291			m.outputProperties(cfg)
292			m.nmakeProperties(cfg)
293		end
294	end
295
296
297
298--
299-- Write a configuration's item definition group, which contains all
300-- of the per-configuration compile and link settings.
301--
302
303	m.elements.itemDefinitionGroup = function(cfg)
304		if cfg.kind == p.UTILITY then
305			return {
306				m.ruleVars,
307				m.buildEvents,
308				m.buildLog,
309			}
310		else
311			return {
312				m.clCompile,
313				m.buildStep,
314				m.fxCompile,
315				m.resourceCompile,
316				m.linker,
317				m.manifest,
318				m.buildEvents,
319				m.ruleVars,
320				m.buildLog,
321			}
322		end
323	end
324
325	function m.itemDefinitionGroup(cfg)
326		if not vstudio.isMakefile(cfg) then
327			p.push('<ItemDefinitionGroup %s>', m.condition(cfg))
328			p.callArray(m.elements.itemDefinitionGroup, cfg)
329			p.pop('</ItemDefinitionGroup>')
330
331		else
332			if cfg == project.getfirstconfig(cfg.project) then
333				p.w('<ItemDefinitionGroup>')
334				p.w('</ItemDefinitionGroup>')
335			end
336		end
337	end
338
339	function m.itemDefinitionGroups(prj)
340		for cfg in project.eachconfig(prj) do
341			m.itemDefinitionGroup(cfg)
342		end
343	end
344
345
346
347--
348-- Write the the <ClCompile> compiler settings block.
349--
350
351	m.elements.clCompile = function(cfg)
352		local calls = {
353			m.precompiledHeader,
354			m.warningLevel,
355			m.treatWarningAsError,
356			m.disableSpecificWarnings,
357			m.treatSpecificWarningsAsErrors,
358			m.basicRuntimeChecks,
359			m.clCompilePreprocessorDefinitions,
360			m.clCompileUndefinePreprocessorDefinitions,
361			m.clCompileAdditionalIncludeDirectories,
362			m.clCompileAdditionalUsingDirectories,
363			m.forceIncludes,
364			m.debugInformationFormat,
365			m.optimization,
366			m.functionLevelLinking,
367			m.intrinsicFunctions,
368			m.justMyCodeDebugging,
369			m.minimalRebuild,
370			m.omitFramePointers,
371			m.stringPooling,
372			m.runtimeLibrary,
373			m.omitDefaultLib,
374			m.exceptionHandling,
375			m.runtimeTypeInfo,
376			m.bufferSecurityCheck,
377			m.treatWChar_tAsBuiltInType,
378			m.floatingPointModel,
379			m.floatingPointExceptions,
380			m.inlineFunctionExpansion,
381			m.enableEnhancedInstructionSet,
382			m.multiProcessorCompilation,
383			m.additionalCompileOptions,
384			m.compileAs,
385			m.callingConvention,
386			m.languageStandard,
387			m.conformanceMode,
388			m.structMemberAlignment,
389			m.useFullPaths,
390			m.removeUnreferencedCodeData
391		}
392
393		if cfg.kind == p.STATICLIB then
394			table.insert(calls, m.programDatabaseFileName)
395		end
396
397		return calls
398	end
399
400	function m.clCompile(cfg)
401		p.push('<ClCompile>')
402		p.callArray(m.elements.clCompile, cfg)
403		p.pop('</ClCompile>')
404	end
405
406--
407-- Write the the <CustomBuildStep> compiler settings block.
408--
409
410	m.elements.buildStep = function(cfg)
411		local calls = {
412			m.buildCommands,
413			m.buildMessage,
414			m.buildOutputs,
415			m.buildInputs
416		}
417
418		return calls
419	end
420
421	function m.buildStep(cfg)
422		if #cfg.buildCommands > 0 or #cfg.buildOutputs > 0 or #cfg.buildInputs > 0 or cfg.buildMessage then
423
424			p.push('<CustomBuildStep>')
425			p.callArray(m.elements.buildStep, cfg)
426			p.pop('</CustomBuildStep>')
427
428		end
429	end
430
431
432--
433-- Write the <FxCompile> settings block.
434--
435
436	m.elements.fxCompile = function(cfg)
437		return {
438			m.fxCompilePreprocessorDefinition,
439			m.fxCompileAdditionalIncludeDirs,
440			m.fxCompileShaderType,
441			m.fxCompileShaderModel,
442			m.fxCompileShaderEntry,
443			m.fxCompileShaderVariableName,
444			m.fxCompileShaderHeaderOutput,
445			m.fxCompileShaderObjectOutput,
446			m.fxCompileShaderAssembler,
447			m.fxCompileShaderAssemblerOutput,
448			m.fxCompileShaderAdditionalOptions,
449		}
450	end
451
452	function m.fxCompile(cfg)
453		if p.config.hasFile(cfg, path.ishlslfile) then
454			local contents = p.capture(function ()
455				p.push()
456				p.callArray(m.elements.fxCompile, cfg)
457				p.pop()
458			end)
459
460			if #contents > 0 then
461				p.push('<FxCompile>')
462				p.outln(contents)
463				p.pop('</FxCompile>')
464			end
465		end
466	end
467
468
469--
470-- Write out the resource compiler block.
471--
472
473	m.elements.resourceCompile = function(cfg)
474		return {
475			m.resourcePreprocessorDefinitions,
476			m.resourceAdditionalIncludeDirectories,
477			m.culture,
478		}
479	end
480
481	function m.resourceCompile(cfg)
482		if p.config.hasFile(cfg, path.isresourcefile) then
483			local contents = p.capture(function ()
484				p.push()
485				p.callArray(m.elements.resourceCompile, cfg)
486				p.pop()
487			end)
488
489			if #contents > 0 then
490				p.push('<ResourceCompile>')
491				p.outln(contents)
492				p.pop('</ResourceCompile>')
493			end
494		end
495	end
496
497
498--
499-- Write out the linker tool block.
500--
501
502	m.elements.linker = function(cfg, explicit)
503		return {
504			m.link,
505			m.lib,
506			m.linkLibraryDependencies,
507		}
508	end
509
510	function m.linker(cfg)
511		local explicit = vstudio.needsExplicitLink(cfg)
512		p.callArray(m.elements.linker, cfg, explicit)
513	end
514
515
516
517	m.elements.link = function(cfg, explicit)
518		if cfg.kind == p.STATICLIB then
519			return {
520				m.subSystem,
521				m.fullProgramDatabaseFile,
522				m.generateDebugInformation,
523				m.optimizeReferences,
524			}
525		else
526			return {
527				m.subSystem,
528				m.fullProgramDatabaseFile,
529				m.generateDebugInformation,
530				m.optimizeReferences,
531				m.additionalDependencies,
532				m.additionalLibraryDirectories,
533				m.importLibrary,
534				m.entryPointSymbol,
535				m.generateMapFile,
536				m.moduleDefinitionFile,
537				m.treatLinkerWarningAsErrors,
538				m.ignoreDefaultLibraries,
539				m.largeAddressAware,
540				m.targetMachine,
541				m.additionalLinkOptions,
542				m.programDatabaseFile,
543				m.assemblyDebug,
544			}
545		end
546	end
547
548	function m.link(cfg, explicit)
549		local contents = p.capture(function ()
550			p.push()
551			p.callArray(m.elements.link, cfg, explicit)
552			p.pop()
553		end)
554		if #contents > 0 then
555			p.push('<Link>')
556			p.outln(contents)
557			p.pop('</Link>')
558		end
559	end
560
561
562
563	m.elements.lib = function(cfg, explicit)
564		if cfg.kind == p.STATICLIB then
565			return {
566				m.additionalDependencies,
567				m.additionalLibraryDirectories,
568				m.treatLinkerWarningAsErrors,
569				m.targetMachine,
570				m.additionalLinkOptions,
571			}
572		else
573			return {}
574		end
575	end
576
577	function m.lib(cfg, explicit)
578		local contents = p.capture(function ()
579			p.push()
580			p.callArray(m.elements.lib, cfg, explicit)
581			p.pop()
582		end)
583		if #contents > 0 then
584			p.push('<Lib>')
585			p.outln(contents)
586			p.pop('</Lib>')
587		end
588	end
589
590
591
592--
593-- Write the manifest section.
594--
595
596	m.elements.manifest = function(cfg)
597		return {
598			m.enableDpiAwareness,
599			m.additionalManifestFiles,
600		}
601	end
602
603	function m.manifest(cfg)
604		if cfg.kind ~= p.STATICLIB then
605			local contents = p.capture(function ()
606				p.push()
607				p.callArray(m.elements.manifest, cfg)
608				p.pop()
609			end)
610			if #contents > 0 then
611				p.push('<Manifest>')
612				p.outln(contents)
613				p.pop('</Manifest>')
614			end
615		end
616	end
617
618
619
620---
621-- Write out the pre- and post-build event settings.
622---
623
624	function m.buildEvents(cfg)
625		local write = function (event)
626			local name = event .. "Event"
627			local field = event:lower()
628			local steps = cfg[field .. "commands"]
629			local msg = cfg[field .. "message"]
630
631			if #steps > 0 then
632				steps = os.translateCommandsAndPaths(steps, cfg.project.basedir, cfg.project.location)
633				p.push('<%s>', name)
634				p.x('<Command>%s</Command>', table.implode(steps, "", "", "\r\n"))
635				if msg then
636					p.x('<Message>%s</Message>', msg)
637				end
638				p.pop('</%s>', name)
639			end
640		end
641
642		write("PreBuild")
643		write("PreLink")
644		write("PostBuild")
645	end
646
647
648
649---
650-- Transform property to string
651---
652
653	function m.getRulePropertyString(rule, prop, value, kind)
654		-- list of paths
655		if kind == "list:path" then
656			return table.concat(vstudio.path(cfg, value), ';')
657		end
658
659		-- path
660		if kind == "path" then
661			return vstudio.path(cfg, value)
662		end
663
664		-- list
665		if type(value) == "table" then
666			return table.concat(value, ";")
667		end
668
669		-- enum
670		if prop.values then
671			value = table.findKeyByValue(prop.values, value)
672		end
673
674		-- primitive
675		return tostring(value)
676	end
677
678
679
680---
681-- Write out project-level custom rule variables.
682---
683
684	function m.ruleVars(cfg)
685		for i = 1, #cfg.rules do
686			local rule = p.global.getRule(cfg.rules[i])
687
688			local contents = p.capture(function ()
689				p.push()
690				for prop in p.rule.eachProperty(rule) do
691					local fld = p.rule.getPropertyField(rule, prop)
692					local value = cfg[fld.name]
693					if value ~= nil then
694						value = m.getRulePropertyString(rule, prop, value, fld.kind)
695
696						if value ~= nil and #value > 0 then
697							m.element(prop.name, nil, '%s', value)
698						end
699					end
700				end
701				p.pop()
702			end)
703
704			if #contents > 0 then
705				p.push('<%s>', rule.name)
706				p.outln(contents)
707				p.pop('</%s>', rule.name)
708			end
709		end
710	end
711
712
713--
714-- Reference any managed assemblies listed in the links()
715--
716
717	function m.assemblyReferences(prj)
718		-- Visual Studio doesn't support per-config references; use
719		-- whatever is contained in the first configuration
720		local cfg = project.getfirstconfig(prj)
721
722		local refs = config.getlinks(cfg, "system", "fullpath", "managed")
723		if #refs > 0 then
724			p.push('<ItemGroup>')
725			for i = 1, #refs do
726				local value = refs[i]
727
728				-- If the link contains a '/' then it is a relative path to
729				-- a local assembly. Otherwise treat it as a system assembly.
730				if value:find('/', 1, true) then
731					p.push('<Reference Include="%s">', path.getbasename(value))
732					p.x('<HintPath>%s</HintPath>', path.translate(value))
733					p.pop('</Reference>')
734				else
735					p.x('<Reference Include="%s" />', path.getbasename(value))
736				end
737			end
738			p.pop('</ItemGroup>')
739		end
740	end
741
742
743	function m.generatedFile(cfg, file)
744		if file.generated then
745			local path = path.translate(file.dependsOn.relpath)
746			m.element("AutoGen", nil, 'true')
747			m.element("DependentUpon", nil, path)
748		end
749	end
750
751
752---
753-- Write out the list of source code files, and any associated configuration.
754---
755
756	function m.files(prj)
757		local groups = m.categorizeSources(prj)
758		for _, group in ipairs(groups) do
759			group.category.emitFiles(prj, group)
760		end
761	end
762
763
764	m.categories = {}
765
766---
767-- ClInclude group
768---
769	m.categories.ClInclude = {
770		name       = "ClInclude",
771		extensions = { ".h", ".hh", ".hpp", ".hxx", ".inl" },
772		priority   = 1,
773
774		emitFiles = function(prj, group)
775			m.emitFiles(prj, group, "ClInclude", {m.generatedFile})
776		end,
777
778		emitFilter = function(prj, group)
779			m.filterGroup(prj, group, "ClInclude")
780		end
781	}
782
783
784---
785-- ClCompile group
786---
787	m.categories.ClCompile = {
788		name       = "ClCompile",
789		extensions = { ".cc", ".cpp", ".cxx", ".c++", ".c", ".s", ".m", ".mm" },
790		priority   = 2,
791
792		emitFiles = function(prj, group)
793			local fileCfgFunc = function(fcfg, condition)
794				if fcfg then
795					return {
796						m.excludedFromBuild,
797						m.objectFileName,
798						m.clCompilePreprocessorDefinitions,
799						m.clCompileUndefinePreprocessorDefinitions,
800						m.optimization,
801						m.forceIncludes,
802						m.precompiledHeader,
803						m.enableEnhancedInstructionSet,
804						m.additionalCompileOptions,
805						m.disableSpecificWarnings,
806						m.treatSpecificWarningsAsErrors,
807						m.basicRuntimeChecks,
808						m.exceptionHandling,
809						m.compileAsManaged,
810						m.compileAs,
811						m.runtimeTypeInfo,
812						m.warningLevelFile,
813					}
814				else
815					return {
816						m.excludedFromBuild
817					}
818				end
819			end
820
821			m.emitFiles(prj, group, "ClCompile", {m.generatedFile}, fileCfgFunc)
822		end,
823
824		emitFilter = function(prj, group)
825			m.filterGroup(prj, group, "ClCompile")
826		end
827	}
828
829
830---
831-- FxCompile group
832---
833	m.categories.FxCompile = {
834		name	   = "FxCompile",
835		extensions = { ".hlsl" },
836		priority   = 4,
837
838		emitFiles = function(prj, group)
839			local fileCfgFunc = function(fcfg, condition)
840				if fcfg then
841					return {
842						m.excludedFromBuild,
843						m.fxCompilePreprocessorDefinition,
844						m.fxCompileAdditionalIncludeDirs,
845						m.fxCompileShaderType,
846						m.fxCompileShaderModel,
847						m.fxCompileShaderEntry,
848						m.fxCompileShaderVariableName,
849						m.fxCompileShaderHeaderOutput,
850						m.fxCompileShaderObjectOutput,
851						m.fxCompileShaderAssembler,
852						m.fxCompileShaderAssemblerOutput,
853						m.fxCompileShaderAdditionalOptions,
854					}
855				else
856					return {
857						m.excludedFromBuild
858					}
859				end
860			end
861
862			m.emitFiles(prj, group, "FxCompile", nil, fileCfgFunc)
863		end,
864
865		emitFilter = function(prj, group)
866			m.filterGroup(prj, group, "FxCompile")
867		end
868	}
869
870
871---
872-- None group
873---
874	m.categories.None = {
875		name = "None",
876		priority = 5,
877
878		emitFiles = function(prj, group)
879			m.emitFiles(prj, group, "None", {m.generatedFile})
880		end,
881
882		emitFilter = function(prj, group)
883			m.filterGroup(prj, group, "None")
884		end
885	}
886
887
888---
889-- ResourceCompile group
890---
891	m.categories.ResourceCompile = {
892		name       = "ResourceCompile",
893		extensions = ".rc",
894		priority   = 6,
895
896		emitFiles = function(prj, group)
897			local fileCfgFunc = {
898				m.excludedFromBuild
899			}
900
901			m.emitFiles(prj, group, "ResourceCompile", nil, fileCfgFunc)
902		end,
903
904		emitFilter = function(prj, group)
905			m.filterGroup(prj, group, "ResourceCompile")
906		end
907	}
908
909
910---
911-- CustomBuild group
912---
913	m.categories.CustomBuild = {
914		name = "CustomBuild",
915		priority = 7,
916
917		emitFiles = function(prj, group)
918			local fileFunc = {
919				m.fileType
920			}
921
922			local fileCfgFunc = {
923				m.excludedFromBuild,
924				m.buildCommands,
925				m.buildOutputs,
926				m.linkObjects,
927				m.buildMessage,
928				m.buildAdditionalInputs
929			}
930
931			m.emitFiles(prj, group, "CustomBuild", fileFunc, fileCfgFunc, function (cfg, fcfg)
932				return fileconfig.hasCustomBuildRule(fcfg)
933			end)
934		end,
935
936		emitFilter = function(prj, group)
937			m.filterGroup(prj, group, "CustomBuild")
938		end
939	}
940
941
942---
943-- Midl group
944---
945	m.categories.Midl = {
946		name       = "Midl",
947		extensions = ".idl",
948		priority   = 8,
949
950		emitFiles = function(prj, group)
951			local fileCfgFunc = {
952				m.excludedFromBuild
953			}
954
955			m.emitFiles(prj, group, "Midl", nil, fileCfgFunc, function(cfg)
956				return cfg.system == p.WINDOWS
957			end)
958		end,
959
960		emitFilter = function(prj, group)
961			m.filterGroup(prj, group, "Midl")
962		end
963	}
964
965
966---
967-- Masm group
968---
969	m.categories.Masm = {
970		name       = "Masm",
971		extensions = ".asm",
972		priority   = 9,
973
974		emitFiles = function(prj, group)
975			local fileCfgFunc = function(fcfg, condition)
976				if fcfg then
977					return {
978						m.MasmPreprocessorDefinitions,
979						m.excludedFromBuild,
980						m.exceptionHandlingSEH,
981					}
982				else
983					return {
984						m.excludedFromBuild
985					}
986				end
987			end
988			m.emitFiles(prj, group, "Masm", nil, fileCfgFunc)
989		end,
990
991		emitFilter = function(prj, group)
992			m.filterGroup(prj, group, "Masm")
993		end,
994
995		emitExtensionSettings = function(prj, group)
996			p.w('<Import Project="$(VCTargetsPath)\\BuildCustomizations\\masm.props" />')
997		end,
998
999		emitExtensionTargets = function(prj, group)
1000			p.w('<Import Project="$(VCTargetsPath)\\BuildCustomizations\\masm.targets" />')
1001		end
1002	}
1003
1004
1005---
1006-- Image group
1007---
1008	m.categories.Image = {
1009		name       = "Image",
1010		extensions = { ".gif", ".jpg", ".jpe", ".png", ".bmp", ".dib", "*.tif", "*.wmf", "*.ras", "*.eps", "*.pcx", "*.pcd", "*.tga", "*.dds" },
1011		priority   = 10,
1012
1013		emitFiles = function(prj, group)
1014			local fileCfgFunc = function(fcfg, condition)
1015				return {
1016					m.excludedFromBuild
1017				}
1018			end
1019			m.emitFiles(prj, group, "Image", nil, fileCfgFunc)
1020		end,
1021
1022		emitFilter = function(prj, group)
1023			m.filterGroup(prj, group, "Image")
1024		end
1025	}
1026
1027
1028---
1029-- Natvis group
1030---
1031	m.categories.Natvis = {
1032		name       = "Natvis",
1033		extensions = { ".natvis" },
1034		priority   = 11,
1035
1036		emitFiles = function(prj, group)
1037			m.emitFiles(prj, group, "Natvis", {m.generatedFile})
1038		end,
1039
1040		emitFilter = function(prj, group)
1041			m.filterGroup(prj, group, "Natvis")
1042		end
1043	}
1044
1045
1046---
1047-- Categorize files into groups.
1048---
1049	function m.categorizeSources(prj)
1050		-- if we already did this, return the cached result.
1051		if prj._vc2010_sources then
1052			return prj._vc2010_sources
1053		end
1054
1055		-- build the new group table.
1056		local result = {}
1057		local groups = {}
1058		prj._vc2010_sources = result
1059
1060		local tr = project.getsourcetree(prj)
1061		tree.traverse(tr, {
1062			onleaf = function(node)
1063				local cat = m.categorizeFile(prj, node)
1064				groups[cat.name] = groups[cat.name] or {
1065					category = cat,
1066					files = {}
1067				}
1068				table.insert(groups[cat.name].files, node)
1069			end
1070		})
1071
1072		-- sort by relative-to path; otherwise VS will reorder the files
1073		for name, group in pairs(groups) do
1074			table.sort(group.files, function (a, b)
1075				return a.relpath < b.relpath
1076			end)
1077			table.insert(result, group)
1078		end
1079
1080		-- sort by category priority then name; so we get stable results.
1081		table.sort(result, function (a, b)
1082			if (a.category.priority == b.category.priority) then
1083				return a.category.name < b.category.name
1084			end
1085			return a.category.priority < b.category.priority
1086		end)
1087
1088		return result
1089	end
1090
1091
1092	function m.categorizeFile(prj, file)
1093		for cfg in project.eachconfig(prj) do
1094			local fcfg = fileconfig.getconfig(file, cfg)
1095			if fcfg then
1096				-- If any configuration for this file uses a custom build step, that's the category to use
1097				if fileconfig.hasCustomBuildRule(fcfg) then
1098					return m.categories.CustomBuild
1099				end
1100
1101				-- also check for buildaction
1102				if fcfg.buildaction then
1103					return m.categories[fcfg.buildaction] or m.categories.None
1104				end
1105			end
1106		end
1107
1108		-- If there is a custom rule associated with it, use that
1109		local rule = p.global.getRuleForFile(file.name, prj.rules)
1110		if rule then
1111			return {
1112				name      = rule.name,
1113				priority  = 100,
1114				rule      = rule,
1115				emitFiles = function(prj, group)
1116					m.emitRuleFiles(prj, group)
1117				end,
1118				emitFilter = function(prj, group)
1119					m.filterGroup(prj, group, group.category.name)
1120				end
1121			}
1122		end
1123
1124		-- Otherwise use the file extension to deduce a category
1125		for _, cat in pairs(m.categories) do
1126			if cat.extensions and path.hasextension(file.name, cat.extensions) then
1127				return cat
1128			end
1129		end
1130
1131		return m.categories.None
1132	end
1133
1134
1135	function m.configPair(cfg)
1136		return vstudio.projectPlatform(cfg) .. "|" .. vstudio.archFromConfig(cfg, true)
1137	end
1138
1139
1140	function m.getTotalCfgCount(prj)
1141		if prj._totalCfgCount then
1142			return prj._totalCfgCount
1143		else
1144			local result = 0
1145			for _ in p.project.eachconfig(prj) do
1146				result = result + 1
1147			end
1148			-- cache result
1149			prj._totalCfgCount = result
1150			return result
1151		end
1152	end
1153
1154
1155	function m.indexConditionalElements()
1156		local nameMap, nameList, settingList
1157		nameMap = {}
1158		nameList = {} -- to preserve ordering
1159		settingList = {} -- to preserve ordering
1160		for _, element in ipairs(m.conditionalElements) do
1161			local settingMap = nameMap[element.name]
1162			if not settingMap then
1163				settingMap = {}
1164				nameMap[element.name] = settingMap
1165				if not table.contains(nameList, element.name) then
1166					table.insert(nameList, element.name)
1167				end
1168			end
1169			--setting will either be value or args
1170			local elementSet = settingMap[element.setting]
1171			if elementSet then
1172				table.insert(elementSet, element)
1173			else
1174				elementSet = {element}
1175				settingMap[element.setting] = elementSet
1176				if not table.contains(settingList, element.setting) then
1177					table.insert(settingList, element.setting)
1178				end
1179			end
1180		end
1181		return nameMap, nameList, settingList
1182	end
1183
1184
1185	function m.emitConditionalElements(prj)
1186		local keyCount = function(tbl)
1187			local count = 0
1188			for _ in pairs(tbl) do count = count + 1 end
1189			return count
1190		end
1191
1192		local nameMap, nameList, settingList
1193		nameMap, nameList, settingList = m.indexConditionalElements()
1194
1195		local totalCfgCount = m.getTotalCfgCount(prj)
1196		for _, name in ipairs(nameList) do
1197			local settingMap = nameMap[name]
1198			local done = false
1199			if keyCount(settingMap)==1 then
1200				for _, setting in ipairs(settingList) do
1201					local elements = settingMap[setting]
1202					if elements~=nil and #elements==totalCfgCount then
1203						local element = elements[1]
1204						local format = string.format('<%s>%s</%s>', name, element.value, name)
1205						p.w(format, table.unpack(element.args))
1206						done = true
1207					end
1208				end
1209			end
1210			if not done then
1211				for _, setting in ipairs(settingList) do
1212					local elements = settingMap[setting]
1213					if elements then
1214						for _, element in ipairs(elements) do
1215							local format = string.format('<%s %s>%s</%s>', name, m.conditionFromConfigText(element.condition), element.value, name)
1216							p.w(format, table.unpack(element.args))
1217						end
1218					end
1219				end
1220			end
1221		end
1222	end
1223
1224	function m.emitFiles(prj, group, tag, fileFunc, fileCfgFunc, checkFunc)
1225		local files = group.files
1226		if files and #files > 0 then
1227			p.push('<ItemGroup>')
1228			for _, file in ipairs(files) do
1229
1230				local contents = p.capture(function ()
1231					p.push()
1232					p.callArray(fileFunc, cfg, file)
1233					m.conditionalElements = {}
1234					for cfg in project.eachconfig(prj) do
1235						local fcfg = fileconfig.getconfig(file, cfg)
1236						if not checkFunc or checkFunc(cfg, fcfg) then
1237							p.callArray(fileCfgFunc, fcfg, m.configPair(cfg))
1238						end
1239					end
1240					if #m.conditionalElements > 0 then
1241						m.emitConditionalElements(prj)
1242					end
1243					p.pop()
1244				end)
1245
1246				local rel = path.translate(file.relpath)
1247
1248				-- SharedItems projects paths are prefixed with a magical variable
1249				if prj.kind == p.SHAREDITEMS then
1250					rel = "$(MSBuildThisFileDirectory)" .. rel
1251				end
1252
1253				if #contents > 0 then
1254					p.push('<%s Include="%s">', tag, rel)
1255					p.outln(contents)
1256					p.pop('</%s>', tag)
1257				else
1258					p.x('<%s Include="%s" />', tag, rel)
1259				end
1260
1261			end
1262			p.pop('</ItemGroup>')
1263		end
1264	end
1265
1266	function m.emitRuleFiles(prj, group)
1267		local files = group.files
1268		local rule = group.category.rule
1269
1270		if files and #files > 0 then
1271			p.push('<ItemGroup>')
1272
1273			for _, file in ipairs(files) do
1274				local contents = p.capture(function()
1275					p.push()
1276					for prop in p.rule.eachProperty(rule) do
1277						local fld = p.rule.getPropertyField(rule, prop)
1278						m.conditionalElements = {}
1279						for cfg in project.eachconfig(prj) do
1280							local fcfg = fileconfig.getconfig(file, cfg)
1281							if fcfg and fcfg[fld.name] then
1282								local value = m.getRulePropertyString(rule, prop, fcfg[fld.name])
1283								if value and #value > 0 then
1284									m.element(prop.name, m.configPair(cfg), '%s', value)
1285								end
1286								end
1287							end
1288						if #m.conditionalElements > 0 then
1289							m.emitConditionalElements(prj)
1290						end
1291					end
1292					p.pop()
1293				end)
1294
1295				if #contents > 0 then
1296					p.push('<%s Include=\"%s\">', rule.name, path.translate(file.relpath))
1297					p.outln(contents)
1298					p.pop('</%s>', rule.name)
1299				else
1300					p.x('<%s Include=\"%s\" />', rule.name, path.translate(file.relpath))
1301				end
1302			end
1303
1304			p.pop('</ItemGroup>')
1305		end
1306	end
1307
1308
1309	function m.isClrMixed(prj)
1310		-- check to see if any files are marked with clr
1311		local isMixed = false
1312		if not prj.clr or prj.clr == p.OFF then
1313			if prj._isClrMixed ~= nil then
1314				isMixed = prj._isClrMixed
1315			else
1316				table.foreachi(prj._.files, function(file)
1317					for cfg in p.project.eachconfig(prj) do
1318						local fcfg = p.fileconfig.getconfig(file, cfg)
1319						if fcfg and fcfg.clr and fcfg.clr ~= p.OFF then
1320							isMixed = true
1321						end
1322					end
1323				end)
1324				prj._isClrMixed = isMixed -- cache the results
1325			end
1326		end
1327		return isMixed
1328	end
1329
1330
1331--
1332-- Generate the list of project dependencies.
1333--
1334
1335	m.elements.projectReferences = function(prj, ref)
1336		if prj.clr ~= p.OFF or (m.isClrMixed(prj) and ref and ref.kind ~=p.STATICLIB) then
1337			return {
1338				m.referenceProject,
1339				m.referencePrivate,
1340				m.referenceOutputAssembly,
1341				m.referenceCopyLocalSatelliteAssemblies,
1342				m.referenceLinkLibraryDependencies,
1343				m.referenceUseLibraryDependences,
1344			}
1345		else
1346			return {
1347				m.referenceProject,
1348			}
1349		end
1350	end
1351
1352	function m.projectReferences(prj)
1353		local refs = project.getdependencies(prj, 'linkOnly')
1354		-- Handle linked shared items projects
1355		local contents = p.capture(function()
1356			p.push()
1357			for _, ref in ipairs(refs) do
1358				if ref.kind == p.SHAREDITEMS then
1359					local relpath = vstudio.path(prj, vstudio.projectfile(ref))
1360					p.x('<Import Project="%s" Label="Shared" />', relpath)
1361				end
1362			end
1363			p.pop()
1364		end)
1365		if #contents > 0 then
1366			p.push('<ImportGroup Label="Shared">')
1367			p.outln(contents)
1368			p.pop('</ImportGroup>')
1369		end
1370
1371		-- Handle all other linked projects
1372		local contents = p.capture(function()
1373			p.push()
1374			for _, ref in ipairs(refs) do
1375				if ref.kind ~= p.SHAREDITEMS then
1376					local relpath = vstudio.path(prj, vstudio.projectfile(ref))
1377					p.push('<ProjectReference Include=\"%s\">', relpath)
1378					p.callArray(m.elements.projectReferences, prj, ref)
1379					p.pop('</ProjectReference>')
1380				end
1381			end
1382			p.pop()
1383		end)
1384		if #contents > 0 then
1385			p.push('<ItemGroup>')
1386			p.outln(contents)
1387			p.pop('</ItemGroup>')
1388		end
1389	end
1390
1391
1392
1393---------------------------------------------------------------------------
1394--
1395-- Handlers for individual project elements
1396--
1397---------------------------------------------------------------------------
1398
1399	function m.additionalDependencies(cfg, explicit)
1400		local links
1401
1402		-- check to see if this project uses an external toolset. If so, let the
1403		-- toolset define the format of the links
1404		local toolset = config.toolset(cfg)
1405		if cfg.system ~= premake.WINDOWS and toolset then
1406			links = toolset.getlinks(cfg, not explicit)
1407		else
1408			links = vstudio.getLinks(cfg, explicit)
1409		end
1410
1411		if #links > 0 then
1412			links = path.translate(table.concat(links, ";"))
1413			m.element("AdditionalDependencies", nil, "%s;%%(AdditionalDependencies)", links)
1414		end
1415	end
1416
1417
1418	function m.additionalIncludeDirectories(cfg, includedirs)
1419		if #includedirs > 0 then
1420			local dirs = vstudio.path(cfg, includedirs)
1421			if #dirs > 0 then
1422				m.element("AdditionalIncludeDirectories", nil, "%s;%%(AdditionalIncludeDirectories)", table.concat(dirs, ";"))
1423			end
1424		end
1425	end
1426
1427
1428	function m.additionalLibraryDirectories(cfg)
1429		if #cfg.libdirs > 0 then
1430			local dirs = table.concat(vstudio.path(cfg, cfg.libdirs), ";")
1431			m.element("AdditionalLibraryDirectories", nil, "%s;%%(AdditionalLibraryDirectories)", dirs)
1432		end
1433	end
1434
1435
1436	function m.additionalManifestFiles(cfg)
1437		-- get the manifests files
1438		local manifests = {}
1439		for _, fname in ipairs(cfg.files) do
1440			if path.getextension(fname) == ".manifest" then
1441				table.insert(manifests, project.getrelative(cfg.project, fname))
1442			end
1443		end
1444
1445		if #manifests > 0 then
1446			m.element("AdditionalManifestFiles", nil, "%s;%%(AdditionalManifestFiles)", table.concat(manifests, ";"))
1447		end
1448	end
1449
1450
1451	function m.additionalUsingDirectories(cfg)
1452		if #cfg.usingdirs > 0 then
1453			local dirs = vstudio.path(cfg, cfg.usingdirs)
1454			if #dirs > 0 then
1455				m.element("AdditionalUsingDirectories", nil, "%s;%%(AdditionalUsingDirectories)", table.concat(dirs, ";"))
1456			end
1457		end
1458	end
1459
1460
1461	function m.largeAddressAware(cfg)
1462		if (cfg.largeaddressaware == true) then
1463			m.element("LargeAddressAware", nil, 'true')
1464		end
1465	end
1466
1467
1468	function m.languageStandard(cfg)
1469		if _ACTION >= "vs2017" then
1470			if (cfg.cppdialect == "C++14") then
1471				m.element("LanguageStandard", nil, 'stdcpp14')
1472			elseif (cfg.cppdialect == "C++17") then
1473				m.element("LanguageStandard", nil, 'stdcpp17')
1474			elseif (cfg.cppdialect == "C++20") then
1475				m.element("LanguageStandard", nil, 'stdcpplatest')
1476			elseif (cfg.cppdialect == "C++latest") then
1477				m.element("LanguageStandard", nil, 'stdcpplatest')
1478			end
1479		end
1480	end
1481
1482	function m.conformanceMode(cfg)
1483		if _ACTION >= "vs2017" then
1484			if cfg.conformancemode ~= nil then
1485				if cfg.conformancemode then
1486					m.element("ConformanceMode", nil, "true")
1487				else
1488					m.element("ConformanceMode", nil, "false")
1489				end
1490			end
1491		end
1492	end
1493
1494	function m.structMemberAlignment(cfg)
1495		local map = {
1496			[1] = "1Byte",
1497			[2] = "2Bytes",
1498			[4] = "4Bytes",
1499			[8] = "8Bytes",
1500			[16] = "16Bytes"
1501		}
1502
1503		local value = map[cfg.structmemberalign]
1504		if value then
1505			m.element("StructMemberAlignment", nil, value)
1506		end
1507	end
1508
1509	function m.useFullPaths(cfg)
1510		if cfg.useFullPaths ~= nil then
1511			if cfg.useFullPaths then
1512				m.element("UseFullPaths", nil, "true")
1513			else
1514				m.element("UseFullPaths", nil, "false")
1515			end
1516		end
1517	end
1518
1519	function m.removeUnreferencedCodeData(cfg)
1520		if cfg.removeUnreferencedCodeData ~= nil then
1521			if cfg.removeUnreferencedCodeData then
1522				m.element("RemoveUnreferencedCodeData", nil, "true")
1523			else
1524				m.element("RemoveUnreferencedCodeData", nil, "false")
1525			end
1526		end
1527	end
1528
1529	function m.additionalCompileOptions(cfg, condition)
1530		local opts = cfg.buildoptions
1531		if _ACTION == "vs2015" or vstudio.isMakefile(cfg) then
1532			if (cfg.cppdialect == "C++14") then
1533				table.insert(opts, "/std:c++14")
1534			elseif (cfg.cppdialect == "C++17") then
1535				table.insert(opts, "/std:c++17")
1536			elseif (cfg.cppdialect == "C++20") then
1537				table.insert(opts, "/std:c++latest")
1538			elseif (cfg.cppdialect == "C++latest") then
1539				table.insert(opts, "/std:c++latest")
1540			end
1541		end
1542
1543		if cfg.toolset and cfg.toolset:startswith("msc") then
1544			local value = iif(cfg.unsignedchar, "On", "Off")
1545			table.insert(opts, p.tools.msc.shared.unsignedchar[value])
1546		elseif _ACTION >= "vs2019" and cfg.toolset and cfg.toolset == "clang" then
1547			local value = iif(cfg.unsignedchar, "On", "Off")
1548			table.insert(opts, p.tools.msc.shared.unsignedchar[value])
1549		end
1550
1551		if #opts > 0 then
1552			opts = table.concat(opts, " ")
1553			m.element("AdditionalOptions", condition, '%s %%(AdditionalOptions)', opts)
1554		end
1555	end
1556
1557
1558	function m.additionalLinkOptions(cfg)
1559		if #cfg.linkoptions > 0 then
1560			local opts = table.concat(cfg.linkoptions, " ")
1561			m.element("AdditionalOptions", nil, "%s %%(AdditionalOptions)", opts)
1562		end
1563	end
1564
1565
1566	function m.compileAsManaged(fcfg, condition)
1567		if fcfg.clr and fcfg ~= p.OFF then
1568			m.element("CompileAsManaged", condition, "true")
1569		end
1570	end
1571
1572
1573	function m.basicRuntimeChecks(cfg, condition)
1574		local prjcfg, filecfg = p.config.normalize(cfg)
1575		local runtime = config.getruntime(prjcfg) or iif(config.isDebugBuild(cfg), "Debug", "Release")
1576		if filecfg then
1577			if filecfg.flags.NoRuntimeChecks or (config.isOptimizedBuild(filecfg) and runtime:endswith("Debug")) then
1578				m.element("BasicRuntimeChecks", condition, "Default")
1579			end
1580		else
1581			if prjcfg.flags.NoRuntimeChecks or (config.isOptimizedBuild(prjcfg) and runtime:endswith("Debug")) then
1582				m.element("BasicRuntimeChecks", nil, "Default")
1583			end
1584		end
1585	end
1586
1587	function m.buildInputs(cfg, condition)
1588		if cfg.buildinputs and #cfg.buildinputs > 0 then
1589			local inputs = project.getrelative(cfg.project, cfg.buildinputs)
1590			m.element("Inputs", condition, '%s', table.concat(inputs, ";"))
1591		end
1592	end
1593
1594	function m.buildAdditionalInputs(fcfg, condition)
1595		if fcfg.buildinputs and #fcfg.buildinputs > 0 then
1596			local inputs = project.getrelative(fcfg.project, fcfg.buildinputs)
1597			m.element("AdditionalInputs", condition, '%s', table.concat(inputs, ";"))
1598		end
1599	end
1600
1601
1602	function m.buildCommands(fcfg, condition)
1603		if #fcfg.buildcommands > 0 then
1604			local commands = os.translateCommandsAndPaths(fcfg.buildcommands, fcfg.project.basedir, fcfg.project.location)
1605			m.element("Command", condition, '%s', table.concat(commands,'\r\n'))
1606		end
1607	end
1608
1609
1610	function m.buildLog(cfg)
1611		if cfg.buildlog and #cfg.buildlog > 0 then
1612			p.push('<BuildLog>')
1613			m.element("Path", nil, "%s", vstudio.path(cfg, cfg.buildlog))
1614			p.pop('</BuildLog>')
1615		end
1616	end
1617
1618
1619	function m.buildMessage(fcfg, condition)
1620		if fcfg.buildmessage then
1621			m.element("Message", condition, '%s', fcfg.buildmessage)
1622		end
1623	end
1624
1625
1626	function m.buildOutputs(fcfg, condition)
1627		if #fcfg.buildoutputs > 0 then
1628			local outputs = project.getrelative(fcfg.project, fcfg.buildoutputs)
1629			m.element("Outputs", condition, '%s', table.concat(outputs, ";"))
1630		end
1631	end
1632
1633
1634	function m.linkObjects(fcfg, condition)
1635		if fcfg.linkbuildoutputs ~= nil then
1636			m.element("LinkObjects", condition, tostring(fcfg.linkbuildoutputs))
1637		end
1638	end
1639
1640
1641	function m.characterSet(cfg)
1642		if not vstudio.isMakefile(cfg) then
1643			local charactersets = {
1644				ASCII = "NotSet",
1645				MBCS = "MultiByte",
1646				Unicode = "Unicode",
1647				Default = "Unicode"
1648			}
1649			m.element("CharacterSet", nil, charactersets[cfg.characterset])
1650		end
1651	end
1652
1653
1654	function m.wholeProgramOptimization(cfg)
1655		if cfg.flags.LinkTimeOptimization then
1656			m.element("WholeProgramOptimization", nil, "true")
1657		end
1658	end
1659
1660	function m.clCompileAdditionalIncludeDirectories(cfg)
1661		m.additionalIncludeDirectories(cfg, cfg.includedirs)
1662	end
1663
1664	function m.clCompileAdditionalUsingDirectories(cfg)
1665		m.additionalUsingDirectories(cfg, cfg.usingdirs)
1666	end
1667
1668
1669	function m.clCompilePreprocessorDefinitions(cfg, condition)
1670		local defines = cfg.defines
1671		if cfg.exceptionhandling == p.OFF then
1672			defines = table.join(defines, "_HAS_EXCEPTIONS=0")
1673		end
1674		m.preprocessorDefinitions(cfg, defines, false, condition)
1675	end
1676
1677
1678	function m.clCompileUndefinePreprocessorDefinitions(cfg, condition)
1679		m.undefinePreprocessorDefinitions(cfg, cfg.undefines, false, condition)
1680	end
1681
1682
1683	function m.clrSupport(cfg)
1684		local value
1685		if cfg.clr == "On" or cfg.clr == "Unsafe" then
1686			value = "true"
1687		elseif cfg.clr ~= p.OFF then
1688			value = cfg.clr
1689		end
1690		if value then
1691			m.element("CLRSupport", nil, value)
1692		end
1693	end
1694
1695
1696	function m.compileAs(cfg, condition)
1697		if p.languages.isc(cfg.compileas) then
1698			m.element("CompileAs", condition, "CompileAsC")
1699		elseif p.languages.iscpp(cfg.compileas) then
1700			m.element("CompileAs", condition, "CompileAsCpp")
1701		end
1702	end
1703
1704
1705	function m.configurationType(cfg)
1706		local types = {
1707			SharedLib = "DynamicLibrary",
1708			StaticLib = "StaticLibrary",
1709			ConsoleApp = "Application",
1710			WindowedApp = "Application",
1711			Makefile = "Makefile",
1712			None = "Makefile",
1713			Utility = "Utility",
1714		}
1715		m.element("ConfigurationType", nil, types[cfg.kind])
1716	end
1717
1718
1719	function m.culture(cfg)
1720		local value = vstudio.cultureForLocale(cfg.locale)
1721		if value then
1722			m.element("Culture", nil, "0x%04x", tostring(value))
1723		end
1724	end
1725
1726
1727	function m.debugInformationFormat(cfg)
1728		local value
1729		local tool, toolVersion = p.config.toolset(cfg)
1730		if (cfg.symbols == p.ON) or (cfg.symbols == "FastLink") or (cfg.symbols == "Full") then
1731			if cfg.debugformat == "c7" then
1732				value = "OldStyle"
1733			elseif (cfg.architecture == "x86_64" and _ACTION < "vs2015") or
1734				   cfg.clr ~= p.OFF or
1735				   config.isOptimizedBuild(cfg) or
1736				   cfg.editandcontinue == p.OFF or
1737				   (toolVersion and toolVersion:startswith("LLVM-vs"))
1738			then
1739				value = "ProgramDatabase"
1740			else
1741				value = "EditAndContinue"
1742			end
1743
1744			m.element("DebugInformationFormat", nil, value)
1745		elseif cfg.symbols == p.OFF then
1746			-- leave field blank for vs2013 and older to workaround bug
1747			if _ACTION < "vs2015" then
1748				value = ""
1749			else
1750				value = "None"
1751			end
1752
1753			m.element("DebugInformationFormat", nil, value)
1754		end
1755	end
1756
1757
1758	function m.enableDpiAwareness(cfg)
1759		local awareness = {
1760			None = "false",
1761			High = "true",
1762			HighPerMonitor = "PerMonitorHighDPIAware",
1763		}
1764		local value = awareness[cfg.dpiawareness]
1765
1766		if value then
1767			m.element("EnableDpiAwareness", nil, value)
1768		end
1769	end
1770
1771
1772	function m.enableEnhancedInstructionSet(cfg, condition)
1773		local v
1774		local x = cfg.vectorextensions
1775		if x == "AVX" and _ACTION > "vs2010" then
1776			v = "AdvancedVectorExtensions"
1777		elseif x == "AVX2" and _ACTION > "vs2012" then
1778			v = "AdvancedVectorExtensions2"
1779		elseif cfg.architecture ~= "x86_64" then
1780			if x == "SSE2" or x == "SSE3" or x == "SSSE3" or x == "SSE4.1" then
1781				v = "StreamingSIMDExtensions2"
1782			elseif x == "SSE" then
1783				v = "StreamingSIMDExtensions"
1784			elseif x == "IA32" and _ACTION > "vs2010" then
1785				v = "NoExtensions"
1786			end
1787		end
1788		if v then
1789			m.element('EnableEnhancedInstructionSet', condition, v)
1790		end
1791	end
1792
1793
1794	function m.entryPointSymbol(cfg)
1795		if cfg.entrypoint then
1796			m.element("EntryPointSymbol", nil, cfg.entrypoint)
1797		end
1798	end
1799
1800
1801	function m.exceptionHandling(cfg, condition)
1802		if cfg.exceptionhandling == p.OFF then
1803			m.element("ExceptionHandling", condition, "false")
1804		elseif cfg.exceptionhandling == "SEH" then
1805			m.element("ExceptionHandling", condition, "Async")
1806		elseif cfg.exceptionhandling == "On" then
1807			m.element("ExceptionHandling", condition, "Sync")
1808		elseif cfg.exceptionhandling == "CThrow" then
1809			m.element("ExceptionHandling", condition, "SyncCThrow")
1810		end
1811	end
1812
1813
1814	function m.excludedFromBuild(filecfg, condition)
1815		if not filecfg or filecfg.flags.ExcludeFromBuild then
1816			m.element("ExcludedFromBuild", condition, "true")
1817		end
1818	end
1819
1820
1821	function m.exceptionHandlingSEH(filecfg, condition)
1822		if not filecfg or filecfg.exceptionhandling == "SEH" then
1823			m.element("UseSafeExceptionHandlers", condition, "true")
1824		end
1825	end
1826
1827
1828	function m.extensionsToDeleteOnClean(cfg)
1829		if #cfg.cleanextensions > 0 then
1830			local value = table.implode(cfg.cleanextensions, "*", ";", "")
1831			m.element("ExtensionsToDeleteOnClean", nil, value .. "$(ExtensionsToDeleteOnClean)")
1832		end
1833	end
1834
1835
1836	function m.fileType(cfg, file)
1837		m.element("FileType", nil, "Document")
1838	end
1839
1840
1841	function m.floatingPointModel(cfg)
1842		if cfg.floatingpoint and cfg.floatingpoint ~= "Default" then
1843			m.element("FloatingPointModel", nil, cfg.floatingpoint)
1844		end
1845	end
1846
1847
1848	function m.floatingPointExceptions(cfg)
1849		if cfg.floatingpointexceptions ~= nil then
1850			if cfg.floatingpointexceptions then
1851				m.element("FloatingPointExceptions", nil, "true")
1852			else
1853				m.element("FloatingPointExceptions", nil, "false")
1854			end
1855		end
1856	end
1857
1858
1859	function m.inlineFunctionExpansion(cfg)
1860		if cfg.inlining then
1861			local types = {
1862				Default = "Default",
1863				Disabled = "Disabled",
1864				Explicit = "OnlyExplicitInline",
1865				Auto = "AnySuitable",
1866			}
1867			m.element("InlineFunctionExpansion", nil, types[cfg.inlining])
1868		end
1869	end
1870
1871
1872	function m.forceIncludes(cfg, condition)
1873		if #cfg.forceincludes > 0 then
1874			local includes = vstudio.path(cfg, cfg.forceincludes)
1875			if #includes > 0 then
1876				m.element("ForcedIncludeFiles", condition, table.concat(includes, ';'))
1877			end
1878		end
1879		if #cfg.forceusings > 0 then
1880			local usings = vstudio.path(cfg, cfg.forceusings)
1881			if #usings > 0 then
1882				m.element("ForcedUsingFiles", condition, table.concat(usings, ';'))
1883			end
1884		end
1885	end
1886
1887
1888	function m.fullProgramDatabaseFile(cfg)
1889		if _ACTION >= "vs2015" and cfg.symbols == "FastLink" then
1890			m.element("FullProgramDatabaseFile", nil, "true")
1891		end
1892	end
1893
1894	function m.assemblyDebug(cfg)
1895		if cfg.assemblydebug then
1896      		m.element("AssemblyDebug", nil, "true")
1897		end
1898	end
1899
1900
1901	function m.functionLevelLinking(cfg)
1902		if cfg.functionlevellinking ~= nil then
1903			if cfg.functionlevellinking then
1904				m.element("FunctionLevelLinking", nil, "true")
1905			else
1906				m.element("FunctionLevelLinking", nil, "false")
1907			end
1908		elseif config.isOptimizedBuild(cfg) then
1909			m.element("FunctionLevelLinking", nil, "true")
1910		end
1911	end
1912
1913
1914	function m.generateDebugInformation(cfg)
1915		local lookup = {}
1916		if _ACTION >= "vs2017" then
1917			lookup[p.ON]       = "true"
1918			lookup[p.OFF]      = "false"
1919			lookup["FastLink"] = "DebugFastLink"
1920			lookup["Full"]     = "DebugFull"
1921		elseif _ACTION == "vs2015" then
1922			lookup[p.ON]       = "true"
1923			lookup[p.OFF]      = "false"
1924			lookup["FastLink"] = "DebugFastLink"
1925			lookup["Full"]     = "true"
1926		else
1927			lookup[p.ON]       = "true"
1928			lookup[p.OFF]      = "false"
1929			lookup["FastLink"] = "true"
1930			lookup["Full"]     = "true"
1931		end
1932
1933		local value = lookup[cfg.symbols]
1934		if value then
1935			m.element("GenerateDebugInformation", nil, value)
1936		end
1937	end
1938
1939
1940	function m.generateManifest(cfg)
1941		if cfg.flags.NoManifest then
1942			m.element("GenerateManifest", nil, "false")
1943		end
1944	end
1945
1946
1947	function m.generateMapFile(cfg)
1948		if cfg.flags.Maps then
1949			m.element("GenerateMapFile", nil, "true")
1950		end
1951	end
1952
1953
1954	function m.ignoreDefaultLibraries(cfg)
1955		if #cfg.ignoredefaultlibraries > 0 then
1956			local ignored = cfg.ignoredefaultlibraries
1957			for i = 1, #ignored do
1958				-- Add extension if required
1959				if not p.tools.msc.getLibraryExtensions()[ignored[i]:match("[^.]+$")] then
1960					ignored[i] = path.appendextension(ignored[i], ".lib")
1961				end
1962			end
1963
1964			m.element("IgnoreSpecificDefaultLibraries", condition, table.concat(ignored, ';'))
1965		end
1966	end
1967
1968
1969	function m.ignoreWarnDuplicateFilename(prj)
1970		-- VS 2013 warns on duplicate file names, even those files which are
1971		-- contained in different, mututally exclusive configurations. See:
1972		-- http://connect.microsoft.com/VisualStudio/feedback/details/797460/incorrect-warning-msb8027-reported-for-files-excluded-from-build
1973		-- Premake already adds unique object names to conflicting file names, so
1974		-- just go ahead and disable that warning.
1975		if _ACTION > "vs2012" then
1976			m.element("IgnoreWarnCompileDuplicatedFilename", nil, "true")
1977		end
1978	end
1979
1980
1981	function m.ignoreImportLibrary(cfg)
1982		if cfg.kind == p.SHAREDLIB and cfg.flags.NoImportLib then
1983			m.element("IgnoreImportLibrary", nil, "true")
1984		end
1985	end
1986
1987
1988	function m.importLanguageTargets(prj)
1989		p.w('<Import Project="$(VCTargetsPath)\\Microsoft.Cpp.targets" />')
1990	end
1991
1992	m.elements.importExtensionTargets = function(prj)
1993		return {
1994			m.importGroupTargets,
1995			m.importRuleTargets,
1996			m.importNuGetTargets,
1997			m.importBuildCustomizationsTargets
1998		}
1999	end
2000
2001	function m.importExtensionTargets(prj)
2002		p.push('<ImportGroup Label="ExtensionTargets">')
2003		p.callArray(m.elements.importExtensionTargets, prj)
2004		p.pop('</ImportGroup>')
2005	end
2006
2007	function m.importGroupTargets(prj)
2008		local groups = m.categorizeSources(prj)
2009		for _, group in ipairs(groups) do
2010			if group.category.emitExtensionTargets then
2011				group.category.emitExtensionTargets(prj, group)
2012			end
2013		end
2014	end
2015
2016	function m.importRuleTargets(prj)
2017		for i = 1, #prj.rules do
2018			local rule = p.global.getRule(prj.rules[i])
2019			local loc = vstudio.path(prj, p.filename(rule, ".targets"))
2020			p.x('<Import Project="%s" />', loc)
2021		end
2022	end
2023
2024	local function nuGetTargetsFile(prj, package)
2025		local packageAPIInfo = vstudio.nuget2010.packageAPIInfo(prj, package)
2026		return p.vstudio.path(prj, p.filename(prj.workspace, string.format("packages\\%s.%s\\build\\native\\%s.targets", vstudio.nuget2010.packageId(package), packageAPIInfo.verbatimVersion or packageAPIInfo.version, vstudio.nuget2010.packageId(package))))
2027	end
2028
2029	function m.importNuGetTargets(prj)
2030		if not vstudio.nuget2010.supportsPackageReferences(prj) then
2031			for i = 1, #prj.nuget do
2032				local targetsFile = nuGetTargetsFile(prj, prj.nuget[i])
2033				p.x('<Import Project="%s" Condition="Exists(\'%s\')" />', targetsFile, targetsFile)
2034			end
2035		end
2036	end
2037
2038	function m.importBuildCustomizationsTargets(prj)
2039		for i, build in ipairs(prj.buildcustomizations) do
2040			p.w('<Import Project="$(VCTargetsPath)\\%s.targets" />', path.translate(build))
2041		end
2042	end
2043
2044
2045
2046	function m.ensureNuGetPackageBuildImports(prj)
2047		if #prj.nuget > 0 then
2048			p.push('<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">')
2049			p.push('<PropertyGroup>')
2050			p.x('<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>')
2051			p.pop('</PropertyGroup>')
2052
2053			for i = 1, #prj.nuget do
2054				local targetsFile = nuGetTargetsFile(prj, prj.nuget[i])
2055				p.x('<Error Condition="!Exists(\'%s\')" Text="$([System.String]::Format(\'$(ErrorText)\', \'%s\'))" />', targetsFile, targetsFile)
2056			end
2057			p.pop('</Target>')
2058		end
2059	end
2060
2061
2062
2063	function m.importDefaultProps(prj)
2064		p.w('<Import Project="$(VCTargetsPath)\\Microsoft.Cpp.Default.props" />')
2065	end
2066
2067
2068
2069	function m.importLanguageSettings(prj)
2070		p.w('<Import Project="$(VCTargetsPath)\\Microsoft.Cpp.props" />')
2071	end
2072
2073	m.elements.importExtensionSettings = function(prj)
2074		return {
2075			m.importGroupSettings,
2076			m.importRuleSettings,
2077			m.importBuildCustomizationsProps
2078		}
2079	end
2080
2081	function m.importExtensionSettings(prj)
2082		p.push('<ImportGroup Label="ExtensionSettings">')
2083		p.callArray(m.elements.importExtensionSettings, prj)
2084		p.pop('</ImportGroup>')
2085	end
2086
2087
2088	function m.importGroupSettings(prj)
2089		local groups = m.categorizeSources(prj)
2090		for _, group in ipairs(groups) do
2091			if group.category.emitExtensionSettings then
2092				group.category.emitExtensionSettings(prj, group)
2093			end
2094		end
2095	end
2096
2097
2098	function m.importRuleSettings(prj)
2099		for i = 1, #prj.rules do
2100			local rule = p.global.getRule(prj.rules[i])
2101			local loc = vstudio.path(prj, p.filename(rule, ".props"))
2102			p.x('<Import Project="%s" />', loc)
2103		end
2104	end
2105
2106
2107	function m.importBuildCustomizationsProps(prj)
2108		for i, build in ipairs(prj.buildcustomizations) do
2109			p.w('<Import Project="$(VCTargetsPath)\\%s.props" />', path.translate(build))
2110		end
2111	end
2112
2113
2114
2115	function m.importLibrary(cfg)
2116		if cfg.kind == p.SHAREDLIB then
2117			m.element("ImportLibrary", nil, "%s", path.translate(cfg.linktarget.relpath))
2118		end
2119	end
2120
2121
2122	function m.includePath(cfg)
2123		local dirs = vstudio.path(cfg, cfg.sysincludedirs)
2124		if #dirs > 0 then
2125			m.element("IncludePath", nil, "%s;$(IncludePath)", table.concat(dirs, ";"))
2126		end
2127	end
2128
2129
2130	function m.intDir(cfg)
2131		local objdir = vstudio.path(cfg, cfg.objdir)
2132		m.element("IntDir", nil, "%s\\", objdir)
2133	end
2134
2135
2136	function m.intrinsicFunctions(cfg)
2137		if cfg.intrinsics ~= nil then
2138			if cfg.intrinsics then
2139				m.element("IntrinsicFunctions", nil, "true")
2140			else
2141				m.element("IntrinsicFunctions", nil, "false")
2142			end
2143		elseif config.isOptimizedBuild(cfg) then
2144			m.element("IntrinsicFunctions", nil, "true")
2145		end
2146	end
2147
2148	function m.justMyCodeDebugging(cfg)
2149		local jmc = cfg.justmycode
2150
2151		if _ACTION >= "vs2017" and jmc == "Off" then
2152			m.element("SupportJustMyCode", nil, "false")
2153		end
2154	end
2155
2156	function m.keyword(prj)
2157		-- try to determine what kind of targets we're building here
2158		local isWin, isManaged, isMakefile
2159		for cfg in project.eachconfig(prj) do
2160			if cfg.system == p.WINDOWS then
2161				isWin = true
2162			end
2163			if cfg.clr ~= p.OFF then
2164				isManaged = true
2165			end
2166			if vstudio.isMakefile(cfg) then
2167				isMakefile = true
2168			end
2169		end
2170
2171		if isWin then
2172			if isMakefile then
2173				m.element("Keyword", nil, "MakeFileProj")
2174			else
2175				if isManaged or m.isClrMixed(prj) then
2176					m.targetFramework(prj)
2177				end
2178				if isManaged then
2179					m.element("Keyword", nil, "ManagedCProj")
2180				else
2181					m.element("Keyword", nil, "Win32Proj")
2182				end
2183				m.element("RootNamespace", nil, "%s", prj.name)
2184			end
2185		end
2186	end
2187
2188
2189	function m.libraryPath(cfg)
2190		local dirs = vstudio.path(cfg, cfg.syslibdirs)
2191		if #dirs > 0 then
2192			m.element("LibraryPath", nil, "%s;$(LibraryPath)", table.concat(dirs, ";"))
2193		end
2194	end
2195
2196
2197
2198	function m.linkIncremental(cfg)
2199		if cfg.kind ~= p.STATICLIB then
2200			m.element("LinkIncremental", nil, "%s", tostring(config.canLinkIncremental(cfg)))
2201		end
2202	end
2203
2204
2205	function m.linkLibraryDependencies(cfg, explicit)
2206		-- Left to its own devices, VS will happily link against a project dependency
2207		-- that has been excluded from the build. As a workaround, disable dependency
2208		-- linking and list all siblings explicitly
2209		if explicit then
2210			p.push('<ProjectReference>')
2211			m.element("LinkLibraryDependencies", nil, "false")
2212			p.pop('</ProjectReference>')
2213		end
2214	end
2215
2216
2217	function m.MasmPreprocessorDefinitions(cfg, condition)
2218		if cfg.defines then
2219			m.preprocessorDefinitions(cfg, cfg.defines, false, condition)
2220		end
2221	end
2222
2223
2224	function m.minimalRebuild(cfg)
2225		if config.isOptimizedBuild(cfg) or
2226		   cfg.flags.NoMinimalRebuild or
2227		   cfg.flags.MultiProcessorCompile or
2228		   cfg.debugformat == p.C7
2229		then
2230			m.element("MinimalRebuild", nil, "false")
2231		end
2232	end
2233
2234
2235	function m.moduleDefinitionFile(cfg)
2236		local df = config.findfile(cfg, ".def")
2237		if df then
2238			m.element("ModuleDefinitionFile", nil, "%s", df)
2239		end
2240	end
2241
2242
2243	function m.multiProcessorCompilation(cfg)
2244		if cfg.flags.MultiProcessorCompile then
2245			m.element("MultiProcessorCompilation", nil, "true")
2246		end
2247	end
2248
2249
2250	function m.nmakeBuildCommands(cfg)
2251		m.nmakeCommandLine(cfg, cfg.buildcommands, "Build")
2252	end
2253
2254
2255	function m.nmakeCleanCommands(cfg)
2256		m.nmakeCommandLine(cfg, cfg.cleancommands, "Clean")
2257	end
2258
2259
2260	function m.nmakeCommandLine(cfg, commands, phase)
2261		if #commands > 0 then
2262			commands = os.translateCommandsAndPaths(commands, cfg.project.basedir, cfg.project.location)
2263			commands = table.concat(p.esc(commands), p.eol())
2264			p.w('<NMake%sCommandLine>%s</NMake%sCommandLine>', phase, commands, phase)
2265		end
2266	end
2267
2268
2269	function m.nmakeIncludeDirs(cfg)
2270		if cfg.kind ~= p.NONE and #cfg.includedirs > 0 then
2271			local dirs = vstudio.path(cfg, cfg.includedirs)
2272			if #dirs > 0 then
2273				m.element("NMakeIncludeSearchPath", nil, "%s", table.concat(dirs, ";"))
2274			end
2275		end
2276	end
2277
2278
2279	function m.nmakeOutDirs(cfg)
2280		if vstudio.isMakefile(cfg) then
2281			m.outDir(cfg)
2282			m.intDir(cfg)
2283		end
2284	end
2285
2286
2287	function m.windowsSDKDesktopARMSupport(cfg)
2288		if cfg.system == p.WINDOWS then
2289			if cfg.architecture == p.ARM then
2290				p.w('<WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport>')
2291			end
2292			if cfg.architecture == p.ARM64 then
2293				p.w('<WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>')
2294			end
2295		end
2296	end
2297
2298
2299	function m.nmakeOutput(cfg)
2300		m.element("NMakeOutput", nil, "$(OutDir)%s", cfg.buildtarget.name)
2301	end
2302
2303
2304	function m.nmakePreprocessorDefinitions(cfg)
2305		if cfg.kind ~= p.NONE and #cfg.defines > 0 then
2306			local defines = table.concat(cfg.defines, ";")
2307			defines = defines .. ";$(NMakePreprocessorDefinitions)"
2308			m.element('NMakePreprocessorDefinitions', nil, defines)
2309		end
2310	end
2311
2312
2313	function m.nmakeRebuildCommands(cfg)
2314		m.nmakeCommandLine(cfg, cfg.rebuildcommands, "ReBuild")
2315	end
2316
2317
2318	function m.objectFileName(fcfg)
2319		if fcfg.objname ~= fcfg.basename then
2320			m.element("ObjectFileName", m.configPair(fcfg.config), "$(IntDir)\\%s.obj", fcfg.objname)
2321		end
2322	end
2323
2324
2325	function m.omitDefaultLib(cfg)
2326		if cfg.flags.OmitDefaultLibrary then
2327			m.element("OmitDefaultLibName", nil, "true")
2328		end
2329	end
2330
2331
2332	function m.omitFramePointers(cfg)
2333		local map = { Off = "false", On = "true" }
2334		local value = map[cfg.omitframepointer]
2335
2336		if value then
2337			m.element("OmitFramePointers", nil, value)
2338		end
2339	end
2340
2341
2342	function m.optimizeReferences(cfg)
2343		if config.isOptimizedBuild(cfg) then
2344			m.element("EnableCOMDATFolding", nil, "true")
2345			m.element("OptimizeReferences", nil, "true")
2346		end
2347	end
2348
2349
2350	function m.optimization(cfg, condition)
2351		local map = { Off="Disabled", On="Full", Debug="Disabled", Full="Full", Size="MinSpace", Speed="MaxSpeed" }
2352		local value = map[cfg.optimize]
2353		if value or not condition then
2354			m.element('Optimization', condition, value or "Disabled")
2355		end
2356	end
2357
2358
2359	function m.outDir(cfg)
2360		local outdir = vstudio.path(cfg, cfg.buildtarget.directory)
2361		m.element("OutDir", nil, "%s\\", outdir)
2362	end
2363
2364
2365	function m.executablePath(cfg)
2366		local dirs = vstudio.path(cfg, cfg.bindirs)
2367		if #dirs > 0 then
2368			dirs = table.translate(dirs, function(dir)
2369				if path.isabsolute(dir) then
2370					return dir
2371				end
2372				return "$(ProjectDir)" .. dir
2373			end)
2374			m.element("ExecutablePath", nil, "%s;$(ExecutablePath)", table.concat(dirs, ";"))
2375		end
2376	end
2377
2378
2379	function m.platformToolset(cfg)
2380		local tool, version = p.config.toolset(cfg)
2381
2382		if not version and _ACTION >= "vs2019" and cfg.toolset == "clang" then
2383			version = "ClangCL"
2384		end
2385
2386		if not version then
2387			local value = p.action.current().toolset
2388			tool, version = p.tools.canonical(value)
2389		end
2390
2391		if version then
2392			if cfg.kind == p.NONE or cfg.kind == p.MAKEFILE then
2393				if p.config.hasFile(cfg, path.iscppfile) or _ACTION >= "vs2015" then
2394					m.element("PlatformToolset", nil, version)
2395				end
2396			else
2397				m.element("PlatformToolset", nil, version)
2398			end
2399		end
2400	end
2401
2402	function m.precompiledHeaderFile(fileName, cfg)
2403		m.element("PrecompiledHeaderFile", nil, "%s", fileName)
2404	end
2405
2406	function m.precompiledHeader(cfg, condition)
2407		local prjcfg, filecfg = p.config.normalize(cfg)
2408		if filecfg then
2409			if prjcfg.pchsource == filecfg.abspath and not prjcfg.flags.NoPCH then
2410				m.element('PrecompiledHeader', condition, 'Create')
2411			elseif filecfg.flags.NoPCH then
2412				m.element('PrecompiledHeader', condition, 'NotUsing')
2413			end
2414		else
2415			if not prjcfg.flags.NoPCH and prjcfg.pchheader then
2416				m.element("PrecompiledHeader", nil, "Use")
2417				m.precompiledHeaderFile(prjcfg.pchheader, prjcfg)
2418			else
2419				m.element("PrecompiledHeader", nil, "NotUsing")
2420			end
2421		end
2422	end
2423
2424
2425	function m.preprocessorDefinitions(cfg, defines, escapeQuotes, condition)
2426		if #defines > 0 then
2427			defines = table.concat(defines, ";")
2428			if escapeQuotes then
2429				defines = defines:gsub('"', '\\"')
2430			end
2431			defines = defines .. ";%%(PreprocessorDefinitions)"
2432			m.element('PreprocessorDefinitions', condition, defines)
2433		end
2434	end
2435
2436
2437	function m.undefinePreprocessorDefinitions(cfg, undefines, escapeQuotes, condition)
2438		if #undefines > 0 then
2439			undefines = table.concat(undefines, ";")
2440			if escapeQuotes then
2441				undefines = undefines:gsub('"', '\\"')
2442			end
2443			undefines = undefines .. ";%%(UndefinePreprocessorDefinitions)"
2444			m.element('UndefinePreprocessorDefinitions', condition, undefines)
2445		end
2446	end
2447
2448	local function getSymbolsPathRelative(cfg)
2449		if cfg.symbolspath and cfg.symbols ~= p.OFF and cfg.debugformat ~= "c7" then
2450			return p.project.getrelative(cfg.project, cfg.symbolspath)
2451		else
2452			return nil
2453		end
2454	end
2455
2456	function m.programDatabaseFile(cfg)
2457		local value = getSymbolsPathRelative(cfg)
2458
2459		if value then
2460			m.element("ProgramDatabaseFile", nil, value)
2461		end
2462	end
2463
2464	function m.programDatabaseFileName(cfg)
2465		local value = getSymbolsPathRelative(cfg)
2466
2467		if value then
2468			m.element("ProgramDataBaseFileName", nil, value)
2469		end
2470	end
2471
2472
2473	function m.projectGuid(prj)
2474		m.element("ProjectGuid", nil, "{%s}", prj.uuid)
2475	end
2476
2477
2478	function m.projectName(prj)
2479		if prj.name ~= prj.filename then
2480			m.element("ProjectName", nil, "%s", prj.name)
2481		end
2482	end
2483
2484
2485	function m.propertyGroup(cfg, label)
2486		local cond
2487		if cfg then
2488			cond = string.format(' %s', m.condition(cfg))
2489		end
2490
2491		if label then
2492			label = string.format(' Label="%s"', label)
2493		end
2494
2495		p.push('<PropertyGroup%s%s>', cond or "", label or "")
2496	end
2497
2498
2499
2500	function m.propertySheets(cfg)
2501		p.push('<ImportGroup Label="PropertySheets" %s>', m.condition(cfg))
2502		p.w('<Import Project="$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props" Condition="exists(\'$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\')" Label="LocalAppDataPlatform" />')
2503		p.pop('</ImportGroup>')
2504	end
2505
2506
2507	function m.propertySheetGroup(prj)
2508		for cfg in project.eachconfig(prj) do
2509			m.propertySheets(cfg)
2510		end
2511	end
2512
2513
2514	function m.referenceCopyLocalSatelliteAssemblies(prj, ref)
2515		m.element("CopyLocalSatelliteAssemblies", nil, "false")
2516	end
2517
2518
2519	function m.referenceLinkLibraryDependencies(prj, ref)
2520		m.element("LinkLibraryDependencies", nil, "true")
2521	end
2522
2523
2524	function m.referenceOutputAssembly(prj, ref)
2525		m.element("ReferenceOutputAssembly", nil, "true")
2526	end
2527
2528
2529	function m.referencePrivate(prj, ref)
2530		m.element("Private", nil, "true")
2531	end
2532
2533
2534	function m.referenceProject(prj, ref)
2535		m.element("Project", nil, "{%s}", ref.uuid)
2536	end
2537
2538
2539	function m.referenceUseLibraryDependences(prj, ref)
2540		m.element("UseLibraryDependencyInputs", nil, "false")
2541	end
2542
2543
2544	function m.resourceAdditionalIncludeDirectories(cfg)
2545		m.additionalIncludeDirectories(cfg, table.join(cfg.includedirs, cfg.resincludedirs))
2546	end
2547
2548
2549	function m.resourcePreprocessorDefinitions(cfg)
2550		local defines = table.join(cfg.defines, cfg.resdefines)
2551		if cfg.exceptionhandling == p.OFF then
2552			table.insert(defines, "_HAS_EXCEPTIONS=0")
2553		end
2554		m.preprocessorDefinitions(cfg, defines, true)
2555	end
2556
2557
2558	function m.runtimeLibrary(cfg)
2559		local runtimes = {
2560			StaticDebug   = "MultiThreadedDebug",
2561			StaticRelease = "MultiThreaded",
2562			SharedDebug = "MultiThreadedDebugDLL",
2563			SharedRelease = "MultiThreadedDLL"
2564		}
2565		local runtime = config.getruntime(cfg)
2566		if runtime then
2567			m.element("RuntimeLibrary", nil, runtimes[runtime])
2568		end
2569	end
2570
2571	function m.callingConvention(cfg)
2572		if cfg.callingconvention then
2573			m.element("CallingConvention", nil, cfg.callingconvention)
2574		end
2575	end
2576
2577	function m.runtimeTypeInfo(cfg, condition)
2578		if cfg.rtti == p.OFF and ((not cfg.clr) or cfg.clr == p.OFF) then
2579			m.element("RuntimeTypeInfo", condition, "false")
2580		elseif cfg.rtti == p.ON then
2581			m.element("RuntimeTypeInfo", condition, "true")
2582		end
2583	end
2584
2585	function m.bufferSecurityCheck(cfg)
2586		local tool, toolVersion = p.config.toolset(cfg)
2587		if cfg.flags.NoBufferSecurityCheck or (toolVersion and toolVersion:startswith("LLVM-vs")) then
2588			m.element("BufferSecurityCheck", nil, "false")
2589		end
2590	end
2591
2592	function m.stringPooling(cfg)
2593		if cfg.stringpooling ~= nil then
2594			if cfg.stringpooling then
2595				m.element("StringPooling", nil, "true")
2596			else
2597				m.element("StringPooling", nil, "false")
2598			end
2599		elseif config.isOptimizedBuild(cfg) then
2600			m.element("StringPooling", nil, "true")
2601		end
2602	end
2603
2604
2605	function m.subSystem(cfg)
2606		local subsystem = iif(cfg.kind == p.CONSOLEAPP, "Console", "Windows")
2607		m.element("SubSystem", nil, subsystem)
2608	end
2609
2610
2611	function m.targetExt(cfg)
2612		local ext = cfg.buildtarget.extension
2613		if ext ~= "" then
2614			m.element("TargetExt", nil, "%s", ext)
2615		else
2616			p.w('<TargetExt>')
2617			p.w('</TargetExt>')
2618		end
2619	end
2620
2621
2622	function m.targetMachine(cfg)
2623		-- If a static library project contains a resource file, VS will choke with
2624		-- "LINK : warning LNK4068: /MACHINE not specified; defaulting to X86"
2625		local targetmachine = {
2626			x86 = "MachineX86",
2627			x86_64 = "MachineX64",
2628		}
2629		if cfg.kind == p.STATICLIB and config.hasFile(cfg, path.isresourcefile) then
2630			local value = targetmachine[cfg.architecture]
2631			if value ~= nil then
2632				m.element("TargetMachine", nil, '%s', value)
2633			end
2634		end
2635	end
2636
2637
2638	function m.targetName(cfg)
2639		m.element("TargetName", nil, "%s%s", cfg.buildtarget.prefix, cfg.buildtarget.basename)
2640	end
2641
2642
2643	function m.latestTargetPlatformVersion(prj)
2644		-- See https://developercommunity.visualstudio.com/content/problem/140294/windowstargetplatformversion-makes-it-impossible-t.html
2645		if _ACTION == "vs2017" then
2646			m.element("LatestTargetPlatformVersion", nil, "$([Microsoft.Build.Utilities.ToolLocationHelper]::GetLatestSDKTargetPlatformVersion('Windows', '10.0'))")
2647		end
2648	end
2649
2650
2651	function m.windowsTargetPlatformVersion(prj, cfg)
2652		if _ACTION < "vs2015" then
2653			return
2654		end
2655
2656		local target = cfg or prj
2657		local version = project.systemversion(target)
2658
2659		-- if this is a config, only emit if different from project
2660		if cfg then
2661			local prjVersion = project.systemversion(prj)
2662			if not prjVersion or version == prjVersion then
2663				return
2664			end
2665		end
2666
2667		-- See https://developercommunity.visualstudio.com/content/problem/140294/windowstargetplatformversion-makes-it-impossible-t.html
2668		if version == "latest" then
2669			if _ACTION == "vs2015" then
2670				version = nil   -- SDK v10 is not supported by VS2015
2671			elseif _ACTION == "vs2017" then
2672				version = "$(LatestTargetPlatformVersion)"
2673			else
2674				version = "10.0"
2675			end
2676		end
2677
2678		if version then
2679			m.element("WindowsTargetPlatformVersion", nil, version)
2680		end
2681	end
2682
2683
2684	function m.xpDeprecationWarning(prj, cfg)
2685		if cfg.toolset == "msc-v141_xp" then
2686			m.element("XPDeprecationWarning", nil, "false")
2687		end
2688	end
2689
2690
2691	function m.preferredToolArchitecture(prj)
2692		if _ACTION >= "vs2013" then
2693			if prj.preferredtoolarchitecture == p.X86_64 then
2694				m.element("PreferredToolArchitecture", nil, 'x64')
2695			elseif prj.preferredtoolarchitecture == p.X86 then
2696				m.element("PreferredToolArchitecture", nil, 'x86')
2697			end
2698		else
2699			if prj.preferredtoolarchitecture == p.X86_64 then
2700				m.element("UseNativeEnvironment", nil, 'true')
2701			end
2702		end
2703	end
2704
2705
2706	function m.treatLinkerWarningAsErrors(cfg)
2707		if cfg.flags.FatalLinkWarnings then
2708			local el = iif(cfg.kind == p.STATICLIB, "Lib", "Linker")
2709			m.element("Treat" .. el .. "WarningAsErrors", nil, "true")
2710		end
2711	end
2712
2713
2714	function m.treatWChar_tAsBuiltInType(cfg)
2715		local map = { On = "true", Off = "false" }
2716		local value = map[cfg.nativewchar]
2717		if value then
2718			m.element("TreatWChar_tAsBuiltInType", nil, value)
2719		end
2720	end
2721
2722
2723	function m.treatWarningAsError(cfg)
2724		if cfg.flags.FatalCompileWarnings and cfg.warnings ~= p.OFF then
2725			m.element("TreatWarningAsError", nil, "true")
2726		end
2727	end
2728
2729
2730	function m.disableSpecificWarnings(cfg, condition)
2731		if #cfg.disablewarnings > 0 then
2732			local warnings = table.concat(cfg.disablewarnings, ";")
2733			warnings = warnings .. ";%%(DisableSpecificWarnings)"
2734			m.element('DisableSpecificWarnings', condition, warnings)
2735		end
2736	end
2737
2738
2739	function m.treatSpecificWarningsAsErrors(cfg, condition)
2740		if #cfg.fatalwarnings > 0 then
2741			local fatal = table.concat(cfg.fatalwarnings, ";")
2742			fatal = fatal .. ";%%(TreatSpecificWarningsAsErrors)"
2743			m.element('TreatSpecificWarningsAsErrors', condition, fatal)
2744		end
2745	end
2746
2747
2748	function m.useDebugLibraries(cfg)
2749		local runtime = config.getruntime(cfg) or iif(config.isDebugBuild(cfg), "Debug", "Release")
2750		m.element("UseDebugLibraries", nil, tostring(runtime:endswith("Debug")))
2751	end
2752
2753
2754	function m.useOfMfc(cfg)
2755		if cfg.flags.MFC then
2756			m.element("UseOfMfc", nil, iif(cfg.staticruntime == "On", "Static", "Dynamic"))
2757		end
2758	end
2759
2760	function m.useOfAtl(cfg)
2761		if cfg.atl then
2762			m.element("UseOfATL", nil, cfg.atl)
2763		end
2764	end
2765
2766
2767
2768	function m.userMacros(cfg)
2769		p.w('<PropertyGroup Label="UserMacros" />')
2770	end
2771
2772
2773	function m.warningLevel(cfg)
2774		local map = { Off = "TurnOffAllWarnings", High = "Level4", Extra = "Level4", Everything = "EnableAllWarnings" }
2775		m.element("WarningLevel", nil, map[cfg.warnings] or "Level3")
2776	end
2777
2778
2779	function m.warningLevelFile(cfg, condition)
2780		local map = { Off = "TurnOffAllWarnings", High = "Level4", Extra = "Level4", Everything = "EnableAllWarnings" }
2781		if cfg.warnings then
2782			m.element("WarningLevel", condition, map[cfg.warnings] or "Level3")
2783		end
2784	end
2785
2786
2787	function m.xmlDeclaration()
2788		p.xmlUtf8()
2789	end
2790
2791	-- Fx Functions
2792	--------------------------------------------------------------------------------------------------------------
2793	--------------------------------------------------------------------------------------------------------------
2794
2795	function m.fxCompilePreprocessorDefinition(cfg, condition)
2796		if cfg.shaderdefines and #cfg.shaderdefines > 0 then
2797			local shaderdefines = table.concat(cfg.shaderdefines, ";")
2798
2799			shaderdefines = shaderdefines .. ";%%(PreprocessorDefinitions)"
2800			m.element('PreprocessorDefinitions', condition, shaderdefines)
2801		end
2802	end
2803
2804	function m.fxCompileAdditionalIncludeDirs(cfg, condition)
2805		if cfg.shaderincludedirs and #cfg.shaderincludedirs > 0 then
2806			local dirs = vstudio.path(cfg, cfg.shaderincludedirs)
2807			m.element('AdditionalIncludeDirectories', condition, "%s;%%(AdditionalIncludeDirectories)", table.concat(dirs, ";"))
2808		end
2809	end
2810
2811	function m.fxCompileShaderType(cfg, condition)
2812		if cfg.shadertype then
2813			m.element("ShaderType", condition, cfg.shadertype)
2814		end
2815	end
2816
2817
2818	function m.fxCompileShaderModel(cfg, condition)
2819		if cfg.shadermodel then
2820			m.element("ShaderModel", condition, cfg.shadermodel)
2821		end
2822	end
2823
2824
2825	function m.fxCompileShaderEntry(cfg, condition)
2826		if cfg.shaderentry then
2827			m.element("EntryPointName", condition, cfg.shaderentry)
2828		end
2829	end
2830
2831
2832	function m.fxCompileShaderVariableName(cfg, condition)
2833		if cfg.shadervariablename then
2834			m.element("VariableName", condition, cfg.shadervariablename)
2835		end
2836	end
2837
2838
2839	function m.fxCompileShaderHeaderOutput(cfg, condition)
2840		if cfg.shaderheaderfileoutput then
2841			m.element("HeaderFileOutput", condition, cfg.shaderheaderfileoutput)
2842		end
2843	end
2844
2845
2846	function m.fxCompileShaderObjectOutput(cfg, condition)
2847		if cfg.shaderobjectfileoutput then
2848			m.element("ObjectFileOutput", condition, cfg.shaderobjectfileoutput)
2849		end
2850	end
2851
2852
2853	function m.fxCompileShaderAssembler(cfg, condition)
2854		if cfg.shaderassembler then
2855			m.element("AssemblerOutput", condition, cfg.shaderassembler)
2856		end
2857	end
2858
2859
2860	function m.fxCompileShaderAssemblerOutput(cfg, condition)
2861		if cfg.shaderassembleroutput then
2862			m.element("AssemblerOutputFile", condition, cfg.shaderassembleroutput)
2863		end
2864	end
2865
2866
2867	function m.fxCompileShaderAdditionalOptions(cfg, condition)
2868		local opts = cfg.shaderoptions
2869		if #opts > 0 then
2870			opts = table.concat(opts, " ")
2871			m.element("AdditionalOptions", condition, '%s %%(AdditionalOptions)', opts)
2872		end
2873	end
2874
2875
2876---------------------------------------------------------------------------
2877--
2878-- Support functions
2879--
2880---------------------------------------------------------------------------
2881
2882--
2883-- Format and return a Visual Studio Condition attribute.
2884--
2885
2886	function m.conditionFromConfigText(cfgText)
2887		return string.format('Condition="\'$(Configuration)|$(Platform)\'==\'%s\'"', p.esc(cfgText))
2888	end
2889
2890	function m.condition(cfg)
2891		return m.conditionFromConfigText(vstudio.projectConfig(cfg))
2892	end
2893
2894
2895--
2896-- Output an individual project XML element, with an optional configuration
2897-- condition.
2898--
2899-- @param depth
2900--    How much to indent the element.
2901-- @param name
2902--    The element name.
2903-- @param condition
2904--    An optional configuration condition, formatted with vc2010.condition().
2905-- @param value
2906--    The element value, which may contain printf formatting tokens.
2907-- @param ...
2908--    Optional additional arguments to satisfy any tokens in the value.
2909--
2910
2911	function m.element(name, condition, value, ...)
2912		local arg = {...}
2913		if select('#',...) == 0 then
2914			value = p.esc(value)
2915		else
2916			for i = 1, #arg do
2917				arg[i] = p.esc(arg[i])
2918			end
2919		end
2920
2921		if condition then
2922			--defer output
2923			local element = {}
2924			element.name = name
2925			element.condition = condition
2926			element.value = value
2927			element.args = arg
2928			if ... then
2929				if value == '%s' then
2930					element.setting = table.concat(arg)
2931				else
2932					element.setting = value .. table.concat(arg)
2933				end
2934			else
2935				element.setting = element.value
2936			end
2937			table.insert(m.conditionalElements, element)
2938		else
2939			local format = string.format('<%s>%s</%s>', name, value, name)
2940			p.w(format, table.unpack(arg))
2941		end
2942	end
2943