diff --git a/.gitignore b/.gitignore index 796bd5be..5ef95a11 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ node_modules/* .DS_Store npm-debug.log package-lock.json +.ns-build-pbxgroup-data.json +*.tgz \ No newline at end of file diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 442a34a4..00000000 --- a/.npmignore +++ /dev/null @@ -1,7 +0,0 @@ -lib/parser/pbxproj.pegjs -test/ -.travis.yml -AUTHORS -Makefile -RELEASENOTES.md -*.tgz \ No newline at end of file diff --git a/.ratignore b/.ratignore deleted file mode 100644 index eecfa9a4..00000000 --- a/.ratignore +++ /dev/null @@ -1,2 +0,0 @@ -fixtures -*.pbxproj diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 14a176e1..00000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -language: node_js -sudo: false - -git: - depth: 10 - -node_js: - - 6 - - 8 - - 10 - -install: - - nvm --version - - node --version - - npm --version - - npm install - -script: - - npm test diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..f2807923 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "skipFiles": ["/**"], + "runtimeExecutable": "nodeunit", + "runtimeArgs": ["test/parser", "test"] + } + ] +} diff --git a/README.md b/README.md index 53f9376e..6f9060ef 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,6 @@ # nativescript-dev-xcode - -[![Build Status](https://travis-ci.org/NativeScript/nativescript-dev-xcode.svg?branch=master)](https://travis-ci.org/apache/cordova-node-xcode) - Parser utility for xcodeproj project files Allows you to edit xcodeproject files and write them back out. @@ -36,19 +31,19 @@ Forked from: [apache/cordova-node-xcode](https://github.com/apache/cordova-node- ```js // API is a bit wonky right now -var xcode = require('xcode'), - fs = require('fs'), - projectPath = 'myproject.xcodeproj/project.pbxproj', - myProj = xcode.project(projectPath); +var xcode = require("xcode"), + fs = require("fs"), + projectPath = "myproject.xcodeproj/project.pbxproj", + myProj = xcode.project(projectPath); // parsing is async, in a different process myProj.parse(function (err) { - myProj.addHeaderFile('foo.h'); - myProj.addSourceFile('foo.m'); - myProj.addFramework('FooKit.framework'); - - fs.writeFileSync(projectPath, myProj.writeSync()); - console.log('new project written'); + myProj.addHeaderFile("foo.h"); + myProj.addSourceFile("foo.m"); + myProj.addFramework("FooKit.framework"); + + fs.writeFileSync(projectPath, myProj.writeSync()); + console.log("new project written"); }); ``` @@ -63,7 +58,9 @@ grammar. Other tests will use the prebuilt parser (`lib/parser/pbxproj.js`). To rebuild the parser js file after editing the grammar, run: - npm run pegjs +``` +npm run pegjs +``` (and be sure to restore the Apache license notice in `lib/parser/pbxproj.js` before committing) diff --git a/lib/constants.js b/lib/constants.js index fefd9def..68d0b158 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -112,6 +112,11 @@ function unquoted(text) { return text == null ? '' : text.replace (/(^")|("$)/g, '') } +function quoteIfNeeded(name) { + const quotedName = (name.indexOf(" ") >= 0 || name.indexOf("@") >= 0) && name[0] !== `"` ? `"${name}"` : name; + return quotedName; +} + function isModuleMapFileType(fileType) { return fileType === FILETYPE_BY_EXTENSION.modulemap; } @@ -137,5 +142,6 @@ module.exports = { isEntitlementFileType, isPlistFileType, isModuleMapFileType, - unquoted + unquoted, + quoteIfNeeded } \ No newline at end of file diff --git a/lib/guidMapper.js b/lib/guidMapper.js new file mode 100644 index 00000000..98300fff --- /dev/null +++ b/lib/guidMapper.js @@ -0,0 +1,52 @@ +const fs = require('fs'); +const path = require('path'); + +function guidMapper(filePath) { + this.filePath = filePath; + this.data = this.loadFromFile(); +} + +guidMapper.prototype.loadFromFile = function () { + try { + const rawData = fs.readFileSync(this.filePath, 'utf8'); + return JSON.parse(rawData); + } catch (error) { + // If file doesn't exist or there's an error parsing it, initialize with an empty object. + return {}; + } +}; + +guidMapper.prototype.writeFileSync = function () { + const jsonData = JSON.stringify(this.data, null, 2); + fs.writeFileSync(this.filePath, jsonData, 'utf8'); +}; + +guidMapper.prototype.addEntry = function (guid, path, name) { + if(!!guid && !! path && !!name){ + this.data[guid] = { path: path, name: name }; + } +}; + +guidMapper.prototype.removeEntry = function (guid) { + if (this.data[guid]) { + delete this.data[guid]; + } +}; + +guidMapper.prototype.getEntries = function () { + return this.data; +}; + +guidMapper.prototype.findEntryGuid = function (name, path) { + for (const guid in this.data) { + if (this.data.hasOwnProperty(guid)) { + const entry = this.data[guid]; + if (entry.path === path && entry.name === name) { + return guid; + } + } + } + return null; +}; + +module.exports = guidMapper; \ No newline at end of file diff --git a/lib/pbxFile.js b/lib/pbxFile.js index 1b52d259..e9770848 100644 --- a/lib/pbxFile.js +++ b/lib/pbxFile.js @@ -128,9 +128,11 @@ function pbxFile(filepath, opt) { if (opt.explicitFileType) { this.explicitFileType = opt.explicitFileType; this.basename = this.basename + '.' + defaultExtension(this); - delete this.path; + // dont delete path as it is used after + // delete this.path; delete this.lastKnownFileType; - delete this.group; + // dont delete group as it is used after + // delete this.group; delete this.defaultEncoding; } @@ -155,4 +157,4 @@ function pbxFile(filepath, opt) { } } -module.exports = pbxFile; \ No newline at end of file +module.exports = pbxFile; diff --git a/lib/pbxProject.js b/lib/pbxProject.js index d5102214..694ca37e 100644 --- a/lib/pbxProject.js +++ b/lib/pbxProject.js @@ -35,7 +35,8 @@ var util = require('util'), isEntitlementFileType = constants.isEntitlementFileType, isAssetFileType = constants.isAssetFileType, isPlistFileType = constants.isPlistFileType, - isModuleMapFileType = constants.isModuleMapFileType; + isModuleMapFileType = constants.isModuleMapFileType, + guidMapper = require('./guidMapper'); function pbxProject(filename) { if (!(this instanceof pbxProject)) @@ -74,6 +75,10 @@ pbxProject.prototype.parseSync = function() { } pbxProject.prototype.writeSync = function(options) { + if(this.pbxGroupTracker){ + this.pbxGroupTracker.writeFileSync(); + } + this.writer = new pbxWriter(this.hash, options); return this.writer.writeSync(); } @@ -98,7 +103,7 @@ pbxProject.prototype.allUuids = function() { pbxProject.prototype.generateUuid = function() { var id = $uuid.v4() .replace(/-/g, '') - .substr(0, 24) + .substring(0, 24) .toUpperCase() if (this.allUuids().indexOf(id) >= 0) { @@ -114,8 +119,8 @@ pbxProject.prototype.addPluginFile = function(path, opt) { file.plugin = true; // durr correctForPluginsPath(file, this); - // null is better for early errors - if (this.hasFile(file.path)) return null; + const existingFile = this.hasFile(file.path); + if (existingFile) return {...file, fileRef: existingFile.fileRef}; file.fileRef = this.generateUuid(); @@ -251,19 +256,25 @@ pbxProject.prototype.addResourceFile = function(path, opt, group) { opt = opt || {}; var file; + var fileIsAlreadyAdded = false; if (opt.plugin) { file = this.addPluginFile(path, opt); if (!file) return false; } else { file = new pbxFile(path, opt); - if (this.hasFile(file.path)) return false; + const existingFile = this.hasFile(file.path); + fileIsAlreadyAdded = !!existingFile; + if (existingFile) { + // use existing fileRef + file.fileRef = existingFile.fileRef; + } } file.uuid = this.generateUuid(); file.target = opt ? opt.target : undefined; - if (!opt.plugin) { + if (!opt.plugin && !fileIsAlreadyAdded) { correctForResourcesPath(file, this); file.fileRef = this.generateUuid(); } @@ -273,7 +284,7 @@ pbxProject.prototype.addResourceFile = function(path, opt, group) { this.addToPbxResourcesBuildPhase(file); // PBXResourcesBuildPhase } - if (!opt.plugin) { + if (!opt.plugin && !fileIsAlreadyAdded) { this.addToPbxFileReferenceSection(file); // PBXFileReference if (group) { if (this.getPBXGroupByKey(group)) { @@ -340,8 +351,7 @@ pbxProject.prototype.addFramework = function(fpath, opt) { var fileReference = this.hasFile(file.path); if (fileReference) { - var key = this.getFileKey(file.path); - file.fileRef = key; + file.fileRef = fileReference.fileRef; } else { this.addToPbxFileReferenceSection(file); // PBXFileReference this.addToFrameworksPbxGroup(file); // PBXGroup @@ -419,13 +429,14 @@ pbxProject.prototype.addCopyfile = function(fpath, opt) { // catch duplicates if (this.hasFile(file.path)) { file = this.hasFile(file.path); + } else { + file.fileRef = this.generateUuid(); + this.addToPbxFileReferenceSection(file); // PBXFileReference } - - file.fileRef = file.uuid = this.generateUuid(); + file.uuid = this.generateUuid(); file.target = opt ? opt.target : undefined; this.addToPbxBuildFileSection(file); // PBXBuildFile - this.addToPbxFileReferenceSection(file); // PBXFileReference this.addToPbxCopyfilesBuildPhase(file); // PBXCopyFilesBuildPhase return file; @@ -465,19 +476,24 @@ pbxProject.prototype.addStaticLibrary = function(path, opt) { opt = opt || {}; var file; + var fileIsAlreadyAdded = false; if (opt.plugin) { file = this.addPluginFile(path, opt); if (!file) return false; } else { file = new pbxFile(path, opt); - if (this.hasFile(file.path)) return false; + const existingFile = this.hasFile(file.path); + fileIsAlreadyAdded = !!existingFile; + if (existingFile) { + file.fileRef = existingFile.fileRef; + } } file.uuid = this.generateUuid(); file.target = opt ? opt.target : undefined; - if (!opt.plugin) { + if (!opt.plugin && !fileIsAlreadyAdded) { file.fileRef = this.generateUuid(); this.addToPbxFileReferenceSection(file); // PBXFileReference } @@ -538,19 +554,31 @@ pbxProject.prototype.findMainPbxGroup = function () { return null; } +pbxProject.prototype.getPbxGroupTracker = function (path) { + + if(!this.pbxGroupTracker){ + this.pbxGroupTracker = new guidMapper($path.join(path, '.ns-build-pbxgroup-data.json')); + } + + return this.pbxGroupTracker; +} pbxProject.prototype.addPbxGroup = function (filePathsArray, name, path, sourceTree, opt) { opt = opt || {}; var srcRootPath = $path.dirname($path.dirname(this.filepath)); - var oldGroup = this.pbxGroupByName(name); - if (oldGroup) { - this.removePbxGroup(name, path); + + var existingGroupId = this.getPbxGroupTracker(srcRootPath).findEntryGuid(name, path); + if(existingGroupId){ + if(this.getPBXGroupByKey(existingGroupId)){ + this.removePbxGroupByKey(existingGroupId, path); + } + this.pbxGroupTracker.removeEntry(existingGroupId); } var groups = this.hash.project.objects['PBXGroup'], pbxGroupUuid = opt.uuid || this.generateUuid(), commentKey = f("%s_comment", pbxGroupUuid), - groupName = (name.indexOf(" ") >= 0 || name.indexOf("@") >= 0) && name[0] !== `"` ? `"${name}"` : name, + groupName = constants.quoteIfNeeded(name), pbxGroup = { isa: 'PBXGroup', children: [], @@ -561,10 +589,13 @@ pbxProject.prototype.addPbxGroup = function (filePathsArray, name, path, sourceT filePathToReference = {}; //path is mandatory only for the main group - if(!opt.filesRelativeToProject) { + if(!opt.filesRelativeToProject && path) { pbxGroup.path = path; } + // save to group to the tracker + this.pbxGroupTracker.addEntry(pbxGroupUuid, path, name); + for (var key in fileReferenceSection) { // only look for comments if (!COMMENT_KEY.test(key)) continue; @@ -593,7 +624,14 @@ pbxProject.prototype.addPbxGroup = function (filePathsArray, name, path, sourceT if(opt.target) { file.target = opt.target; } - if (fs.existsSync(filePath) && fs.lstatSync(filePath).isDirectory() && !isAssetFileType(file.lastKnownFileType)) { + // if the file is a symLink, isDirectory() returns false + // but isFile() too so we need to see if the realPath is a directory + const exists = fs.existsSync(filePath); + const stats = exists && fs.lstatSync(filePath); + const isSymlink = exists && fs.lstatSync(filePath).isSymbolicLink(); + let symRealPath = isSymlink && fs.realpathSync(filePath); + const isRealDir = stats && stats.isDirectory() || (isSymlink && fs.lstatSync(symRealPath).isDirectory()); + if (exists && isRealDir && !isAssetFileType(file.lastKnownFileType)) { if($path.extname(filePath) === ".lproj") { continue; } @@ -611,7 +649,7 @@ pbxProject.prototype.addPbxGroup = function (filePathsArray, name, path, sourceT } if(isEntitlementFileType(file.lastKnownFileType)) { - this.addToBuildSettings('CODE_SIGN_ENTITLEMENTS', file.path, opt.target); + this.addToBuildSettings('CODE_SIGN_ENTITLEMENTS', constants.quoteIfNeeded(file.path), opt.target); continue; } @@ -702,7 +740,7 @@ pbxProject.prototype.removePbxGroupByKey = function(groupKey, path) { if (!group) { return; } - + path = path || ""; var children = group.children; @@ -832,20 +870,20 @@ pbxProject.prototype.removeFromPluginsPbxGroup = function(file) { } } -pbxProject.prototype.addToResourcesPbxGroup = function(file) { - var pluginsGroup = this.pbxGroupByName('Resources'); +pbxProject.prototype.addToResourcesPbxGroup = function(file, groupName = 'Resources') { + var pluginsGroup = this.pbxGroupByName(groupName); if (!pluginsGroup) { - this.addPbxGroup([file.path], 'Resources'); + this.addPbxGroup([file.path], groupName); } else { pluginsGroup.children.push(pbxGroupChild(file)); } } -pbxProject.prototype.removeFromResourcesPbxGroup = function(file) { - if (!this.pbxGroupByName('Resources')) { +pbxProject.prototype.removeFromResourcesPbxGroup = function(file, groupName = 'Resources') { + if (!this.pbxGroupByName(groupName)) { return null; } - var pluginsGroupChildren = this.pbxGroupByName('Resources').children, i; + var pluginsGroupChildren = this.pbxGroupByName(groupName).children, i; for (i in pluginsGroupChildren) { if (pbxGroupChild(file).value == pluginsGroupChildren[i].value && pbxGroupChild(file).comment == pluginsGroupChildren[i].comment) { @@ -858,7 +896,7 @@ pbxProject.prototype.removeFromResourcesPbxGroup = function(file) { pbxProject.prototype.addToFrameworksPbxGroup = function(file) { var pluginsGroup = this.pbxGroupByName('Frameworks'); if (!pluginsGroup) { - this.addPbxGroup([file.path], 'Frameworks'); + this.addPbxGroup([file.path], 'Frameworks', 'Frameworks', null, { isMain: true, filesRelativeToProject: true}); } else { pluginsGroup.children.push(pbxGroupChild(file)); } @@ -1064,7 +1102,7 @@ pbxProject.prototype.addTargetDependency = function(target, dependencyTargets) { containerPortal: this.hash.project['rootObject'], containerPortal_comment: this.hash.project['rootObject_comment'], proxyType: 1, - remoteGlobalIDString: dependencyTargetUuid, + remoteGlobalIDString: dependencyTargetUuid, remoteInfo: nativeTargets[dependencyTargetUuid].name }, targetDependency = { @@ -1087,6 +1125,35 @@ pbxProject.prototype.addTargetDependency = function(target, dependencyTargets) { return { uuid: target, target: nativeTargets[target] }; } +pbxProject.prototype.removeBuildPhase = function(comment, target) { // Build phase files should be removed separately + var buildPhaseUuid = undefined, + buildPhaseTargetUuid = target || this.getFirstTarget().uuid + + if (this.hash.project.objects['PBXNativeTarget'][buildPhaseTargetUuid]['buildPhases']) { + let phases = this.hash.project.objects['PBXNativeTarget'][buildPhaseTargetUuid]['buildPhases']; + for (let i = 0; i < phases.length; i++) { + const phase = phases[i]; + if (phase.comment === comment) { + buildPhaseUuid = phase.value; + let commentKey = f("%s_comment", buildPhaseUuid) + if (this.hash.project.objects['PBXCopyFilesBuildPhase']) { + let phase = this.hash.project.objects['PBXCopyFilesBuildPhase'][commentKey] + delete phase + } + + if (this.hash.project.objects['PBXShellScriptBuildPhase']) { + let phase = this.hash.project.objects['PBXShellScriptBuildPhase'][commentKey] + delete phase + } + + phases.splice(i, 1); + } + } + + } + +} + pbxProject.prototype.addBuildPhase = function(filePathsArray, buildPhaseType, comment, target, optionsOrFolderType, subfolderPath) { var buildPhaseSection, fileReferenceSection = this.pbxFileReferenceSection(), @@ -1264,11 +1331,23 @@ pbxProject.prototype.pbxResourcesBuildPhaseObj = function(target) { } pbxProject.prototype.pbxFrameworksBuildPhaseObj = function(target) { - return this.buildPhaseObject('PBXFrameworksBuildPhase', 'Frameworks', target); + let buildPhase = this.buildPhaseObject('PBXFrameworksBuildPhase', 'Frameworks', target); + if (!buildPhase) { + // Create Frameworks phase in parent target + const phase = this.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target, 'frameworks'); + buildPhase = phase.buildPhase; + } + return buildPhase; } pbxProject.prototype.pbxEmbedFrameworksBuildPhaseObj = function (target) { - return this.buildPhaseObject('PBXCopyFilesBuildPhase', 'Embed Frameworks', target); + let buildPhase = this.buildPhaseObject('PBXCopyFilesBuildPhase', 'Embed Frameworks', target); + if (!buildPhase) { + // Create CopyFiles phase in parent target + const phase = this.addBuildPhase([], 'PBXCopyFilesBuildPhase', 'Embed Frameworks', target, 'frameworks'); + buildPhase = phase.buildPhase; + } + return buildPhase; }; // Find Build Phase from group/target @@ -1498,7 +1577,11 @@ pbxProject.prototype.addToHeaderSearchPaths = function(file, productName) { buildSettings['HEADER_SEARCH_PATHS'] = [INHERITED]; } - buildSettings['HEADER_SEARCH_PATHS'].push(searchPathForFile(file, this)); + // Check if the search path is already in the HEADER_SEARCH_PATHS and add it if it's not. + const searchPath = searchPathForFile(file, this); + if (buildSettings['HEADER_SEARCH_PATHS'].indexOf(searchPath) < 0) { + buildSettings['HEADER_SEARCH_PATHS'].push(searchPath); + } } } @@ -1608,7 +1691,7 @@ pbxProject.prototype.hasFile = function(filePath) { for (id in files) { file = files[id]; if (file.path == filePath || file.path == ('"' + filePath + '"')) { - return file; + return {...file, fileRef: id}; } } @@ -1628,11 +1711,12 @@ pbxProject.prototype.getFileKey = function(filePath) { return false; } -pbxProject.prototype.addTarget = function(name, type, subfolder, parentTarget) { +pbxProject.prototype.addTarget = function(name, type, subfolder, parentTarget, productTargetType) { // Setup uuid and name of new target var targetUuid = this.generateUuid(), targetType = type, + productTargetType = productTargetType || targetType, targetSubfolder = subfolder || name, targetName = name.trim(); @@ -1681,7 +1765,7 @@ pbxProject.prototype.addTarget = function(name, type, subfolder, parentTarget) { // Product: Create var productName = targetName, - productType = producttypeForTargettype(targetType), + productType = producttypeForTargettype(productTargetType), productFileType = filetypeForProducttype(productType), productFile = this.addProductFile(productName, { group: 'Copy Files', 'target': targetUuid, 'explicitFileType': productFileType}), productFileName = productFile.basename; @@ -1709,10 +1793,10 @@ pbxProject.prototype.addTarget = function(name, type, subfolder, parentTarget) { // Target: Add to PBXNativeTarget section this.addToPbxNativeTargetSection(target); - if (targetType === 'app_extension' || targetType === 'watch_extension' || targetType === 'watch_app') { - const isWatchApp = targetType === 'watch_app'; + if (productTargetType === 'app_extension' || productTargetType === 'watch_extension' || productTargetType === 'watch_app') { + const isWatchApp = productTargetType === 'watch_app'; const copyTargetUuid = parentTarget || this.getFirstTarget().uuid; - const phaseComment = targetType === 'watch_app' ? 'Embed Watch Content' : 'Copy Files'; + const phaseComment = productTargetType === 'watch_app' ? 'Embed Watch Content' : 'Copy Files'; let destination; if(isWatchApp) { @@ -1720,7 +1804,7 @@ pbxProject.prototype.addTarget = function(name, type, subfolder, parentTarget) { } // Create CopyFiles phase in parent target - this.addBuildPhase([], 'PBXCopyFilesBuildPhase', phaseComment, copyTargetUuid, targetType, destination); + this.addBuildPhase([], 'PBXCopyFilesBuildPhase', phaseComment, copyTargetUuid, productTargetType, destination); // Add product to CopyFiles phase this.addToPbxCopyfilesBuildPhase(productFile, phaseComment, copyTargetUuid); @@ -1783,10 +1867,13 @@ pbxProject.prototype.removeTarget = function(target, targetKey) { if(!stillReferenced) { var frameworkFileRef = fileReferenceSection[fileRef]; - var fileToRemove = new pbxFile(unquote(frameworkFileRef.path), {basename: frameworkFileRef.name}); - fileToRemove.fileRef = fileRef; - this.removeFromFrameworksPbxGroup(fileToRemove); - removeItemAndCommentFromSectionByUuid(fileReferenceSection, fileRef); + if (frameworkFileRef?.path) { + // when working with widgets, the framework might be in a different group + var fileToRemove = new pbxFile(unquote(frameworkFileRef.path), {basename: frameworkFileRef.name}); + fileToRemove.fileRef = fileRef; + this.removeFromFrameworksPbxGroup(fileToRemove); + removeItemAndCommentFromSectionByUuid(fileReferenceSection, fileRef); + } } } files = files.concat(frameworkFiles); @@ -1983,7 +2070,7 @@ function pbxFileReferenceObj(file) { fileObject.name = "\"" + fileObject.name + "\""; } - if(!file.path.match(NO_SPECIAL_SYMBOLS)) { + if(file.path && !file.path.match(NO_SPECIAL_SYMBOLS)) { fileObject.path = "\"" + fileObject.path + "\""; } @@ -2087,7 +2174,7 @@ function correctForFrameworksPath(file, project) { function correctForPath(file, project, group) { var r_group_dir = new RegExp('^' + group + '[\\\\/]'); - if (project.pbxGroupByName(group).path) + if (project.pbxGroupByName(group)?.path) file.path = file.path.replace(r_group_dir, ''); return file; @@ -2337,7 +2424,7 @@ pbxProject.prototype.getPBXGroupByKey = function(key) { }; pbxProject.prototype.getPBXVariantGroupByKey = function(key) { - return this.hash.project.objects['PBXVariantGroup'][key]; + return this.hash.project.objects['PBXVariantGroup']?.[key]; }; @@ -2450,10 +2537,11 @@ pbxProject.prototype.getPBXObject = function(name) { pbxProject.prototype.addFile = function (path, group, opt) { var file = new pbxFile(path, opt); - // null is better for early errors - if (this.hasFile(file.path)) return null; + const existingFile = this.hasFile(file.path); + if (existingFile) return {...file, fileRef: existingFile.fileRef}; file.fileRef = this.generateUuid(); + file.target = opt ? opt.target : undefined; this.addToPbxFileReferenceSection(file); // PBXFileReference diff --git a/package.json b/package.json index 5591b6af..69b21107 100644 --- a/package.json +++ b/package.json @@ -1,26 +1,30 @@ { - "author": "Telerik ", + "author": "NativeScript TSC ", "name": "nativescript-dev-xcode", "description": "parser for xcodeproj/project.pbxproj files", "main": "index.js", - "version": "0.2.0", + "version": "0.8.1", + "files": [ + "lib", + "!lib/parser/pbxproj.pegjs" + ], "repository": { "url": "https://github.com/NativeScript/nativescript-dev-xcode.git" }, "engines": { - "node": ">=6.0.0" + "node": ">=14.0.0" }, "dependencies": { - "simple-plist": "^1.0.0", - "uuid": "^3.3.2" + "simple-plist": "1.3.1", + "uuid": "9.0.1" }, "devDependencies": { - "nodeunit": "^0.11.3", - "pegjs": "^0.10.0" + "nodeunit": "0.11.3", + "pegjs": "0.10.0" }, "scripts": { - "pegjs": "node_modules/.bin/pegjs lib/parser/pbxproj.pegjs", - "test": "node_modules/.bin/nodeunit test/parser test" + "pegjs": "pegjs lib/parser/pbxproj.pegjs", + "test": "nodeunit test/parser test" }, "license": "Apache-2.0" } diff --git a/test/guidMapper.js b/test/guidMapper.js new file mode 100644 index 00000000..8c9cb72a --- /dev/null +++ b/test/guidMapper.js @@ -0,0 +1,69 @@ +var guidMapper = require('../lib/guidMapper'); +const fs = require('fs'); +const $uuid = require('uuid'); +const TEST_FILE_NAME = 'test/.ns-build-pbxgroup-data.json'; +const goodGUID = $uuid.v4(); +const goodName = "goodName"; +const badName = 'badName'; +const goodPath = "goodPath"; +const badPath = "badPath"; +exports.setUp = function(callback) { + if(fs.existsSync(TEST_FILE_NAME)){ + fs.rmSync(TEST_FILE_NAME); + } + callback(); +} +exports.tearDown = function(callback) { + if(fs.existsSync(TEST_FILE_NAME)){ + fs.rmSync(TEST_FILE_NAME); + } + callback(); +} +function addTestData(){ + var mapper = new guidMapper(TEST_FILE_NAME); + mapper.addEntry(goodGUID, goodPath, goodName); + mapper.writeFileSync(); +} +exports.operations = { + 'should be able to add to map': function(test) { + var mapper = new guidMapper(TEST_FILE_NAME); + mapper.addEntry(goodGUID, goodPath, goodName); + mapper.writeFileSync(); + mapper = new guidMapper(TEST_FILE_NAME); + const result = mapper.findEntryGuid(goodName, goodPath); + + test.ok(result === goodGUID) + test.done(); + }, + 'should not match only on name': function(test) { + addTestData(); + var mapper = new guidMapper(TEST_FILE_NAME); + + const result = mapper.findEntryGuid(goodName, badPath); + + test.ok(result === null) + test.done(); + }, + 'should not match only on path': function(test) { + addTestData(); + var mapper = new guidMapper(TEST_FILE_NAME); + + const result = mapper.findEntryGuid(badName, goodPath); + + test.ok(result === null) + test.done(); + }, + 'can remove': function(test) { + addTestData(); + var mapper = new guidMapper(TEST_FILE_NAME); + mapper.removeEntry(goodGUID); + var result = mapper.findEntryGuid(goodName, goodPath); + + test.ok(result === null); + mapper.writeFileSync(); + result = mapper.findEntryGuid(goodName, goodPath); + test.ok(result === null) + + test.done(); + } +} \ No newline at end of file diff --git a/test/pbxProject.js b/test/pbxProject.js index e846294a..860c4f48 100644 --- a/test/pbxProject.js +++ b/test/pbxProject.js @@ -433,3 +433,20 @@ exports['addToPbxFileReferenceSection'] = { } } + +exports['addPbxGroup'] = { + 'should not add the same group twice': function (test) { + var newProj = new pbx('test/parser/projects/group.pbxproj'); + newProj.parse(function (err, hash) { + this.hash.project.objects['PBXVariantGroup']={}; + var group1 = newProj.addPbxGroup(['test/somefile'], "TestGroup", "test/somepath", null, null); + var group2 = newProj.addPbxGroup(['test/somefile'], "TestGroup", "test/somepath", null, null); + test.equal(newProj.getPBXGroupByKey(group1.uuid), null); + test.equal(newProj.getPBXGroupByKey(group2.uuid).name, "TestGroup"); + test.equal(newProj.getPbxGroupTracker().getEntries().hasOwnProperty(group1.uuid), false); + test.equal(newProj.getPbxGroupTracker().getEntries().hasOwnProperty(group2.uuid), true); + + test.done(); + }); + } +} \ No newline at end of file diff --git a/test/removeBuildPhase.js b/test/removeBuildPhase.js new file mode 100644 index 00000000..bd35c241 --- /dev/null +++ b/test/removeBuildPhase.js @@ -0,0 +1,43 @@ +/** + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + 'License'); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +var fullProject = require('./fixtures/full-project') + fullProjectStr = JSON.stringify(fullProject), + pbx = require('../lib/pbxProject'), + proj = new pbx('.'); + +function cleanHash() { + return JSON.parse(fullProjectStr); +} + +exports.setUp = function (callback) { + proj.hash = cleanHash(); + callback(); +} + +exports.removeBuildPhase = { + + 'should remove a pbxBuildPhase': function (test) { + const comment = 'My build phase'; + var buildPhase = proj.addBuildPhase([], 'PBXSourcesBuildPhase', comment); + proj.removeBuildPhase(comment) + let phases = proj.hash.project.objects['PBXNativeTarget'][proj.getFirstTarget().uuid]['buildPhases']; + + test.ok(phases.map(p => p.comment).indexOf(comment) === -1); + test.done() + }, +}