1--
2-- xcode_common.lua
3-- Functions to generate the different sections of an Xcode project.
4-- Copyright (c) 2009-2015 Jason Perkins and the Premake project
5--
6
7	local p = premake
8	local xcode = p.modules.xcode
9	local tree  = p.tree
10	local workspace = p.workspace
11	local project = p.project
12	local config = p.config
13	local fileconfig = p.fileconfig
14
15
16--
17-- Return the Xcode build category for a given file, based on the file extension.
18--
19-- @param node
20--    The node to identify.
21-- @returns
22--    An Xcode build category, one of "Sources", "Resources", "Frameworks", or nil.
23--
24
25	function xcode.getbuildcategory(node)
26		local categories = {
27			[".a"] = "Frameworks",
28			[".c"] = "Sources",
29			[".cc"] = "Sources",
30			[".cpp"] = "Sources",
31			[".cxx"] = "Sources",
32			[".dylib"] = "Frameworks",
33			[".framework"] = "Frameworks",
34			[".m"] = "Sources",
35			[".mm"] = "Sources",
36			[".strings"] = "Resources",
37			[".nib"] = "Resources",
38			[".xib"] = "Resources",
39			[".storyboard"] = "Resources",
40			[".icns"] = "Resources",
41			[".s"] = "Sources",
42			[".S"] = "Sources",
43		}
44		if node.isResource then
45			return "Resources"
46		end
47		return categories[path.getextension(node.name)]
48	end
49
50	function xcode.isItemResource(project, node)
51
52		local res;
53
54		if project and project.xcodebuildresources then
55			if type(project.xcodebuildresources) == "table" then
56				res = project.xcodebuildresources
57			end
58		end
59
60		local function checkItemInList(item, list)
61			if item then
62				if list then
63					if type(list) == "table" then
64						for _,v in pairs(list) do
65							if string.find(item, v) then
66								return true
67							end
68						end
69					end
70				end
71			end
72			return false
73		end
74
75		--print (node.path, node.buildid, node.cfg, res)
76		if (checkItemInList(node.path, res)) then
77			return true
78		end
79
80		return false
81	end
82--
83-- Return the Xcode type for a given file, based on the file extension.
84--
85-- @param fname
86--    The file name to identify.
87-- @returns
88--    An Xcode file type, string.
89--
90
91	function xcode.getfiletype(node, cfg)
92
93		if node.configs then
94			local filecfg = fileconfig.getconfig(node, cfg)
95			if filecfg then
96				if filecfg.language == "ObjC" then
97					return "sourcecode.c.objc"
98				elseif 	filecfg.language == "ObjCpp" then
99					return "sourcecode.cpp.objcpp"
100				end
101			end
102		end
103
104		local types = {
105			[".c"]         = "sourcecode.c.c",
106			[".cc"]        = "sourcecode.cpp.cpp",
107			[".cpp"]       = "sourcecode.cpp.cpp",
108			[".css"]       = "text.css",
109			[".cxx"]       = "sourcecode.cpp.cpp",
110			[".S"]         = "sourcecode.asm.asm",
111			[".framework"] = "wrapper.framework",
112			[".gif"]       = "image.gif",
113			[".h"]         = "sourcecode.c.h",
114			[".html"]      = "text.html",
115			[".lua"]       = "sourcecode.lua",
116			[".m"]         = "sourcecode.c.objc",
117			[".mm"]        = "sourcecode.cpp.objc",
118			[".nib"]       = "wrapper.nib",
119			[".storyboard"] = "file.storyboard",
120			[".pch"]       = "sourcecode.c.h",
121			[".plist"]     = "text.plist.xml",
122			[".strings"]   = "text.plist.strings",
123			[".xib"]       = "file.xib",
124			[".icns"]      = "image.icns",
125			[".s"]         = "sourcecode.asm",
126			[".bmp"]       = "image.bmp",
127			[".wav"]       = "audio.wav",
128			[".xcassets"]  = "folder.assetcatalog",
129
130		}
131		return types[path.getextension(node.path)] or "text"
132	end
133
134--
135-- Print user configuration references contained in xcodeconfigreferences
136-- @param offset
137--    offset used by function _p
138-- @param cfg
139--    configuration
140--
141
142	local function xcodePrintUserConfigReferences(offset, cfg, tr, kind)
143		local referenceName
144		if kind == "project" then
145			referenceName = cfg.xcodeconfigreferenceproject
146		elseif kind == "target" then
147			referenceName = cfg.xcodeconfigreferencetarget
148		end
149		tree.traverse(tr, {
150			onleaf = function(node)
151				filename = node.name
152				if node.id and path.getextension(filename) == ".xcconfig" then
153					if filename == referenceName then
154						_p(offset, 'baseConfigurationReference = %s /* %s */;', node.id, filename)
155						return
156					end
157				end
158			end
159		}, false)
160	end
161
162
163
164	local escapeSpecialChars = {
165		['\n'] = '\\n',
166		['\r'] = '\\r',
167		['\t'] = '\\t',
168	}
169
170	local function escapeChar(c)
171		return escapeSpecialChars[c] or '\\'..c
172	end
173
174	local function escapeArg(value)
175		value = value:gsub('[\'"\\\n\r\t ]', escapeChar)
176		return value
177	end
178
179	local function escapeSetting(value)
180		value = value:gsub('["\\\n\r\t]', escapeChar)
181		return value
182	end
183
184	local function stringifySetting(value)
185		value = value..''
186		if not value:match('^[%a%d_./]+$') then
187			value = '"'..escapeSetting(value)..'"'
188		end
189		return value
190	end
191
192	local function customStringifySetting(value)
193		value = value..''
194
195		local test = value:match('^[%a%d_./%+]+$')
196		if test then
197			value = '"'..escapeSetting(value)..'"'
198		end
199		return value
200	end
201
202	local function printSetting(level, name, value)
203		if type(value) == 'function' then
204			value(level, name)
205		elseif type(value) ~= 'table' then
206			_p(level, '%s = %s;', stringifySetting(name), stringifySetting(value))
207		--elseif #value == 1 then
208			--_p(level, '%s = %s;', stringifySetting(name), stringifySetting(value[1]))
209		elseif #value >= 1 then
210			_p(level, '%s = (', stringifySetting(name))
211			for _, item in ipairs(value) do
212				_p(level + 1, '%s,', stringifySetting(item))
213			end
214			_p(level, ');')
215		end
216	end
217
218	local function printSettingsTable(level, settings)
219		-- Maintain alphabetic order to be consistent
220		local keys = table.keys(settings)
221		table.sort(keys)
222		for _, k in ipairs(keys) do
223			printSetting(level, k, settings[k])
224		end
225	end
226
227	local function overrideSettings(settings, overrides)
228		if type(overrides) == 'table' then
229			for name, value in pairs(overrides) do
230				-- Allow an override to remove a value by using false
231				settings[name] = iif(value ~= false, value, nil)
232			end
233		end
234	end
235
236--
237-- Return the Xcode product type, based target kind.
238--
239-- @param node
240--    The product node to identify.
241-- @returns
242--    An Xcode product type, string.
243--
244
245	function xcode.getproducttype(node)
246		local types = {
247			ConsoleApp  = "com.apple.product-type.tool",
248			WindowedApp = "com.apple.product-type.application",
249			StaticLib   = "com.apple.product-type.library.static",
250			SharedLib   = "com.apple.product-type.library.dynamic",
251		}
252		return types[node.cfg.kind]
253	end
254
255
256--
257-- Return the Xcode target type, based on the target file extension.
258--
259-- @param node
260--    The product node to identify.
261-- @returns
262--    An Xcode target type, string.
263--
264
265	function xcode.gettargettype(node)
266		local types = {
267			ConsoleApp  = "\"compiled.mach-o.executable\"",
268			WindowedApp = "wrapper.application",
269			StaticLib   = "archive.ar",
270			SharedLib   = "\"compiled.mach-o.dylib\"",
271		}
272		return types[node.cfg.kind]
273	end
274
275
276--
277-- Return a unique file name for a project. Since Xcode uses .xcodeproj's to
278-- represent both workspaces and projects there is a likely change of a name
279-- collision. Tack on a number to differentiate them.
280--
281-- @param prj
282--    The project being queried.
283-- @returns
284--    A uniqued file name
285--
286
287	function xcode.getxcodeprojname(prj)
288		-- if there is a workspace with matching name, then use "projectname1.xcodeproj"
289		-- just get something working for now
290		local fname = p.filename(prj, ".xcodeproj")
291		return fname
292	end
293
294
295--
296-- Returns true if the file name represents a framework.
297--
298-- @param fname
299--    The name of the file to test.
300--
301
302	function xcode.isframework(fname)
303		return (path.getextension(fname) == ".framework")
304	end
305
306
307--
308-- Retrieves a unique 12 byte ID for an object.
309-- This function accepts an array of parameters that will be used to generate the id.
310--
311-- @returns
312--    A 24-character string representing the 12 byte ID.
313--
314
315	function xcode.newid(...)
316		local name = ''
317		local arg = {...}
318		for i, v in pairs(arg) do
319			name = name..v..'****'
320		end
321
322
323		return ("%08X%08X%08X"):format(name:hash(16777619), name:hash(2166136261), name:hash(46577619))
324	end
325
326
327--
328-- Create a product tree node and all projects in a workspace; assigning IDs
329-- that are needed for inter-project dependencies.
330--
331-- @param wks
332--    The workspace to prepare.
333--
334
335	function xcode.prepareWorkspace(wks)
336		-- create and cache a list of supported platforms
337		wks.xcode = { }
338
339		for prj in p.workspace.eachproject(wks) do
340			-- need a configuration to get the target information
341			local cfg = project.getconfig(prj, prj.configurations[1], prj.platforms[1])
342
343			-- build the product tree node
344			local bundlepath = cfg.buildtarget.bundlename ~= "" and cfg.buildtarget.bundlename or cfg.buildtarget.name;
345			if (prj.external) then
346				bundlepath = cfg.project.name
347			end
348
349			local node = p.tree.new(path.getname(bundlepath))
350
351			node.cfg = cfg
352			node.id = xcode.newid(node.name, "product")
353			node.targetid = xcode.newid(node.name, "target")
354
355			-- attach it to the project
356			prj.xcode = {}
357			prj.xcode.projectnode = node
358		end
359	end
360
361
362---------------------------------------------------------------------------
363-- Section generator functions, in the same order in which they appear
364-- in the .pbxproj file
365---------------------------------------------------------------------------
366
367	function xcode.PBXBuildFile(tr)
368		local settings = {};
369		tree.traverse(tr, {
370			onnode = function(node)
371				if node.buildid then
372					settings[node.buildid] = function(level)
373						_p(level,'%s /* %s in %s */ = {isa = PBXBuildFile; fileRef = %s /* %s */; };',
374							node.buildid, node.name, xcode.getbuildcategory(node), node.id, node.name)
375					end
376				end
377			end
378		})
379
380		if not table.isempty(settings) then
381			_p('/* Begin PBXBuildFile section */')
382			printSettingsTable(2, settings);
383			_p('/* End PBXBuildFile section */')
384			_p('')
385		end
386	end
387
388
389	function xcode.PBXContainerItemProxy(tr)
390		local settings = {}
391		for _, node in ipairs(tr.projects.children) do
392			settings[node.productproxyid] = function()
393				_p(2,'%s /* PBXContainerItemProxy */ = {', node.productproxyid)
394				_p(3,'isa = PBXContainerItemProxy;')
395				_p(3,'containerPortal = %s /* %s */;', node.id, path.getrelative(node.parent.parent.project.location, node.path))
396				_p(3,'proxyType = 2;')
397				_p(3,'remoteGlobalIDString = %s;', node.project.xcode.projectnode.id)
398				_p(3,'remoteInfo = %s;', stringifySetting(node.project.xcode.projectnode.name))
399				_p(2,'};')
400			end
401			settings[node.targetproxyid] = function()
402				_p(2,'%s /* PBXContainerItemProxy */ = {', node.targetproxyid)
403				_p(3,'isa = PBXContainerItemProxy;')
404				_p(3,'containerPortal = %s /* %s */;', node.id, path.getrelative(node.parent.parent.project.location, node.path))
405				_p(3,'proxyType = 1;')
406				_p(3,'remoteGlobalIDString = %s;', node.project.xcode.projectnode.targetid)
407				_p(3,'remoteInfo = %s;', stringifySetting(node.project.xcode.projectnode.name))
408				_p(2,'};')
409			end
410		end
411
412		if not table.isempty(settings) then
413			_p('/* Begin PBXContainerItemProxy section */')
414			printSettingsTable(2, settings);
415			_p('/* End PBXContainerItemProxy section */')
416			_p('')
417		end
418	end
419
420
421	function xcode.PBXFileReference(tr)
422		local cfg = project.getfirstconfig(tr.project)
423		local settings = {}
424
425		tree.traverse(tr, {
426			onleaf = function(node)
427				-- I'm only listing files here, so ignore anything without a path
428				if not node.path then
429					return
430				end
431
432				-- is this the product node, describing the output target?
433				if node.kind == "product" then
434					settings[node.id] = function(level)
435						_p(level,'%s /* %s */ = {isa = PBXFileReference; explicitFileType = %s; includeInIndex = 0; name = %s; path = %s; sourceTree = BUILT_PRODUCTS_DIR; };',
436							node.id, node.name, xcode.gettargettype(node), stringifySetting(node.name), stringifySetting(path.getname(node.cfg.buildtarget.bundlename ~= "" and node.cfg.buildtarget.bundlename or node.cfg.buildtarget.relpath)))
437					end
438				-- is this a project dependency?
439				elseif node.parent.parent == tr.projects then
440					settings[node.parent.id] = function(level)
441						-- ms Is there something wrong with path is relative ?
442						-- if we have a and b without slashes get relative should assume the same parent folder and return ../
443						-- this works if we put it like below
444						local relpath = path.getrelative(path.getabsolute(tr.project.location), path.getabsolute(node.parent.project.location))
445						_p(level,'%s /* %s */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = %s; path = %s; sourceTree = SOURCE_ROOT; };',
446							node.parent.id, node.name, customStringifySetting(node.parent.name), stringifySetting(path.join(relpath, node.parent.name)))
447					end
448				-- something else
449				else
450					settings[node.id] = function(level)
451					local pth, src
452					if xcode.isframework(node.path) then
453						--respect user supplied paths
454						-- look for special variable-starting paths for different sources
455						local nodePath = node.path
456						local _, matchEnd, variable = string.find(nodePath, "^%$%((.+)%)/")
457						if variable then
458							-- by skipping the last '/' we support the same absolute/relative
459							-- paths as before
460							nodePath = string.sub(nodePath, matchEnd + 1)
461						end
462						if string.find(nodePath,'/')  then
463							if string.find(nodePath,'^%.')then
464								--error('relative paths are not currently supported for frameworks')
465								pth = path.getrelative(tr.project.location, node.path)
466								--print(tr.project.location, node.path , pth)
467								src = "SOURCE_ROOT"
468								variable = src
469							else
470								pth = nodePath
471								src = "<absolute>"
472							end
473						end
474						-- if it starts with a variable, use that as the src instead
475						if variable then
476							src = variable
477							-- if we are using a different source tree, it has to be relative
478							-- to that source tree, so get rid of any leading '/'
479							if string.find(pth, '^/') then
480								pth = string.sub(pth, 2)
481							end
482						else
483							pth = "System/Library/Frameworks/" .. node.path
484							src = "SDKROOT"
485						end
486					else
487						-- something else; probably a source code file
488						src = "<group>"
489
490						if node.abspath then
491							pth = path.getrelative(tr.project.location, node.abspath)
492						else
493							pth = node.path
494						end
495						--end
496						end
497						_p(level,'%s /* %s */ = {isa = PBXFileReference; lastKnownFileType = %s; name = %s; path = %s; sourceTree = %s; };',
498							node.id, node.name, xcode.getfiletype(node, cfg), stringifySetting(node.name), stringifySetting(pth), stringifySetting(src))
499					end
500				end
501			end
502		})
503
504		if not table.isempty(settings) then
505			_p('/* Begin PBXFileReference section */')
506			printSettingsTable(2, settings)
507			_p('/* End PBXFileReference section */')
508			_p('')
509		end
510	end
511
512
513	function xcode.PBXFrameworksBuildPhase(tr)
514		_p('/* Begin PBXFrameworksBuildPhase section */')
515		_p(2,'%s /* Frameworks */ = {', tr.products.children[1].fxstageid)
516		_p(3,'isa = PBXFrameworksBuildPhase;')
517		_p(3,'buildActionMask = 2147483647;')
518		_p(3,'files = (')
519
520		-- write out library dependencies
521		tree.traverse(tr.frameworks, {
522			onleaf = function(node)
523				if node.buildid then
524					_p(4,'%s /* %s in Frameworks */,', node.buildid, node.name)
525				end
526			end
527		})
528
529		-- write out project dependencies
530		tree.traverse(tr.projects, {
531			onleaf = function(node)
532				if node.buildid then
533					_p(4,'%s /* %s in Frameworks */,', node.buildid, node.name)
534				end
535			end
536		})
537
538		_p(3,');')
539		_p(3,'runOnlyForDeploymentPostprocessing = 0;')
540		_p(2,'};')
541		_p('/* End PBXFrameworksBuildPhase section */')
542		_p('')
543	end
544
545
546	function xcode.PBXGroup(tr)
547		local settings = {}
548
549		tree.traverse(tr, {
550			onnode = function(node)
551				-- Skip over anything that isn't a proper group
552				if (node.path and #node.children == 0) or node.kind == "vgroup" then
553					return
554				end
555
556				settings[node.productgroupid or node.id] = function()
557					-- project references get special treatment
558					if node.parent == tr.projects then
559						_p(2,'%s /* Products */ = {', node.productgroupid)
560					else
561						_p(2,'%s /* %s */ = {', node.id, node.name)
562					end
563
564					_p(3,'isa = PBXGroup;')
565					_p(3,'children = (')
566					for _, childnode in ipairs(node.children) do
567						_p(4,'%s /* %s */,', childnode.id, childnode.name)
568					end
569					_p(3,');')
570
571					if node.parent == tr.projects then
572						_p(3,'name = Products;')
573					else
574						_p(3,'name = %s;', stringifySetting(node.name))
575
576						local vpath = project.getvpath(tr.project, node.name)
577
578						if node.path and node.name ~= vpath then
579							local p = node.path
580							if node.parent.path then
581								p = path.getrelative(node.parent.path, node.path)
582							end
583							_p(3,'path = %s;', stringifySetting(p))
584						end
585					end
586
587					_p(3,'sourceTree = "<group>";')
588					_p(2,'};')
589				end
590			end
591		}, true)
592
593		if not table.isempty(settings) then
594			_p('/* Begin PBXGroup section */')
595			printSettingsTable(2, settings)
596			_p('/* End PBXGroup section */')
597			_p('')
598		end
599	end
600
601
602	function xcode.PBXNativeTarget(tr)
603		_p('/* Begin PBXNativeTarget section */')
604		for _, node in ipairs(tr.products.children) do
605			local name = tr.project.name
606
607			-- This function checks whether there are build commands of a specific
608			-- type to be executed; they will be generated correctly, but the project
609			-- commands will not contain any per-configuration commands, so the logic
610			-- has to be extended a bit to account for that.
611			local function hasBuildCommands(which)
612				-- standard check...this is what existed before
613				if #tr.project[which] > 0 then
614					return true
615				end
616				-- what if there are no project-level commands? check configs...
617				for _, cfg in ipairs(tr.configs) do
618					if #cfg[which] > 0 then
619						return true
620					end
621				end
622			end
623
624			_p(2,'%s /* %s */ = {', node.targetid, name)
625			_p(3,'isa = PBXNativeTarget;')
626			_p(3,'buildConfigurationList = %s /* Build configuration list for PBXNativeTarget "%s" */;', node.cfgsection, escapeSetting(name))
627			_p(3,'buildPhases = (')
628			if hasBuildCommands('prebuildcommands') then
629				_p(4,'9607AE1010C857E500CD1376 /* Prebuild */,')
630			end
631			_p(4,'%s /* Resources */,', node.resstageid)
632			_p(4,'%s /* Sources */,', node.sourcesid)
633			if hasBuildCommands('prelinkcommands') then
634				_p(4,'9607AE3510C85E7E00CD1376 /* Prelink */,')
635			end
636			_p(4,'%s /* Frameworks */,', node.fxstageid)
637			if hasBuildCommands('postbuildcommands') then
638				_p(4,'9607AE3710C85E8F00CD1376 /* Postbuild */,')
639			end
640			_p(3,');')
641			_p(3,'buildRules = (')
642			_p(3,');')
643
644			_p(3,'dependencies = (')
645			for _, node in ipairs(tr.projects.children) do
646				_p(4,'%s /* PBXTargetDependency */,', node.targetdependid)
647			end
648			_p(3,');')
649
650			_p(3,'name = %s;', stringifySetting(name))
651
652			local p
653			if node.cfg.kind == "ConsoleApp" then
654				p = "$(HOME)/bin"
655			elseif node.cfg.kind == "WindowedApp" then
656				p = "$(HOME)/Applications"
657			end
658			if p then
659				_p(3,'productInstallPath = %s;', stringifySetting(p))
660			end
661
662			_p(3,'productName = %s;', stringifySetting(name))
663			_p(3,'productReference = %s /* %s */;', node.id, node.name)
664			_p(3,'productType = %s;', stringifySetting(xcode.getproducttype(node)))
665			_p(2,'};')
666		end
667		_p('/* End PBXNativeTarget section */')
668		_p('')
669	end
670
671
672	function xcode.PBXProject(tr)
673		_p('/* Begin PBXProject section */')
674		_p(2,'08FB7793FE84155DC02AAC07 /* Project object */ = {')
675		_p(3,'isa = PBXProject;')
676		_p(3,'buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "%s" */;', tr.name)
677		_p(3,'compatibilityVersion = "Xcode 3.2";')
678		_p(3,'hasScannedForEncodings = 1;')
679		_p(3,'mainGroup = %s /* %s */;', tr.id, tr.name)
680		_p(3,'projectDirPath = "";')
681
682		if #tr.projects.children > 0 then
683			_p(3,'projectReferences = (')
684			for _, node in ipairs(tr.projects.children) do
685				_p(4,'{')
686				_p(5,'ProductGroup = %s /* Products */;', node.productgroupid)
687				_p(5,'ProjectRef = %s /* %s */;', node.id, path.getname(node.path))
688				_p(4,'},')
689			end
690			_p(3,');')
691		end
692
693		_p(3,'projectRoot = "";')
694		_p(3,'targets = (')
695		for _, node in ipairs(tr.products.children) do
696			_p(4,'%s /* %s */,', node.targetid, node.name)
697		end
698		_p(3,');')
699		_p(2,'};')
700		_p('/* End PBXProject section */')
701		_p('')
702	end
703
704
705	function xcode.PBXReferenceProxy(tr)
706		local settings = {}
707
708		tree.traverse(tr.projects, {
709			onleaf = function(node)
710				settings[node.id] = function()
711					_p(2,'%s /* %s */ = {', node.id, node.name)
712					_p(3,'isa = PBXReferenceProxy;')
713					_p(3,'fileType = %s;', xcode.gettargettype(node))
714					_p(3,'path = %s;', stringifySetting(node.name))
715					_p(3,'remoteRef = %s /* PBXContainerItemProxy */;', node.parent.productproxyid)
716					_p(3,'sourceTree = BUILT_PRODUCTS_DIR;')
717					_p(2,'};')
718				end
719			end
720		})
721
722		if not table.isempty(settings) then
723			_p('/* Begin PBXReferenceProxy section */')
724			printSettingsTable(2, settings)
725			_p('/* End PBXReferenceProxy section */')
726			_p('')
727		end
728	end
729
730
731	function xcode.PBXResourcesBuildPhase(tr)
732		_p('/* Begin PBXResourcesBuildPhase section */')
733		for _, target in ipairs(tr.products.children) do
734			_p(2,'%s /* Resources */ = {', target.resstageid)
735			_p(3,'isa = PBXResourcesBuildPhase;')
736			_p(3,'buildActionMask = 2147483647;')
737			_p(3,'files = (')
738			tree.traverse(tr, {
739				onnode = function(node)
740					if xcode.getbuildcategory(node) == "Resources" then
741						_p(4,'%s /* %s in Resources */,', node.buildid, node.name)
742					end
743				end
744			})
745			_p(3,');')
746			_p(3,'runOnlyForDeploymentPostprocessing = 0;')
747			_p(2,'};')
748		end
749		_p('/* End PBXResourcesBuildPhase section */')
750		_p('')
751	end
752
753	function xcode.PBXShellScriptBuildPhase(tr)
754		local wrapperWritten = false
755
756		local function doblock(id, name, which)
757			-- start with the project-level commands (most common)
758			local prjcmds = tr.project[which]
759			local commands = table.join(prjcmds, {})
760
761			-- see if there are any config-specific commands to add
762			for _, cfg in ipairs(tr.configs) do
763				local cfgcmds = cfg[which]
764				if #cfgcmds > #prjcmds then
765					table.insert(commands, 'if [ "${CONFIGURATION}" = "' .. cfg.buildcfg .. '" ]; then')
766					for i = #prjcmds + 1, #cfgcmds do
767						table.insert(commands, cfgcmds[i])
768					end
769					table.insert(commands, 'fi')
770				end
771			end
772
773			if #commands > 0 then
774				commands = os.translateCommands(commands, p.MACOSX)
775				if not wrapperWritten then
776					_p('/* Begin PBXShellScriptBuildPhase section */')
777					wrapperWritten = true
778				end
779				_p(2,'%s /* %s */ = {', id, name)
780				_p(3,'isa = PBXShellScriptBuildPhase;')
781				_p(3,'buildActionMask = 2147483647;')
782				_p(3,'files = (')
783				_p(3,');')
784				_p(3,'inputPaths = (');
785				_p(3,');');
786				_p(3,'name = %s;', name);
787				_p(3,'outputPaths = (');
788				_p(3,');');
789				_p(3,'runOnlyForDeploymentPostprocessing = 0;');
790				_p(3,'shellPath = /bin/sh;');
791				_p(3,'shellScript = %s;', stringifySetting(table.concat(commands, '\n')))
792				_p(2,'};')
793			end
794		end
795
796		doblock("9607AE1010C857E500CD1376", "Prebuild", "prebuildcommands")
797		doblock("9607AE3510C85E7E00CD1376", "Prelink", "prelinkcommands")
798		doblock("9607AE3710C85E8F00CD1376", "Postbuild", "postbuildcommands")
799
800		if wrapperWritten then
801			_p('/* End PBXShellScriptBuildPhase section */')
802		end
803	end
804
805
806	function xcode.PBXSourcesBuildPhase(tr)
807		_p('/* Begin PBXSourcesBuildPhase section */')
808		for _, target in ipairs(tr.products.children) do
809			_p(2,'%s /* Sources */ = {', target.sourcesid)
810			_p(3,'isa = PBXSourcesBuildPhase;')
811			_p(3,'buildActionMask = 2147483647;')
812			_p(3,'files = (')
813			tree.traverse(tr, {
814				onleaf = function(node)
815					if xcode.getbuildcategory(node) == "Sources" then
816						_p(4,'%s /* %s in Sources */,', node.buildid, node.name)
817					end
818				end
819			})
820			_p(3,');')
821			_p(3,'runOnlyForDeploymentPostprocessing = 0;')
822			_p(2,'};')
823		end
824		_p('/* End PBXSourcesBuildPhase section */')
825		_p('')
826	end
827
828
829	function xcode.PBXVariantGroup(tr)
830		local settings = {}
831		tree.traverse(tr, {
832			onbranch = function(node)
833				settings[node.id] = function()
834					if node.kind == "vgroup" then
835						_p(2,'%s /* %s */ = {', node.id, node.name)
836						_p(3,'isa = PBXVariantGroup;')
837						_p(3,'children = (')
838						for _, lang in ipairs(node.children) do
839							_p(4,'%s /* %s */,', lang.id, lang.name)
840						end
841						_p(3,');')
842						_p(3,'name = %s;', node.name)
843						_p(3,'sourceTree = "<group>";')
844						_p(2,'};')
845					end
846				end
847			end
848		})
849
850		if not table.isempty(settings) then
851			_p('/* Begin PBXVariantGroup section */')
852			printSettingsTable(2, settings)
853			_p('/* End PBXVariantGroup section */')
854			_p('')
855		end
856	end
857
858
859	function xcode.PBXTargetDependency(tr)
860		local settings = {}
861		tree.traverse(tr.projects, {
862			onleaf = function(node)
863				settings[node.parent.targetdependid] = function()
864					_p(2,'%s /* PBXTargetDependency */ = {', node.parent.targetdependid)
865					_p(3,'isa = PBXTargetDependency;')
866					_p(3,'name = %s;', stringifySetting(node.name))
867					_p(3,'targetProxy = %s /* PBXContainerItemProxy */;', node.parent.targetproxyid)
868					_p(2,'};')
869				end
870			end
871		})
872
873		if not table.isempty(settings) then
874			_p('/* Begin PBXTargetDependency section */')
875			printSettingsTable(2, settings)
876			_p('/* End PBXTargetDependency section */')
877			_p('')
878		end
879	end
880
881
882	function xcode.XCBuildConfiguration_Target(tr, target, cfg)
883		local settings = {}
884
885		settings['ALWAYS_SEARCH_USER_PATHS'] = 'NO'
886
887		if cfg.symbols ~= p.OFF then
888			settings['DEBUG_INFORMATION_FORMAT'] = 'dwarf-with-dsym'
889		end
890
891		if cfg.kind ~= "StaticLib" and cfg.buildtarget.prefix ~= '' then
892			settings['EXECUTABLE_PREFIX'] = cfg.buildtarget.prefix
893		end
894
895		--[[if cfg.targetextension then
896			local ext = cfg.targetextension
897			ext = iif(ext:startswith('.'), ext:sub(2), ext)
898			settings['EXECUTABLE_EXTENSION'] = ext
899		end]]
900
901		local outdir = path.getrelative(tr.project.location, path.getdirectory(cfg.buildtarget.relpath))
902		if outdir ~= "." then
903			settings['CONFIGURATION_BUILD_DIR'] = outdir
904		end
905
906		settings['GCC_DYNAMIC_NO_PIC'] = 'NO'
907
908		if tr.infoplist then
909			settings['INFOPLIST_FILE'] = config.findfile(cfg, path.getextension(tr.infoplist.name))
910		end
911
912		installpaths = {
913			ConsoleApp = '/usr/local/bin',
914			WindowedApp = '"$(HOME)/Applications"',
915			SharedLib = '/usr/local/lib',
916			StaticLib = '/usr/local/lib',
917		}
918		settings['INSTALL_PATH'] = installpaths[cfg.kind]
919
920		local fileNameList = {}
921		local file_tree = project.getsourcetree(tr.project)
922		tree.traverse(tr, {
923				onnode = function(node)
924					if node.buildid and not node.isResource and node.abspath then
925						-- ms this seems to work on visual studio !!!
926						-- why not in xcode ??
927						local filecfg = fileconfig.getconfig(node, cfg)
928						if filecfg and filecfg.flags.ExcludeFromBuild then
929						--fileNameList = fileNameList .. " " ..filecfg.name
930							table.insert(fileNameList, escapeArg(node.name))
931						end
932
933						--ms new way
934						-- if the file is not in this config file list excluded it from build !!!
935						--if not cfg.files[node.abspath] then
936						--	table.insert(fileNameList, escapeArg(node.name))
937						--end
938					end
939				end
940			})
941
942		if not table.isempty(fileNameList) then
943			settings['EXCLUDED_SOURCE_FILE_NAMES'] = fileNameList
944		end
945		settings['PRODUCT_NAME'] = cfg.buildtarget.basename
946
947		--ms not by default ...add it manually if you need it
948		--settings['COMBINE_HIDPI_IMAGES'] = 'YES'
949
950		overrideSettings(settings, cfg.xcodebuildsettings)
951
952		_p(2,'%s /* %s */ = {', cfg.xcode.targetid, cfg.buildcfg)
953		_p(3,'isa = XCBuildConfiguration;')
954		_p(3,'buildSettings = {')
955		printSettingsTable(4, settings)
956		_p(3,'};')
957		printSetting(3, 'name', cfg.buildcfg);
958		_p(2,'};')
959	end
960
961
962	function xcode.XCBuildConfiguration_CLanguageStandard(settings, cfg)
963		if cfg.cdialect and cfg.cdialect ~= "Default" then
964			settings['GCC_C_LANGUAGE_STANDARD'] = cfg.cdialect
965		else
966			settings['GCC_C_LANGUAGE_STANDARD'] = 'gnu99'
967		end
968	end
969
970
971	function xcode.XCBuildConfiguration_CppLanguageStandard(settings, cfg)
972		if cfg.cppdialect and cfg.cppdialect ~= "Default" then
973			settings['CLANG_CXX_LANGUAGE_STANDARD'] = cfg.cppdialect
974		end
975	end
976
977
978	function xcode.XCBuildConfiguration_Project(tr, cfg)
979		local settings = {}
980
981		local archs = {
982			Native = "$(NATIVE_ARCH_ACTUAL)",
983			x86    = "i386",
984			x86_64 = "x86_64",
985			Universal32 = "$(ARCHS_STANDARD_32_BIT)",
986			Universal64 = "$(ARCHS_STANDARD_64_BIT)",
987			Universal = "$(ARCHS_STANDARD_32_64_BIT)",
988		}
989
990		settings['ARCHS'] = archs[cfg.platform or "Native"]
991
992		--ms This is the default so don;t write it
993		--settings['SDKROOT'] = 'macosx'
994
995		local targetdir = path.getdirectory(cfg.buildtarget.relpath)
996		if targetdir ~= "." then
997			settings['CONFIGURATION_BUILD_DIR'] = '$(SYMROOT)'
998		end
999
1000		settings['CONFIGURATION_TEMP_DIR'] = '$(OBJROOT)'
1001
1002		if config.isDebugBuild(cfg) then
1003			settings['COPY_PHASE_STRIP'] = 'NO'
1004		end
1005
1006		xcode.XCBuildConfiguration_CLanguageStandard(settings, cfg)
1007		xcode.XCBuildConfiguration_CppLanguageStandard(settings, cfg)
1008
1009		if cfg.exceptionhandling == p.OFF then
1010			settings['GCC_ENABLE_CPP_EXCEPTIONS'] = 'NO'
1011		end
1012
1013		if cfg.rtti == p.OFF then
1014			settings['GCC_ENABLE_CPP_RTTI'] = 'NO'
1015		end
1016
1017		if cfg.symbols == p.ON and cfg.editandcontinue ~= "Off" then
1018			settings['GCC_ENABLE_FIX_AND_CONTINUE'] = 'YES'
1019		end
1020
1021		if cfg.exceptionhandling == p.OFF then
1022			settings['GCC_ENABLE_OBJC_EXCEPTIONS'] = 'NO'
1023		end
1024
1025		local optimizeMap = { On = 3, Size = 's', Speed = 3, Full = 'fast', Debug = 1 }
1026		settings['GCC_OPTIMIZATION_LEVEL'] = optimizeMap[cfg.optimize] or 0
1027
1028		if cfg.pchheader and not cfg.flags.NoPCH then
1029			settings['GCC_PRECOMPILE_PREFIX_HEADER'] = 'YES'
1030			settings['GCC_PREFIX_HEADER'] = cfg.pchheader
1031		end
1032
1033		local escapedDefines = { }
1034		for i,v in ipairs(cfg.defines) do
1035			escapedDefines[i] = escapeArg(v)
1036		end
1037		settings['GCC_PREPROCESSOR_DEFINITIONS'] = escapedDefines
1038
1039		settings["GCC_SYMBOLS_PRIVATE_EXTERN"] = 'NO'
1040
1041		if cfg.flags.FatalWarnings then
1042			settings['GCC_TREAT_WARNINGS_AS_ERRORS'] = 'YES'
1043		end
1044
1045		settings['GCC_WARN_ABOUT_RETURN_TYPE'] = 'YES'
1046		settings['GCC_WARN_UNUSED_VARIABLE'] = 'YES'
1047
1048		local includedirs = project.getrelative(cfg.project, cfg.includedirs)
1049		for i,v in ipairs(includedirs) do
1050			cfg.includedirs[i] = p.quoted(v)
1051		end
1052		settings['USER_HEADER_SEARCH_PATHS'] = cfg.includedirs
1053
1054		local sysincludedirs = project.getrelative(cfg.project, cfg.sysincludedirs)
1055		for i,v in ipairs(sysincludedirs) do
1056			cfg.sysincludedirs[i] = p.quoted(v)
1057		end
1058		if not table.isempty(cfg.sysincludedirs) then
1059			table.insert(cfg.sysincludedirs, "$(inherited)")
1060		end
1061		settings['HEADER_SEARCH_PATHS'] = cfg.sysincludedirs
1062
1063		for i,v in ipairs(cfg.libdirs) do
1064			cfg.libdirs[i] = p.project.getrelative(cfg.project, cfg.libdirs[i])
1065		end
1066		settings['LIBRARY_SEARCH_PATHS'] = cfg.libdirs
1067
1068		for i,v in ipairs(cfg.frameworkdirs) do
1069			cfg.frameworkdirs[i] = p.project.getrelative(cfg.project, cfg.frameworkdirs[i])
1070		end
1071		settings['FRAMEWORK_SEARCH_PATHS'] = cfg.frameworkdirs
1072
1073		local objDir = path.getrelative(tr.project.location, cfg.objdir)
1074		settings['OBJROOT'] = objDir
1075
1076		settings['ONLY_ACTIVE_ARCH'] = iif(p.config.isDebugBuild(cfg), 'YES', 'NO')
1077
1078		-- build list of "other" C/C++ flags
1079		local checks = {
1080			["-ffast-math"]          = cfg.floatingpoint == "Fast",
1081			["-ffloat-store"]        = cfg.floatingpoint == "Strict",
1082			["-fomit-frame-pointer"] = cfg.flags.NoFramePointer,
1083		}
1084
1085		local flags = { }
1086		for flag, check in pairs(checks) do
1087			if check then
1088				table.insert(flags, flag)
1089			end
1090		end
1091
1092
1093		--[[if (type(cfg.buildoptions) == "table") then
1094			for k,v in pairs(cfg.buildoptions) do
1095				table.insertflat(flags, string.explode(v," -"))
1096			end
1097		end
1098		]]
1099
1100		settings['OTHER_CFLAGS'] = table.join(flags, cfg.buildoptions)
1101
1102		-- build list of "other" linked flags. All libraries that aren't frameworks
1103		-- are listed here, so I don't have to try and figure out if they are ".a"
1104		-- or ".dylib", which Xcode requires to list in the Frameworks section
1105		flags = { }
1106		for _, lib in ipairs(config.getlinks(cfg, "system")) do
1107			if not xcode.isframework(lib) then
1108				table.insert(flags, "-l" .. lib)
1109			end
1110		end
1111
1112		--ms this step is for reference projects only
1113		for _, lib in ipairs(config.getlinks(cfg, "dependencies", "object")) do
1114			if (lib.external) then
1115				if not xcode.isframework(lib.linktarget.basename) then
1116					table.insert(flags, "-l" .. escapeArg(lib.linktarget.basename))
1117				end
1118			end
1119		end
1120
1121		settings['OTHER_LDFLAGS'] = table.join(flags, cfg.linkoptions)
1122
1123		if cfg.flags.StaticRuntime then
1124			settings['STANDARD_C_PLUS_PLUS_LIBRARY_TYPE'] = 'static'
1125		end
1126
1127		if targetdir ~= "." then
1128			settings['SYMROOT'] = path.getrelative(tr.project.location, targetdir)
1129		end
1130
1131		if cfg.warnings == "Extra" then
1132			settings['WARNING_CFLAGS'] = '-Wall -Wextra'
1133		end
1134
1135		overrideSettings(settings, cfg.xcodebuildsettings)
1136
1137		_p(2,'%s /* %s */ = {', cfg.xcode.projectid, cfg.buildcfg)
1138		_p(3,'isa = XCBuildConfiguration;')
1139		_p(3,'buildSettings = {')
1140		printSettingsTable(4, settings)
1141		_p(3,'};')
1142		printSetting(3, 'name', cfg.buildcfg);
1143		_p(2,'};')
1144	end
1145
1146
1147	function xcode.XCBuildConfiguration(tr)
1148		local settings = {}
1149
1150		for _, target in ipairs(tr.products.children) do
1151			for _, cfg in ipairs(tr.configs) do
1152				settings[cfg.xcode.targetid] = function()
1153					xcode.XCBuildConfiguration_Target(tr, target, cfg)
1154				end
1155			end
1156		end
1157		for _, cfg in ipairs(tr.configs) do
1158			settings[cfg.xcode.projectid] = function()
1159				xcode.XCBuildConfiguration_Project(tr, cfg)
1160			end
1161		end
1162
1163		if not table.isempty(settings) then
1164			_p('/* Begin XCBuildConfiguration section */')
1165			printSettingsTable(0, settings)
1166			_p('/* End XCBuildConfiguration section */')
1167			_p('')
1168		end
1169	end
1170
1171
1172	function xcode.XCBuildConfigurationList(tr)
1173		local wks = tr.project.workspace
1174		local defaultCfgName = stringifySetting(tr.configs[1].buildcfg)
1175		local settings = {}
1176
1177		for _, target in ipairs(tr.products.children) do
1178			settings[target.cfgsection] = function()
1179				_p(2,'%s /* Build configuration list for PBXNativeTarget "%s" */ = {', target.cfgsection, target.name)
1180				_p(3,'isa = XCConfigurationList;')
1181				_p(3,'buildConfigurations = (')
1182				for _, cfg in ipairs(tr.configs) do
1183					_p(4,'%s /* %s */,', cfg.xcode.targetid, cfg.buildcfg)
1184				end
1185				_p(3,');')
1186				_p(3,'defaultConfigurationIsVisible = 0;')
1187				_p(3,'defaultConfigurationName = %s;', defaultCfgName)
1188				_p(2,'};')
1189			end
1190		end
1191		settings['1DEB928908733DD80010E9CD'] = function()
1192			_p(2,'1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "%s" */ = {', tr.name)
1193			_p(3,'isa = XCConfigurationList;')
1194			_p(3,'buildConfigurations = (')
1195			for _, cfg in ipairs(tr.configs) do
1196				_p(4,'%s /* %s */,', cfg.xcode.projectid, cfg.buildcfg)
1197			end
1198			_p(3,');')
1199			_p(3,'defaultConfigurationIsVisible = 0;')
1200			_p(3,'defaultConfigurationName = %s;', defaultCfgName)
1201			_p(2,'};')
1202		end
1203
1204		_p('/* Begin XCConfigurationList section */')
1205		printSettingsTable(2, settings)
1206		_p('/* End XCConfigurationList section */')
1207	end
1208