xcodeproj 0.1.0 → 0.2.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|