Parser utility for xcodeproj/project.pbxproj files that allows editing and writing back Xcode project files
Build phase management for controlling compilation, resource copying, framework linking, and custom script execution with support for all Xcode build phase types.
Create custom build phases for advanced build workflows.
/**
* Add custom build phase to target
* Creates and configures build phase with specified files and options
* @param {string[]} filePathsArray - Files to include in build phase
* @param {string} buildPhaseType - Build phase type (PBXSourcesBuildPhase, etc.)
* @param {string} comment - Build phase name/comment displayed in Xcode
* @param {string} target - Target UUID (optional, defaults to first target)
* @param {object|string} options - Build phase options or folder type for copy phases
* @param {string} subfolderPath - Subfolder path for copy phases
* @returns {object} Build phase object with UUID and configuration
*/
addBuildPhase(filePathsArray, buildPhaseType, comment, target, options, subfolderPath);Usage Examples:
// Add custom sources build phase
const sourcePhase = proj.addBuildPhase(
['src/CustomCode.m', 'src/Helper.m'],
'PBXSourcesBuildPhase',
'Custom Sources'
);
// Add custom resources build phase
const resourcePhase = proj.addBuildPhase(
['assets/config.plist', 'assets/data.json'],
'PBXResourcesBuildPhase',
'Configuration Resources'
);
// Add copy files build phase for frameworks
const copyPhase = proj.addBuildPhase(
['Frameworks/Custom.framework'],
'PBXCopyFilesBuildPhase',
'Embed Custom Frameworks',
null, // Use default target
'frameworks', // Destination folder type
'$(BUILT_PRODUCTS_DIR)'
);
// Add shell script build phase
const scriptPhase = proj.addBuildPhase(
[], // No files for script phases
'PBXShellScriptBuildPhase',
'Run SwiftLint',
null,
{
shellPath: '/bin/sh',
shellScript: 'if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo "warning: SwiftLint not installed"\nfi',
inputPaths: ['$(SRCROOT)/.swiftlint.yml'],
outputPaths: []
}
);Access existing build phases and their objects.
/**
* Get sources build phase object for target
* @param {string} target - Target UUID
* @returns {object} Sources build phase object
*/
pbxSourcesBuildPhaseObj(target);
/**
* Get resources build phase object for target
* @param {string} target - Target UUID
* @returns {object} Resources build phase object
*/
pbxResourcesBuildPhaseObj(target);
/**
* Get frameworks build phase object for target
* @param {string} target - Target UUID
* @returns {object} Frameworks build phase object
*/
pbxFrameworksBuildPhaseObj(target);
/**
* Get embed frameworks build phase object for target
* @param {string} target - Target UUID
* @returns {object} Embed frameworks build phase object
*/
pbxEmbedFrameworksBuildPhaseObj(target);
/**
* Get copy files build phase object for target
* @param {string} target - Target UUID
* @returns {object} Copy files build phase object
*/
pbxCopyfilesBuildPhaseObj(target);
/**
* Find build phase by group and target
* @param {string} group - Build phase group name
* @param {string} target - Target UUID
* @returns {string} Build phase comment key
*/
buildPhase(group, target);
/**
* Get build phase object by name, group, and target
* @param {string} name - Build phase type name
* @param {string} group - Build phase group
* @param {string} target - Target UUID
* @returns {object} Build phase object
*/
buildPhaseObject(name, group, target);Usage Examples:
// Get default build phases
const mainTarget = proj.getFirstTarget().uuid;
const sourcesPhase = proj.pbxSourcesBuildPhaseObj(mainTarget);
console.log('Sources files:', sourcesPhase.files.length);
const resourcesPhase = proj.pbxResourcesBuildPhaseObj(mainTarget);
console.log('Resource files:', resourcesPhase.files.length);
const frameworksPhase = proj.pbxFrameworksBuildPhaseObj(mainTarget);
console.log('Linked frameworks:', frameworksPhase.files.length);
// Find custom build phase
const customPhase = proj.buildPhaseObject('PBXShellScriptBuildPhase', 'Run Script', mainTarget);
if (customPhase) {
console.log('Script phase found:', customPhase.name);
}Add and remove files from specific build phases.
// Internal methods typically called by higher-level file management functions
/**
* Add file to sources build phase
* @param {object} file - File object to add
*/
addToPbxSourcesBuildPhase(file);
/**
* Remove file from sources build phase
* @param {object} file - File object to remove
*/
removeFromPbxSourcesBuildPhase(file);
/**
* Add file to resources build phase
* @param {object} file - File object to add
*/
addToPbxResourcesBuildPhase(file);
/**
* Remove file from resources build phase
* @param {object} file - File object to remove
*/
removeFromPbxResourcesBuildPhase(file);
/**
* Add file to frameworks build phase
* @param {object} file - File object to add
*/
addToPbxFrameworksBuildPhase(file);
/**
* Remove file from frameworks build phase
* @param {object} file - File object to remove
*/
removeFromPbxFrameworksBuildPhase(file);
/**
* Add file to embed frameworks build phase
* @param {object} file - File object to add
*/
addToPbxEmbedFrameworksBuildPhase(file);
/**
* Remove file from embed frameworks build phase
* @param {object} file - File object to remove
*/
removeFromPbxEmbedFrameworksBuildPhase(file);
/**
* Add file to copy files build phase
* @param {object} file - File object to add
*/
addToPbxCopyfilesBuildPhase(file);
/**
* Remove file from copy files build phase
* @param {object} file - File object to remove
*/
removeFromPbxCopyfilesBuildPhase(file);Usage Examples:
// These methods are typically called automatically by addSourceFile, addFramework, etc.
// but can be used directly for fine-grained control
// Add file to specific build phases manually
const sourceFile = { uuid: 'FILE_UUID', fileRef: 'FILE_REF_UUID', basename: 'MyFile.m' };
proj.addToPbxBuildFileSection(sourceFile); // Must add to build file section first
proj.addToPbxSourcesBuildPhase(sourceFile);
// Remove from build phases
proj.removeFromPbxSourcesBuildPhase(sourceFile);
proj.removeFromPbxBuildFileSection(sourceFile);/**
* Supported build phase types
*/
const BUILD_PHASE_TYPES = {
'PBXSourcesBuildPhase': 'Compile Sources',
'PBXResourcesBuildPhase': 'Copy Bundle Resources',
'PBXFrameworksBuildPhase': 'Link Binary With Libraries',
'PBXCopyFilesBuildPhase': 'Copy Files',
'PBXShellScriptBuildPhase': 'Run Script'
};
/**
* Copy files destination types
*/
const COPY_DESTINATIONS = {
'absolute_path': 0,
'wrapper': 1,
'executables': 6,
'resources': 7,
'frameworks': 10,
'shared_frameworks': 11,
'shared_support': 12,
'plugins': 13,
'products_directory': 16,
'java_resources': 15,
'xpc_services': 0
};
/**
* Target type to destination mapping for copy phases
*/
const DESTINATION_BY_TARGET_TYPE = {
'application': 'wrapper',
'app_extension': 'plugins',
'bundle': 'wrapper',
'command_line_tool': 'wrapper',
'dynamic_library': 'products_directory',
'framework': 'shared_frameworks',
'frameworks': 'frameworks',
'static_library': 'products_directory',
'unit_test_bundle': 'wrapper',
'watch_app': 'wrapper',
'watch2_app': 'products_directory',
'watch_extension': 'plugins',
'watch2_extension': 'plugins'
};/**
* Options for shell script build phases
*/
interface ShellScriptOptions {
/** Shell path (e.g., '/bin/sh', '/usr/bin/python3') */
shellPath: string;
/** Shell script content */
shellScript: string;
/** Input file paths for dependency tracking */
inputPaths?: string[];
/** Output file paths for dependency tracking */
outputPaths?: string[];
/** Show environment variables in build log */
showEnvVarsInLog?: boolean;
/** Run script only when installing */
runOnlyForDeploymentPostprocessing?: boolean;
}/**
* Options for copy files build phases
*/
interface CopyFilesOptions {
/** Destination folder type */
folderType: string;
/** Subfolder path within destination */
subfolderPath?: string;
/** Only copy when installing */
runOnlyForDeploymentPostprocessing?: boolean;
}/**
* Base build phase structure
*/
interface BuildPhase {
/** Build phase type */
isa: string;
/** Build action mask */
buildActionMask: number;
/** Files in build phase */
files: BuildFile[];
/** Run only for deployment post-processing */
runOnlyForDeploymentPostprocessing: number;
}
interface PBXShellScriptBuildPhase extends BuildPhase {
/** Script name */
name: string;
/** Input file paths */
inputPaths: string[];
/** Output file paths */
outputPaths: string[];
/** Shell path */
shellPath: string;
/** Shell script content */
shellScript: string;
}
interface PBXCopyFilesBuildPhase extends BuildPhase {
/** Copy phase name */
name: string;
/** Destination path */
dstPath: string;
/** Destination subfolder specification */
dstSubfolderSpec: number;
}
interface BuildFile {
/** Build file UUID */
value: string;
/** Build file comment */
comment: string;
}SwiftLint Integration:
const swiftLintPhase = proj.addBuildPhase(
[],
'PBXShellScriptBuildPhase',
'SwiftLint',
null,
{
shellPath: '/bin/sh',
shellScript: `if which swiftlint >/dev/null; then
swiftlint
else
echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
fi`,
inputPaths: [],
outputPaths: []
}
);Code Generation Script:
const codeGenPhase = proj.addBuildPhase(
[],
'PBXShellScriptBuildPhase',
'Generate Code',
null,
{
shellPath: '/usr/bin/python3',
shellScript: `import os
import sys
# Add your code generation logic here
print("Generating code...")`,
inputPaths: ['$(SRCROOT)/Templates/'],
outputPaths: ['$(DERIVED_FILE_DIR)/Generated.swift']
}
);Framework Embedding:
// Embed custom frameworks for app extensions
const embedPhase = proj.addBuildPhase(
['CustomFramework.framework', 'ThirdPartySDK.framework'],
'PBXCopyFilesBuildPhase',
'Embed Frameworks',
extensionTarget.uuid,
'frameworks',
''
);Resource Processing:
// Custom resource processing
const processResourcesPhase = proj.addBuildPhase(
[],
'PBXShellScriptBuildPhase',
'Process Resources',
null,
{
shellPath: '/bin/sh',
shellScript: `# Process and optimize images
if which imageoptim >/dev/null; then
find "$SRCROOT" -name "*.png" -exec imageoptim {} \\;
fi
# Validate plist files
find "$SRCROOT" -name "*.plist" -exec plutil -lint {} \\;`,
inputPaths: ['$(SRCROOT)/Resources/'],
outputPaths: []
}
);Multi-Target Build Phase:
// Add the same script phase to multiple targets
const targets = [
proj.getFirstTarget(),
proj.pbxTargetByName('MyExtension'),
proj.pbxTargetByName('MyFramework')
];
targets.forEach(target => {
if (target) {
proj.addBuildPhase(
[],
'PBXShellScriptBuildPhase',
'Common Build Script',
target.uuid || proj.findTargetKey(target.name),
{
shellPath: '/bin/sh',
shellScript: 'echo "Building target: ${TARGET_NAME}"',
inputPaths: [],
outputPaths: []
}
);
}
});Conditional Build Phases:
// Build phase that runs only in specific configurations
const debugOnlyPhase = proj.addBuildPhase(
[],
'PBXShellScriptBuildPhase',
'Debug Only Script',
null,
{
shellPath: '/bin/sh',
shellScript: `if [ "$CONFIGURATION" = "Debug" ]; then
echo "Running debug-only operations..."
# Debug-specific operations here
fi`,
inputPaths: [],
outputPaths: []
}
);Script Phase Ordering:
Dependency Tracking:
inputPaths and outputPaths for scripts that generate files$(SRCROOT), $(BUILT_PRODUCTS_DIR), and other Xcode variables for portabilityPerformance Optimization:
Install with Tessl CLI
npx tessl i tessl/npm-xcode