xcodeproj 0.1.0 → 0.2.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ext/xcodeproj/extconf.h +8 -0
- data/ext/xcodeproj/extconf.rb +1 -0
- data/ext/xcodeproj/xcodeproj_ext.c +69 -11
- data/lib/xcodeproj.rb +1 -1
- data/lib/xcodeproj/config.rb +142 -10
- data/lib/xcodeproj/inflector.rb +1 -1
- data/lib/xcodeproj/project.rb +154 -566
- data/lib/xcodeproj/project/association.rb +54 -0
- data/lib/xcodeproj/project/association/has_many.rb +51 -0
- data/lib/xcodeproj/project/association/has_one.rb +39 -0
- data/lib/xcodeproj/project/association/reflection.rb +88 -0
- data/lib/xcodeproj/project/object.rb +207 -0
- data/lib/xcodeproj/project/object/build_phase.rb +89 -0
- data/lib/xcodeproj/project/object/configuration.rb +100 -0
- data/lib/xcodeproj/project/object/file_reference.rb +71 -0
- data/lib/xcodeproj/project/object/group.rb +102 -0
- data/lib/xcodeproj/project/object/native_target.rb +126 -0
- data/lib/xcodeproj/project/object_list.rb +146 -0
- metadata +19 -6
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'xcodeproj/project/association/has_many'
|
2
|
+
require 'xcodeproj/project/association/has_one'
|
3
|
+
require 'xcodeproj/project/association/reflection'
|
4
|
+
|
5
|
+
module Xcodeproj
|
6
|
+
class Project
|
7
|
+
module Object
|
8
|
+
|
9
|
+
class AbstractPBXObject
|
10
|
+
class << self
|
11
|
+
def has_many(plural_attr_name, options = {}, &block)
|
12
|
+
create_association(:has_many, plural_attr_name, options, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def has_one(singular_attr_name, options = {})
|
16
|
+
create_association(:has_one, singular_attr_name, options)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def create_association(type, name, options, &block)
|
22
|
+
reflection = create_reflection(type, name, options)
|
23
|
+
unless reflection.inverse?
|
24
|
+
attribute(reflection.attribute_name, :as => reflection.attribute_getter)
|
25
|
+
end
|
26
|
+
define_method(reflection.getter) do
|
27
|
+
reflection.association_for(self, &block).get
|
28
|
+
end
|
29
|
+
define_method(reflection.setter) do |new_value|
|
30
|
+
reflection.association_for(self, &block).set(new_value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Association
|
37
|
+
attr_reader :owner, :reflection
|
38
|
+
|
39
|
+
def initialize(owner, reflection, &block)
|
40
|
+
@owner, @reflection, @block = owner, reflection, block
|
41
|
+
end
|
42
|
+
|
43
|
+
def get
|
44
|
+
@reflection.inverse? ? inverse_get : direct_get
|
45
|
+
end
|
46
|
+
|
47
|
+
def set(value)
|
48
|
+
@reflection.inverse? ? inverse_set(value) : direct_set(value)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Xcodeproj
|
2
|
+
class Project
|
3
|
+
module Object
|
4
|
+
class Association
|
5
|
+
|
6
|
+
class HasMany < Association
|
7
|
+
def direct_get
|
8
|
+
uuids = @owner.send(@reflection.attribute_getter)
|
9
|
+
if @block
|
10
|
+
# Evaluate the block, which was specified at the class level, in
|
11
|
+
# the instance’s context.
|
12
|
+
@owner.list_by_class(uuids, @reflection.klass) do |list|
|
13
|
+
list.let(:push) do |new_object|
|
14
|
+
@owner.instance_exec(new_object, &@block)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
else
|
18
|
+
@owner.list_by_class(uuids, @reflection.klass)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def inverse_get
|
23
|
+
PBXObjectList.new(@reflection.klass, @owner.project) do |list|
|
24
|
+
list.let(:uuid_scope) do
|
25
|
+
@owner.project.objects.list_by_class(@reflection.klass).select do |object|
|
26
|
+
object.send(@reflection.inverse.attribute_getter) == @owner.uuid
|
27
|
+
end.map(&:uuid)
|
28
|
+
end
|
29
|
+
list.let(:push) do |new_object|
|
30
|
+
new_object.send(@reflection.inverse.attribute_setter, @owner.uuid)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# @todo Currently this does not call the @block, which means that
|
36
|
+
# in theory (like with a group's children) the object stays
|
37
|
+
# asociated with its old group.
|
38
|
+
def direct_set(list)
|
39
|
+
@owner.send(@reflection.attribute_setter, list.map(&:uuid))
|
40
|
+
end
|
41
|
+
|
42
|
+
def inverse_set(list)
|
43
|
+
raise NotImplementedError
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Xcodeproj
|
2
|
+
class Project
|
3
|
+
module Object
|
4
|
+
class Association
|
5
|
+
|
6
|
+
# @todo Does this need 'new object'-callback support too?
|
7
|
+
class HasOne < Association
|
8
|
+
def direct_get
|
9
|
+
@owner.project.objects[@owner.send(@reflection.attribute_getter)]
|
10
|
+
end
|
11
|
+
|
12
|
+
def inverse_get
|
13
|
+
# Loop over all objects of the class and find the one that includes
|
14
|
+
# this object in the specified uuid list.
|
15
|
+
@owner.project.objects.list_by_class(@reflection.klass).find do |object|
|
16
|
+
object.send(@reflection.inverse.attribute_getter).include?(@owner.uuid)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def direct_set(object)
|
21
|
+
@owner.send(@reflection.attribute_setter, object.uuid)
|
22
|
+
end
|
23
|
+
|
24
|
+
def inverse_set(object)
|
25
|
+
# Remove this object from the uuid list of the target
|
26
|
+
# that this object was associated to.
|
27
|
+
if previous = @owner.send(@reflection.name)
|
28
|
+
previous.send(@reflection.inverse.attribute_getter).delete(@owner.uuid)
|
29
|
+
end
|
30
|
+
# Now assign this object to the new object
|
31
|
+
object.send(@reflection.inverse.attribute_getter) << @owner.uuid if object
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'xcodeproj/inflector'
|
2
|
+
|
3
|
+
module Xcodeproj
|
4
|
+
class Project
|
5
|
+
module Object
|
6
|
+
|
7
|
+
class AbstractPBXObject
|
8
|
+
def self.reflections
|
9
|
+
@reflections ||= []
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.create_reflection(type, name, options)
|
13
|
+
(reflections << Association::Reflection.new(type, name, options)).last
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.reflection(name)
|
17
|
+
reflections.find { |r| r.name.to_s == name.to_s }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Association
|
22
|
+
class Reflection
|
23
|
+
def initialize(type, name, options)
|
24
|
+
@type, @name, @options = type, name.to_s, options
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :type, :name, :options
|
28
|
+
|
29
|
+
def klass
|
30
|
+
@options[:class] ||= begin
|
31
|
+
name = "PBX#{@name.classify}"
|
32
|
+
name = "XC#{@name.classify}" unless Xcodeproj::Project::Object.const_defined?(name)
|
33
|
+
Xcodeproj::Project::Object.const_get(name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def inverse
|
38
|
+
klass.reflection(@options[:inverse_of])
|
39
|
+
end
|
40
|
+
|
41
|
+
def inverse?
|
42
|
+
!!@options[:inverse_of]
|
43
|
+
end
|
44
|
+
|
45
|
+
def attribute_name
|
46
|
+
(@options[:uuid] || @options[:uuids] || @name).to_sym
|
47
|
+
end
|
48
|
+
|
49
|
+
def attribute_getter
|
50
|
+
case type
|
51
|
+
when :has_many
|
52
|
+
uuid_method_name.pluralize
|
53
|
+
when :has_one
|
54
|
+
uuid_method_name
|
55
|
+
end.to_sym
|
56
|
+
end
|
57
|
+
|
58
|
+
def attribute_setter
|
59
|
+
"#{attribute_getter}=".to_sym
|
60
|
+
end
|
61
|
+
|
62
|
+
def getter
|
63
|
+
@name.to_sym
|
64
|
+
end
|
65
|
+
|
66
|
+
def setter
|
67
|
+
"#{@name}=".to_sym
|
68
|
+
end
|
69
|
+
|
70
|
+
def association_for(owner, &block)
|
71
|
+
case type
|
72
|
+
when :has_many then Association::HasMany
|
73
|
+
when :has_one then Association::HasOne
|
74
|
+
end.new(owner, self, &block)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def uuid_method_name
|
80
|
+
(@options[:uuids_as] || @options[:uuid] || @options[:uuids] || "#{@name.singularize}_reference").to_s.singularize
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'xcodeproj/inflector'
|
2
|
+
|
3
|
+
module Xcodeproj
|
4
|
+
class Project
|
5
|
+
# This is the namespace in which all the classes that wrap the objects in
|
6
|
+
# a Xcode project reside.
|
7
|
+
#
|
8
|
+
# The base class from which all classes inherit is AbstractPBXObject.
|
9
|
+
#
|
10
|
+
# If you need to deal with these classes directly, it's possible to include
|
11
|
+
# this namespace into yours, making it unnecessary to prefix them with
|
12
|
+
# Xcodeproj::Project::Object.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
#
|
16
|
+
# class SourceFileSorter
|
17
|
+
# include Xcodeproj::Project::Object
|
18
|
+
# end
|
19
|
+
module Object
|
20
|
+
|
21
|
+
# Missing constants that begin with either `PBX' or `XC' are assumed to
|
22
|
+
# be valid classes in a Xcode project. A new AbstractPBXObject subclass is
|
23
|
+
# created for the constant and returned.
|
24
|
+
#
|
25
|
+
# @return [Class] The generated class inhertiting from AbstractPBXObject.
|
26
|
+
def self.const_missing(name)
|
27
|
+
if name.to_s =~ /^(PBX|XC)/
|
28
|
+
klass = Class.new(AbstractPBXObject)
|
29
|
+
const_set(name, klass)
|
30
|
+
klass
|
31
|
+
else
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# This is the base class of all object types that can exist in a Xcode
|
37
|
+
# project. As such it provides common behavior, but you can only use
|
38
|
+
# instances of subclasses of AbstractPBXObject, because this class does
|
39
|
+
# not exist in actual Xcode projects.
|
40
|
+
class AbstractPBXObject
|
41
|
+
|
42
|
+
# This defines accessor methods for a key-value pair which occurs in the
|
43
|
+
# attributes hash that the object wraps.
|
44
|
+
#
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
#
|
48
|
+
# # Create getter and setter methods named after the key it corresponds to
|
49
|
+
# # in the attributes hash:
|
50
|
+
#
|
51
|
+
# class PBXBuildPhase < AbstractPBXObject
|
52
|
+
# attribute :settings
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# build_phase.attributes # => { 'settings' => { 'COMPILER_FLAGS' => '-fobjc-arc' }, ... }
|
56
|
+
# build_phase.settings # => { 'COMPILER_FLAGS' => '-fobjc-arc' }
|
57
|
+
#
|
58
|
+
# build_phase.settings = { 'COMPILER_FLAGS' => '-fobjc-no-arc' }
|
59
|
+
# build_phase.attributes # => { 'settings' => { 'COMPILER_FLAGS' => '-fobjc-no-arc' }, ... }
|
60
|
+
#
|
61
|
+
# # Or with a custom getter and setter methods:
|
62
|
+
#
|
63
|
+
# class PBXCopyFilesBuildPhase < AbstractPBXObject
|
64
|
+
# attribute :dst_path, :as => :destination_path
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# build_phase.attributes # => { 'dstPath' => 'some/path', ... }
|
68
|
+
# build_phase.destination_path # => 'some/path'
|
69
|
+
#
|
70
|
+
# build_phase.destination_path = 'another/path'
|
71
|
+
# build_phase.attributes # => { 'dstPath' => 'another/path', ... }
|
72
|
+
#
|
73
|
+
#
|
74
|
+
# @param [Symbol, String] attribute_name The key in snake case.
|
75
|
+
# @option options [Symbol String] :as An optional custom name for
|
76
|
+
# the getter and setter methods.
|
77
|
+
def self.attribute(name, options = {})
|
78
|
+
accessor_name = (options[:as] || name).to_s
|
79
|
+
attribute_name = name.to_s.camelize(:lower) # change `foo_bar' to `fooBar'
|
80
|
+
define_method(accessor_name) { @attributes[attribute_name] }
|
81
|
+
define_method("#{accessor_name}=") { |value| @attributes[attribute_name] = value }
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.isa
|
85
|
+
@isa ||= name.split('::').last
|
86
|
+
end
|
87
|
+
|
88
|
+
attr_reader :uuid, :attributes, :project
|
89
|
+
|
90
|
+
# [String] the object's class name
|
91
|
+
attribute :isa
|
92
|
+
|
93
|
+
# [String] the object's name
|
94
|
+
attribute :name
|
95
|
+
|
96
|
+
# It is not recommended that you instantiate objects through this
|
97
|
+
# constructor. It is much easier to use associations to create them.
|
98
|
+
#
|
99
|
+
# @example
|
100
|
+
#
|
101
|
+
# file_reference = project.files.new('path' => 'path/to/file')
|
102
|
+
#
|
103
|
+
# @return [AbstractPBXObject]
|
104
|
+
def initialize(project, uuid, attributes)
|
105
|
+
@project, @attributes = project, attributes
|
106
|
+
self.isa ||= self.class.isa
|
107
|
+
unless uuid
|
108
|
+
# Add new objects to the main hash with a unique UUID
|
109
|
+
uuid = generate_uuid
|
110
|
+
@project.add_object_hash(uuid, @attributes)
|
111
|
+
end
|
112
|
+
@uuid = uuid
|
113
|
+
end
|
114
|
+
|
115
|
+
def destroy
|
116
|
+
@project.objects_hash.delete(uuid)
|
117
|
+
end
|
118
|
+
|
119
|
+
def ==(other)
|
120
|
+
other.is_a?(AbstractPBXObject) && self.uuid == other.uuid
|
121
|
+
end
|
122
|
+
|
123
|
+
def <=>(other)
|
124
|
+
self.uuid <=> other.uuid
|
125
|
+
end
|
126
|
+
|
127
|
+
def inspect
|
128
|
+
"#<#{isa} UUID: `#{uuid}', name: `#{name}'>"
|
129
|
+
end
|
130
|
+
|
131
|
+
def matches_attributes?(attributes)
|
132
|
+
attributes.all? do |attribute, expected_value|
|
133
|
+
return nil unless respond_to?(attribute)
|
134
|
+
|
135
|
+
if expected_value.is_a?(Hash)
|
136
|
+
send(attribute).matches_attributes?(expected_value)
|
137
|
+
else
|
138
|
+
send(attribute) == expected_value
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns a PBXObjectList instance of objects in the `uuid_list`.
|
144
|
+
#
|
145
|
+
# By default this list will scope the list by objects matching the
|
146
|
+
# specified class and add objects, pushed onto the list, to the given
|
147
|
+
# `uuid_list` array.
|
148
|
+
#
|
149
|
+
# If a block is given the list instance is yielded so that the default
|
150
|
+
# callbacks can be overridden.
|
151
|
+
#
|
152
|
+
# @param [Array] uuid_list The UUID array instance which is
|
153
|
+
# part of the internal data. If this
|
154
|
+
# would be an arbitrary array and an
|
155
|
+
# object is added, then it doesn't
|
156
|
+
# actually modify the internal data,
|
157
|
+
# meaning the change is lost.
|
158
|
+
#
|
159
|
+
# @param [AbstractPBXObject] klass The AbstractPBXObject subclass to
|
160
|
+
# which the list should be scoped.
|
161
|
+
#
|
162
|
+
# @yield [PBXObjectList] The list instance, allowing you to
|
163
|
+
# easily override the callbacks.
|
164
|
+
#
|
165
|
+
# @return [PBXObjectList<klass>] The list of matching objects.
|
166
|
+
def list_by_class(uuid_list, klass)
|
167
|
+
PBXObjectList.new(klass, @project) do |list|
|
168
|
+
list.let(:uuid_scope) do
|
169
|
+
# TODO why does this not work? should be more efficient.
|
170
|
+
#uuid_list.select do |uuid|
|
171
|
+
#@project.objects_hash[uuid]['isa'] == klass.isa
|
172
|
+
#end
|
173
|
+
uuid_list.map { |uuid| @project.objects[uuid] }.select { |o| o.is_a?(klass) }.map(&:uuid)
|
174
|
+
end
|
175
|
+
list.let(:push) do |new_object|
|
176
|
+
# Add the uuid of a newly created object to the uuids list
|
177
|
+
uuid_list << new_object.uuid
|
178
|
+
end
|
179
|
+
yield list if block_given?
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
# Generate a truly unique UUID. This is to ensure that cutting up the
|
186
|
+
# UUID in the xcodeproj extension doesn't cause a collision.
|
187
|
+
def generate_uuid
|
188
|
+
begin
|
189
|
+
uuid = Xcodeproj.generate_uuid
|
190
|
+
end while @project.objects_hash.has_key?(uuid)
|
191
|
+
uuid
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
require 'xcodeproj/project/association'
|
200
|
+
require 'xcodeproj/project/object_list'
|
201
|
+
|
202
|
+
# Now load the rest of the classes which inherit from AbstractPBXObject.
|
203
|
+
require 'xcodeproj/project/object/build_phase'
|
204
|
+
require 'xcodeproj/project/object/configuration'
|
205
|
+
require 'xcodeproj/project/object/file_reference'
|
206
|
+
require 'xcodeproj/project/object/group'
|
207
|
+
require 'xcodeproj/project/object/native_target'
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Xcodeproj
|
2
|
+
class Project
|
3
|
+
module Object
|
4
|
+
|
5
|
+
class PBXBuildFile < AbstractPBXObject
|
6
|
+
# [Hash] the list of build settings for this file
|
7
|
+
attribute :settings
|
8
|
+
|
9
|
+
has_one :file, :uuid => :file_ref
|
10
|
+
end
|
11
|
+
|
12
|
+
class PBXBuildPhase < AbstractPBXObject
|
13
|
+
has_many :build_files, :uuids => :files, :uuids_as => :build_file_references
|
14
|
+
|
15
|
+
# [String] some kind of magic number which seems to always be '2147483647'
|
16
|
+
attribute :build_action_mask
|
17
|
+
|
18
|
+
# [String] wether or not this should only be processed before deployment
|
19
|
+
# (I guess). This cane be either '0', or '1'
|
20
|
+
attribute :run_only_for_deployment_postprocessing
|
21
|
+
|
22
|
+
def initialize(*)
|
23
|
+
super
|
24
|
+
self.build_file_references ||= []
|
25
|
+
# These are always the same, no idea what they are.
|
26
|
+
self.build_action_mask ||= "2147483647"
|
27
|
+
self.run_only_for_deployment_postprocessing ||= "0"
|
28
|
+
end
|
29
|
+
|
30
|
+
def files
|
31
|
+
PBXObjectList.new(PBXFileReference, @project) do |list|
|
32
|
+
list.let(:uuid_scope) { self.build_files.map(&:file_ref) }
|
33
|
+
list.let(:push) do |file|
|
34
|
+
self.build_files << file.build_files.new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def <<(file)
|
40
|
+
files << file
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class PBXCopyFilesBuildPhase < PBXBuildPhase
|
45
|
+
# [String] the path where this file should be copied to
|
46
|
+
attribute :dst_path
|
47
|
+
|
48
|
+
# [String] a magic number which always seems to be "16"
|
49
|
+
attribute :dst_subfolder_spec
|
50
|
+
|
51
|
+
def initialize(*)
|
52
|
+
super
|
53
|
+
self.dst_path ||= '$(PRODUCT_NAME)'
|
54
|
+
self.dst_subfolder_spec ||= "16"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class PBXSourcesBuildPhase < PBXBuildPhase; end
|
59
|
+
class PBXFrameworksBuildPhase < PBXBuildPhase; end
|
60
|
+
|
61
|
+
# @todo Should `files`, `input_paths`, and `output_paths` be has_many
|
62
|
+
# associations with file references?
|
63
|
+
class PBXShellScriptBuildPhase < PBXBuildPhase
|
64
|
+
attribute :name
|
65
|
+
|
66
|
+
attribute :files
|
67
|
+
attribute :input_paths
|
68
|
+
attribute :output_paths
|
69
|
+
|
70
|
+
# [String] The path to the script interpreter. Defaults to `/bin/sh`.
|
71
|
+
attribute :shell_path
|
72
|
+
|
73
|
+
# [String] The actual script to perform.
|
74
|
+
attribute :shell_script
|
75
|
+
|
76
|
+
def initialize(*)
|
77
|
+
super
|
78
|
+
self.files ||= []
|
79
|
+
self.input_paths ||= []
|
80
|
+
self.output_paths ||= []
|
81
|
+
|
82
|
+
self.shell_path ||= '/bin/sh'
|
83
|
+
self.shell_script ||= ''
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|