├── .npmignore ├── index.js ├── .gitignore ├── Makefile ├── AUTHORS ├── test ├── parser │ ├── projects │ │ ├── hash.pbxproj │ │ ├── dots-in-names.pbxproj │ │ ├── section.pbxproj │ │ ├── fail.pbxproj │ │ ├── with_array.pbxproj │ │ ├── expected │ │ │ └── with_array_expected.pbxproj │ │ ├── section-entries.pbxproj │ │ ├── section-split.pbxproj │ │ ├── two-sections.pbxproj │ │ ├── comments.pbxproj │ │ ├── nested-object.pbxproj │ │ ├── header-search.pbxproj │ │ ├── build-config.pbxproj │ │ ├── build-files.pbxproj │ │ └── file-references.pbxproj │ ├── comments.js │ ├── file-references.js │ ├── dotsInNames.js │ ├── header-search.js │ ├── two-sections.js │ ├── section-entries.js │ ├── section.js │ ├── build-config.js │ ├── hash.js │ ├── section-split.js │ └── with_array.js ├── pbxTargetByName.js ├── FrameworkSearchPaths.js ├── pbxItemByComment.js ├── xcode5searchPaths.js ├── LibrarySearchPaths.js ├── HeaderSearchPaths.js ├── fixtures │ ├── buildFiles.json │ └── library-search-paths.json ├── pbxWriter.js ├── addToPbxFileReferenceSection.js ├── addHeaderFile.js ├── removeHeaderFile.js ├── multipleTargets.js ├── addBuildPhase.js ├── removeFramework.js ├── removeSourceFile.js ├── addXCConfigurationList.js ├── addSourceFile.js ├── pbxFile.js ├── removeResourceFile.js ├── addTargetDependency.js ├── addFramework.js ├── addPbxGroup.js ├── addStaticLibrary.js ├── addResourceFile.js └── pbxProject.js ├── lib ├── parseJob.js ├── pbxFile.js ├── parser │ └── pbxproj.pegjs └── pbxWriter.js ├── package.json ├── LICENSE └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | exports.project = require('./lib/pbxProject') 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | .DS_Store 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | tests: 2 | nodeunit test/* test/parser/* 3 | 4 | parser: 5 | pegjs lib/parser/pbxproj.pegjs 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Andrew Lunny (@alunny) 2 | Anis Kadri (@imhotep) 3 | Mike Reinstein (@mreinstein) 4 | Filip Maj (@filmaj) 5 | Brett Rudd (@goya) 6 | Bob Easterday (@bobeast) 7 | -------------------------------------------------------------------------------- /test/parser/projects/hash.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 45; 7 | nonObject = 29B97313FDCFA39411CA2CEF; 8 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 9 | } 10 | -------------------------------------------------------------------------------- /lib/parseJob.js: -------------------------------------------------------------------------------- 1 | // parsing is slow and blocking right now 2 | // so we do it in a separate process 3 | var fs = require('fs'), 4 | parser = require('./parser/pbxproj'), 5 | path = process.argv[2], 6 | fileContents, obj; 7 | 8 | try { 9 | fileContents = fs.readFileSync(path, 'utf-8'), 10 | obj = parser.parse(fileContents) 11 | process.send(obj) 12 | process.exit() 13 | } catch (e) { 14 | process.send(e) 15 | process.exit(1) 16 | } 17 | -------------------------------------------------------------------------------- /test/parser/comments.js: -------------------------------------------------------------------------------- 1 | var PEG = require('pegjs'), 2 | fs = require('fs'), 3 | pbx = fs.readFileSync('test/parser/projects/comments.pbxproj', 'utf-8'), 4 | grammar = fs.readFileSync('lib/parser/pbxproj.pegjs', 'utf-8'), 5 | parser = PEG.buildParser(grammar); 6 | 7 | // Cordova 1.8 has the Apache headers as comments in the pbxproj file 8 | // I DON'T KNOW WHY 9 | exports['should ignore comments outside the main object'] = function (test) { 10 | parser.parse(pbx); 11 | test.done(); 12 | } 13 | -------------------------------------------------------------------------------- /test/parser/file-references.js: -------------------------------------------------------------------------------- 1 | var PEG = require('pegjs'), 2 | fs = require('fs'), 3 | pbx = fs.readFileSync('test/parser/projects/file-references.pbxproj', 'utf-8'), 4 | grammar = fs.readFileSync('lib/parser/pbxproj.pegjs', 'utf-8'), 5 | parser = PEG.buildParser(grammar), 6 | rawProj = parser.parse(pbx), 7 | project = rawProj.project; 8 | 9 | exports['should have a PBXFileReference section'] = function (test) { 10 | test.ok(project.objects['PBXFileReference']); 11 | test.done(); 12 | } 13 | -------------------------------------------------------------------------------- /test/parser/projects/dots-in-names.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | isa = PBXProject; 4 | attributes = { 5 | LastUpgradeCheck = 500; 6 | TargetAttributes = { 7 | 1D6058900D05DD3D006BFB54 = { 8 | DevelopmentTeam = 9KM9BSKFZ3; 9 | SystemCapabilities = { 10 | com.apple.BackgroundModes = { 11 | enabled = 1; 12 | }; 13 | }; 14 | }; 15 | }; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /test/parser/projects/section.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 45; 7 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 8 | objects = { 9 | /* Begin PBXTargetDependency section */ 10 | 301BF551109A68C00062928A /* PBXTargetDependency */ = { 11 | isa = PBXTargetDependency; 12 | name = PhoneGapLib; 13 | targetProxy = 301BF550109A68C00062928A /* PBXContainerItemProxy */; 14 | }; 15 | /* End PBXTargetDependency section */ 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /test/parser/projects/fail.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | THIS SHOULD FAIL TO PARSE 3 | { 4 | archiveVersion = 1; 5 | classes = { 6 | }; 7 | objectVersion = 45; 8 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 9 | objects = { 10 | /* Begin PBXTargetDependency section */ 11 | 301BF551109A68C00062928A /* PBXTargetDependency */ = { 12 | isa = PBXTargetDependency; 13 | name = PhoneGapLib; 14 | targetProxy = 301BF550109A68C00062928A /* PBXContainerItemProxy */; 15 | }; 16 | /* End PBXTargetDependency section */ 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrew Lunny ", 3 | "name": "xcode", 4 | "description": "parser for xcodeproj/project.pbxproj files", 5 | "main":"index.js", 6 | "version": "1.5.2-NativeScript", 7 | "repository": { 8 | "url": "https://github.com/alunny/node-xcode.git" 9 | }, 10 | "engines": { 11 | "node": ">=0.6.7" 12 | }, 13 | "dependencies": { 14 | "pegjs":"0.6.2", 15 | "node-uuid":"1.3.3" 16 | }, 17 | "devDependencies": { 18 | "nodeunit":"0.9.0" 19 | }, 20 | "scripts": { 21 | "test": "node_modules/.bin/nodeunit test/parser test" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012 Andrew Lunny, Adobe Systems 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /test/parser/projects/with_array.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | empties = ( 7 | ); 8 | ARCHS = ( 9 | armv6, 10 | armv7, 11 | ); 12 | files = ( 13 | 1D60589B0D05DD56006BFB54 /* main.m in Sources */, 14 | 1D3623260D0F684500981E51 /* AppDelegate.m in Sources */, 15 | ); 16 | LIBS = ( 17 | "$(SRCROOT)/bestgame/libs/**" 18 | ); 19 | FRAMEWORK_SEARCH_PATHS = ( 20 | "$(inherited)", 21 | "$(SRCROOT)/bestgame/libs/**" 22 | ); 23 | objectVersion = 45; 24 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 25 | } 26 | -------------------------------------------------------------------------------- /test/parser/dotsInNames.js: -------------------------------------------------------------------------------- 1 | var PEG = require('pegjs'), 2 | fs = require('fs'), 3 | pbx = fs.readFileSync('test/parser/projects/dots-in-names.pbxproj', 'utf-8'), 4 | grammar = fs.readFileSync('lib/parser/pbxproj.pegjs', 'utf-8'), 5 | parser = PEG.buildParser(grammar), 6 | rawProj = parser.parse(pbx), 7 | project = rawProj.project; 8 | 9 | exports['should parse com.apple.BackgroundModes'] = function (test) { 10 | var targets = project.attributes.TargetAttributes['1D6058900D05DD3D006BFB54'], 11 | backgroundModes = targets.SystemCapabilities['com.apple.BackgroundModes']; 12 | 13 | test.deepEqual(backgroundModes, {enabled: 1}); 14 | test.done() 15 | } 16 | -------------------------------------------------------------------------------- /test/parser/projects/expected/with_array_expected.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | empties = ( 7 | ); 8 | ARCHS = ( 9 | armv6, 10 | armv7, 11 | ); 12 | files = ( 13 | 1D60589B0D05DD56006BFB54 /* main.m in Sources */, 14 | 1D3623260D0F684500981E51 /* AppDelegate.m in Sources */, 15 | ); 16 | LIBS = ( 17 | "$(SRCROOT)/bestgame/libs/**", 18 | ); 19 | FRAMEWORK_SEARCH_PATHS = ( 20 | "$(inherited)", 21 | "$(SRCROOT)/bestgame/libs/**", 22 | ); 23 | objectVersion = 45; 24 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 25 | } 26 | -------------------------------------------------------------------------------- /test/parser/header-search.js: -------------------------------------------------------------------------------- 1 | var PEG = require('pegjs'), 2 | fs = require('fs'), 3 | pbx = fs.readFileSync('test/parser/projects/header-search.pbxproj', 'utf-8'), 4 | grammar = fs.readFileSync('lib/parser/pbxproj.pegjs', 'utf-8'), 5 | parser = PEG.buildParser(grammar), 6 | rawProj = parser.parse(pbx), 7 | project = rawProj.project; 8 | 9 | exports['should read a decimal value correctly'] = function (test) { 10 | var debug = project.objects['XCBuildConfiguration']['C01FCF4F08A954540054247B'], 11 | hsPaths = debug.buildSettings['HEADER_SEARCH_PATHS'], 12 | expected = '"\\"$(TARGET_BUILD_DIR)/usr/local/lib/include\\""'; 13 | 14 | test.equal(hsPaths[0], expected); 15 | test.done(); 16 | } 17 | -------------------------------------------------------------------------------- /test/parser/two-sections.js: -------------------------------------------------------------------------------- 1 | var PEG = require('pegjs'), 2 | fs = require('fs'), 3 | pbx = fs.readFileSync('test/parser/projects/two-sections.pbxproj', 'utf-8'), 4 | grammar = fs.readFileSync('lib/parser/pbxproj.pegjs', 'utf-8'), 5 | parser = PEG.buildParser(grammar), 6 | rawProj = parser.parse(pbx), 7 | project = rawProj.project; 8 | 9 | exports['should parse a project with two sections'] = function (test) { 10 | // if it gets this far it's worked 11 | test.done(); 12 | } 13 | 14 | exports['should have both sections on the project object'] = function (test) { 15 | test.ok(project.objects['PBXTargetDependency']); 16 | test.ok(project.objects['PBXSourcesBuildPhase']); 17 | test.done(); 18 | } 19 | -------------------------------------------------------------------------------- /test/parser/projects/section-entries.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 45; 7 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 8 | objects = { 9 | /* Begin PBXVariantGroup section */ 10 | 1F766FDC13BBADB100FB74C0 /* Localizable.strings */ = { 11 | isa = PBXVariantGroup; 12 | children = ( 13 | 1F766FDD13BBADB100FB74C0 /* en */, 14 | ); 15 | name = Localizable.strings; 16 | sourceTree = ""; 17 | }; 18 | 1F766FDF13BBADB100FB74C0 /* Localizable.strings */ = { 19 | isa = PBXVariantGroup; 20 | children = ( 21 | 1F766FE013BBADB100FB74C0 /* es */, 22 | ); 23 | name = Localizable.strings; 24 | sourceTree = ""; 25 | }; 26 | /* End PBXVariantGroup section */ 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /test/parser/projects/section-split.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 45; 7 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 8 | objects = { 9 | /* Begin PBXTargetDependency section */ 10 | 301BF551109A68C00062928A /* PBXTargetDependency */ = { 11 | isa = PBXTargetDependency; 12 | name = PhoneGapLib; 13 | targetProxy = 301BF550109A68C00062928A /* PBXContainerItemProxy */; 14 | }; 15 | /* End PBXTargetDependency section */ 16 | 17 | /* Begin PBXTargetDependency section */ 18 | 45FDD1944D304A9F96DF3AC6 /* PBXTargetDependency */ = { 19 | isa = PBXTargetDependency; 20 | name = SecondLib; 21 | targetProxy = 301BF550109A68C00062928A /* PBXContainerItemProxy */; 22 | }; 23 | /* End PBXTargetDependency section */ 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /test/pbxTargetByName.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | proj = new pbx('.'); 5 | 6 | function cleanHash() { 7 | return JSON.parse(fullProjectStr); 8 | } 9 | 10 | exports.setUp = function (callback) { 11 | proj.hash = cleanHash(); 12 | callback(); 13 | } 14 | 15 | exports.pbxTargetByName = { 16 | 'should return PBXNativeTarget': function (test) { 17 | var pbxTarget = proj.pbxTargetByName('KitchenSinktablet'); 18 | 19 | test.ok(pbxTarget); 20 | test.equals(pbxTarget.isa, 'PBXNativeTarget'); 21 | test.done() 22 | }, 23 | 'should return null when PBXNativeTarget not found': function (test) { 24 | var pbxTarget = proj.pbxTargetByName('Invalid'); 25 | 26 | test.equal(pbxTarget, null); 27 | test.done() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/parser/projects/two-sections.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 45; 7 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 8 | objects = { 9 | /* Begin PBXTargetDependency section */ 10 | 301BF551109A68C00062928A /* PBXTargetDependency */ = { 11 | isa = PBXTargetDependency; 12 | name = PhoneGapLib; 13 | targetProxy = 301BF550109A68C00062928A /* PBXContainerItemProxy */; 14 | }; 15 | /* End PBXTargetDependency section */ 16 | 17 | /* Begin PBXSourcesBuildPhase section */ 18 | 1D60588E0D05DD3D006BFB54 /* Sources */ = { 19 | isa = PBXSourcesBuildPhase; 20 | buildActionMask = 2147483647; 21 | files = ( 22 | 1D60589B0D05DD56006BFB54 /* main.m in Sources */, 23 | 1D3623260D0F684500981E51 /* AppDelegate.m in Sources */, 24 | ); 25 | runOnlyForDeploymentPostprocessing = 0; 26 | }; 27 | /* End PBXSourcesBuildPhase section */ 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /test/parser/section-entries.js: -------------------------------------------------------------------------------- 1 | var PEG = require('pegjs'), 2 | fs = require('fs'), 3 | pbx = fs.readFileSync('test/parser/projects/section-entries.pbxproj', 'utf-8'), 4 | grammar = fs.readFileSync('lib/parser/pbxproj.pegjs', 'utf-8'), 5 | parser = PEG.buildParser(grammar), 6 | rawProj = parser.parse(pbx), 7 | project = rawProj.project; 8 | 9 | exports['should have a PBXVariantGroup section'] = function (test) { 10 | test.ok(project.objects['PBXVariantGroup']); 11 | test.done(); 12 | } 13 | 14 | exports['should have two children for PBXVariantGroup'] = function (test) { 15 | test.ok(project.objects['PBXVariantGroup']['1F766FDF13BBADB100FB74C0']); 16 | test.ok(project.objects['PBXVariantGroup']['1F766FDC13BBADB100FB74C0']); 17 | test.done(); 18 | } 19 | 20 | exports['should store quote-surround values correctly'] = function (test) { 21 | var localizable = project.objects['PBXVariantGroup']['1F766FDF13BBADB100FB74C0']; 22 | 23 | test.equal(localizable.sourceTree, '""'); 24 | test.done(); 25 | } 26 | -------------------------------------------------------------------------------- /test/parser/projects/comments.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | /* 3 | # 4 | # Licensed to the Apache Software Foundation (ASF) under one 5 | # or more contributor license agreements. See the NOTICE file 6 | # distributed with this work for additional information 7 | # regarding copyright ownership. The ASF licenses this file 8 | # to you under the Apache License, Version 2.0 (the 9 | # "License"); you may not use this file except in compliance 10 | # with the License. You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, 15 | # software distributed under the License is distributed on an 16 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | # KIND, either express or implied. See the License for the 18 | # specific language governing permissions and limitations 19 | # under the License. 20 | # 21 | */ 22 | { 23 | archiveVersion = 1; 24 | classes = { 25 | }; 26 | objectVersion = 45; 27 | nonObject = 29B97313FDCFA39411CA2CEF; 28 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 29 | } 30 | 31 | -------------------------------------------------------------------------------- /test/parser/projects/nested-object.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 45; 7 | objects = { 8 | /* Begin PBXProject section */ 9 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 10 | isa = PBXProject; 11 | buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "KitchenSinktablet" */; 12 | compatibilityVersion = "Xcode 3.1"; 13 | developmentRegion = English; 14 | hasScannedForEncodings = 1; 15 | knownRegions = ( 16 | English, 17 | Japanese, 18 | French, 19 | German, 20 | en, 21 | es, 22 | ); 23 | mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; 24 | projectDirPath = ""; 25 | projectReferences = ( 26 | { 27 | ProductGroup = 301BF52E109A57CC0062928A /* Products */; 28 | ProjectRef = 301BF52D109A57CC0062928A /* PhoneGapLib.xcodeproj */; 29 | }, 30 | ); 31 | projectRoot = ""; 32 | targets = ( 33 | 1D6058900D05DD3D006BFB54 /* KitchenSinktablet */, 34 | ); 35 | }; 36 | /* End PBXProject section */ 37 | }; 38 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 39 | } 40 | -------------------------------------------------------------------------------- /test/parser/section.js: -------------------------------------------------------------------------------- 1 | var PEG = require('pegjs'), 2 | fs = require('fs'), 3 | pbx = fs.readFileSync('test/parser/projects/section.pbxproj', 'utf-8'), 4 | grammar = fs.readFileSync('lib/parser/pbxproj.pegjs', 'utf-8'), 5 | parser = PEG.buildParser(grammar), 6 | rawProj = parser.parse(pbx), 7 | project = rawProj.project; 8 | 9 | exports['should have a PBXTargetDependency section'] = function (test) { 10 | test.ok(project.objects['PBXTargetDependency']); 11 | test.done(); 12 | } 13 | 14 | exports['should have the right child of PBXTargetDependency section'] = function (test) { 15 | test.ok(project.objects['PBXTargetDependency']['301BF551109A68C00062928A']); 16 | test.done(); 17 | } 18 | 19 | exports['should have the right properties on the dependency'] = function (test) { 20 | var dependency = project.objects['PBXTargetDependency']['301BF551109A68C00062928A']; 21 | 22 | test.equal(dependency.isa, 'PBXTargetDependency') 23 | test.equal(dependency.name, 'PhoneGapLib') 24 | test.equal(dependency.targetProxy, '301BF550109A68C00062928A') 25 | test.equal(dependency['targetProxy_comment'], 'PBXContainerItemProxy') 26 | 27 | test.done(); 28 | } 29 | -------------------------------------------------------------------------------- /test/parser/build-config.js: -------------------------------------------------------------------------------- 1 | var PEG = require('pegjs'), 2 | fs = require('fs'), 3 | pbx = fs.readFileSync('test/parser/projects/build-config.pbxproj', 'utf-8'), 4 | grammar = fs.readFileSync('lib/parser/pbxproj.pegjs', 'utf-8'), 5 | parser = PEG.buildParser(grammar), 6 | rawProj = parser.parse(pbx), 7 | util = require('util'), 8 | project = rawProj.project; 9 | 10 | exports['should parse the build config section'] = function (test) { 11 | // if it gets this far it's worked 12 | test.done(); 13 | } 14 | 15 | exports['should read a decimal value correctly'] = function (test) { 16 | var xcbConfig = project.objects['XCBuildConfiguration'], 17 | debugSettings = xcbConfig['1D6058950D05DD3E006BFB54'].buildSettings; 18 | 19 | test.strictEqual(debugSettings['IPHONEOS_DEPLOYMENT_TARGET'], '3.0'); 20 | test.done(); 21 | } 22 | 23 | exports['should read an escaped value correctly'] = function (test) { 24 | var xcbConfig = project.objects['XCBuildConfiguration'], 25 | debugSettings = xcbConfig['C01FCF4F08A954540054247B'].buildSettings, 26 | expt = '"\\"$(PHONEGAPLIB)/Classes/JSON\\" \\"$(PHONEGAPLIB)/Classes\\""'; 27 | 28 | test.strictEqual(debugSettings['USER_HEADER_SEARCH_PATHS'], expt); 29 | test.done(); 30 | } 31 | -------------------------------------------------------------------------------- /test/parser/hash.js: -------------------------------------------------------------------------------- 1 | var PEG = require('pegjs'), 2 | fs = require('fs'), 3 | pbx = fs.readFileSync('test/parser/projects/hash.pbxproj', 'utf-8'), 4 | grammar = fs.readFileSync('lib/parser/pbxproj.pegjs', 'utf-8'), 5 | parser = PEG.buildParser(grammar), 6 | rawProj = parser.parse(pbx), 7 | project = rawProj.project; 8 | 9 | exports['should have the top-line comment in place'] = function (test) { 10 | test.equals(rawProj.headComment, '!$*UTF8*$!'); 11 | test.done() 12 | } 13 | 14 | exports['should parse a numeric attribute'] = function (test) { 15 | test.strictEqual(project.archiveVersion, 1); 16 | test.strictEqual(project.objectVersion, 45); 17 | test.done() 18 | } 19 | 20 | exports['should parse an empty object'] = function (test) { 21 | var empty = project.classes; 22 | test.equal(Object.keys(empty).length, 0); 23 | test.done() 24 | } 25 | 26 | exports['should split out properties and comments'] = function (test) { 27 | test.equal(project.rootObject, '29B97313FDCFA39411CA2CEA'); 28 | test.equal(project['rootObject_comment'], 'Project object'); 29 | test.done(); 30 | } 31 | 32 | exports['should parse non-commented hash things'] = function (test) { 33 | test.equal(project.nonObject, '29B97313FDCFA39411CA2CEF'); 34 | test.done(); 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-xcode 2 | 3 | > parser/toolkit for xcodeproj project files 4 | 5 | Allows you to edit xcodeproject files and write them back out. 6 | 7 | ## Example 8 | 9 | // API is a bit wonky right now 10 | var xcode = require('xcode'), 11 | fs = require('fs'), 12 | projectPath = 'myproject.xcodeproj/project.pbxproj', 13 | myProj = xcode.project(projectPath); 14 | 15 | // parsing is async, in a different process 16 | myProj.parse(function (err) { 17 | myProj.addHeaderFile('foo.h'); 18 | myProj.addSourceFile('foo.m'); 19 | myProj.addFramework('FooKit.framework'); 20 | 21 | fs.writeFileSync(projectPath, myProj.writeSync()); 22 | console.log('new project written'); 23 | }); 24 | 25 | ## Working on the parser 26 | 27 | If there's a problem parsing, you will want to edit the grammar under 28 | `lib/parser/pbxproj.pegjs`. You can test it online with the PEGjs online thingy 29 | at http://pegjs.majda.cz/online - I have had some mixed results though. 30 | 31 | Tests under the `test/parser` directory will compile the parser from the 32 | grammar. Other tests will use the prebuilt parser (`lib/parser/pbxproj.js`). 33 | 34 | To rebuild the parser js file after editing the grammar, run: 35 | 36 | ./node_modules/.bin/pegjs lib/parser/pbxproj.pegjs 37 | 38 | (easier if `./node_modules/.bin` is in your path) 39 | 40 | ## License 41 | 42 | MIT 43 | -------------------------------------------------------------------------------- /test/parser/section-split.js: -------------------------------------------------------------------------------- 1 | var PEG = require('pegjs'), 2 | fs = require('fs'), 3 | pbx = fs.readFileSync('test/parser/projects/section-split.pbxproj', 'utf-8'), 4 | grammar = fs.readFileSync('lib/parser/pbxproj.pegjs', 'utf-8'), 5 | parser = PEG.buildParser(grammar), 6 | rawProj = parser.parse(pbx), 7 | project = rawProj.project; 8 | 9 | exports['should have a PBXTargetDependency section'] = function (test) { 10 | test.ok(project.objects['PBXTargetDependency']); 11 | test.done(); 12 | } 13 | 14 | exports['should have the right child of PBXTargetDependency section'] = function (test) { 15 | test.ok(project.objects['PBXTargetDependency']['301BF551109A68C00062928A']); 16 | test.done(); 17 | } 18 | 19 | exports['should have the right properties on the dependency'] = function (test) { 20 | var dependency = project.objects['PBXTargetDependency']['301BF551109A68C00062928A']; 21 | 22 | test.equal(dependency.isa, 'PBXTargetDependency') 23 | test.equal(dependency.name, 'PhoneGapLib') 24 | test.equal(dependency.targetProxy, '301BF550109A68C00062928A') 25 | test.equal(dependency['targetProxy_comment'], 'PBXContainerItemProxy') 26 | 27 | test.done(); 28 | } 29 | 30 | exports['should merge two PBXTargetDependency sections'] = function (test) { 31 | test.ok(project.objects['PBXTargetDependency']['301BF551109A68C00062928A']); 32 | test.ok(project.objects['PBXTargetDependency']['45FDD1944D304A9F96DF3AC6']); 33 | test.done(); 34 | } 35 | 36 | -------------------------------------------------------------------------------- /test/parser/with_array.js: -------------------------------------------------------------------------------- 1 | var PEG = require('pegjs'), 2 | fs = require('fs'), 3 | pbx = fs.readFileSync('test/parser/projects/with_array.pbxproj', 'utf-8'), 4 | grammar = fs.readFileSync('lib/parser/pbxproj.pegjs', 'utf-8'), 5 | parser = PEG.buildParser(grammar), 6 | rawProj = parser.parse(pbx), 7 | project = rawProj.project; 8 | 9 | exports['should parse arrays with commented entries'] = function (test) { 10 | test.ok(project.files instanceof Array); 11 | test.equal(project.files.length, 2); 12 | test.done() 13 | } 14 | 15 | exports['should parse arrays with uncommented entries'] = function (test) { 16 | test.ok(project.ARCHS instanceof Array); 17 | test.equal(project.ARCHS.length, 2); 18 | test.done() 19 | } 20 | 21 | exports['should parse empty arrays'] = function (test) { 22 | test.ok(project.empties instanceof Array); 23 | test.equal(project.empties.length, 0); 24 | test.done(); 25 | } 26 | 27 | exports['should be correct ordered'] = function (test) { 28 | var archs = project.ARCHS; 29 | test.equal(archs[0], 'armv6'); 30 | test.equal(archs[1], 'armv7'); 31 | test.done(); 32 | } 33 | 34 | exports['should parse values and comments correctly'] = function (test) { 35 | var appDelegate = project.files[1] 36 | test.equal(appDelegate.value, '1D3623260D0F684500981E51') 37 | test.equal(appDelegate.comment, 'AppDelegate.m in Sources') 38 | test.done() 39 | } 40 | -------------------------------------------------------------------------------- /test/parser/projects/header-search.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | /* Begin XCBuildConfiguration section */ 9 | C01FCF4F08A954540054247B /* Debug */ = { 10 | isa = XCBuildConfiguration; 11 | baseConfigurationReference = 307D28A1123043350040C0FA /* CordovaBuildSettings.xcconfig */; 12 | buildSettings = { 13 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 14 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 15 | GCC_C_LANGUAGE_STANDARD = c99; 16 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 17 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 18 | GCC_WARN_UNUSED_VARIABLE = YES; 19 | HEADER_SEARCH_PATHS = ( 20 | "\"$(TARGET_BUILD_DIR)/usr/local/lib/include\"", 21 | "\"$(OBJROOT)/UninstalledProducts/include\"", 22 | "\"$(BUILT_PRODUCTS_DIR)\"", 23 | ); 24 | IPHONEOS_DEPLOYMENT_TARGET = 4.2; 25 | OTHER_LDFLAGS = ( 26 | "-weak_framework", 27 | CoreFoundation, 28 | "-weak_framework", 29 | UIKit, 30 | "-weak_framework", 31 | AVFoundation, 32 | "-weak_framework", 33 | CoreMedia, 34 | "-weak-lSystem", 35 | "-all_load", 36 | "-Obj-C", 37 | ); 38 | SDKROOT = iphoneos; 39 | SKIP_INSTALL = NO; 40 | USER_HEADER_SEARCH_PATHS = ""; 41 | }; 42 | name = Debug; 43 | }; 44 | /* End XCBuildConfiguration section */ 45 | }; 46 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 47 | } 48 | -------------------------------------------------------------------------------- /test/FrameworkSearchPaths.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | pbxFile = require('../lib/pbxFile'), 5 | proj = new pbx('.'); 6 | 7 | var pbxFile = { 8 | path:'some/path/include', 9 | dirname: 'some/path', 10 | customFramework: true 11 | } 12 | function cleanHash() { 13 | return JSON.parse(fullProjectStr); 14 | } 15 | 16 | exports.setUp = function (callback) { 17 | proj.hash = cleanHash(); 18 | callback(); 19 | } 20 | 21 | var PRODUCT_NAME = '"KitchenSinktablet"'; 22 | 23 | exports.addAndRemoveToFromFrameworkSearchPaths = { 24 | 'add should add the path to each configuration section':function(test) { 25 | proj.addToFrameworkSearchPaths(pbxFile); 26 | var config = proj.pbxXCBuildConfigurationSection(); 27 | for (var ref in config) { 28 | if (ref.indexOf('_comment') > -1 || config[ref].buildSettings.PRODUCT_NAME != PRODUCT_NAME) continue; 29 | var lib = config[ref].buildSettings.FRAMEWORK_SEARCH_PATHS; 30 | test.ok(lib[1].indexOf('some/path') > -1); 31 | } 32 | test.done(); 33 | }, 34 | 'remove should remove from the path to each configuration section':function(test) { 35 | proj.addToFrameworkSearchPaths(pbxFile); 36 | proj.removeFromFrameworkSearchPaths(pbxFile); 37 | var config = proj.pbxXCBuildConfigurationSection(); 38 | for (var ref in config) { 39 | if (ref.indexOf('_comment') > -1 || config[ref].buildSettings.PRODUCT_NAME != PRODUCT_NAME) continue; 40 | var lib = config[ref].buildSettings.FRAMEWORK_SEARCH_PATHS; 41 | test.ok(lib.length === 1); 42 | test.ok(lib[0].indexOf('some/path') == -1); 43 | } 44 | test.done(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/pbxItemByComment.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | proj = new pbx('.'); 5 | 6 | function cleanHash() { 7 | return JSON.parse(fullProjectStr); 8 | } 9 | 10 | exports.setUp = function (callback) { 11 | proj.hash = cleanHash(); 12 | callback(); 13 | } 14 | 15 | exports.pbxItemByComment = { 16 | 'should return PBXTargetDependency': function (test) { 17 | var pbxItem = proj.pbxItemByComment('PBXTargetDependency', 'PBXTargetDependency'); 18 | 19 | test.ok(pbxItem); 20 | test.equals(pbxItem.isa, 'PBXTargetDependency'); 21 | test.done() 22 | }, 23 | 'should return PBXContainerItemProxy': function (test) { 24 | var pbxItem = proj.pbxItemByComment('libPhoneGap.a', 'PBXReferenceProxy'); 25 | 26 | test.ok(pbxItem); 27 | test.equals(pbxItem.isa, 'PBXReferenceProxy'); 28 | test.done() 29 | }, 30 | 'should return PBXResourcesBuildPhase': function (test) { 31 | var pbxItem = proj.pbxItemByComment('Resources', 'PBXResourcesBuildPhase'); 32 | 33 | test.ok(pbxItem); 34 | test.equals(pbxItem.isa, 'PBXResourcesBuildPhase'); 35 | test.done() 36 | }, 37 | 'should return PBXShellScriptBuildPhase': function (test) { 38 | var pbxItem = proj.pbxItemByComment('Touch www folder', 'PBXShellScriptBuildPhase'); 39 | 40 | test.ok(pbxItem); 41 | test.equals(pbxItem.isa, 'PBXShellScriptBuildPhase'); 42 | test.done() 43 | }, 44 | 'should return null when PBXNativeTarget not found': function (test) { 45 | var pbxItem = proj.pbxItemByComment('Invalid', 'PBXTargetDependency'); 46 | 47 | test.equal(pbxItem, null); 48 | test.done() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/xcode5searchPaths.js: -------------------------------------------------------------------------------- 1 | var xcode5proj = require('./fixtures/library-search-paths') 2 | xcode5projStr = JSON.stringify(xcode5proj), 3 | pbx = require('../lib/pbxProject'), 4 | pbxFile = require('../lib/pbxFile'), 5 | proj = new pbx('.'), 6 | libPoop = { path: 'some/path/poop.a' }; 7 | 8 | function cleanHash() { 9 | return JSON.parse(xcode5projStr); 10 | } 11 | 12 | exports.setUp = function (callback) { 13 | proj.hash = cleanHash(); 14 | callback(); 15 | } 16 | 17 | var PRODUCT_NAME = '"$(TARGET_NAME)"'; 18 | 19 | exports.addAndRemoveToFromLibrarySearchPaths = { 20 | 'add should add the path to each configuration section':function(test) { 21 | var expected = '"\\"$(SRCROOT)/$(TARGET_NAME)/some/path\\""', 22 | config = proj.pbxXCBuildConfigurationSection(), 23 | ref, lib, refSettings; 24 | 25 | proj.addToLibrarySearchPaths(libPoop); 26 | 27 | for (ref in config) { 28 | if (ref.indexOf('_comment') > -1) 29 | continue; 30 | 31 | refSettings = config[ref].buildSettings; 32 | 33 | if (refSettings.PRODUCT_NAME != PRODUCT_NAME) 34 | continue; 35 | 36 | lib = refSettings.LIBRARY_SEARCH_PATHS; 37 | test.equal(lib[1], expected); 38 | } 39 | test.done(); 40 | }, 41 | 42 | 'remove should remove from the path to each configuration section':function(test) { 43 | var config, ref, lib; 44 | 45 | proj.addToLibrarySearchPaths(libPoop); 46 | proj.removeFromLibrarySearchPaths(libPoop); 47 | 48 | config = proj.pbxXCBuildConfigurationSection(); 49 | for (ref in config) { 50 | if (ref.indexOf('_comment') > -1 || config[ref].buildSettings.PRODUCT_NAME != PRODUCT_NAME) continue; 51 | 52 | lib = config[ref].buildSettings.LIBRARY_SEARCH_PATHS; 53 | test.ok(lib.length === 1); 54 | test.ok(lib[0].indexOf('$(SRCROOT)/KitchenSinktablet/some/path') == -1); 55 | } 56 | test.done(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/LibrarySearchPaths.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | pbxFile = require('../lib/pbxFile'), 5 | proj = new pbx('.'); 6 | 7 | function cleanHash() { 8 | return JSON.parse(fullProjectStr); 9 | } 10 | 11 | exports.setUp = function (callback) { 12 | proj.hash = cleanHash(); 13 | callback(); 14 | } 15 | 16 | var PRODUCT_NAME = '"KitchenSinktablet"'; 17 | 18 | exports.addAndRemoveToFromLibrarySearchPaths = { 19 | 'add should add the path to each configuration section':function(test) { 20 | proj.addToLibrarySearchPaths({ 21 | path:'some/path/poop.a' 22 | }); 23 | var config = proj.pbxXCBuildConfigurationSection(); 24 | for (var ref in config) { 25 | if (ref.indexOf('_comment') > -1 || config[ref].buildSettings.PRODUCT_NAME != PRODUCT_NAME) continue; 26 | var lib = config[ref].buildSettings.LIBRARY_SEARCH_PATHS; 27 | test.ok(lib[1].indexOf('$(SRCROOT)/KitchenSinktablet/some/path') > -1); 28 | } 29 | test.done(); 30 | }, 31 | 'add should not mangle string arguments and add to each config section':function(test) { 32 | var libPath = '../../some/path'; 33 | proj.addToLibrarySearchPaths(libPath); 34 | var config = proj.pbxXCBuildConfigurationSection(); 35 | for (var ref in config) { 36 | if (ref.indexOf('_comment') > -1 || config[ref].buildSettings.PRODUCT_NAME != PRODUCT_NAME) continue; 37 | var lib = config[ref].buildSettings.LIBRARY_SEARCH_PATHS; 38 | test.ok(lib[1].indexOf(libPath) > -1); 39 | } 40 | test.done(); 41 | }, 42 | 'remove should remove from the path to each configuration section':function(test) { 43 | var libPath = 'some/path/poop.a'; 44 | proj.addToLibrarySearchPaths({ 45 | path:libPath 46 | }); 47 | proj.removeFromLibrarySearchPaths({ 48 | path:libPath 49 | }); 50 | var config = proj.pbxXCBuildConfigurationSection(); 51 | for (var ref in config) { 52 | if (ref.indexOf('_comment') > -1 || config[ref].buildSettings.PRODUCT_NAME != PRODUCT_NAME) continue; 53 | var lib = config[ref].buildSettings.LIBRARY_SEARCH_PATHS; 54 | test.ok(lib.length === 1); 55 | test.ok(lib[0].indexOf('$(SRCROOT)/KitchenSinktablet/some/path') == -1); 56 | } 57 | test.done(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/HeaderSearchPaths.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | pbxFile = require('../lib/pbxFile'), 5 | proj = new pbx('.'); 6 | 7 | function cleanHash() { 8 | return JSON.parse(fullProjectStr); 9 | } 10 | 11 | exports.setUp = function (callback) { 12 | proj.hash = cleanHash(); 13 | callback(); 14 | } 15 | 16 | var PRODUCT_NAME = '"KitchenSinktablet"'; 17 | 18 | exports.addAndRemoveToFromHeaderSearchPaths = { 19 | 'add should add the path to each configuration section':function(test) { 20 | proj.addToHeaderSearchPaths({ 21 | path:'some/path/include' 22 | }); 23 | var config = proj.pbxXCBuildConfigurationSection(); 24 | for (var ref in config) { 25 | if (ref.indexOf('_comment') > -1 || config[ref].buildSettings.PRODUCT_NAME != PRODUCT_NAME) continue; 26 | var lib = config[ref].buildSettings.HEADER_SEARCH_PATHS; 27 | test.ok(lib[1].indexOf('$(SRCROOT)/KitchenSinktablet/some/path') > -1); 28 | } 29 | test.done(); 30 | }, 31 | 'add should not mangle string arguments and add to each config section':function(test) { 32 | var includePath = '../../some/path'; 33 | proj.addToHeaderSearchPaths(includePath); 34 | var config = proj.pbxXCBuildConfigurationSection(); 35 | for (var ref in config) { 36 | if (ref.indexOf('_comment') > -1 || config[ref].buildSettings.PRODUCT_NAME != PRODUCT_NAME) continue; 37 | var lib = config[ref].buildSettings.HEADER_SEARCH_PATHS; 38 | test.ok(lib[1].indexOf(includePath) > -1); 39 | } 40 | test.done(); 41 | }, 42 | 'remove should remove from the path to each configuration section':function(test) { 43 | var libPath = 'some/path/include'; 44 | proj.addToHeaderSearchPaths({ 45 | path:libPath 46 | }); 47 | proj.removeFromHeaderSearchPaths({ 48 | path:libPath 49 | }); 50 | var config = proj.pbxXCBuildConfigurationSection(); 51 | for (var ref in config) { 52 | if (ref.indexOf('_comment') > -1 || config[ref].buildSettings.PRODUCT_NAME != PRODUCT_NAME) continue; 53 | var lib = config[ref].buildSettings.HEADER_SEARCH_PATHS; 54 | test.ok(lib.length === 1); 55 | test.ok(lib[0].indexOf('$(SRCROOT)/KitchenSinktablet/some/path/include') == -1); 56 | } 57 | test.done(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/fixtures/buildFiles.json: -------------------------------------------------------------------------------- 1 | {"project":{"archiveVersion":1,"classes":{},"objectVersion":45,"objects":{"XCBuildConfiguration":{"1D6058940D05DD3E006BFB54":{"isa":"XCBuildConfiguration","buildSettings":{"ALWAYS_SEARCH_USER_PATHS":"NO","ARCHS":["armv6","armv7"],"COPY_PHASE_STRIP":"NO","GCC_DYNAMIC_NO_PIC":"NO","GCC_OPTIMIZATION_LEVEL":0,"GCC_PRECOMPILE_PREFIX_HEADER":"YES","GCC_PREFIX_HEADER":"\"KitchenSinktablet-Prefix.pch\"","INFOPLIST_FILE":"\"KitchenSinktablet-Info.plist\"","IPHONEOS_DEPLOYMENT_TARGET":"3.0","ONLY_ACTIVE_ARCH":"NO","PRODUCT_NAME":"\"KitchenSinktablet\"","TARGETED_DEVICE_FAMILY":"\"1,2\""},"name":"Debug"},"1D6058940D05DD3E006BFB54_comment":"Debug","1D6058950D05DD3E006BFB54":{"isa":"XCBuildConfiguration","buildSettings":{"ALWAYS_SEARCH_USER_PATHS":"NO","ARCHS":["armv6","armv7"],"COPY_PHASE_STRIP":"YES","GCC_PRECOMPILE_PREFIX_HEADER":"YES","GCC_PREFIX_HEADER":"\"KitchenSinktablet-Prefix.pch\"","INFOPLIST_FILE":"\"KitchenSinktablet-Info.plist\"","IPHONEOS_DEPLOYMENT_TARGET":"3.0","ONLY_ACTIVE_ARCH":"NO","PRODUCT_NAME":"\"KitchenSinktablet\"","TARGETED_DEVICE_FAMILY":"\"1,2\""},"name":"Release"},"1D6058950D05DD3E006BFB54_comment":"Release","C01FCF4F08A954540054247B":{"isa":"XCBuildConfiguration","baseConfigurationReference":"307D28A1123043350040C0FA","baseConfigurationReference_comment":"PhoneGapBuildSettings.xcconfig","buildSettings":{"ARCHS":"\"$(ARCHS_STANDARD_32_BIT)\"","\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\"":"\"iPhone Distribution\"","GCC_C_LANGUAGE_STANDARD":"c99","GCC_VERSION":"com.apple.compilers.llvmgcc42","GCC_WARN_ABOUT_RETURN_TYPE":"YES","GCC_WARN_UNUSED_VARIABLE":"YES","IPHONEOS_DEPLOYMENT_TARGET":"3.0","OTHER_LDFLAGS":["\"-weak_framework\"","UIKit","\"-weak_framework\"","AVFoundation","\"-weak_framework\"","CoreMedia","\"-weak_library\"","/usr/lib/libSystem.B.dylib","\"-all_load\"","\"-Obj-C\""],"PREBINDING":"NO","SDKROOT":"iphoneos","SKIP_INSTALL":"NO","USER_HEADER_SEARCH_PATHS":"\"\"$(PHONEGAPLIB)/Classes/JSON\" \"$(PHONEGAPLIB)/Classes\"\""},"name":"Debug"},"C01FCF4F08A954540054247B_comment":"Debug","C01FCF5008A954540054247B":{"isa":"XCBuildConfiguration","baseConfigurationReference":"307D28A1123043350040C0FA","baseConfigurationReference_comment":"PhoneGapBuildSettings.xcconfig","buildSettings":{"ARCHS":"\"$(ARCHS_STANDARD_32_BIT)\"","\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\"":"\"iPhone Distribution\"","GCC_C_LANGUAGE_STANDARD":"c99","GCC_VERSION":"com.apple.compilers.llvmgcc42","GCC_WARN_ABOUT_RETURN_TYPE":"YES","GCC_WARN_UNUSED_VARIABLE":"YES","IPHONEOS_DEPLOYMENT_TARGET":"3.0","OTHER_LDFLAGS":["\"-weak_framework\"","UIKit","\"-weak_framework\"","AVFoundation","\"-weak_framework\"","CoreMedia","\"-weak_library\"","/usr/lib/libSystem.B.dylib","\"-all_load\"","\"-Obj-C\""],"PREBINDING":"NO","SDKROOT":"iphoneos","SKIP_INSTALL":"NO","USER_HEADER_SEARCH_PATHS":"\"\"$(PHONEGAPLIB)/Classes/JSON\" \"$(PHONEGAPLIB)/Classes\"\""},"name":"Release"},"C01FCF5008A954540054247B_comment":"Release"}},"rootObject":"29B97313FDCFA39411CA2CEA","rootObject_comment":"Project object"},"headComment":"!$*UTF8*$!"} 2 | -------------------------------------------------------------------------------- /test/pbxWriter.js: -------------------------------------------------------------------------------- 1 | var pbx = require('../lib/pbxProject'), 2 | fs = require('fs'), 3 | myProj; 4 | 5 | function testProjectContents(filename, test, expectedFilename) { 6 | var myProj = new pbx(filename); 7 | 8 | var content; 9 | if (expectedFilename) { 10 | content = fs.readFileSync(expectedFilename, 'utf-8'); 11 | } else { 12 | content = fs.readFileSync(filename, 'utf-8'); 13 | } 14 | // normalize tabs vs strings 15 | content = content.replace(/ /g, '\t'); 16 | 17 | myProj.parse(function (err, projHash) { 18 | var written = myProj.writeSync(); 19 | 20 | test.equal(content, written); 21 | test.done(); 22 | }); 23 | } 24 | 25 | // for debugging failing tests 26 | function testContentsInDepth(filename, test) { 27 | var myProj = new pbx(filename), 28 | content = fs.readFileSync(filename, 'utf-8'); 29 | 30 | // normalize tabs vs strings 31 | content = content.replace(/ /g, '\t'); 32 | 33 | myProj.parse(function (err, projHash) { 34 | var written = myProj.writeSync(), 35 | writtenLines = written.split('\n') 36 | contentLines = content.split('\n') 37 | 38 | test.equal(writtenLines.length, contentLines.length); 39 | 40 | for (var i=0; i"'); 28 | test.equal(myProj.pbxFileReferenceSection()[file.fileRef].fileEncoding, 4); 29 | test.equal(myProj.pbxFileReferenceSection()[file.fileRef + "_comment"], 'file.m'); 30 | test.done(); 31 | }, 32 | 'should add file with preset explicitFileType to fileReferenceSection correctly': function (test) { 33 | var appexFile = { fileRef: myProj.generateUuid(), isa: 'PBXFileReference', explicitFileType: '"wrapper.app-extension"', path: "WatchKit Extension.appex"}; 34 | 35 | myProj.addToPbxFileReferenceSection(appexFile) 36 | 37 | test.equal(myProj.pbxFileReferenceSection()[appexFile.fileRef].isa, 'PBXFileReference'); 38 | test.equal(myProj.pbxFileReferenceSection()[appexFile.fileRef].explicitFileType, '"wrapper.app-extension"'); 39 | test.equal(myProj.pbxFileReferenceSection()[appexFile.fileRef].path, '"WatchKit Extension.appex"'); 40 | test.done(); 41 | }, 42 | 'should add file with preset includeInIndex to fileReferenceSection correctly': function (test) { 43 | var appexFile = { fileRef: myProj.generateUuid(), isa: 'PBXFileReference', includeInIndex: 0, path: "WatchKit Extension.appex"}; 44 | 45 | myProj.addToPbxFileReferenceSection(appexFile) 46 | 47 | test.equal(myProj.pbxFileReferenceSection()[appexFile.fileRef].isa, 'PBXFileReference'); 48 | test.equal(myProj.pbxFileReferenceSection()[appexFile.fileRef].includeInIndex, 0); 49 | test.equal(myProj.pbxFileReferenceSection()[appexFile.fileRef].path, '"WatchKit Extension.appex"'); 50 | test.done(); 51 | }, 52 | 'should add file with preset sourceTree to fileReferenceSection correctly': function (test) { 53 | var appexFile = { fileRef: myProj.generateUuid(), isa: 'PBXFileReference', sourceTree: 'BUILT_PRODUCTS_DIR', path: "WatchKit Extension.appex"}; 54 | 55 | myProj.addToPbxFileReferenceSection(appexFile) 56 | 57 | test.equal(myProj.pbxFileReferenceSection()[appexFile.fileRef].isa, 'PBXFileReference'); 58 | test.equal(myProj.pbxFileReferenceSection()[appexFile.fileRef].sourceTree, 'BUILT_PRODUCTS_DIR'); 59 | test.equal(myProj.pbxFileReferenceSection()[appexFile.fileRef].path, '"WatchKit Extension.appex"'); 60 | test.done(); 61 | } 62 | } -------------------------------------------------------------------------------- /test/parser/projects/build-config.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 45; 7 | objects = { 8 | /* Begin XCBuildConfiguration section */ 9 | 1D6058940D05DD3E006BFB54 /* Debug */ = { 10 | isa = XCBuildConfiguration; 11 | buildSettings = { 12 | ALWAYS_SEARCH_USER_PATHS = NO; 13 | ARCHS = ( 14 | armv6, 15 | armv7, 16 | ); 17 | COPY_PHASE_STRIP = NO; 18 | GCC_DYNAMIC_NO_PIC = NO; 19 | GCC_OPTIMIZATION_LEVEL = 0; 20 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 21 | GCC_PREFIX_HEADER = "KitchenSinktablet-Prefix.pch"; 22 | INFOPLIST_FILE = "KitchenSinktablet-Info.plist"; 23 | IPHONEOS_DEPLOYMENT_TARGET = 3.0; 24 | ONLY_ACTIVE_ARCH = NO; 25 | PRODUCT_NAME = "KitchenSinktablet"; 26 | TARGETED_DEVICE_FAMILY = "1,2"; 27 | }; 28 | name = Debug; 29 | }; 30 | 1D6058950D05DD3E006BFB54 /* Release */ = { 31 | isa = XCBuildConfiguration; 32 | buildSettings = { 33 | ALWAYS_SEARCH_USER_PATHS = NO; 34 | ARCHS = ( 35 | armv6, 36 | armv7, 37 | ); 38 | COPY_PHASE_STRIP = YES; 39 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 40 | GCC_PREFIX_HEADER = "KitchenSinktablet-Prefix.pch"; 41 | INFOPLIST_FILE = "KitchenSinktablet-Info.plist"; 42 | IPHONEOS_DEPLOYMENT_TARGET = 3.0; 43 | ONLY_ACTIVE_ARCH = NO; 44 | PRODUCT_NAME = "KitchenSinktablet"; 45 | TARGETED_DEVICE_FAMILY = "1,2"; 46 | }; 47 | name = Release; 48 | }; 49 | C01FCF4F08A954540054247B /* Debug */ = { 50 | isa = XCBuildConfiguration; 51 | baseConfigurationReference = 307D28A1123043350040C0FA /* PhoneGapBuildSettings.xcconfig */; 52 | buildSettings = { 53 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 54 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; 55 | GCC_C_LANGUAGE_STANDARD = c99; 56 | GCC_VERSION = com.apple.compilers.llvmgcc42; 57 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 58 | GCC_WARN_UNUSED_VARIABLE = YES; 59 | IPHONEOS_DEPLOYMENT_TARGET = 3.0; 60 | OTHER_LDFLAGS = ( 61 | "-weak_framework", 62 | UIKit, 63 | "-weak_framework", 64 | AVFoundation, 65 | "-weak_framework", 66 | CoreMedia, 67 | "-weak_library", 68 | /usr/lib/libSystem.B.dylib, 69 | "-all_load", 70 | "-Obj-C", 71 | ); 72 | PREBINDING = NO; 73 | SDKROOT = iphoneos; 74 | SKIP_INSTALL = NO; 75 | USER_HEADER_SEARCH_PATHS = "\"$(PHONEGAPLIB)/Classes/JSON\" \"$(PHONEGAPLIB)/Classes\""; 76 | }; 77 | name = Debug; 78 | }; 79 | C01FCF5008A954540054247B /* Release */ = { 80 | isa = XCBuildConfiguration; 81 | baseConfigurationReference = 307D28A1123043350040C0FA /* PhoneGapBuildSettings.xcconfig */; 82 | buildSettings = { 83 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 84 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; 85 | GCC_C_LANGUAGE_STANDARD = c99; 86 | GCC_VERSION = com.apple.compilers.llvmgcc42; 87 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 88 | GCC_WARN_UNUSED_VARIABLE = YES; 89 | IPHONEOS_DEPLOYMENT_TARGET = 3.0; 90 | OTHER_LDFLAGS = ( 91 | "-weak_framework", 92 | UIKit, 93 | "-weak_framework", 94 | AVFoundation, 95 | "-weak_framework", 96 | CoreMedia, 97 | "-weak_library", 98 | /usr/lib/libSystem.B.dylib, 99 | "-all_load", 100 | "-Obj-C", 101 | ); 102 | PREBINDING = NO; 103 | SDKROOT = iphoneos; 104 | SKIP_INSTALL = NO; 105 | USER_HEADER_SEARCH_PATHS = "\"$(PHONEGAPLIB)/Classes/JSON\" \"$(PHONEGAPLIB)/Classes\""; 106 | }; 107 | name = Release; 108 | }; 109 | /* End XCBuildConfiguration section */ 110 | }; 111 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 112 | } 113 | -------------------------------------------------------------------------------- /test/addHeaderFile.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | pbxFile = require('../lib/pbxFile'), 5 | proj = new pbx('.'); 6 | 7 | function cleanHash() { 8 | return JSON.parse(fullProjectStr); 9 | } 10 | 11 | exports.setUp = function (callback) { 12 | proj.hash = cleanHash(); 13 | callback(); 14 | } 15 | 16 | exports.addHeaderFile = { 17 | 'should return a pbxFile': function (test) { 18 | var newFile = proj.addHeaderFile('file.h'); 19 | 20 | test.equal(newFile.constructor, pbxFile); 21 | test.done() 22 | }, 23 | 'should set a fileRef on the pbxFile': function (test) { 24 | var newFile = proj.addHeaderFile('file.h'); 25 | 26 | test.ok(newFile.fileRef); 27 | test.done() 28 | }, 29 | 'should populate the PBXFileReference section with 2 fields': function (test) { 30 | var newFile = proj.addHeaderFile('file.h'), 31 | fileRefSection = proj.pbxFileReferenceSection(), 32 | frsLength = Object.keys(fileRefSection).length; 33 | 34 | test.equal(68, frsLength); 35 | test.ok(fileRefSection[newFile.fileRef]); 36 | test.ok(fileRefSection[newFile.fileRef + '_comment']); 37 | 38 | test.done(); 39 | }, 40 | 'should populate the PBXFileReference comment correctly': function (test) { 41 | var newFile = proj.addHeaderFile('file.h'), 42 | fileRefSection = proj.pbxFileReferenceSection(), 43 | commentKey = newFile.fileRef + '_comment'; 44 | 45 | test.equal(fileRefSection[commentKey], 'file.h'); 46 | test.done(); 47 | }, 48 | 'should add the PBXFileReference object correctly': function (test) { 49 | var newFile = proj.addHeaderFile('Plugins/file.h'), 50 | fileRefSection = proj.pbxFileReferenceSection(), 51 | fileRefEntry = fileRefSection[newFile.fileRef]; 52 | 53 | test.equal(fileRefEntry.isa, 'PBXFileReference'); 54 | test.equal(fileRefEntry.fileEncoding, 4); 55 | test.equal(fileRefEntry.lastKnownFileType, 'sourcecode.c.h'); 56 | test.equal(fileRefEntry.name, '"file.h"'); 57 | test.equal(fileRefEntry.path, '"file.h"'); 58 | test.equal(fileRefEntry.sourceTree, '""'); 59 | 60 | test.done(); 61 | }, 62 | 'should add to the Plugins PBXGroup group': function (test) { 63 | var newFile = proj.addHeaderFile('Plugins/file.h'), 64 | plugins = proj.pbxGroupByName('Plugins'); 65 | 66 | test.equal(plugins.children.length, 1); 67 | test.done(); 68 | }, 69 | 'should have the right values for the PBXGroup entry': function (test) { 70 | var newFile = proj.addHeaderFile('Plugins/file.h'), 71 | plugins = proj.pbxGroupByName('Plugins'), 72 | pluginObj = plugins.children[0]; 73 | 74 | test.equal(pluginObj.comment, 'file.h'); 75 | test.equal(pluginObj.value, newFile.fileRef); 76 | test.done(); 77 | }, 78 | 'duplicate entries': { 79 | 'should return false': function (test) { 80 | var newFile = proj.addHeaderFile('Plugins/file.h'); 81 | 82 | test.ok(!proj.addHeaderFile('Plugins/file.h')); 83 | test.done(); 84 | }, 85 | 'should not add another entry anywhere': function (test) { 86 | var newFile = proj.addHeaderFile('Plugins/file.h'), 87 | fileRefSection = proj.pbxFileReferenceSection(), 88 | frsLength = Object.keys(fileRefSection).length, 89 | plugins = proj.pbxGroupByName('Plugins'); 90 | 91 | proj.addHeaderFile('Plugins/file.h'); 92 | 93 | test.equal(68, frsLength); 94 | test.equal(plugins.children.length, 1); 95 | test.done(); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /test/removeHeaderFile.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | pbxFile = require('../lib/pbxFile'), 5 | proj = new pbx('.'); 6 | 7 | function cleanHash() { 8 | return JSON.parse(fullProjectStr); 9 | } 10 | 11 | exports.setUp = function (callback) { 12 | proj.hash = cleanHash(); 13 | callback(); 14 | } 15 | 16 | exports.removeHeaderFile = { 17 | 'should return a pbxFile': function (test) { 18 | var newFile = proj.addHeaderFile('file.h'); 19 | 20 | test.equal(newFile.constructor, pbxFile); 21 | 22 | var deletedFile = proj.removeHeaderFile('file.h'); 23 | 24 | test.equal(deletedFile.constructor, pbxFile); 25 | 26 | test.done() 27 | }, 28 | 'should set a fileRef on the pbxFile': function (test) { 29 | var newFile = proj.addHeaderFile('file.h'); 30 | 31 | test.ok(newFile.fileRef); 32 | 33 | var deletedFile = proj.removeHeaderFile('file.h'); 34 | 35 | test.ok(deletedFile.fileRef); 36 | 37 | test.done() 38 | }, 39 | 'should remove 2 fields from the PBXFileReference section': function (test) { 40 | var newFile = proj.addHeaderFile('file.h'), 41 | fileRefSection = proj.pbxFileReferenceSection(), 42 | frsLength = Object.keys(fileRefSection).length; 43 | 44 | test.equal(68, frsLength); 45 | test.ok(fileRefSection[newFile.fileRef]); 46 | test.ok(fileRefSection[newFile.fileRef + '_comment']); 47 | 48 | var deletedFile = proj.removeHeaderFile('file.h'), 49 | fileRefSection = proj.pbxFileReferenceSection(), 50 | frsLength = Object.keys(fileRefSection).length; 51 | 52 | test.equal(66, frsLength); 53 | test.ok(!fileRefSection[deletedFile.fileRef]); 54 | test.ok(!fileRefSection[deletedFile.fileRef + '_comment']); 55 | 56 | test.done(); 57 | }, 58 | 'should remove comment from the PBXFileReference correctly': function (test) { 59 | var newFile = proj.addHeaderFile('file.h'), 60 | fileRefSection = proj.pbxFileReferenceSection(), 61 | commentKey = newFile.fileRef + '_comment'; 62 | 63 | test.equal(fileRefSection[commentKey], 'file.h'); 64 | 65 | var deletedFile = proj.removeHeaderFile('file.h'), 66 | fileRefSection = proj.pbxFileReferenceSection(), 67 | commentKey = deletedFile.fileRef + '_comment'; 68 | test.ok(!fileRefSection[commentKey]); 69 | 70 | test.done(); 71 | }, 72 | 'should remove the PBXFileReference object correctly': function (test) { 73 | var newFile = proj.addHeaderFile('Plugins/file.h'), 74 | fileRefSection = proj.pbxFileReferenceSection(), 75 | fileRefEntry = fileRefSection[newFile.fileRef]; 76 | 77 | test.equal(fileRefEntry.isa, 'PBXFileReference'); 78 | test.equal(fileRefEntry.fileEncoding, 4); 79 | test.equal(fileRefEntry.lastKnownFileType, 'sourcecode.c.h'); 80 | test.equal(fileRefEntry.name, '"file.h"'); 81 | test.equal(fileRefEntry.path, '"file.h"'); 82 | test.equal(fileRefEntry.sourceTree, '""'); 83 | 84 | var deletedFile = proj.removeHeaderFile('Plugins/file.h'), 85 | fileRefSection = proj.pbxFileReferenceSection(), 86 | fileRefEntry = fileRefSection[deletedFile.fileRef]; 87 | 88 | test.ok(!fileRefEntry); 89 | 90 | test.done(); 91 | }, 92 | 'should remove from the Plugins PBXGroup group': function (test) { 93 | var newFile = proj.addHeaderFile('Plugins/file.h'), 94 | plugins = proj.pbxGroupByName('Plugins'); 95 | 96 | test.equal(plugins.children.length, 1); 97 | 98 | var deletedFile = proj.removeHeaderFile('Plugins/file.h'), 99 | plugins = proj.pbxGroupByName('Plugins'); 100 | 101 | test.equal(plugins.children.length, 0); 102 | 103 | test.done(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /test/parser/projects/build-files.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 45; 7 | objects = { 8 | /* Begin PBXBuildFile section */ 9 | 1D3623260D0F684500981E51 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* AppDelegate.m */; }; 10 | 1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; }; 11 | 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; 12 | 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 13 | 1F766FE113BBADB100FB74C0 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1F766FDC13BBADB100FB74C0 /* Localizable.strings */; }; 14 | 1F766FE213BBADB100FB74C0 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1F766FDF13BBADB100FB74C0 /* Localizable.strings */; }; 15 | 288765FD0DF74451002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765FC0DF74451002DB57D /* CoreGraphics.framework */; }; 16 | 301BF552109A68D80062928A /* libPhoneGap.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF535109A57CC0062928A /* libPhoneGap.a */; }; 17 | 301BF570109A69640062928A /* www in Resources */ = {isa = PBXBuildFile; fileRef = 301BF56E109A69640062928A /* www */; }; 18 | 301BF5B5109A6A2B0062928A /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5B4109A6A2B0062928A /* AddressBook.framework */; }; 19 | 301BF5B7109A6A2B0062928A /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5B6109A6A2B0062928A /* AddressBookUI.framework */; }; 20 | 301BF5B9109A6A2B0062928A /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5B8109A6A2B0062928A /* AudioToolbox.framework */; }; 21 | 301BF5BB109A6A2B0062928A /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5BA109A6A2B0062928A /* AVFoundation.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 22 | 301BF5BD109A6A2B0062928A /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5BC109A6A2B0062928A /* CFNetwork.framework */; }; 23 | 301BF5BF109A6A2B0062928A /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5BE109A6A2B0062928A /* CoreLocation.framework */; }; 24 | 301BF5C1109A6A2B0062928A /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5C0109A6A2B0062928A /* MediaPlayer.framework */; }; 25 | 301BF5C3109A6A2B0062928A /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5C2109A6A2B0062928A /* QuartzCore.framework */; }; 26 | 301BF5C5109A6A2B0062928A /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF5C4109A6A2B0062928A /* SystemConfiguration.framework */; }; 27 | 3053AC6F109B7857006FCFE7 /* VERSION in Resources */ = {isa = PBXBuildFile; fileRef = 3053AC6E109B7857006FCFE7 /* VERSION */; }; 28 | 305D5FD1115AB8F900A74A75 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 305D5FD0115AB8F900A74A75 /* MobileCoreServices.framework */; }; 29 | 3072F99713A8081B00425683 /* Capture.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 3072F99613A8081B00425683 /* Capture.bundle */; }; 30 | 307D28A2123043360040C0FA /* PhoneGapBuildSettings.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 307D28A1123043350040C0FA /* PhoneGapBuildSettings.xcconfig */; }; 31 | 308D05371370CCF300D202BF /* icon-72.png in Resources */ = {isa = PBXBuildFile; fileRef = 308D052E1370CCF300D202BF /* icon-72.png */; }; 32 | 308D05381370CCF300D202BF /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 308D052F1370CCF300D202BF /* icon.png */; }; 33 | 308D05391370CCF300D202BF /* icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 308D05301370CCF300D202BF /* icon@2x.png */; }; 34 | 308D053C1370CCF300D202BF /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 308D05341370CCF300D202BF /* Default.png */; }; 35 | 308D053D1370CCF300D202BF /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 308D05351370CCF300D202BF /* Default@2x.png */; }; 36 | 30E1352710E2C1420031B30D /* PhoneGap.plist in Resources */ = {isa = PBXBuildFile; fileRef = 30E1352610E2C1420031B30D /* PhoneGap.plist */; }; 37 | 30E5649213A7FCAF007403D8 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30E5649113A7FCAF007403D8 /* CoreMedia.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 38 | /* End PBXBuildFile section */ 39 | }; 40 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 41 | } 42 | -------------------------------------------------------------------------------- /lib/pbxFile.js: -------------------------------------------------------------------------------- 1 | var $path = require('path'), 2 | util = require('util'), 3 | HEADER_FILE_TYPE_SUFFIX = ".h", 4 | SOURCE_CODE_FILE_TYPE_PREFIX = "sourcecode.", 5 | M_EXTENSION = /[.]m$/, SOURCE_FILE = 'sourcecode.c.objc', 6 | C_EXTENSION = /[.]c$/, C_SOURCE_FILE = 'sourcecode.c.c', 7 | H_EXTENSION = /[.]h$/, HEADER_FILE = 'sourcecode.c.h', 8 | MM_EXTENSION = /[.]mm$/, MM_SOURCE_FILE = 'sourcecode.cpp.objcpp', 9 | HPP_EXTENSION = /[.](hpp|hxx|h\+\+|hh)$/, CPP_HEADER_FILE = 'sourcecode.cpp.h', 10 | CPP_EXTENSION = /[.](cpp|cxx|c\+\+|cc)$/, CPP_SOURCE_FILE = 'sourcecode.cpp.cpp', 11 | BUNDLE_EXTENSION = /[.]bundle$/, BUNDLE = '"wrapper.plug-in"', 12 | XIB_EXTENSION = /[.]xib$/, XIB_FILE = 'file.xib', 13 | DYLIB_EXTENSION = /[.]dylib$/, DYLIB = '"compiled.mach-o.dylib"', 14 | FRAMEWORK_EXTENSION = /[.]framework$/, FRAMEWORK = 'wrapper.framework', 15 | ARCHIVE_EXTENSION = /[.]a$/, ARCHIVE = 'archive.ar', 16 | PNG_EXTENSION = /[.]png/, PNG_IMAGE = "image.png", 17 | DEFAULT_SOURCE_TREE = '""', 18 | DEFAULT_FILE_ENCODING = 4; 19 | 20 | function fileTypes() { 21 | return { 22 | SOURCE_FILE, 23 | C_SOURCE_FILE, 24 | HEADER_FILE, 25 | MM_SOURCE_FILE, 26 | CPP_HEADER_FILE, 27 | CPP_SOURCE_FILE, 28 | BUNDLE, 29 | XIB_FILE, 30 | FRAMEWORK, 31 | DYLIB, 32 | ARCHIVE, 33 | PNG_IMAGE, 34 | } 35 | } 36 | 37 | function isSourceOrHeaderFileType(fileType) { 38 | return fileType.startsWith(SOURCE_CODE_FILE_TYPE_PREFIX); 39 | } 40 | 41 | function isHeaderFileType(fileType) { 42 | return fileType.endsWith(HEADER_FILE_TYPE_SUFFIX); 43 | } 44 | 45 | function detectLastType(path) { 46 | if (M_EXTENSION.test(path)) 47 | return SOURCE_FILE; 48 | 49 | if (C_EXTENSION.test(path)) 50 | return C_SOURCE_FILE; 51 | 52 | if (H_EXTENSION.test(path)) 53 | return HEADER_FILE; 54 | 55 | if (MM_EXTENSION.test(path)) 56 | return MM_SOURCE_FILE; 57 | 58 | if (CPP_EXTENSION.test(path)) 59 | return CPP_SOURCE_FILE; 60 | 61 | if (HPP_EXTENSION.test(path)) 62 | return CPP_HEADER_FILE; 63 | 64 | if (BUNDLE_EXTENSION.test(path)) 65 | return BUNDLE; 66 | 67 | if (XIB_EXTENSION.test(path)) 68 | return XIB_FILE; 69 | 70 | if (FRAMEWORK_EXTENSION.test(path)) 71 | return FRAMEWORK; 72 | 73 | if (DYLIB_EXTENSION.test(path)) 74 | return DYLIB; 75 | 76 | if (ARCHIVE_EXTENSION.test(path)) 77 | return ARCHIVE; 78 | 79 | if (PNG_EXTENSION.test(path)) 80 | return PNG_IMAGE; 81 | 82 | // dunno 83 | return 'unknown'; 84 | } 85 | 86 | function fileEncoding(file) { 87 | if (file.lastType != BUNDLE && file.lastType !== PNG_IMAGE && !file.customFramework) { 88 | return DEFAULT_FILE_ENCODING; 89 | } 90 | } 91 | 92 | function defaultSourceTree(file) { 93 | if (( file.lastType == DYLIB || file.lastType == FRAMEWORK ) && !file.customFramework) { 94 | return 'SDKROOT'; 95 | } else { 96 | return DEFAULT_SOURCE_TREE; 97 | } 98 | } 99 | 100 | function correctPath(file, filepath) { 101 | if (file.lastType == FRAMEWORK && !file.customFramework) { 102 | return 'System/Library/Frameworks/' + filepath; 103 | } else if (file.lastType == DYLIB) { 104 | return 'usr/lib/' + filepath; 105 | } else { 106 | return filepath; 107 | } 108 | } 109 | 110 | function correctGroup(file) { 111 | if (isSourceOrHeaderFileType(file.lastType) && !isHeaderFileType(file.lastType)) { 112 | return 'Sources'; 113 | } else if (file.lastType == DYLIB || file.lastType == ARCHIVE || file.lastType == FRAMEWORK) { 114 | return 'Frameworks'; 115 | } else { 116 | return 'Resources'; 117 | } 118 | } 119 | 120 | function pbxFile(filepath, opt) { 121 | var opt = opt || {}; 122 | 123 | this.lastType = opt.lastType || detectLastType(filepath); 124 | 125 | // for custom frameworks 126 | if(opt.customFramework == true) { 127 | this.customFramework = true; 128 | this.dirname = $path.dirname(filepath); 129 | } 130 | 131 | this.basename = opt.basename || $path.basename(filepath); 132 | this.path = correctPath(this, filepath); 133 | this.group = correctGroup(this); 134 | 135 | this.sourceTree = opt.sourceTree || defaultSourceTree(this); 136 | this.fileEncoding = opt.fileEncoding || fileEncoding(this); 137 | 138 | if (opt.weak && opt.weak === true) 139 | this.settings = { ATTRIBUTES: ['Weak'] }; 140 | 141 | if (opt.compilerFlags) { 142 | if (!this.settings) 143 | this.settings = {}; 144 | this.settings.COMPILER_FLAGS = util.format('"%s"', opt.compilerFlags); 145 | } 146 | } 147 | 148 | module.exports = { 149 | pbxFile: pbxFile, 150 | fileTypes: fileTypes, 151 | isSourceOrHeaderFileType, 152 | isHeaderFileType, 153 | } 154 | -------------------------------------------------------------------------------- /test/multipleTargets.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/multiple-targets') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | pbxFile = require('../lib/pbxFile'), 5 | proj = new pbx('.'); 6 | 7 | function cleanHash() { 8 | return JSON.parse(fullProjectStr); 9 | } 10 | 11 | exports.setUp = function (callback) { 12 | proj.hash = cleanHash(); 13 | callback(); 14 | } 15 | 16 | exports.addFilesToTarget = { 17 | 'should add the file to a proper target': function (test) { 18 | 19 | var target = "1D6058900D05DD3D006BFB54"; 20 | var filename = "file.m"; 21 | 22 | var opt = { target : target }; 23 | var newFile = proj.addSourceFile(filename,opt); 24 | 25 | test.equal(newFile.constructor, pbxFile); 26 | 27 | var sources = proj.pbxSourcesBuildPhaseObj(target); 28 | test.equal(sources.files[5].comment, filename+" in Sources"); 29 | 30 | test.done(); 31 | }, 32 | 'should remove the file from the proper target': function (test) { 33 | 34 | var target = "1D6058900D05DD3D006BFB54"; 35 | var filename = "file.m"; 36 | 37 | var opt = { target : target }; 38 | var newFile = proj.addSourceFile(filename,opt); 39 | 40 | test.equal(newFile.constructor, pbxFile); 41 | 42 | var sources = proj.pbxSourcesBuildPhaseObj(target); 43 | test.equal(sources.files[5].comment, filename+" in Sources"); 44 | var l = sources.files.length; 45 | 46 | proj.removeSourceFile(filename,opt); 47 | var sources = proj.pbxSourcesBuildPhaseObj(target); 48 | test.equal(sources.files.length,l-1); 49 | 50 | test.done(); 51 | }, 52 | 'should fail when specifying an invalid target': function (test) { 53 | 54 | var target = "XXXXX"; 55 | var filename = "file.m"; 56 | 57 | var opt = { target : target }; 58 | test.throws(function(){ 59 | proj.addSourceFile(filename,opt); 60 | }); 61 | 62 | 63 | test.done(); 64 | }, 65 | 'should add the library to a proper target': function (test) { 66 | 67 | var target = "1D6058900D05DD3D006BFB54"; 68 | var filename = "library.lib"; 69 | 70 | var opt = { target : target }; 71 | var newFile = proj.addStaticLibrary(filename,opt); 72 | 73 | test.equal(newFile.constructor, pbxFile); 74 | 75 | var libraries = proj.pbxFrameworksBuildPhaseObj(target); 76 | test.equal(libraries.files[4].comment, filename+" in Resources"); 77 | 78 | test.done(); 79 | }, 80 | 'should remove the library to a proper target': function (test) { 81 | 82 | var target = "1D6058900D05DD3D006BFB54"; 83 | var filename = "library.lib"; 84 | 85 | var opt = { target : target }; 86 | var newFile = proj.addStaticLibrary(filename,opt); 87 | 88 | test.equal(newFile.constructor, pbxFile); 89 | 90 | var libraries = proj.pbxFrameworksBuildPhaseObj(target); 91 | test.equal(libraries.files[4].comment, filename+" in Resources"); 92 | var l = libraries.files.length; 93 | 94 | proj.removeFramework(filename,opt); 95 | var libraries = proj.pbxFrameworksBuildPhaseObj(target); 96 | test.equal(libraries.files.length,l-1); 97 | 98 | test.done(); 99 | 100 | }, 101 | 'should add the framework to a proper target': function (test) { 102 | 103 | var target = "1D6058900D05DD3D006BFB54"; 104 | var filename = "delta.framework"; 105 | 106 | var opt = { target : target }; 107 | var newFile = proj.addFramework(filename,opt); 108 | 109 | test.equal(newFile.constructor, pbxFile); 110 | 111 | var frameworks = proj.pbxFrameworksBuildPhaseObj(target); 112 | test.equal(frameworks.files[4].comment, filename+" in Frameworks"); 113 | 114 | test.done(); 115 | }, 116 | 'should add a ressource fileto a proper target': function (test) { 117 | 118 | var target = "1D6058900D05DD3D006BFB54"; 119 | var filename = "delta.png"; 120 | 121 | var opt = { target : target }; 122 | var newFile = proj.addResourceFile(filename,opt); 123 | 124 | test.equal(newFile.constructor, pbxFile); 125 | 126 | var resources = proj.pbxResourcesBuildPhaseObj(target); 127 | test.equal(resources.files[26].comment, filename+" in Resources"); 128 | 129 | test.done(); 130 | }, 131 | 'should remove a ressource file from a proper target': function (test) { 132 | 133 | var target = "1D6058900D05DD3D006BFB54"; 134 | var filename = "delta.png"; 135 | 136 | var opt = { target : target }; 137 | var newFile = proj.addResourceFile(filename,opt); 138 | 139 | test.equal(newFile.constructor, pbxFile); 140 | 141 | var resources = proj.pbxResourcesBuildPhaseObj(target); 142 | test.equal(resources.files[26].comment, filename+" in Resources"); 143 | 144 | var l = resources.files.length; 145 | 146 | proj.removeResourceFile(filename,opt); 147 | var resources = proj.pbxResourcesBuildPhaseObj(target); 148 | test.equal(resources.files.length,l-1); 149 | 150 | test.done(); 151 | }, 152 | } 153 | 154 | -------------------------------------------------------------------------------- /test/addBuildPhase.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | proj = new pbx('.'); 5 | 6 | function cleanHash() { 7 | return JSON.parse(fullProjectStr); 8 | } 9 | 10 | exports.setUp = function (callback) { 11 | proj.hash = cleanHash(); 12 | callback(); 13 | } 14 | 15 | exports.addBuildPhase = { 16 | 'should return a pbxBuildPhase': function (test) { 17 | var buildPhase = proj.addBuildPhase(['file.m'], 'PBXSourcesBuildPhase', 'My build phase'); 18 | 19 | test.ok(typeof buildPhase === 'object'); 20 | test.done() 21 | }, 22 | 'should set a uuid on the pbxBuildPhase': function (test) { 23 | var buildPhase = proj.addBuildPhase(['file.m'], 'PBXSourcesBuildPhase', 'My build phase'); 24 | 25 | test.ok(buildPhase.uuid); 26 | test.done() 27 | }, 28 | 'should add all files to build phase': function (test) { 29 | var buildPhase = proj.addBuildPhase(['file.m', 'assets.bundle'], 'PBXResourcesBuildPhase', 'My build phase').buildPhase; 30 | for (var index = 0; index < buildPhase.files.length; index++) { 31 | var file = buildPhase.files[index]; 32 | test.ok(file.value); 33 | } 34 | 35 | test.done() 36 | }, 37 | 'should add the PBXBuildPhase object correctly': function (test) { 38 | var buildPhase = proj.addBuildPhase(['file.m', 'assets.bundle'], 'PBXResourcesBuildPhase', 'My build phase').buildPhase, 39 | buildPhaseInPbx = proj.buildPhaseObject('PBXResourcesBuildPhase', 'My build phase'); 40 | 41 | test.equal(buildPhaseInPbx, buildPhase); 42 | test.equal(buildPhaseInPbx.isa, 'PBXResourcesBuildPhase'); 43 | test.equal(buildPhaseInPbx.buildActionMask, 2147483647); 44 | test.equal(buildPhaseInPbx.runOnlyForDeploymentPostprocessing, 0); 45 | test.done(); 46 | }, 47 | 'should add each of the files to PBXBuildFile section': function (test) { 48 | var buildPhase = proj.addBuildPhase(['file.m', 'assets.bundle'], 'PBXResourcesBuildPhase', 'My build phase').buildPhase, 49 | buildFileSection = proj.pbxBuildFileSection(); 50 | 51 | for (var index = 0; index < buildPhase.files.length; index++) { 52 | var file = buildPhase.files[index]; 53 | test.ok(buildFileSection[file.value]); 54 | } 55 | 56 | test.done(); 57 | }, 58 | 'should add each of the files to PBXFileReference section': function (test) { 59 | var buildPhase = proj.addBuildPhase(['file.m', 'assets.bundle'], 'PBXResourcesBuildPhase', 'My build phase').buildPhase, 60 | fileRefSection = proj.pbxFileReferenceSection(), 61 | buildFileSection = proj.pbxBuildFileSection(), 62 | fileRefs = []; 63 | 64 | for (var index = 0; index < buildPhase.files.length; index++) { 65 | var file = buildPhase.files[index], 66 | fileRef = buildFileSection[file.value].fileRef; 67 | 68 | test.ok(fileRefSection[fileRef]); 69 | } 70 | 71 | test.done(); 72 | }, 73 | 'should not add files to PBXFileReference section if already added': function (test) { 74 | var fileRefSection = proj.pbxFileReferenceSection(), 75 | initialFileReferenceSectionItemsCount = Object.keys(fileRefSection), 76 | buildPhase = proj.addBuildPhase(['AppDelegate.m', 'main.m'], 'PBXResourcesBuildPhase', 'My build phase').buildPhase, 77 | afterAdditionBuildFileSectionItemsCount = Object.keys(fileRefSection); 78 | 79 | test.deepEqual(initialFileReferenceSectionItemsCount, afterAdditionBuildFileSectionItemsCount); 80 | test.done(); 81 | }, 82 | 'should not add files to PBXBuildFile section if already added': function (test) { 83 | var buildFileSection = proj.pbxBuildFileSection(), 84 | initialBuildFileSectionItemsCount = Object.keys(buildFileSection), 85 | buildPhase = proj.addBuildPhase(['AppDelegate.m', 'main.m'], 'PBXResourcesBuildPhase', 'My build phase').buildPhase, 86 | afterAdditionBuildFileSectionItemsCount = Object.keys(buildFileSection); 87 | 88 | test.deepEqual(initialBuildFileSectionItemsCount, afterAdditionBuildFileSectionItemsCount); 89 | test.done(); 90 | }, 91 | 'should add only missing files to PBXFileReference section': function (test) { 92 | var fileRefSection = proj.pbxFileReferenceSection(), 93 | buildFileSection = proj.pbxBuildFileSection(), 94 | initialFileReferenceSectionItemsCount = Object.keys(fileRefSection), 95 | buildPhase = proj.addBuildPhase(['file.m', 'AppDelegate.m'], 'PBXResourcesBuildPhase', 'My build phase').buildPhase, 96 | afterAdditionBuildFileSectionItemsCount = Object.keys(fileRefSection); 97 | 98 | for (var index = 0; index < buildPhase.files.length; index++) { 99 | var file = buildPhase.files[index], 100 | fileRef = buildFileSection[file.value].fileRef; 101 | 102 | test.ok(fileRefSection[fileRef]); 103 | } 104 | 105 | test.deepEqual(initialFileReferenceSectionItemsCount.length, afterAdditionBuildFileSectionItemsCount.length - 2); 106 | test.done(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /test/removeFramework.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | pbxFile = require('../lib/pbxFile'), 5 | proj = new pbx('.'); 6 | 7 | function cleanHash() { 8 | return JSON.parse(fullProjectStr); 9 | } 10 | 11 | exports.setUp = function (callback) { 12 | proj.hash = cleanHash(); 13 | callback(); 14 | } 15 | 16 | function nonComments(obj) { 17 | var keys = Object.keys(obj), 18 | newObj = {}, i = 0; 19 | 20 | for (i; i < keys.length; i++) { 21 | if (!/_comment$/.test(keys[i])) { 22 | newObj[keys[i]] = obj[keys[i]]; 23 | } 24 | } 25 | 26 | return newObj; 27 | } 28 | 29 | function frameworkSearchPaths(proj) { 30 | var configs = nonComments(proj.pbxXCBuildConfigurationSection()), 31 | allPaths = [], 32 | ids = Object.keys(configs), i, buildSettings; 33 | 34 | for (i = 0; i< ids.length; i++) { 35 | buildSettings = configs[ids[i]].buildSettings; 36 | 37 | if (buildSettings['FRAMEWORK_SEARCH_PATHS']) { 38 | allPaths.push(buildSettings['FRAMEWORK_SEARCH_PATHS']); 39 | } 40 | } 41 | 42 | return allPaths; 43 | } 44 | 45 | exports.removeFramework = { 46 | 'should return a pbxFile': function (test) { 47 | var newFile = proj.addFramework('libsqlite3.dylib'); 48 | 49 | test.equal(newFile.constructor, pbxFile); 50 | 51 | var deletedFile = proj.removeFramework('libsqlite3.dylib'); 52 | 53 | test.equal(deletedFile.constructor, pbxFile); 54 | 55 | test.done() 56 | }, 57 | 'should set a fileRef on the pbxFile': function (test) { 58 | var newFile = proj.addFramework('libsqlite3.dylib'); 59 | 60 | test.ok(newFile.fileRef); 61 | 62 | var deletedFile = proj.removeFramework('libsqlite3.dylib'); 63 | 64 | test.ok(deletedFile.fileRef); 65 | 66 | test.done() 67 | }, 68 | 'should remove 2 fields from the PBXFileReference section': function (test) { 69 | var newFile = proj.addFramework('libsqlite3.dylib'); 70 | fileRefSection = proj.pbxFileReferenceSection(), 71 | frsLength = Object.keys(fileRefSection).length; 72 | 73 | test.equal(68, frsLength); 74 | test.ok(fileRefSection[newFile.fileRef]); 75 | test.ok(fileRefSection[newFile.fileRef + '_comment']); 76 | 77 | var deletedFile = proj.removeFramework('libsqlite3.dylib'); 78 | frsLength = Object.keys(fileRefSection).length; 79 | 80 | test.equal(66, frsLength); 81 | test.ok(!fileRefSection[deletedFile.fileRef]); 82 | test.ok(!fileRefSection[deletedFile.fileRef + '_comment']); 83 | 84 | test.done(); 85 | }, 86 | 'should remove 2 fields from the PBXBuildFile section': function (test) { 87 | var newFile = proj.addFramework('libsqlite3.dylib'), 88 | buildFileSection = proj.pbxBuildFileSection(), 89 | bfsLength = Object.keys(buildFileSection).length; 90 | 91 | test.equal(60, bfsLength); 92 | test.ok(buildFileSection[newFile.uuid]); 93 | test.ok(buildFileSection[newFile.uuid + '_comment']); 94 | 95 | var deletedFile = proj.removeFramework('libsqlite3.dylib'); 96 | 97 | bfsLength = Object.keys(buildFileSection).length; 98 | 99 | test.equal(58, bfsLength); 100 | test.ok(!buildFileSection[deletedFile.uuid]); 101 | test.ok(!buildFileSection[deletedFile.uuid + '_comment']); 102 | 103 | test.done(); 104 | }, 105 | 'should remove from the Frameworks PBXGroup': function (test) { 106 | var newLength = proj.pbxGroupByName('Frameworks').children.length + 1, 107 | newFile = proj.addFramework('libsqlite3.dylib'), 108 | frameworks = proj.pbxGroupByName('Frameworks'); 109 | 110 | test.equal(frameworks.children.length, newLength); 111 | 112 | var deletedFile = proj.removeFramework('libsqlite3.dylib'), 113 | newLength = newLength - 1; 114 | 115 | test.equal(frameworks.children.length, newLength); 116 | 117 | test.done(); 118 | }, 119 | 'should remove from the PBXFrameworksBuildPhase': function (test) { 120 | var newFile = proj.addFramework('libsqlite3.dylib'), 121 | frameworks = proj.pbxFrameworksBuildPhaseObj(); 122 | 123 | test.equal(frameworks.files.length, 16); 124 | 125 | var deletedFile = proj.removeFramework('libsqlite3.dylib'), 126 | frameworks = proj.pbxFrameworksBuildPhaseObj(); 127 | 128 | test.equal(frameworks.files.length, 15); 129 | 130 | test.done(); 131 | }, 132 | 'should remove custom frameworks': function (test) { 133 | var newFile = proj.addFramework('/path/to/Custom.framework'), 134 | frameworks = proj.pbxFrameworksBuildPhaseObj(); 135 | 136 | test.equal(frameworks.files.length, 16); 137 | 138 | var deletedFile = proj.removeFramework('/path/to/Custom.framework'), 139 | frameworks = proj.pbxFrameworksBuildPhaseObj(); 140 | 141 | test.equal(frameworks.files.length, 15); 142 | 143 | var frameworkPaths = frameworkSearchPaths(proj); 144 | expectedPath = '"/path/to"'; 145 | 146 | for (i = 0; i < frameworkPaths.length; i++) { 147 | var current = frameworkPaths[i]; 148 | test.ok(current.indexOf('"$(inherited)"') == -1); 149 | test.ok(current.indexOf(expectedPath) == -1); 150 | } 151 | 152 | test.done(); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /test/removeSourceFile.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | pbxFile = require('../lib/pbxFile'), 5 | proj = new pbx('.'); 6 | 7 | function cleanHash() { 8 | return JSON.parse(fullProjectStr); 9 | } 10 | 11 | exports.setUp = function (callback) { 12 | proj.hash = cleanHash(); 13 | callback(); 14 | } 15 | 16 | exports.removeSourceFile = { 17 | 'should return a pbxFile': function (test) { 18 | proj.addSourceFile('file.m'); 19 | var newFile = proj.removeSourceFile('file.m'); 20 | 21 | test.equal(newFile.constructor, pbxFile); 22 | test.done() 23 | }, 24 | 'should set a uuid on the pbxFile': function (test) { 25 | proj.addSourceFile('file.m'); 26 | var newFile = proj.removeSourceFile('file.m'); 27 | 28 | test.ok(newFile.uuid); 29 | test.done() 30 | }, 31 | 'should set a fileRef on the pbxFile': function (test) { 32 | proj.addSourceFile('file.m'); 33 | var newFile = proj.removeSourceFile('file.m'); 34 | 35 | test.ok(newFile.fileRef); 36 | test.done() 37 | }, 38 | 'should remove 2 fields from the PBXBuildFile section': function (test) { 39 | proj.addSourceFile('file.m'); 40 | var newFile = proj.removeSourceFile('file.m'), 41 | buildFileSection = proj.pbxBuildFileSection(), 42 | bfsLength = Object.keys(buildFileSection).length; 43 | 44 | test.equal(58, bfsLength); 45 | test.ok(!buildFileSection[newFile.uuid]); 46 | test.ok(!buildFileSection[newFile.uuid + '_comment']); 47 | 48 | test.done(); 49 | }, 50 | 'should remove comment from the PBXBuildFile correctly': function (test) { 51 | proj.addSourceFile('file.m'); 52 | var newFile = proj.removeSourceFile('file.m'), 53 | commentKey = newFile.uuid + '_comment', 54 | buildFileSection = proj.pbxBuildFileSection(); 55 | test.notEqual(!buildFileSection[commentKey], 'file.m in Sources'); 56 | test.done(); 57 | }, 58 | 'should remove the PBXBuildFile object correctly': function (test) { 59 | proj.addSourceFile('file.m'); 60 | var newFile = proj.removeSourceFile('file.m'), 61 | buildFileSection = proj.pbxBuildFileSection(), 62 | buildFileEntry = buildFileSection[newFile.uuid]; 63 | 64 | test.equal(buildFileEntry, undefined); 65 | 66 | test.done(); 67 | }, 68 | 'should remove 2 fields from the PBXFileReference section': function (test) { 69 | proj.addSourceFile('file.m'); 70 | var newFile = proj.removeSourceFile('file.m'), 71 | fileRefSection = proj.pbxFileReferenceSection(), 72 | frsLength = Object.keys(fileRefSection).length; 73 | 74 | test.equal(66, frsLength); 75 | test.ok(!fileRefSection[newFile.fileRef]); 76 | test.ok(!fileRefSection[newFile.fileRef + '_comment']); 77 | 78 | test.done(); 79 | }, 80 | 'should remove the PBXFileReference comment correctly': function (test) { 81 | proj.addSourceFile('file.m'); 82 | var newFile = proj.removeSourceFile('file.m'), 83 | fileRefSection = proj.pbxFileReferenceSection(), 84 | commentKey = newFile.fileRef + '_comment'; 85 | 86 | test.ok(!fileRefSection[commentKey]); 87 | test.done(); 88 | }, 89 | 'should remove the PBXFileReference object correctly': function (test) { 90 | proj.addSourceFile('file.m'); 91 | var newFile = proj.removeSourceFile('Plugins/file.m'), 92 | fileRefSection = proj.pbxFileReferenceSection(), 93 | fileRefEntry = fileRefSection[newFile.fileRef]; 94 | test.ok(!fileRefEntry); 95 | test.done(); 96 | }, 97 | 'should remove from the Plugins PBXGroup group': function (test) { 98 | proj.addSourceFile('Plugins/file.m'); 99 | var newFile = proj.removeSourceFile('Plugins/file.m'), 100 | plugins = proj.pbxGroupByName('Plugins'); 101 | test.equal(plugins.children.length, 0); 102 | test.done(); 103 | }, 104 | 'should have the right values for the PBXGroup entry': function (test) { 105 | proj.addSourceFile('Plugins/file.m'); 106 | var newFile = proj.removeSourceFile('Plugins/file.m'), 107 | plugins = proj.pbxGroupByName('Plugins'), 108 | pluginObj = plugins.children[0]; 109 | 110 | test.ok(!pluginObj); 111 | test.done(); 112 | }, 113 | 'should remove from the PBXSourcesBuildPhase': function (test) { 114 | proj.addSourceFile('Plugins/file.m'); 115 | var newFile = proj.removeSourceFile('Plugins/file.m'), 116 | sources = proj.pbxSourcesBuildPhaseObj(); 117 | 118 | test.equal(sources.files.length, 2); 119 | test.done(); 120 | }, 121 | 'should have the right values for the Sources entry': function (test) { 122 | proj.addSourceFile('Plugins/file.m'); 123 | var newFile = proj.removeSourceFile('Plugins/file.m'), 124 | sources = proj.pbxSourcesBuildPhaseObj(), 125 | sourceObj = sources.files[2]; 126 | 127 | test.ok(!sourceObj); 128 | test.done(); 129 | }, 130 | 'should remove file from PBXFileReference after modified by Xcode': function(test) { 131 | var fileRef = proj.addSourceFile('Plugins/file.m').fileRef; 132 | 133 | // Simulate Xcode's behaviour of stripping quotes around path and name 134 | // properties. 135 | var entry = proj.pbxFileReferenceSection()[fileRef]; 136 | entry.name = entry.name.replace(/^"(.*)"$/, "$1"); 137 | entry.path = entry.path.replace(/^"(.*)"$/, "$1"); 138 | 139 | var newFile = proj.removeSourceFile('Plugins/file.m'); 140 | 141 | test.ok(newFile.uuid); 142 | test.ok(!proj.pbxFileReferenceSection()[fileRef]); 143 | test.done(); 144 | } 145 | } 146 | 147 | -------------------------------------------------------------------------------- /test/addXCConfigurationList.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | proj = new pbx('.'), 5 | debugConfiguration = { 6 | isa: 'XCBuildConfiguration', 7 | buildSettings: { 8 | GCC_PREPROCESSOR_DEFINITIONS: [ 9 | '"DEBUG=1"', 10 | '"$(inherited)"', 11 | ], 12 | INFOPLIST_FILE: "Info.Plist", 13 | LD_RUNPATH_SEARCH_PATHS: '"$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"', 14 | PRODUCT_NAME: '"${TARGET_NAME}"', 15 | SKIP_INSTALL: 'YES' 16 | }, 17 | name: 'Debug' 18 | }, 19 | releaseConfiguration = { 20 | isa: 'XCBuildConfiguration', 21 | buildSettings: { 22 | INFOPLIST_FILE: "Info.Plist", 23 | LD_RUNPATH_SEARCH_PATHS: '"$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"', 24 | PRODUCT_NAME: '"${TARGET_NAME}"', 25 | SKIP_INSTALL: 'YES' 26 | }, 27 | name: 'Release' 28 | }; 29 | 30 | function cleanHash() { 31 | return JSON.parse(fullProjectStr); 32 | } 33 | 34 | exports.setUp = function (callback) { 35 | proj.hash = cleanHash(); 36 | callback(); 37 | } 38 | 39 | exports.addXCConfigurationList = { 40 | 'should return an XCConfigurationList': function (test) { 41 | var myProj = new pbx('test/parser/projects/full.pbxproj').parseSync(), 42 | xcConfigurationList = myProj.addXCConfigurationList([debugConfiguration, releaseConfiguration], 'Release', 'XCConfigurationList Comment'); 43 | 44 | test.ok(typeof xcConfigurationList === 'object'); 45 | test.done(); 46 | }, 47 | 'should set a uuid on the XCConfigurationList': function (test) { 48 | var myProj = new pbx('test/parser/projects/full.pbxproj').parseSync(), 49 | xcConfigurationList = myProj.addXCConfigurationList([debugConfiguration, releaseConfiguration], 'Release', 'XCConfigurationList Comment'); 50 | 51 | test.ok(xcConfigurationList.uuid); 52 | test.done(); 53 | }, 54 | 'should add configurations to pbxBuildConfigurationSection': function (test) { 55 | var myProj = new pbx('test/parser/projects/full.pbxproj').parseSync(), 56 | pbxBuildConfigurationSection = myProj.pbxXCBuildConfigurationSection(), 57 | xcConfigurationList = myProj.addXCConfigurationList([debugConfiguration, releaseConfiguration], 'Release', 'XCConfigurationList Comment'), 58 | xcConfigurationListConfigurations = xcConfigurationList.xcConfigurationList.buildConfigurations; 59 | 60 | for (var index = 0; index < xcConfigurationListConfigurations.length; index++) { 61 | var configuration = xcConfigurationListConfigurations[index]; 62 | test.ok(pbxBuildConfigurationSection[configuration.value]); 63 | } 64 | 65 | test.done(); 66 | }, 67 | 'should add XCConfigurationList to pbxXCConfigurationListSection': function (test) { 68 | var myProj = new pbx('test/parser/projects/full.pbxproj').parseSync(), 69 | pbxXCConfigurationListSection = myProj.pbxXCConfigurationList(); 70 | xcConfigurationList = myProj.addXCConfigurationList([debugConfiguration, releaseConfiguration], 'Release', 'XCConfigurationList Comment'); 71 | 72 | test.ok(pbxXCConfigurationListSection[xcConfigurationList.uuid]); 73 | test.done(); 74 | }, 75 | 'should add XCConfigurationList object correctly': function (test) { 76 | var myProj = new pbx('test/parser/projects/full.pbxproj').parseSync(), 77 | pbxXCConfigurationListSection = myProj.pbxXCConfigurationList(); 78 | xcConfigurationList = myProj.addXCConfigurationList([debugConfiguration, releaseConfiguration], 'Release', 'XCConfigurationList Comment'), 79 | xcConfigurationListInPbx = pbxXCConfigurationListSection[xcConfigurationList.uuid]; 80 | 81 | test.deepEqual(xcConfigurationListInPbx, xcConfigurationList.xcConfigurationList); 82 | test.done(); 83 | }, 84 | 'should add correct configurations to XCConfigurationList and to pbxBuildConfigurationSection': function (test) { 85 | var myProj = new pbx('test/parser/projects/full.pbxproj').parseSync(), 86 | pbxXCConfigurationListSection = myProj.pbxXCConfigurationList(); 87 | pbxBuildConfigurationSection = myProj.pbxXCBuildConfigurationSection(), 88 | xcConfigurationList = myProj.addXCConfigurationList([debugConfiguration, releaseConfiguration], 'Release', 'XCConfigurationList Comment'), 89 | xcConfigurationListConfigurations = xcConfigurationList.xcConfigurationList.buildConfigurations, 90 | expectedConfigurations = [], 91 | xcConfigurationListInPbx = pbxXCConfigurationListSection[xcConfigurationList.uuid]; 92 | 93 | for (var index = 0; index < xcConfigurationListConfigurations.length; index++) { 94 | var configuration = xcConfigurationListConfigurations[index]; 95 | expectedConfigurations.push(pbxBuildConfigurationSection[configuration.value]); 96 | } 97 | 98 | test.deepEqual(expectedConfigurations, [debugConfiguration, releaseConfiguration]); 99 | test.deepEqual(xcConfigurationListInPbx.buildConfigurations, xcConfigurationListConfigurations); 100 | test.done(); 101 | }, 102 | 'should set comments for pbxBuildConfigurations': function (test) { 103 | var myProj = new pbx('test/parser/projects/full.pbxproj').parseSync(), 104 | pbxBuildConfigurationSection = myProj.pbxXCBuildConfigurationSection(), 105 | xcConfigurationList = myProj.addXCConfigurationList([debugConfiguration, releaseConfiguration], 'Release', 'XCConfigurationList Comment'), 106 | xcConfigurationListConfigurations = xcConfigurationList.xcConfigurationList.buildConfigurations; 107 | 108 | for (var index = 0; index < xcConfigurationListConfigurations.length; index++) { 109 | var configuration = xcConfigurationListConfigurations[index]; 110 | test.ok(pbxBuildConfigurationSection[configuration.value + '_comment']); 111 | } 112 | 113 | test.done(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /test/addSourceFile.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | pbxFile = require('../lib/pbxFile'), 5 | proj = new pbx('.'); 6 | 7 | function cleanHash() { 8 | return JSON.parse(fullProjectStr); 9 | } 10 | 11 | exports.setUp = function (callback) { 12 | proj.hash = cleanHash(); 13 | callback(); 14 | } 15 | 16 | exports.addSourceFile = { 17 | 'should return a pbxFile': function (test) { 18 | var newFile = proj.addSourceFile('file.m'); 19 | 20 | test.equal(newFile.constructor, pbxFile); 21 | test.done() 22 | }, 23 | 'should set a uuid on the pbxFile': function (test) { 24 | var newFile = proj.addSourceFile('file.m'); 25 | 26 | test.ok(newFile.uuid); 27 | test.done() 28 | }, 29 | 'should set a fileRef on the pbxFile': function (test) { 30 | var newFile = proj.addSourceFile('file.m'); 31 | 32 | test.ok(newFile.fileRef); 33 | test.done() 34 | }, 35 | 'should populate the PBXBuildFile section with 2 fields': function (test) { 36 | var newFile = proj.addSourceFile('file.m'), 37 | buildFileSection = proj.pbxBuildFileSection(), 38 | bfsLength = Object.keys(buildFileSection).length; 39 | 40 | test.equal(60, bfsLength); 41 | test.ok(buildFileSection[newFile.uuid]); 42 | test.ok(buildFileSection[newFile.uuid + '_comment']); 43 | 44 | test.done(); 45 | }, 46 | 'should add the PBXBuildFile comment correctly': function (test) { 47 | var newFile = proj.addSourceFile('file.m'), 48 | commentKey = newFile.uuid + '_comment', 49 | buildFileSection = proj.pbxBuildFileSection(); 50 | 51 | test.equal(buildFileSection[commentKey], 'file.m in Sources'); 52 | test.done(); 53 | }, 54 | 'should add the PBXBuildFile object correctly': function (test) { 55 | var newFile = proj.addSourceFile('file.m'), 56 | buildFileSection = proj.pbxBuildFileSection(), 57 | buildFileEntry = buildFileSection[newFile.uuid]; 58 | 59 | test.equal(buildFileEntry.isa, 'PBXBuildFile'); 60 | test.equal(buildFileEntry.fileRef, newFile.fileRef); 61 | test.equal(buildFileEntry.fileRef_comment, 'file.m'); 62 | 63 | test.done(); 64 | }, 65 | 'should populate the PBXFileReference section with 2 fields': function (test) { 66 | var newFile = proj.addSourceFile('file.m'), 67 | fileRefSection = proj.pbxFileReferenceSection(), 68 | frsLength = Object.keys(fileRefSection).length; 69 | 70 | test.equal(68, frsLength); 71 | test.ok(fileRefSection[newFile.fileRef]); 72 | test.ok(fileRefSection[newFile.fileRef + '_comment']); 73 | 74 | test.done(); 75 | }, 76 | 'should populate the PBXFileReference comment correctly': function (test) { 77 | var newFile = proj.addSourceFile('file.m'), 78 | fileRefSection = proj.pbxFileReferenceSection(), 79 | commentKey = newFile.fileRef + '_comment'; 80 | 81 | test.equal(fileRefSection[commentKey], 'file.m'); 82 | test.done(); 83 | }, 84 | 'should add the PBXFileReference object correctly': function (test) { 85 | var newFile = proj.addSourceFile('Plugins/file.m'), 86 | fileRefSection = proj.pbxFileReferenceSection(), 87 | fileRefEntry = fileRefSection[newFile.fileRef]; 88 | 89 | test.equal(fileRefEntry.isa, 'PBXFileReference'); 90 | test.equal(fileRefEntry.fileEncoding, 4); 91 | test.equal(fileRefEntry.lastKnownFileType, 'sourcecode.c.objc'); 92 | test.equal(fileRefEntry.name, '"file.m"'); 93 | test.equal(fileRefEntry.path, '"file.m"'); 94 | test.equal(fileRefEntry.sourceTree, '""'); 95 | 96 | test.done(); 97 | }, 98 | 'should add to the Plugins PBXGroup group': function (test) { 99 | var newFile = proj.addSourceFile('Plugins/file.m'), 100 | plugins = proj.pbxGroupByName('Plugins'); 101 | 102 | test.equal(plugins.children.length, 1); 103 | test.done(); 104 | }, 105 | 'should have the right values for the PBXGroup entry': function (test) { 106 | var newFile = proj.addSourceFile('Plugins/file.m'), 107 | plugins = proj.pbxGroupByName('Plugins'), 108 | pluginObj = plugins.children[0]; 109 | 110 | test.equal(pluginObj.comment, 'file.m'); 111 | test.equal(pluginObj.value, newFile.fileRef); 112 | test.done(); 113 | }, 114 | 'should add to the PBXSourcesBuildPhase': function (test) { 115 | var newFile = proj.addSourceFile('Plugins/file.m'), 116 | sources = proj.pbxSourcesBuildPhaseObj(); 117 | 118 | test.equal(sources.files.length, 3); 119 | test.done(); 120 | }, 121 | 'should have the right values for the Sources entry': function (test) { 122 | var newFile = proj.addSourceFile('Plugins/file.m'), 123 | sources = proj.pbxSourcesBuildPhaseObj(), 124 | sourceObj = sources.files[2]; 125 | 126 | test.equal(sourceObj.comment, 'file.m in Sources'); 127 | test.equal(sourceObj.value, newFile.uuid); 128 | test.done(); 129 | }, 130 | 'duplicate entries': { 131 | 'should return false': function (test) { 132 | var newFile = proj.addSourceFile('Plugins/file.m'); 133 | 134 | test.ok(!proj.addSourceFile('Plugins/file.m')); 135 | test.done(); 136 | }, 137 | 'should not add another entry anywhere': function (test) { 138 | var newFile = proj.addSourceFile('Plugins/file.m'), 139 | buildFileSection = proj.pbxBuildFileSection(), 140 | bfsLength = Object.keys(buildFileSection).length, 141 | fileRefSection = proj.pbxFileReferenceSection(), 142 | frsLength = Object.keys(fileRefSection).length, 143 | plugins = proj.pbxGroupByName('Plugins'), 144 | sources = proj.pbxSourcesBuildPhaseObj(); 145 | 146 | // duplicate! 147 | proj.addSourceFile('Plugins/file.m'); 148 | 149 | test.equal(60, bfsLength); // BuildFileSection 150 | test.equal(68, frsLength); // FileReferenceSection 151 | test.equal(plugins.children.length, 1); // Plugins pbxGroup 152 | test.equal(sources.files.length, 3); // SourcesBuildPhhase 153 | test.done(); 154 | } 155 | } 156 | } 157 | 158 | -------------------------------------------------------------------------------- /lib/parser/pbxproj.pegjs: -------------------------------------------------------------------------------- 1 | { 2 | function merge(hash, secondHash) { 3 | secondHash = secondHash[0] 4 | for(var i in secondHash) { 5 | hash[i] = merge_obj(hash[i], secondHash[i]); 6 | } 7 | 8 | return hash; 9 | } 10 | 11 | function merge_obj(obj, secondObj) { 12 | if (!obj) 13 | return secondObj; 14 | 15 | for(var i in secondObj) 16 | obj[i] = merge_obj(obj[i], secondObj[i]); 17 | 18 | return obj; 19 | } 20 | } 21 | 22 | /* 23 | * Project: point of entry from pbxproj file 24 | */ 25 | Project 26 | = headComment:SingleLineComment? InlineComment? _ obj:Object NewLine _ 27 | { 28 | var proj = Object.create(null) 29 | proj.project = obj 30 | 31 | if (headComment) { 32 | proj.headComment = headComment 33 | } 34 | 35 | return proj; 36 | } 37 | 38 | /* 39 | * Object: basic hash data structure with Assignments 40 | */ 41 | Object 42 | = "{" obj:(AssignmentList / EmptyBody) "}" 43 | { return obj } 44 | 45 | EmptyBody 46 | = _ 47 | { return Object.create(null) } 48 | 49 | AssignmentList 50 | = _ head:Assignment _ tail:AssignmentList* _ 51 | { 52 | if (tail) return merge(head,tail) 53 | else return head 54 | } 55 | / _ head:DelimitedSection _ tail:AssignmentList* 56 | { 57 | if (tail) return merge(head,tail) 58 | else return head 59 | } 60 | 61 | /* 62 | * Assignments 63 | * can be simple "key = value" 64 | * or commented "key /* real key * / = value" 65 | */ 66 | Assignment 67 | = SimpleAssignment / CommentedAssignment 68 | 69 | SimpleAssignment 70 | = id:Identifier _ "=" _ val:Value ";" 71 | { 72 | var result = Object.create(null); 73 | result[id] = val 74 | return result 75 | } 76 | 77 | CommentedAssignment 78 | = commentedId:CommentedIdentifier _ "=" _ val:Value ";" 79 | { 80 | var result = Object.create(null), 81 | commentKey = commentedId.id + '_comment'; 82 | 83 | result[commentedId.id] = val; 84 | result[commentKey] = commentedId[commentKey]; 85 | return result; 86 | 87 | } 88 | / 89 | id:Identifier _ "=" _ commentedVal:CommentedValue ";" 90 | { 91 | var result = Object.create(null); 92 | result[id] = commentedVal.value; 93 | result[id + "_comment"] = commentedVal.comment; 94 | return result; 95 | } 96 | 97 | CommentedIdentifier 98 | = id:Identifier _ comment:InlineComment 99 | { 100 | var result = Object.create(null); 101 | result.id = id; 102 | result[id + "_comment"] = comment.trim(); 103 | return result 104 | } 105 | 106 | CommentedValue 107 | = literal:Value _ comment:InlineComment 108 | { 109 | var result = Object.create(null) 110 | result.comment = comment.trim(); 111 | result.value = literal.trim(); 112 | return result; 113 | } 114 | 115 | InlineComment 116 | = InlineCommentOpen body:[^*]+ InlineCommentClose 117 | { return body.join('') } 118 | 119 | InlineCommentOpen 120 | = "/*" 121 | 122 | InlineCommentClose 123 | = "*/" 124 | 125 | /* 126 | * DelimitedSection - ad hoc project structure pbxproj files use 127 | */ 128 | DelimitedSection 129 | = begin:DelimitedSectionBegin _ fields:(AssignmentList / EmptyBody) _ DelimitedSectionEnd 130 | { 131 | var section = Object.create(null); 132 | section[begin.name] = fields 133 | 134 | return section 135 | } 136 | 137 | DelimitedSectionBegin 138 | = "/* Begin " sectionName:Identifier " section */" NewLine 139 | { return { name: sectionName } } 140 | 141 | DelimitedSectionEnd 142 | = "/* End " sectionName:Identifier " section */" NewLine 143 | { return { name: sectionName } } 144 | 145 | /* 146 | * Arrays: lists of values, possible wth comments 147 | */ 148 | Array 149 | = "(" arr:(ArrayBody / EmptyArray ) ")" { return arr } 150 | 151 | EmptyArray 152 | = _ { return [] } 153 | 154 | ArrayBody 155 | = _ head:ArrayEntry _ tail:ArrayBody? _ 156 | { 157 | if (tail) { 158 | tail.unshift(head); 159 | return tail; 160 | } else { 161 | return [head]; 162 | } 163 | } 164 | 165 | ArrayEntry 166 | = SimpleArrayEntry / CommentedArrayEntry 167 | 168 | SimpleArrayEntry 169 | = val:Value EndArrayEntry { return val } 170 | 171 | CommentedArrayEntry 172 | = val:Value _ comment:InlineComment EndArrayEntry 173 | { 174 | var result = Object.create(null); 175 | result.value = val.trim(); 176 | result.comment = comment.trim(); 177 | return result; 178 | } 179 | 180 | EndArrayEntry 181 | = "," / _ &")" 182 | 183 | /* 184 | * Identifiers and Values 185 | */ 186 | Identifier 187 | = id:[A-Za-z0-9_.]+ { return id.join('') } 188 | / QuotedString 189 | 190 | Value 191 | = Object / Array / NumberValue / StringValue 192 | 193 | NumberValue 194 | = DecimalValue / IntegerValue 195 | 196 | DecimalValue 197 | = decimal:(IntegerValue "." IntegerValue) 198 | { 199 | // store decimals as strings 200 | // as JS doesn't differentiate bw strings and numbers 201 | return decimal.join('') 202 | } 203 | 204 | IntegerValue 205 | = !Alpha number:Digit+ !NonTerminator 206 | { return parseInt(number.join(''), 10) } 207 | 208 | StringValue 209 | = QuotedString / LiteralString 210 | 211 | QuotedString 212 | = DoubleQuote str:QuotedBody DoubleQuote { return '"' + str + '"' } 213 | 214 | QuotedBody 215 | = str:NonQuote+ { return str.join('') } 216 | 217 | NonQuote 218 | = EscapedQuote / !DoubleQuote char:. { return char } 219 | 220 | EscapedQuote 221 | = "\\" DoubleQuote { return '\\"' } 222 | 223 | LiteralString 224 | = literal:LiteralChar+ { return literal.join('') } 225 | 226 | LiteralChar 227 | = !InlineCommentOpen !LineTerminator char:NonTerminator 228 | { return char } 229 | 230 | NonTerminator 231 | = [^;,\n] 232 | 233 | /* 234 | * SingleLineComment - used for the encoding comment 235 | */ 236 | SingleLineComment 237 | = "//" _ contents:OneLineString NewLine 238 | { return contents } 239 | 240 | OneLineString 241 | = contents:NonLine* 242 | { return contents.join('') } 243 | 244 | /* 245 | * Simple character checking rules 246 | */ 247 | Digit 248 | = [0-9] 249 | 250 | Alpha 251 | = [A-Za-z] 252 | 253 | DoubleQuote 254 | = '"' 255 | 256 | _ "whitespace" 257 | = whitespace* 258 | 259 | whitespace 260 | = NewLine / [\t ] 261 | 262 | NonLine 263 | = !NewLine char:Char 264 | { return char } 265 | 266 | LineTerminator 267 | = NewLine / ";" 268 | 269 | NewLine 270 | = [\n\r] 271 | 272 | Char 273 | = . 274 | -------------------------------------------------------------------------------- /test/parser/projects/file-references.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 45; 7 | objects = { 8 | /* Begin PBXFileReference section */ 9 | 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 10 | 1D3623240D0F684500981E51 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 11 | 1D3623250D0F684500981E51 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 12 | 1D6058910D05DD3D006BFB54 /* KitchenSinktablet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "KitchenSinktablet.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 13 | 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 14 | 1F766FDD13BBADB100FB74C0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Localizable.strings; sourceTree = ""; }; 15 | 1F766FE013BBADB100FB74C0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = Localizable.strings; sourceTree = ""; }; 16 | 288765FC0DF74451002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 17 | 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 18 | 301BF52D109A57CC0062928A /* PhoneGapLib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = PhoneGapLib.xcodeproj; sourceTree = PHONEGAPLIB; }; 19 | 301BF56E109A69640062928A /* www */ = {isa = PBXFileReference; lastKnownFileType = folder; path = www; sourceTree = SOURCE_ROOT; }; 20 | 301BF5B4109A6A2B0062928A /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; 21 | 301BF5B6109A6A2B0062928A /* AddressBookUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBookUI.framework; path = System/Library/Frameworks/AddressBookUI.framework; sourceTree = SDKROOT; }; 22 | 301BF5B8109A6A2B0062928A /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 23 | 301BF5BA109A6A2B0062928A /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 24 | 301BF5BC109A6A2B0062928A /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; 25 | 301BF5BE109A6A2B0062928A /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; 26 | 301BF5C0109A6A2B0062928A /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; }; 27 | 301BF5C2109A6A2B0062928A /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 28 | 301BF5C4109A6A2B0062928A /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 29 | 3053AC6E109B7857006FCFE7 /* VERSION */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = VERSION; sourceTree = PHONEGAPLIB; }; 30 | 305D5FD0115AB8F900A74A75 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 31 | 3072F99613A8081B00425683 /* Capture.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = Capture.bundle; path = Resources/Capture.bundle; sourceTree = ""; }; 32 | 307D28A1123043350040C0FA /* PhoneGapBuildSettings.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = PhoneGapBuildSettings.xcconfig; sourceTree = ""; }; 33 | 308D052E1370CCF300D202BF /* icon-72.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-72.png"; sourceTree = ""; }; 34 | 308D052F1370CCF300D202BF /* 1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 1.png; sourceTree = ""; }; 35 | 308D05301370CCF300D202BF /* icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon@2x.png"; sourceTree = ""; }; 36 | 308D05341370CCF300D202BF /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; 37 | 308D05351370CCF300D202BF /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; 38 | 30E1352610E2C1420031B30D /* PhoneGap.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = PhoneGap.plist; sourceTree = ""; }; 39 | 30E5649113A7FCAF007403D8 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; 40 | 32CA4F630368D1EE00C91783 /* KitchenSinktablet-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "KitchenSinktablet-Prefix.pch"; sourceTree = ""; }; 41 | 8D1107310486CEB800E47090 /* KitchenSinktablet-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "KitchenSinktablet-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; 42 | /* End PBXFileReference section */ 43 | }; 44 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 45 | } 46 | -------------------------------------------------------------------------------- /test/pbxFile.js: -------------------------------------------------------------------------------- 1 | var pbxFile = require('../lib/pbxFile'); 2 | 3 | exports['lastType'] = { 4 | 'should detect that a .m path means sourcecode.c.objc': function (test) { 5 | var sourceFile = new pbxFile('Plugins/ChildBrowser.m'); 6 | 7 | test.equal('sourcecode.c.objc', sourceFile.lastType); 8 | test.done(); 9 | }, 10 | 11 | 'should detect that a .h path means sourceFile.c.h': function (test) { 12 | var sourceFile = new pbxFile('Plugins/ChildBrowser.h'); 13 | 14 | test.equal('sourcecode.c.h', sourceFile.lastType); 15 | test.done(); 16 | }, 17 | 18 | 'should detect that a .bundle path means "wrapper.plug-in"': function (test) { 19 | var sourceFile = new pbxFile('Plugins/ChildBrowser.bundle'); 20 | 21 | test.equal('"wrapper.plug-in"', sourceFile.lastType); 22 | test.done(); 23 | }, 24 | 25 | 'should detect that a .xib path means file.xib': function (test) { 26 | var sourceFile = new pbxFile('Plugins/ChildBrowser.xib'); 27 | 28 | test.equal('file.xib', sourceFile.lastType); 29 | test.done(); 30 | }, 31 | 32 | 'should detect that a .dylib path means "compiled.mach-o.dylib"': function (test) { 33 | var sourceFile = new pbxFile('libsqlite3.dylib'); 34 | 35 | test.equal('"compiled.mach-o.dylib"', sourceFile.lastType); 36 | test.done(); 37 | }, 38 | 39 | 'should detect that a .framework path means wrapper.framework': function (test) { 40 | var sourceFile = new pbxFile('MessageUI.framework'); 41 | 42 | test.equal('wrapper.framework', sourceFile.lastType); 43 | test.done(); 44 | }, 45 | 46 | 'should detect that a .framework/.a path means archive.ar': function (test) { 47 | var sourceFile = new pbxFile('MessageUI.framework/libGoogleAnalytics.a'); 48 | 49 | test.equal('archive.ar', sourceFile.lastType); 50 | test.done(); 51 | }, 52 | 53 | 'should detect that a .a path means archive.ar': function (test) { 54 | var sourceFile = new pbxFile('libGoogleAnalytics.a'); 55 | 56 | test.equal('archive.ar', sourceFile.lastType); 57 | test.done(); 58 | }, 59 | 60 | 'should allow lastType to be overridden': function (test) { 61 | var sourceFile = new pbxFile('Plugins/ChildBrowser.m', 62 | { lastType: 'somestupidtype' }); 63 | 64 | test.equal('somestupidtype', sourceFile.lastType); 65 | test.done(); 66 | }, 67 | 68 | 'should set lastType to unknown if undetectable': function (test) { 69 | var sourceFile = new pbxFile('Plugins/ChildBrowser.guh'); 70 | 71 | test.equal('unknown', sourceFile.lastType); 72 | test.done(); 73 | } 74 | } 75 | 76 | exports['group'] = { 77 | 'should be Sources for source files': function (test) { 78 | var sourceFile = new pbxFile('Plugins/ChildBrowser.m'); 79 | 80 | test.equal('Sources', sourceFile.group); 81 | test.done(); 82 | }, 83 | 'should be Frameworks for frameworks': function (test) { 84 | var framework = new pbxFile('libsqlite3.dylib'); 85 | 86 | test.equal('Frameworks', framework.group); 87 | test.done(); 88 | }, 89 | 'should be Resources for all other files': function (test) { 90 | var headerFile = new pbxFile('Plugins/ChildBrowser.h'), 91 | xibFile = new pbxFile('Plugins/ChildBrowser.xib'); 92 | 93 | test.equal('Resources', headerFile.group); 94 | test.equal('Resources', xibFile.group); 95 | test.done(); 96 | }, 97 | 'should be Frameworks for archives': function (test) { 98 | var archive = new pbxFile('libGoogleAnalytics.a'); 99 | 100 | test.equal('Frameworks', archive.group); 101 | test.done(); 102 | } 103 | } 104 | 105 | exports['basename'] = { 106 | 'should be as expected': function (test) { 107 | var sourceFile = new pbxFile('Plugins/ChildBrowser.m'); 108 | 109 | test.equal('ChildBrowser.m', sourceFile.basename); 110 | test.done(); 111 | } 112 | } 113 | 114 | exports['sourceTree'] = { 115 | 'should be SDKROOT for dylibs': function (test) { 116 | var sourceFile = new pbxFile('libsqlite3.dylib'); 117 | 118 | test.equal('SDKROOT', sourceFile.sourceTree); 119 | test.done(); 120 | }, 121 | 122 | 'should be SDKROOT for frameworks': function (test) { 123 | var sourceFile = new pbxFile('MessageUI.framework'); 124 | 125 | test.equal('SDKROOT', sourceFile.sourceTree); 126 | test.done(); 127 | }, 128 | 129 | 'should default to "" otherwise': function (test) { 130 | var sourceFile = new pbxFile('Plugins/ChildBrowser.m'); 131 | 132 | test.equal('""', sourceFile.sourceTree); 133 | test.done(); 134 | }, 135 | 136 | 'should be overridable either way': function (test) { 137 | var sourceFile = new pbxFile('Plugins/ChildBrowser.m', 138 | { sourceTree: 'SOMETHING'}); 139 | 140 | test.equal('SOMETHING', sourceFile.sourceTree); 141 | test.done(); 142 | }, 143 | 144 | 'should be "" for archives': function (test) { 145 | var archive = new pbxFile('libGoogleAnalytics.a'); 146 | 147 | test.equal('""', archive.sourceTree); 148 | test.done(); 149 | } 150 | } 151 | 152 | exports['path'] = { 153 | 'should be "usr/lib" for dylibs (relative to SDKROOT)': function (test) { 154 | var sourceFile = new pbxFile('libsqlite3.dylib'); 155 | 156 | test.equal('usr/lib/libsqlite3.dylib', sourceFile.path); 157 | test.done(); 158 | }, 159 | 160 | 'should be "System/Library/Frameworks" for frameworks': function (test) { 161 | var sourceFile = new pbxFile('MessageUI.framework'); 162 | 163 | test.equal('System/Library/Frameworks/MessageUI.framework', sourceFile.path); 164 | test.done(); 165 | }, 166 | 167 | 168 | 'should default to the first argument otherwise': function (test) { 169 | var sourceFile = new pbxFile('Plugins/ChildBrowser.m'); 170 | 171 | test.equal('Plugins/ChildBrowser.m', sourceFile.path); 172 | test.done(); 173 | } 174 | } 175 | 176 | exports['settings'] = { 177 | 'should not be defined by default': function (test) { 178 | var sourceFile = new pbxFile('social.framework'); 179 | 180 | test.equal(undefined, sourceFile.settings); 181 | test.done(); 182 | }, 183 | 184 | 'should be undefined if weak is false or non-boolean': function (test) { 185 | var sourceFile1 = new pbxFile('social.framework', 186 | { weak: false }); 187 | var sourceFile2 = new pbxFile('social.framework', 188 | { weak: 'bad_value' }); 189 | 190 | test.equal(undefined, sourceFile1.settings); 191 | test.equal(undefined, sourceFile2.settings); 192 | test.done(); 193 | }, 194 | 195 | 'should be {ATTRIBUTES:["Weak"]} if weak linking specified': function (test) { 196 | var sourceFile = new pbxFile('social.framework', 197 | { weak: true }); 198 | 199 | test.deepEqual({ATTRIBUTES:["Weak"]}, sourceFile.settings); 200 | test.done(); 201 | }, 202 | 203 | 'should be {COMPILER_FLAGS:"blah"} if compiler flags specified': function (test) { 204 | var sourceFile = new pbxFile('Plugins/BarcodeScanner.m', 205 | { compilerFlags: "-std=c++11 -fno-objc-arc" }); 206 | 207 | test.deepEqual({COMPILER_FLAGS:'"-std=c++11 -fno-objc-arc"'}, sourceFile.settings); 208 | test.done(); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /test/removeResourceFile.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | pbxFile = require('../lib/pbxFile'), 5 | proj = new pbx('.'); 6 | 7 | function cleanHash() { 8 | return JSON.parse(fullProjectStr); 9 | } 10 | 11 | exports.setUp = function (callback) { 12 | proj.hash = cleanHash(); 13 | callback(); 14 | } 15 | 16 | exports.removeResourceFile = { 17 | 'should return a pbxFile': function (test) { 18 | var newFile = proj.addResourceFile('assets.bundle'); 19 | 20 | test.equal(newFile.constructor, pbxFile); 21 | 22 | var deletedFile = proj.removeResourceFile('assets.bundle'); 23 | 24 | test.equal(deletedFile.constructor, pbxFile); 25 | 26 | test.done() 27 | }, 28 | 'should set a uuid on the pbxFile': function (test) { 29 | var newFile = proj.addResourceFile('assets.bundle'); 30 | 31 | test.ok(newFile.uuid); 32 | 33 | var deletedFile = proj.removeResourceFile('assets.bundle'); 34 | 35 | test.ok(deletedFile.uuid); 36 | 37 | test.done() 38 | }, 39 | 'should set a fileRef on the pbxFile': function (test) { 40 | var newFile = proj.addResourceFile('assets.bundle'); 41 | 42 | test.ok(newFile.fileRef); 43 | 44 | var deletedFile = proj.removeResourceFile('assets.bundle'); 45 | 46 | test.ok(deletedFile.fileRef); 47 | 48 | test.done() 49 | }, 50 | 'should remove 2 fields from the PBXBuildFile section': function (test) { 51 | var newFile = proj.addResourceFile('assets.bundle'), 52 | buildFileSection = proj.pbxBuildFileSection(), 53 | bfsLength = Object.keys(buildFileSection).length; 54 | 55 | test.equal(60, bfsLength); 56 | test.ok(buildFileSection[newFile.uuid]); 57 | test.ok(buildFileSection[newFile.uuid + '_comment']); 58 | 59 | var deletedFile = proj.removeResourceFile('assets.bundle'), 60 | buildFileSection = proj.pbxBuildFileSection(), 61 | bfsLength = Object.keys(buildFileSection).length; 62 | 63 | test.equal(58, bfsLength); 64 | test.ok(!buildFileSection[deletedFile.uuid]); 65 | test.ok(!buildFileSection[deletedFile.uuid + '_comment']); 66 | 67 | test.done(); 68 | }, 69 | 'should remove the PBXBuildFile comment correctly': function (test) { 70 | var newFile = proj.addResourceFile('assets.bundle'), 71 | commentKey = newFile.uuid + '_comment', 72 | buildFileSection = proj.pbxBuildFileSection(); 73 | 74 | test.equal(buildFileSection[commentKey], 'assets.bundle in Resources'); 75 | 76 | var deletedFile = proj.removeResourceFile('assets.bundle'), 77 | commentKey = deletedFile.uuid + '_comment', 78 | buildFileSection = proj.pbxBuildFileSection(); 79 | 80 | test.ok(!buildFileSection[commentKey]); 81 | 82 | test.done(); 83 | }, 84 | 'should remove the PBXBuildFile object correctly': function (test) { 85 | var newFile = proj.addResourceFile('assets.bundle'), 86 | buildFileSection = proj.pbxBuildFileSection(), 87 | buildFileEntry = buildFileSection[newFile.uuid]; 88 | 89 | test.equal(buildFileEntry.isa, 'PBXBuildFile'); 90 | test.equal(buildFileEntry.fileRef, newFile.fileRef); 91 | test.equal(buildFileEntry.fileRef_comment, 'assets.bundle'); 92 | 93 | var deletedFile = proj.removeResourceFile('assets.bundle'), 94 | buildFileSection = proj.pbxBuildFileSection(), 95 | buildFileEntry = buildFileSection[deletedFile.uuid]; 96 | 97 | test.ok(!buildFileEntry); 98 | 99 | test.done(); 100 | }, 101 | 'should remove 2 fields from the PBXFileReference section': function (test) { 102 | var newFile = proj.addResourceFile('assets.bundle'), 103 | fileRefSection = proj.pbxFileReferenceSection(), 104 | frsLength = Object.keys(fileRefSection).length; 105 | 106 | test.equal(68, frsLength); 107 | test.ok(fileRefSection[newFile.fileRef]); 108 | test.ok(fileRefSection[newFile.fileRef + '_comment']); 109 | 110 | var deletedFile = proj.removeResourceFile('assets.bundle'), 111 | fileRefSection = proj.pbxFileReferenceSection(), 112 | frsLength = Object.keys(fileRefSection).length; 113 | 114 | test.equal(66, frsLength); 115 | test.ok(!fileRefSection[deletedFile.fileRef]); 116 | test.ok(!fileRefSection[deletedFile.fileRef + '_comment']); 117 | 118 | test.done(); 119 | }, 120 | 'should populate the PBXFileReference comment correctly': function (test) { 121 | var newFile = proj.addResourceFile('assets.bundle'), 122 | fileRefSection = proj.pbxFileReferenceSection(), 123 | commentKey = newFile.fileRef + '_comment'; 124 | 125 | test.equal(fileRefSection[commentKey], 'assets.bundle'); 126 | 127 | var deletedFile = proj.removeResourceFile('assets.bundle'), 128 | fileRefSection = proj.pbxFileReferenceSection(), 129 | commentKey = deletedFile.fileRef + '_comment'; 130 | 131 | test.ok(!fileRefSection[commentKey]); 132 | test.done(); 133 | }, 134 | 'should remove the PBXFileReference object correctly': function (test) { 135 | delete proj.pbxGroupByName('Resources').path; 136 | 137 | var newFile = proj.addResourceFile('Resources/assets.bundle'), 138 | fileRefSection = proj.pbxFileReferenceSection(), 139 | fileRefEntry = fileRefSection[newFile.fileRef]; 140 | 141 | test.equal(fileRefEntry.isa, 'PBXFileReference'); 142 | test.equal(fileRefEntry.fileEncoding, undefined); 143 | test.equal(fileRefEntry.lastKnownFileType, '"wrapper.plug-in"'); 144 | test.equal(fileRefEntry.name, '"assets.bundle"'); 145 | test.equal(fileRefEntry.path, '"Resources/assets.bundle"'); 146 | test.equal(fileRefEntry.sourceTree, '""'); 147 | 148 | var deletedFile = proj.removeResourceFile('Resources/assets.bundle'), 149 | fileRefSection = proj.pbxFileReferenceSection(), 150 | fileRefEntry = fileRefSection[deletedFile.fileRef]; 151 | 152 | test.ok(!fileRefEntry); 153 | 154 | test.done(); 155 | }, 156 | 'should remove from the Resources PBXGroup group': function (test) { 157 | var newFile = proj.addResourceFile('Resources/assets.bundle'), 158 | resources = proj.pbxGroupByName('Resources'); 159 | 160 | test.equal(resources.children.length, 10); 161 | 162 | var deletedFile = proj.removeResourceFile('Resources/assets.bundle'), 163 | resources = proj.pbxGroupByName('Resources'); 164 | 165 | test.equal(resources.children.length, 9); 166 | test.done(); 167 | }, 168 | 'should remove from the PBXSourcesBuildPhase': function (test) { 169 | var newFile = proj.addResourceFile('Resources/assets.bundle'), 170 | sources = proj.pbxResourcesBuildPhaseObj(); 171 | 172 | test.equal(sources.files.length, 13); 173 | 174 | var deletedFile = proj.removeResourceFile('Resources/assets.bundle'), 175 | sources = proj.pbxResourcesBuildPhaseObj(); 176 | 177 | test.equal(sources.files.length, 12); 178 | test.done(); 179 | }, 180 | tearDown: function (callback) { 181 | delete proj.pbxGroupByName('Resources').path; 182 | callback(); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /test/addTargetDependency.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | proj = new pbx('.'); 5 | 6 | function cleanHash() { 7 | return JSON.parse(fullProjectStr); 8 | } 9 | 10 | exports.setUp = function (callback) { 11 | proj.hash = cleanHash(); 12 | callback(); 13 | } 14 | 15 | exports.addTargetDependency = { 16 | 'should return undefined when no target specified': function (test) { 17 | var buildPhase = proj.addTargetDependency(); 18 | 19 | test.ok(typeof buildPhase === 'undefined'); 20 | test.done() 21 | }, 22 | 'should throw when target not found in nativeTargetsSection': function (test) { 23 | test.throws(function() { 24 | proj.addTargetDependency('invalidTarget'); 25 | }); 26 | test.done() 27 | }, 28 | 'should throw when any dependency target not found in nativeTargetsSection': function (test) { 29 | test.throws(function() { 30 | proj.addTargetDependency('1D6058900D05DD3D006BFB54', ['invalidTarget']); 31 | }); 32 | test.done() 33 | }, 34 | 'should return the pbxTarget': function (test) { 35 | var target = proj.addTargetDependency('1D6058900D05DD3D006BFB54', ['1D6058900D05DD3D006BFB54']); 36 | 37 | test.ok(typeof target == 'object'); 38 | test.ok(target.uuid); 39 | test.ok(target.target); 40 | test.done(); 41 | }, 42 | 'should add targetDependencies to target': function (test) { 43 | var targetInPbxProj = proj.pbxNativeTarget()['1D6058900D05DD3D006BFB55']; 44 | test.deepEqual(targetInPbxProj.dependencies, []); 45 | 46 | var target = proj.addTargetDependency('1D6058900D05DD3D006BFB55', ['1D6058900D05DD3D006BFB54', '1D6058900D05DD3D006BFB55']).target; 47 | test.deepEqual(targetInPbxProj.dependencies, target.dependencies) 48 | test.done() 49 | }, 50 | 'should create a PBXTargetDependency for each dependency target': function (test) { 51 | var pbxTargetDependencySection = proj.hash.project.objects['PBXTargetDependency'], 52 | target = proj.addTargetDependency('1D6058900D05DD3D006BFB54', ['1D6058900D05DD3D006BFB54', '1D6058900D05DD3D006BFB55']).target; 53 | 54 | for (var index = 0; index < target.dependencies.length; index++) { 55 | var dependency = target.dependencies[index].value; 56 | test.ok(pbxTargetDependencySection[dependency]); 57 | } 58 | 59 | test.done() 60 | }, 61 | 'should set right comment for each dependency target': function (test) { 62 | var pbxTargetDependencySection = proj.hash.project.objects['PBXTargetDependency'], 63 | target = proj.addTargetDependency('1D6058900D05DD3D006BFB54', ['1D6058900D05DD3D006BFB54', '1D6058900D05DD3D006BFB55']).target; 64 | 65 | for (var index = 0; index < target.dependencies.length; index++) { 66 | var dependencyCommentKey = target.dependencies[index].value + '_comment'; 67 | test.equal(pbxTargetDependencySection[dependencyCommentKey], 'PBXTargetDependency'); 68 | } 69 | 70 | test.done() 71 | }, 72 | 'should create a PBXContainerItemProxy for each PBXTargetDependency': function (test) { 73 | var pbxTargetDependencySection = proj.hash.project.objects['PBXTargetDependency'], 74 | pbxContainerItemProxySection = proj.hash.project.objects['PBXContainerItemProxy'], 75 | target = proj.addTargetDependency('1D6058900D05DD3D006BFB54', ['1D6058900D05DD3D006BFB54', '1D6058900D05DD3D006BFB55']).target; 76 | 77 | for (var index = 0; index < target.dependencies.length; index++) { 78 | var dependency = target.dependencies[index].value, 79 | targetProxy = pbxTargetDependencySection[dependency]['targetProxy']; 80 | 81 | test.ok(pbxContainerItemProxySection[targetProxy]); 82 | } 83 | 84 | test.done() 85 | }, 86 | 'should set each PBXContainerItemProxy`s remoteGlobalIDString correctly': function (test) { 87 | var pbxTargetDependencySection = proj.hash.project.objects['PBXTargetDependency'], 88 | pbxContainerItemProxySection = proj.hash.project.objects['PBXContainerItemProxy'], 89 | target = proj.addTargetDependency('1D6058900D05DD3D006BFB55', ['1D6058900D05DD3D006BFB54', '1D6058900D05DD3D006BFB55']).target, 90 | remoteGlobalIDStrings = []; 91 | 92 | for (var index = 0; index < target.dependencies.length; index++) { 93 | var dependency = target.dependencies[index].value, 94 | targetProxy = pbxTargetDependencySection[dependency]['targetProxy']; 95 | 96 | remoteGlobalIDStrings.push(pbxContainerItemProxySection[targetProxy]['remoteGlobalIDString']); 97 | } 98 | 99 | test.deepEqual(remoteGlobalIDStrings, ['1D6058900D05DD3D006BFB54', '1D6058900D05DD3D006BFB55']); 100 | test.done() 101 | }, 102 | 'should set each PBXContainerItemProxy`s remoteInfo correctly': function (test) { 103 | var pbxTargetDependencySection = proj.hash.project.objects['PBXTargetDependency'], 104 | pbxContainerItemProxySection = proj.hash.project.objects['PBXContainerItemProxy'], 105 | target = proj.addTargetDependency('1D6058900D05DD3D006BFB55', ['1D6058900D05DD3D006BFB54', '1D6058900D05DD3D006BFB55']).target, 106 | remoteInfoArray = []; 107 | 108 | for (var index = 0; index < target.dependencies.length; index++) { 109 | var dependency = target.dependencies[index].value, 110 | targetProxy = pbxTargetDependencySection[dependency]['targetProxy']; 111 | 112 | remoteInfoArray.push(pbxContainerItemProxySection[targetProxy]['remoteInfo']); 113 | } 114 | 115 | test.deepEqual(remoteInfoArray, ['"KitchenSinktablet"', '"TestApp"']); 116 | test.done() 117 | }, 118 | 'should set each PBXContainerItemProxy`s containerPortal correctly': function (test) { 119 | var pbxTargetDependencySection = proj.hash.project.objects['PBXTargetDependency'], 120 | pbxContainerItemProxySection = proj.hash.project.objects['PBXContainerItemProxy'], 121 | target = proj.addTargetDependency('1D6058900D05DD3D006BFB55', ['1D6058900D05DD3D006BFB54', '1D6058900D05DD3D006BFB55']).target; 122 | 123 | for (var index = 0; index < target.dependencies.length; index++) { 124 | var dependency = target.dependencies[index].value, 125 | targetProxy = pbxTargetDependencySection[dependency]['targetProxy']; 126 | 127 | test.equal(pbxContainerItemProxySection[targetProxy]['containerPortal'], proj.hash.project['rootObject']); 128 | } 129 | 130 | test.done() 131 | }, 132 | 'should set each PBXContainerItemProxy`s proxyType correctly': function (test) { 133 | var pbxTargetDependencySection = proj.hash.project.objects['PBXTargetDependency'], 134 | pbxContainerItemProxySection = proj.hash.project.objects['PBXContainerItemProxy'], 135 | target = proj.addTargetDependency('1D6058900D05DD3D006BFB55', ['1D6058900D05DD3D006BFB54', '1D6058900D05DD3D006BFB55']).target; 136 | 137 | for (var index = 0; index < target.dependencies.length; index++) { 138 | var dependency = target.dependencies[index].value, 139 | targetProxy = pbxTargetDependencySection[dependency]['targetProxy']; 140 | 141 | test.equal(pbxContainerItemProxySection[targetProxy]['proxyType'], 1); 142 | } 143 | 144 | test.done() 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /lib/pbxWriter.js: -------------------------------------------------------------------------------- 1 | var pbxProj = require('./pbxProject'), 2 | util = require('util'), 3 | f = util.format, 4 | INDENT = '\t', 5 | COMMENT_KEY = /_comment$/, 6 | QUOTED = /^"(.*)"$/, 7 | EventEmitter = require('events').EventEmitter 8 | 9 | // indentation 10 | function i(x) { 11 | if (x <=0) 12 | return ''; 13 | else 14 | return INDENT + i(x-1); 15 | } 16 | 17 | function comment(key, parent) { 18 | var text = parent[key + '_comment']; 19 | 20 | if (text) 21 | return text; 22 | else 23 | return null; 24 | } 25 | 26 | // copied from underscore 27 | function isObject(obj) { 28 | return obj === Object(obj) 29 | } 30 | 31 | function isArray(obj) { 32 | return Array.isArray(obj) 33 | } 34 | 35 | function pbxWriter(contents) { 36 | this.contents = contents; 37 | this.sync = false; 38 | this.indentLevel = 0; 39 | } 40 | 41 | util.inherits(pbxWriter, EventEmitter); 42 | 43 | pbxWriter.prototype.write = function (str) { 44 | var fmt = f.apply(null, arguments); 45 | 46 | if (this.sync) { 47 | this.buffer += f("%s%s", i(this.indentLevel), fmt); 48 | } else { 49 | // do stream write 50 | } 51 | } 52 | 53 | pbxWriter.prototype.writeFlush = function (str) { 54 | var oldIndent = this.indentLevel; 55 | 56 | this.indentLevel = 0; 57 | 58 | this.write.apply(this, arguments) 59 | 60 | this.indentLevel = oldIndent; 61 | } 62 | 63 | pbxWriter.prototype.writeSync = function () { 64 | this.sync = true; 65 | this.buffer = ""; 66 | 67 | this.writeHeadComment(); 68 | this.writeProject(); 69 | 70 | return this.buffer; 71 | } 72 | 73 | pbxWriter.prototype.writeHeadComment = function () { 74 | if (this.contents.headComment) { 75 | this.write("// %s\n", this.contents.headComment) 76 | } 77 | } 78 | 79 | pbxWriter.prototype.writeProject = function () { 80 | var proj = this.contents.project, 81 | key, cmt, obj; 82 | 83 | this.write("{\n") 84 | 85 | if (proj) { 86 | this.indentLevel++; 87 | 88 | for (key in proj) { 89 | // skip comments 90 | if (COMMENT_KEY.test(key)) continue; 91 | 92 | cmt = comment(key, proj); 93 | obj = proj[key]; 94 | 95 | if (isArray(obj)) { 96 | this.writeArray(obj, key) 97 | } else if (isObject(obj)) { 98 | this.write("%s = {\n", key); 99 | this.indentLevel++; 100 | 101 | if (key === 'objects') { 102 | this.writeObjectsSections(obj) 103 | } else { 104 | this.writeObject(obj) 105 | } 106 | 107 | this.indentLevel--; 108 | this.write("};\n"); 109 | } else if (cmt) { 110 | this.write("%s = %s /* %s */;\n", key, obj, cmt) 111 | } else { 112 | this.write("%s = %s;\n", key, obj) 113 | } 114 | } 115 | 116 | this.indentLevel--; 117 | } 118 | 119 | this.write("}\n") 120 | } 121 | 122 | pbxWriter.prototype.writeObject = function (object) { 123 | var key, obj, cmt; 124 | 125 | for (key in object) { 126 | if (COMMENT_KEY.test(key)) continue; 127 | 128 | cmt = comment(key, object); 129 | obj = object[key]; 130 | 131 | if (isArray(obj)) { 132 | this.writeArray(obj, key) 133 | } else if (isObject(obj)) { 134 | this.write("%s = {\n", key); 135 | this.indentLevel++; 136 | 137 | this.writeObject(obj) 138 | 139 | this.indentLevel--; 140 | this.write("};\n"); 141 | } else { 142 | if (cmt) { 143 | this.write("%s = %s /* %s */;\n", key, obj, cmt) 144 | } else { 145 | this.write("%s = %s;\n", key, obj) 146 | } 147 | } 148 | } 149 | } 150 | 151 | pbxWriter.prototype.writeObjectsSections = function (objects) { 152 | var first = true, 153 | key, obj; 154 | 155 | for (key in objects) { 156 | if (!first) { 157 | this.writeFlush("\n") 158 | } else { 159 | first = false; 160 | } 161 | 162 | obj = objects[key]; 163 | 164 | if (isObject(obj)) { 165 | this.writeSectionComment(key, true); 166 | 167 | this.writeSection(obj); 168 | 169 | this.writeSectionComment(key, false); 170 | } 171 | } 172 | } 173 | 174 | pbxWriter.prototype.writeArray = function (arr, name) { 175 | var i, entry; 176 | 177 | this.write("%s = (\n", name); 178 | this.indentLevel++; 179 | 180 | for (i=0; i < arr.length; i++) { 181 | entry = arr[i] 182 | 183 | if (entry.value && entry.comment) { 184 | this.write('%s /* %s */,\n', entry.value, entry.comment); 185 | } else if (isObject(entry)) { 186 | this.write('{\n'); 187 | this.indentLevel++; 188 | 189 | this.writeObject(entry); 190 | 191 | this.indentLevel--; 192 | this.write('},\n'); 193 | } else { 194 | this.write('%s,\n', entry); 195 | } 196 | } 197 | 198 | this.indentLevel--; 199 | this.write(");\n"); 200 | } 201 | 202 | pbxWriter.prototype.writeSectionComment = function (name, begin) { 203 | if (begin) { 204 | this.writeFlush("/* Begin %s section */\n", name) 205 | } else { // end 206 | this.writeFlush("/* End %s section */\n", name) 207 | } 208 | } 209 | 210 | pbxWriter.prototype.writeSection = function (section) { 211 | var key, obj, cmt; 212 | 213 | // section should only contain objects 214 | for (key in section) { 215 | if (COMMENT_KEY.test(key)) continue; 216 | 217 | cmt = comment(key, section); 218 | obj = section[key] 219 | 220 | if (obj.isa == 'PBXBuildFile' || obj.isa == 'PBXFileReference') { 221 | this.writeInlineObject(key, cmt, obj); 222 | } else { 223 | if (cmt) { 224 | this.write("%s /* %s */ = {\n", key, cmt); 225 | } else { 226 | this.write("%s = {\n", key); 227 | } 228 | 229 | this.indentLevel++ 230 | 231 | this.writeObject(obj) 232 | 233 | this.indentLevel-- 234 | this.write("};\n"); 235 | } 236 | } 237 | } 238 | 239 | pbxWriter.prototype.writeInlineObject = function (n, d, r) { 240 | var output = []; 241 | 242 | var inlineObjectHelper = function (name, desc, ref) { 243 | var key, cmt, obj; 244 | 245 | if (desc) { 246 | output.push(f("%s /* %s */ = {", name, desc)); 247 | } else { 248 | output.push(f("%s = {", name)); 249 | } 250 | 251 | for (key in ref) { 252 | if (COMMENT_KEY.test(key)) continue; 253 | 254 | cmt = comment(key, ref); 255 | obj = ref[key]; 256 | 257 | if (isArray(obj)) { 258 | output.push(f("%s = (", key)); 259 | 260 | for (var i=0; i < obj.length; i++) { 261 | output.push(f("%s, ", obj[i])) 262 | } 263 | 264 | output.push("); "); 265 | } else if (isObject(obj)) { 266 | inlineObjectHelper(key, cmt, obj) 267 | } else if (cmt) { 268 | output.push(f("%s = %s /* %s */; ", key, obj, cmt)) 269 | } else { 270 | output.push(f("%s = %s; ", key, obj)) 271 | } 272 | } 273 | 274 | output.push("}; "); 275 | } 276 | 277 | inlineObjectHelper(n, d, r); 278 | 279 | this.write("%s\n", output.join('').trim()); 280 | } 281 | 282 | module.exports = pbxWriter; 283 | -------------------------------------------------------------------------------- /test/addFramework.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | pbxFile = require('../lib/pbxFile'), 5 | proj = new pbx('.'); 6 | 7 | function cleanHash() { 8 | return JSON.parse(fullProjectStr); 9 | } 10 | 11 | exports.setUp = function (callback) { 12 | proj.hash = cleanHash(); 13 | callback(); 14 | } 15 | 16 | function nonComments(obj) { 17 | var keys = Object.keys(obj), 18 | newObj = {}, i = 0; 19 | 20 | for (i; i < keys.length; i++) { 21 | if (!/_comment$/.test(keys[i])) { 22 | newObj[keys[i]] = obj[keys[i]]; 23 | } 24 | } 25 | 26 | return newObj; 27 | } 28 | 29 | function frameworkSearchPaths(proj) { 30 | var configs = nonComments(proj.pbxXCBuildConfigurationSection()), 31 | allPaths = [], 32 | ids = Object.keys(configs), i, buildSettings; 33 | 34 | for (i = 0; i< ids.length; i++) { 35 | buildSettings = configs[ids[i]].buildSettings; 36 | 37 | if (buildSettings['FRAMEWORK_SEARCH_PATHS']) { 38 | allPaths.push(buildSettings['FRAMEWORK_SEARCH_PATHS']); 39 | } 40 | } 41 | 42 | return allPaths; 43 | } 44 | 45 | exports.addFramework = { 46 | 'should return a pbxFile': function (test) { 47 | var newFile = proj.addFramework('libsqlite3.dylib'); 48 | console.log(newFile); 49 | 50 | test.equal(newFile.constructor, pbxFile); 51 | test.done() 52 | }, 53 | 'should set a fileRef on the pbxFile': function (test) { 54 | var newFile = proj.addFramework('libsqlite3.dylib'); 55 | 56 | test.ok(newFile.fileRef); 57 | test.done() 58 | }, 59 | 'should populate the PBXFileReference section with 2 fields': function (test) { 60 | var newFile = proj.addFramework('libsqlite3.dylib'); 61 | fileRefSection = proj.pbxFileReferenceSection(), 62 | frsLength = Object.keys(fileRefSection).length; 63 | 64 | test.equal(68, frsLength); 65 | test.ok(fileRefSection[newFile.fileRef]); 66 | test.ok(fileRefSection[newFile.fileRef + '_comment']); 67 | 68 | test.done(); 69 | }, 70 | 'should populate the PBXFileReference comment correctly': function (test) { 71 | var newFile = proj.addFramework('libsqlite3.dylib'); 72 | fileRefSection = proj.pbxFileReferenceSection(), 73 | commentKey = newFile.fileRef + '_comment'; 74 | 75 | test.equal(fileRefSection[commentKey], 'libsqlite3.dylib'); 76 | test.done(); 77 | }, 78 | 'should add the PBXFileReference object correctly': function (test) { 79 | var newFile = proj.addFramework('libsqlite3.dylib'), 80 | fileRefSection = proj.pbxFileReferenceSection(), 81 | fileRefEntry = fileRefSection[newFile.fileRef]; 82 | 83 | test.equal(fileRefEntry.isa, 'PBXFileReference'); 84 | test.equal(fileRefEntry.lastKnownFileType, '"compiled.mach-o.dylib"'); 85 | test.equal(fileRefEntry.name, '"libsqlite3.dylib"'); 86 | test.equal(fileRefEntry.path, '"usr/lib/libsqlite3.dylib"'); 87 | test.equal(fileRefEntry.sourceTree, 'SDKROOT'); 88 | 89 | test.done(); 90 | }, 91 | 'should populate the PBXBuildFile section with 2 fields': function (test) { 92 | var newFile = proj.addFramework('libsqlite3.dylib'), 93 | buildFileSection = proj.pbxBuildFileSection(), 94 | bfsLength = Object.keys(buildFileSection).length; 95 | 96 | test.equal(60, bfsLength); 97 | test.ok(buildFileSection[newFile.uuid]); 98 | test.ok(buildFileSection[newFile.uuid + '_comment']); 99 | 100 | test.done(); 101 | }, 102 | 'should add the PBXBuildFile comment correctly': function (test) { 103 | var newFile = proj.addFramework('libsqlite3.dylib'), 104 | commentKey = newFile.uuid + '_comment', 105 | buildFileSection = proj.pbxBuildFileSection(); 106 | 107 | test.equal(buildFileSection[commentKey], 'libsqlite3.dylib in Frameworks'); 108 | test.done(); 109 | }, 110 | 'should add the PBXBuildFile object correctly': function (test) { 111 | var newFile = proj.addFramework('libsqlite3.dylib'), 112 | buildFileSection = proj.pbxBuildFileSection(), 113 | buildFileEntry = buildFileSection[newFile.uuid]; 114 | 115 | test.equal(buildFileEntry.isa, 'PBXBuildFile'); 116 | test.equal(buildFileEntry.fileRef, newFile.fileRef); 117 | test.equal(buildFileEntry.fileRef_comment, 'libsqlite3.dylib'); 118 | test.equal(buildFileEntry.settings, undefined); 119 | 120 | test.done(); 121 | }, 122 | 'should add the PBXBuildFile object correctly /w weak linked frameworks': function (test) { 123 | var newFile = proj.addFramework('libsqlite3.dylib', { weak: true }), 124 | buildFileSection = proj.pbxBuildFileSection(), 125 | buildFileEntry = buildFileSection[newFile.uuid]; 126 | 127 | test.equal(buildFileEntry.isa, 'PBXBuildFile'); 128 | test.equal(buildFileEntry.fileRef, newFile.fileRef); 129 | test.equal(buildFileEntry.fileRef_comment, 'libsqlite3.dylib'); 130 | test.deepEqual(buildFileEntry.settings, { ATTRIBUTES: [ 'Weak' ] }); 131 | 132 | test.done(); 133 | }, 134 | 'should add to the Frameworks PBXGroup': function (test) { 135 | var newLength = proj.pbxGroupByName('Frameworks').children.length + 1, 136 | newFile = proj.addFramework('libsqlite3.dylib'), 137 | frameworks = proj.pbxGroupByName('Frameworks'); 138 | 139 | test.equal(frameworks.children.length, newLength); 140 | test.done(); 141 | }, 142 | 'should have the right values for the PBXGroup entry': function (test) { 143 | var newFile = proj.addFramework('libsqlite3.dylib'), 144 | frameworks = proj.pbxGroupByName('Frameworks').children, 145 | framework = frameworks[frameworks.length - 1]; 146 | 147 | test.equal(framework.comment, 'libsqlite3.dylib'); 148 | test.equal(framework.value, newFile.fileRef); 149 | test.done(); 150 | }, 151 | 'should add to the PBXFrameworksBuildPhase': function (test) { 152 | var newFile = proj.addFramework('libsqlite3.dylib'), 153 | frameworks = proj.pbxFrameworksBuildPhaseObj(); 154 | 155 | test.equal(frameworks.files.length, 16); 156 | test.done(); 157 | }, 158 | 'should have the right values for the Sources entry': function (test) { 159 | var newFile = proj.addFramework('libsqlite3.dylib'), 160 | frameworks = proj.pbxFrameworksBuildPhaseObj(), 161 | framework = frameworks.files[15]; 162 | 163 | test.equal(framework.comment, 'libsqlite3.dylib in Frameworks'); 164 | test.equal(framework.value, newFile.uuid); 165 | test.done(); 166 | }, 167 | 'duplicate entries': { 168 | 'should return false': function (test) { 169 | var newFile = proj.addFramework('libsqlite3.dylib'); 170 | 171 | test.ok(!proj.addFramework('libsqlite3.dylib')); 172 | test.done(); 173 | } 174 | }, 175 | 'should pbxFile correctly for custom frameworks': function (test) { 176 | var newFile = proj.addFramework('/path/to/Custom.framework', {customFramework: true}); 177 | 178 | test.ok(newFile.customFramework); 179 | test.ok(!newFile.fileEncoding); 180 | test.equal(newFile.sourceTree, '""'); 181 | test.equal(newFile.group, 'Frameworks'); 182 | test.equal(newFile.basename, 'Custom.framework'); 183 | test.equal(newFile.dirname, '/path/to'); 184 | // XXX framework has to be copied over to PROJECT root. That is what XCode does when you drag&drop 185 | test.equal(newFile.path, '/path/to/Custom.framework'); 186 | 187 | 188 | // should add path to framework search path 189 | var frameworkPaths = frameworkSearchPaths(proj); 190 | expectedPath = '"\\"/path/to\\""'; 191 | 192 | for (i = 0; i < frameworkPaths.length; i++) { 193 | var current = frameworkPaths[i]; 194 | test.ok(current.indexOf('"$(inherited)"') >= 0); 195 | test.ok(current.indexOf(expectedPath) >= 0); 196 | } 197 | test.done(); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /test/addPbxGroup.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | proj = new pbx('.'); 5 | 6 | function cleanHash() { 7 | return JSON.parse(fullProjectStr); 8 | } 9 | 10 | exports.setUp = function (callback) { 11 | proj.hash = cleanHash(); 12 | callback(); 13 | } 14 | 15 | exports.addPbxGroup = { 16 | 'should return a pbxGroup': function (test) { 17 | var pbxGroup = proj.addPbxGroup(['file.m'], 'MyGroup', 'Application', 'Application', '""'); 18 | 19 | test.ok(typeof pbxGroup === 'object'); 20 | test.done() 21 | }, 22 | 'should set a uuid on the pbxGroup': function (test) { 23 | var pbxGroup = proj.addPbxGroup(['file.m'], 'MyGroup', 'Application', 'Application', '""'); 24 | 25 | test.ok(pbxGroup.uuid); 26 | test.done() 27 | }, 28 | 'should add all files to pbxGroup': function (test) { 29 | var pbxGroup = proj.addPbxGroup(['file.m'], 'MyGroup', 'Application', 'Application', '""'); 30 | for (var index = 0; index < pbxGroup.pbxGroup.children.length; index++) { 31 | var file = pbxGroup.pbxGroup.children[index]; 32 | test.ok(file.value); 33 | } 34 | 35 | test.done() 36 | }, 37 | 'should add the PBXGroup object correctly': function (test) { 38 | var pbxGroup = proj.addPbxGroup(['file.m'], 'MyGroup', 'Application', '""'); 39 | pbxGroupInPbx = proj.pbxGroupByName('MyGroup'); 40 | 41 | test.equal(pbxGroupInPbx.children, pbxGroup.pbxGroup.children); 42 | test.equal(pbxGroupInPbx.isa, 'PBXGroup'); 43 | test.equal(pbxGroupInPbx.path, 'Application'); 44 | test.equal(pbxGroupInPbx.sourceTree, '""'); 45 | test.done(); 46 | }, 47 | 'should add sourceTree if no other specified': function (test) { 48 | var pbxGroup = proj.addPbxGroup(['file.m'], 'MyGroup', 'Application'); 49 | pbxGroupInPbx = proj.pbxGroupByName('MyGroup'); 50 | 51 | test.equal(pbxGroupInPbx.sourceTree, '""'); 52 | test.done(); 53 | }, 54 | 'should add each of the files to PBXBuildFile section': function (test) { 55 | var buildFileSection = proj.pbxBuildFileSection(); 56 | for (var key in buildFileSection) { 57 | test.notEqual(buildFileSection[key].fileRef_comment, 'file.m'); 58 | test.notEqual(buildFileSection[key].fileRef_comment, 'assets.bundle'); 59 | } 60 | 61 | var initialBuildFileSectionItemsCount = Object.keys(buildFileSection), 62 | pbxGroup = proj.addPbxGroup(['file.m', 'assets.bundle'], 'MyGroup', 'Application', '""'), 63 | afterAdditionBuildFileSectionItemsCount = Object.keys(buildFileSection); 64 | 65 | // for each file added in the build file section two keyes are added - one for the object and one for the comment 66 | test.equal(initialBuildFileSectionItemsCount.length, afterAdditionBuildFileSectionItemsCount.length - 4); 67 | test.done(); 68 | }, 69 | 'should not add any of the files to PBXBuildFile section if already added': function (test) { 70 | var buildFileSection = proj.pbxBuildFileSection(), 71 | initialBuildFileSectionItemsCount = Object.keys(buildFileSection), 72 | pbxGroup = proj.addPbxGroup(['AppDelegate.m', 'AppDelegate.h'], 'MyGroup', 'Application', '""'), 73 | afterAdditionBuildFileSectionItemsCount = Object.keys(buildFileSection); 74 | 75 | test.deepEqual(initialBuildFileSectionItemsCount, afterAdditionBuildFileSectionItemsCount); 76 | test.done(); 77 | }, 78 | 'should not add any of the files to PBXBuildFile section when they contain special symbols and are already added': function (test) { 79 | var buildFileSection = proj.pbxBuildFileSection(), 80 | initialBuildFileSectionItemsCount = Object.keys(buildFileSection), 81 | pbxGroup = proj.addPbxGroup(['KitchenSinktablet.app'], 'MyGroup', 'Application', '""'), 82 | afterAdditionBuildFileSectionItemsCount = Object.keys(buildFileSection); 83 | 84 | test.deepEqual(initialBuildFileSectionItemsCount, afterAdditionBuildFileSectionItemsCount); 85 | test.done(); 86 | }, 87 | 'should add all files which are not added and not add files already added to PBXBuildFile section': function (test) { 88 | var buildFileSection = proj.pbxBuildFileSection(); 89 | for (var key in buildFileSection) { 90 | test.notEqual(buildFileSection[key].fileRef_comment, 'file.m'); 91 | test.notEqual(buildFileSection[key].fileRef_comment, 'assets.bundle'); 92 | } 93 | 94 | var initialBuildFileSectionItemsCount = Object.keys(buildFileSection), 95 | pbxGroup = proj.addPbxGroup(['AppDelegate.m', 'AppDelegate.h', 'file.m', 'assets.bundle'], 'MyGroup', 'Application', '""'), 96 | afterAdditionBuildFileSectionItemsCount = Object.keys(buildFileSection); 97 | 98 | // for each file added in the build file section two keyes are added - one for the object and one for the comment 99 | test.equal(initialBuildFileSectionItemsCount.length, afterAdditionBuildFileSectionItemsCount.length - 4); 100 | test.done(); 101 | }, 102 | 'should add each of the files to PBXFileReference section': function (test) { 103 | var fileReference = proj.pbxFileReferenceSection(); 104 | for (var key in fileReference) { 105 | test.notEqual(fileReference[key].fileRef_comment, 'file.m'); 106 | test.notEqual(fileReference[key].fileRef_comment, 'assets.bundle'); 107 | } 108 | var pbxGroup = proj.addPbxGroup(['file.m', 'assets.bundle'], 'MyGroup', 'Application', '""'); 109 | for (var index = 0; index < pbxGroup.pbxGroup.children.length; index++) { 110 | var file = pbxGroup.pbxGroup.children[index]; 111 | test.ok(fileReference[file.value]); 112 | } 113 | 114 | test.done(); 115 | }, 116 | 'should not add any of the files to PBXFileReference section if already added': function (test) { 117 | var fileReference = proj.pbxFileReferenceSection (), 118 | initialBuildFileSectionItemsCount = Object.keys(fileReference), 119 | pbxGroup = proj.addPbxGroup(['AppDelegate.m', 'AppDelegate.h'], 'MyGroup', 'Application', '""'), 120 | afterAdditionBuildFileSectionItemsCount = Object.keys(fileReference); 121 | 122 | test.deepEqual(initialBuildFileSectionItemsCount, afterAdditionBuildFileSectionItemsCount); 123 | test.done(); 124 | }, 125 | 'should not add any of the files to PBXFileReference section when they contain special symbols and are already added': function (test) { 126 | var fileReference = proj.pbxFileReferenceSection (), 127 | initialBuildFileSectionItemsCount = Object.keys(fileReference), 128 | pbxGroup = proj.addPbxGroup(['KitchenSinktablet.app'], 'MyGroup', 'Application', '""'), 129 | afterAdditionBuildFileSectionItemsCount = Object.keys(fileReference); 130 | 131 | test.deepEqual(initialBuildFileSectionItemsCount, afterAdditionBuildFileSectionItemsCount); 132 | test.done(); 133 | }, 134 | 'should add all files which are not added and not add files already added to PBXFileReference section': function (test) { 135 | var fileReference = proj.pbxFileReferenceSection (); 136 | for (var key in fileReference) { 137 | test.notEqual(fileReference[key].fileRef_comment, 'file.m'); 138 | test.notEqual(fileReference[key].fileRef_comment, 'assets.bundle'); 139 | } 140 | 141 | var initialBuildFileSectionItemsCount = Object.keys(fileReference), 142 | pbxGroup = proj.addPbxGroup(['AppDelegate.m', 'AppDelegate.h', 'file.m', 'assets.bundle'], 'MyGroup', 'Application', '""'), 143 | afterAdditionBuildFileSectionItemsCount = Object.keys(fileReference); 144 | 145 | // for each file added in the file reference section two keyes are added - one for the object and one for the comment 146 | test.equal(initialBuildFileSectionItemsCount.length, afterAdditionBuildFileSectionItemsCount.length - 4); 147 | test.done(); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /test/addStaticLibrary.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | pbxFile = require('../lib/pbxFile'), 5 | proj = new pbx('.'); 6 | 7 | function cleanHash() { 8 | return JSON.parse(fullProjectStr); 9 | } 10 | 11 | function nonComments(obj) { 12 | var keys = Object.keys(obj), 13 | newObj = {}, i = 0; 14 | 15 | for (i; i < keys.length; i++) { 16 | if (!/_comment$/.test(keys[i])) { 17 | newObj[keys[i]] = obj[keys[i]]; 18 | } 19 | } 20 | 21 | return newObj; 22 | } 23 | 24 | function librarySearchPaths(proj) { 25 | var configs = nonComments(proj.pbxXCBuildConfigurationSection()), 26 | allPaths = [], 27 | ids = Object.keys(configs), i, buildSettings; 28 | 29 | for (i = 0; i< ids.length; i++) { 30 | buildSettings = configs[ids[i]].buildSettings; 31 | 32 | if (buildSettings['LIBRARY_SEARCH_PATHS']) { 33 | allPaths.push(buildSettings['LIBRARY_SEARCH_PATHS']); 34 | } 35 | } 36 | 37 | return allPaths; 38 | } 39 | 40 | exports.setUp = function (callback) { 41 | proj.hash = cleanHash(); 42 | callback(); 43 | } 44 | 45 | exports.addStaticLibrary = { 46 | 'should return a pbxFile': function (test) { 47 | var newFile = proj.addStaticLibrary('libGoogleAnalytics.a'); 48 | 49 | test.equal(newFile.constructor, pbxFile); 50 | test.done() 51 | }, 52 | 'should set a fileRef on the pbxFile': function (test) { 53 | var newFile = proj.addStaticLibrary('libGoogleAnalytics.a'); 54 | 55 | test.ok(newFile.fileRef); 56 | test.done() 57 | }, 58 | 'should populate the PBXBuildFile section with 2 fields': function (test) { 59 | var newFile = proj.addStaticLibrary('libGoogleAnalytics.a'); 60 | buildFileSection = proj.pbxBuildFileSection(), 61 | bfsLength = Object.keys(buildFileSection).length; 62 | 63 | test.equal(60, bfsLength); 64 | test.ok(buildFileSection[newFile.uuid]); 65 | test.ok(buildFileSection[newFile.uuid + '_comment']); 66 | 67 | test.done(); 68 | }, 69 | 'should populate the PBXBuildFile section with 2 fields as plugin': function (test) { 70 | var newFile = proj.addStaticLibrary('libGoogleAnalytics.a', 71 | { plugin: true }); 72 | buildFileSection = proj.pbxBuildFileSection(), 73 | bfsLength = Object.keys(buildFileSection).length; 74 | 75 | test.equal(60, bfsLength); 76 | test.ok(buildFileSection[newFile.uuid]); 77 | test.ok(buildFileSection[newFile.uuid + '_comment']); 78 | 79 | test.done(); 80 | }, 81 | 'should add the PBXBuildFile comment correctly': function (test) { 82 | var newFile = proj.addStaticLibrary('libGoogleAnalytics.a'); 83 | commentKey = newFile.uuid + '_comment', 84 | buildFileSection = proj.pbxBuildFileSection(); 85 | 86 | test.equal(buildFileSection[commentKey], 'libGoogleAnalytics.a in Frameworks'); 87 | test.done(); 88 | }, 89 | 'should add the PBXBuildFile object correctly': function (test) { 90 | var newFile = proj.addStaticLibrary('libGoogleAnalytics.a'); 91 | buildFileSection = proj.pbxBuildFileSection(), 92 | buildFileEntry = buildFileSection[newFile.uuid]; 93 | 94 | test.equal(buildFileEntry.isa, 'PBXBuildFile'); 95 | test.equal(buildFileEntry.fileRef, newFile.fileRef); 96 | test.equal(buildFileEntry.fileRef_comment, 'libGoogleAnalytics.a'); 97 | 98 | test.done(); 99 | }, 100 | 'should populate the PBXFileReference section with 2 fields': function (test) { 101 | var newFile = proj.addStaticLibrary('libGoogleAnalytics.a'); 102 | fileRefSection = proj.pbxFileReferenceSection(), 103 | frsLength = Object.keys(fileRefSection).length; 104 | 105 | test.equal(68, frsLength); 106 | test.ok(fileRefSection[newFile.fileRef]); 107 | test.ok(fileRefSection[newFile.fileRef + '_comment']); 108 | 109 | test.done(); 110 | }, 111 | 'should populate the PBXFileReference comment correctly': function (test) { 112 | var newFile = proj.addStaticLibrary('libGoogleAnalytics.a'); 113 | fileRefSection = proj.pbxFileReferenceSection(), 114 | commentKey = newFile.fileRef + '_comment'; 115 | 116 | test.equal(fileRefSection[commentKey], 'libGoogleAnalytics.a'); 117 | test.done(); 118 | }, 119 | 'should add the PBXFileReference object correctly': function (test) { 120 | var newFile = proj.addStaticLibrary('libGoogleAnalytics.a'), 121 | fileRefSection = proj.pbxFileReferenceSection(), 122 | fileRefEntry = fileRefSection[newFile.fileRef]; 123 | 124 | test.equal(fileRefEntry.isa, 'PBXFileReference'); 125 | test.equal(fileRefEntry.lastKnownFileType, 'archive.ar'); 126 | test.equal(fileRefEntry.name, '"libGoogleAnalytics.a"'); 127 | test.equal(fileRefEntry.path, '"libGoogleAnalytics.a"'); 128 | test.equal(fileRefEntry.sourceTree, '""'); 129 | 130 | test.done(); 131 | }, 132 | 'should add to the PBXFrameworksBuildPhase': function (test) { 133 | var newFile = proj.addStaticLibrary('libGoogleAnalytics.a'), 134 | frameworks = proj.pbxFrameworksBuildPhaseObj(); 135 | 136 | test.equal(frameworks.files.length, 16); 137 | test.done(); 138 | }, 139 | 'should have the right values for the Sources entry': function (test) { 140 | var newFile = proj.addStaticLibrary('libGoogleAnalytics.a'), 141 | frameworks = proj.pbxFrameworksBuildPhaseObj(), 142 | framework = frameworks.files[15]; 143 | 144 | test.equal(framework.comment, 'libGoogleAnalytics.a in Frameworks'); 145 | test.equal(framework.value, newFile.uuid); 146 | test.done(); 147 | }, 148 | 'should set LIBRARY_SEARCH_PATHS for appropriate build configurations': function (test) { 149 | var newFile = proj.addStaticLibrary('libGoogleAnalytics.a'), 150 | configs = nonComments(proj.pbxXCBuildConfigurationSection()), 151 | ids = Object.keys(configs), i, buildSettings; 152 | 153 | for (i = 0; i< ids.length; i++) { 154 | buildSettings = configs[ids[i]].buildSettings; 155 | 156 | if (buildSettings['PRODUCT_NAME'] == '"KitchenSinktablet"') { 157 | test.ok(buildSettings['LIBRARY_SEARCH_PATHS']); 158 | } 159 | } 160 | 161 | test.done(); 162 | }, 163 | 'should ensure LIBRARY_SEARCH_PATHS inherits defaults correctly': function (test) { 164 | var newFile = proj.addStaticLibrary('libGoogleAnalytics.a'), 165 | libraryPaths = librarySearchPaths(proj), 166 | expectedPath = '"\\"$(SRCROOT)/KitchenSinktablet\\""', 167 | i, current; 168 | 169 | for (i = 0; i < libraryPaths.length; i++) { 170 | current = libraryPaths[i]; 171 | test.ok(current.indexOf('"$(inherited)"') >= 0); 172 | } 173 | 174 | test.done(); 175 | }, 176 | 'should ensure the new library is in LIBRARY_SEARCH_PATHS': function (test) { 177 | var newFile = proj.addStaticLibrary('libGoogleAnalytics.a'), 178 | libraryPaths = librarySearchPaths(proj), 179 | expectedPath = '"\\"$(SRCROOT)/KitchenSinktablet\\""', 180 | i, current; 181 | 182 | for (i = 0; i < libraryPaths.length; i++) { 183 | current = libraryPaths[i]; 184 | test.ok(current.indexOf(expectedPath) >= 0); 185 | } 186 | 187 | test.done(); 188 | }, 189 | 'should add to the Plugins group, optionally': function (test) { 190 | var newFile = proj.addStaticLibrary('libGoogleAnalytics.a', 191 | { plugin: true }), 192 | plugins = proj.pbxGroupByName('Plugins'); 193 | 194 | test.equal(plugins.children.length, 1); 195 | test.done(); 196 | }, 197 | 'should add the right LIBRARY_SEARCH_PATHS entry for plugins': { 198 | 'with group set': function (test) { 199 | plugins = proj.pbxGroupByName('Plugins'); 200 | plugins.path = '"Test200/Plugins"'; 201 | 202 | var newFile = proj.addStaticLibrary('Plugins/libGoogleAnalytics.a', 203 | { plugin: true }), 204 | libraryPaths = librarySearchPaths(proj), 205 | expectedPath = '"\\"$(SRCROOT)/Test200/Plugins\\""', 206 | i, current; 207 | 208 | for (i = 0; i < libraryPaths.length; i++) { 209 | current = libraryPaths[i]; 210 | test.ok(current.indexOf(expectedPath) >= 0, 211 | expectedPath + ' not found in ' + current); 212 | } 213 | 214 | test.done(); 215 | }, 216 | 'without group set': function (test) { 217 | plugins = proj.pbxGroupByName('Plugins'); 218 | delete plugins.path; 219 | 220 | var newFile = proj.addStaticLibrary('Plugins/libGoogleAnalytics.a', 221 | { plugin: true }), 222 | libraryPaths = librarySearchPaths(proj), 223 | expectedPath = '"\\"$(SRCROOT)/KitchenSinktablet/Plugins\\""', 224 | i, current; 225 | 226 | for (i = 0; i < libraryPaths.length; i++) { 227 | current = libraryPaths[i]; 228 | test.ok(current.indexOf(expectedPath) >= 0, 229 | expectedPath + ' not found in ' + current); 230 | } 231 | 232 | test.done(); 233 | } 234 | }, 235 | 'duplicate entries': { 236 | 'should return false': function (test) { 237 | var newFile = proj.addStaticLibrary('libGoogleAnalytics.a'); 238 | 239 | test.ok(!proj.addStaticLibrary('libGoogleAnalytics.a')); 240 | test.done(); 241 | }, 242 | 'should return false (plugin entries)': function (test) { 243 | var newFile = proj.addStaticLibrary('Plugins/libGoogleAnalytics.a', 244 | { plugin: true }); 245 | 246 | test.ok(!proj.addStaticLibrary('Plugins/libGoogleAnalytics.a', 247 | { plugin: true })); 248 | test.done(); 249 | }, 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /test/addResourceFile.js: -------------------------------------------------------------------------------- 1 | var fullProject = require('./fixtures/full-project') 2 | fullProjectStr = JSON.stringify(fullProject), 3 | pbx = require('../lib/pbxProject'), 4 | pbxFile = require('../lib/pbxFile'), 5 | proj = new pbx('.'); 6 | 7 | function cleanHash() { 8 | return JSON.parse(fullProjectStr); 9 | } 10 | 11 | exports.setUp = function (callback) { 12 | proj.hash = cleanHash(); 13 | callback(); 14 | } 15 | 16 | exports.addResourceFile = { 17 | 'should return a pbxFile': function (test) { 18 | var newFile = proj.addResourceFile('assets.bundle'); 19 | 20 | test.equal(newFile.constructor, pbxFile); 21 | test.done() 22 | }, 23 | 'should set a uuid on the pbxFile': function (test) { 24 | var newFile = proj.addResourceFile('assets.bundle'); 25 | 26 | test.ok(newFile.uuid); 27 | test.done() 28 | }, 29 | 'should set a fileRef on the pbxFile': function (test) { 30 | var newFile = proj.addResourceFile('assets.bundle'); 31 | 32 | test.ok(newFile.fileRef); 33 | test.done() 34 | }, 35 | 'should populate the PBXBuildFile section with 2 fields': function (test) { 36 | var newFile = proj.addResourceFile('assets.bundle'), 37 | buildFileSection = proj.pbxBuildFileSection(), 38 | bfsLength = Object.keys(buildFileSection).length; 39 | 40 | test.equal(60, bfsLength); 41 | test.ok(buildFileSection[newFile.uuid]); 42 | test.ok(buildFileSection[newFile.uuid + '_comment']); 43 | 44 | test.done(); 45 | }, 46 | 'should add the PBXBuildFile comment correctly': function (test) { 47 | var newFile = proj.addResourceFile('assets.bundle'), 48 | commentKey = newFile.uuid + '_comment', 49 | buildFileSection = proj.pbxBuildFileSection(); 50 | 51 | test.equal(buildFileSection[commentKey], 'assets.bundle in Resources'); 52 | test.done(); 53 | }, 54 | 'should add the PBXBuildFile object correctly': function (test) { 55 | var newFile = proj.addResourceFile('assets.bundle'), 56 | buildFileSection = proj.pbxBuildFileSection(), 57 | buildFileEntry = buildFileSection[newFile.uuid]; 58 | 59 | test.equal(buildFileEntry.isa, 'PBXBuildFile'); 60 | test.equal(buildFileEntry.fileRef, newFile.fileRef); 61 | test.equal(buildFileEntry.fileRef_comment, 'assets.bundle'); 62 | 63 | test.done(); 64 | }, 65 | 'should populate the PBXFileReference section with 2 fields': function (test) { 66 | var newFile = proj.addResourceFile('assets.bundle'), 67 | fileRefSection = proj.pbxFileReferenceSection(), 68 | frsLength = Object.keys(fileRefSection).length; 69 | 70 | test.equal(68, frsLength); 71 | test.ok(fileRefSection[newFile.fileRef]); 72 | test.ok(fileRefSection[newFile.fileRef + '_comment']); 73 | 74 | test.done(); 75 | }, 76 | 'should populate the PBXFileReference comment correctly': function (test) { 77 | var newFile = proj.addResourceFile('assets.bundle'), 78 | fileRefSection = proj.pbxFileReferenceSection(), 79 | commentKey = newFile.fileRef + '_comment'; 80 | 81 | test.equal(fileRefSection[commentKey], 'assets.bundle'); 82 | test.done(); 83 | }, 84 | 'should add the PBXFileReference object correctly': function (test) { 85 | delete proj.pbxGroupByName('Resources').path; 86 | 87 | var newFile = proj.addResourceFile('Resources/assets.bundle'), 88 | fileRefSection = proj.pbxFileReferenceSection(), 89 | fileRefEntry = fileRefSection[newFile.fileRef]; 90 | 91 | test.equal(fileRefEntry.isa, 'PBXFileReference'); 92 | test.equal(fileRefEntry.fileEncoding, undefined); 93 | test.equal(fileRefEntry.lastKnownFileType, '"wrapper.plug-in"'); 94 | test.equal(fileRefEntry.name, '"assets.bundle"'); 95 | test.equal(fileRefEntry.path, '"Resources/assets.bundle"'); 96 | test.equal(fileRefEntry.sourceTree, '""'); 97 | 98 | test.done(); 99 | }, 100 | 'should add to the Resources PBXGroup group': function (test) { 101 | var newFile = proj.addResourceFile('Resources/assets.bundle'), 102 | resources = proj.pbxGroupByName('Resources'); 103 | 104 | test.equal(resources.children.length, 10); 105 | test.done(); 106 | }, 107 | 'should have the right values for the PBXGroup entry': function (test) { 108 | var newFile = proj.addResourceFile('Resources/assets.bundle'), 109 | resources = proj.pbxGroupByName('Resources'), 110 | resourceObj = resources.children[9]; 111 | 112 | test.equal(resourceObj.comment, 'assets.bundle'); 113 | test.equal(resourceObj.value, newFile.fileRef); 114 | test.done(); 115 | }, 116 | 'should add to the PBXSourcesBuildPhase': function (test) { 117 | var newFile = proj.addResourceFile('Resources/assets.bundle'), 118 | sources = proj.pbxResourcesBuildPhaseObj(); 119 | 120 | test.equal(sources.files.length, 13); 121 | test.done(); 122 | }, 123 | 'should have the right values for the Sources entry': function (test) { 124 | var newFile = proj.addResourceFile('Resources/assets.bundle'), 125 | sources = proj.pbxResourcesBuildPhaseObj(), 126 | sourceObj = sources.files[12]; 127 | 128 | test.equal(sourceObj.comment, 'assets.bundle in Resources'); 129 | test.equal(sourceObj.value, newFile.uuid); 130 | test.done(); 131 | }, 132 | 'should remove "Resources/" from path if group path is set': function (test) { 133 | var resources = proj.pbxGroupByName('Resources'), 134 | newFile; 135 | 136 | resources.path = '"Test200/Resources"'; 137 | newFile = proj.addResourceFile('Resources/assets.bundle'); 138 | 139 | test.equal(newFile.path, 'assets.bundle'); 140 | test.done(); 141 | }, 142 | 'when added with { plugin: true }': { 143 | 144 | 'should add the PBXFileReference with the "Plugins" path': function (test) { 145 | delete proj.pbxGroupByName('Plugins').path; 146 | 147 | var newFile = proj.addResourceFile('Plugins/assets.bundle', 148 | { plugin: true }), 149 | fileRefSection = proj.pbxFileReferenceSection(), 150 | fileRefEntry = fileRefSection[newFile.fileRef]; 151 | 152 | test.equal(fileRefEntry.isa, 'PBXFileReference'); 153 | test.equal(fileRefEntry.fileEncoding, undefined); 154 | test.equal(fileRefEntry.lastKnownFileType, '"wrapper.plug-in"'); 155 | test.equal(fileRefEntry.name, '"assets.bundle"'); 156 | test.equal(fileRefEntry.path, '"Plugins/assets.bundle"'); 157 | test.equal(fileRefEntry.sourceTree, '""'); 158 | test.done(); 159 | }, 160 | 161 | 'should add to the Plugins PBXGroup group': function (test) { 162 | var newFile = proj.addResourceFile('Plugins/assets.bundle', 163 | { plugin: true }), 164 | plugins = proj.pbxGroupByName('Plugins'); 165 | 166 | test.equal(plugins.children.length, 1); 167 | test.done(); 168 | }, 169 | 170 | 'should have the Plugins values for the PBXGroup entry': function (test) { 171 | var newFile = proj.addResourceFile('Plugins/assets.bundle', 172 | { plugin: true }), 173 | plugins = proj.pbxGroupByName('Plugins'), 174 | pluginObj = plugins.children[0]; 175 | 176 | test.equal(pluginObj.comment, 'assets.bundle'); 177 | test.equal(pluginObj.value, newFile.fileRef); 178 | test.done(); 179 | }, 180 | 181 | 'should add to the PBXSourcesBuildPhase': function (test) { 182 | var newFile = proj.addResourceFile('Plugins/assets.bundle', 183 | { plugin: true }), 184 | sources = proj.pbxResourcesBuildPhaseObj(); 185 | 186 | test.equal(sources.files.length, 13); 187 | test.done(); 188 | }, 189 | 190 | 'should have the right values for the Sources entry': function (test) { 191 | var newFile = proj.addResourceFile('Plugins/assets.bundle', 192 | { plugin: true }), 193 | sources = proj.pbxResourcesBuildPhaseObj(), 194 | sourceObj = sources.files[12]; 195 | 196 | test.equal(sourceObj.comment, 'assets.bundle in Resources'); 197 | test.equal(sourceObj.value, newFile.uuid); 198 | test.done(); 199 | }, 200 | 201 | 'should remove "Plugins/" from path if group path is set': function (test) { 202 | var plugins = proj.pbxGroupByName('Plugins'), 203 | newFile; 204 | 205 | plugins.path = '"Test200/Plugins"'; 206 | newFile = proj.addResourceFile('Plugins/assets.bundle', 207 | { plugin: true }); 208 | 209 | test.equal(newFile.path, 'assets.bundle'); 210 | test.done(); 211 | } 212 | }, 213 | 'duplicate entries': { 214 | 'should return false': function (test) { 215 | var newFile = proj.addResourceFile('Plugins/assets.bundle'); 216 | 217 | test.ok(!proj.addResourceFile('Plugins/assets.bundle')); 218 | test.done(); 219 | }, 220 | 'should return false (plugin entries)': function (test) { 221 | var newFile = proj.addResourceFile('Plugins/assets.bundle', 222 | { plugin: true }); 223 | 224 | test.ok(!proj.addResourceFile('Plugins/assets.bundle', 225 | { plugin: true })); 226 | test.done(); 227 | }, 228 | 'should not add another entry anywhere': function (test) { 229 | var newFile = proj.addResourceFile('Plugins/assets.bundle'), 230 | buildFileSection = proj.pbxBuildFileSection(), 231 | bfsLength = Object.keys(buildFileSection).length, 232 | fileRefSection = proj.pbxFileReferenceSection(), 233 | frsLength = Object.keys(fileRefSection).length, 234 | resources = proj.pbxGroupByName('Resources'), 235 | sources = proj.pbxResourcesBuildPhaseObj(); 236 | 237 | proj.addResourceFile('Plugins/assets.bundle'); 238 | 239 | // check lengths 240 | test.equal(60, bfsLength); 241 | test.equal(68, frsLength); 242 | test.equal(resources.children.length, 10); 243 | test.equal(sources.files.length, 13); 244 | test.done(); 245 | } 246 | }, 247 | tearDown: function (callback) { 248 | delete proj.pbxGroupByName('Resources').path; 249 | callback(); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /test/pbxProject.js: -------------------------------------------------------------------------------- 1 | var pbx = require('../lib/pbxProject'), 2 | buildConfig = require('./fixtures/buildFiles'), 3 | jsonProject = require('./fixtures/full-project'), 4 | fs = require('fs'), 5 | project; 6 | 7 | exports['creation'] = { 8 | 'should create a pbxProject with the new operator': function (test) { 9 | var myProj = new pbx('test/parser/projects/hash.pbxproj'); 10 | 11 | test.ok(myProj instanceof pbx); 12 | test.done(); 13 | }, 14 | 'should create a pbxProject without the new operator': function (test) { 15 | var myProj = pbx('test/parser/projects/hash.pbxproj'); 16 | 17 | test.ok(myProj instanceof pbx); 18 | test.done(); 19 | } 20 | } 21 | 22 | exports['parseSync function'] = { 23 | 'should return the hash object': function (test) { 24 | var myProj = new pbx('test/parser/projects/hash.pbxproj') 25 | , projHash = myProj.parseSync(); 26 | test.ok(projHash); 27 | test.done(); 28 | }, 29 | 'should contain valid data in the returned objects hash': function (test) { 30 | var myProj = new pbx('test/parser/projects/hash.pbxproj') 31 | , projHash = myProj.parseSync(); 32 | test.ok(projHash); 33 | 34 | test.equal(projHash.hash.project.archiveVersion, 1); 35 | test.equal(projHash.hash.project.objectVersion, 45); 36 | test.equal(projHash.hash.project.nonObject, '29B97313FDCFA39411CA2CEF'); 37 | 38 | test.done(); 39 | }, 40 | } 41 | 42 | exports['parse function'] = { 43 | 'should emit an "end" event': function (test) { 44 | var myProj = new pbx('test/parser/projects/hash.pbxproj'); 45 | 46 | myProj.parse().on('end', function (err, projHash) { 47 | test.done(); 48 | }) 49 | }, 50 | 'should take the end callback as a parameter': function (test) { 51 | var myProj = new pbx('test/parser/projects/hash.pbxproj'); 52 | 53 | myProj.parse(function (err, projHash) { 54 | test.done(); 55 | }) 56 | }, 57 | 'should allow evented error handling': function (test) { 58 | var myProj = new pbx('NotARealPath.pbxproj'); 59 | 60 | myProj.parse().on('error', function (err) { 61 | test.equal(typeof err, "object"); 62 | test.done(); 63 | }) 64 | }, 65 | 'should pass the hash object to the callback function': function (test) { 66 | var myProj = new pbx('test/parser/projects/hash.pbxproj'); 67 | 68 | myProj.parse(function (err, projHash) { 69 | test.ok(projHash); 70 | test.done(); 71 | }) 72 | }, 73 | 'should handle projects with comments in the header': function (test) { 74 | var myProj = new pbx('test/parser/projects/comments.pbxproj'); 75 | 76 | myProj.parse(function (err, projHash) { 77 | test.ok(projHash); 78 | test.done(); 79 | }) 80 | }, 81 | 'should attach the hash object to the pbx object': function (test) { 82 | var myProj = new pbx('test/parser/projects/hash.pbxproj'); 83 | 84 | myProj.parse(function (err, projHash) { 85 | test.ok(myProj.hash); 86 | test.done(); 87 | }) 88 | }, 89 | 'it should pass an error object back when the parsing fails': function (test) { 90 | var myProj = new pbx('test/parser/projects/fail.pbxproj'); 91 | 92 | myProj.parse(function (err, projHash) { 93 | test.ok(err); 94 | test.done(); 95 | }) 96 | } 97 | } 98 | 99 | exports['allUuids function'] = { 100 | 'should return the right amount of uuids': function (test) { 101 | var project = new pbx('.'), 102 | uuids; 103 | 104 | project.hash = buildConfig; 105 | uuids = project.allUuids(); 106 | 107 | test.equal(uuids.length, 4); 108 | test.done(); 109 | } 110 | } 111 | 112 | exports['generateUuid function'] = { 113 | 'should return a 24 character string': function (test) { 114 | var project = new pbx('.'), 115 | newUUID; 116 | 117 | project.hash = buildConfig; 118 | newUUID = project.generateUuid(); 119 | 120 | test.equal(newUUID.length, 24); 121 | test.done(); 122 | }, 123 | 'should be an uppercase hex string': function (test) { 124 | var project = new pbx('.'), 125 | uHex = /^[A-F0-9]{24}$/, 126 | newUUID; 127 | 128 | project.hash = buildConfig; 129 | newUUID = project.generateUuid(); 130 | 131 | test.ok(uHex.test(newUUID)); 132 | test.done(); 133 | } 134 | } 135 | 136 | var bcpbx = 'test/parser/projects/build-config.pbxproj'; 137 | var original_pbx = fs.readFileSync(bcpbx, 'utf-8'); 138 | 139 | exports['updateProductName function'] = { 140 | setUp:function(callback) { 141 | callback(); 142 | }, 143 | tearDown:function(callback) { 144 | fs.writeFileSync(bcpbx, original_pbx, 'utf-8'); 145 | callback(); 146 | }, 147 | 'should change the PRODUCT_NAME field in the .pbxproj file': function (test) { 148 | var myProj = new pbx('test/parser/projects/build-config.pbxproj'); 149 | myProj.parse(function(err, hash) { 150 | myProj.updateProductName('furious anger'); 151 | var newContents = myProj.writeSync(); 152 | test.ok(newContents.match(/PRODUCT_NAME\s*=\s*"furious anger"/)); 153 | test.done(); 154 | }); 155 | } 156 | } 157 | 158 | exports['updateBuildProperty function'] = { 159 | setUp:function(callback) { 160 | callback(); 161 | }, 162 | tearDown:function(callback) { 163 | fs.writeFileSync(bcpbx, original_pbx, 'utf-8'); 164 | callback(); 165 | }, 166 | 'should change build properties in the .pbxproj file': function (test) { 167 | var myProj = new pbx('test/parser/projects/build-config.pbxproj'); 168 | myProj.parse(function(err, hash) { 169 | myProj.updateBuildProperty('TARGETED_DEVICE_FAMILY', '"arm"'); 170 | var newContents = myProj.writeSync(); 171 | test.ok(newContents.match(/TARGETED_DEVICE_FAMILY\s*=\s*"arm"/)); 172 | myProj.updateBuildProperty('OTHER_LDFLAGS', ['T','E','S','T']); 173 | newContents = myProj.writeSync(); 174 | test.ok(newContents.match(/OTHER_LDFLAGS\s*=\s*\(\s*T,\s*E,\s*S,\s*T,\s*\)/)) 175 | test.done(); 176 | }); 177 | } 178 | } 179 | 180 | exports['productName field'] = { 181 | 'should return the product name': function (test) { 182 | var newProj = new pbx('.'); 183 | newProj.hash = jsonProject; 184 | 185 | test.equal(newProj.productName, 'KitchenSinktablet'); 186 | test.done(); 187 | } 188 | } 189 | 190 | exports['addPluginFile function'] = { 191 | 'should strip the Plugin path prefix': function (test) { 192 | var myProj = new pbx('test/parser/projects/full.pbxproj'); 193 | 194 | myProj.parse(function (err, hash) { 195 | test.equal(myProj.addPluginFile('Plugins/testMac.m').path, 'testMac.m'); 196 | test.equal(myProj.addPluginFile('Plugins\\testWin.m').path, 'testWin.m'); 197 | test.done(); 198 | }); 199 | }, 200 | 'should add files to the .pbxproj file using the / path seperator': function (test) { 201 | var myProj = new pbx('test/parser/projects/full.pbxproj'); 202 | 203 | myProj.parse(function (err, hash) { 204 | var file = myProj.addPluginFile('myPlugin\\newFile.m'); 205 | 206 | test.equal(myProj.pbxFileReferenceSection()[file.fileRef].path, '"myPlugin/newFile.m"'); 207 | test.done(); 208 | }); 209 | } 210 | } 211 | 212 | exports['hasFile'] = { 213 | 'should return true if the file is in the project': function (test) { 214 | var newProj = new pbx('.'); 215 | newProj.hash = jsonProject; 216 | 217 | // sourceTree: '""' 218 | test.ok(newProj.hasFile('AppDelegate.m')) 219 | test.done() 220 | }, 221 | 'should return false if the file is not in the project': function (test) { 222 | var newProj = new pbx('.'); 223 | newProj.hash = jsonProject; 224 | 225 | // sourceTree: '""' 226 | test.ok(!newProj.hasFile('NotTheAppDelegate.m')) 227 | test.done() 228 | } 229 | } 230 | 231 | exports['addToPbxFileReferenceSection'] = { 232 | 'should not quote name when no special characters present in basename': function (test) { 233 | var newProj = new pbx('.'); 234 | newProj.hash = jsonProject, 235 | file = { 236 | uuid: newProj.generateUuid(), 237 | fileRef: newProj.generateUuid(), 238 | isa: 'PBXFileReference', 239 | explicitFileType: 'wrapper.application', 240 | includeInIndex: 0, 241 | basename: "SomeFile.m", 242 | path: "SomePath.m", 243 | sourceTree: 'BUILT_PRODUCTS_DIR' 244 | }, 245 | fileRefSection = newProj.pbxFileReferenceSection(); 246 | 247 | newProj.addToPbxFileReferenceSection(file); 248 | test.equal(fileRefSection[file.fileRef].name, "SomeFile.m"); 249 | test.done(); 250 | }, 251 | 'should quote name when special characters present in basename': function (test) { 252 | var newProj = new pbx('.'); 253 | newProj.hash = jsonProject, 254 | file = { 255 | uuid: newProj.generateUuid(), 256 | fileRef: newProj.generateUuid(), 257 | isa: 'PBXFileReference', 258 | explicitFileType: 'wrapper.application', 259 | includeInIndex: 0, 260 | basename: "Some File.m", 261 | path: "SomePath.m", 262 | sourceTree: 'BUILT_PRODUCTS_DIR' 263 | }, 264 | fileRefSection = newProj.pbxFileReferenceSection(); 265 | 266 | newProj.addToPbxFileReferenceSection(file); 267 | test.equal(fileRefSection[file.fileRef].name, '"Some File.m"'); 268 | test.done(); 269 | }, 270 | 'should not quote path when no special characters present in path': function (test) { 271 | var newProj = new pbx('.'); 272 | newProj.hash = jsonProject, 273 | file = { 274 | uuid: newProj.generateUuid(), 275 | fileRef: newProj.generateUuid(), 276 | isa: 'PBXFileReference', 277 | explicitFileType: 'wrapper.application', 278 | includeInIndex: 0, 279 | basename: "SomeFile.m", 280 | path: "SomePath.m", 281 | sourceTree: 'BUILT_PRODUCTS_DIR' 282 | }, 283 | fileRefSection = newProj.pbxFileReferenceSection(); 284 | 285 | newProj.addToPbxFileReferenceSection(file); 286 | test.equal(fileRefSection[file.fileRef].path, "SomePath.m"); 287 | test.done(); 288 | }, 289 | 'should quote path when special characters present in path': function (test) { 290 | var newProj = new pbx('.'); 291 | newProj.hash = jsonProject, 292 | file = { 293 | uuid: newProj.generateUuid(), 294 | fileRef: newProj.generateUuid(), 295 | isa: 'PBXFileReference', 296 | explicitFileType: 'wrapper.application', 297 | includeInIndex: 0, 298 | basename: "SomeFile.m", 299 | path: "SomeFolder/Some Path.m", 300 | sourceTree: 'BUILT_PRODUCTS_DIR' 301 | }, 302 | fileRefSection = newProj.pbxFileReferenceSection(); 303 | 304 | newProj.addToPbxFileReferenceSection(file); 305 | test.equal(fileRefSection[file.fileRef].path, '"SomeFolder/Some Path.m"'); 306 | test.done(); 307 | }, 308 | 'should quote path and name when special characters present in path and basename': function (test) { 309 | var newProj = new pbx('.'); 310 | newProj.hash = jsonProject, 311 | file = { 312 | uuid: newProj.generateUuid(), 313 | fileRef: newProj.generateUuid(), 314 | isa: 'PBXFileReference', 315 | explicitFileType: 'wrapper.application', 316 | includeInIndex: 0, 317 | basename: "Some File.m", 318 | path: "SomeFolder/Some Path.m", 319 | sourceTree: 'BUILT_PRODUCTS_DIR' 320 | }, 321 | fileRefSection = newProj.pbxFileReferenceSection(); 322 | 323 | newProj.addToPbxFileReferenceSection(file); 324 | test.equal(fileRefSection[file.fileRef].name, '"Some File.m"'); 325 | test.equal(fileRefSection[file.fileRef].path, '"SomeFolder/Some Path.m"'); 326 | test.done(); 327 | } 328 | } 329 | 330 | -------------------------------------------------------------------------------- /test/fixtures/library-search-paths.json: -------------------------------------------------------------------------------- 1 | {"project":{"archiveVersion":1,"classes":{},"objectVersion":46,"objects":{"PBXBuildFile":{"B9E76573180BDDA800888C06":{"isa":"PBXBuildFile","fileRef":"B9E76572180BDDA800888C06","fileRef_comment":"Foundation.framework"},"B9E76573180BDDA800888C06_comment":"Foundation.framework in Frameworks","B9E76575180BDDA800888C06":{"isa":"PBXBuildFile","fileRef":"B9E76574180BDDA800888C06","fileRef_comment":"CoreGraphics.framework"},"B9E76575180BDDA800888C06_comment":"CoreGraphics.framework in Frameworks","B9E76577180BDDA800888C06":{"isa":"PBXBuildFile","fileRef":"B9E76576180BDDA800888C06","fileRef_comment":"UIKit.framework"},"B9E76577180BDDA800888C06_comment":"UIKit.framework in Frameworks","B9E7657D180BDDA800888C06":{"isa":"PBXBuildFile","fileRef":"B9E7657B180BDDA800888C06","fileRef_comment":"InfoPlist.strings"},"B9E7657D180BDDA800888C06_comment":"InfoPlist.strings in Resources","B9E7657F180BDDA800888C06":{"isa":"PBXBuildFile","fileRef":"B9E7657E180BDDA800888C06","fileRef_comment":"main.m"},"B9E7657F180BDDA800888C06_comment":"main.m in Sources","B9E76583180BDDA800888C06":{"isa":"PBXBuildFile","fileRef":"B9E76582180BDDA800888C06","fileRef_comment":"AppDelegate.m"},"B9E76583180BDDA800888C06_comment":"AppDelegate.m in Sources","B9E76586180BDDA800888C06":{"isa":"PBXBuildFile","fileRef":"B9E76584180BDDA800888C06","fileRef_comment":"Main_iPhone.storyboard"},"B9E76586180BDDA800888C06_comment":"Main_iPhone.storyboard in Resources","B9E76589180BDDA800888C06":{"isa":"PBXBuildFile","fileRef":"B9E76587180BDDA800888C06","fileRef_comment":"Main_iPad.storyboard"},"B9E76589180BDDA800888C06_comment":"Main_iPad.storyboard in Resources","B9E7658C180BDDA800888C06":{"isa":"PBXBuildFile","fileRef":"B9E7658B180BDDA800888C06","fileRef_comment":"MasterViewController.m"},"B9E7658C180BDDA800888C06_comment":"MasterViewController.m in Sources","B9E7658F180BDDA800888C06":{"isa":"PBXBuildFile","fileRef":"B9E7658E180BDDA800888C06","fileRef_comment":"DetailViewController.m"},"B9E7658F180BDDA800888C06_comment":"DetailViewController.m in Sources","B9E76591180BDDA800888C06":{"isa":"PBXBuildFile","fileRef":"B9E76590180BDDA800888C06","fileRef_comment":"Images.xcassets"},"B9E76591180BDDA800888C06_comment":"Images.xcassets in Resources","B9E76598180BDDA800888C06":{"isa":"PBXBuildFile","fileRef":"B9E76597180BDDA800888C06","fileRef_comment":"XCTest.framework"},"B9E76598180BDDA800888C06_comment":"XCTest.framework in Frameworks","B9E76599180BDDA800888C06":{"isa":"PBXBuildFile","fileRef":"B9E76572180BDDA800888C06","fileRef_comment":"Foundation.framework"},"B9E76599180BDDA800888C06_comment":"Foundation.framework in Frameworks","B9E7659A180BDDA800888C06":{"isa":"PBXBuildFile","fileRef":"B9E76576180BDDA800888C06","fileRef_comment":"UIKit.framework"},"B9E7659A180BDDA800888C06_comment":"UIKit.framework in Frameworks","B9E765A2180BDDA800888C06":{"isa":"PBXBuildFile","fileRef":"B9E765A0180BDDA800888C06","fileRef_comment":"InfoPlist.strings"},"B9E765A2180BDDA800888C06_comment":"InfoPlist.strings in Resources","B9E765A4180BDDA800888C06":{"isa":"PBXBuildFile","fileRef":"B9E765A3180BDDA800888C06","fileRef_comment":"testTests.m"},"B9E765A4180BDDA800888C06_comment":"testTests.m in Sources"},"PBXContainerItemProxy":{"B9E7659B180BDDA800888C06":{"isa":"PBXContainerItemProxy","containerPortal":"B9E76567180BDDA800888C06","containerPortal_comment":"Project object","proxyType":1,"remoteGlobalIDString":"B9E7656E180BDDA800888C06","remoteInfo":"test"},"B9E7659B180BDDA800888C06_comment":"PBXContainerItemProxy"},"PBXFileReference":{"B9E7656F180BDDA800888C06":{"isa":"PBXFileReference","explicitFileType":"wrapper.application","includeInIndex":0,"path":"test.app","sourceTree":"BUILT_PRODUCTS_DIR"},"B9E7656F180BDDA800888C06_comment":"test.app","B9E76572180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"wrapper.framework","name":"Foundation.framework","path":"System/Library/Frameworks/Foundation.framework","sourceTree":"SDKROOT"},"B9E76572180BDDA800888C06_comment":"Foundation.framework","B9E76574180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"wrapper.framework","name":"CoreGraphics.framework","path":"System/Library/Frameworks/CoreGraphics.framework","sourceTree":"SDKROOT"},"B9E76574180BDDA800888C06_comment":"CoreGraphics.framework","B9E76576180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"wrapper.framework","name":"UIKit.framework","path":"System/Library/Frameworks/UIKit.framework","sourceTree":"SDKROOT"},"B9E76576180BDDA800888C06_comment":"UIKit.framework","B9E7657A180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"text.plist.xml","path":"\"test-Info.plist\"","sourceTree":"\"\""},"B9E7657A180BDDA800888C06_comment":"test-Info.plist","B9E7657C180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"text.plist.strings","name":"en","path":"en.lproj/InfoPlist.strings","sourceTree":"\"\""},"B9E7657C180BDDA800888C06_comment":"en","B9E7657E180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"sourcecode.c.objc","path":"main.m","sourceTree":"\"\""},"B9E7657E180BDDA800888C06_comment":"main.m","B9E76580180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"sourcecode.c.h","path":"\"test-Prefix.pch\"","sourceTree":"\"\""},"B9E76580180BDDA800888C06_comment":"test-Prefix.pch","B9E76581180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"sourcecode.c.h","path":"AppDelegate.h","sourceTree":"\"\""},"B9E76581180BDDA800888C06_comment":"AppDelegate.h","B9E76582180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"sourcecode.c.objc","path":"AppDelegate.m","sourceTree":"\"\""},"B9E76582180BDDA800888C06_comment":"AppDelegate.m","B9E76585180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"file.storyboard","name":"Base","path":"Base.lproj/Main_iPhone.storyboard","sourceTree":"\"\""},"B9E76585180BDDA800888C06_comment":"Base","B9E76588180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"file.storyboard","name":"Base","path":"Base.lproj/Main_iPad.storyboard","sourceTree":"\"\""},"B9E76588180BDDA800888C06_comment":"Base","B9E7658A180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"sourcecode.c.h","path":"MasterViewController.h","sourceTree":"\"\""},"B9E7658A180BDDA800888C06_comment":"MasterViewController.h","B9E7658B180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"sourcecode.c.objc","path":"MasterViewController.m","sourceTree":"\"\""},"B9E7658B180BDDA800888C06_comment":"MasterViewController.m","B9E7658D180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"sourcecode.c.h","path":"DetailViewController.h","sourceTree":"\"\""},"B9E7658D180BDDA800888C06_comment":"DetailViewController.h","B9E7658E180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"sourcecode.c.objc","path":"DetailViewController.m","sourceTree":"\"\""},"B9E7658E180BDDA800888C06_comment":"DetailViewController.m","B9E76590180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"folder.assetcatalog","path":"Images.xcassets","sourceTree":"\"\""},"B9E76590180BDDA800888C06_comment":"Images.xcassets","B9E76596180BDDA800888C06":{"isa":"PBXFileReference","explicitFileType":"wrapper.cfbundle","includeInIndex":0,"path":"testTests.xctest","sourceTree":"BUILT_PRODUCTS_DIR"},"B9E76596180BDDA800888C06_comment":"testTests.xctest","B9E76597180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"wrapper.framework","name":"XCTest.framework","path":"Library/Frameworks/XCTest.framework","sourceTree":"DEVELOPER_DIR"},"B9E76597180BDDA800888C06_comment":"XCTest.framework","B9E7659F180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"text.plist.xml","path":"\"testTests-Info.plist\"","sourceTree":"\"\""},"B9E7659F180BDDA800888C06_comment":"testTests-Info.plist","B9E765A1180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"text.plist.strings","name":"en","path":"en.lproj/InfoPlist.strings","sourceTree":"\"\""},"B9E765A1180BDDA800888C06_comment":"en","B9E765A3180BDDA800888C06":{"isa":"PBXFileReference","lastKnownFileType":"sourcecode.c.objc","path":"testTests.m","sourceTree":"\"\""},"B9E765A3180BDDA800888C06_comment":"testTests.m"},"PBXFrameworksBuildPhase":{"B9E7656C180BDDA800888C06":{"isa":"PBXFrameworksBuildPhase","buildActionMask":2147483647,"files":[{"value":"B9E76575180BDDA800888C06","comment":"CoreGraphics.framework in Frameworks"},{"value":"B9E76577180BDDA800888C06","comment":"UIKit.framework in Frameworks"},{"value":"B9E76573180BDDA800888C06","comment":"Foundation.framework in Frameworks"}],"runOnlyForDeploymentPostprocessing":0},"B9E7656C180BDDA800888C06_comment":"Frameworks","B9E76593180BDDA800888C06":{"isa":"PBXFrameworksBuildPhase","buildActionMask":2147483647,"files":[{"value":"B9E76598180BDDA800888C06","comment":"XCTest.framework in Frameworks"},{"value":"B9E7659A180BDDA800888C06","comment":"UIKit.framework in Frameworks"},{"value":"B9E76599180BDDA800888C06","comment":"Foundation.framework in Frameworks"}],"runOnlyForDeploymentPostprocessing":0},"B9E76593180BDDA800888C06_comment":"Frameworks"},"PBXGroup":{"B9E76566180BDDA800888C06":{"isa":"PBXGroup","children":[{"value":"B9E76578180BDDA800888C06","comment":"test"},{"value":"B9E7659D180BDDA800888C06","comment":"testTests"},{"value":"B9E76571180BDDA800888C06","comment":"Frameworks"},{"value":"B9E76570180BDDA800888C06","comment":"Products"}],"sourceTree":"\"\""},"B9E76570180BDDA800888C06":{"isa":"PBXGroup","children":[{"value":"B9E7656F180BDDA800888C06","comment":"test.app"},{"value":"B9E76596180BDDA800888C06","comment":"testTests.xctest"}],"name":"Products","sourceTree":"\"\""},"B9E76570180BDDA800888C06_comment":"Products","B9E76571180BDDA800888C06":{"isa":"PBXGroup","children":[{"value":"B9E76572180BDDA800888C06","comment":"Foundation.framework"},{"value":"B9E76574180BDDA800888C06","comment":"CoreGraphics.framework"},{"value":"B9E76576180BDDA800888C06","comment":"UIKit.framework"},{"value":"B9E76597180BDDA800888C06","comment":"XCTest.framework"}],"name":"Frameworks","sourceTree":"\"\""},"B9E76571180BDDA800888C06_comment":"Frameworks","B9E76578180BDDA800888C06":{"isa":"PBXGroup","children":[{"value":"B9E76581180BDDA800888C06","comment":"AppDelegate.h"},{"value":"B9E76582180BDDA800888C06","comment":"AppDelegate.m"},{"value":"B9E76584180BDDA800888C06","comment":"Main_iPhone.storyboard"},{"value":"B9E76587180BDDA800888C06","comment":"Main_iPad.storyboard"},{"value":"B9E7658A180BDDA800888C06","comment":"MasterViewController.h"},{"value":"B9E7658B180BDDA800888C06","comment":"MasterViewController.m"},{"value":"B9E7658D180BDDA800888C06","comment":"DetailViewController.h"},{"value":"B9E7658E180BDDA800888C06","comment":"DetailViewController.m"},{"value":"B9E76590180BDDA800888C06","comment":"Images.xcassets"},{"value":"B9E76579180BDDA800888C06","comment":"Supporting Files"}],"path":"test","sourceTree":"\"\""},"B9E76578180BDDA800888C06_comment":"test","B9E76579180BDDA800888C06":{"isa":"PBXGroup","children":[{"value":"B9E7657A180BDDA800888C06","comment":"test-Info.plist"},{"value":"B9E7657B180BDDA800888C06","comment":"InfoPlist.strings"},{"value":"B9E7657E180BDDA800888C06","comment":"main.m"},{"value":"B9E76580180BDDA800888C06","comment":"test-Prefix.pch"}],"name":"\"Supporting Files\"","sourceTree":"\"\""},"B9E76579180BDDA800888C06_comment":"Supporting Files","B9E7659D180BDDA800888C06":{"isa":"PBXGroup","children":[{"value":"B9E765A3180BDDA800888C06","comment":"testTests.m"},{"value":"B9E7659E180BDDA800888C06","comment":"Supporting Files"}],"path":"testTests","sourceTree":"\"\""},"B9E7659D180BDDA800888C06_comment":"testTests","B9E7659E180BDDA800888C06":{"isa":"PBXGroup","children":[{"value":"B9E7659F180BDDA800888C06","comment":"testTests-Info.plist"},{"value":"B9E765A0180BDDA800888C06","comment":"InfoPlist.strings"}],"name":"\"Supporting Files\"","sourceTree":"\"\""},"B9E7659E180BDDA800888C06_comment":"Supporting Files"},"PBXNativeTarget":{"B9E7656E180BDDA800888C06":{"isa":"PBXNativeTarget","buildConfigurationList":"B9E765A7180BDDA800888C06","buildConfigurationList_comment":"Build configuration list for PBXNativeTarget \"test\"","buildPhases":[{"value":"B9E7656B180BDDA800888C06","comment":"Sources"},{"value":"B9E7656C180BDDA800888C06","comment":"Frameworks"},{"value":"B9E7656D180BDDA800888C06","comment":"Resources"}],"buildRules":[],"dependencies":[],"name":"test","productName":"test","productReference":"B9E7656F180BDDA800888C06","productReference_comment":"test.app","productType":"\"com.apple.product-type.application\""},"B9E7656E180BDDA800888C06_comment":"test","B9E76595180BDDA800888C06":{"isa":"PBXNativeTarget","buildConfigurationList":"B9E765AA180BDDA800888C06","buildConfigurationList_comment":"Build configuration list for PBXNativeTarget \"testTests\"","buildPhases":[{"value":"B9E76592180BDDA800888C06","comment":"Sources"},{"value":"B9E76593180BDDA800888C06","comment":"Frameworks"},{"value":"B9E76594180BDDA800888C06","comment":"Resources"}],"buildRules":[],"dependencies":[{"value":"B9E7659C180BDDA800888C06","comment":"PBXTargetDependency"}],"name":"testTests","productName":"testTests","productReference":"B9E76596180BDDA800888C06","productReference_comment":"testTests.xctest","productType":"\"com.apple.product-type.bundle.unit-test\""},"B9E76595180BDDA800888C06_comment":"testTests"},"PBXProject":{"B9E76567180BDDA800888C06":{"isa":"PBXProject","attributes":{"LastUpgradeCheck":500,"ORGANIZATIONNAME":"Cordova","TargetAttributes":{"B9E76595180BDDA800888C06":{"TestTargetID":"B9E7656E180BDDA800888C06"}}},"buildConfigurationList":"B9E7656A180BDDA800888C06","buildConfigurationList_comment":"Build configuration list for PBXProject \"test\"","compatibilityVersion":"\"Xcode 3.2\"","developmentRegion":"English","hasScannedForEncodings":0,"knownRegions":["en","Base"],"mainGroup":"B9E76566180BDDA800888C06","productRefGroup":"B9E76570180BDDA800888C06","productRefGroup_comment":"Products","projectDirPath":"\"\"","projectRoot":"\"\"","targets":[{"value":"B9E7656E180BDDA800888C06","comment":"test"},{"value":"B9E76595180BDDA800888C06","comment":"testTests"}]},"B9E76567180BDDA800888C06_comment":"Project object"},"PBXResourcesBuildPhase":{"B9E7656D180BDDA800888C06":{"isa":"PBXResourcesBuildPhase","buildActionMask":2147483647,"files":[{"value":"B9E76589180BDDA800888C06","comment":"Main_iPad.storyboard in Resources"},{"value":"B9E76591180BDDA800888C06","comment":"Images.xcassets in Resources"},{"value":"B9E76586180BDDA800888C06","comment":"Main_iPhone.storyboard in Resources"},{"value":"B9E7657D180BDDA800888C06","comment":"InfoPlist.strings in Resources"}],"runOnlyForDeploymentPostprocessing":0},"B9E7656D180BDDA800888C06_comment":"Resources","B9E76594180BDDA800888C06":{"isa":"PBXResourcesBuildPhase","buildActionMask":2147483647,"files":[{"value":"B9E765A2180BDDA800888C06","comment":"InfoPlist.strings in Resources"}],"runOnlyForDeploymentPostprocessing":0},"B9E76594180BDDA800888C06_comment":"Resources"},"PBXSourcesBuildPhase":{"B9E7656B180BDDA800888C06":{"isa":"PBXSourcesBuildPhase","buildActionMask":2147483647,"files":[{"value":"B9E76583180BDDA800888C06","comment":"AppDelegate.m in Sources"},{"value":"B9E7658C180BDDA800888C06","comment":"MasterViewController.m in Sources"},{"value":"B9E7657F180BDDA800888C06","comment":"main.m in Sources"},{"value":"B9E7658F180BDDA800888C06","comment":"DetailViewController.m in Sources"}],"runOnlyForDeploymentPostprocessing":0},"B9E7656B180BDDA800888C06_comment":"Sources","B9E76592180BDDA800888C06":{"isa":"PBXSourcesBuildPhase","buildActionMask":2147483647,"files":[{"value":"B9E765A4180BDDA800888C06","comment":"testTests.m in Sources"}],"runOnlyForDeploymentPostprocessing":0},"B9E76592180BDDA800888C06_comment":"Sources"},"PBXTargetDependency":{"B9E7659C180BDDA800888C06":{"isa":"PBXTargetDependency","target":"B9E7656E180BDDA800888C06","target_comment":"test","targetProxy":"B9E7659B180BDDA800888C06","targetProxy_comment":"PBXContainerItemProxy"},"B9E7659C180BDDA800888C06_comment":"PBXTargetDependency"},"PBXVariantGroup":{"B9E7657B180BDDA800888C06":{"isa":"PBXVariantGroup","children":[{"value":"B9E7657C180BDDA800888C06","comment":"en"}],"name":"InfoPlist.strings","sourceTree":"\"\""},"B9E7657B180BDDA800888C06_comment":"InfoPlist.strings","B9E76584180BDDA800888C06":{"isa":"PBXVariantGroup","children":[{"value":"B9E76585180BDDA800888C06","comment":"Base"}],"name":"Main_iPhone.storyboard","sourceTree":"\"\""},"B9E76584180BDDA800888C06_comment":"Main_iPhone.storyboard","B9E76587180BDDA800888C06":{"isa":"PBXVariantGroup","children":[{"value":"B9E76588180BDDA800888C06","comment":"Base"}],"name":"Main_iPad.storyboard","sourceTree":"\"\""},"B9E76587180BDDA800888C06_comment":"Main_iPad.storyboard","B9E765A0180BDDA800888C06":{"isa":"PBXVariantGroup","children":[{"value":"B9E765A1180BDDA800888C06","comment":"en"}],"name":"InfoPlist.strings","sourceTree":"\"\""},"B9E765A0180BDDA800888C06_comment":"InfoPlist.strings"},"XCBuildConfiguration":{"B9E765A5180BDDA800888C06":{"isa":"XCBuildConfiguration","buildSettings":{"ALWAYS_SEARCH_USER_PATHS":"NO","ARCHS":"\"$(ARCHS_STANDARD_INCLUDING_64_BIT)\"","CLANG_CXX_LANGUAGE_STANDARD":"\"gnu++0x\"","CLANG_CXX_LIBRARY":"\"libc++\"","CLANG_ENABLE_MODULES":"YES","CLANG_ENABLE_OBJC_ARC":"YES","CLANG_WARN_BOOL_CONVERSION":"YES","CLANG_WARN_CONSTANT_CONVERSION":"YES","CLANG_WARN_DIRECT_OBJC_ISA_USAGE":"YES_ERROR","CLANG_WARN_EMPTY_BODY":"YES","CLANG_WARN_ENUM_CONVERSION":"YES","CLANG_WARN_INT_CONVERSION":"YES","CLANG_WARN_OBJC_ROOT_CLASS":"YES_ERROR","CLANG_WARN__DUPLICATE_METHOD_MATCH":"YES","\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\"":"\"iPhone Developer\"","COPY_PHASE_STRIP":"NO","GCC_C_LANGUAGE_STANDARD":"gnu99","GCC_DYNAMIC_NO_PIC":"NO","GCC_OPTIMIZATION_LEVEL":0,"GCC_PREPROCESSOR_DEFINITIONS":["\"DEBUG=1\"","\"$(inherited)\""],"GCC_SYMBOLS_PRIVATE_EXTERN":"NO","GCC_WARN_64_TO_32_BIT_CONVERSION":"YES","GCC_WARN_ABOUT_RETURN_TYPE":"YES_ERROR","GCC_WARN_UNDECLARED_SELECTOR":"YES","GCC_WARN_UNINITIALIZED_AUTOS":"YES","GCC_WARN_UNUSED_FUNCTION":"YES","GCC_WARN_UNUSED_VARIABLE":"YES","IPHONEOS_DEPLOYMENT_TARGET":"7.0","LIBRARY_SEARCH_PATHS":"\"$(inherited)\"","ONLY_ACTIVE_ARCH":"YES","SDKROOT":"iphoneos","TARGETED_DEVICE_FAMILY":"\"1,2\""},"name":"Debug"},"B9E765A5180BDDA800888C06_comment":"Debug","B9E765A6180BDDA800888C06":{"isa":"XCBuildConfiguration","buildSettings":{"ALWAYS_SEARCH_USER_PATHS":"NO","ARCHS":"\"$(ARCHS_STANDARD_INCLUDING_64_BIT)\"","CLANG_CXX_LANGUAGE_STANDARD":"\"gnu++0x\"","CLANG_CXX_LIBRARY":"\"libc++\"","CLANG_ENABLE_MODULES":"YES","CLANG_ENABLE_OBJC_ARC":"YES","CLANG_WARN_BOOL_CONVERSION":"YES","CLANG_WARN_CONSTANT_CONVERSION":"YES","CLANG_WARN_DIRECT_OBJC_ISA_USAGE":"YES_ERROR","CLANG_WARN_EMPTY_BODY":"YES","CLANG_WARN_ENUM_CONVERSION":"YES","CLANG_WARN_INT_CONVERSION":"YES","CLANG_WARN_OBJC_ROOT_CLASS":"YES_ERROR","CLANG_WARN__DUPLICATE_METHOD_MATCH":"YES","\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\"":"\"iPhone Developer\"","COPY_PHASE_STRIP":"YES","ENABLE_NS_ASSERTIONS":"NO","GCC_C_LANGUAGE_STANDARD":"gnu99","GCC_WARN_64_TO_32_BIT_CONVERSION":"YES","GCC_WARN_ABOUT_RETURN_TYPE":"YES_ERROR","GCC_WARN_UNDECLARED_SELECTOR":"YES","GCC_WARN_UNINITIALIZED_AUTOS":"YES","GCC_WARN_UNUSED_FUNCTION":"YES","GCC_WARN_UNUSED_VARIABLE":"YES","IPHONEOS_DEPLOYMENT_TARGET":"7.0","LIBRARY_SEARCH_PATHS":"\"$(inherited)\"","SDKROOT":"iphoneos","TARGETED_DEVICE_FAMILY":"\"1,2\"","VALIDATE_PRODUCT":"YES"},"name":"Release"},"B9E765A6180BDDA800888C06_comment":"Release","B9E765A8180BDDA800888C06":{"isa":"XCBuildConfiguration","buildSettings":{"ASSETCATALOG_COMPILER_APPICON_NAME":"AppIcon","ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME":"LaunchImage","GCC_PRECOMPILE_PREFIX_HEADER":"YES","GCC_PREFIX_HEADER":"\"test/test-Prefix.pch\"","INFOPLIST_FILE":"\"test/test-Info.plist\"","LIBRARY_SEARCH_PATHS":"\"$(inherited)\"","PRODUCT_NAME":"\"$(TARGET_NAME)\"","WRAPPER_EXTENSION":"app"},"name":"Debug"},"B9E765A8180BDDA800888C06_comment":"Debug","B9E765A9180BDDA800888C06":{"isa":"XCBuildConfiguration","buildSettings":{"ASSETCATALOG_COMPILER_APPICON_NAME":"AppIcon","ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME":"LaunchImage","GCC_PRECOMPILE_PREFIX_HEADER":"YES","GCC_PREFIX_HEADER":"\"test/test-Prefix.pch\"","INFOPLIST_FILE":"\"test/test-Info.plist\"","LIBRARY_SEARCH_PATHS":"\"$(inherited)\"","PRODUCT_NAME":"\"$(TARGET_NAME)\"","WRAPPER_EXTENSION":"app"},"name":"Release"},"B9E765A9180BDDA800888C06_comment":"Release","B9E765AB180BDDA800888C06":{"isa":"XCBuildConfiguration","buildSettings":{"ARCHS":"\"$(ARCHS_STANDARD_INCLUDING_64_BIT)\"","BUNDLE_LOADER":"\"$(BUILT_PRODUCTS_DIR)/test.app/test\"","FRAMEWORK_SEARCH_PATHS":["\"$(SDKROOT)/Developer/Library/Frameworks\"","\"$(inherited)\"","\"$(DEVELOPER_FRAMEWORKS_DIR)\""],"GCC_PRECOMPILE_PREFIX_HEADER":"YES","GCC_PREFIX_HEADER":"\"test/test-Prefix.pch\"","GCC_PREPROCESSOR_DEFINITIONS":["\"DEBUG=1\"","\"$(inherited)\""],"INFOPLIST_FILE":"\"testTests/testTests-Info.plist\"","PRODUCT_NAME":"\"$(TARGET_NAME)\"","TEST_HOST":"\"$(BUNDLE_LOADER)\"","WRAPPER_EXTENSION":"xctest"},"name":"Debug"},"B9E765AB180BDDA800888C06_comment":"Debug","B9E765AC180BDDA800888C06":{"isa":"XCBuildConfiguration","buildSettings":{"ARCHS":"\"$(ARCHS_STANDARD_INCLUDING_64_BIT)\"","BUNDLE_LOADER":"\"$(BUILT_PRODUCTS_DIR)/test.app/test\"","FRAMEWORK_SEARCH_PATHS":["\"$(SDKROOT)/Developer/Library/Frameworks\"","\"$(inherited)\"","\"$(DEVELOPER_FRAMEWORKS_DIR)\""],"GCC_PRECOMPILE_PREFIX_HEADER":"YES","GCC_PREFIX_HEADER":"\"test/test-Prefix.pch\"","INFOPLIST_FILE":"\"testTests/testTests-Info.plist\"","PRODUCT_NAME":"\"$(TARGET_NAME)\"","TEST_HOST":"\"$(BUNDLE_LOADER)\"","WRAPPER_EXTENSION":"xctest"},"name":"Release"},"B9E765AC180BDDA800888C06_comment":"Release"},"XCConfigurationList":{"B9E7656A180BDDA800888C06":{"isa":"XCConfigurationList","buildConfigurations":[{"value":"B9E765A5180BDDA800888C06","comment":"Debug"},{"value":"B9E765A6180BDDA800888C06","comment":"Release"}],"defaultConfigurationIsVisible":0,"defaultConfigurationName":"Release"},"B9E7656A180BDDA800888C06_comment":"Build configuration list for PBXProject \"test\"","B9E765A7180BDDA800888C06":{"isa":"XCConfigurationList","buildConfigurations":[{"value":"B9E765A8180BDDA800888C06","comment":"Debug"},{"value":"B9E765A9180BDDA800888C06","comment":"Release"}],"defaultConfigurationIsVisible":0},"B9E765A7180BDDA800888C06_comment":"Build configuration list for PBXNativeTarget \"test\"","B9E765AA180BDDA800888C06":{"isa":"XCConfigurationList","buildConfigurations":[{"value":"B9E765AB180BDDA800888C06","comment":"Debug"},{"value":"B9E765AC180BDDA800888C06","comment":"Release"}],"defaultConfigurationIsVisible":0},"B9E765AA180BDDA800888C06_comment":"Build configuration list for PBXNativeTarget \"testTests\""}},"rootObject":"B9E76567180BDDA800888C06","rootObject_comment":"Project object"}} 2 | --------------------------------------------------------------------------------