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
data/ext/xcodeproj/extconf.rb
CHANGED
@@ -4,7 +4,10 @@
|
|
4
4
|
#include "extconf.h"
|
5
5
|
|
6
6
|
#include "ruby.h"
|
7
|
+
#if HAVE_RUBY_ST_H
|
7
8
|
#include "ruby/st.h"
|
9
|
+
#endif
|
10
|
+
|
8
11
|
#include "CoreFoundation/CoreFoundation.h"
|
9
12
|
#include "CoreFoundation/CFStream.h"
|
10
13
|
#include "CoreFoundation/CFPropertyList.h"
|
@@ -29,7 +32,14 @@ str_to_cfstr(VALUE str) {
|
|
29
32
|
return CFStringCreateWithCString(NULL, RSTRING_PTR(rb_String(str)), kCFStringEncodingUTF8);
|
30
33
|
}
|
31
34
|
|
32
|
-
|
35
|
+
/* Generates a UUID. The original version is truncated, so this is not 100%
|
36
|
+
* guaranteed to be unique. However, the `PBXObject#generate_uuid` method
|
37
|
+
* checks that the UUID does not exist yet, in the project, before using it.
|
38
|
+
*
|
39
|
+
* @note Meant for internal use only.
|
40
|
+
*
|
41
|
+
* @return [String] A 24 characters long UUID.
|
42
|
+
*/
|
33
43
|
static VALUE
|
34
44
|
generate_uuid(void) {
|
35
45
|
CFUUIDRef uuid = CFUUIDCreate(NULL);
|
@@ -69,11 +79,22 @@ hash_set(const void *keyRef, const void *valueRef, void *hash) {
|
|
69
79
|
value = rb_ary_new();
|
70
80
|
CFIndex i, count = CFArrayGetCount(valueRef);
|
71
81
|
for (i = 0; i < count; i++) {
|
72
|
-
|
73
|
-
|
74
|
-
|
82
|
+
CFTypeRef elementRef = CFArrayGetValueAtIndex(valueRef, i);
|
83
|
+
CFTypeID elementType = CFGetTypeID(elementRef);
|
84
|
+
if (elementType == CFStringGetTypeID()) {
|
85
|
+
rb_ary_push(value, cfstr_to_str(elementRef));
|
86
|
+
|
87
|
+
} else if (elementType == CFDictionaryGetTypeID()) {
|
88
|
+
VALUE hashElement = rb_hash_new();
|
89
|
+
CFDictionaryApplyFunction(elementRef, hash_set, (void *)hashElement);
|
90
|
+
rb_ary_push(value, hashElement);
|
91
|
+
|
75
92
|
} else {
|
76
|
-
|
93
|
+
CFStringRef descriptionRef = CFCopyDescription(elementRef);
|
94
|
+
// obviously not optimial, but we're raising here, so it doesn't really matter
|
95
|
+
VALUE description = cfstr_to_str(descriptionRef);
|
96
|
+
rb_raise(rb_eTypeError, "Plist array value contains a object type unsupported by Xcodeproj. In: `%s'", RSTRING_PTR(description));
|
97
|
+
CFRelease(descriptionRef);
|
77
98
|
}
|
78
99
|
}
|
79
100
|
|
@@ -94,15 +115,26 @@ dictionary_set(st_data_t key, st_data_t value, CFMutableDictionaryRef dict) {
|
|
94
115
|
0,
|
95
116
|
&kCFTypeDictionaryKeyCallBacks,
|
96
117
|
&kCFTypeDictionaryValueCallBacks);
|
97
|
-
|
118
|
+
rb_hash_foreach(value, dictionary_set, (st_data_t)valueRef);
|
98
119
|
|
99
120
|
} else if (TYPE(value) == T_ARRAY) {
|
100
121
|
long i, count = RARRAY_LEN(value);
|
101
122
|
valueRef = CFArrayCreateMutable(NULL, count, &kCFTypeArrayCallBacks);
|
102
123
|
for (i = 0; i < count; i++) {
|
103
|
-
|
104
|
-
|
105
|
-
|
124
|
+
VALUE element = RARRAY_PTR(value)[i];
|
125
|
+
CFTypeRef elementRef = NULL;
|
126
|
+
if (TYPE(element) == T_HASH) {
|
127
|
+
elementRef = CFDictionaryCreateMutable(NULL,
|
128
|
+
0,
|
129
|
+
&kCFTypeDictionaryKeyCallBacks,
|
130
|
+
&kCFTypeDictionaryValueCallBacks);
|
131
|
+
rb_hash_foreach(element, dictionary_set, (st_data_t)elementRef);
|
132
|
+
} else {
|
133
|
+
// otherwise coerce to string
|
134
|
+
elementRef = str_to_cfstr(element);
|
135
|
+
}
|
136
|
+
CFArrayAppendValue((CFMutableArrayRef)valueRef, elementRef);
|
137
|
+
CFRelease(elementRef);
|
106
138
|
}
|
107
139
|
|
108
140
|
} else {
|
@@ -130,7 +162,19 @@ str_to_url(VALUE path) {
|
|
130
162
|
}
|
131
163
|
|
132
164
|
|
133
|
-
|
165
|
+
/* @overload read_plist(path)
|
166
|
+
*
|
167
|
+
* Reads from the specified path and de-serializes the property list.
|
168
|
+
*
|
169
|
+
* @note This does not yet support all possible types that can exist in a valid property list.
|
170
|
+
*
|
171
|
+
* @note This currently only assumes to be given an Xcode project document.
|
172
|
+
* This means that it only accepts dictionaries, arrays, and strings in
|
173
|
+
* the document.
|
174
|
+
*
|
175
|
+
* @param [String] path The path to the property list file.
|
176
|
+
* @return [Hash] The dictionary contents of the document.
|
177
|
+
*/
|
134
178
|
static VALUE
|
135
179
|
read_plist(VALUE self, VALUE path) {
|
136
180
|
CFPropertyListRef dict;
|
@@ -159,6 +203,20 @@ read_plist(VALUE self, VALUE path) {
|
|
159
203
|
return hash;
|
160
204
|
}
|
161
205
|
|
206
|
+
/* @overload write_plist(hash, path)
|
207
|
+
*
|
208
|
+
* Writes the serialized contents of a property list to the specified path.
|
209
|
+
*
|
210
|
+
* @note This does not yet support all possible types that can exist in a valid property list.
|
211
|
+
*
|
212
|
+
* @note This currently only assumes to be given an Xcode project document.
|
213
|
+
* This means that it only accepts dictionaries, arrays, and strings in
|
214
|
+
* the document.
|
215
|
+
*
|
216
|
+
* @param [Hash] hash The property list to serialize.
|
217
|
+
* @param [String] path The path to the property list file.
|
218
|
+
* @return [true, false] Wether or not saving was successful.
|
219
|
+
*/
|
162
220
|
static VALUE
|
163
221
|
write_plist(VALUE self, VALUE hash, VALUE path) {
|
164
222
|
VALUE h = rb_check_convert_type(hash, T_HASH, "Hash", "to_hash");
|
@@ -171,7 +229,7 @@ write_plist(VALUE self, VALUE hash, VALUE path) {
|
|
171
229
|
&kCFTypeDictionaryKeyCallBacks,
|
172
230
|
&kCFTypeDictionaryValueCallBacks);
|
173
231
|
|
174
|
-
|
232
|
+
rb_hash_foreach(h, dictionary_set, (st_data_t)dict);
|
175
233
|
|
176
234
|
CFURLRef fileURL = str_to_url(path);
|
177
235
|
CFWriteStreamRef stream = CFWriteStreamCreateWithFile(NULL, fileURL);
|
data/lib/xcodeproj.rb
CHANGED
data/lib/xcodeproj/config.rb
CHANGED
@@ -1,31 +1,163 @@
|
|
1
1
|
module Xcodeproj
|
2
|
+
# This class holds the data for a Xcode build settings file (xcconfig) and
|
3
|
+
# serializes it.
|
2
4
|
class Config
|
3
|
-
|
5
|
+
# Returns a new instance of Config
|
6
|
+
#
|
7
|
+
# @param [Hash, File, String] xcconfig_hash_or_file Initial data.
|
8
|
+
require 'set'
|
9
|
+
|
10
|
+
attr_accessor :attributes, :frameworks ,:libraries
|
11
|
+
|
12
|
+
def initialize(xcconfig_hash_or_file = {})
|
4
13
|
@attributes = {}
|
5
|
-
|
14
|
+
@includes = []
|
15
|
+
@frameworks, @libraries = Set.new, Set.new
|
16
|
+
merge!(extract_hash(xcconfig_hash_or_file))
|
6
17
|
end
|
7
18
|
|
19
|
+
# @return [Hash] The internal data.
|
8
20
|
def to_hash
|
9
|
-
@attributes
|
21
|
+
hash = @attributes.dup
|
22
|
+
flags = hash['OTHER_LDFLAGS'] || ''
|
23
|
+
flags = flags.dup.strip
|
24
|
+
flags << libraries.to_a.sort.reduce('') {| memo, l | memo << " -l#{l}" }
|
25
|
+
flags << frameworks.to_a.sort.reduce('') {| memo, f | memo << " -framework #{f}" }
|
26
|
+
hash['OTHER_LDFLAGS'] = flags.strip
|
27
|
+
hash.delete('OTHER_LDFLAGS') if flags.strip.empty?
|
28
|
+
hash
|
10
29
|
end
|
11
30
|
|
31
|
+
def ==(other)
|
32
|
+
other.respond_to?(:to_hash) && other.to_hash == self.to_hash
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Array] Config's include file list
|
36
|
+
# @example
|
37
|
+
#
|
38
|
+
# Consider following xcconfig file:
|
39
|
+
#
|
40
|
+
# #include "SomeConfig"
|
41
|
+
# Key1 = Value1
|
42
|
+
# Key2 = Value2
|
43
|
+
#
|
44
|
+
# config.includes # => [ "SomeConfig" ]
|
45
|
+
def includes
|
46
|
+
@includes
|
47
|
+
end
|
48
|
+
|
49
|
+
# Merges the given xcconfig hash or Config into the internal data.
|
50
|
+
#
|
51
|
+
# If a key in the given hash already exists, in the internal data, then its
|
52
|
+
# value is appended to the value in the internal data.
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
#
|
56
|
+
# config = Config.new('PODS_ROOT' => '"$(SRCROOT)/Pods"', 'OTHER_LDFLAGS' => '-lxml2')
|
57
|
+
# config.merge!('OTHER_LDFLAGS' => '-lz', 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers"')
|
58
|
+
# config.to_hash # => { 'PODS_ROOT' => '"$(SRCROOT)/Pods"', 'OTHER_LDFLAGS' => '-lxml2 -lz', 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers"' }
|
59
|
+
#
|
60
|
+
# @param [Hash, Config] xcconfig The data to merge into the internal data.
|
12
61
|
def merge!(xcconfig)
|
13
|
-
xcconfig.
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
62
|
+
if xcconfig.is_a? Config
|
63
|
+
@attributes.merge!(xcconfig.attributes) { |key, v1, v2| "#{v1} #{v2}" }
|
64
|
+
@libraries.merge xcconfig.libraries
|
65
|
+
@frameworks.merge xcconfig.frameworks
|
66
|
+
else
|
67
|
+
@attributes.merge!(xcconfig.to_hash) { |key, v1, v2| "#{v1} #{v2}" }
|
68
|
+
# Parse frameworks and libraries. Then remove the from the linker flags
|
69
|
+
flags = @attributes['OTHER_LDFLAGS']
|
70
|
+
return unless flags
|
71
|
+
|
72
|
+
frameworks = flags.scan(/-framework\s+([^\s]+)/).map { |m| m[0] }
|
73
|
+
libraries = flags.scan(/-l ?([^\s]+)/).map { |m| m[0] }
|
74
|
+
@frameworks.merge frameworks
|
75
|
+
@libraries.merge libraries
|
76
|
+
|
77
|
+
new_flags = flags.dup
|
78
|
+
frameworks.each {|f| new_flags.gsub!("-framework #{f}", "") }
|
79
|
+
libraries.each {|l| new_flags.gsub!("-l#{l}", ""); new_flags.gsub!("-l #{l}", "") }
|
80
|
+
@attributes['OTHER_LDFLAGS'] = new_flags.gsub("\w*", ' ').strip
|
19
81
|
end
|
20
82
|
end
|
21
83
|
alias_method :<<, :merge!
|
22
84
|
|
85
|
+
def merge(config)
|
86
|
+
self.dup.tap { |x|x.merge!(config) }
|
87
|
+
end
|
88
|
+
|
89
|
+
def dup
|
90
|
+
Xcodeproj::Config.new(self.to_hash.dup)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Serializes the internal data in the xcconfig format.
|
94
|
+
#
|
95
|
+
# @example
|
96
|
+
#
|
97
|
+
# config = Config.new('PODS_ROOT' => '"$(SRCROOT)/Pods"', 'OTHER_LDFLAGS' => '-lxml2')
|
98
|
+
# config.to_s # => "PODS_ROOT = \"$(SRCROOT)/Pods\"\nOTHER_LDFLAGS = -lxml2"
|
99
|
+
#
|
100
|
+
# @return [String] The serialized internal data.
|
23
101
|
def to_s
|
24
|
-
|
102
|
+
to_hash.map { |key, value| "#{key} = #{value}" }.join("\n")
|
103
|
+
end
|
104
|
+
|
105
|
+
def inspect
|
106
|
+
to_hash.inspect
|
25
107
|
end
|
26
108
|
|
109
|
+
# Writes the serialized representation of the internal data to the given
|
110
|
+
# path.
|
111
|
+
#
|
112
|
+
# @param [Pathname] pathname The file that the data should be written to.
|
27
113
|
def save_as(pathname)
|
28
114
|
pathname.open('w') { |file| file << to_s }
|
29
115
|
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def extract_hash(argument)
|
120
|
+
if argument.respond_to? :read
|
121
|
+
hash_from_file_content(argument.read)
|
122
|
+
elsif File.readable? argument.to_s
|
123
|
+
hash_from_file_content(File.read(argument))
|
124
|
+
else
|
125
|
+
argument
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def hash_from_file_content(raw_string)
|
130
|
+
hash = {}
|
131
|
+
raw_string.split("\n").each do |line|
|
132
|
+
uncommented_line = strip_comment(line)
|
133
|
+
if include = extract_include(uncommented_line)
|
134
|
+
@includes.push include
|
135
|
+
else
|
136
|
+
key, value = extract_key_value(uncommented_line)
|
137
|
+
hash[key] = value if key
|
138
|
+
end
|
139
|
+
end
|
140
|
+
hash
|
141
|
+
end
|
142
|
+
|
143
|
+
def strip_comment(line)
|
144
|
+
line.partition('//').first
|
145
|
+
end
|
146
|
+
|
147
|
+
def extract_include(line)
|
148
|
+
regexp = /#include\s*"(.+)"/
|
149
|
+
match = line.match(regexp)
|
150
|
+
match[1] if match
|
151
|
+
end
|
152
|
+
|
153
|
+
def extract_key_value(line)
|
154
|
+
key, value = line.split('=', 2)
|
155
|
+
if key && value
|
156
|
+
[key.strip, value.strip]
|
157
|
+
else
|
158
|
+
[]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
30
162
|
end
|
31
163
|
end
|
data/lib/xcodeproj/inflector.rb
CHANGED
@@ -189,7 +189,7 @@ module Xcodeproj
|
|
189
189
|
if first_letter_in_uppercase
|
190
190
|
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
191
191
|
else
|
192
|
-
lower_case_and_underscored_word.
|
192
|
+
lower_case_and_underscored_word[0,1].downcase + camelize(lower_case_and_underscored_word)[1..-1]
|
193
193
|
end
|
194
194
|
end
|
195
195
|
|
data/lib/xcodeproj/project.rb
CHANGED
@@ -1,555 +1,37 @@
|
|
1
1
|
require 'fileutils'
|
2
|
-
require '
|
2
|
+
require 'pathname'
|
3
3
|
require 'xcodeproj/xcodeproj_ext'
|
4
4
|
|
5
|
+
require 'xcodeproj/project/object'
|
6
|
+
|
5
7
|
module Xcodeproj
|
8
|
+
# This class represents a Xcode project document.
|
9
|
+
#
|
10
|
+
# It can be used to manipulate existing documents or even create new ones
|
11
|
+
# from scratch.
|
12
|
+
#
|
13
|
+
# The Project API returns instances of AbstractPBXObject which wrap the objects
|
14
|
+
# described in the Xcode project document.
|
6
15
|
class Project
|
7
|
-
|
8
|
-
class
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
attr_reader :name, :options
|
14
|
-
|
15
|
-
def klass
|
16
|
-
@options[:class] ||= begin
|
17
|
-
name = "PBX#{@name.classify}"
|
18
|
-
name = "XC#{@name.classify}" unless Project.const_defined?(name)
|
19
|
-
Project.const_get(name)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def inverse
|
24
|
-
klass.reflection(@options[:inverse_of])
|
25
|
-
end
|
26
|
-
|
27
|
-
def inverse?
|
28
|
-
!!@options[:inverse_of]
|
29
|
-
end
|
30
|
-
|
31
|
-
def singular_name
|
32
|
-
@options[:singular_name] || @name.singularize
|
33
|
-
end
|
34
|
-
|
35
|
-
def singular_getter
|
36
|
-
singular_name
|
37
|
-
end
|
38
|
-
|
39
|
-
def singular_setter
|
40
|
-
"#{singular_name}="
|
41
|
-
end
|
42
|
-
|
43
|
-
def plural_name
|
44
|
-
@name.pluralize
|
45
|
-
end
|
46
|
-
|
47
|
-
def plural_getter
|
48
|
-
plural_name
|
49
|
-
end
|
50
|
-
|
51
|
-
def plural_setter
|
52
|
-
"#{plural_name}="
|
53
|
-
end
|
54
|
-
|
55
|
-
def uuid_attribute
|
56
|
-
@options[:uuid] || @name
|
57
|
-
end
|
58
|
-
|
59
|
-
def uuid_method_name
|
60
|
-
(@options[:uuid] || @options[:uuids] || "#{singular_name}Reference").to_s.singularize
|
61
|
-
end
|
62
|
-
|
63
|
-
def uuid_getter
|
64
|
-
uuid_method_name
|
65
|
-
end
|
66
|
-
|
67
|
-
def uuid_setter
|
68
|
-
"#{uuid_method_name}="
|
69
|
-
end
|
70
|
-
|
71
|
-
def uuids_method_name
|
72
|
-
uuid_method_name.pluralize
|
73
|
-
end
|
74
|
-
|
75
|
-
def uuids_getter
|
76
|
-
uuids_method_name
|
77
|
-
end
|
78
|
-
|
79
|
-
def uuids_setter
|
80
|
-
"#{uuids_method_name}="
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def self.reflections
|
85
|
-
@reflections ||= []
|
86
|
-
end
|
87
|
-
|
88
|
-
def self.create_reflection(name, options)
|
89
|
-
(reflections << AssociationReflection.new(name, options)).last
|
90
|
-
end
|
91
|
-
|
92
|
-
def self.reflection(name)
|
93
|
-
reflections.find { |r| r.name.to_s == name.to_s }
|
94
|
-
end
|
95
|
-
|
96
|
-
def self.attribute(attribute_name, accessor_name = nil)
|
97
|
-
attribute_name = attribute_name.to_s
|
98
|
-
name = (accessor_name || attribute_name).to_s
|
99
|
-
define_method(name) { @attributes[attribute_name] }
|
100
|
-
define_method("#{name}=") { |value| @attributes[attribute_name] = value }
|
101
|
-
end
|
102
|
-
|
103
|
-
def self.attributes(*names)
|
104
|
-
names.each { |name| attribute(name) }
|
105
|
-
end
|
106
|
-
|
107
|
-
def self.has_many(plural_attr_name, options = {}, &block)
|
108
|
-
reflection = create_reflection(plural_attr_name, options)
|
109
|
-
if reflection.inverse?
|
110
|
-
define_method(reflection.name) do
|
111
|
-
scoped = @project.objects.select_by_class(reflection.klass).select do |object|
|
112
|
-
object.send(reflection.inverse.uuid_getter) == self.uuid
|
113
|
-
end
|
114
|
-
PBXObjectList.new(reflection.klass, @project, scoped) do |object|
|
115
|
-
object.send(reflection.inverse.uuid_setter, self.uuid)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
else
|
119
|
-
attribute(reflection.name, reflection.uuids_getter)
|
120
|
-
define_method(reflection.name) do
|
121
|
-
uuids = send(reflection.uuids_getter)
|
122
|
-
if block
|
123
|
-
# Evaluate the block, which was specified at the class level, in
|
124
|
-
# the instance’s context.
|
125
|
-
list_by_class(uuids, reflection.klass) do |object|
|
126
|
-
instance_exec(object, &block)
|
127
|
-
end
|
128
|
-
else
|
129
|
-
list_by_class(uuids, reflection.klass)
|
130
|
-
end
|
131
|
-
end
|
132
|
-
define_method(reflection.plural_setter) do |objects|
|
133
|
-
send(reflection.uuids_setter, objects.map(&:uuid))
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
def self.has_one(singular_attr_name, options = {})
|
139
|
-
reflection = create_reflection(singular_attr_name, options)
|
140
|
-
if reflection.inverse?
|
141
|
-
define_method(reflection.name) do
|
142
|
-
# Loop over all objects of the class and find the one that includes
|
143
|
-
# this object in the specified uuid list.
|
144
|
-
@project.objects.select_by_class(reflection.klass).find do |object|
|
145
|
-
object.send(reflection.inverse.uuids_getter).include?(self.uuid)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
define_method(reflection.singular_setter) do |object|
|
149
|
-
# Remove this object from the uuid list of the target
|
150
|
-
# that this object was associated to.
|
151
|
-
if previous = send(reflection.name)
|
152
|
-
previous.send(reflection.inverse.uuids_getter).delete(self.uuid)
|
153
|
-
end
|
154
|
-
# Now assign this object to the new object
|
155
|
-
object.send(reflection.inverse.uuids_getter) << self.uuid if object
|
156
|
-
end
|
157
|
-
else
|
158
|
-
attribute(reflection.uuid_attribute, reflection.uuid_getter)
|
159
|
-
define_method(reflection.name) do
|
160
|
-
@project.objects[send(reflection.uuid_getter)]
|
161
|
-
end
|
162
|
-
define_method(reflection.singular_setter) do |object|
|
163
|
-
send(reflection.uuid_setter, object.uuid)
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
def self.isa
|
169
|
-
@isa ||= name.split('::').last
|
170
|
-
end
|
171
|
-
|
172
|
-
attr_reader :uuid, :attributes
|
173
|
-
attributes :isa, :name
|
174
|
-
|
175
|
-
def initialize(project, uuid, attributes)
|
176
|
-
@project, @attributes = project, attributes
|
177
|
-
unless uuid
|
178
|
-
# Add new objects to the main hash with a unique UUID
|
179
|
-
begin; uuid = generate_uuid; end while @project.objects_hash.has_key?(uuid)
|
180
|
-
@project.objects_hash[uuid] = @attributes
|
181
|
-
end
|
182
|
-
@uuid = uuid
|
183
|
-
self.isa ||= self.class.isa
|
184
|
-
end
|
185
|
-
|
186
|
-
def ==(other)
|
187
|
-
other.is_a?(PBXObject) && self.uuid == other.uuid
|
188
|
-
end
|
189
|
-
|
190
|
-
def inspect
|
191
|
-
"#<#{isa} UUID: `#{uuid}', name: `#{name}'>"
|
192
|
-
end
|
193
|
-
|
194
|
-
private
|
195
|
-
|
196
|
-
def generate_uuid
|
197
|
-
Xcodeproj.generate_uuid
|
198
|
-
end
|
199
|
-
|
200
|
-
def list_by_class(uuids, klass, scoped = nil, &block)
|
201
|
-
unless scoped
|
202
|
-
scoped = uuids.map { |uuid| @project.objects[uuid] }.select { |o| o.is_a?(klass) }
|
203
|
-
end
|
204
|
-
if block
|
205
|
-
PBXObjectList.new(klass, @project, scoped, &block)
|
206
|
-
else
|
207
|
-
PBXObjectList.new(klass, @project, scoped) do |object|
|
208
|
-
# Add the uuid of a newly created object to the uuids list
|
209
|
-
uuids << object.uuid
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
# Missing constants that begin with either `PBX' or `XC' are assumed to be
|
216
|
-
# valid classes in a Xcode project. A new PBXObject subclass is created
|
217
|
-
# for the constant and returned.
|
218
|
-
def self.const_missing(name)
|
219
|
-
if name.to_s =~ /^(PBX|XC)/
|
220
|
-
klass = Class.new(PBXObject)
|
221
|
-
const_set(name, klass)
|
222
|
-
klass
|
223
|
-
else
|
224
|
-
super
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
class PBXFileReference < PBXObject
|
229
|
-
attributes :path, :sourceTree, :explicitFileType, :lastKnownFileType, :includeInIndex
|
230
|
-
has_many :buildFiles, :inverse_of => :file
|
231
|
-
has_one :group, :inverse_of => :children
|
232
|
-
|
233
|
-
def self.new_static_library(project, productName)
|
234
|
-
new(project, nil, {
|
235
|
-
"path" => "lib#{productName}.a",
|
236
|
-
"includeInIndex" => "0", # no idea what this is
|
237
|
-
"sourceTree" => "BUILT_PRODUCTS_DIR",
|
238
|
-
})
|
239
|
-
end
|
240
|
-
|
241
|
-
def initialize(project, uuid, attributes)
|
242
|
-
is_new = uuid.nil?
|
243
|
-
super
|
244
|
-
self.path = path if path # sets default name
|
245
|
-
self.sourceTree ||= 'SOURCE_ROOT'
|
246
|
-
if is_new
|
247
|
-
@project.main_group.children << self
|
248
|
-
end
|
249
|
-
set_default_file_type!
|
250
|
-
end
|
251
|
-
|
252
|
-
alias_method :_path=, :path=
|
253
|
-
def path=(path)
|
254
|
-
self._path = path
|
255
|
-
self.name ||= pathname.basename.to_s
|
256
|
-
path
|
257
|
-
end
|
258
|
-
|
259
|
-
def pathname
|
260
|
-
Pathname.new(path)
|
261
|
-
end
|
262
|
-
|
263
|
-
def set_default_file_type!
|
264
|
-
return if explicitFileType || lastKnownFileType
|
265
|
-
case path
|
266
|
-
when /\.a$/
|
267
|
-
self.explicitFileType = 'archive.ar'
|
268
|
-
when /\.framework$/
|
269
|
-
self.lastKnownFileType = 'wrapper.framework'
|
270
|
-
when /\.xcconfig$/
|
271
|
-
self.lastKnownFileType = 'text.xcconfig'
|
272
|
-
end
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
class PBXGroup < PBXObject
|
277
|
-
attributes :sourceTree
|
278
|
-
|
279
|
-
has_many :children, :class => PBXFileReference do |object|
|
280
|
-
if object.is_a?(Xcodeproj::Project::PBXFileReference)
|
281
|
-
# Associating the file to this group through the inverse
|
282
|
-
# association will also remove it from the group it was in.
|
283
|
-
object.group = self
|
284
|
-
else
|
285
|
-
# TODO What objects can actually be in a group and don't they
|
286
|
-
# all need the above treatment.
|
287
|
-
childReferences << object.uuid
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
|
-
def initialize(*)
|
292
|
-
super
|
293
|
-
self.sourceTree ||= '<group>'
|
294
|
-
self.childReferences ||= []
|
295
|
-
end
|
296
|
-
|
297
|
-
def files
|
298
|
-
list_by_class(childReferences, Xcodeproj::Project::PBXFileReference) do |file|
|
299
|
-
file.group = self
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
def source_files
|
304
|
-
files = self.files.reject { |file| file.buildFiles.empty? }
|
305
|
-
list_by_class(childReferences, Xcodeproj::Project::PBXFileReference, files) do |file|
|
306
|
-
file.group = self
|
307
|
-
end
|
308
|
-
end
|
309
|
-
|
310
|
-
def groups
|
311
|
-
list_by_class(childReferences, Xcodeproj::Project::PBXGroup)
|
312
|
-
end
|
313
|
-
|
314
|
-
def <<(child)
|
315
|
-
children << child
|
316
|
-
end
|
317
|
-
end
|
318
|
-
|
319
|
-
class PBXBuildFile < PBXObject
|
320
|
-
attributes :settings
|
321
|
-
has_one :file, :uuid => :fileRef
|
322
|
-
end
|
323
|
-
|
324
|
-
class PBXBuildPhase < PBXObject
|
325
|
-
# TODO rename this to buildFiles and add a files :through => :buildFiles shortcut
|
326
|
-
has_many :files, :class => PBXBuildFile
|
327
|
-
|
328
|
-
attributes :buildActionMask, :runOnlyForDeploymentPostprocessing
|
329
|
-
|
330
|
-
def initialize(*)
|
331
|
-
super
|
332
|
-
self.fileReferences ||= []
|
333
|
-
# These are always the same, no idea what they are.
|
334
|
-
self.buildActionMask ||= "2147483647"
|
335
|
-
self.runOnlyForDeploymentPostprocessing ||= "0"
|
16
|
+
module Object
|
17
|
+
class PBXProject < AbstractPBXObject
|
18
|
+
has_many :targets, :class => PBXNativeTarget
|
19
|
+
has_one :products_group, :uuid => :product_ref_group, :class => PBXGroup
|
20
|
+
has_one :build_configuration_list, :class => XCConfigurationList
|
336
21
|
end
|
337
22
|
end
|
338
23
|
|
339
|
-
|
340
|
-
attributes :dstPath, :dstSubfolderSpec
|
341
|
-
|
342
|
-
def initialize(*)
|
343
|
-
super
|
344
|
-
self.dstSubfolderSpec ||= "16"
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
class PBXSourcesBuildPhase < PBXBuildPhase; end
|
349
|
-
class PBXFrameworksBuildPhase < PBXBuildPhase; end
|
350
|
-
class PBXShellScriptBuildPhase < PBXBuildPhase
|
351
|
-
attribute :shellScript
|
352
|
-
end
|
353
|
-
|
354
|
-
class PBXNativeTarget < PBXObject
|
355
|
-
STATIC_LIBRARY = 'com.apple.product-type.library.static'
|
356
|
-
|
357
|
-
attributes :productName, :productType
|
358
|
-
|
359
|
-
has_many :buildPhases
|
360
|
-
has_many :dependencies # TODO :class => ?
|
361
|
-
has_many :buildRules # TODO :class => ?
|
362
|
-
has_one :buildConfigurationList
|
363
|
-
has_one :product, :uuid => :productReference
|
364
|
-
|
365
|
-
def self.new_static_library(project, productName)
|
366
|
-
# TODO should probably switch the uuid and attributes argument
|
367
|
-
target = new(project, nil, 'productType' => STATIC_LIBRARY, 'productName' => productName)
|
368
|
-
target.product = project.files.new_static_library(productName)
|
369
|
-
products = project.groups.find { |g| g.name == 'Products' }
|
370
|
-
products ||= project.groups.new({ 'name' => 'Products'})
|
371
|
-
products.children << target.product
|
372
|
-
target.buildPhases.add(PBXSourcesBuildPhase)
|
373
|
-
|
374
|
-
buildPhase = target.buildPhases.add(PBXFrameworksBuildPhase)
|
375
|
-
frameworks = project.groups.find { |g| g.name == 'Frameworks' }
|
376
|
-
frameworks ||= project.groups.new({ 'name' => 'Frameworks'})
|
377
|
-
frameworks.files.each do |framework|
|
378
|
-
buildPhase.files << framework.buildFiles.new
|
379
|
-
end
|
380
|
-
|
381
|
-
target.buildPhases.add(PBXCopyFilesBuildPhase, 'dstPath' => '$(PRODUCT_NAME)')
|
382
|
-
target
|
383
|
-
end
|
384
|
-
|
385
|
-
# You need to specify a product. For a static library you can use
|
386
|
-
# PBXFileReference.new_static_library.
|
387
|
-
def initialize(project, *)
|
388
|
-
super
|
389
|
-
self.name ||= productName
|
390
|
-
self.buildRuleReferences ||= []
|
391
|
-
self.dependencyReferences ||= []
|
392
|
-
self.buildPhaseReferences ||= []
|
393
|
-
|
394
|
-
unless buildConfigurationList
|
395
|
-
self.buildConfigurationList = project.objects.add(XCConfigurationList)
|
396
|
-
# TODO or should this happen in buildConfigurationList?
|
397
|
-
buildConfigurationList.buildConfigurations.new('name' => 'Debug')
|
398
|
-
buildConfigurationList.buildConfigurations.new('name' => 'Release')
|
399
|
-
end
|
400
|
-
end
|
401
|
-
|
402
|
-
alias_method :_product=, :product=
|
403
|
-
def product=(product)
|
404
|
-
self._product = product
|
405
|
-
product.group = @project.products
|
406
|
-
end
|
407
|
-
|
408
|
-
def buildConfigurations
|
409
|
-
buildConfigurationList.buildConfigurations
|
410
|
-
end
|
411
|
-
|
412
|
-
def source_build_phases
|
413
|
-
buildPhases.select_by_class(PBXSourcesBuildPhase)
|
414
|
-
end
|
415
|
-
|
416
|
-
def copy_files_build_phases
|
417
|
-
buildPhases.select_by_class(PBXCopyFilesBuildPhase)
|
418
|
-
end
|
419
|
-
|
420
|
-
def frameworks_build_phases
|
421
|
-
buildPhases.select_by_class(PBXFrameworksBuildPhase)
|
422
|
-
end
|
423
|
-
|
424
|
-
# Finds an existing file reference or creates a new one.
|
425
|
-
def add_source_file(path, copy_header_phase = nil, compiler_flags = nil)
|
426
|
-
file = @project.files.find { |file| file.path == path.to_s } || @project.files.new('path' => path.to_s)
|
427
|
-
buildFile = file.buildFiles.new
|
428
|
-
if path.extname == '.h'
|
429
|
-
buildFile.settings = { 'ATTRIBUTES' => ["Public"] }
|
430
|
-
# Working around a bug in Xcode 4.2 betas, remove this once the Xcode bug is fixed:
|
431
|
-
# https://github.com/alloy/cocoapods/issues/13
|
432
|
-
#phase = copy_header_phase || headers_build_phases.first
|
433
|
-
phase = copy_header_phase || copy_files_build_phases.first
|
434
|
-
phase.files << buildFile
|
435
|
-
else
|
436
|
-
buildFile.settings = { 'COMPILER_FLAGS' => compiler_flags } if compiler_flags
|
437
|
-
source_build_phases.first.files << buildFile
|
438
|
-
end
|
439
|
-
file
|
440
|
-
end
|
441
|
-
end
|
442
|
-
|
443
|
-
class XCBuildConfiguration < PBXObject
|
444
|
-
attribute :buildSettings
|
445
|
-
has_one :baseConfiguration, :uuid => :baseConfigurationReference
|
446
|
-
|
447
|
-
def initialize(*)
|
448
|
-
super
|
449
|
-
# TODO These are from an iOS static library, need to check if it works for any product type
|
450
|
-
self.buildSettings = {
|
451
|
-
'DSTROOT' => '/tmp/xcodeproj.dst',
|
452
|
-
'GCC_PRECOMPILE_PREFIX_HEADER' => 'YES',
|
453
|
-
'GCC_VERSION' => 'com.apple.compilers.llvm.clang.1_0',
|
454
|
-
'PRODUCT_NAME' => '$(TARGET_NAME)',
|
455
|
-
'SKIP_INSTALL' => 'YES',
|
456
|
-
}.merge(buildSettings || {})
|
457
|
-
end
|
458
|
-
end
|
459
|
-
|
460
|
-
class XCConfigurationList < PBXObject
|
461
|
-
has_many :buildConfigurations
|
462
|
-
|
463
|
-
def initialize(*)
|
464
|
-
super
|
465
|
-
self.buildConfigurationReferences ||= []
|
466
|
-
end
|
467
|
-
end
|
468
|
-
|
469
|
-
class PBXProject < PBXObject
|
470
|
-
has_many :targets, :class => PBXNativeTarget
|
471
|
-
has_one :products, :singular_name => :products, :uuid => :productRefGroup, :class => PBXGroup
|
472
|
-
end
|
473
|
-
|
474
|
-
class PBXObjectList
|
475
|
-
include Enumerable
|
476
|
-
|
477
|
-
def initialize(represented_class, project, scoped, &new_object_callback)
|
478
|
-
@represented_class = represented_class
|
479
|
-
@project = project
|
480
|
-
@scoped_hash = scoped.is_a?(Array) ? scoped.inject({}) { |h, o| h[o.uuid] = o.attributes; h } : scoped
|
481
|
-
@callback = new_object_callback
|
482
|
-
end
|
483
|
-
|
484
|
-
def empty?
|
485
|
-
@scoped_hash.empty?
|
486
|
-
end
|
487
|
-
|
488
|
-
def [](uuid)
|
489
|
-
if hash = @scoped_hash[uuid]
|
490
|
-
Project.const_get(hash['isa']).new(@project, uuid, hash)
|
491
|
-
end
|
492
|
-
end
|
493
|
-
|
494
|
-
def add(klass, hash = {})
|
495
|
-
object = klass.new(@project, nil, hash)
|
496
|
-
@callback.call(object) if @callback
|
497
|
-
object
|
498
|
-
end
|
499
|
-
|
500
|
-
def new(hash = {})
|
501
|
-
add(@represented_class, hash)
|
502
|
-
end
|
503
|
-
|
504
|
-
def <<(object)
|
505
|
-
@callback.call(object) if @callback
|
506
|
-
end
|
507
|
-
|
508
|
-
def each
|
509
|
-
@scoped_hash.keys.each do |uuid|
|
510
|
-
yield self[uuid]
|
511
|
-
end
|
512
|
-
end
|
513
|
-
|
514
|
-
def ==(other)
|
515
|
-
self.to_a == other.to_a
|
516
|
-
end
|
517
|
-
|
518
|
-
def first
|
519
|
-
to_a.first
|
520
|
-
end
|
521
|
-
|
522
|
-
def last
|
523
|
-
to_a.last
|
524
|
-
end
|
525
|
-
|
526
|
-
def inspect
|
527
|
-
"<PBXObjectList: #{map(&:inspect)}>"
|
528
|
-
end
|
529
|
-
|
530
|
-
# Only makes sense on lists that contain mixed classes.
|
531
|
-
def select_by_class(klass)
|
532
|
-
scoped = Hash[*@scoped_hash.select { |_, attr| attr['isa'] == klass.isa }.flatten]
|
533
|
-
PBXObjectList.new(klass, @project, scoped) do |object|
|
534
|
-
# Objects added to the subselection should still use the same
|
535
|
-
# callback as this list.
|
536
|
-
self << object
|
537
|
-
end
|
538
|
-
end
|
539
|
-
|
540
|
-
def method_missing(name, *args, &block)
|
541
|
-
if @represented_class.respond_to?(name)
|
542
|
-
object = @represented_class.send(name, @project, *args)
|
543
|
-
# The callbacks are only for PBXObject instances instantiated
|
544
|
-
# from the class method that we forwarded the message to.
|
545
|
-
@callback.call(object) if object.is_a?(PBXObject)
|
546
|
-
object
|
547
|
-
else
|
548
|
-
super
|
549
|
-
end
|
550
|
-
end
|
551
|
-
end
|
24
|
+
include Object
|
552
25
|
|
26
|
+
# Opens a Xcode project document if a path to one is given, otherwise a new
|
27
|
+
# Project is created.
|
28
|
+
#
|
29
|
+
# @param [Pathname, String] xcodeproj The path to the Xcode project
|
30
|
+
# document (xcodeproj).
|
31
|
+
#
|
32
|
+
# @return [Project] A new Project instance or one with
|
33
|
+
# the data of an existing Xcode
|
34
|
+
# document.
|
553
35
|
def initialize(xcodeproj = nil)
|
554
36
|
if xcodeproj
|
555
37
|
file = File.join(xcodeproj, 'project.pbxproj')
|
@@ -561,86 +43,180 @@ module Xcodeproj
|
|
561
43
|
'objectVersion' => '46',
|
562
44
|
'objects' => {}
|
563
45
|
}
|
564
|
-
|
46
|
+
main_group = groups.new
|
47
|
+
self.root_object = objects.add(PBXProject, {
|
565
48
|
'attributes' => { 'LastUpgradeCheck' => '0420' },
|
566
49
|
'compatibilityVersion' => 'Xcode 3.2',
|
567
50
|
'developmentRegion' => 'English',
|
568
51
|
'hasScannedForEncodings' => '0',
|
569
52
|
'knownRegions' => ['en'],
|
570
|
-
'mainGroup' =>
|
53
|
+
'mainGroup' => main_group.uuid,
|
54
|
+
'productRefGroup' => main_group.groups.new('name' => 'Products').uuid,
|
571
55
|
'projectDirPath' => '',
|
572
56
|
'projectRoot' => '',
|
573
57
|
'targets' => []
|
574
58
|
})
|
59
|
+
|
60
|
+
config_list = objects.add(XCConfigurationList)
|
61
|
+
config_list.default_configuration_name = 'Release'
|
62
|
+
config_list.default_configuration_is_visible = '0'
|
63
|
+
config_list.build_configurations.new('name' => 'Debug')
|
64
|
+
config_list.build_configurations.new('name' => 'Release')
|
65
|
+
self.root_object.build_configuration_list = config_list
|
66
|
+
|
67
|
+
# TODO make this work
|
68
|
+
#self.root_object.product_reference = groups.new('name' => 'Products').uuid
|
575
69
|
end
|
576
70
|
end
|
577
71
|
|
72
|
+
# @return [Hash] The internal data.
|
578
73
|
def to_hash
|
579
74
|
@plist
|
580
75
|
end
|
581
76
|
|
77
|
+
def ==(other)
|
78
|
+
other.respond_to?(:to_hash) && @plist == other.to_hash
|
79
|
+
end
|
80
|
+
|
81
|
+
# This gives access to the objects part of the internal data hash. It is,
|
82
|
+
# however, **not** recommended to use this to add a hash for an object, for
|
83
|
+
# that see `add_object_hash`.
|
84
|
+
#
|
85
|
+
# @return [Hash] The `objects` part of the internal data.
|
582
86
|
def objects_hash
|
583
87
|
@plist['objects']
|
584
88
|
end
|
585
89
|
|
586
|
-
|
587
|
-
|
90
|
+
# This is the preferred way to add an object attributes hash to the objects
|
91
|
+
# hash, as it validates the data before inserting it.
|
92
|
+
#
|
93
|
+
# @param [String] uuid The UUID of the object.
|
94
|
+
# @param [Hash] attributes The attributes of the object.
|
95
|
+
#
|
96
|
+
# @raise [ArgumentError] Raised if the value of the `isa` key is equal
|
97
|
+
# to `AbstractPBXObject`.
|
98
|
+
#
|
99
|
+
# @todo Ideally we would do more validation here, but I don't think we know
|
100
|
+
# of all classes that can exist yet.
|
101
|
+
def add_object_hash(uuid, attributes)
|
102
|
+
if attributes['isa'] !~ /^(PBX|XC)/
|
103
|
+
raise ArgumentError, "Attempted to insert a `#{attributes['isa']}' instance into the objects hash, which is not allowed."
|
104
|
+
end
|
105
|
+
objects_hash[uuid] = attributes
|
588
106
|
end
|
589
107
|
|
108
|
+
# @return [PBXProject] The root object of the project.
|
590
109
|
def root_object
|
591
110
|
objects[@plist['rootObject']]
|
592
111
|
end
|
593
112
|
|
113
|
+
# @param [PBXProject] object The object to assign as the root object.
|
594
114
|
def root_object=(object)
|
595
115
|
@plist['rootObject'] = object.uuid
|
596
116
|
end
|
597
117
|
|
118
|
+
# @return [PBXObjectList<AbstractPBXObject>] A list of all the objects in the
|
119
|
+
# project.
|
120
|
+
def objects
|
121
|
+
PBXObjectList.new(AbstractPBXObject, self) do |list|
|
122
|
+
list.let(:uuid_scope) { objects_hash.keys }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# @return [PBXObjectList<PBXGroup>] A list of all the groups in the
|
127
|
+
# project.
|
598
128
|
def groups
|
599
|
-
objects.
|
129
|
+
objects.list_by_class(PBXGroup)
|
600
130
|
end
|
601
|
-
|
131
|
+
|
132
|
+
# Tries to find a group with the given name.
|
133
|
+
#
|
134
|
+
# @param [String] name The name of the group to find.
|
135
|
+
# @return [PBXGroup, nil] The PBXgroup, if found.
|
136
|
+
def group(name)
|
137
|
+
groups.object_named(name)
|
138
|
+
end
|
139
|
+
|
140
|
+
# @return [PBXGroup] The main top-level group.
|
602
141
|
def main_group
|
603
142
|
objects[root_object.attributes['mainGroup']]
|
604
143
|
end
|
605
144
|
|
145
|
+
# @return [PBXObjectList<PBXFileReference>] A list of all the files in the
|
146
|
+
# project.
|
606
147
|
def files
|
607
|
-
objects.
|
608
|
-
end
|
609
|
-
|
148
|
+
objects.list_by_class(PBXFileReference)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Adds a file reference for a system framework to the project.
|
152
|
+
#
|
153
|
+
# The file reference can then be added to the buildFiles of a
|
154
|
+
# PBXFrameworksBuildPhase.
|
155
|
+
#
|
156
|
+
# @example
|
157
|
+
#
|
158
|
+
# framework = project.add_system_framework('QuartzCore')
|
159
|
+
#
|
160
|
+
# target = project.targets.first
|
161
|
+
# build_phase = target.frameworks_build_phases.first
|
162
|
+
# build_phase.files << framework.buildFiles.new
|
163
|
+
#
|
164
|
+
# @todo Make it possible to do: `build_phase << framework`
|
165
|
+
#
|
166
|
+
# @param [String] name The name of a framework in the SDK System
|
167
|
+
# directory.
|
168
|
+
# @return [PBXFileReference] The file reference object.
|
610
169
|
def add_system_framework(name)
|
611
|
-
|
170
|
+
group = groups.where('name' => 'Frameworks') || groups.new('name' => 'Frameworks')
|
171
|
+
group.files.new({
|
612
172
|
'name' => "#{name}.framework",
|
613
173
|
'path' => "System/Library/Frameworks/#{name}.framework",
|
614
174
|
'sourceTree' => 'SDKROOT'
|
615
175
|
})
|
616
176
|
end
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
'inputPaths' => [],
|
623
|
-
'outputPaths' => [],
|
624
|
-
'shellPath' => '/bin/sh',
|
625
|
-
'shellScript' => script_path
|
626
|
-
})
|
177
|
+
|
178
|
+
# @return [PBXObjectList<XCBuildConfiguration] A list of project wide
|
179
|
+
# build configurations.
|
180
|
+
def build_configurations
|
181
|
+
root_object.build_configuration_list.build_configurations
|
627
182
|
end
|
628
183
|
|
629
|
-
|
630
|
-
|
184
|
+
# @param [String] name The name of a project wide build configuration.
|
185
|
+
#
|
186
|
+
# @return [Hash] The build settings of the project wide build
|
187
|
+
# configuration with the given name.
|
188
|
+
def build_settings(name)
|
189
|
+
root_object.build_configuration_list.build_settings(name)
|
631
190
|
end
|
632
191
|
|
192
|
+
# @todo There are probably other target types too. E.g. an aggregate.
|
193
|
+
#
|
194
|
+
# @return [PBXObjectList<PBXNativeTarget>] A list of all the targets in
|
195
|
+
# the project.
|
633
196
|
def targets
|
634
197
|
# Better to check the project object for targets to ensure they are
|
635
198
|
# actually there so the project will work
|
636
199
|
root_object.targets
|
637
200
|
end
|
638
201
|
|
202
|
+
# @return [PBXGroup] The group which holds the product file references.
|
203
|
+
def products_group
|
204
|
+
root_object.products_group
|
205
|
+
end
|
206
|
+
|
207
|
+
# @return [PBXObjectList<PBXFileReference>] A list of the product file
|
208
|
+
# references.
|
639
209
|
def products
|
640
|
-
|
210
|
+
products_group.children
|
641
211
|
end
|
642
212
|
|
213
|
+
# @private
|
643
214
|
IGNORE_GROUPS = ['Frameworks', 'Products', 'Supporting Files']
|
215
|
+
|
216
|
+
# @todo I think this is here because of easier testing in CocoaPods. Move
|
217
|
+
# this extension to the CocoaPods specs.
|
218
|
+
#
|
219
|
+
# @return [Hash] A list of all the groups and their source files.
|
644
220
|
def source_files
|
645
221
|
source_files = {}
|
646
222
|
groups.each do |group|
|
@@ -650,6 +226,18 @@ module Xcodeproj
|
|
650
226
|
source_files
|
651
227
|
end
|
652
228
|
|
229
|
+
# Serializes the internal data as a property list and stores it on disk at
|
230
|
+
# the given path.
|
231
|
+
#
|
232
|
+
# @example
|
233
|
+
#
|
234
|
+
# project.save_as("path/to/Project.xcodeproj") # => true
|
235
|
+
#
|
236
|
+
# @param [String, Pathname] projpath The path where the data should be
|
237
|
+
# stored.
|
238
|
+
#
|
239
|
+
# @return [true, false] Returns whether or not saving was
|
240
|
+
# successful.
|
653
241
|
def save_as(projpath)
|
654
242
|
projpath = projpath.to_s
|
655
243
|
FileUtils.mkdir_p(projpath)
|