No Description

pbxProject.js 69KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201
  1. /**
  2. Licensed to the Apache Software Foundation (ASF) under one
  3. or more contributor license agreements. See the NOTICE file
  4. distributed with this work for additional information
  5. regarding copyright ownership. The ASF licenses this file
  6. to you under the Apache License, Version 2.0 (the
  7. 'License'); you may not use this file except in compliance
  8. with the License. You may obtain a copy of the License at
  9. http://www.apache.org/licenses/LICENSE-2.0
  10. Unless required by applicable law or agreed to in writing,
  11. software distributed under the License is distributed on an
  12. 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  13. KIND, either express or implied. See the License for the
  14. specific language governing permissions and limitations
  15. under the License.
  16. */
  17. var util = require('util'),
  18. f = util.format,
  19. EventEmitter = require('events').EventEmitter,
  20. path = require('path'),
  21. uuid = require('uuid'),
  22. fork = require('child_process').fork,
  23. pbxWriter = require('./pbxWriter'),
  24. pbxFile = require('./pbxFile'),
  25. fs = require('fs'),
  26. parser = require('./parser/pbxproj'),
  27. plist = require('simple-plist'),
  28. COMMENT_KEY = /_comment$/
  29. function pbxProject(filename) {
  30. if (!(this instanceof pbxProject))
  31. return new pbxProject(filename);
  32. this.filepath = path.resolve(filename)
  33. }
  34. util.inherits(pbxProject, EventEmitter)
  35. pbxProject.prototype.parse = function(cb) {
  36. var worker = fork(__dirname + '/parseJob.js', [this.filepath])
  37. worker.on('message', function(msg) {
  38. if (msg.name == 'SyntaxError' || msg.code) {
  39. this.emit('error', msg);
  40. } else {
  41. this.hash = msg;
  42. this.emit('end', null, msg)
  43. }
  44. }.bind(this));
  45. if (cb) {
  46. this.on('error', cb);
  47. this.on('end', cb);
  48. }
  49. return this;
  50. }
  51. pbxProject.prototype.parseSync = function() {
  52. var file_contents = fs.readFileSync(this.filepath, 'utf-8');
  53. this.hash = parser.parse(file_contents);
  54. return this;
  55. }
  56. pbxProject.prototype.writeSync = function(options) {
  57. this.writer = new pbxWriter(this.hash, options);
  58. return this.writer.writeSync();
  59. }
  60. pbxProject.prototype.allUuids = function() {
  61. var sections = this.hash.project.objects,
  62. uuids = [],
  63. section;
  64. for (key in sections) {
  65. section = sections[key]
  66. uuids = uuids.concat(Object.keys(section))
  67. }
  68. uuids = uuids.filter(function(str) {
  69. return !COMMENT_KEY.test(str) && str.length == 24;
  70. });
  71. return uuids;
  72. }
  73. pbxProject.prototype.generateUuid = function() {
  74. var id = uuid.v4()
  75. .replace(/-/g, '')
  76. .substr(0, 24)
  77. .toUpperCase()
  78. if (this.allUuids().indexOf(id) >= 0) {
  79. return this.generateUuid();
  80. } else {
  81. return id;
  82. }
  83. }
  84. pbxProject.prototype.addPluginFile = function(path, opt) {
  85. var file = new pbxFile(path, opt);
  86. file.plugin = true; // durr
  87. correctForPluginsPath(file, this);
  88. // null is better for early errors
  89. if (this.hasFile(file.path)) return null;
  90. file.fileRef = this.generateUuid();
  91. this.addToPbxFileReferenceSection(file); // PBXFileReference
  92. this.addToPluginsPbxGroup(file); // PBXGroup
  93. return file;
  94. }
  95. pbxProject.prototype.removePluginFile = function(path, opt) {
  96. var file = new pbxFile(path, opt);
  97. correctForPluginsPath(file, this);
  98. this.removeFromPbxFileReferenceSection(file); // PBXFileReference
  99. this.removeFromPluginsPbxGroup(file); // PBXGroup
  100. return file;
  101. }
  102. pbxProject.prototype.addProductFile = function(targetPath, opt) {
  103. var file = new pbxFile(targetPath, opt);
  104. file.includeInIndex = 0;
  105. file.fileRef = this.generateUuid();
  106. file.target = opt ? opt.target : undefined;
  107. file.group = opt ? opt.group : undefined;
  108. file.uuid = this.generateUuid();
  109. file.path = file.basename;
  110. this.addToPbxFileReferenceSection(file);
  111. this.addToProductsPbxGroup(file); // PBXGroup
  112. return file;
  113. }
  114. pbxProject.prototype.removeProductFile = function(path, opt) {
  115. var file = new pbxFile(path, opt);
  116. this.removeFromProductsPbxGroup(file); // PBXGroup
  117. return file;
  118. }
  119. /**
  120. *
  121. * @param path {String}
  122. * @param opt {Object} see pbxFile for avail options
  123. * @param group {String} group key
  124. * @returns {Object} file; see pbxFile
  125. */
  126. pbxProject.prototype.addSourceFile = function (path, opt, group) {
  127. var file;
  128. if (group) {
  129. file = this.addFile(path, group, opt);
  130. }
  131. else {
  132. file = this.addPluginFile(path, opt);
  133. }
  134. if (!file) return false;
  135. file.target = opt ? opt.target : undefined;
  136. file.uuid = this.generateUuid();
  137. this.addToPbxBuildFileSection(file); // PBXBuildFile
  138. this.addToPbxSourcesBuildPhase(file); // PBXSourcesBuildPhase
  139. return file;
  140. }
  141. /**
  142. *
  143. * @param path {String}
  144. * @param opt {Object} see pbxFile for avail options
  145. * @param group {String} group key
  146. * @returns {Object} file; see pbxFile
  147. */
  148. pbxProject.prototype.removeSourceFile = function (path, opt, group) {
  149. var file;
  150. if (group) {
  151. file = this.removeFile(path, group, opt);
  152. }
  153. else {
  154. file = this.removePluginFile(path, opt);
  155. }
  156. file.target = opt ? opt.target : undefined;
  157. this.removeFromPbxBuildFileSection(file); // PBXBuildFile
  158. this.removeFromPbxSourcesBuildPhase(file); // PBXSourcesBuildPhase
  159. return file;
  160. }
  161. /**
  162. *
  163. * @param path {String}
  164. * @param opt {Object} see pbxFile for avail options
  165. * @param group {String} group key
  166. * @returns {Object} file; see pbxFile
  167. */
  168. pbxProject.prototype.addHeaderFile = function (path, opt, group) {
  169. if (group) {
  170. return this.addFile(path, group, opt);
  171. }
  172. else {
  173. return this.addPluginFile(path, opt);
  174. }
  175. }
  176. /**
  177. *
  178. * @param path {String}
  179. * @param opt {Object} see pbxFile for avail options
  180. * @param group {String} group key
  181. * @returns {Object} file; see pbxFile
  182. */
  183. pbxProject.prototype.removeHeaderFile = function (path, opt, group) {
  184. if (group) {
  185. return this.removeFile(path, group, opt);
  186. }
  187. else {
  188. return this.removePluginFile(path, opt);
  189. }
  190. }
  191. /**
  192. *
  193. * @param path {String}
  194. * @param opt {Object} see pbxFile for avail options
  195. * @param group {String} group key
  196. * @returns {Object} file; see pbxFile
  197. */
  198. pbxProject.prototype.addResourceFile = function(path, opt, group) {
  199. opt = opt || {};
  200. var file;
  201. if (opt.plugin) {
  202. file = this.addPluginFile(path, opt);
  203. if (!file) return false;
  204. } else {
  205. file = new pbxFile(path, opt);
  206. if (this.hasFile(file.path)) return false;
  207. }
  208. file.uuid = this.generateUuid();
  209. file.target = opt ? opt.target : undefined;
  210. if (!opt.plugin) {
  211. correctForResourcesPath(file, this);
  212. file.fileRef = this.generateUuid();
  213. }
  214. if (!opt.variantGroup) {
  215. this.addToPbxBuildFileSection(file); // PBXBuildFile
  216. this.addToPbxResourcesBuildPhase(file); // PBXResourcesBuildPhase
  217. }
  218. if (!opt.plugin) {
  219. this.addToPbxFileReferenceSection(file); // PBXFileReference
  220. if (group) {
  221. if (this.getPBXGroupByKey(group)) {
  222. this.addToPbxGroup(file, group); //Group other than Resources (i.e. 'splash')
  223. }
  224. else if (this.getPBXVariantGroupByKey(group)) {
  225. this.addToPbxVariantGroup(file, group); // PBXVariantGroup
  226. }
  227. }
  228. else {
  229. this.addToResourcesPbxGroup(file); // PBXGroup
  230. }
  231. }
  232. return file;
  233. }
  234. /**
  235. *
  236. * @param path {String}
  237. * @param opt {Object} see pbxFile for avail options
  238. * @param group {String} group key
  239. * @returns {Object} file; see pbxFile
  240. */
  241. pbxProject.prototype.removeResourceFile = function(path, opt, group) {
  242. var file = new pbxFile(path, opt);
  243. file.target = opt ? opt.target : undefined;
  244. correctForResourcesPath(file, this);
  245. this.removeFromPbxBuildFileSection(file); // PBXBuildFile
  246. this.removeFromPbxFileReferenceSection(file); // PBXFileReference
  247. if (group) {
  248. if (this.getPBXGroupByKey(group)) {
  249. this.removeFromPbxGroup(file, group); //Group other than Resources (i.e. 'splash')
  250. }
  251. else if (this.getPBXVariantGroupByKey(group)) {
  252. this.removeFromPbxVariantGroup(file, group); // PBXVariantGroup
  253. }
  254. }
  255. else {
  256. this.removeFromResourcesPbxGroup(file); // PBXGroup
  257. }
  258. this.removeFromPbxResourcesBuildPhase(file); // PBXResourcesBuildPhase
  259. return file;
  260. }
  261. pbxProject.prototype.addFramework = function(fpath, opt) {
  262. var customFramework = opt && opt.customFramework == true;
  263. var link = !opt || (opt.link == undefined || opt.link); //defaults to true if not specified
  264. var embed = opt && opt.embed; //defaults to false if not specified
  265. if (opt) {
  266. delete opt.embed;
  267. }
  268. var file = new pbxFile(fpath, opt);
  269. file.uuid = this.generateUuid();
  270. file.fileRef = this.generateUuid();
  271. file.target = opt ? opt.target : undefined;
  272. if (this.hasFile(file.path)) return false;
  273. this.addToPbxBuildFileSection(file); // PBXBuildFile
  274. this.addToPbxFileReferenceSection(file); // PBXFileReference
  275. this.addToFrameworksPbxGroup(file); // PBXGroup
  276. if (link) {
  277. this.addToPbxFrameworksBuildPhase(file); // PBXFrameworksBuildPhase
  278. }
  279. if (customFramework) {
  280. this.addToFrameworkSearchPaths(file);
  281. if (embed) {
  282. opt.embed = embed;
  283. var embeddedFile = new pbxFile(fpath, opt);
  284. embeddedFile.uuid = this.generateUuid();
  285. embeddedFile.fileRef = file.fileRef;
  286. //keeping a separate PBXBuildFile entry for Embed Frameworks
  287. this.addToPbxBuildFileSection(embeddedFile); // PBXBuildFile
  288. this.addToPbxEmbedFrameworksBuildPhase(embeddedFile); // PBXCopyFilesBuildPhase
  289. return embeddedFile;
  290. }
  291. }
  292. return file;
  293. }
  294. pbxProject.prototype.removeFramework = function(fpath, opt) {
  295. var embed = opt && opt.embed;
  296. if (opt) {
  297. delete opt.embed;
  298. }
  299. var file = new pbxFile(fpath, opt);
  300. file.target = opt ? opt.target : undefined;
  301. this.removeFromPbxBuildFileSection(file); // PBXBuildFile
  302. this.removeFromPbxFileReferenceSection(file); // PBXFileReference
  303. this.removeFromFrameworksPbxGroup(file); // PBXGroup
  304. this.removeFromPbxFrameworksBuildPhase(file); // PBXFrameworksBuildPhase
  305. if (opt && opt.customFramework) {
  306. this.removeFromFrameworkSearchPaths(file);
  307. }
  308. opt = opt || {};
  309. opt.embed = true;
  310. var embeddedFile = new pbxFile(fpath, opt);
  311. embeddedFile.fileRef = file.fileRef;
  312. this.removeFromPbxBuildFileSection(embeddedFile); // PBXBuildFile
  313. this.removeFromPbxEmbedFrameworksBuildPhase(embeddedFile); // PBXCopyFilesBuildPhase
  314. return file;
  315. }
  316. pbxProject.prototype.addCopyfile = function(fpath, opt) {
  317. var file = new pbxFile(fpath, opt);
  318. // catch duplicates
  319. if (this.hasFile(file.path)) {
  320. file = this.hasFile(file.path);
  321. }
  322. file.fileRef = file.uuid = this.generateUuid();
  323. file.target = opt ? opt.target : undefined;
  324. this.addToPbxBuildFileSection(file); // PBXBuildFile
  325. this.addToPbxFileReferenceSection(file); // PBXFileReference
  326. this.addToPbxCopyfilesBuildPhase(file); // PBXCopyFilesBuildPhase
  327. return file;
  328. }
  329. pbxProject.prototype.pbxCopyfilesBuildPhaseObj = function(target) {
  330. return this.buildPhaseObject('PBXCopyFilesBuildPhase', 'Copy Files', target);
  331. }
  332. pbxProject.prototype.addToPbxCopyfilesBuildPhase = function(file) {
  333. var sources = this.buildPhaseObject('PBXCopyFilesBuildPhase', 'Copy Files', file.target);
  334. sources.files.push(pbxBuildPhaseObj(file));
  335. }
  336. pbxProject.prototype.removeCopyfile = function(fpath, opt) {
  337. var file = new pbxFile(fpath, opt);
  338. file.target = opt ? opt.target : undefined;
  339. this.removeFromPbxBuildFileSection(file); // PBXBuildFile
  340. this.removeFromPbxFileReferenceSection(file); // PBXFileReference
  341. this.removeFromPbxCopyfilesBuildPhase(file); // PBXFrameworksBuildPhase
  342. return file;
  343. }
  344. pbxProject.prototype.removeFromPbxCopyfilesBuildPhase = function(file) {
  345. var sources = this.pbxCopyfilesBuildPhaseObj(file.target);
  346. for (i in sources.files) {
  347. if (sources.files[i].comment == longComment(file)) {
  348. sources.files.splice(i, 1);
  349. break;
  350. }
  351. }
  352. }
  353. pbxProject.prototype.addStaticLibrary = function(path, opt) {
  354. opt = opt || {};
  355. var file;
  356. if (opt.plugin) {
  357. file = this.addPluginFile(path, opt);
  358. if (!file) return false;
  359. } else {
  360. file = new pbxFile(path, opt);
  361. if (this.hasFile(file.path)) return false;
  362. }
  363. file.uuid = this.generateUuid();
  364. file.target = opt ? opt.target : undefined;
  365. if (!opt.plugin) {
  366. file.fileRef = this.generateUuid();
  367. this.addToPbxFileReferenceSection(file); // PBXFileReference
  368. }
  369. this.addToPbxBuildFileSection(file); // PBXBuildFile
  370. this.addToPbxFrameworksBuildPhase(file); // PBXFrameworksBuildPhase
  371. this.addToLibrarySearchPaths(file); // make sure it gets built!
  372. return file;
  373. }
  374. // helper addition functions
  375. pbxProject.prototype.addToPbxBuildFileSection = function(file) {
  376. var commentKey = f("%s_comment", file.uuid);
  377. this.pbxBuildFileSection()[file.uuid] = pbxBuildFileObj(file);
  378. this.pbxBuildFileSection()[commentKey] = pbxBuildFileComment(file);
  379. }
  380. pbxProject.prototype.removeFromPbxBuildFileSection = function(file) {
  381. var uuid;
  382. for (uuid in this.pbxBuildFileSection()) {
  383. if (this.pbxBuildFileSection()[uuid].fileRef_comment == file.basename) {
  384. file.uuid = uuid;
  385. delete this.pbxBuildFileSection()[uuid];
  386. var commentKey = f("%s_comment", uuid);
  387. delete this.pbxBuildFileSection()[commentKey];
  388. }
  389. }
  390. }
  391. pbxProject.prototype.addPbxGroup = function(filePathsArray, name, path, sourceTree) {
  392. var groups = this.hash.project.objects['PBXGroup'],
  393. pbxGroupUuid = this.generateUuid(),
  394. commentKey = f("%s_comment", pbxGroupUuid),
  395. pbxGroup = {
  396. isa: 'PBXGroup',
  397. children: [],
  398. name: name,
  399. path: path,
  400. sourceTree: sourceTree ? sourceTree : '"<group>"'
  401. },
  402. fileReferenceSection = this.pbxFileReferenceSection(),
  403. filePathToReference = {};
  404. for (var key in fileReferenceSection) {
  405. // only look for comments
  406. if (!COMMENT_KEY.test(key)) continue;
  407. var fileReferenceKey = key.split(COMMENT_KEY)[0],
  408. fileReference = fileReferenceSection[fileReferenceKey];
  409. filePathToReference[fileReference.path] = { fileRef: fileReferenceKey, basename: fileReferenceSection[key] };
  410. }
  411. for (var index = 0; index < filePathsArray.length; index++) {
  412. var filePath = filePathsArray[index],
  413. filePathQuoted = "\"" + filePath + "\"";
  414. if (filePathToReference[filePath]) {
  415. pbxGroup.children.push(pbxGroupChild(filePathToReference[filePath]));
  416. continue;
  417. } else if (filePathToReference[filePathQuoted]) {
  418. pbxGroup.children.push(pbxGroupChild(filePathToReference[filePathQuoted]));
  419. continue;
  420. }
  421. var file = new pbxFile(filePath);
  422. file.uuid = this.generateUuid();
  423. file.fileRef = this.generateUuid();
  424. this.addToPbxFileReferenceSection(file); // PBXFileReference
  425. this.addToPbxBuildFileSection(file); // PBXBuildFile
  426. pbxGroup.children.push(pbxGroupChild(file));
  427. }
  428. if (groups) {
  429. groups[pbxGroupUuid] = pbxGroup;
  430. groups[commentKey] = name;
  431. }
  432. return { uuid: pbxGroupUuid, pbxGroup: pbxGroup };
  433. }
  434. pbxProject.prototype.removePbxGroup = function (groupName) {
  435. var section = this.hash.project.objects['PBXGroup'],
  436. key, itemKey;
  437. for (key in section) {
  438. // only look for comments
  439. if (!COMMENT_KEY.test(key)) continue;
  440. if (section[key] == groupName) {
  441. itemKey = key.split(COMMENT_KEY)[0];
  442. delete section[itemKey];
  443. }
  444. }
  445. }
  446. pbxProject.prototype.addToPbxProjectSection = function(target) {
  447. var newTarget = {
  448. value: target.uuid,
  449. comment: pbxNativeTargetComment(target.pbxNativeTarget)
  450. };
  451. this.pbxProjectSection()[this.getFirstProject()['uuid']]['targets'].push(newTarget);
  452. }
  453. pbxProject.prototype.addToPbxNativeTargetSection = function(target) {
  454. var commentKey = f("%s_comment", target.uuid);
  455. this.pbxNativeTargetSection()[target.uuid] = target.pbxNativeTarget;
  456. this.pbxNativeTargetSection()[commentKey] = target.pbxNativeTarget.name;
  457. }
  458. pbxProject.prototype.addToPbxFileReferenceSection = function(file) {
  459. var commentKey = f("%s_comment", file.fileRef);
  460. this.pbxFileReferenceSection()[file.fileRef] = pbxFileReferenceObj(file);
  461. this.pbxFileReferenceSection()[commentKey] = pbxFileReferenceComment(file);
  462. }
  463. pbxProject.prototype.removeFromPbxFileReferenceSection = function(file) {
  464. var i;
  465. var refObj = pbxFileReferenceObj(file);
  466. for (i in this.pbxFileReferenceSection()) {
  467. if (this.pbxFileReferenceSection()[i].name == refObj.name ||
  468. ('"' + this.pbxFileReferenceSection()[i].name + '"') == refObj.name ||
  469. this.pbxFileReferenceSection()[i].path == refObj.path ||
  470. ('"' + this.pbxFileReferenceSection()[i].path + '"') == refObj.path) {
  471. file.fileRef = file.uuid = i;
  472. delete this.pbxFileReferenceSection()[i];
  473. break;
  474. }
  475. }
  476. var commentKey = f("%s_comment", file.fileRef);
  477. if (this.pbxFileReferenceSection()[commentKey] != undefined) {
  478. delete this.pbxFileReferenceSection()[commentKey];
  479. }
  480. return file;
  481. }
  482. pbxProject.prototype.addToXcVersionGroupSection = function(file) {
  483. if (!file.models || !file.currentModel) {
  484. throw new Error("Cannot create a XCVersionGroup section from not a data model document file");
  485. }
  486. var commentKey = f("%s_comment", file.fileRef);
  487. if (!this.xcVersionGroupSection()[file.fileRef]) {
  488. this.xcVersionGroupSection()[file.fileRef] = {
  489. isa: 'XCVersionGroup',
  490. children: file.models.map(function (el) { return el.fileRef; }),
  491. currentVersion: file.currentModel.fileRef,
  492. name: path.basename(file.path),
  493. path: file.path,
  494. sourceTree: '"<group>"',
  495. versionGroupType: 'wrapper.xcdatamodel'
  496. };
  497. this.xcVersionGroupSection()[commentKey] = path.basename(file.path);
  498. }
  499. }
  500. pbxProject.prototype.addToPluginsPbxGroup = function(file) {
  501. var pluginsGroup = this.pbxGroupByName('Plugins');
  502. if (!pluginsGroup) {
  503. this.addPbxGroup([file.path], 'Plugins');
  504. } else {
  505. pluginsGroup.children.push(pbxGroupChild(file));
  506. }
  507. }
  508. pbxProject.prototype.removeFromPluginsPbxGroup = function(file) {
  509. if (!this.pbxGroupByName('Plugins')) {
  510. return null;
  511. }
  512. var pluginsGroupChildren = this.pbxGroupByName('Plugins').children, i;
  513. for (i in pluginsGroupChildren) {
  514. if (pbxGroupChild(file).value == pluginsGroupChildren[i].value &&
  515. pbxGroupChild(file).comment == pluginsGroupChildren[i].comment) {
  516. pluginsGroupChildren.splice(i, 1);
  517. break;
  518. }
  519. }
  520. }
  521. pbxProject.prototype.addToResourcesPbxGroup = function(file) {
  522. var pluginsGroup = this.pbxGroupByName('Resources');
  523. if (!pluginsGroup) {
  524. this.addPbxGroup([file.path], 'Resources');
  525. } else {
  526. pluginsGroup.children.push(pbxGroupChild(file));
  527. }
  528. }
  529. pbxProject.prototype.removeFromResourcesPbxGroup = function(file) {
  530. if (!this.pbxGroupByName('Resources')) {
  531. return null;
  532. }
  533. var pluginsGroupChildren = this.pbxGroupByName('Resources').children, i;
  534. for (i in pluginsGroupChildren) {
  535. if (pbxGroupChild(file).value == pluginsGroupChildren[i].value &&
  536. pbxGroupChild(file).comment == pluginsGroupChildren[i].comment) {
  537. pluginsGroupChildren.splice(i, 1);
  538. break;
  539. }
  540. }
  541. }
  542. pbxProject.prototype.addToFrameworksPbxGroup = function(file) {
  543. var pluginsGroup = this.pbxGroupByName('Frameworks');
  544. if (!pluginsGroup) {
  545. this.addPbxGroup([file.path], 'Frameworks');
  546. } else {
  547. pluginsGroup.children.push(pbxGroupChild(file));
  548. }
  549. }
  550. pbxProject.prototype.removeFromFrameworksPbxGroup = function(file) {
  551. if (!this.pbxGroupByName('Frameworks')) {
  552. return null;
  553. }
  554. var pluginsGroupChildren = this.pbxGroupByName('Frameworks').children;
  555. for (i in pluginsGroupChildren) {
  556. if (pbxGroupChild(file).value == pluginsGroupChildren[i].value &&
  557. pbxGroupChild(file).comment == pluginsGroupChildren[i].comment) {
  558. pluginsGroupChildren.splice(i, 1);
  559. break;
  560. }
  561. }
  562. }
  563. pbxProject.prototype.addToPbxEmbedFrameworksBuildPhase = function (file) {
  564. var sources = this.pbxEmbedFrameworksBuildPhaseObj(file.target);
  565. if (sources) {
  566. sources.files.push(pbxBuildPhaseObj(file));
  567. }
  568. }
  569. pbxProject.prototype.removeFromPbxEmbedFrameworksBuildPhase = function (file) {
  570. var sources = this.pbxEmbedFrameworksBuildPhaseObj(file.target);
  571. if (sources) {
  572. var files = [];
  573. for (i in sources.files) {
  574. if (sources.files[i].comment != longComment(file)) {
  575. files.push(sources.files[i]);
  576. }
  577. }
  578. sources.files = files;
  579. }
  580. }
  581. pbxProject.prototype.addToProductsPbxGroup = function(file) {
  582. var productsGroup = this.pbxGroupByName('Products');
  583. if (!productsGroup) {
  584. this.addPbxGroup([file.path], 'Products');
  585. } else {
  586. productsGroup.children.push(pbxGroupChild(file));
  587. }
  588. }
  589. pbxProject.prototype.removeFromProductsPbxGroup = function(file) {
  590. if (!this.pbxGroupByName('Products')) {
  591. return null;
  592. }
  593. var productsGroupChildren = this.pbxGroupByName('Products').children, i;
  594. for (i in productsGroupChildren) {
  595. if (pbxGroupChild(file).value == productsGroupChildren[i].value &&
  596. pbxGroupChild(file).comment == productsGroupChildren[i].comment) {
  597. productsGroupChildren.splice(i, 1);
  598. break;
  599. }
  600. }
  601. }
  602. pbxProject.prototype.addToPbxSourcesBuildPhase = function(file) {
  603. var sources = this.pbxSourcesBuildPhaseObj(file.target);
  604. sources.files.push(pbxBuildPhaseObj(file));
  605. }
  606. pbxProject.prototype.removeFromPbxSourcesBuildPhase = function(file) {
  607. var sources = this.pbxSourcesBuildPhaseObj(file.target), i;
  608. for (i in sources.files) {
  609. if (sources.files[i].comment == longComment(file)) {
  610. sources.files.splice(i, 1);
  611. break;
  612. }
  613. }
  614. }
  615. pbxProject.prototype.addToPbxResourcesBuildPhase = function(file) {
  616. var sources = this.pbxResourcesBuildPhaseObj(file.target);
  617. sources.files.push(pbxBuildPhaseObj(file));
  618. }
  619. pbxProject.prototype.removeFromPbxResourcesBuildPhase = function(file) {
  620. var sources = this.pbxResourcesBuildPhaseObj(file.target), i;
  621. for (i in sources.files) {
  622. if (sources.files[i].comment == longComment(file)) {
  623. sources.files.splice(i, 1);
  624. break;
  625. }
  626. }
  627. }
  628. pbxProject.prototype.addToPbxFrameworksBuildPhase = function(file) {
  629. var sources = this.pbxFrameworksBuildPhaseObj(file.target);
  630. sources.files.push(pbxBuildPhaseObj(file));
  631. }
  632. pbxProject.prototype.removeFromPbxFrameworksBuildPhase = function(file) {
  633. var sources = this.pbxFrameworksBuildPhaseObj(file.target);
  634. for (i in sources.files) {
  635. if (sources.files[i].comment == longComment(file)) {
  636. sources.files.splice(i, 1);
  637. break;
  638. }
  639. }
  640. }
  641. pbxProject.prototype.addXCConfigurationList = function(configurationObjectsArray, defaultConfigurationName, comment) {
  642. var pbxBuildConfigurationSection = this.pbxXCBuildConfigurationSection(),
  643. pbxXCConfigurationListSection = this.pbxXCConfigurationList(),
  644. xcConfigurationListUuid = this.generateUuid(),
  645. commentKey = f("%s_comment", xcConfigurationListUuid),
  646. xcConfigurationList = {
  647. isa: 'XCConfigurationList',
  648. buildConfigurations: [],
  649. defaultConfigurationIsVisible: 0,
  650. defaultConfigurationName: defaultConfigurationName
  651. };
  652. for (var index = 0; index < configurationObjectsArray.length; index++) {
  653. var configuration = configurationObjectsArray[index],
  654. configurationUuid = this.generateUuid(),
  655. configurationCommentKey = f("%s_comment", configurationUuid);
  656. pbxBuildConfigurationSection[configurationUuid] = configuration;
  657. pbxBuildConfigurationSection[configurationCommentKey] = configuration.name;
  658. xcConfigurationList.buildConfigurations.push({ value: configurationUuid, comment: configuration.name });
  659. }
  660. if (pbxXCConfigurationListSection) {
  661. pbxXCConfigurationListSection[xcConfigurationListUuid] = xcConfigurationList;
  662. pbxXCConfigurationListSection[commentKey] = comment;
  663. }
  664. return { uuid: xcConfigurationListUuid, xcConfigurationList: xcConfigurationList };
  665. }
  666. pbxProject.prototype.addTargetDependency = function(target, dependencyTargets) {
  667. if (!target)
  668. return undefined;
  669. var nativeTargets = this.pbxNativeTargetSection();
  670. if (typeof nativeTargets[target] == "undefined")
  671. throw new Error("Invalid target: " + target);
  672. for (var index = 0; index < dependencyTargets.length; index++) {
  673. var dependencyTarget = dependencyTargets[index];
  674. if (typeof nativeTargets[dependencyTarget] == "undefined")
  675. throw new Error("Invalid target: " + dependencyTarget);
  676. }
  677. var pbxTargetDependency = 'PBXTargetDependency',
  678. pbxContainerItemProxy = 'PBXContainerItemProxy',
  679. pbxTargetDependencySection = this.hash.project.objects[pbxTargetDependency],
  680. pbxContainerItemProxySection = this.hash.project.objects[pbxContainerItemProxy];
  681. for (var index = 0; index < dependencyTargets.length; index++) {
  682. var dependencyTargetUuid = dependencyTargets[index],
  683. dependencyTargetCommentKey = f("%s_comment", dependencyTargetUuid),
  684. targetDependencyUuid = this.generateUuid(),
  685. targetDependencyCommentKey = f("%s_comment", targetDependencyUuid),
  686. itemProxyUuid = this.generateUuid(),
  687. itemProxyCommentKey = f("%s_comment", itemProxyUuid),
  688. itemProxy = {
  689. isa: pbxContainerItemProxy,
  690. containerPortal: this.hash.project['rootObject'],
  691. containerPortal_comment: this.hash.project['rootObject_comment'],
  692. proxyType: 1,
  693. remoteGlobalIDString: dependencyTargetUuid,
  694. remoteInfo: nativeTargets[dependencyTargetUuid].name
  695. },
  696. targetDependency = {
  697. isa: pbxTargetDependency,
  698. target: dependencyTargetUuid,
  699. target_comment: nativeTargets[dependencyTargetCommentKey],
  700. targetProxy: itemProxyUuid,
  701. targetProxy_comment: pbxContainerItemProxy
  702. };
  703. if (pbxContainerItemProxySection && pbxTargetDependencySection) {
  704. pbxContainerItemProxySection[itemProxyUuid] = itemProxy;
  705. pbxContainerItemProxySection[itemProxyCommentKey] = pbxContainerItemProxy;
  706. pbxTargetDependencySection[targetDependencyUuid] = targetDependency;
  707. pbxTargetDependencySection[targetDependencyCommentKey] = pbxTargetDependency;
  708. nativeTargets[target].dependencies.push({ value: targetDependencyUuid, comment: pbxTargetDependency })
  709. }
  710. }
  711. return { uuid: target, target: nativeTargets[target] };
  712. }
  713. pbxProject.prototype.addBuildPhase = function(filePathsArray, buildPhaseType, comment, target, optionsOrFolderType, subfolderPath) {
  714. var buildPhaseSection,
  715. fileReferenceSection = this.pbxFileReferenceSection(),
  716. buildFileSection = this.pbxBuildFileSection(),
  717. buildPhaseUuid = this.generateUuid(),
  718. buildPhaseTargetUuid = target || this.getFirstTarget().uuid,
  719. commentKey = f("%s_comment", buildPhaseUuid),
  720. buildPhase = {
  721. isa: buildPhaseType,
  722. buildActionMask: 2147483647,
  723. files: [],
  724. runOnlyForDeploymentPostprocessing: 0
  725. },
  726. filePathToBuildFile = {};
  727. if (buildPhaseType === 'PBXCopyFilesBuildPhase') {
  728. buildPhase = pbxCopyFilesBuildPhaseObj(buildPhase, optionsOrFolderType, subfolderPath, comment);
  729. } else if (buildPhaseType === 'PBXShellScriptBuildPhase') {
  730. buildPhase = pbxShellScriptBuildPhaseObj(buildPhase, optionsOrFolderType, comment)
  731. }
  732. if (!this.hash.project.objects[buildPhaseType]) {
  733. this.hash.project.objects[buildPhaseType] = new Object();
  734. }
  735. if (!this.hash.project.objects[buildPhaseType][buildPhaseUuid]) {
  736. this.hash.project.objects[buildPhaseType][buildPhaseUuid] = buildPhase;
  737. this.hash.project.objects[buildPhaseType][commentKey] = comment;
  738. }
  739. if (this.hash.project.objects['PBXNativeTarget'][buildPhaseTargetUuid]['buildPhases']) {
  740. this.hash.project.objects['PBXNativeTarget'][buildPhaseTargetUuid]['buildPhases'].push({
  741. value: buildPhaseUuid,
  742. comment: comment
  743. })
  744. }
  745. for (var key in buildFileSection) {
  746. // only look for comments
  747. if (!COMMENT_KEY.test(key)) continue;
  748. var buildFileKey = key.split(COMMENT_KEY)[0],
  749. buildFile = buildFileSection[buildFileKey];
  750. fileReference = fileReferenceSection[buildFile.fileRef];
  751. if (!fileReference) continue;
  752. var pbxFileObj = new pbxFile(fileReference.path);
  753. filePathToBuildFile[fileReference.path] = { uuid: buildFileKey, basename: pbxFileObj.basename, group: pbxFileObj.group };
  754. }
  755. for (var index = 0; index < filePathsArray.length; index++) {
  756. var filePath = filePathsArray[index],
  757. filePathQuoted = "\"" + filePath + "\"",
  758. file = new pbxFile(filePath);
  759. if (filePathToBuildFile[filePath]) {
  760. buildPhase.files.push(pbxBuildPhaseObj(filePathToBuildFile[filePath]));
  761. continue;
  762. } else if (filePathToBuildFile[filePathQuoted]) {
  763. buildPhase.files.push(pbxBuildPhaseObj(filePathToBuildFile[filePathQuoted]));
  764. continue;
  765. }
  766. file.uuid = this.generateUuid();
  767. file.fileRef = this.generateUuid();
  768. this.addToPbxFileReferenceSection(file); // PBXFileReference
  769. this.addToPbxBuildFileSection(file); // PBXBuildFile
  770. buildPhase.files.push(pbxBuildPhaseObj(file));
  771. }
  772. if (buildPhaseSection) {
  773. buildPhaseSection[buildPhaseUuid] = buildPhase;
  774. buildPhaseSection[commentKey] = comment;
  775. }
  776. return { uuid: buildPhaseUuid, buildPhase: buildPhase };
  777. }
  778. // helper access functions
  779. pbxProject.prototype.pbxProjectSection = function() {
  780. return this.hash.project.objects['PBXProject'];
  781. }
  782. pbxProject.prototype.pbxBuildFileSection = function() {
  783. return this.hash.project.objects['PBXBuildFile'];
  784. }
  785. pbxProject.prototype.pbxXCBuildConfigurationSection = function() {
  786. return this.hash.project.objects['XCBuildConfiguration'];
  787. }
  788. pbxProject.prototype.pbxFileReferenceSection = function() {
  789. return this.hash.project.objects['PBXFileReference'];
  790. }
  791. pbxProject.prototype.pbxNativeTargetSection = function() {
  792. return this.hash.project.objects['PBXNativeTarget'];
  793. }
  794. pbxProject.prototype.xcVersionGroupSection = function () {
  795. if (typeof this.hash.project.objects['XCVersionGroup'] !== 'object') {
  796. this.hash.project.objects['XCVersionGroup'] = {};
  797. }
  798. return this.hash.project.objects['XCVersionGroup'];
  799. }
  800. pbxProject.prototype.pbxXCConfigurationList = function() {
  801. return this.hash.project.objects['XCConfigurationList'];
  802. }
  803. pbxProject.prototype.pbxGroupByName = function(name) {
  804. var groups = this.hash.project.objects['PBXGroup'],
  805. key, groupKey;
  806. for (key in groups) {
  807. // only look for comments
  808. if (!COMMENT_KEY.test(key)) continue;
  809. if (groups[key] == name) {
  810. groupKey = key.split(COMMENT_KEY)[0];
  811. return groups[groupKey];
  812. }
  813. }
  814. return null;
  815. }
  816. pbxProject.prototype.pbxTargetByName = function(name) {
  817. return this.pbxItemByComment(name, 'PBXNativeTarget');
  818. }
  819. pbxProject.prototype.findTargetKey = function(name) {
  820. var targets = this.hash.project.objects['PBXNativeTarget'];
  821. for (var key in targets) {
  822. // only look for comments
  823. if (COMMENT_KEY.test(key)) continue;
  824. var target = targets[key];
  825. if (target.name === name) {
  826. return key;
  827. }
  828. }
  829. return null;
  830. }
  831. pbxProject.prototype.pbxItemByComment = function(name, pbxSectionName) {
  832. var section = this.hash.project.objects[pbxSectionName],
  833. key, itemKey;
  834. for (key in section) {
  835. // only look for comments
  836. if (!COMMENT_KEY.test(key)) continue;
  837. if (section[key] == name) {
  838. itemKey = key.split(COMMENT_KEY)[0];
  839. return section[itemKey];
  840. }
  841. }
  842. return null;
  843. }
  844. pbxProject.prototype.pbxSourcesBuildPhaseObj = function(target) {
  845. return this.buildPhaseObject('PBXSourcesBuildPhase', 'Sources', target);
  846. }
  847. pbxProject.prototype.pbxResourcesBuildPhaseObj = function(target) {
  848. return this.buildPhaseObject('PBXResourcesBuildPhase', 'Resources', target);
  849. }
  850. pbxProject.prototype.pbxFrameworksBuildPhaseObj = function(target) {
  851. return this.buildPhaseObject('PBXFrameworksBuildPhase', 'Frameworks', target);
  852. }
  853. pbxProject.prototype.pbxEmbedFrameworksBuildPhaseObj = function (target) {
  854. return this.buildPhaseObject('PBXCopyFilesBuildPhase', 'Embed Frameworks', target);
  855. };
  856. // Find Build Phase from group/target
  857. pbxProject.prototype.buildPhase = function(group, target) {
  858. if (!target)
  859. return undefined;
  860. var nativeTargets = this.pbxNativeTargetSection();
  861. if (typeof nativeTargets[target] == "undefined")
  862. throw new Error("Invalid target: " + target);
  863. var nativeTarget = nativeTargets[target];
  864. var buildPhases = nativeTarget.buildPhases;
  865. for(var i in buildPhases)
  866. {
  867. var buildPhase = buildPhases[i];
  868. if (buildPhase.comment==group)
  869. return buildPhase.value + "_comment";
  870. }
  871. }
  872. pbxProject.prototype.buildPhaseObject = function(name, group, target) {
  873. var section = this.hash.project.objects[name],
  874. obj, sectionKey, key;
  875. var buildPhase = this.buildPhase(group, target);
  876. for (key in section) {
  877. // only look for comments
  878. if (!COMMENT_KEY.test(key)) continue;
  879. // select the proper buildPhase
  880. if (buildPhase && buildPhase!=key)
  881. continue;
  882. if (section[key] == group) {
  883. sectionKey = key.split(COMMENT_KEY)[0];
  884. return section[sectionKey];
  885. }
  886. }
  887. return null;
  888. }
  889. pbxProject.prototype.addBuildProperty = function(prop, value, build_name) {
  890. var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
  891. key, configuration;
  892. for (key in configurations){
  893. configuration = configurations[key];
  894. if (!build_name || configuration.name === build_name){
  895. configuration.buildSettings[prop] = value;
  896. }
  897. }
  898. }
  899. pbxProject.prototype.removeBuildProperty = function(prop, build_name) {
  900. var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
  901. key, configuration;
  902. for (key in configurations){
  903. configuration = configurations[key];
  904. if (configuration.buildSettings[prop] &&
  905. !build_name || configuration.name === build_name){
  906. delete configuration.buildSettings[prop];
  907. }
  908. }
  909. }
  910. /**
  911. *
  912. * @param prop {String}
  913. * @param value {String|Array|Object|Number|Boolean}
  914. * @param build {String} Release or Debug
  915. * @param targetName {String} the target which will be updated
  916. */
  917. pbxProject.prototype.updateBuildProperty = function(prop, value, build, targetName) {
  918. let validConfigs = [];
  919. if(targetName) {
  920. const target = this.pbxTargetByName(targetName);
  921. const targetBuildConfigs = target && target.buildConfigurationList;
  922. const xcConfigList = this.pbxXCConfigurationList();
  923. // Collect the UUID's from the configuration of our target
  924. for (const configName in xcConfigList) {
  925. if (!COMMENT_KEY.test(configName) && targetBuildConfigs === configName) {
  926. const buildVariants = xcConfigList[configName].buildConfigurations;
  927. for (const item of buildVariants) {
  928. validConfigs.push(item.value);
  929. }
  930. break;
  931. }
  932. }
  933. }
  934. var configs = this.pbxXCBuildConfigurationSection();
  935. for (var configName in configs) {
  936. if (!COMMENT_KEY.test(configName)) {
  937. if (targetName && !validConfigs.includes(configName)) continue;
  938. var config = configs[configName];
  939. if ( (build && config.name === build) || (!build) ) {
  940. config.buildSettings[prop] = value;
  941. }
  942. }
  943. }
  944. }
  945. pbxProject.prototype.updateProductName = function(name) {
  946. this.updateBuildProperty('PRODUCT_NAME', '"' + name + '"');
  947. }
  948. pbxProject.prototype.removeFromFrameworkSearchPaths = function(file) {
  949. var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
  950. INHERITED = '"$(inherited)"',
  951. SEARCH_PATHS = 'FRAMEWORK_SEARCH_PATHS',
  952. config, buildSettings, searchPaths;
  953. var new_path = searchPathForFile(file, this);
  954. for (config in configurations) {
  955. buildSettings = configurations[config].buildSettings;
  956. if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
  957. continue;
  958. searchPaths = buildSettings[SEARCH_PATHS];
  959. if (searchPaths && Array.isArray(searchPaths)) {
  960. var matches = searchPaths.filter(function(p) {
  961. return p.indexOf(new_path) > -1;
  962. });
  963. matches.forEach(function(m) {
  964. var idx = searchPaths.indexOf(m);
  965. searchPaths.splice(idx, 1);
  966. });
  967. }
  968. }
  969. }
  970. pbxProject.prototype.addToFrameworkSearchPaths = function(file) {
  971. var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
  972. INHERITED = '"$(inherited)"',
  973. config, buildSettings, searchPaths;
  974. for (config in configurations) {
  975. buildSettings = configurations[config].buildSettings;
  976. if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
  977. continue;
  978. if (!buildSettings['FRAMEWORK_SEARCH_PATHS']
  979. || buildSettings['FRAMEWORK_SEARCH_PATHS'] === INHERITED) {
  980. buildSettings['FRAMEWORK_SEARCH_PATHS'] = [INHERITED];
  981. }
  982. buildSettings['FRAMEWORK_SEARCH_PATHS'].push(searchPathForFile(file, this));
  983. }
  984. }
  985. pbxProject.prototype.removeFromLibrarySearchPaths = function(file) {
  986. var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
  987. INHERITED = '"$(inherited)"',
  988. SEARCH_PATHS = 'LIBRARY_SEARCH_PATHS',
  989. config, buildSettings, searchPaths;
  990. var new_path = searchPathForFile(file, this);
  991. for (config in configurations) {
  992. buildSettings = configurations[config].buildSettings;
  993. if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
  994. continue;
  995. searchPaths = buildSettings[SEARCH_PATHS];
  996. if (searchPaths && Array.isArray(searchPaths)) {
  997. var matches = searchPaths.filter(function(p) {
  998. return p.indexOf(new_path) > -1;
  999. });
  1000. matches.forEach(function(m) {
  1001. var idx = searchPaths.indexOf(m);
  1002. searchPaths.splice(idx, 1);
  1003. });
  1004. }
  1005. }
  1006. }
  1007. pbxProject.prototype.addToLibrarySearchPaths = function(file) {
  1008. var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
  1009. INHERITED = '"$(inherited)"',
  1010. config, buildSettings, searchPaths;
  1011. for (config in configurations) {
  1012. buildSettings = configurations[config].buildSettings;
  1013. if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
  1014. continue;
  1015. if (!buildSettings['LIBRARY_SEARCH_PATHS']
  1016. || buildSettings['LIBRARY_SEARCH_PATHS'] === INHERITED) {
  1017. buildSettings['LIBRARY_SEARCH_PATHS'] = [INHERITED];
  1018. }
  1019. if (typeof file === 'string') {
  1020. buildSettings['LIBRARY_SEARCH_PATHS'].push(file);
  1021. } else {
  1022. buildSettings['LIBRARY_SEARCH_PATHS'].push(searchPathForFile(file, this));
  1023. }
  1024. }
  1025. }
  1026. pbxProject.prototype.removeFromHeaderSearchPaths = function(file) {
  1027. var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
  1028. INHERITED = '"$(inherited)"',
  1029. SEARCH_PATHS = 'HEADER_SEARCH_PATHS',
  1030. config, buildSettings, searchPaths;
  1031. var new_path = searchPathForFile(file, this);
  1032. for (config in configurations) {
  1033. buildSettings = configurations[config].buildSettings;
  1034. if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
  1035. continue;
  1036. if (buildSettings[SEARCH_PATHS]) {
  1037. var matches = buildSettings[SEARCH_PATHS].filter(function(p) {
  1038. return p.indexOf(new_path) > -1;
  1039. });
  1040. matches.forEach(function(m) {
  1041. var idx = buildSettings[SEARCH_PATHS].indexOf(m);
  1042. buildSettings[SEARCH_PATHS].splice(idx, 1);
  1043. });
  1044. }
  1045. }
  1046. }
  1047. pbxProject.prototype.addToHeaderSearchPaths = function(file) {
  1048. var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
  1049. INHERITED = '"$(inherited)"',
  1050. config, buildSettings, searchPaths;
  1051. for (config in configurations) {
  1052. buildSettings = configurations[config].buildSettings;
  1053. if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
  1054. continue;
  1055. if (!buildSettings['HEADER_SEARCH_PATHS']) {
  1056. buildSettings['HEADER_SEARCH_PATHS'] = [INHERITED];
  1057. }
  1058. if (typeof file === 'string') {
  1059. buildSettings['HEADER_SEARCH_PATHS'].push(file);
  1060. } else {
  1061. buildSettings['HEADER_SEARCH_PATHS'].push(searchPathForFile(file, this));
  1062. }
  1063. }
  1064. }
  1065. pbxProject.prototype.addToOtherLinkerFlags = function (flag) {
  1066. var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
  1067. INHERITED = '"$(inherited)"',
  1068. OTHER_LDFLAGS = 'OTHER_LDFLAGS',
  1069. config, buildSettings;
  1070. for (config in configurations) {
  1071. buildSettings = configurations[config].buildSettings;
  1072. if (unquote(buildSettings['PRODUCT_NAME']) != this.productName)
  1073. continue;
  1074. if (!buildSettings[OTHER_LDFLAGS]
  1075. || buildSettings[OTHER_LDFLAGS] === INHERITED) {
  1076. buildSettings[OTHER_LDFLAGS] = [INHERITED];
  1077. }
  1078. buildSettings[OTHER_LDFLAGS].push(flag);
  1079. }
  1080. }
  1081. pbxProject.prototype.removeFromOtherLinkerFlags = function (flag) {
  1082. var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
  1083. OTHER_LDFLAGS = 'OTHER_LDFLAGS',
  1084. config, buildSettings;
  1085. for (config in configurations) {
  1086. buildSettings = configurations[config].buildSettings;
  1087. if (unquote(buildSettings['PRODUCT_NAME']) != this.productName) {
  1088. continue;
  1089. }
  1090. if (buildSettings[OTHER_LDFLAGS]) {
  1091. var matches = buildSettings[OTHER_LDFLAGS].filter(function (p) {
  1092. return p.indexOf(flag) > -1;
  1093. });
  1094. matches.forEach(function (m) {
  1095. var idx = buildSettings[OTHER_LDFLAGS].indexOf(m);
  1096. buildSettings[OTHER_LDFLAGS].splice(idx, 1);
  1097. });
  1098. }
  1099. }
  1100. }
  1101. pbxProject.prototype.addToBuildSettings = function (buildSetting, value) {
  1102. var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
  1103. config, buildSettings;
  1104. for (config in configurations) {
  1105. buildSettings = configurations[config].buildSettings;
  1106. buildSettings[buildSetting] = value;
  1107. }
  1108. }
  1109. pbxProject.prototype.removeFromBuildSettings = function (buildSetting) {
  1110. var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
  1111. config, buildSettings;
  1112. for (config in configurations) {
  1113. buildSettings = configurations[config].buildSettings;
  1114. if (buildSettings[buildSetting]) {
  1115. delete buildSettings[buildSetting];
  1116. }
  1117. }
  1118. }
  1119. // a JS getter. hmmm
  1120. pbxProject.prototype.__defineGetter__("productName", function() {
  1121. var configurations = nonComments(this.pbxXCBuildConfigurationSection()),
  1122. config, productName;
  1123. for (config in configurations) {
  1124. productName = configurations[config].buildSettings['PRODUCT_NAME'];
  1125. if (productName) {
  1126. return unquote(productName);
  1127. }
  1128. }
  1129. });
  1130. // check if file is present
  1131. pbxProject.prototype.hasFile = function(filePath) {
  1132. var files = nonComments(this.pbxFileReferenceSection()),
  1133. file, id;
  1134. for (id in files) {
  1135. file = files[id];
  1136. if (file.path == filePath || file.path == ('"' + filePath + '"')) {
  1137. return file;
  1138. }
  1139. }
  1140. return false;
  1141. }
  1142. pbxProject.prototype.addTarget = function(name, type, subfolder, bundleId) {
  1143. // Setup uuid and name of new target
  1144. var targetUuid = this.generateUuid(),
  1145. targetType = type,
  1146. targetSubfolder = subfolder || name,
  1147. targetName = name.trim(),
  1148. targetBundleId = bundleId;
  1149. // Check type against list of allowed target types
  1150. if (!targetName) {
  1151. throw new Error("Target name missing.");
  1152. }
  1153. // Check type against list of allowed target types
  1154. if (!targetType) {
  1155. throw new Error("Target type missing.");
  1156. }
  1157. // Check type against list of allowed target types
  1158. if (!producttypeForTargettype(targetType)) {
  1159. throw new Error("Target type invalid: " + targetType);
  1160. }
  1161. // Build Configuration: Create
  1162. var buildConfigurationsList = [
  1163. {
  1164. name: 'Debug',
  1165. isa: 'XCBuildConfiguration',
  1166. buildSettings: {
  1167. GCC_PREPROCESSOR_DEFINITIONS: ['"DEBUG=1"', '"$(inherited)"'],
  1168. INFOPLIST_FILE: '"' + path.join(targetSubfolder, targetSubfolder + '-Info.plist' + '"'),
  1169. LD_RUNPATH_SEARCH_PATHS: '"$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"',
  1170. PRODUCT_NAME: '"' + targetName + '"',
  1171. SKIP_INSTALL: 'YES'
  1172. }
  1173. },
  1174. {
  1175. name: 'Release',
  1176. isa: 'XCBuildConfiguration',
  1177. buildSettings: {
  1178. INFOPLIST_FILE: '"' + path.join(targetSubfolder, targetSubfolder + '-Info.plist' + '"'),
  1179. LD_RUNPATH_SEARCH_PATHS: '"$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"',
  1180. PRODUCT_NAME: '"' + targetName + '"',
  1181. SKIP_INSTALL: 'YES'
  1182. }
  1183. }
  1184. ];
  1185. // Add optional bundleId to build configuration
  1186. if (targetBundleId) {
  1187. buildConfigurationsList = buildConfigurationsList.map((elem) => {
  1188. elem.buildSettings.PRODUCT_BUNDLE_IDENTIFIER = '"' + targetBundleId + '"';
  1189. return elem;
  1190. });
  1191. }
  1192. // Build Configuration: Add
  1193. var buildConfigurations = this.addXCConfigurationList(buildConfigurationsList, 'Release', 'Build configuration list for PBXNativeTarget "' + targetName +'"');
  1194. // Product: Create
  1195. var productName = targetName,
  1196. productType = producttypeForTargettype(targetType),
  1197. productFileType = filetypeForProducttype(productType),
  1198. productFile = this.addProductFile(productName, { group: 'Copy Files', 'target': targetUuid, 'explicitFileType': productFileType}),
  1199. productFileName = productFile.basename;
  1200. // Product: Add to build file list
  1201. this.addToPbxBuildFileSection(productFile);
  1202. // Target: Create
  1203. var target = {
  1204. uuid: targetUuid,
  1205. pbxNativeTarget: {
  1206. isa: 'PBXNativeTarget',
  1207. name: '"' + targetName + '"',
  1208. productName: '"' + targetName + '"',
  1209. productReference: productFile.fileRef,
  1210. productType: '"' + producttypeForTargettype(targetType) + '"',
  1211. buildConfigurationList: buildConfigurations.uuid,
  1212. buildPhases: [],
  1213. buildRules: [],
  1214. dependencies: []
  1215. }
  1216. };
  1217. // Target: Add to PBXNativeTarget section
  1218. this.addToPbxNativeTargetSection(target)
  1219. // Product: Embed (only for "extension"-type targets)
  1220. if (targetType === 'app_extension') {
  1221. // Create CopyFiles phase in first target
  1222. this.addBuildPhase([], 'PBXCopyFilesBuildPhase', 'Copy Files', this.getFirstTarget().uuid, targetType)
  1223. // Add product to CopyFiles phase
  1224. this.addToPbxCopyfilesBuildPhase(productFile)
  1225. // this.addBuildPhaseToTarget(newPhase.buildPhase, this.getFirstTarget().uuid)
  1226. } else if (targetType === 'watch2_app') {
  1227. // Create CopyFiles phase in first target
  1228. this.addBuildPhase(
  1229. [targetName + '.app'],
  1230. 'PBXCopyFilesBuildPhase',
  1231. 'Embed Watch Content',
  1232. this.getFirstTarget().uuid,
  1233. targetType,
  1234. '"$(CONTENTS_FOLDER_PATH)/Watch"'
  1235. );
  1236. } else if (targetType === 'watch2_extension') {
  1237. // Create CopyFiles phase in watch target (if exists)
  1238. var watch2Target = this.getTarget(producttypeForTargettype('watch2_app'));
  1239. if (watch2Target) {
  1240. this.addBuildPhase(
  1241. [targetName + '.appex'],
  1242. 'PBXCopyFilesBuildPhase',
  1243. 'Embed App Extensions',
  1244. watch2Target.uuid,
  1245. targetType
  1246. );
  1247. }
  1248. }
  1249. // Target: Add uuid to root project
  1250. this.addToPbxProjectSection(target);
  1251. // Target: Add dependency for this target to other targets
  1252. if (targetType === 'watch2_extension') {
  1253. var watch2Target = this.getTarget(producttypeForTargettype('watch2_app'));
  1254. if (watch2Target) {
  1255. this.addTargetDependency(watch2Target.uuid, [target.uuid]);
  1256. }
  1257. } else {
  1258. this.addTargetDependency(this.getFirstTarget().uuid, [target.uuid]);
  1259. }
  1260. // Return target on success
  1261. return target;
  1262. };
  1263. // helper object creation functions
  1264. function pbxBuildFileObj(file) {
  1265. var obj = Object.create(null);
  1266. obj.isa = 'PBXBuildFile';
  1267. obj.fileRef = file.fileRef;
  1268. obj.fileRef_comment = file.basename;
  1269. if (file.settings) obj.settings = file.settings;
  1270. return obj;
  1271. }
  1272. function pbxFileReferenceObj(file) {
  1273. var fileObject = {
  1274. isa: "PBXFileReference",
  1275. name: "\"" + file.basename + "\"",
  1276. path: "\"" + file.path.replace(/\\/g, '/') + "\"",
  1277. sourceTree: file.sourceTree,
  1278. fileEncoding: file.fileEncoding,
  1279. lastKnownFileType: file.lastKnownFileType,
  1280. explicitFileType: file.explicitFileType,
  1281. includeInIndex: file.includeInIndex
  1282. };
  1283. return fileObject;
  1284. }
  1285. function pbxGroupChild(file) {
  1286. var obj = Object.create(null);
  1287. obj.value = file.fileRef;
  1288. obj.comment = file.basename;
  1289. return obj;
  1290. }
  1291. function pbxBuildPhaseObj(file) {
  1292. var obj = Object.create(null);
  1293. obj.value = file.uuid;
  1294. obj.comment = longComment(file);
  1295. return obj;
  1296. }
  1297. function pbxCopyFilesBuildPhaseObj(obj, folderType, subfolderPath, phaseName) {
  1298. // Add additional properties for 'CopyFiles' build phase
  1299. var DESTINATION_BY_TARGETTYPE = {
  1300. application: 'wrapper',
  1301. app_extension: 'plugins',
  1302. bundle: 'wrapper',
  1303. command_line_tool: 'wrapper',
  1304. dynamic_library: 'products_directory',
  1305. framework: 'shared_frameworks',
  1306. frameworks: 'frameworks',
  1307. static_library: 'products_directory',
  1308. unit_test_bundle: 'wrapper',
  1309. watch_app: 'wrapper',
  1310. watch2_app: 'products_directory',
  1311. watch_extension: 'plugins',
  1312. watch2_extension: 'plugins'
  1313. }
  1314. var SUBFOLDERSPEC_BY_DESTINATION = {
  1315. absolute_path: 0,
  1316. executables: 6,
  1317. frameworks: 10,
  1318. java_resources: 15,
  1319. plugins: 13,
  1320. products_directory: 16,
  1321. resources: 7,
  1322. shared_frameworks: 11,
  1323. shared_support: 12,
  1324. wrapper: 1,
  1325. xpc_services: 0
  1326. }
  1327. obj.name = '"' + phaseName + '"';
  1328. obj.dstPath = subfolderPath || '""';
  1329. obj.dstSubfolderSpec = SUBFOLDERSPEC_BY_DESTINATION[DESTINATION_BY_TARGETTYPE[folderType]];
  1330. return obj;
  1331. }
  1332. function pbxShellScriptBuildPhaseObj(obj, options, phaseName) {
  1333. obj.name = '"' + phaseName + '"';
  1334. obj.inputPaths = options.inputPaths || [];
  1335. obj.outputPaths = options.outputPaths || [];
  1336. obj.shellPath = options.shellPath;
  1337. obj.shellScript = '"' + options.shellScript.replace(/"/g, '\\"') + '"';
  1338. return obj;
  1339. }
  1340. function pbxBuildFileComment(file) {
  1341. return longComment(file);
  1342. }
  1343. function pbxFileReferenceComment(file) {
  1344. return file.basename || path.basename(file.path);
  1345. }
  1346. function pbxNativeTargetComment(target) {
  1347. return target.name;
  1348. }
  1349. function longComment(file) {
  1350. return f("%s in %s", file.basename, file.group);
  1351. }
  1352. // respect <group> path
  1353. function correctForPluginsPath(file, project) {
  1354. return correctForPath(file, project, 'Plugins');
  1355. }
  1356. function correctForResourcesPath(file, project) {
  1357. return correctForPath(file, project, 'Resources');
  1358. }
  1359. function correctForFrameworksPath(file, project) {
  1360. return correctForPath(file, project, 'Frameworks');
  1361. }
  1362. function correctForPath(file, project, group) {
  1363. var r_group_dir = new RegExp('^' + group + '[\\\\/]');
  1364. if (project.pbxGroupByName(group).path)
  1365. file.path = file.path.replace(r_group_dir, '');
  1366. return file;
  1367. }
  1368. function searchPathForFile(file, proj) {
  1369. var plugins = proj.pbxGroupByName('Plugins'),
  1370. pluginsPath = plugins ? plugins.path : null,
  1371. fileDir = path.dirname(file.path);
  1372. if (fileDir == '.') {
  1373. fileDir = '';
  1374. } else {
  1375. fileDir = '/' + fileDir;
  1376. }
  1377. if (file.plugin && pluginsPath) {
  1378. return '"\\"$(SRCROOT)/' + unquote(pluginsPath) + '\\""';
  1379. } else if (file.customFramework && file.dirname) {
  1380. return '"\\"' + file.dirname + '\\""';
  1381. } else {
  1382. return '"\\"$(SRCROOT)/' + proj.productName + fileDir + '\\""';
  1383. }
  1384. }
  1385. function nonComments(obj) {
  1386. var keys = Object.keys(obj),
  1387. newObj = {}, i = 0;
  1388. for (i; i < keys.length; i++) {
  1389. if (!COMMENT_KEY.test(keys[i])) {
  1390. newObj[keys[i]] = obj[keys[i]];
  1391. }
  1392. }
  1393. return newObj;
  1394. }
  1395. function unquote(str) {
  1396. if (str) return str.replace(/^"(.*)"$/, "$1");
  1397. }
  1398. function buildPhaseNameForIsa (isa) {
  1399. BUILDPHASENAME_BY_ISA = {
  1400. PBXCopyFilesBuildPhase: 'Copy Files',
  1401. PBXResourcesBuildPhase: 'Resources',
  1402. PBXSourcesBuildPhase: 'Sources',
  1403. PBXFrameworksBuildPhase: 'Frameworks'
  1404. }
  1405. return BUILDPHASENAME_BY_ISA[isa]
  1406. }
  1407. function producttypeForTargettype (targetType) {
  1408. PRODUCTTYPE_BY_TARGETTYPE = {
  1409. application: 'com.apple.product-type.application',
  1410. app_extension: 'com.apple.product-type.app-extension',
  1411. bundle: 'com.apple.product-type.bundle',
  1412. command_line_tool: 'com.apple.product-type.tool',
  1413. dynamic_library: 'com.apple.product-type.library.dynamic',
  1414. framework: 'com.apple.product-type.framework',
  1415. static_library: 'com.apple.product-type.library.static',
  1416. unit_test_bundle: 'com.apple.product-type.bundle.unit-test',
  1417. watch_app: 'com.apple.product-type.application.watchapp',
  1418. watch2_app: 'com.apple.product-type.application.watchapp2',
  1419. watch_extension: 'com.apple.product-type.watchkit-extension',
  1420. watch2_extension: 'com.apple.product-type.watchkit2-extension'
  1421. };
  1422. return PRODUCTTYPE_BY_TARGETTYPE[targetType]
  1423. }
  1424. function filetypeForProducttype (productType) {
  1425. FILETYPE_BY_PRODUCTTYPE = {
  1426. 'com.apple.product-type.application': '"wrapper.application"',
  1427. 'com.apple.product-type.app-extension': '"wrapper.app-extension"',
  1428. 'com.apple.product-type.bundle': '"wrapper.plug-in"',
  1429. 'com.apple.product-type.tool': '"compiled.mach-o.dylib"',
  1430. 'com.apple.product-type.library.dynamic': '"compiled.mach-o.dylib"',
  1431. 'com.apple.product-type.framework': '"wrapper.framework"',
  1432. 'com.apple.product-type.library.static': '"archive.ar"',
  1433. 'com.apple.product-type.bundle.unit-test': '"wrapper.cfbundle"',
  1434. 'com.apple.product-type.application.watchapp': '"wrapper.application"',
  1435. 'com.apple.product-type.application.watchapp2': '"wrapper.application"',
  1436. 'com.apple.product-type.watchkit-extension': '"wrapper.app-extension"',
  1437. 'com.apple.product-type.watchkit2-extension': '"wrapper.app-extension"'
  1438. };
  1439. return FILETYPE_BY_PRODUCTTYPE[productType]
  1440. }
  1441. pbxProject.prototype.getFirstProject = function() {
  1442. // Get pbxProject container
  1443. var pbxProjectContainer = this.pbxProjectSection();
  1444. // Get first pbxProject UUID
  1445. var firstProjectUuid = Object.keys(pbxProjectContainer)[0];
  1446. // Get first pbxProject
  1447. var firstProject = pbxProjectContainer[firstProjectUuid];
  1448. return {
  1449. uuid: firstProjectUuid,
  1450. firstProject: firstProject
  1451. }
  1452. }
  1453. pbxProject.prototype.getFirstTarget = function() {
  1454. // Get first target's UUID
  1455. var firstTargetUuid = this.getFirstProject()['firstProject']['targets'][0].value;
  1456. // Get first pbxNativeTarget
  1457. var firstTarget = this.pbxNativeTargetSection()[firstTargetUuid];
  1458. return {
  1459. uuid: firstTargetUuid,
  1460. firstTarget: firstTarget
  1461. }
  1462. }
  1463. pbxProject.prototype.getTarget = function(productType) {
  1464. // Find target by product type
  1465. var targets = this.getFirstProject()['firstProject']['targets'];
  1466. var nativeTargets = this.pbxNativeTargetSection();
  1467. for (var i = 0; i < targets.length; i++) {
  1468. var target = targets[i];
  1469. var targetUuid = target.value;
  1470. if (nativeTargets[targetUuid]['productType'] === '"' + productType + '"') {
  1471. // Get pbxNativeTarget
  1472. var nativeTarget = this.pbxNativeTargetSection()[targetUuid];
  1473. return {
  1474. uuid: targetUuid,
  1475. target: nativeTarget
  1476. };
  1477. }
  1478. }
  1479. return null;
  1480. }
  1481. /*** NEW ***/
  1482. pbxProject.prototype.addToPbxGroupType = function (file, groupKey, groupType) {
  1483. var group = this.getPBXGroupByKeyAndType(groupKey, groupType);
  1484. if (group && group.children !== undefined) {
  1485. if (typeof file === 'string') {
  1486. //Group Key
  1487. var childGroup = {
  1488. value:file,
  1489. };
  1490. if (this.getPBXGroupByKey(file)) {
  1491. childGroup.comment = this.getPBXGroupByKey(file).name;
  1492. }
  1493. else if (this.getPBXVariantGroupByKey(file)) {
  1494. childGroup.comment = this.getPBXVariantGroupByKey(file).name;
  1495. }
  1496. group.children.push(childGroup);
  1497. }
  1498. else {
  1499. //File Object
  1500. group.children.push(pbxGroupChild(file));
  1501. }
  1502. }
  1503. }
  1504. pbxProject.prototype.addToPbxVariantGroup = function (file, groupKey) {
  1505. this.addToPbxGroupType(file, groupKey, 'PBXVariantGroup');
  1506. }
  1507. pbxProject.prototype.addToPbxGroup = function (file, groupKey) {
  1508. this.addToPbxGroupType(file, groupKey, 'PBXGroup');
  1509. }
  1510. pbxProject.prototype.pbxCreateGroupWithType = function(name, pathName, groupType) {
  1511. //Create object
  1512. var model = {
  1513. isa: '"' + groupType + '"',
  1514. children: [],
  1515. name: name,
  1516. sourceTree: '"<group>"'
  1517. };
  1518. if (pathName) model.path = pathName;
  1519. var key = this.generateUuid();
  1520. //Create comment
  1521. var commendId = key + '_comment';
  1522. //add obj and commentObj to groups;
  1523. var groups = this.hash.project.objects[groupType];
  1524. if (!groups) {
  1525. groups = this.hash.project.objects[groupType] = new Object();
  1526. }
  1527. groups[commendId] = name;
  1528. groups[key] = model;
  1529. return key;
  1530. }
  1531. pbxProject.prototype.pbxCreateVariantGroup = function(name) {
  1532. return this.pbxCreateGroupWithType(name, undefined, 'PBXVariantGroup')
  1533. }
  1534. pbxProject.prototype.pbxCreateGroup = function(name, pathName) {
  1535. return this.pbxCreateGroupWithType(name, pathName, 'PBXGroup');
  1536. }
  1537. pbxProject.prototype.removeFromPbxGroupAndType = function (file, groupKey, groupType) {
  1538. var group = this.getPBXGroupByKeyAndType(groupKey, groupType);
  1539. if (group) {
  1540. var groupChildren = group.children, i;
  1541. for(i in groupChildren) {
  1542. if(pbxGroupChild(file).value == groupChildren[i].value &&
  1543. pbxGroupChild(file).comment == groupChildren[i].comment) {
  1544. groupChildren.splice(i, 1);
  1545. break;
  1546. }
  1547. }
  1548. }
  1549. }
  1550. pbxProject.prototype.removeFromPbxGroup = function (file, groupKey) {
  1551. this.removeFromPbxGroupAndType(file, groupKey, 'PBXGroup');
  1552. }
  1553. pbxProject.prototype.removeFromPbxVariantGroup = function (file, groupKey) {
  1554. this.removeFromPbxGroupAndType(file, groupKey, 'PBXVariantGroup');
  1555. }
  1556. pbxProject.prototype.getPBXGroupByKeyAndType = function(key, groupType) {
  1557. return this.hash.project.objects[groupType][key];
  1558. };
  1559. pbxProject.prototype.getPBXGroupByKey = function(key) {
  1560. return this.hash.project.objects['PBXGroup'][key];
  1561. };
  1562. pbxProject.prototype.getPBXVariantGroupByKey = function(key) {
  1563. return this.hash.project.objects['PBXVariantGroup'][key];
  1564. };
  1565. pbxProject.prototype.findPBXGroupKeyAndType = function(criteria, groupType) {
  1566. var groups = this.hash.project.objects[groupType];
  1567. var target;
  1568. for (var key in groups) {
  1569. // only look for comments
  1570. if (COMMENT_KEY.test(key)) continue;
  1571. var group = groups[key];
  1572. if (criteria && criteria.path && criteria.name) {
  1573. if (criteria.path === group.path && criteria.name === group.name) {
  1574. target = key;
  1575. break
  1576. }
  1577. }
  1578. else if (criteria && criteria.path) {
  1579. if (criteria.path === group.path) {
  1580. target = key;
  1581. break
  1582. }
  1583. }
  1584. else if (criteria && criteria.name) {
  1585. if (criteria.name === group.name) {
  1586. target = key;
  1587. break
  1588. }
  1589. }
  1590. }
  1591. return target;
  1592. }
  1593. pbxProject.prototype.findPBXGroupKey = function(criteria) {
  1594. return this.findPBXGroupKeyAndType(criteria, 'PBXGroup');
  1595. }
  1596. pbxProject.prototype.findPBXVariantGroupKey = function(criteria) {
  1597. return this.findPBXGroupKeyAndType(criteria, 'PBXVariantGroup');
  1598. }
  1599. pbxProject.prototype.addLocalizationVariantGroup = function(name) {
  1600. var groupKey = this.pbxCreateVariantGroup(name);
  1601. var resourceGroupKey = this.findPBXGroupKey({name: 'Resources'});
  1602. this.addToPbxGroup(groupKey, resourceGroupKey);
  1603. var localizationVariantGroup = {
  1604. uuid: this.generateUuid(),
  1605. fileRef: groupKey,
  1606. basename: name
  1607. }
  1608. this.addToPbxBuildFileSection(localizationVariantGroup); // PBXBuildFile
  1609. this.addToPbxResourcesBuildPhase(localizationVariantGroup); //PBXResourcesBuildPhase
  1610. return localizationVariantGroup;
  1611. };
  1612. pbxProject.prototype.addKnownRegion = function (name) {
  1613. if (!this.pbxProjectSection()[this.getFirstProject()['uuid']]['knownRegions']) {
  1614. this.pbxProjectSection()[this.getFirstProject()['uuid']]['knownRegions'] = [];
  1615. }
  1616. if (!this.hasKnownRegion(name)) {
  1617. this.pbxProjectSection()[this.getFirstProject()['uuid']]['knownRegions'].push(name);
  1618. }
  1619. }
  1620. pbxProject.prototype.removeKnownRegion = function (name) {
  1621. var regions = this.pbxProjectSection()[this.getFirstProject()['uuid']]['knownRegions'];
  1622. if (regions) {
  1623. for (var i = 0; i < regions.length; i++) {
  1624. if (regions[i] === name) {
  1625. regions.splice(i, 1);
  1626. break;
  1627. }
  1628. }
  1629. this.pbxProjectSection()[this.getFirstProject()['uuid']]['knownRegions'] = regions;
  1630. }
  1631. }
  1632. pbxProject.prototype.hasKnownRegion = function (name) {
  1633. var regions = this.pbxProjectSection()[this.getFirstProject()['uuid']]['knownRegions'];
  1634. if (regions) {
  1635. for (var i in regions) {
  1636. if (regions[i] === name) {
  1637. return true;
  1638. }
  1639. }
  1640. }
  1641. return false;
  1642. }
  1643. pbxProject.prototype.getPBXObject = function(name) {
  1644. return this.hash.project.objects[name];
  1645. }
  1646. pbxProject.prototype.addFile = function (path, group, opt) {
  1647. var file = new pbxFile(path, opt);
  1648. // null is better for early errors
  1649. if (this.hasFile(file.path)) return null;
  1650. file.fileRef = this.generateUuid();
  1651. this.addToPbxFileReferenceSection(file); // PBXFileReference
  1652. if (this.getPBXGroupByKey(group)) {
  1653. this.addToPbxGroup(file, group); // PBXGroup
  1654. }
  1655. else if (this.getPBXVariantGroupByKey(group)) {
  1656. this.addToPbxVariantGroup(file, group); // PBXVariantGroup
  1657. }
  1658. return file;
  1659. }
  1660. pbxProject.prototype.removeFile = function (path, group, opt) {
  1661. var file = new pbxFile(path, opt);
  1662. this.removeFromPbxFileReferenceSection(file); // PBXFileReference
  1663. if (this.getPBXGroupByKey(group)) {
  1664. this.removeFromPbxGroup(file, group); // PBXGroup
  1665. }
  1666. else if (this.getPBXVariantGroupByKey(group)) {
  1667. this.removeFromPbxVariantGroup(file, group); // PBXVariantGroup
  1668. }
  1669. return file;
  1670. }
  1671. pbxProject.prototype.getBuildProperty = function(prop, build, targetName) {
  1672. var target;
  1673. let validConfigs = [];
  1674. if (targetName) {
  1675. const target = this.pbxTargetByName(targetName);
  1676. const targetBuildConfigs = target && target.buildConfigurationList;
  1677. const xcConfigList = this.pbxXCConfigurationList();
  1678. // Collect the UUID's from the configuration of our target
  1679. for (const configName in xcConfigList) {
  1680. if (!COMMENT_KEY.test(configName) && targetBuildConfigs === configName) {
  1681. const buildVariants = xcConfigList[configName].buildConfigurations;
  1682. for (const item of buildVariants) {
  1683. validConfigs.push(item.value);
  1684. }
  1685. break;
  1686. }
  1687. }
  1688. }
  1689. var configs = this.pbxXCBuildConfigurationSection();
  1690. for (var configName in configs) {
  1691. if (!COMMENT_KEY.test(configName)) {
  1692. if (targetName && !validConfigs.includes(configName)) continue;
  1693. var config = configs[configName];
  1694. if ( (build && config.name === build) || (build === undefined) ) {
  1695. if (config.buildSettings[prop] !== undefined) {
  1696. target = config.buildSettings[prop];
  1697. }
  1698. }
  1699. }
  1700. }
  1701. return target;
  1702. }
  1703. pbxProject.prototype.getBuildConfigByName = function(name) {
  1704. var target = {};
  1705. var configs = this.pbxXCBuildConfigurationSection();
  1706. for (var configName in configs) {
  1707. if (!COMMENT_KEY.test(configName)) {
  1708. var config = configs[configName];
  1709. if (config.name === name) {
  1710. target[configName] = config;
  1711. }
  1712. }
  1713. }
  1714. return target;
  1715. }
  1716. pbxProject.prototype.addDataModelDocument = function(filePath, group, opt) {
  1717. if (!group) {
  1718. group = 'Resources';
  1719. }
  1720. if (!this.getPBXGroupByKey(group)) {
  1721. group = this.findPBXGroupKey({ name: group });
  1722. }
  1723. var file = new pbxFile(filePath, opt);
  1724. if (!file || this.hasFile(file.path)) return null;
  1725. file.fileRef = this.generateUuid();
  1726. this.addToPbxGroup(file, group);
  1727. if (!file) return false;
  1728. file.target = opt ? opt.target : undefined;
  1729. file.uuid = this.generateUuid();
  1730. this.addToPbxBuildFileSection(file);
  1731. this.addToPbxSourcesBuildPhase(file);
  1732. file.models = [];
  1733. var currentVersionName;
  1734. var modelFiles = fs.readdirSync(file.path);
  1735. for (var index in modelFiles) {
  1736. var modelFileName = modelFiles[index];
  1737. var modelFilePath = path.join(filePath, modelFileName);
  1738. if (modelFileName == '.xccurrentversion') {
  1739. currentVersionName = plist.readFileSync(modelFilePath)._XCCurrentVersionName;
  1740. continue;
  1741. }
  1742. var modelFile = new pbxFile(modelFilePath);
  1743. modelFile.fileRef = this.generateUuid();
  1744. this.addToPbxFileReferenceSection(modelFile);
  1745. file.models.push(modelFile);
  1746. if (currentVersionName && currentVersionName === modelFileName) {
  1747. file.currentModel = modelFile;
  1748. }
  1749. }
  1750. if (!file.currentModel) {
  1751. file.currentModel = file.models[0];
  1752. }
  1753. this.addToXcVersionGroupSection(file);
  1754. return file;
  1755. }
  1756. pbxProject.prototype.addTargetAttribute = function(prop, value, target) {
  1757. var attributes = this.getFirstProject()['firstProject']['attributes'];
  1758. if (attributes['TargetAttributes'] === undefined) {
  1759. attributes['TargetAttributes'] = {};
  1760. }
  1761. target = target || this.getFirstTarget();
  1762. if (attributes['TargetAttributes'][target.uuid] === undefined) {
  1763. attributes['TargetAttributes'][target.uuid] = {};
  1764. }
  1765. attributes['TargetAttributes'][target.uuid][prop] = value;
  1766. }
  1767. pbxProject.prototype.removeTargetAttribute = function(prop, target) {
  1768. var attributes = this.getFirstProject()['firstProject']['attributes'];
  1769. target = target || this.getFirstTarget();
  1770. if (attributes['TargetAttributes'] &&
  1771. attributes['TargetAttributes'][target.uuid]) {
  1772. delete attributes['TargetAttributes'][target.uuid][prop];
  1773. }
  1774. }
  1775. module.exports = pbxProject;