1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "config.h"
24 #include "xcode.h"
25 
26 #include <fstream>
27 #include <algorithm>
28 
29 #ifdef MACOSX
30 #include <sstream>
31 #include <iomanip>
32 #include <CommonCrypto/CommonCrypto.h>
33 #endif
34 
35 namespace CreateProjectTool {
36 
37 #define DEBUG_XCODE_HASH 0
38 
39 #define IOS_TARGET 0
40 #define OSX_TARGET 1
41 
42 #define ADD_DEFINE(defines, name) \
43 	defines.push_back(name);
44 
45 #define REMOVE_DEFINE(defines, name) \
46 	{ ValueList::iterator i = std::find(defines.begin(), defines.end(), name); if (i != defines.end()) defines.erase(i); }
47 
48 #define CONTAINS_DEFINE(defines, name) \
49 	(std::find(defines.begin(), defines.end(), name) != defines.end())
50 
51 #define ADD_SETTING(config, key, value) \
52 	config._settings[key] = Setting(value, "", kSettingsNoQuote);
53 
54 #define ADD_SETTING_ORDER(config, key, value, order) \
55 	config.settings[key] = Setting(value, "", kSettingsNoQuote, 0, order);
56 
57 #define ADD_SETTING_ORDER_NOVALUE(config, key, comment, order) \
58 	config._settings[key] = Setting("", comment, kSettingsNoValue, 0, order);
59 
60 #define ADD_SETTING_QUOTE(config, key, value) \
61 	config._settings[key] = Setting(value);
62 
63 #define ADD_SETTING_QUOTE_VAR(config, key, value) \
64 	config._settings[key] = Setting(value, "", kSettingsQuoteVariable);
65 
66 #define ADD_SETTING_LIST(config, key, values, flags, indent) \
67 	config._settings[key] = Setting(values, flags, indent);
68 
69 #define REMOVE_SETTING(config, key) \
70 	config._settings.erase(key);
71 
72 #define ADD_BUILD_FILE(id, name, fileRefId, comment) { \
73 	Object *buildFile = new Object(this, id, name, "PBXBuildFile", "PBXBuildFile", comment); \
74 	buildFile->addProperty("fileRef", fileRefId, name, kSettingsNoValue); \
75 	_buildFile.add(buildFile); \
76 	_buildFile._flags = kSettingsSingleItem; \
77 }
78 
79 #define ADD_FILE_REFERENCE(id, name, properties) { \
80 	Object *fileRef = new Object(this, id, name, "PBXFileReference", "PBXFileReference", name); \
81 	if (!properties._fileEncoding.empty()) fileRef->addProperty("fileEncoding", properties._fileEncoding, "", kSettingsNoValue); \
82 	if (!properties._lastKnownFileType.empty()) fileRef->addProperty("lastKnownFileType", properties._lastKnownFileType, "", kSettingsNoValue|kSettingsQuoteVariable); \
83 	if (!properties._fileName.empty()) fileRef->addProperty("name", properties._fileName, "", kSettingsNoValue|kSettingsQuoteVariable); \
84 	if (!properties._filePath.empty()) fileRef->addProperty("path", properties._filePath, "", kSettingsNoValue|kSettingsQuoteVariable); \
85 	if (!properties._sourceTree.empty()) fileRef->addProperty("sourceTree", properties._sourceTree, "", kSettingsNoValue); \
86 	_fileReference.add(fileRef); \
87 	_fileReference._flags = kSettingsSingleItem; \
88 }
89 
producesObjectFileOnOSX(const std::string & fileName)90 bool producesObjectFileOnOSX(const std::string &fileName) {
91 	std::string n, ext;
92 	splitFilename(fileName, n, ext);
93 
94 	// Note that the difference between this and the general producesObjectFile is that
95 	// this one adds Objective-C(++), and removes asm-support.
96 	if (ext == "cpp" || ext == "c" || ext == "m" || ext == "mm")
97 		return true;
98 	else
99 		return false;
100 }
101 
targetIsIOS(const std::string & targetName)102 bool targetIsIOS(const std::string &targetName) {
103 	return targetName.length() > 4 && targetName.substr(targetName.length() - 4) == "-iOS";
104 }
105 
shouldSkipFileForTarget(const std::string & fileID,const std::string & targetName,const std::string & fileName)106 bool shouldSkipFileForTarget(const std::string &fileID, const std::string &targetName, const std::string &fileName) {
107 	// Rules:
108 	// - if the parent directory is "backends/platform/ios7", the file belongs to the iOS target.
109 	// - if the parent directory is "/sdl", the file belongs to the macOS target.
110 	// - if the file has a suffix, like "_osx", or "_ios", the file belongs to one of the target.
111 	// - if the file is an macOS icon file (icns), it belongs to the macOS target.
112 	std::string name, ext;
113 	splitFilename(fileName, name, ext);
114 
115 	if (targetIsIOS(targetName)) {
116 		// iOS target: we skip all files with the "_osx" suffix
117 		if (name.length() > 4 && name.substr(name.length() - 4) == "_osx") {
118 			return true;
119 		}
120 		// We don't need SDL for the iOS target
121 		static const std::string sdl_directory = "/sdl/";
122 		static const std::string surfacesdl_directory = "/surfacesdl/";
123 		static const std::string doublebufferdl_directory = "/doublebuffersdl/";
124 		if (fileID.find(sdl_directory) != std::string::npos
125 		 || fileID.find(surfacesdl_directory) != std::string::npos
126 		 || fileID.find(doublebufferdl_directory) != std::string::npos) {
127 			return true;
128 		 }
129 		if (ext == "icns") {
130 			return true;
131 		}
132 	}
133 	else {
134 		// Ugly hack: explicitly remove the browser.cpp file.
135 		// The problem is that we have only one project for two different targets,
136 		// and the parsing of the "mk" files added this file for both targets...
137 		if (fileID.length() > 12 && fileID.substr(fileID.length() - 12) == "/browser.cpp") {
138 			return true;
139 		}
140 		// macOS target: we skip all files with the "_ios" suffix
141 		if (name.length() > 4 && name.substr(name.length() - 4) == "_ios") {
142 			return true;
143 		}
144 		// macOS target: we skip all files with the "ios7_" prefix
145 		if (name.length() > 5 && name.substr(0, 5) == "ios7_") {
146 			return true;
147 		}
148 		// parent directory
149 		const std::string directory = fileID.substr(0, fileID.length() - fileName.length());
150 		static const std::string iphone_directory = "backends/platform/ios7";
151 		if (directory.length() > iphone_directory.length() && directory.substr(directory.length() - iphone_directory.length()) == iphone_directory) {
152 			return true;
153 		}
154 	}
155 	return false;
156 }
157 
Group(XcodeProvider * objectParent,const std::string & groupName,const std::string & uniqueName,const std::string & path)158 XcodeProvider::Group::Group(XcodeProvider *objectParent, const std::string &groupName, const std::string &uniqueName, const std::string &path) : Object(objectParent, uniqueName, groupName, "PBXGroup", "", groupName) {
159 	bool path_is_absolute = (path.length() > 0 && path.at(0) == '/');
160 	addProperty("name", _name, "", kSettingsNoValue | kSettingsQuoteVariable);
161 	addProperty("sourceTree", path_is_absolute ? "<absolute>" : "<group>", "", kSettingsNoValue | kSettingsQuoteVariable);
162 
163 	if (path != "") {
164 		addProperty("path", path, "", kSettingsNoValue | kSettingsQuoteVariable);
165 	}
166 	_childOrder = 0;
167 	_treeName = uniqueName;
168 }
169 
ensureChildExists(const std::string & name)170 void XcodeProvider::Group::ensureChildExists(const std::string &name) {
171 	std::map<std::string, Group*>::iterator it = _childGroups.find(name);
172 	if (it == _childGroups.end()) {
173 		Group *child = new Group(_parent, name, this->_treeName + '/' + name, name);
174 		_childGroups[name] = child;
175 		addChildGroup(child);
176 		_parent->_groups.add(child);
177 	}
178 }
179 
addChildInternal(const std::string & id,const std::string & comment)180 void XcodeProvider::Group::addChildInternal(const std::string &id, const std::string &comment) {
181 	if (_properties.find("children") == _properties.end()) {
182 		Property children;
183 		children._hasOrder = true;
184 		children._flags = kSettingsAsList;
185 		_properties["children"] = children;
186 	}
187 	_properties["children"]._settings[id] = Setting("", comment + " in Sources", kSettingsNoValue, 0, _childOrder++);
188 	if (_childOrder == 1) {
189 		// Force children to use () even when there is only 1 child.
190 		// Also this enforces the use of "," after the single item, instead of ; (see writeProperty)
191 		_properties["children"]._flags |= kSettingsSingleItem;
192 	} else {
193 		_properties["children"]._flags ^= kSettingsSingleItem;
194 	}
195 
196 }
197 
addChildGroup(const Group * group)198 void XcodeProvider::Group::addChildGroup(const Group *group) {
199 	addChildInternal(_parent->getHash(group->_treeName), group->_treeName);
200 }
201 
addChildFile(const std::string & name)202 void XcodeProvider::Group::addChildFile(const std::string &name) {
203 	std::string id = "FileReference_" + _treeName + "/" + name;
204 	addChildInternal(_parent->getHash(id), name);
205 	FileProperty property = FileProperty(name, name, name, "\"<group>\"");
206 
207 	_parent->addFileReference(id, name, property);
208 	if (producesObjectFileOnOSX(name)) {
209 		_parent->addBuildFile(_treeName + "/" + name, name, _parent->getHash(id), name + " in Sources");
210 	}
211 }
212 
addChildByHash(const std::string & hash,const std::string & name)213 void XcodeProvider::Group::addChildByHash(const std::string &hash, const std::string &name) {
214 	addChildInternal(hash, name);
215 }
216 
getChildGroup(const std::string & name)217 XcodeProvider::Group *XcodeProvider::Group::getChildGroup(const std::string &name) {
218 	std::map<std::string, Group *>::iterator it = _childGroups.find(name);
219 	assert(it != _childGroups.end());
220 	return it->second;
221 }
222 
touchGroupsForPath(const std::string & path)223 XcodeProvider::Group *XcodeProvider::touchGroupsForPath(const std::string &path) {
224 	if (_rootSourceGroup == NULL) {
225 		assert(path == _projectRoot);
226 		_rootSourceGroup = new Group(this, "Sources", path, path);
227 		_groups.add(_rootSourceGroup);
228 		return _rootSourceGroup;
229 	} else {
230 		assert(path.find(_projectRoot) == 0);
231 		std::string subPath = path.substr(_projectRoot.size() + 1);
232 		Group *currentGroup = _rootSourceGroup;
233 		size_t firstPathComponent = subPath.find_first_of('/');
234 		// We assume here that all paths have trailing '/', otherwise this breaks.
235 		while (firstPathComponent != std::string::npos) {
236 			currentGroup->ensureChildExists(subPath.substr(0, firstPathComponent));
237 			currentGroup = currentGroup->getChildGroup(subPath.substr(0, firstPathComponent));
238 			subPath = subPath.substr(firstPathComponent + 1);
239 			firstPathComponent = subPath.find_first_of('/');
240 		}
241 		return currentGroup;
242 	}
243 }
244 
addFileReference(const std::string & id,const std::string & name,FileProperty properties)245 void XcodeProvider::addFileReference(const std::string &id, const std::string &name, FileProperty properties) {
246 	Object *fileRef = new Object(this, id, name, "PBXFileReference", "PBXFileReference", name);
247 	if (!properties._fileEncoding.empty()) fileRef->addProperty("fileEncoding", properties._fileEncoding, "", kSettingsNoValue);
248 	if (!properties._lastKnownFileType.empty()) fileRef->addProperty("lastKnownFileType", properties._lastKnownFileType, "", kSettingsNoValue | kSettingsQuoteVariable);
249 	if (!properties._fileName.empty()) fileRef->addProperty("name", properties._fileName, "", kSettingsNoValue | kSettingsQuoteVariable);
250 	if (!properties._filePath.empty()) fileRef->addProperty("path", properties._filePath, "", kSettingsNoValue | kSettingsQuoteVariable);
251 	if (!properties._sourceTree.empty()) fileRef->addProperty("sourceTree", properties._sourceTree, "", kSettingsNoValue);
252 	_fileReference.add(fileRef);
253 	_fileReference._flags = kSettingsSingleItem;
254 }
255 
addProductFileReference(const std::string & id,const std::string & name)256 void XcodeProvider::addProductFileReference(const std::string &id, const std::string &name) {
257 	Object *fileRef = new Object(this, id, name, "PBXFileReference", "PBXFileReference", name);
258 	fileRef->addProperty("explicitFileType", "wrapper.application", "", kSettingsNoValue | kSettingsQuoteVariable);
259 	fileRef->addProperty("includeInIndex", "0", "", kSettingsNoValue);
260 	fileRef->addProperty("path", name, "", kSettingsNoValue | kSettingsQuoteVariable);
261 	fileRef->addProperty("sourceTree", "BUILT_PRODUCTS_DIR", "", kSettingsNoValue);
262 	_fileReference.add(fileRef);
263 	_fileReference._flags = kSettingsSingleItem;
264 }
265 
addBuildFile(const std::string & id,const std::string & name,const std::string & fileRefId,const std::string & comment)266 void XcodeProvider::addBuildFile(const std::string &id, const std::string &name, const std::string &fileRefId, const std::string &comment) {
267 
268 	Object *buildFile = new Object(this, id, name, "PBXBuildFile", "PBXBuildFile", comment);
269 	buildFile->addProperty("fileRef", fileRefId, name, kSettingsNoValue);
270 	_buildFile.add(buildFile);
271 	_buildFile._flags = kSettingsSingleItem;
272 }
273 
XcodeProvider(StringList & global_warnings,std::map<std::string,StringList> & project_warnings,const int version)274 XcodeProvider::XcodeProvider(StringList &global_warnings, std::map<std::string, StringList> &project_warnings, const int version)
275 	: ProjectProvider(global_warnings, project_warnings, version) {
276 	_rootSourceGroup = NULL;
277 }
278 
addResourceFiles(const BuildSetup & setup,StringList & includeList,StringList & excludeList)279 void XcodeProvider::addResourceFiles(const BuildSetup &setup, StringList &includeList, StringList &excludeList) {
280 	includeList.push_back(setup.srcDir + "/dists/ios7/Info.plist");
281 
282 	ValueList &resources = getResourceFiles();
283 	for (ValueList::iterator it = resources.begin(); it != resources.end(); ++it) {
284 		includeList.push_back(setup.srcDir + "/" + *it);
285 	}
286 
287 	StringList td;
288 	createModuleList(setup.srcDir + "/backends/platform/ios7", setup.defines, td, includeList, excludeList);
289 }
290 
createWorkspace(const BuildSetup & setup)291 void XcodeProvider::createWorkspace(const BuildSetup &setup) {
292 	// Create project folder
293 	std::string workspace = setup.outputDir + '/' + PROJECT_NAME ".xcodeproj";
294 	createDirectory(workspace);
295 	_projectRoot = setup.srcDir;
296 	touchGroupsForPath(_projectRoot);
297 
298 	// Setup global objects
299 	setupDefines(setup);
300 	_targets.push_back(PROJECT_DESCRIPTION "-iOS");
301 	_targets.push_back(PROJECT_DESCRIPTION "-macOS");
302 	setupCopyFilesBuildPhase();
303 	setupFrameworksBuildPhase(setup);
304 	setupNativeTarget();
305 	setupProject();
306 	setupResourcesBuildPhase();
307 	setupBuildConfiguration(setup);
308 	setupImageAssetCatalog(setup);
309 }
310 
311 // We are done with constructing all the object graph and we got through every project, output the main project file
312 // (this is kind of a hack since other providers use separate project files)
createOtherBuildFiles(const BuildSetup & setup)313 void XcodeProvider::createOtherBuildFiles(const BuildSetup &setup) {
314 	// This needs to be done at the end when all build files have been accounted for
315 	setupSourcesBuildPhase();
316 
317 	ouputMainProjectFile(setup);
318 }
319 
320 // Store information about a project here, for use at the end
createProjectFile(const std::string &,const std::string &,const BuildSetup & setup,const std::string & moduleDir,const StringList & includeList,const StringList & excludeList)321 void XcodeProvider::createProjectFile(const std::string &, const std::string &, const BuildSetup &setup, const std::string &moduleDir,
322                                       const StringList &includeList, const StringList &excludeList) {
323 	std::string modulePath;
324 	if (!moduleDir.compare(0, setup.srcDir.size(), setup.srcDir)) {
325 		modulePath = moduleDir.substr(setup.srcDir.size());
326 		if (!modulePath.empty() && modulePath.at(0) == '/')
327 			modulePath.erase(0, 1);
328 	}
329 
330 	std::ofstream project;
331 	if (modulePath.size())
332 		addFilesToProject(moduleDir, project, includeList, excludeList, setup.filePrefix + '/' + modulePath);
333 	else
334 		addFilesToProject(moduleDir, project, includeList, excludeList, setup.filePrefix);
335 }
336 
337 //////////////////////////////////////////////////////////////////////////
338 // Main Project file
339 //////////////////////////////////////////////////////////////////////////
ouputMainProjectFile(const BuildSetup & setup)340 void XcodeProvider::ouputMainProjectFile(const BuildSetup &setup) {
341 	std::ofstream project((setup.outputDir + '/' + PROJECT_NAME ".xcodeproj" + '/' + "project.pbxproj").c_str());
342 	if (!project)
343 		error("Could not open \"" + setup.outputDir + '/' + PROJECT_NAME ".xcodeproj" + '/' + "project.pbxproj\" for writing");
344 
345 	//////////////////////////////////////////////////////////////////////////
346 	// Header
347 	project << "// !$*UTF8*$!\n"
348 	           "{\n"
349 	           "\t" << writeSetting("archiveVersion", "1", "", kSettingsNoQuote) << ";\n"
350 	           "\tclasses = {\n"
351 	           "\t};\n"
352 	           "\t" << writeSetting("objectVersion", "46", "", kSettingsNoQuote) << ";\n"
353 	           "\tobjects = {\n";
354 
355 	//////////////////////////////////////////////////////////////////////////
356 	// List of objects
357 	project << _buildFile.toString();
358 	project << _copyFilesBuildPhase.toString();
359 	project << _fileReference.toString();
360 	project << _frameworksBuildPhase.toString();
361 	project << _groups.toString();
362 	project << _nativeTarget.toString();
363 	project << _project.toString();
364 	project << _resourcesBuildPhase.toString();
365 	project << _sourcesBuildPhase.toString();
366 	project << _buildConfiguration.toString();
367 	project << _configurationList.toString();
368 
369 	//////////////////////////////////////////////////////////////////////////
370 	// Footer
371 	project << "\t};\n"
372 	           "\t" << writeSetting("rootObject", getHash("PBXProject"), "Project object", kSettingsNoQuote) << ";\n"
373 	           "}\n";
374 
375 }
376 
377 //////////////////////////////////////////////////////////////////////////
378 // Files
379 //////////////////////////////////////////////////////////////////////////
writeFileListToProject(const FileNode & dir,std::ofstream & projectFile,const int indentation,const StringList & duplicate,const std::string & objPrefix,const std::string & filePrefix)380 void XcodeProvider::writeFileListToProject(const FileNode &dir, std::ofstream &projectFile, const int indentation,
381                                            const StringList &duplicate, const std::string &objPrefix, const std::string &filePrefix) {
382 
383 	// Ensure that top-level groups are generated for i.e. engines/
384 	Group *group = touchGroupsForPath(filePrefix);
385 	for (FileNode::NodeList::const_iterator i = dir.children.begin(); i != dir.children.end(); ++i) {
386 		const FileNode *node = *i;
387 
388 		// Iff it is a file, then add (build) file references. Since we're using Groups and not File References
389 		// for folders, we shouldn't add folders as file references, obviously.
390 		if (node->children.empty()) {
391 			group->addChildFile(node->name);
392 		}
393 		// Process child nodes
394 		if (!node->children.empty())
395 			writeFileListToProject(*node, projectFile, indentation + 1, duplicate, objPrefix + node->name + '_', filePrefix + node->name + '/');
396 	}
397 }
398 
399 //////////////////////////////////////////////////////////////////////////
400 // Setup functions
401 //////////////////////////////////////////////////////////////////////////
setupCopyFilesBuildPhase()402 void XcodeProvider::setupCopyFilesBuildPhase() {
403 	// Nothing to do here
404 }
405 
406 #define DEF_SYSFRAMEWORK(framework) properties[framework".framework"] = FileProperty("wrapper.framework", framework".framework", "System/Library/Frameworks/" framework ".framework", "SDKROOT"); \
407 	ADD_SETTING_ORDER_NOVALUE(children, getHash(framework".framework"), framework".framework", fwOrder++);
408 
409 #define DEF_SYSTBD(lib) properties[lib".tbd"] = FileProperty("sourcecode.text-based-dylib-definition", lib".tbd", "usr/lib/" lib ".tbd", "SDKROOT"); \
410 	ADD_SETTING_ORDER_NOVALUE(children, getHash(lib".tbd"), lib".tbd", fwOrder++);
411 
412 #define DEF_LOCALLIB_STATIC_PATH(path,lib,absolute) properties[lib".a"] = FileProperty("archive.ar", lib ".a", path, (absolute ? "\"<absolute>\"" : "\"<group>\"")); \
413 	ADD_SETTING_ORDER_NOVALUE(children, getHash(lib".a"), lib".a", fwOrder++);
414 
415 #define DEF_LOCALLIB_STATIC(lib) DEF_LOCALLIB_STATIC_PATH("/usr/local/lib/" lib ".a", lib, true)
416 
417 
418 /**
419  * Sets up the frameworks build phase.
420  *
421  * (each native target has different build rules)
422  */
setupFrameworksBuildPhase(const BuildSetup & setup)423 void XcodeProvider::setupFrameworksBuildPhase(const BuildSetup &setup) {
424 	_frameworksBuildPhase._comment = "PBXFrameworksBuildPhase";
425 
426 	// Just use a hardcoded id for the Frameworks-group
427 	Group *frameworksGroup = new Group(this, "Frameworks", "PBXGroup_CustomTemplate_Frameworks_", "");
428 
429 	Property children;
430 	children._hasOrder = true;
431 	children._flags = kSettingsAsList;
432 
433 	// Setup framework file properties
434 	std::map<std::string, FileProperty> properties;
435 	int fwOrder = 0;
436 	// Frameworks
437 	DEF_SYSFRAMEWORK("ApplicationServices");
438 	DEF_SYSFRAMEWORK("AudioToolbox");
439 	DEF_SYSFRAMEWORK("AudioUnit");
440 	DEF_SYSFRAMEWORK("Carbon");
441 	DEF_SYSFRAMEWORK("Cocoa");
442 	DEF_SYSFRAMEWORK("CoreAudio");
443 	DEF_SYSFRAMEWORK("CoreMIDI");
444 	DEF_SYSFRAMEWORK("CoreGraphics");
445 	DEF_SYSFRAMEWORK("CoreFoundation");
446 	DEF_SYSFRAMEWORK("CoreMIDI");
447 	DEF_SYSFRAMEWORK("Foundation");
448 	DEF_SYSFRAMEWORK("IOKit");
449 	DEF_SYSFRAMEWORK("OpenGLES");
450 	DEF_SYSFRAMEWORK("QuartzCore");
451 	DEF_SYSFRAMEWORK("UIKit");
452 	DEF_SYSTBD("libiconv");
453 
454 	// Local libraries
455 	DEF_LOCALLIB_STATIC("libFLAC");
456 	DEF_LOCALLIB_STATIC("libmad");
457 	DEF_LOCALLIB_STATIC("libvorbisidec");
458 	DEF_LOCALLIB_STATIC("libfreetype");
459 //	DEF_LOCALLIB_STATIC("libmpeg2");
460 
461 	std::string absoluteOutputDir;
462 #ifdef POSIX
463 	char *c_path = realpath(setup.outputDir.c_str(), NULL);
464 	absoluteOutputDir = c_path;
465 	absoluteOutputDir += "/lib";
466 	free(c_path);
467 #else
468 	absoluteOutputDir = "lib";
469 #endif
470 
471 	DEF_LOCALLIB_STATIC_PATH(absoluteOutputDir + "/libFLAC.a",       "libFLAC",       true);
472 	DEF_LOCALLIB_STATIC_PATH(absoluteOutputDir + "/libfreetype.a",   "libfreetype",   true);
473 	DEF_LOCALLIB_STATIC_PATH(absoluteOutputDir + "/libogg.a",        "libogg",        true);
474 	DEF_LOCALLIB_STATIC_PATH(absoluteOutputDir + "/libpng.a",        "libpng",        true);
475 	DEF_LOCALLIB_STATIC_PATH(absoluteOutputDir + "/libvorbis.a",     "libvorbis",     true);
476 	DEF_LOCALLIB_STATIC_PATH(absoluteOutputDir + "/libmad.a",        "libmad",        true);
477 	DEF_LOCALLIB_STATIC_PATH(absoluteOutputDir + "/libfluidsynth.a", "libfluidsynth", true);
478 	DEF_LOCALLIB_STATIC_PATH(absoluteOutputDir + "/libglib.a",       "libglib",       true);
479 	DEF_LOCALLIB_STATIC_PATH(absoluteOutputDir + "/libffi.a",        "libffi",        true);
480 
481 	frameworksGroup->_properties["children"] = children;
482 	_groups.add(frameworksGroup);
483 	// Force this to be added as a sub-group in the root.
484 	_rootSourceGroup->addChildGroup(frameworksGroup);
485 
486 
487 	// Declare this here, as it's used across all the targets
488 	int order = 0;
489 
490 	//////////////////////////////////////////////////////////////////////////
491 	// ScummVM-iOS
492 	Object *framework_iPhone = new Object(this, "PBXFrameworksBuildPhase_" + _targets[IOS_TARGET], "PBXFrameworksBuildPhase", "PBXFrameworksBuildPhase", "", "Frameworks");
493 
494 	framework_iPhone->addProperty("buildActionMask", "2147483647", "", kSettingsNoValue);
495 	framework_iPhone->addProperty("runOnlyForDeploymentPostprocessing", "0", "", kSettingsNoValue);
496 
497 	// List of frameworks
498 	Property iOS_files;
499 	iOS_files._hasOrder = true;
500 	iOS_files._flags = kSettingsAsList;
501 
502 	ValueList frameworks_iOS;
503 	frameworks_iOS.push_back("CoreAudio.framework");
504 	frameworks_iOS.push_back("CoreGraphics.framework");
505 	frameworks_iOS.push_back("CoreFoundation.framework");
506 	frameworks_iOS.push_back("Foundation.framework");
507 	frameworks_iOS.push_back("UIKit.framework");
508 	frameworks_iOS.push_back("AudioToolbox.framework");
509 	frameworks_iOS.push_back("QuartzCore.framework");
510 	frameworks_iOS.push_back("OpenGLES.framework");
511 
512 	if (CONTAINS_DEFINE(setup.defines, "USE_FLAC")) {
513 		frameworks_iOS.push_back("libFLAC.a");
514 	}
515 	if (CONTAINS_DEFINE(setup.defines, "USE_FREETYPE2")) {
516 		frameworks_iOS.push_back("libfreetype.a");
517 	}
518 	if (CONTAINS_DEFINE(setup.defines, "USE_PNG")) {
519 		frameworks_iOS.push_back("libpng.a");
520 	}
521 	if (CONTAINS_DEFINE(setup.defines, "USE_VORBIS")) {
522 		frameworks_iOS.push_back("libogg.a");
523 		frameworks_iOS.push_back("libvorbis.a");
524 	}
525 	if (CONTAINS_DEFINE(setup.defines, "USE_MAD")) {
526 		frameworks_iOS.push_back("libmad.a");
527 	}
528 	if (CONTAINS_DEFINE(setup.defines, "USE_FLUIDSYNTH")) {
529 		frameworks_iOS.push_back("libfluidsynth.a");
530 		frameworks_iOS.push_back("libglib.a");
531 		frameworks_iOS.push_back("libffi.a");
532 		frameworks_iOS.push_back("CoreMIDI.framework");
533 		frameworks_iOS.push_back("libiconv.tbd");
534 	}
535 
536 	for (ValueList::iterator framework = frameworks_iOS.begin(); framework != frameworks_iOS.end(); framework++) {
537 		std::string id = "Frameworks_" + *framework + "_iphone";
538 		std::string comment = *framework + " in Frameworks";
539 
540 		ADD_SETTING_ORDER_NOVALUE(iOS_files, getHash(id), comment, order++);
541 		ADD_BUILD_FILE(id, *framework, getHash(*framework), comment);
542 		ADD_FILE_REFERENCE(*framework, *framework, properties[*framework]);
543 	}
544 
545 	framework_iPhone->_properties["files"] = iOS_files;
546 
547 	_frameworksBuildPhase.add(framework_iPhone);
548 
549 	//////////////////////////////////////////////////////////////////////////
550 	// ScummVM-macOS
551 	Object *framework_OSX = new Object(this, "PBXFrameworksBuildPhase_" + _targets[OSX_TARGET], "PBXFrameworksBuildPhase", "PBXFrameworksBuildPhase", "", "Frameworks");
552 
553 	framework_OSX->addProperty("buildActionMask", "2147483647", "", kSettingsNoValue);
554 	framework_OSX->addProperty("runOnlyForDeploymentPostprocessing", "0", "", kSettingsNoValue);
555 
556 	// List of frameworks
557 	Property osx_files;
558 	osx_files._hasOrder = true;
559 	osx_files._flags = kSettingsAsList;
560 
561 	ValueList frameworks_osx;
562 	frameworks_osx.push_back("CoreFoundation.framework");
563 	frameworks_osx.push_back("Foundation.framework");
564 	frameworks_osx.push_back("AudioToolbox.framework");
565 	frameworks_osx.push_back("CoreMIDI.framework");
566 	frameworks_osx.push_back("CoreAudio.framework");
567 	frameworks_osx.push_back("QuartzCore.framework");
568 	frameworks_osx.push_back("Carbon.framework");
569 	frameworks_osx.push_back("ApplicationServices.framework");
570 	frameworks_osx.push_back("IOKit.framework");
571 	frameworks_osx.push_back("Cocoa.framework");
572 	frameworks_osx.push_back("AudioUnit.framework");
573 
574 	order = 0;
575 	for (ValueList::iterator framework = frameworks_osx.begin(); framework != frameworks_osx.end(); framework++) {
576 		std::string id = "Frameworks_" + *framework + "_osx";
577 		std::string comment = *framework + " in Frameworks";
578 
579 		ADD_SETTING_ORDER_NOVALUE(osx_files, getHash(id), comment, order++);
580 		ADD_BUILD_FILE(id, *framework, getHash(*framework), comment);
581 		ADD_FILE_REFERENCE(*framework, *framework, properties[*framework]);
582 	}
583 
584 	framework_OSX->_properties["files"] = osx_files;
585 
586 	_frameworksBuildPhase.add(framework_OSX);
587 }
588 
setupNativeTarget()589 void XcodeProvider::setupNativeTarget() {
590 	_nativeTarget._comment = "PBXNativeTarget";
591 
592 	// Just use a hardcoded id for the Products-group
593 	Group *productsGroup = new Group(this, "Products", "PBXGroup_CustomTemplate_Products_" , "");
594 	// Output native target section
595 	for (unsigned int i = 0; i < _targets.size(); i++) {
596 		Object *target = new Object(this, "PBXNativeTarget_" + _targets[i], "PBXNativeTarget", "PBXNativeTarget", "", _targets[i]);
597 
598 		target->addProperty("buildConfigurationList", getHash("XCConfigurationList_" + _targets[i]), "Build configuration list for PBXNativeTarget \"" + _targets[i] + "\"", kSettingsNoValue);
599 
600 		Property buildPhases;
601 		buildPhases._hasOrder = true;
602 		buildPhases._flags = kSettingsAsList;
603 		buildPhases._settings[getHash("PBXResourcesBuildPhase_" + _targets[i])] = Setting("", "Resources", kSettingsNoValue, 0, 0);
604 		buildPhases._settings[getHash("PBXSourcesBuildPhase_" + _targets[i])] = Setting("", "Sources", kSettingsNoValue, 0, 1);
605 		buildPhases._settings[getHash("PBXFrameworksBuildPhase_" + _targets[i])] = Setting("", "Frameworks", kSettingsNoValue, 0, 2);
606 		target->_properties["buildPhases"] = buildPhases;
607 
608 		target->addProperty("buildRules", "", "", kSettingsNoValue | kSettingsAsList);
609 
610 		target->addProperty("dependencies", "", "", kSettingsNoValue | kSettingsAsList);
611 
612 		target->addProperty("name", _targets[i], "", kSettingsNoValue | kSettingsQuoteVariable);
613 		target->addProperty("productName", PROJECT_NAME, "", kSettingsNoValue);
614 		addProductFileReference("PBXFileReference_" PROJECT_DESCRIPTION ".app_" + _targets[i], PROJECT_DESCRIPTION ".app");
615 		productsGroup->addChildByHash(getHash("PBXFileReference_" PROJECT_DESCRIPTION ".app_" + _targets[i]), PROJECT_DESCRIPTION ".app");
616 		target->addProperty("productReference", getHash("PBXFileReference_" PROJECT_DESCRIPTION ".app_" + _targets[i]), PROJECT_DESCRIPTION ".app", kSettingsNoValue);
617 		target->addProperty("productType", "com.apple.product-type.application", "", kSettingsNoValue | kSettingsQuoteVariable);
618 
619 		_nativeTarget.add(target);
620 	}
621 	_rootSourceGroup->addChildGroup(productsGroup);
622 	_groups.add(productsGroup);
623 }
624 
setupProject()625 void XcodeProvider::setupProject() {
626 	_project._comment = "PBXProject";
627 
628 	Object *project = new Object(this, "PBXProject", "PBXProject", "PBXProject", "", "Project object");
629 
630 	project->addProperty("buildConfigurationList", getHash("XCConfigurationList_scummvm"), "Build configuration list for PBXProject \"" PROJECT_NAME "\"", kSettingsNoValue);
631 	project->addProperty("compatibilityVersion", "Xcode 3.2", "", kSettingsNoValue | kSettingsQuoteVariable);
632 	project->addProperty("developmentRegion", "English", "", kSettingsNoValue);
633 	project->addProperty("hasScannedForEncodings", "1", "", kSettingsNoValue);
634 
635 	// List of known regions
636 	Property regions;
637 	regions._flags = kSettingsAsList;
638 	ADD_SETTING_ORDER_NOVALUE(regions, "English", "", 0);
639 	ADD_SETTING_ORDER_NOVALUE(regions, "Japanese", "", 1);
640 	ADD_SETTING_ORDER_NOVALUE(regions, "French", "", 2);
641 	ADD_SETTING_ORDER_NOVALUE(regions, "German", "", 3);
642 	project->_properties["knownRegions"] = regions;
643 
644 	project->addProperty("mainGroup", _rootSourceGroup->getHashRef(), "CustomTemplate", kSettingsNoValue);
645 	project->addProperty("productRefGroup", getHash("PBXGroup_CustomTemplate_Products_"), "" , kSettingsNoValue);
646 	project->addProperty("projectDirPath", _projectRoot, "", kSettingsNoValue | kSettingsQuoteVariable);
647 	project->addProperty("projectRoot", "", "", kSettingsNoValue | kSettingsQuoteVariable);
648 
649 	// List of targets
650 	Property targets;
651 	targets._flags = kSettingsAsList;
652 	targets._settings[getHash("PBXNativeTarget_" + _targets[IOS_TARGET])] = Setting("", _targets[IOS_TARGET], kSettingsNoValue, 0, 0);
653 	targets._settings[getHash("PBXNativeTarget_" + _targets[OSX_TARGET])] = Setting("", _targets[OSX_TARGET], kSettingsNoValue, 0, 1);
654 	project->_properties["targets"] = targets;
655 
656 	// Force list even when there is only a single target
657 	project->_properties["targets"]._flags |= kSettingsSingleItem;
658 
659 	_project.add(project);
660 }
661 
getResourceFiles() const662 XcodeProvider::ValueList& XcodeProvider::getResourceFiles() const {
663 	static ValueList files;
664 	if (files.empty()) {
665 		files.push_back("gui/themes/scummclassic.zip");
666 		files.push_back("gui/themes/scummmodern.zip");
667 		files.push_back("gui/themes/translations.dat");
668 		files.push_back("dists/engine-data/access.dat");
669 		files.push_back("dists/engine-data/cryo.dat");
670 		files.push_back("dists/engine-data/drascula.dat");
671 		files.push_back("dists/engine-data/hugo.dat");
672 		files.push_back("dists/engine-data/kyra.dat");
673 		files.push_back("dists/engine-data/lure.dat");
674 		files.push_back("dists/engine-data/mort.dat");
675 		files.push_back("dists/engine-data/neverhood.dat");
676 		files.push_back("dists/engine-data/queen.tbl");
677 		files.push_back("dists/engine-data/sky.cpt");
678 		files.push_back("dists/engine-data/teenagent.dat");
679 		files.push_back("dists/engine-data/titanic.dat");
680 		files.push_back("dists/engine-data/tony.dat");
681 		files.push_back("dists/engine-data/toon.dat");
682 		files.push_back("dists/engine-data/wintermute.zip");
683 		files.push_back("dists/engine-data/macventure.dat");
684 		files.push_back("dists/pred.dic");
685 		files.push_back("icons/scummvm.icns");
686 	}
687 	return files;
688 }
689 
setupResourcesBuildPhase()690 void XcodeProvider::setupResourcesBuildPhase() {
691 	_resourcesBuildPhase._comment = "PBXResourcesBuildPhase";
692 
693 	ValueList &files_list = getResourceFiles();
694 
695 	// Same as for containers: a rule for each native target
696 	for (unsigned int i = 0; i < _targets.size(); i++) {
697 		Object *resource = new Object(this, "PBXResourcesBuildPhase_" + _targets[i], "PBXResourcesBuildPhase", "PBXResourcesBuildPhase", "", "Resources");
698 
699 		resource->addProperty("buildActionMask", "2147483647", "", kSettingsNoValue);
700 
701 		// Add default files
702 		Property files;
703 		files._hasOrder = true;
704 		files._flags = kSettingsAsList;
705 
706 		int order = 0;
707 		for (ValueList::iterator file = files_list.begin(); file != files_list.end(); file++) {
708 			if (shouldSkipFileForTarget(*file, _targets[i], *file)) {
709 				continue;
710 			}
711 			std::string resourceAbsolutePath = _projectRoot + "/" + *file;
712 			std::string file_id = "FileReference_" + resourceAbsolutePath;
713 			std::string base = basename(*file);
714 			std::string comment = base + " in Resources";
715 			addBuildFile(resourceAbsolutePath, base, getHash(file_id), comment);
716 			ADD_SETTING_ORDER_NOVALUE(files, getHash(resourceAbsolutePath), comment, order++);
717 		}
718 
719 		resource->_properties["files"] = files;
720 
721 		resource->addProperty("runOnlyForDeploymentPostprocessing", "0", "", kSettingsNoValue);
722 
723 		_resourcesBuildPhase.add(resource);
724 	}
725 }
726 
setupSourcesBuildPhase()727 void XcodeProvider::setupSourcesBuildPhase() {
728 	_sourcesBuildPhase._comment = "PBXSourcesBuildPhase";
729 
730 	// Same as for containers: a rule for each native target
731 	for (unsigned int i = 0; i < _targets.size(); i++) {
732 		const std::string &targetName = _targets[i];
733 		Object *source = new Object(this, "PBXSourcesBuildPhase_" + _targets[i], "PBXSourcesBuildPhase", "PBXSourcesBuildPhase", "", "Sources");
734 
735 		source->addProperty("buildActionMask", "2147483647", "", kSettingsNoValue);
736 
737 		Property files;
738 		files._hasOrder = true;
739 		files._flags = kSettingsAsList;
740 
741 		int order = 0;
742 		for (std::vector<Object *>::iterator file = _buildFile._objects.begin(); file != _buildFile._objects.end(); ++file) {
743 			const std::string &fileName = (*file)->_name;
744 			if (shouldSkipFileForTarget((*file)->_id, targetName, fileName)) {
745 				continue;
746 			}
747 			if (!producesObjectFileOnOSX(fileName)) {
748 				continue;
749 			}
750 			std::string comment = fileName + " in Sources";
751 			ADD_SETTING_ORDER_NOVALUE(files, getHash((*file)->_id), comment, order++);
752 		}
753 
754 		setupAdditionalSources(targetName, files, order);
755 
756 		source->_properties["files"] = files;
757 
758 		source->addProperty("runOnlyForDeploymentPostprocessing", "0", "", kSettingsNoValue);
759 
760 		_sourcesBuildPhase.add(source);
761 	}
762 }
763 
764 // Setup all build configurations
setupBuildConfiguration(const BuildSetup & setup)765 void XcodeProvider::setupBuildConfiguration(const BuildSetup &setup) {
766 
767 	_buildConfiguration._comment = "XCBuildConfiguration";
768 	_buildConfiguration._flags = kSettingsAsList;
769 
770 	std::string projectOutputDirectory;
771 #ifdef POSIX
772 	char *rp = realpath(setup.outputDir.c_str(), NULL);
773 	projectOutputDirectory = rp;
774 	free(rp);
775 #endif
776 
777 	/****************************************
778 	 * ScummVM - Project Level
779 	 ****************************************/
780 
781 	// Debug
782 	Object *scummvm_Debug_Object = new Object(this, "XCBuildConfiguration_" PROJECT_NAME "_Debug", PROJECT_NAME, "XCBuildConfiguration", "PBXProject", "Debug");
783 	Property scummvm_Debug;
784 	ADD_SETTING(scummvm_Debug, "ALWAYS_SEARCH_USER_PATHS", "NO");
785 	ADD_SETTING_QUOTE(scummvm_Debug, "USER_HEADER_SEARCH_PATHS", "$(SRCROOT) $(SRCROOT)/engines");
786 	ADD_SETTING_QUOTE(scummvm_Debug, "CODE_SIGN_IDENTITY", "");
787 	ADD_SETTING_QUOTE_VAR(scummvm_Debug, "CODE_SIGN_IDENTITY[sdk=iphoneos*]", "");
788 	ADD_SETTING_QUOTE(scummvm_Debug, "FRAMEWORK_SEARCH_PATHS", "");
789 	ADD_SETTING(scummvm_Debug, "GCC_C_LANGUAGE_STANDARD", "c99");
790 	ADD_SETTING(scummvm_Debug, "GCC_ENABLE_CPP_EXCEPTIONS", "NO");
791 	ADD_SETTING(scummvm_Debug, "GCC_ENABLE_CPP_RTTI", "YES");
792 	ADD_SETTING(scummvm_Debug, "GCC_INPUT_FILETYPE", "automatic");
793 	ADD_SETTING(scummvm_Debug, "GCC_OPTIMIZATION_LEVEL", "0");
794 	ADD_SETTING(scummvm_Debug, "GCC_WARN_SIGN_COMPARE", "YES");
795 	ADD_SETTING(scummvm_Debug, "WARNING_CFLAGS", "-Wno-multichar");
796 	ValueList scummvm_defines(_defines);
797 	REMOVE_DEFINE(scummvm_defines, "MACOSX");
798 	REMOVE_DEFINE(scummvm_defines, "IPHONE");
799 	REMOVE_DEFINE(scummvm_defines, "IPHONE_IOS7");
800 	REMOVE_DEFINE(scummvm_defines, "IPHONE_SANDBOXED");
801 	REMOVE_DEFINE(scummvm_defines, "SDL_BACKEND");
802 	ADD_SETTING_LIST(scummvm_Debug, "GCC_PREPROCESSOR_DEFINITIONS", scummvm_defines, kSettingsNoQuote | kSettingsAsList, 5);
803 	ADD_SETTING(scummvm_Debug, "GCC_WARN_ABOUT_RETURN_TYPE", "YES");
804 	ADD_SETTING(scummvm_Debug, "GCC_WARN_UNUSED_VARIABLE", "YES");
805 	ValueList scummvm_HeaderPaths;
806 	scummvm_HeaderPaths.push_back("include/");
807 	scummvm_HeaderPaths.push_back("$(SRCROOT)/engines/");
808 	scummvm_HeaderPaths.push_back("$(SRCROOT)");
809 	ADD_SETTING_LIST(scummvm_Debug, "HEADER_SEARCH_PATHS", scummvm_HeaderPaths, kSettingsQuoteVariable | kSettingsAsList, 5);
810 	ADD_SETTING_QUOTE(scummvm_Debug, "LIBRARY_SEARCH_PATHS", "");
811 	ADD_SETTING(scummvm_Debug, "ONLY_ACTIVE_ARCH", "YES");
812 	ADD_SETTING_QUOTE(scummvm_Debug, "OTHER_CFLAGS", "");
813 	ADD_SETTING_QUOTE(scummvm_Debug, "OTHER_LDFLAGS", "-lz");
814 	ADD_SETTING(scummvm_Debug, "ENABLE_TESTABILITY", "YES");
815 
816 	scummvm_Debug_Object->addProperty("name", "Debug", "", kSettingsNoValue);
817 	scummvm_Debug_Object->_properties["buildSettings"] = scummvm_Debug;
818 
819 	// Release
820 	Object *scummvm_Release_Object = new Object(this, "XCBuildConfiguration_" PROJECT_NAME "_Release", PROJECT_NAME, "XCBuildConfiguration", "PBXProject", "Release");
821 	Property scummvm_Release(scummvm_Debug);
822 	REMOVE_SETTING(scummvm_Release, "GCC_C_LANGUAGE_STANDARD");       // Not sure why we remove that, or any of the other warnings
823 	REMOVE_SETTING(scummvm_Release, "GCC_WARN_ABOUT_RETURN_TYPE");
824 	REMOVE_SETTING(scummvm_Release, "GCC_WARN_UNUSED_VARIABLE");
825 	REMOVE_SETTING(scummvm_Release, "ONLY_ACTIVE_ARCH");
826 	REMOVE_SETTING(scummvm_Release, "ENABLE_TESTABILITY");
827 
828 	scummvm_Release_Object->addProperty("name", "Release", "", kSettingsNoValue);
829 	scummvm_Release_Object->_properties["buildSettings"] = scummvm_Release;
830 
831 	_buildConfiguration.add(scummvm_Debug_Object);
832 	_buildConfiguration.add(scummvm_Release_Object);
833 
834 	///****************************************
835 	// * ScummVM - iOS Target
836 	// ****************************************/
837 
838 	// Debug
839 	Object *iPhone_Debug_Object = new Object(this, "XCBuildConfiguration_" PROJECT_DESCRIPTION "-iPhone_Debug", _targets[IOS_TARGET] /* ScummVM-iPhone */, "XCBuildConfiguration", "PBXNativeTarget", "Debug");
840 	Property iPhone_Debug;
841 	ADD_SETTING_QUOTE(iPhone_Debug, "CODE_SIGN_IDENTITY", "iPhone Developer");
842 	ADD_SETTING_QUOTE_VAR(iPhone_Debug, "CODE_SIGN_IDENTITY[sdk=iphoneos*]", "iPhone Developer");
843 	ADD_SETTING(iPhone_Debug, "COPY_PHASE_STRIP", "NO");
844 	ADD_SETTING_QUOTE(iPhone_Debug, "DEBUG_INFORMATION_FORMAT", "dwarf");
845 	ValueList iPhone_FrameworkSearchPaths;
846 	iPhone_FrameworkSearchPaths.push_back("$(inherited)");
847 	iPhone_FrameworkSearchPaths.push_back("\"$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks\"");
848 	ADD_SETTING_LIST(iPhone_Debug, "FRAMEWORK_SEARCH_PATHS", iPhone_FrameworkSearchPaths, kSettingsAsList, 5);
849 	ADD_SETTING(iPhone_Debug, "GCC_DYNAMIC_NO_PIC", "NO");
850 	ADD_SETTING(iPhone_Debug, "GCC_ENABLE_CPP_EXCEPTIONS", "NO");
851 	ADD_SETTING(iPhone_Debug, "GCC_OPTIMIZATION_LEVEL", "0");
852 	ADD_SETTING(iPhone_Debug, "GCC_PRECOMPILE_PREFIX_HEADER", "NO");
853 	ADD_SETTING(iPhone_Debug, "GCC_WARN_64_TO_32_BIT_CONVERSION", "NO");
854 	ADD_SETTING_QUOTE(iPhone_Debug, "GCC_PREFIX_HEADER", "");
855 	ADD_SETTING(iPhone_Debug, "GCC_UNROLL_LOOPS", "YES");
856 	ValueList iPhone_HeaderSearchPaths;
857 	iPhone_HeaderSearchPaths.push_back("$(SRCROOT)/engines/");
858 	iPhone_HeaderSearchPaths.push_back("$(SRCROOT)");
859 	iPhone_HeaderSearchPaths.push_back("\"" + projectOutputDirectory + "\"");
860 	iPhone_HeaderSearchPaths.push_back("\"" + projectOutputDirectory + "/include\"");
861 	ADD_SETTING_LIST(iPhone_Debug, "HEADER_SEARCH_PATHS", iPhone_HeaderSearchPaths, kSettingsAsList | kSettingsQuoteVariable, 5);
862 	ADD_SETTING_QUOTE(iPhone_Debug, "INFOPLIST_FILE", "$(SRCROOT)/dists/ios7/Info.plist");
863 	ValueList iPhone_LibPaths;
864 	iPhone_LibPaths.push_back("$(inherited)");
865 	iPhone_LibPaths.push_back("\"" + projectOutputDirectory + "/lib\"");
866 	ADD_SETTING_LIST(iPhone_Debug, "LIBRARY_SEARCH_PATHS", iPhone_LibPaths, kSettingsAsList, 5);
867 	ADD_SETTING(iPhone_Debug, "ONLY_ACTIVE_ARCH", "YES");
868 	ADD_SETTING(iPhone_Debug, "PRODUCT_NAME", PROJECT_NAME);
869 	ADD_SETTING(iPhone_Debug, "PRODUCT_BUNDLE_IDENTIFIER", "\"org.scummvm.${PRODUCT_NAME}\"");
870 	ADD_SETTING(iPhone_Debug, "IPHONEOS_DEPLOYMENT_TARGET", "7.1");
871 	//ADD_SETTING_QUOTE(iPhone_Debug, "PROVISIONING_PROFILE", "EF590570-5FAC-4346-9071-D609DE2B28D8");
872 	ADD_SETTING_QUOTE_VAR(iPhone_Debug, "PROVISIONING_PROFILE[sdk=iphoneos*]", "");
873 	ADD_SETTING(iPhone_Debug, "SDKROOT", "iphoneos");
874 	ADD_SETTING_QUOTE(iPhone_Debug, "TARGETED_DEVICE_FAMILY", "1,2");
875 	ValueList scummvmIOS_defines;
876 	ADD_DEFINE(scummvmIOS_defines, "\"$(inherited)\"");
877 	ADD_DEFINE(scummvmIOS_defines, "IPHONE");
878 	ADD_DEFINE(scummvmIOS_defines, "IPHONE_IOS7");
879 	ADD_DEFINE(scummvmIOS_defines, "IPHONE_SANDBOXED");
880 	ADD_SETTING_LIST(iPhone_Debug, "GCC_PREPROCESSOR_DEFINITIONS", scummvmIOS_defines, kSettingsNoQuote | kSettingsAsList, 5);
881 	ADD_SETTING(iPhone_Debug, "ASSETCATALOG_COMPILER_APPICON_NAME", "AppIcon");
882 	ADD_SETTING(iPhone_Debug, "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME", "LaunchImage");
883 
884 	iPhone_Debug_Object->addProperty("name", "Debug", "", kSettingsNoValue);
885 	iPhone_Debug_Object->_properties["buildSettings"] = iPhone_Debug;
886 
887 	// Release
888 	Object *iPhone_Release_Object = new Object(this, "XCBuildConfiguration_" PROJECT_DESCRIPTION "-iPhone_Release", _targets[IOS_TARGET] /* ScummVM-iPhone */, "XCBuildConfiguration", "PBXNativeTarget", "Release");
889 	Property iPhone_Release(iPhone_Debug);
890 	ADD_SETTING(iPhone_Release, "GCC_OPTIMIZATION_LEVEL", "3");
891 	ADD_SETTING(iPhone_Release, "COPY_PHASE_STRIP", "YES");
892 	REMOVE_SETTING(iPhone_Release, "GCC_DYNAMIC_NO_PIC");
893 	ADD_SETTING(iPhone_Release, "WRAPPER_EXTENSION", "app");
894 	REMOVE_SETTING(iPhone_Release, "DEBUG_INFORMATION_FORMAT");
895 	ADD_SETTING_QUOTE(iPhone_Release, "DEBUG_INFORMATION_FORMAT", "dwarf-with-dsym");
896 
897 	iPhone_Release_Object->addProperty("name", "Release", "", kSettingsNoValue);
898 	iPhone_Release_Object->_properties["buildSettings"] = iPhone_Release;
899 
900 	_buildConfiguration.add(iPhone_Debug_Object);
901 	_buildConfiguration.add(iPhone_Release_Object);
902 
903 	/****************************************
904 	 * ScummVM - macOS Target
905 	 ****************************************/
906 
907 	// Debug
908 	Object *scummvmOSX_Debug_Object = new Object(this, "XCBuildConfiguration_" PROJECT_DESCRIPTION "-OSX_Debug", _targets[OSX_TARGET] /* ScummVM-macOS */, "XCBuildConfiguration", "PBXNativeTarget", "Debug");
909 	Property scummvmOSX_Debug;
910 	ADD_SETTING(scummvmOSX_Debug, "COMBINE_HIDPI_IMAGES", "YES");
911 	ADD_SETTING(scummvmOSX_Debug, "SDKROOT", "macosx");
912 	ADD_SETTING(scummvmOSX_Debug, "COPY_PHASE_STRIP", "NO");
913 	ADD_SETTING_QUOTE(scummvmOSX_Debug, "DEBUG_INFORMATION_FORMAT", "dwarf");
914 	ADD_SETTING_QUOTE(scummvmOSX_Debug, "FRAMEWORK_SEARCH_PATHS", "");
915 	ADD_SETTING(scummvmOSX_Debug, "GCC_C_LANGUAGE_STANDARD", "c99");
916 	ADD_SETTING(scummvmOSX_Debug, "GCC_ENABLE_CPP_EXCEPTIONS", "NO");
917 	ADD_SETTING(scummvmOSX_Debug, "GCC_ENABLE_CPP_RTTI", "YES");
918 	ADD_SETTING(scummvmOSX_Debug, "GCC_DYNAMIC_NO_PIC", "NO");
919 	ADD_SETTING(scummvmOSX_Debug, "GCC_OPTIMIZATION_LEVEL", "0");
920 	ADD_SETTING(scummvmOSX_Debug, "GCC_PRECOMPILE_PREFIX_HEADER", "NO");
921 	ADD_SETTING_QUOTE(scummvmOSX_Debug, "GCC_PREFIX_HEADER", "");
922 	ValueList scummvmOSX_defines;
923 	ADD_DEFINE(scummvmOSX_defines, "\"$(inherited)\"");
924 	ADD_DEFINE(scummvmOSX_defines, "SDL_BACKEND");
925 	ADD_DEFINE(scummvmOSX_defines, "MACOSX");
926 	ADD_SETTING_LIST(scummvmOSX_Debug, "GCC_PREPROCESSOR_DEFINITIONS", scummvmOSX_defines, kSettingsNoQuote | kSettingsAsList, 5);
927 	ADD_SETTING_QUOTE(scummvmOSX_Debug, "GCC_VERSION", "");
928 	ValueList scummvmOSX_HeaderPaths;
929 	if (setup.useSDL2) {
930 		scummvmOSX_HeaderPaths.push_back("/usr/local/include/SDL2");
931 	} else {
932 		scummvmOSX_HeaderPaths.push_back("/usr/local/include/SDL");
933 	}
934 	scummvmOSX_HeaderPaths.push_back("/usr/local/include");
935 	scummvmOSX_HeaderPaths.push_back("/usr/local/include/freetype2");
936 	scummvmOSX_HeaderPaths.push_back("include/");
937 	scummvmOSX_HeaderPaths.push_back("$(SRCROOT)/engines/");
938 	scummvmOSX_HeaderPaths.push_back("$(SRCROOT)");
939 	ADD_SETTING_LIST(scummvmOSX_Debug, "HEADER_SEARCH_PATHS", scummvmOSX_HeaderPaths, kSettingsQuoteVariable | kSettingsAsList, 5);
940 	ADD_SETTING_QUOTE(scummvmOSX_Debug, "INFOPLIST_FILE", "$(SRCROOT)/dists/macosx/Info.plist");
941 	ValueList scummvmOSX_LibPaths;
942 	scummvmOSX_LibPaths.push_back("/usr/local/lib");
943 	scummvmOSX_LibPaths.push_back("\"$(inherited)\"");
944 	scummvmOSX_LibPaths.push_back("\"\\\"$(SRCROOT)/lib\\\"\"");
945 	ADD_SETTING_LIST(scummvmOSX_Debug, "LIBRARY_SEARCH_PATHS", scummvmOSX_LibPaths, kSettingsNoQuote | kSettingsAsList, 5);
946 	ADD_SETTING_QUOTE(scummvmOSX_Debug, "OTHER_CFLAGS", "");
947 	ValueList scummvmOSX_LdFlags;
948 	scummvmOSX_LdFlags.push_back("-logg");
949 	scummvmOSX_LdFlags.push_back("-lpng");
950 	scummvmOSX_LdFlags.push_back("-ljpeg");
951 	scummvmOSX_LdFlags.push_back("-ltheora");
952 	scummvmOSX_LdFlags.push_back("-lfreetype");
953 	scummvmOSX_LdFlags.push_back("-lvorbisfile");
954 	scummvmOSX_LdFlags.push_back("-lvorbis");
955 	scummvmOSX_LdFlags.push_back("-lmad");
956 	scummvmOSX_LdFlags.push_back("-lFLAC");
957 	scummvmOSX_LdFlags.push_back("-lcurl");
958 	if (setup.useSDL2) {
959 		scummvmOSX_LdFlags.push_back("-lSDL2main");
960 		scummvmOSX_LdFlags.push_back("-lSDL2");
961 	} else {
962 		scummvmOSX_LdFlags.push_back("-lSDLmain");
963 		scummvmOSX_LdFlags.push_back("-lSDL");
964 	}
965 	scummvmOSX_LdFlags.push_back("-lz");
966 	ADD_SETTING_LIST(scummvmOSX_Debug, "OTHER_LDFLAGS", scummvmOSX_LdFlags, kSettingsAsList, 5);
967 	ADD_SETTING(scummvmOSX_Debug, "PRODUCT_NAME", PROJECT_NAME);
968 
969 	scummvmOSX_Debug_Object->addProperty("name", "Debug", "", kSettingsNoValue);
970 	scummvmOSX_Debug_Object->_properties["buildSettings"] = scummvmOSX_Debug;
971 
972 	// Release
973 	Object *scummvmOSX_Release_Object = new Object(this, "XCBuildConfiguration_" PROJECT_DESCRIPTION "-OSX_Release", _targets[OSX_TARGET] /* ScummVM-macOS */, "XCBuildConfiguration", "PBXNativeTarget", "Release");
974 	Property scummvmOSX_Release(scummvmOSX_Debug);
975 	ADD_SETTING(scummvmOSX_Release, "COPY_PHASE_STRIP", "YES");
976 	REMOVE_SETTING(scummvmOSX_Release, "GCC_DYNAMIC_NO_PIC");
977 	REMOVE_SETTING(scummvmOSX_Release, "GCC_OPTIMIZATION_LEVEL");
978 	ADD_SETTING(scummvmOSX_Release, "WRAPPER_EXTENSION", "app");
979 	REMOVE_SETTING(scummvmOSX_Release, "DEBUG_INFORMATION_FORMAT");
980 	ADD_SETTING_QUOTE(scummvmOSX_Release, "DEBUG_INFORMATION_FORMAT", "dwarf-with-dsym");
981 
982 	scummvmOSX_Release_Object->addProperty("name", "Release", "", kSettingsNoValue);
983 	scummvmOSX_Release_Object->_properties["buildSettings"] = scummvmOSX_Release;
984 
985 	_buildConfiguration.add(scummvmOSX_Debug_Object);
986 	_buildConfiguration.add(scummvmOSX_Release_Object);
987 
988 	// Warning: This assumes we have all configurations with a Debug & Release pair
989 	for (std::vector<Object *>::iterator config = _buildConfiguration._objects.begin(); config != _buildConfiguration._objects.end(); config++) {
990 
991 		Object *configList = new Object(this, "XCConfigurationList_" + (*config)->_name, (*config)->_name, "XCConfigurationList", "", "Build configuration list for " + (*config)->_refType + " \"" + (*config)->_name + "\"");
992 
993 		Property buildConfigs;
994 		buildConfigs._flags = kSettingsAsList;
995 
996 		buildConfigs._settings[getHash((*config)->_id)] = Setting("", "Debug", kSettingsNoValue, 0, 0);
997 		buildConfigs._settings[getHash((*(++config))->_id)] = Setting("", "Release", kSettingsNoValue, 0, 1);
998 
999 		configList->_properties["buildConfigurations"] = buildConfigs;
1000 
1001 		configList->addProperty("defaultConfigurationIsVisible", "0", "", kSettingsNoValue);
1002 		configList->addProperty("defaultConfigurationName", "Release", "", kSettingsNoValue);
1003 
1004 		_configurationList.add(configList);
1005 	}
1006 }
1007 
setupImageAssetCatalog(const BuildSetup & setup)1008 void XcodeProvider::setupImageAssetCatalog(const BuildSetup &setup) {
1009 	const std::string filename = "Images.xcassets";
1010 	const std::string absoluteCatalogPath = _projectRoot + "/dists/ios7/" + filename;
1011 	const std::string id = "FileReference_" + absoluteCatalogPath;
1012 	Group *group = touchGroupsForPath(absoluteCatalogPath);
1013 	group->addChildFile(filename);
1014 	addBuildFile(absoluteCatalogPath, filename, getHash(id), "Image Asset Catalog");
1015 }
1016 
setupAdditionalSources(std::string targetName,Property & files,int & order)1017 void XcodeProvider::setupAdditionalSources(std::string targetName, Property &files, int &order) {
1018 	if (targetIsIOS(targetName)) {
1019 		const std::string absoluteCatalogPath = _projectRoot + "/dists/ios7/Images.xcassets";
1020 		ADD_SETTING_ORDER_NOVALUE(files, getHash(absoluteCatalogPath), "Image Asset Catalog", order++);
1021 	}
1022 }
1023 
1024 //////////////////////////////////////////////////////////////////////////
1025 // Misc
1026 //////////////////////////////////////////////////////////////////////////
1027 
1028 // Setup global defines
setupDefines(const BuildSetup & setup)1029 void XcodeProvider::setupDefines(const BuildSetup &setup) {
1030 
1031 	for (StringList::const_iterator i = setup.defines.begin(); i != setup.defines.end(); ++i) {
1032 		if (*i == "USE_NASM" || *i == "USE_FLUIDSYNTH")  // Not supported on Mac
1033 			continue;
1034 
1035 		ADD_DEFINE(_defines, *i);
1036 	}
1037 	// Add special defines for Mac support
1038 	REMOVE_DEFINE(_defines, "MACOSX");
1039 	REMOVE_DEFINE(_defines, "IPHONE");
1040 	REMOVE_DEFINE(_defines, "IPHONE_IOS7");
1041 	REMOVE_DEFINE(_defines, "IPHONE_SANDBOXED");
1042 	REMOVE_DEFINE(_defines, "SDL_BACKEND");
1043 	ADD_DEFINE(_defines, "USE_TEXT_CONSOLE_FOR_DEBUGGER");
1044 	ADD_DEFINE(_defines, "CONFIG_H");
1045 	ADD_DEFINE(_defines, "UNIX");
1046 	ADD_DEFINE(_defines, "SCUMMVM");
1047 }
1048 
1049 //////////////////////////////////////////////////////////////////////////
1050 // Object hash
1051 //////////////////////////////////////////////////////////////////////////
1052 
getHash(std::string key)1053 std::string XcodeProvider::getHash(std::string key) {
1054 
1055 #if DEBUG_XCODE_HASH
1056 	return key;
1057 #else
1058 	// Check to see if the key is already in the dictionary
1059 	std::map<std::string, std::string>::iterator hashIterator = _hashDictionnary.find(key);
1060 	if (hashIterator != _hashDictionnary.end())
1061 		return hashIterator->second;
1062 
1063 	// Generate a new key from the file hash and insert it into the dictionary
1064 #ifdef MACOSX
1065 	std::string hash = md5(key);
1066 #else
1067 	std::string hash = newHash();
1068 #endif
1069 
1070 	_hashDictionnary[key] = hash;
1071 
1072 	return hash;
1073 #endif
1074 }
1075 
isSeparator(char s)1076 bool isSeparator(char s) { return (s == '-'); }
1077 
1078 #ifdef MACOSX
md5(std::string key)1079 std::string XcodeProvider::md5(std::string key) {
1080 	unsigned char md[CC_MD5_DIGEST_LENGTH];
1081 	CC_MD5(key.c_str(), (CC_LONG) key.length(), md);
1082 	std::stringstream stream;
1083 	stream << std::hex << std::setfill('0') << std::setw(2);
1084 	for (int i=0; i<CC_MD5_DIGEST_LENGTH; i++) {
1085 		stream << (unsigned int) md[i];
1086 	}
1087 	return stream.str();
1088 }
1089 #endif
1090 
newHash() const1091 std::string XcodeProvider::newHash() const {
1092 	std::string hash = createUUID();
1093 
1094 	// Remove { and - from UUID and resize to 96-bits uppercase hex string
1095 	hash.erase(remove_if(hash.begin(), hash.end(), isSeparator), hash.end());
1096 
1097 	hash.resize(24);
1098 	std::transform(hash.begin(), hash.end(), hash.begin(), toupper);
1099 
1100 	return hash;
1101 }
1102 
1103 //////////////////////////////////////////////////////////////////////////
1104 // Output
1105 //////////////////////////////////////////////////////////////////////////
1106 
replace(std::string input,const std::string find,std::string replaceStr)1107 std::string replace(std::string input, const std::string find, std::string replaceStr) {
1108 	std::string::size_type pos = 0;
1109 	std::string::size_type findLen = find.length();
1110 	std::string::size_type replaceLen = replaceStr.length();
1111 
1112 	if (findLen == 0)
1113 		return input;
1114 
1115 	for (; (pos = input.find(find, pos)) != std::string::npos;) {
1116 		input.replace(pos, findLen, replaceStr);
1117 		pos += replaceLen;
1118 	}
1119 
1120 	return input;
1121 }
1122 
writeProperty(const std::string & variable,Property & prop,int flags) const1123 std::string XcodeProvider::writeProperty(const std::string &variable, Property &prop, int flags) const {
1124 	std::string output;
1125 
1126 	output += (flags & kSettingsSingleItem ? "" : "\t\t\t") + variable + " = ";
1127 
1128 	if (prop._settings.size() > 1 || (prop._flags & kSettingsSingleItem))
1129 		output += (prop._flags & kSettingsAsList) ? "(\n" : "{\n";
1130 
1131 	OrderedSettingList settings = prop.getOrderedSettingList();
1132 	for (OrderedSettingList::const_iterator setting = settings.begin(); setting != settings.end(); ++setting) {
1133 		if (settings.size() > 1 || (prop._flags & kSettingsSingleItem))
1134 			output += (flags & kSettingsSingleItem ? " " : "\t\t\t\t");
1135 
1136 		output += writeSetting(setting->first, setting->second);
1137 
1138 		// The combination of kSettingsAsList, and kSettingsSingleItem should use "," and not ";" (i.e children
1139 		// in PBXGroup, so we special case that case here.
1140 		if ((prop._flags & kSettingsAsList) && (prop._settings.size() > 1 || (prop._flags & kSettingsSingleItem))) {
1141 			output += (prop._settings.size() > 0) ? ",\n" : "\n";
1142 		} else {
1143 			output += ";";
1144 			output += (flags & kSettingsSingleItem ? " " : "\n");
1145 		}
1146 	}
1147 
1148 	if (prop._settings.size() > 1 || (prop._flags & kSettingsSingleItem))
1149 		output += (prop._flags & kSettingsAsList) ? "\t\t\t);\n" : "\t\t\t};\n";
1150 
1151 	return output;
1152 }
1153 
writeSetting(const std::string & variable,std::string value,std::string comment,int flags,int indent) const1154 std::string XcodeProvider::writeSetting(const std::string &variable, std::string value, std::string comment, int flags, int indent) const {
1155 	return writeSetting(variable, Setting(value, comment, flags, indent));
1156 }
1157 
1158 // Heavily modified (not in a good way) function, imported from the QMake
1159 // XCode project generator pbuilder_pbx.cpp, writeSettings() (under LGPL 2.1)
writeSetting(const std::string & variable,const Setting & setting) const1160 std::string XcodeProvider::writeSetting(const std::string &variable, const Setting &setting) const {
1161 	std::string output;
1162 	const std::string quote = (setting._flags & kSettingsNoQuote) ? "" : "\"";
1163 	const std::string escape_quote = quote.empty() ? "" : "\\" + quote;
1164 	std::string newline = "\n";
1165 
1166 	// Get indent level
1167 	for (int i = 0; i < setting._indent; ++i)
1168 		newline += "\t";
1169 
1170 	// Setup variable
1171 	std::string var = (setting._flags & kSettingsQuoteVariable) ? "\"" + variable + "\"" : variable;
1172 
1173 	// Output a list
1174 	if (setting._flags & kSettingsAsList) {
1175 		output += var + ((setting._flags & kSettingsNoValue) ? "(" : " = (") + newline;
1176 
1177 		for (unsigned int i = 0, count = 0; i < setting._entries.size(); ++i) {
1178 
1179 			std::string value = setting._entries.at(i)._value;
1180 			if (!value.empty()) {
1181 				if (count++ > 0)
1182 					output += "," + newline;
1183 
1184 				output += quote + replace(value, quote, escape_quote) + quote;
1185 
1186 				std::string comment = setting._entries.at(i)._comment;
1187 				if (!comment.empty())
1188 					output += " /* " + comment + " */";
1189 			}
1190 
1191 		}
1192 		// Add closing ")" on new line
1193 		newline.resize(newline.size() - 1);
1194 		output += (setting._flags & kSettingsNoValue) ? "\t\t\t)" : "," + newline + ")";
1195 	} else {
1196 		output += var;
1197 
1198 		output += (setting._flags & kSettingsNoValue) ? "" : " = " + quote;
1199 
1200 		for (unsigned int i = 0; i < setting._entries.size(); ++i) {
1201 			std::string value = setting._entries.at(i)._value;
1202 			if (i)
1203 				output += " ";
1204 			output += value;
1205 
1206 			std::string comment = setting._entries.at(i)._comment;
1207 			if (!comment.empty())
1208 				output += " /* " + comment + " */";
1209 		}
1210 
1211 		output += (setting._flags & kSettingsNoValue) ? "" : quote;
1212 	}
1213 	return output;
1214 }
1215 
1216 } // End of CreateProjectTool namespace
1217