1--
2-- gmake2_cpp.lua
3-- Generate a C/C++ project makefile.
4-- (c) 2016-2017 Jason Perkins, Blizzard Entertainment and the Premake project
5--
6
7	local p = premake
8	local gmake2 = p.modules.gmake2
9
10	gmake2.cpp       = {}
11	local cpp        = gmake2.cpp
12
13	local project    = p.project
14	local config     = p.config
15	local fileconfig = p.fileconfig
16
17
18---
19-- Add namespace for element definition lists for premake.callarray()
20---
21
22	cpp.elements = {}
23
24
25--
26-- Generate a GNU make C++ project makefile, with support for the new platforms API.
27--
28
29	cpp.elements.makefile = function(prj)
30		return {
31			gmake2.header,
32			gmake2.phonyRules,
33			gmake2.shellType,
34			cpp.createRuleTable,
35			cpp.outputConfigurationSection,
36			cpp.outputPerFileConfigurationSection,
37			cpp.createFileTable,
38			cpp.outputFilesSection,
39			cpp.outputRulesSection,
40			cpp.outputFileRuleSection,
41			cpp.dependencies,
42		}
43	end
44
45
46	function cpp.generate(prj)
47		p.eol("\n")
48		p.callArray(cpp.elements.makefile, prj)
49
50		-- allow the garbage collector to clean things up.
51		for cfg in project.eachconfig(prj) do
52			cfg._gmake = nil
53		end
54		prj._gmake = nil
55	end
56
57
58	function cpp.initialize()
59		rule 'cpp'
60			fileExtension { ".cc", ".cpp", ".cxx", ".mm" }
61			buildoutputs  { "$(OBJDIR)/%{file.objname}.o" }
62			buildmessage  '$(notdir $<)'
63			buildcommands {'$(CXX) %{premake.modules.gmake2.cpp.fileFlags(cfg, file)} $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"'}
64
65		rule 'cc'
66			fileExtension {".c", ".s", ".m"}
67			buildoutputs  { "$(OBJDIR)/%{file.objname}.o" }
68			buildmessage  '$(notdir $<)'
69			buildcommands {'$(CC) %{premake.modules.gmake2.cpp.fileFlags(cfg, file)} $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"'}
70
71		rule 'resource'
72			fileExtension ".rc"
73			buildoutputs  { "$(OBJDIR)/%{file.objname}.res" }
74			buildmessage  '$(notdir $<)'
75			buildcommands {'$(RESCOMP) $< -O coff -o "$@" $(ALL_RESFLAGS)'}
76
77		global(nil)
78	end
79
80
81	function cpp.createRuleTable(prj)
82		local rules = {}
83
84		local function addRule(extension, rule)
85			if type(extension) == 'table' then
86				for _, value in ipairs(extension) do
87					addRule(value, rule)
88				end
89			else
90				rules[extension] = rule
91			end
92		end
93
94		-- add all rules.
95		local usedRules = table.join({'cpp', 'cc', 'resource'}, prj.rules)
96		for _, name in ipairs(usedRules) do
97			local rule = p.global.getRule(name)
98			addRule(rule.fileExtension, rule)
99		end
100
101		-- create fileset categories.
102		local filesets = {
103			['.o']   = 'OBJECTS',
104			['.obj'] = 'OBJECTS',
105			['.cc']  = 'SOURCES',
106			['.cpp'] = 'SOURCES',
107			['.cxx'] = 'SOURCES',
108			['.mm']  = 'SOURCES',
109			['.c']   = 'SOURCES',
110			['.s']   = 'SOURCES',
111			['.m']   = 'SOURCES',
112			['.rc']  = 'RESOURCES',
113		}
114
115		-- cache the result.
116		prj._gmake = prj._gmake or {}
117		prj._gmake.rules = rules
118		prj._gmake.filesets = filesets
119	end
120
121
122	function cpp.createFileTable(prj)
123		for cfg in project.eachconfig(prj) do
124			cfg._gmake = cfg._gmake or {}
125			cfg._gmake.filesets = {}
126			cfg._gmake.fileRules = {}
127
128			local files = table.shallowcopy(prj._.files)
129			table.foreachi(files, function(node)
130				cpp.addFile(cfg, node)
131			end)
132
133			for _, f in pairs(cfg._gmake.filesets) do
134				table.sort(f)
135			end
136
137			cfg._gmake.kinds = table.keys(cfg._gmake.filesets)
138			table.sort(cfg._gmake.kinds)
139
140			prj._gmake.kinds = table.join(prj._gmake.kinds or {}, cfg._gmake.kinds)
141		end
142
143		-- we need to reassign object sequences if we generated any files.
144		if prj.hasGeneratedFiles and p.project.iscpp(prj) then
145			p.oven.assignObjectSequences(prj)
146		end
147
148		prj._gmake.kinds = table.unique(prj._gmake.kinds)
149		table.sort(prj._gmake.kinds)
150	end
151
152
153	function cpp.addFile(cfg, node)
154		local filecfg = fileconfig.getconfig(node, cfg)
155		if not filecfg or filecfg.flags.ExcludeFromBuild then
156			return
157		end
158
159		-- skip generated files, since we try to figure it out manually below.
160		if node.generated then
161			return
162		end
163
164		-- process custom build commands.
165		if fileconfig.hasCustomBuildRule(filecfg) then
166			local env = table.shallowcopy(filecfg.environ)
167			env.PathVars = {
168				["file.basename"]     = { absolute = false, token = node.basename },
169				["file.abspath"]      = { absolute = true,  token = node.abspath },
170				["file.relpath"]      = { absolute = false, token = node.relpath },
171				["file.name"]         = { absolute = false, token = node.name },
172				["file.objname"]      = { absolute = false, token = node.objname },
173				["file.path"]         = { absolute = true,  token = node.path },
174				["file.directory"]    = { absolute = true,  token = path.getdirectory(node.abspath) },
175				["file.reldirectory"] = { absolute = false, token = path.getdirectory(node.relpath) },
176			}
177
178			local shadowContext = p.context.extent(filecfg, env)
179
180			local buildoutputs = p.project.getrelative(cfg.project, shadowContext.buildoutputs)
181			if buildoutputs and #buildoutputs > 0 then
182				local file = {
183					buildoutputs  = buildoutputs,
184					source        = node.relpath,
185					buildmessage  = shadowContext.buildmessage,
186					buildcommands = shadowContext.buildcommands,
187					buildinputs   = p.project.getrelative(cfg.project, shadowContext.buildinputs)
188				}
189				table.insert(cfg._gmake.fileRules, file)
190
191				for _, output in ipairs(buildoutputs) do
192					cpp.addGeneratedFile(cfg, node, output)
193				end
194			end
195		else
196			cpp.addRuleFile(cfg, node)
197		end
198	end
199
200	function cpp.determineFiletype(cfg, node)
201		-- determine which filetype to use
202		local filecfg = fileconfig.getconfig(node, cfg)
203		local fileext = path.getextension(node.abspath):lower()
204		if filecfg and filecfg.compileas then
205			if p.languages.isc(filecfg.compileas) then
206				fileext = ".c"
207			elseif p.languages.iscpp(filecfg.compileas) then
208				fileext = ".cpp"
209			end
210		end
211
212		return fileext;
213	end
214
215	function cpp.addGeneratedFile(cfg, source, filename)
216		-- mark that we have generated files.
217		cfg.project.hasGeneratedFiles = true
218
219		-- add generated file to the project.
220		local files = cfg.project._.files
221		local node = files[filename]
222		if not node then
223			node = fileconfig.new(filename, cfg.project)
224			files[filename] = node
225			table.insert(files, node)
226		end
227
228		-- always overwrite the dependency information.
229		node.dependsOn = source
230		node.generated = true
231
232		-- add to config if not already added.
233		if not fileconfig.getconfig(node, cfg) then
234			fileconfig.addconfig(node, cfg)
235		end
236
237		-- determine which filetype to use
238		local fileext = cpp.determineFiletype(cfg, node)
239		-- add file to the fileset.
240		local filesets = cfg.project._gmake.filesets
241		local kind     = filesets[fileext] or "CUSTOM"
242
243		-- don't link generated object files automatically if it's explicitly
244		-- disabled.
245		if path.isobjectfile(filename) and source.linkbuildoutputs == false then
246			kind = "CUSTOM"
247		end
248
249		local fileset = cfg._gmake.filesets[kind] or {}
250		table.insert(fileset, filename)
251		cfg._gmake.filesets[kind] = fileset
252
253		local generatedKind = "GENERATED"
254		local generatedFileset = cfg._gmake.filesets[generatedKind] or {}
255		table.insert(generatedFileset, filename)
256		cfg._gmake.filesets[generatedKind] = generatedFileset
257
258		-- recursively setup rules.
259		cpp.addRuleFile(cfg, node)
260	end
261
262	function cpp.addRuleFile(cfg, node)
263		local rules = cfg.project._gmake.rules
264		local fileext = cpp.determineFiletype(cfg, node)
265		local rule = rules[fileext]
266		if rule then
267
268			local filecfg = fileconfig.getconfig(node, cfg)
269			local environ = table.shallowcopy(filecfg.environ)
270
271			if rule.propertydefinition then
272				gmake2.prepareEnvironment(rule, environ, cfg)
273				gmake2.prepareEnvironment(rule, environ, filecfg)
274			end
275
276			local shadowContext = p.context.extent(rule, environ)
277
278			local buildoutputs  = shadowContext.buildoutputs
279			local buildmessage  = shadowContext.buildmessage
280			local buildcommands = shadowContext.buildcommands
281			local buildinputs   = shadowContext.buildinputs
282
283			buildoutputs = p.project.getrelative(cfg.project, buildoutputs)
284			if buildoutputs and #buildoutputs > 0 then
285				local file = {
286					buildoutputs  = buildoutputs,
287					source        = node.relpath,
288					buildmessage  = buildmessage,
289					buildcommands = buildcommands,
290					buildinputs   = buildinputs
291				}
292				table.insert(cfg._gmake.fileRules, file)
293
294				for _, output in ipairs(buildoutputs) do
295					cpp.addGeneratedFile(cfg, node, output)
296				end
297			end
298		end
299	end
300
301
302--
303-- Write out the settings for a particular configuration.
304--
305
306	cpp.elements.configuration = function(cfg)
307		return {
308			cpp.tools,
309			gmake2.target,
310			gmake2.objdir,
311			cpp.pch,
312			cpp.defines,
313			cpp.includes,
314			cpp.forceInclude,
315			cpp.cppFlags,
316			cpp.cFlags,
317			cpp.cxxFlags,
318			cpp.resFlags,
319			cpp.libs,
320			cpp.ldDeps,
321			cpp.ldFlags,
322			cpp.linkCmd,
323			cpp.bindirs,
324			cpp.exepaths,
325			gmake2.settings,
326			gmake2.preBuildCmds,
327			gmake2.preLinkCmds,
328			gmake2.postBuildCmds,
329		}
330	end
331
332
333	function cpp.outputConfigurationSection(prj)
334		_p('# Configurations')
335		_p('# #############################################')
336		_p('')
337		gmake2.outputSection(prj, cpp.elements.configuration)
338	end
339
340
341	function cpp.tools(cfg, toolset)
342		local tool = toolset.gettoolname(cfg, "cc")
343		if tool then
344			_p('ifeq ($(origin CC), default)')
345			_p('  CC = %s', tool)
346			_p('endif' )
347		end
348
349		tool = toolset.gettoolname(cfg, "cxx")
350		if tool then
351			_p('ifeq ($(origin CXX), default)')
352			_p('  CXX = %s', tool)
353			_p('endif' )
354		end
355
356		tool = toolset.gettoolname(cfg, "ar")
357		if tool then
358			_p('ifeq ($(origin AR), default)')
359			_p('  AR = %s', tool)
360			_p('endif' )
361		end
362
363		tool = toolset.gettoolname(cfg, "rc")
364		if tool then
365			_p('RESCOMP = %s', tool)
366		end
367	end
368
369
370	function cpp.pch(cfg, toolset)
371		-- If there is no header, or if PCH has been disabled, I can early out
372		if not cfg.pchheader or cfg.flags.NoPCH then
373			return
374		end
375
376		-- Visual Studio requires the PCH header to be specified in the same way
377		-- it appears in the #include statements used in the source code; the PCH
378		-- source actual handles the compilation of the header. GCC compiles the
379		-- header file directly, and needs the file's actual file system path in
380		-- order to locate it.
381
382		-- To maximize the compatibility between the two approaches, see if I can
383		-- locate the specified PCH header on one of the include file search paths
384		-- and, if so, adjust the path automatically so the user doesn't have
385		-- add a conditional configuration to the project script.
386
387		local pch = cfg.pchheader
388		local found = false
389
390		-- test locally in the project folder first (this is the most likely location)
391		local testname = path.join(cfg.project.basedir, pch)
392		if os.isfile(testname) then
393			pch = project.getrelative(cfg.project, testname)
394			found = true
395		else
396			-- else scan in all include dirs.
397			for _, incdir in ipairs(cfg.includedirs) do
398				testname = path.join(incdir, pch)
399				if os.isfile(testname) then
400					pch = project.getrelative(cfg.project, testname)
401					found = true
402					break
403				end
404			end
405		end
406
407		if not found then
408			pch = project.getrelative(cfg.project, path.getabsolute(pch))
409		end
410
411		p.outln('PCH = ' .. pch)
412		p.outln('PCH_PLACEHOLDER = $(OBJDIR)/$(notdir $(PCH))')
413		p.outln('GCH = $(PCH_PLACEHOLDER).gch')
414	end
415
416
417	function cpp.defines(cfg, toolset)
418		p.outln('DEFINES +=' .. gmake2.list(table.join(toolset.getdefines(cfg.defines, cfg), toolset.getundefines(cfg.undefines))))
419	end
420
421
422	function cpp.includes(cfg, toolset)
423		local includes = toolset.getincludedirs(cfg, cfg.includedirs, cfg.sysincludedirs)
424		p.outln('INCLUDES +=' .. gmake2.list(includes))
425	end
426
427
428	function cpp.forceInclude(cfg, toolset)
429		local includes = toolset.getforceincludes(cfg)
430		p.outln('FORCE_INCLUDE +=' .. gmake2.list(includes))
431	end
432
433
434	function cpp.cppFlags(cfg, toolset)
435		local flags = gmake2.list(toolset.getcppflags(cfg))
436		p.outln('ALL_CPPFLAGS += $(CPPFLAGS)' .. flags .. ' $(DEFINES) $(INCLUDES)')
437	end
438
439
440	function cpp.cFlags(cfg, toolset)
441		local flags = gmake2.list(table.join(toolset.getcflags(cfg), cfg.buildoptions))
442		p.outln('ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS)' .. flags)
443	end
444
445
446	function cpp.cxxFlags(cfg, toolset)
447		local flags = gmake2.list(table.join(toolset.getcxxflags(cfg), cfg.buildoptions))
448		p.outln('ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CPPFLAGS)' .. flags)
449	end
450
451
452	function cpp.resFlags(cfg, toolset)
453		local resflags = table.join(toolset.getdefines(cfg.resdefines), toolset.getincludedirs(cfg, cfg.resincludedirs), cfg.resoptions)
454		p.outln('ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES)' .. gmake2.list(resflags))
455	end
456
457
458	function cpp.libs(cfg, toolset)
459		local flags = toolset.getlinks(cfg)
460		p.outln('LIBS +=' .. gmake2.list(flags, true))
461	end
462
463
464	function cpp.ldDeps(cfg, toolset)
465		local deps = config.getlinks(cfg, "siblings", "fullpath")
466		p.outln('LDDEPS +=' .. gmake2.list(p.esc(deps)))
467	end
468
469
470	function cpp.ldFlags(cfg, toolset)
471		local flags = table.join(toolset.getLibraryDirectories(cfg), toolset.getrunpathdirs(cfg, cfg.runpathdirs), toolset.getldflags(cfg), cfg.linkoptions)
472		p.outln('ALL_LDFLAGS += $(LDFLAGS)' .. gmake2.list(flags))
473	end
474
475
476	function cpp.linkCmd(cfg, toolset)
477		if cfg.kind == p.STATICLIB then
478			if cfg.architecture == p.UNIVERSAL then
479				p.outln('LINKCMD = libtool -o "$@" $(OBJECTS)')
480			else
481				p.outln('LINKCMD = $(AR) -rcs "$@" $(OBJECTS)')
482			end
483		elseif cfg.kind == p.UTILITY then
484			-- Empty LINKCMD for Utility (only custom build rules)
485			p.outln('LINKCMD =')
486		else
487			-- this was $(TARGET) $(LDFLAGS) $(OBJECTS)
488			--   but had trouble linking to certain static libs; $(OBJECTS) moved up
489			-- $(LDFLAGS) moved to end (http://sourceforge.net/p/premake/patches/107/)
490			-- $(LIBS) moved to end (http://sourceforge.net/p/premake/bugs/279/)
491
492			local cc = iif(p.languages.isc(cfg.language), "CC", "CXX")
493			p.outln('LINKCMD = $(' .. cc .. ') -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS)')
494		end
495	end
496
497
498	function cpp.bindirs(cfg, toolset)
499		local dirs = project.getrelative(cfg.project, cfg.bindirs)
500		if #dirs > 0 then
501			p.outln('EXECUTABLE_PATHS = "' .. table.concat(dirs, ":") .. '"')
502		end
503	end
504
505
506	function cpp.exepaths(cfg, toolset)
507		local dirs = project.getrelative(cfg.project, cfg.bindirs)
508		if #dirs > 0 then
509			p.outln('EXE_PATHS = export PATH=$(EXECUTABLE_PATHS):$$PATH;')
510		end
511	end
512
513
514--
515-- Write out the per file configurations.
516--
517	function cpp.outputPerFileConfigurationSection(prj)
518		_p('# Per File Configurations')
519		_p('# #############################################')
520		_p('')
521		for cfg in project.eachconfig(prj) do
522			table.foreachi(prj._.files, function(node)
523				local fcfg = fileconfig.getconfig(node, cfg)
524				if fcfg then
525					cpp.perFileFlags(cfg, fcfg)
526				end
527			end)
528		end
529		_p('')
530	end
531
532	function cpp.makeVarName(prj, value, saltValue)
533		prj._gmake = prj._gmake or {}
534		prj._gmake.varlist = prj._gmake.varlist or {}
535		prj._gmake.varlistlength = prj._gmake.varlistlength or 0
536		local cache = prj._gmake.varlist
537		local length = prj._gmake.varlistlength
538
539		local key = value .. saltValue
540
541		if (cache[key] ~= nil) then
542			return cache[key], false
543		end
544
545		local var = string.format("PERFILE_FLAGS_%d", length)
546		cache[key] = var
547
548		prj._gmake.varlistlength = length + 1
549
550		return var, true
551	end
552
553	function cpp.perFileFlags(cfg, fcfg)
554		local toolset = gmake2.getToolSet(cfg)
555
556		local isCFile = path.iscfile(fcfg.name)
557
558		local getflags = iif(isCFile, toolset.getcflags, toolset.getcxxflags)
559		local value = gmake2.list(table.join(getflags(fcfg), fcfg.buildoptions))
560
561		if fcfg.defines or fcfg.undefines then
562			local defs = table.join(toolset.getdefines(fcfg.defines, cfg), toolset.getundefines(fcfg.undefines))
563			if #defs > 0 then
564				value = value .. gmake2.list(defs)
565			end
566		end
567
568		if fcfg.includedirs or fcfg.sysincludedirs then
569			local includes = toolset.getincludedirs(cfg, fcfg.includedirs, fcfg.sysincludedirs)
570			if #includes > 0 then
571				value = value ..  gmake2.list(includes)
572			end
573		end
574
575		if #value > 0 then
576			local newPerFileFlag = false
577			fcfg.flagsVariable, newPerFileFlag = cpp.makeVarName(cfg.project, value, iif(isCFile, '_C', '_CPP'))
578			if newPerFileFlag then
579				if isCFile then
580					_p('%s = $(ALL_CFLAGS)%s', fcfg.flagsVariable, value)
581				else
582					_p('%s = $(ALL_CXXFLAGS)%s', fcfg.flagsVariable, value)
583				end
584			end
585		end
586	end
587
588	function cpp.fileFlags(cfg, file)
589		local fcfg = fileconfig.getconfig(file, cfg)
590		local flags = {}
591
592		if cfg.pchheader and not cfg.flags.NoPCH and (not fcfg or not fcfg.flags.NoPCH) then
593			table.insert(flags, "-include $(PCH_PLACEHOLDER)")
594		end
595
596		if fcfg and fcfg.flagsVariable then
597			table.insert(flags, string.format("$(%s)", fcfg.flagsVariable))
598		else
599			local fileExt = cpp.determineFiletype(cfg, file)
600
601			if path.iscfile(fileExt) then
602				table.insert(flags, "$(ALL_CFLAGS)")
603			elseif path.iscppfile(fileExt) then
604				table.insert(flags, "$(ALL_CXXFLAGS)")
605			end
606		end
607
608		return table.concat(flags, ' ')
609	end
610
611--
612-- Write out the file sets.
613--
614
615	cpp.elements.filesets = function(cfg)
616		local result = {}
617		for _, kind in ipairs(cfg._gmake.kinds) do
618			for _, f in ipairs(cfg._gmake.filesets[kind]) do
619				table.insert(result, function(cfg, toolset)
620					cpp.outputFileset(cfg, kind, f)
621				end)
622			end
623		end
624		return result
625	end
626
627	function cpp.outputFilesSection(prj)
628		_p('# File sets')
629		_p('# #############################################')
630		_p('')
631
632		for _, kind in ipairs(prj._gmake.kinds) do
633			_x('%s :=', kind)
634		end
635		_x('')
636
637		gmake2.outputSection(prj, cpp.elements.filesets)
638	end
639
640	function cpp.outputFileset(cfg, kind, file)
641		_x('%s += %s', kind, file)
642	end
643
644
645--
646-- Write out the targets.
647--
648
649	cpp.elements.rules = function(cfg)
650		return {
651			cpp.allRules,
652			cpp.targetRules,
653			gmake2.targetDirRules,
654			gmake2.objDirRules,
655			cpp.cleanRules,
656			gmake2.preBuildRules,
657			cpp.customDeps,
658			cpp.pchRules,
659		}
660	end
661
662
663	function cpp.outputRulesSection(prj)
664		_p('# Rules')
665		_p('# #############################################')
666		_p('')
667		gmake2.outputSection(prj, cpp.elements.rules)
668	end
669
670
671	function cpp.allRules(cfg, toolset)
672		if cfg.system == p.MACOSX and cfg.kind == p.WINDOWEDAPP then
673			_p('all: $(TARGET) $(dir $(TARGETDIR))PkgInfo $(dir $(TARGETDIR))Info.plist')
674			_p('\t@:')
675			_p('')
676			_p('$(dir $(TARGETDIR))PkgInfo:')
677			_p('$(dir $(TARGETDIR))Info.plist:')
678		else
679			_p('all: $(TARGET)')
680			_p('\t@:')
681		end
682		_p('')
683	end
684
685
686	function cpp.targetRules(cfg, toolset)
687		local targets = ''
688
689		for _, kind in ipairs(cfg._gmake.kinds) do
690			if kind ~= 'OBJECTS' and kind ~= 'RESOURCES' then
691				targets = targets .. '$(' .. kind .. ') '
692			end
693		end
694
695		targets = targets .. '$(OBJECTS) $(LDDEPS)'
696		if cfg._gmake.filesets['RESOURCES'] then
697			targets = targets .. ' $(RESOURCES)'
698		end
699
700		_p('$(TARGET): %s | $(TARGETDIR)', targets)
701		_p('\t$(PRELINKCMDS)')
702		_p('\t@echo Linking %s', cfg.project.name)
703		_p('\t$(SILENT) $(LINKCMD)')
704		_p('\t$(POSTBUILDCMDS)')
705		_p('')
706	end
707
708
709	function cpp.customDeps(cfg, toolset)
710		for _, kind in ipairs(cfg._gmake.kinds) do
711			if kind == 'CUSTOM' or kind == 'SOURCES' then
712				_p('$(%s): | prebuild', kind)
713			end
714		end
715	end
716
717
718	function cpp.cleanRules(cfg, toolset)
719		_p('clean:')
720		_p('\t@echo Cleaning %s', cfg.project.name)
721		_p('ifeq (posix,$(SHELLTYPE))')
722		_p('\t$(SILENT) rm -f  $(TARGET)')
723		_p('\t$(SILENT) rm -rf $(GENERATED)')
724		_p('\t$(SILENT) rm -rf $(OBJDIR)')
725		_p('else')
726		_p('\t$(SILENT) if exist $(subst /,\\\\,$(TARGET)) del $(subst /,\\\\,$(TARGET))')
727		_p('\t$(SILENT) if exist $(subst /,\\\\,$(GENERATED)) rmdir /s /q $(subst /,\\\\,$(GENERATED))')
728		_p('\t$(SILENT) if exist $(subst /,\\\\,$(OBJDIR)) rmdir /s /q $(subst /,\\\\,$(OBJDIR))')
729		_p('endif')
730		_p('')
731	end
732
733
734	function cpp.pchRules(cfg, toolset)
735		_p('ifneq (,$(PCH))')
736		_p('$(OBJECTS): $(GCH) | $(PCH_PLACEHOLDER)')
737		_p('$(GCH): $(PCH) | prebuild')
738		_p('\t@echo $(notdir $<)')
739		local cmd = iif(p.languages.isc(cfg.language), "$(CC) -x c-header $(ALL_CFLAGS)", "$(CXX) -x c++-header $(ALL_CXXFLAGS)")
740		_p('\t$(SILENT) %s -o "$@" -MF "$(@:%%.gch=%%.d)" -c "$<"', cmd)
741		_p('$(PCH_PLACEHOLDER): $(GCH) | $(OBJDIR)')
742		_p('ifeq (posix,$(SHELLTYPE))')
743		_p('\t$(SILENT) touch "$@"')
744		_p('else')
745		_p('\t$(SILENT) echo $null >> "$@"')
746		_p('endif')
747		_p('else')
748		_p('$(OBJECTS): | prebuild')
749		_p('endif')
750		_p('')
751	end
752
753--
754-- Output the file compile targets.
755--
756
757	cpp.elements.fileRules = function(cfg)
758		local funcs = {}
759		for _, fileRule in ipairs(cfg._gmake.fileRules) do
760			table.insert(funcs, function(cfg, toolset)
761				cpp.outputFileRules(cfg, fileRule)
762			end)
763		end
764		return funcs
765	end
766
767
768	function cpp.outputFileRuleSection(prj)
769		_p('# File Rules')
770		_p('# #############################################')
771		_p('')
772		gmake2.outputSection(prj, cpp.elements.fileRules)
773	end
774
775
776	function cpp.outputFileRules(cfg, file)
777		local dependencies = p.esc(file.source)
778		if file.buildinputs and #file.buildinputs > 0 then
779			dependencies = dependencies .. " " .. table.concat(p.esc(file.buildinputs), " ")
780		end
781
782		_p('%s: %s', file.buildoutputs[1], dependencies)
783
784		if file.buildmessage then
785			_p('\t@echo %s', file.buildmessage)
786		end
787
788		if file.buildcommands then
789			local cmds = os.translateCommandsAndPaths(file.buildcommands, cfg.project.basedir, cfg.project.location)
790			for _, cmd in ipairs(cmds) do
791				if cfg.bindirs and #cfg.bindirs > 0 then
792					_p('\t$(SILENT) $(EXE_PATHS) %s', cmd)
793				else
794					_p('\t$(SILENT) %s', cmd)
795				end
796			end
797		end
798
799		-- TODO: this is a hack with some imperfect side-effects.
800		--       better solution would be to emit a dummy file for the rule, and then outputs depend on it (must clean up dummy in 'clean')
801		--       better yet, is to use pattern rules, but we need to detect that all outputs have the same stem
802		if #file.buildoutputs > 1 then
803			_p('%s: %s', table.concat({ table.unpack(file.buildoutputs, 2) }, ' '), file.buildoutputs[1])
804		end
805	end
806
807
808---------------------------------------------------------------------------
809--
810-- Handlers for individual makefile elements
811--
812---------------------------------------------------------------------------
813
814
815	function cpp.dependencies(prj)
816		-- include the dependencies, built by GCC (with the -MMD flag)
817		_p('-include $(OBJECTS:%%.o=%%.d)')
818		_p('ifneq (,$(PCH))')
819			_p('  -include $(PCH_PLACEHOLDER).d')
820		_p('endif')
821	end
822