Commit be78f0dc authored by Nolan Waite's avatar Nolan Waite

Switch to xcodeproj gem

parent 87e1a0bf
...@@ -31,8 +31,7 @@ Gem::Specification.new do |s| ...@@ -31,8 +31,7 @@ Gem::Specification.new do |s|
" $ sudo macgem install rubygems-compile\n" \ " $ sudo macgem install rubygems-compile\n" \
" $ sudo macgem compile cocoapods\n\n" " $ sudo macgem compile cocoapods\n\n"
s.add_runtime_dependency 'activesupport', '~> 3.1.1' s.add_runtime_dependency 'xcodeproj', '~> 0.0.1'
s.add_runtime_dependency 'i18n', '~> 0.6.0' # only needed for ActiveSupport :-/
## Make sure you can build the gem on older versions of RubyGems too: ## Make sure you can build the gem on older versions of RubyGems too:
s.rubygems_version = "1.6.2" s.rubygems_version = "1.6.2"
......
require 'rubygems'
require 'xcodeproj'
module Pod module Pod
VERSION = '0.2.0' VERSION = '0.2.0'
...@@ -19,13 +22,6 @@ module Pod ...@@ -19,13 +22,6 @@ module Pod
autoload :Specification, 'cocoapods/specification' autoload :Specification, 'cocoapods/specification'
autoload :Version, 'cocoapods/version' autoload :Version, 'cocoapods/version'
module Xcode
autoload :Config, 'cocoapods/xcode/config'
autoload :CopyResourcesScript, 'cocoapods/xcode/copy_resources_script'
autoload :Project, 'cocoapods/xcode/project'
autoload :Workspace, 'cocoapods/xcode/workspace'
end
autoload :Pathname, 'pathname' autoload :Pathname, 'pathname'
end end
...@@ -35,3 +31,25 @@ class Pathname ...@@ -35,3 +31,25 @@ class Pathname
end end
end end
# Sorry to dump these here...
class Xcode::Project
# Shortcut access to the `Pods' PBXGroup.
def pods
groups.find { |g| g.name == 'Pods' } || groups.new({ 'name' => 'Pods' })
end
# Adds a group as child to the `Pods' group.
def add_pod_group(name)
pods.groups.new('name' => name)
end
class PBXCopyFilesBuildPhase
def self.new_pod_dir(project, pod_name, path)
new(project, nil, {
"dstPath" => "$(PUBLIC_HEADERS_FOLDER_PATH)/#{path}",
"name" => "Copy #{pod_name} Public Headers",
})
end
end
end
...@@ -14,6 +14,36 @@ module Pod ...@@ -14,6 +14,36 @@ module Pod
end end
end end
class CopyResourcesScript
CONTENT = <<EOS
#!/bin/sh
install_resource()
{
echo "cp -R ${SRCROOT}/Pods/$1 ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
cp -R ${SRCROOT}/Pods/$1 ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}
}
EOS
attr_reader :resources
# A list of files relative to the project pods root.
def initialize(resources)
@resources = resources
end
def save_as(pathname)
pathname.open('w') do |script|
script.puts CONTENT
@resources.each do |resource|
script.puts "install_resource '#{resource}'"
end
end
# TODO use File api
system("chmod +x '#{pathname}'")
end
end
class Target class Target
include Config::Mixin include Config::Mixin
include Shared include Shared
...@@ -40,7 +70,7 @@ module Pod ...@@ -40,7 +70,7 @@ module Pod
end end
def copy_resources_script def copy_resources_script
@copy_resources_script ||= Xcode::CopyResourcesScript.new(build_specifications.map do |spec| @copy_resources_script ||= CopyResourcesScript.new(build_specifications.map do |spec|
spec.expanded_resources spec.expanded_resources
end.flatten) end.flatten)
end end
......
...@@ -38,7 +38,8 @@ module Pod ...@@ -38,7 +38,8 @@ module Pod
'INSTALL_PATH' => "$(BUILT_PRODUCTS_DIR)", 'INSTALL_PATH' => "$(BUILT_PRODUCTS_DIR)",
'GCC_WARN_ABOUT_MISSING_PROTOTYPES' => 'YES', 'GCC_WARN_ABOUT_MISSING_PROTOTYPES' => 'YES',
'GCC_WARN_ABOUT_RETURN_TYPE' => 'YES', 'GCC_WARN_ABOUT_RETURN_TYPE' => 'YES',
'GCC_WARN_UNUSED_VARIABLE' => 'YES' 'GCC_WARN_UNUSED_VARIABLE' => 'YES',
'OTHER_LDFLAGS' => ''
}, },
:debug => { :debug => {
'GCC_DYNAMIC_NO_PIC' => 'NO', 'GCC_DYNAMIC_NO_PIC' => 'NO',
......
module Pod
module Xcode
class Config
def initialize(xcconfig = {})
@attributes = {}
merge!(xcconfig)
end
def to_hash
@attributes
end
def merge!(xcconfig)
xcconfig.to_hash.each do |key, value|
if existing_value = @attributes[key]
@attributes[key] = "#{existing_value} #{value}"
else
@attributes[key] = value
end
end
end
alias_method :<<, :merge!
def to_s
@attributes.map { |key, value| "#{key} = #{value}" }.join("\n")
end
def save_as(pathname)
pathname.open('w') { |file| file << to_s }
end
end
end
end
module Pod
module Xcode
class CopyResourcesScript
CONTENT = <<EOS
#!/bin/sh
install_resource()
{
echo "cp -R ${SRCROOT}/Pods/$1 ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
cp -R ${SRCROOT}/Pods/$1 ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}
}
EOS
attr_reader :resources
# A list of files relative to the project pods root.
def initialize(resources)
@resources = resources
end
def save_as(pathname)
pathname.open('w') do |script|
script.puts CONTENT
@resources.each do |resource|
script.puts "install_resource '#{resource}'"
end
end
# TODO use File api
system("chmod +x '#{pathname}'")
end
end
end
end
framework 'Foundation'
require 'fileutils'
require 'active_support/inflector'
require 'active_support/core_ext/string/inflections'
module Pod
module Xcode
class Project
class PBXObject
class AssociationReflection
def initialize(name, options)
@name, @options = name.to_s, options
end
attr_reader :name, :options
def klass
@options[:class] ||= begin
name = "PBX#{@name.classify}"
name = "XC#{@name.classify}" unless Project.const_defined?(name)
Project.const_get(name)
end
end
def inverse
klass.reflection(@options[:inverse_of])
end
def inverse?
!!@options[:inverse_of]
end
def singular_name
@options[:singular_name] || @name.singularize
end
def singular_getter
singular_name
end
def singular_setter
"#{singular_name}="
end
def plural_name
@name.pluralize
end
def plural_getter
plural_name
end
def plural_setter
"#{plural_name}="
end
def uuid_attribute
@options[:uuid] || @name
end
def uuid_method_name
(@options[:uuid] || @options[:uuids] || "#{singular_name}Reference").to_s.singularize
end
def uuid_getter
uuid_method_name
end
def uuid_setter
"#{uuid_method_name}="
end
def uuids_method_name
uuid_method_name.pluralize
end
def uuids_getter
uuids_method_name
end
def uuids_setter
"#{uuids_method_name}="
end
end
def self.reflections
@reflections ||= []
end
def self.create_reflection(name, options)
(reflections << AssociationReflection.new(name, options)).last
end
def self.reflection(name)
reflections.find { |r| r.name.to_s == name.to_s }
end
def self.attribute(attribute_name, accessor_name = nil)
attribute_name = attribute_name.to_s
name = (accessor_name || attribute_name).to_s
define_method(name) { @attributes[attribute_name] }
define_method("#{name}=") { |value| @attributes[attribute_name] = value }
end
def self.attributes(*names)
names.each { |name| attribute(name) }
end
def self.has_many(plural_attr_name, options = {}, &block)
reflection = create_reflection(plural_attr_name, options)
if reflection.inverse?
define_method(reflection.name) do
scoped = @project.objects.select_by_class(reflection.klass).select do |object|
object.send(reflection.inverse.uuid_getter) == self.uuid
end
PBXObjectList.new(reflection.klass, @project, scoped) do |object|
object.send(reflection.inverse.uuid_setter, self.uuid)
end
end
else
attribute(reflection.name, reflection.uuids_getter)
define_method(reflection.name) do
uuids = send(reflection.uuids_getter)
if block
# Evaluate the block, which was specified at the class level, in
# the instance’s context.
list_by_class(uuids, reflection.klass) do |object|
instance_exec(object, &block)
end
else
list_by_class(uuids, reflection.klass)
end
end
define_method(reflection.plural_setter) do |objects|
send(reflection.uuids_setter, objects.map(&:uuid))
end
end
end
def self.has_one(singular_attr_name, options = {})
reflection = create_reflection(singular_attr_name, options)
if reflection.inverse?
define_method(reflection.name) do
# Loop over all objects of the class and find the one that includes
# this object in the specified uuid list.
@project.objects.select_by_class(reflection.klass).find do |object|
object.send(reflection.inverse.uuids_getter).include?(self.uuid)
end
end
define_method(reflection.singular_setter) do |object|
# Remove this object from the uuid list of the target
# that this object was associated to.
if previous = send(reflection.name)
previous.send(reflection.inverse.uuids_getter).delete(self.uuid)
end
# Now assign this object to the new object
object.send(reflection.inverse.uuids_getter) << self.uuid if object
end
else
attribute(reflection.uuid_attribute, reflection.uuid_getter)
define_method(reflection.name) do
@project.objects[send(reflection.uuid_getter)]
end
define_method(reflection.singular_setter) do |object|
send(reflection.uuid_setter, object.uuid)
end
end
end
def self.isa
@isa ||= name.split('::').last
end
attr_reader :uuid, :attributes
attributes :isa, :name
def initialize(project, uuid, attributes)
@project, @attributes = project, attributes
unless uuid
# Add new objects to the main hash with a unique UUID
begin; uuid = generate_uuid; end while @project.objects_hash.has_key?(uuid)
@project.objects_hash[uuid] = @attributes
end
@uuid = uuid
self.isa ||= self.class.isa
end
def ==(other)
other.is_a?(PBXObject) && self.uuid == other.uuid
end
def inspect
"#<#{isa} UUID: `#{uuid}', name: `#{name}'>"
end
private
def generate_uuid
_uuid = CFUUIDCreate(nil)
uuid = CFUUIDCreateString(nil, _uuid)
CFRelease(_uuid)
CFMakeCollectable(uuid)
# Xcode's version is actually shorter, not worrying about collisions too much right now.
uuid.gsub('-', '')[0..23]
end
def list_by_class(uuids, klass, scoped = nil, &block)
unless scoped
scoped = uuids.map { |uuid| @project.objects[uuid] }.select { |o| o.is_a?(klass) }
end
if block
PBXObjectList.new(klass, @project, scoped, &block)
else
PBXObjectList.new(klass, @project, scoped) do |object|
# Add the uuid of a newly created object to the uuids list
uuids << object.uuid
end
end
end
end
# Missing constants that begin with either `PBX' or `XC' are assumed to be
# valid classes in a Xcode project. A new PBXObject subclass is created
# for the constant and returned.
def self.const_missing(name)
if name =~ /^(PBX|XC)/
klass = Class.new(PBXObject)
const_set(name, klass)
klass
else
super
end
end
class PBXFileReference < PBXObject
attributes :path, :sourceTree, :explicitFileType, :lastKnownFileType, :includeInIndex
has_many :buildFiles, :inverse_of => :file
has_one :group, :inverse_of => :children
def self.new_static_library(project, productName)
new(project, nil, {
"path" => "lib#{productName}.a",
"includeInIndex" => "0", # no idea what this is
"sourceTree" => "BUILT_PRODUCTS_DIR",
})
end
def initialize(project, uuid, attributes)
is_new = uuid.nil?
super
self.path = path if path # sets default name
self.sourceTree ||= 'SOURCE_ROOT'
if is_new
@project.main_group.children << self
end
set_default_file_type!
end
alias_method :_path=, :path=
def path=(path)
self._path = path
self.name ||= pathname.basename.to_s
path
end
def pathname
Pathname.new(path)
end
def set_default_file_type!
return if explicitFileType || lastKnownFileType
case path
when /\.a$/
self.explicitFileType = 'archive.ar'
when /\.framework$/
self.lastKnownFileType = 'wrapper.framework'
when /\.xcconfig$/
self.lastKnownFileType = 'text.xcconfig'
end
end
end
class PBXGroup < PBXObject
attributes :sourceTree
has_many :children, :class => PBXFileReference do |object|
if object.is_a?(Pod::Xcode::Project::PBXFileReference)
# Associating the file to this group through the inverse
# association will also remove it from the group it was in.
object.group = self
else
# TODO What objects can actually be in a group and don't they
# all need the above treatment.
childReferences << object.uuid
end
end
def initialize(*)
super
self.sourceTree ||= '<group>'
self.childReferences ||= []
end
def files
list_by_class(childReferences, Pod::Xcode::Project::PBXFileReference) do |file|
file.group = self
end
end
def source_files
files = self.files.reject { |file| file.buildFiles.empty? }
list_by_class(childReferences, Pod::Xcode::Project::PBXFileReference, files) do |file|
file.group = self
end
end
def groups
list_by_class(childReferences, Pod::Xcode::Project::PBXGroup)
end
def <<(child)
children << child
end
end
class PBXBuildFile < PBXObject
attributes :settings
has_one :file, :uuid => :fileRef
end
class PBXBuildPhase < PBXObject
# TODO rename this to buildFiles and add a files :through => :buildFiles shortcut
has_many :files, :class => PBXBuildFile
attributes :buildActionMask, :runOnlyForDeploymentPostprocessing
def initialize(*)
super
self.fileReferences ||= []
# These are always the same, no idea what they are.
self.buildActionMask ||= "2147483647"
self.runOnlyForDeploymentPostprocessing ||= "0"
end
end
class PBXCopyFilesBuildPhase < PBXBuildPhase
attributes :dstPath, :dstSubfolderSpec
def self.new_pod_dir(project, pod_name, path)
new(project, nil, {
"dstPath" => "$(PRODUCT_NAME)/#{path}",
"name" => "Copy #{pod_name} Public Headers",
})
end
def initialize(*)
super
self.dstSubfolderSpec ||= "16"
end
end
class PBXSourcesBuildPhase < PBXBuildPhase; end
class PBXFrameworksBuildPhase < PBXBuildPhase; end
class PBXShellScriptBuildPhase < PBXBuildPhase
attribute :shellScript
end
class PBXNativeTarget < PBXObject
STATIC_LIBRARY = 'com.apple.product-type.library.static'
attributes :productName, :productType
has_many :buildPhases
has_many :dependencies # TODO :class => ?
has_many :buildRules # TODO :class => ?
has_one :buildConfigurationList
has_one :product, :uuid => :productReference
def self.new_static_library(project, productName)
# TODO should probably switch the uuid and attributes argument
target = new(project, nil, 'productType' => STATIC_LIBRARY, 'productName' => productName)
target.product = project.files.new_static_library(productName)
target.buildPhases.add(PBXSourcesBuildPhase)
buildPhase = target.buildPhases.add(PBXFrameworksBuildPhase)
project.groups.find { |g| g.name == 'Frameworks' }.files.each do |framework|
buildPhase.files << framework.buildFiles.new
end
target.buildPhases.add(PBXCopyFilesBuildPhase, 'dstPath' => '$(PRODUCT_NAME)')
target
end
# You need to specify a product. For a static library you can use
# PBXFileReference.new_static_library.
def initialize(project, *)
super
self.name ||= productName
self.buildRuleReferences ||= []
self.dependencyReferences ||= []
self.buildPhaseReferences ||= []
unless buildConfigurationList
self.buildConfigurationList = project.objects.add(XCConfigurationList)
# TODO or should this happen in buildConfigurationList?
buildConfigurationList.buildConfigurations.new('name' => 'Debug')
buildConfigurationList.buildConfigurations.new('name' => 'Release')
end
end
alias_method :_product=, :product=
def product=(product)
self._product = product
product.group = @project.products
end
def buildConfigurations
buildConfigurationList.buildConfigurations
end
def source_build_phases
buildPhases.select_by_class(PBXSourcesBuildPhase)
end
def copy_files_build_phases
buildPhases.select_by_class(PBXCopyFilesBuildPhase)
end
def frameworks_build_phases
buildPhases.select_by_class(PBXFrameworksBuildPhase)
end
# Finds an existing file reference or creates a new one.
def add_source_file(path, copy_header_phase = nil, compiler_flags = nil)
file = @project.files.find { |file| file.path == path.to_s } || @project.files.new('path' => path.to_s)
buildFile = file.buildFiles.new
if path.extname == '.h'
buildFile.settings = { 'ATTRIBUTES' => ["Public"] }
# Working around a bug in Xcode 4.2 betas, remove this once the Xcode bug is fixed:
# https://github.com/alloy/cocoapods/issues/13
#phase = copy_header_phase || headers_build_phases.first
phase = copy_header_phase || copy_files_build_phases.first
phase.files << buildFile
else
buildFile.settings = { 'COMPILER_FLAGS' => compiler_flags } if compiler_flags
source_build_phases.first.files << buildFile
end
file
end
end
class XCBuildConfiguration < PBXObject
attribute :buildSettings
has_one :baseConfiguration, :uuid => :baseConfigurationReference
def initialize(*)
super
# TODO These are from an iOS static library, need to check if it works for any product type
self.buildSettings = {
'DSTROOT' => '/tmp/Pods.dst',
'GCC_PRECOMPILE_PREFIX_HEADER' => 'YES',
'GCC_VERSION' => 'com.apple.compilers.llvm.clang.1_0',
# The OTHER_LDFLAGS option *has* to be overriden so that it does not
# use those from the xcconfig (for CocoaPods specifically).
'OTHER_LDFLAGS' => '',
'PRODUCT_NAME' => '$(TARGET_NAME)',
'SKIP_INSTALL' => 'YES',
}.merge(buildSettings || {})
end
end
class XCConfigurationList < PBXObject
has_many :buildConfigurations
def initialize(*)
super
self.buildConfigurationReferences ||= []
end
end
class PBXProject < PBXObject
has_many :targets, :class => PBXNativeTarget
has_one :products, :singular_name => :products, :uuid => :productRefGroup, :class => PBXGroup
end
class PBXObjectList
include Enumerable
def initialize(represented_class, project, scoped, &new_object_callback)
@represented_class = represented_class
@project = project
@scoped_hash = scoped.is_a?(Array) ? scoped.inject({}) { |h, o| h[o.uuid] = o.attributes; h } : scoped
@callback = new_object_callback
end
def empty?
@scoped_hash.empty?
end
def [](uuid)
if hash = @scoped_hash[uuid]
Project.const_get(hash['isa']).new(@project, uuid, hash)
end
end
def add(klass, hash = {})
object = klass.new(@project, nil, hash)
@callback.call(object) if @callback
object
end
def new(hash = {})
add(@represented_class, hash)
end
def <<(object)
@callback.call(object) if @callback
end
def each
@scoped_hash.keys.each do |uuid|
yield self[uuid]
end
end
def ==(other)
self.to_a == other.to_a
end
def first
to_a.first
end
def last
to_a.last
end
def inspect
"<PBXObjectList: #{map(&:inspect)}>"
end
# Only makes sense on lists that contain mixed classes.
def select_by_class(klass)
scoped = @scoped_hash.select { |_, attr| attr['isa'] == klass.isa }
PBXObjectList.new(klass, @project, scoped) do |object|
# Objects added to the subselection should still use the same
# callback as this list.
self << object
end
end
def method_missing(name, *args, &block)
if @represented_class.respond_to?(name)
object = @represented_class.send(name, @project, *args)
# The callbacks are only for PBXObject instances instantiated
# from the class method that we forwarded the message to.
@callback.call(object) if object.is_a?(PBXObject)
object
else
super
end
end
end
def initialize(xcodeproj = nil)
if xcodeproj
file = File.join(xcodeproj, 'project.pbxproj')
@plist = NSMutableDictionary.dictionaryWithContentsOfFile(file.to_s)
else
@plist = {
'archiveVersion' => '1',
'classes' => {},
'objectVersion' => '46',
'objects' => {}
}
self.root_object = objects.add(Xcode::Project::PBXProject, {
'attributes' => { 'LastUpgradeCheck' => '0420' },
'compatibilityVersion' => 'Xcode 3.2',
'developmentRegion' => 'English',
'hasScannedForEncodings' => '0',
'knownRegions' => ['en'],
'mainGroup' => groups.new.uuid,
'projectDirPath' => '',
'projectRoot' => '',
'targets' => []
})
end
end
def to_hash
@plist
end
def objects_hash
@plist['objects']
end
def objects
@objects ||= PBXObjectList.new(PBXObject, self, objects_hash)
end
def root_object
objects[@plist['rootObject']]
end
def root_object=(object)
@plist['rootObject'] = object.uuid
end
def groups
objects.select_by_class(PBXGroup)
end
def main_group
objects[root_object.attributes['mainGroup']]
end
# Shortcut access to the `Pods' PBXGroup.
def pods
groups.find { |g| g.name == 'Pods' }
end
# Adds a group as child to the `Pods' group.
def add_pod_group(name)
pods.groups.new('name' => name)
end
def files
objects.select_by_class(PBXFileReference)
end
def add_system_framework(name)
files.new({
'name' => "#{name}.framework",
'path' => "System/Library/Frameworks/#{name}.framework",
'sourceTree' => 'SDKROOT'
})
end
def add_shell_script_build_phase(name, script_path)
objects.add(Xcode::Project::PBXShellScriptBuildPhase, {
'name' => name,
'files' => [],
'inputPaths' => [],
'outputPaths' => [],
'shellPath' => '/bin/sh',
'shellScript' => script_path
})
end
def build_files
objects.select_by_class(PBXBuildFile)
end
def targets
# Better to check the project object for targets to ensure they are
# actually there so the project will work
root_object.targets
end
def products
root_object.products
end
IGNORE_GROUPS = ['Pods', 'Frameworks', 'Products', 'Supporting Files']
def source_files
source_files = {}
groups.each do |group|
next if group.name.nil? || IGNORE_GROUPS.include?(group.name)
source_files[group.name] = group.source_files.map(&:pathname)
end
source_files
end
def save_as(projpath)
projpath = projpath.to_s
FileUtils.mkdir_p(projpath)
@plist.writeToFile(File.join(projpath, 'project.pbxproj'), atomically:true)
end
end
end
end
framework 'Foundation'
require 'fileutils'
module Pod
module Xcode
class Workspace
def initialize(*projpaths)
@projpaths = projpaths
end
def self.new_from_xcworkspace(path)
begin
from_s(File.read(File.join(path, 'contents.xcworkspacedata')))
rescue Errno::ENOENT
new
end
end
def self.from_s(xml)
doc = NSXMLDocument.alloc.initWithXMLString(xml, options:0, error:nil)
projpaths = doc.nodesForXPath("/Workspace/FileRef", error:nil).map do |node|
node.attributeForName("location").stringValue.sub(/^group:/, '')
end
new(*projpaths)
end
attr_reader :projpaths
def <<(projpath)
@projpaths << projpath
end
def include?(projpath)
@projpaths.include?(projpath)
end
TEMPLATE = %q[<?xml version="1.0" encoding="UTF-8"?><Workspace version="1.0"></Workspace>]
def to_s
doc = NSXMLDocument.alloc.initWithXMLString(TEMPLATE, options:0, error:nil)
@projpaths.each do |projpath|
el = NSXMLNode.elementWithName("FileRef")
el.addAttribute(NSXMLNode.attributeWithName("location", stringValue:"group:#{projpath}"))
doc.rootElement.addChild(el)
end
NSString.alloc.initWithData(doc.XMLData, encoding:NSUTF8StringEncoding)
end
def save_as(path)
FileUtils.mkdir_p(path)
File.open(File.join(path, 'contents.xcworkspacedata'), 'w') do |out|
out << to_s
end
end
end
end
end
require File.expand_path('../../../spec_helper', __FILE__)
describe "Pod::Xcode::Config" do
extend SpecHelper::TemporaryDirectory
before do
@config = Pod::Xcode::Config.new('OTHER_LD_FLAGS' => '-framework Foundation')
end
it "merges another config hash in place" do
@config.merge!('HEADER_SEARCH_PATHS' => '/some/path')
@config.to_hash.should == {
'OTHER_LD_FLAGS' => '-framework Foundation',
'HEADER_SEARCH_PATHS' => '/some/path'
}
end
it "appends a value for the same key when merging" do
@config.merge!('OTHER_LD_FLAGS' => '-l xml2.2.7.3')
@config.to_hash.should == {
'OTHER_LD_FLAGS' => '-framework Foundation -l xml2.2.7.3'
}
end
it "creates the config file" do
@config.merge!('HEADER_SEARCH_PATHS' => '/some/path')
@config.merge!('OTHER_LD_FLAGS' => '-l xml2.2.7.3')
@config.save_as(temporary_directory + 'Pods.xcconfig')
(temporary_directory + 'Pods.xcconfig').read.split("\n").sort.should == [
"OTHER_LD_FLAGS = -framework Foundation -l xml2.2.7.3",
"HEADER_SEARCH_PATHS = /some/path"
].sort
end
end
require File.expand_path('../../../spec_helper', __FILE__)
describe "Pod::Xcode::Project" do
extend SpecHelper::TemporaryDirectory
before do
@project = Pod::ProjectTemplate.for_platform(:ios)
end
def find_objects(conditions)
@project.objects_hash.select do |_, object|
object.objectsForKeys(conditions.keys, notFoundMarker:Object.new) == conditions.values
end
end
def find_object(conditions)
find_objects(conditions).first
end
before do
@target = @project.targets.new_static_library('Pods')
end
it "returns the objects hash" do
@project.objects_hash.should == @project.to_hash['objects']
end
describe "PBXObject" do
before do
@object = Pod::Xcode::Project::PBXObject.new(@project, nil, 'name' => 'AnObject')
end
it "merges the class name into the attributes" do
@object.isa.should == 'PBXObject'
@object.attributes['isa'].should == 'PBXObject'
end
it "takes a name" do
@object.name.should == 'AnObject'
@object.name = 'AnotherObject'
@object.name.should == 'AnotherObject'
end
it "generates a uuid" do
@object.uuid.should.be.instance_of String
@object.uuid.size.should == 24
@object.uuid.should == @object.uuid
end
it "adds the object to the objects hash" do
@project.objects_hash[@object.uuid].should == @object.attributes
end
end
describe "a PBXFileReference" do
it "sets a default file type" do
framework, library, xcconfig = %w[framework a xcconfig].map { |n| @project.files.new('path' => "Rockin.#{n}") }
framework.lastKnownFileType.should == 'wrapper.framework'
framework.explicitFileType.should == nil
library.lastKnownFileType.should == nil
library.explicitFileType.should == 'archive.ar'
xcconfig.lastKnownFileType.should == 'text.xcconfig'
xcconfig.explicitFileType.should == nil
end
it "doesn't set a file type when overridden" do
fakework = @project.files.new('path' => 'Sup.framework', 'lastKnownFileType' => 'fish')
fakework.lastKnownFileType.should == 'fish'
makework = @project.files.new('path' => 'n2m.framework', 'explicitFileType' => 'tree')
makework.lastKnownFileType.should == nil
end
before do
@file = @project.files.new('path' => 'some/file.m')
end
it "is automatically added to the main group" do
@file.group.should == @project.main_group
end
it "is removed from the original group when added to another group" do
@project.pods.children << @file
@file.group.should == @project.pods
@project.main_group.children.should.not.include @file
end
end
describe "a new PBXBuildPhase" do
before do
@phase = @project.objects.add(Pod::Xcode::Project::PBXBuildPhase)
end
it "has an empty list of files" do
@phase.files.to_a.should == []
end
it "always returns the same buildActionMask (no idea what it is)" do
@phase.buildActionMask.should == "2147483647"
end
it "always returns zero for runOnlyForDeploymentPostprocessing (no idea what it is)" do
@phase.runOnlyForDeploymentPostprocessing.should == "0"
end
end
describe "a new PBXCopyFilesBuildPhase" do
before do
@phase = @project.objects.add(Pod::Xcode::Project::PBXCopyFilesBuildPhase, 'dstPath' => 'some/path')
end
it "is a PBXBuildPhase" do
@phase.should.be.kind_of Pod::Xcode::Project::PBXBuildPhase
end
it "returns the dstPath" do
@phase.dstPath.should == 'some/path'
end
it "returns the dstSubfolderSpec (no idea what it is yet, but it's not always the same)" do
@phase.dstSubfolderSpec.should == "16"
end
end
describe "a new PBXSourcesBuildPhase" do
before do
@phase = @project.objects.add(Pod::Xcode::Project::PBXSourcesBuildPhase)
end
it "is a PBXBuildPhase" do
@phase.should.be.kind_of Pod::Xcode::Project::PBXBuildPhase
end
end
describe "a new PBXFrameworksBuildPhase" do
before do
@phase = @project.objects.add(Pod::Xcode::Project::PBXFrameworksBuildPhase)
end
it "is a PBXBuildPhase" do
@phase.should.be.kind_of Pod::Xcode::Project::PBXBuildPhase
end
end
describe "a new XCBuildConfiguration" do
before do
@configuration = @project.objects.add(Pod::Xcode::Project::XCBuildConfiguration)
end
it "returns the xcconfig that this configuration is based on (baseConfigurationReference)" do
xcconfig = @project.objects.new
@configuration.baseConfiguration = xcconfig
@configuration.baseConfigurationReference.should == xcconfig.uuid
end
end
describe "a new XCConfigurationList" do
before do
@list = @project.objects.add(Pod::Xcode::Project::XCConfigurationList)
end
it "returns the configurations" do
configuration = @project.objects.add(Pod::Xcode::Project::XCBuildConfiguration)
@list.buildConfigurations.to_a.should == []
@list.buildConfigurations = [configuration]
@list.buildConfigurationReferences.should == [configuration.uuid]
end
end
describe "a new PBXNativeTarget" do
it "returns the product name, which is the name of the binary (minus prefix/suffix)" do
@target.name.should == "Pods"
@target.productName.should == "Pods"
end
it "returns the product" do
product = @target.product
product.uuid.should == @target.productReference
product.should.be.instance_of Pod::Xcode::Project::PBXFileReference
product.path.should == "libPods.a"
product.name.should == "libPods.a"
product.group.name.should == "Products"
product.sourceTree.should == "BUILT_PRODUCTS_DIR"
product.explicitFileType.should == "archive.ar"
product.includeInIndex.should == "0"
end
it "returns that product type is a static library" do
@target.productType.should == "com.apple.product-type.library.static"
end
it "returns the buildConfigurationList" do
list = @target.buildConfigurationList
list.should.be.instance_of Pod::Xcode::Project::XCConfigurationList
list.buildConfigurations.each do |configuration|
configuration.buildSettings.should == {
'DSTROOT' => '/tmp/Pods.dst',
'GCC_PRECOMPILE_PREFIX_HEADER' => 'YES',
# TODO do we need a default?
#'GCC_PREFIX_HEADER' => 'Pods-Prefix.pch',
'OTHER_LDFLAGS' => '',
'GCC_VERSION' => 'com.apple.compilers.llvm.clang.1_0',
'PRODUCT_NAME' => '$(TARGET_NAME)',
'SKIP_INSTALL' => 'YES',
}
end
end
it "returns an empty list of dependencies and buildRules (not sure yet which classes those are yet)" do
@target.dependencies.to_a.should == []
@target.buildRules.to_a.should == []
end
describe "concerning its build phases" do
extend SpecHelper::TemporaryDirectory
it "returns an empty sources build phase" do
phase = @target.buildPhases.select_by_class(Pod::Xcode::Project::PBXSourcesBuildPhase).first
phase.files.to_a.should == []
end
it "returns a libraries/frameworks build phase, which by default only contains `Foundation.framework'" do
phase = @target.buildPhases.select_by_class(Pod::Xcode::Project::PBXFrameworksBuildPhase).first
phase.files.map { |buildFile| buildFile.file.name }.should == ['Foundation.framework']
end
it "returns an empty 'copy headers' phase" do
phase = @target.buildPhases.select_by_class(Pod::Xcode::Project::PBXCopyFilesBuildPhase).first
phase.dstPath.should == "$(PUBLIC_HEADERS_FOLDER_PATH)"
phase.files.to_a.should == []
end
end
end
it "returns the objects as PBXObject instances" do
@project.objects.each do |object|
@project.objects_hash[object.uuid].should == object.attributes
end
end
it "adds any type of new PBXObject to the objects hash" do
object = @project.objects.add(Pod::Xcode::Project::PBXObject, 'name' => 'An Object')
object.name.should == 'An Object'
@project.objects_hash[object.uuid].should == object.attributes
end
it "adds a new PBXObject, of the configured type, to the objects hash" do
group = @project.groups.new('name' => 'A new group')
group.isa.should == 'PBXGroup'
group.name.should == 'A new group'
@project.objects_hash[group.uuid].should == group.attributes
end
it "adds a new PBXFileReference to the objects hash" do
file = @project.files.new('path' => '/some/file.m')
file.isa.should == 'PBXFileReference'
file.name.should == 'file.m'
file.path.should == '/some/file.m'
file.sourceTree.should == 'SOURCE_ROOT'
@project.objects_hash[file.uuid].should == file.attributes
end
it "adds a new PBXBuildFile to the objects hash when a new PBXFileReference is created" do
file = @project.files.new('name' => '/some/source/file.h')
build_file = file.buildFiles.new
build_file.file = file
build_file.fileRef.should == file.uuid
build_file.isa.should == 'PBXBuildFile'
@project.objects_hash[build_file.uuid].should == build_file.attributes
end
it "adds a group to the `Pods' group" do
group = @project.add_pod_group('JSONKit')
@project.pods.childReferences.should.include group.uuid
find_object({
'isa' => 'PBXGroup',
'name' => 'JSONKit',
'sourceTree' => '<group>',
'children' => []
}).should.not == nil
end
it "adds an `m' or `c' file to the `sources build' phase list" do
%w{ m mm c cpp }.each do |ext|
path = Pathname.new("path/to/file.#{ext}")
file = @target.add_source_file(path)
# ensure that it was added to all objects
file = @project.objects[file.uuid]
phase = @target.buildPhases.find { |phase| phase.is_a?(Pod::Xcode::Project::PBXSourcesBuildPhase) }
phase.files.map { |buildFile| buildFile.file }.should.include file
phase = @target.buildPhases.find { |phase| phase.is_a?(Pod::Xcode::Project::PBXCopyFilesBuildPhase) }
phase.files.map { |buildFile| buildFile.file }.should.not.include file
end
end
it "adds custom compiler flags to the PBXBuildFile object if specified" do
build_file_uuids = []
%w{ m mm c cpp }.each do |ext|
path = Pathname.new("path/to/file.#{ext}")
file = @project.targets.first.add_source_file(path, nil, '-fno-obj-arc')
find_object({
'isa' => 'PBXBuildFile',
'fileRef' => file.uuid,
'settings' => {'COMPILER_FLAGS' => '-fno-obj-arc' }
}).should.not == nil
end
end
it "creates a copy build header phase which will copy headers to a specified path" do
phase = @project.targets.first.copy_files_build_phases.new_pod_dir("SomePod", "Path/To/Source")
find_object({
'isa' => 'PBXCopyFilesBuildPhase',
'dstPath' => '$(PUBLIC_HEADERS_FOLDER_PATH)/Path/To/Source',
'name' => 'Copy SomePod Public Headers'
}).should.not == nil
@project.targets.first.buildPhases.should.include phase
end
# TODO add test for the optional copy_header_phase
#it "adds a `h' file as a build file and adds it to the `headers build' phase list" do
it "adds a `h' file as a build file and adds it to the `copy header files' build phase list" do
path = Pathname.new("path/to/file.h")
file = @target.add_source_file(path)
# ensure that it was added to all objects
file = @project.objects[file.uuid]
phase = @target.buildPhases.find { |phase| phase.is_a?(Pod::Xcode::Project::PBXSourcesBuildPhase) }
phase.files.map { |buildFile| buildFile.file }.should.not.include file
phase = @target.buildPhases.find { |phase| phase.is_a?(Pod::Xcode::Project::PBXCopyFilesBuildPhase) }
phase.files.map { |buildFile| buildFile.file }.should.include file
end
it "saves the template with the adjusted project" do
@project.save_as(File.join(temporary_directory, 'Pods.xcodeproj'))
project_file = (temporary_directory + 'Pods.xcodeproj/project.pbxproj')
NSDictionary.dictionaryWithContentsOfFile(project_file.to_s).should == @project.to_hash
end
it "returns all source files" do
group = @project.groups.new('name' => 'SomeGroup')
files = [Pathname.new('/some/file.h'), Pathname.new('/some/file.m')]
files.each { |file| group << @target.add_source_file(file) }
group.source_files.map(&:pathname).sort.should == files.sort
end
end
require File.expand_path('../../../spec_helper', __FILE__)
describe "Pod::Xcode::Workspace" do
before do
@workspace = Pod::Xcode::Workspace.new('Pods/Pods.xcodeproj', 'App.xcodeproj')
end
it "accepts new projects" do
@workspace << 'Framework.xcodeproj'
@workspace.projpaths.should.include 'Framework.xcodeproj'
end
before do
@doc = NSXMLDocument.alloc.initWithXMLString(@workspace.to_s, options:0, error:nil)
end
it "is the right xml workspace version" do
@doc.rootElement.attributeForName("version").stringValue.should == "1.0"
end
it "refers to the projects in xml" do
@doc.nodesForXPath("/Workspace/FileRef", error:nil).map do |node|
node.attributeForName("location").stringValue.sub(/^group:/, '')
end.sort.should == ['App.xcodeproj', 'Pods/Pods.xcodeproj']
end
end
require File.expand_path('../../spec_helper', __FILE__)
describe 'Xcode::Project' do
before do
@project = Xcode::Project.new
end
def find_object(conditions)
@project.objects_hash.select do |_, object|
object.objectsForKeys(conditions.keys, notFoundMarker:Object.new) == conditions.values
end.first
end
it "adds a group to the `Pods' group" do
group = @project.add_pod_group('JSONKit')
@project.pods.childReferences.should.include group.uuid
find_object({
'isa' => 'PBXGroup',
'name' => 'JSONKit',
'sourceTree' => '<group>',
'children' => []
}).should.not == nil
end
it "creates a copy build header phase which will copy headers to a specified path" do
@project.targets.new
phase = @project.targets.first.copy_files_build_phases.new_pod_dir("SomePod", "Path/To/Source")
find_object({
'isa' => 'PBXCopyFilesBuildPhase',
'dstPath' => '$(PUBLIC_HEADERS_FOLDER_PATH)/Path/To/Source',
'name' => 'Copy SomePod Public Headers'
}).should.not == nil
@project.targets.first.buildPhases.should.include phase
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment