Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 96 additions & 6 deletions lib/pbxProject.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,56 @@ const parser = require('./parser/pbxproj');
const plist = require('simple-plist');
const COMMENT_KEY = /_comment$/;

// MARK: Start of Typings

/**
* A list of known build phase types.
* @typedef {'PBXCopyFilesBuildPhase' |
* 'PBXResourcesBuildPhase' |
* 'PBXFrameworksBuildPhase' |
* 'PBXHeadersBuildPhase' |
* 'PBXShellScriptBuildPhase' |
* 'PBXSourcesBuildPhase'
* } BuildPhaseType
*/

/**
* The result object returned when adding a build phase.
* @typedef {object} AddBuildPhaseResults
* @property {string} uuid Build Phase UUID
* @property {object} buildPhase Build Phase object
*/

/**
* A list of valid target types used for copy files build configurations.
* @typedef {'application'
* | 'app_extension'
* | 'bundle'
* | 'command_line_tool'
* | 'dynamic_library'
* | 'framework'
* | 'frameworks'
* | 'static_library'
* | 'unit_test_bundle'
* | 'watch_app'
* | 'watch2_app'
* | 'watch_extension'
* | 'watch2_extension'
* } CopyFilesTargetType
*/

/**
* Options used when adding a PBXShellScriptBuildPhase.
* @typedef {object} ShellScriptOptions
* @property {string[]} inputPaths input paths
* @property {string[]} outputPaths output Paths
* @property {string} shellPath shell paths
* @property {0 | 1} runOnlyForDeploymentPostprocessing run script for install builds only
* @property {1} [alwaysOutOfDate] disables dependency analysis when set to true
*/

// MARK: End of Typings

function PBXProject (filename) {
if (!(this instanceof PBXProject)) { return new PBXProject(filename); }

Expand Down Expand Up @@ -851,6 +901,25 @@ PBXProject.prototype.addTargetDependency = function (target, dependencyTargets)
return { uuid: target, target: nativeTargets[target] };
};

/**
* Add files to specific build phase by defining the build phase type.
*
* @example
* // Adding a file to Resource Build Phase.
* myProject.addBuildPhase(
* ['MyAssets.xcassets'],
* 'PBXResourcesBuildPhase',
* 'Resources'
* );
*
* @param {string[]} filePathsArray collection of file paths
* @param {BuildPhaseType} buildPhaseType target build phase
* @param {string} comment name of the build phase. (e.g. "Copy My Files")
* @param {string} target UUID of the PBXNativeTarget (defaults to first target)
* @param {CopyFilesTargetType|ShellScriptOptions} optionsOrFolderType either sell script options or copy files target type
* @param {string} subfolderPath copy files subfolder path (default: "")
* @returns {AddBuildPhaseResults} object containing the build phase & uuid
*/
PBXProject.prototype.addBuildPhase = function (filePathsArray, buildPhaseType, comment, target, optionsOrFolderType, subfolderPath) {
let buildPhaseSection;
const fileReferenceSection = this.pbxFileReferenceSection();
Expand Down Expand Up @@ -896,18 +965,33 @@ PBXProject.prototype.addBuildPhase = function (filePathsArray, buildPhaseType, c
const buildFile = buildFileSection[buildFileKey];
const fileReference = fileReferenceSection[buildFile.fileRef];

if (!fileReference) continue;
// If a file reference does not define either the name or path property,
// it will not be included to the filePathToBuildFile lookup table.
// If the file reference defines both properies, the path property will
// take priority.
const fileIdentifier = fileReference?.path || fileReference?.name;

const pbxFileObj = new PBXFile(fileReference.path);
if (!fileIdentifier) {
continue;
}

filePathToBuildFile[fileReference.path] = { uuid: buildFileKey, basename: pbxFileObj.basename, group: pbxFileObj.group };
const pbxFileObj = new PBXFile(fileIdentifier);

// Creates a lookup table of existing PBXBuildFile entries keyed by fileIdentifier.
// Used later when adding files to the build phase to reuse the existing PBXBuildFile
// instead of generating new PBXBuildFile.
filePathToBuildFile[fileIdentifier] = {
uuid: buildFileKey,
basename: pbxFileObj.basename,
group: pbxFileObj.group
};
}

for (let index = 0; index < filePathsArray.length; index++) {
const filePath = filePathsArray[index];
const filePathQuoted = '"' + filePath + '"';
const file = new PBXFile(filePath);

// Checks for an existing PBXBuildFile entry from the filePathToBuildFile lookup table.
if (filePathToBuildFile[filePath]) {
buildPhase.files.push(pbxBuildPhaseObj(filePathToBuildFile[filePath]));
continue;
Expand All @@ -916,10 +1000,16 @@ PBXProject.prototype.addBuildPhase = function (filePathsArray, buildPhaseType, c
continue;
}

// If no PBXBuildFile entry is found in the lookup table, a new entry is created
// with a new UUID. It is then:
// 1. Added to the PBXFileReferenceSection
// 2. Added to the PBXBuildFileSection
// 3. Pushed onto the build phase file list.
const file = new PBXFile(filePath);
file.uuid = this.generateUuid();
file.fileRef = this.generateUuid();
this.addToPbxFileReferenceSection(file); // PBXFileReference
this.addToPbxBuildFileSection(file); // PBXBuildFile
this.addToPbxFileReferenceSection(file);
this.addToPbxBuildFileSection(file);
buildPhase.files.push(pbxBuildPhaseObj(file));
}

Expand Down
Loading