/pbxplorer

Ruby classes for parsing and editing Xcode project (.pbxproj) files, includes interactive help mode

Primary LanguageRuby

pbxplorer

pbxplorer is a set of Ruby classes for parsing, editing, and saving Xcode project (.pbxproj) files. It can also be used to explore the contents of a project using the interactive Ruby shell.

Dependencies

pbxplorer has no Ruby dependencies outside of the standard library. It uses the OS X plutil command to parse the project file.

Overview

An Xcode project (.pbxproj) file is essentially a hash of objects keyed by UUID. The objects are hashes of properties keyed by name. The associated values can be strings, arrays, or hashes.

Each object hash contains an "isa" property that identifies its type. pbxplorer loads a project file hash and replaces each generic object hash with a specific subclass based on its type.

Class Hierarchy

PBXObject and PBXBuildPhase are abstract superclasses. All other subclasses of PBXObject correspond to object hashes in the project, based on their "isa" property.

PBXObject
    PBXBuildFile
    PBXBuildPhase
        PBXFrameworksBuildPhase
        PBXResourcesBuildPhase
        PBXShellScriptBuildPhase
        PBXSourcesBuildPhase
    PBXFileReference
    PBXGroup
        PBXVariantGroup
    PBXProject
    PBXNativeTarget
    XCBuildConfiguration
    XCConfigurationList
XCProjectFile

Reference Hierarchy

In the project file, objects are referenced from other objects by UUID. pbxplorer includes convenience methods to return the objects themselves rather than the UUIDs. Below are the method names and object types returned.

PBXProject
    build_configuration_list: XCConfigurationList
    main_group: PBXGroup
    targets: [PBXNativeTarget]
  
XCConfigurationList
    build_configurations: [XCBuildConfiguration]
  
PBXGroup
    children: [PBXGroup|PBXFileReference]
    subgroups: [PBXGroup]
    file_refs: [PBXFileReference]
    variant_groups: [PBXVariantGroup]

PBXNativeTarget
    build_configuration_list: XCBuildConfigurationList
    build_phases: [PBXBuildPhase]
    product_file_ref: PBXFileReference

PBXBuildPhase
    build_files: [PBXBuildFile]

PBXBuildFile
    file_ref: PBXFileReference

Interactive Use

To get started:

$ irb
>> require 'pbxplorer'
=> true
>> XCProjectFile.help
project_file = XCProjectFile.new '/path/to/project.pbxproj'
=> XCProjectFile

The help displays a suggested statement followed by the type of the assigned result. Enter the statement in the shell, editing as necessary:

>> project_file = XCProjectFile.new 'project.pbxproj'
=> {
  rootObject = "4D0B81831657473000DEF560"
  objects = < 63 objects >
}

ThisXCProjectFileinstance contains a reference to the root object (aPBXProjectinstance), and the collection of all objects.

Get help from the project_file object:

>> project_file.help
project = project_file.project
=> PBXProject

Enter the statement:

>> project = project_file.project
=> 4D0B81831657473000DEF560 = {
  attributes = {"CLASSPREFIX"=>"Example", "LastUpgradeCheck"=>"0450", "ORGANIZATIONNAME"=>"Example"}
  projectRoot = ""
  hasScannedForEncodings = "0"
  mainGroup = "4D0B81811657473000DEF560"
  buildConfigurationList = "4D0B81861657473000DEF560"
  compatibilityVersion = "Xcode 3.2"
  productRefGroup = "4D0B818D1657473000DEF560"
  projectDirPath = ""
  knownRegions = ["en"]
  targets = ["4D0B818B1657473000DEF560", "4D0B81AC1657473100DEF560"]
  developmentRegion = "English"
  isa = "PBXProject"
}

ThisPBXProjectinstance contains an array of references to targets (PBXNativeTargetinstances).

Get help from the project object:

>> project.help
target = project.targets.first
=> PBXNativeTarget
list = project.build_configuration_list
=> XCConfigurationList
group = project.main_group
=> PBXGroup

This time the help suggests three objects we can retrieve next; the firstPBXNativeTargetinstance, the soleXCConfigurationListinstance, and the mainPBXGroupinstance.

Enter the target statement:

>> target = project.targets.first
=> 4D0B818B1657473000DEF560 = {
  productName = "Example"
  productReference = "4D0B818C1657473000DEF560"
  name = "Example"
  dependencies = []
  buildConfigurationList = "4D0B81BF1657473100DEF560"
  productType = "com.apple.product-type.application"
  buildPhases = ["4D0B81881657473000DEF560", "4D0B81891657473000DEF560", "4D0B818A1657473000DEF560"]
  isa = "PBXNativeTarget"
  buildRules = []
}

You can continue exploring the object graph in this manner, down to PBXFileReferenceobjects, which contain no references to other objects.

Example: removing a source file

file_ref = project_file.objects_of_class(PBXFileReference, { "path" => "ExampleTests.m" }).first
build_phases = project_file.objects_of_class PBXBuildPhase
build_files = project_file.objects_of_class PBXBuildFile, { "fileRef" => file_ref.uuid }
build_file_uuids = build_files.map { |obj| obj.uuid }
groups = project_file.objects_of_class PBXGroup

build_phases.each { |phase| phase["files"] -= build_file_uuids }
build_files.each { |obj| project_file.remove_object obj }
groups.each { |group| group["children"].delete file_ref }
project_file.remove_object file_ref

project_file.save

Example: adding a source file

file_ref = project_file.new_object PBXFileReference, { "path" => "Example.c", "sourceTree" => "<group>",  "lastKnownFileType" => "sourcecode.c.c" }
build_file = project_file.new_object PBXBuildFile, { "fileRef" => file_ref.uuid }

build_phases = project_file.objects_of_class PBXSourcesBuildPhase
build_phases.each { |phase| phase["files"] << build_file.uuid }

project_file.save

LICENSE

MIT license.