xcodeproj 0.3.5 → 0.4.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/bin/xcodeproj +13 -0
- data/lib/xcodeproj.rb +13 -1
- data/lib/xcodeproj/command.rb +131 -0
- data/lib/xcodeproj/command/project_diff.rb +53 -0
- data/lib/xcodeproj/command/show.rb +35 -0
- data/lib/xcodeproj/command/target_diff.rb +43 -0
- data/lib/xcodeproj/config.rb +66 -38
- data/lib/xcodeproj/constants.rb +146 -0
- data/lib/xcodeproj/helper.rb +28 -0
- data/lib/xcodeproj/project.rb +462 -156
- data/lib/xcodeproj/project/object.rb +251 -138
- data/lib/xcodeproj/project/object/build_configuration.rb +27 -0
- data/lib/xcodeproj/project/object/build_file.rb +26 -0
- data/lib/xcodeproj/project/object/build_phase.rb +132 -61
- data/lib/xcodeproj/project/object/build_rule.rb +46 -0
- data/lib/xcodeproj/project/object/configuration_list.rb +47 -0
- data/lib/xcodeproj/project/object/container_item_proxy.rb +49 -0
- data/lib/xcodeproj/project/object/file_reference.rb +80 -48
- data/lib/xcodeproj/project/object/group.rb +213 -89
- data/lib/xcodeproj/project/object/native_target.rb +171 -114
- data/lib/xcodeproj/project/object/root_object.rb +66 -0
- data/lib/xcodeproj/project/object/target_dependency.rb +23 -0
- data/lib/xcodeproj/project/object_attributes.rb +382 -0
- data/lib/xcodeproj/project/object_list.rb +64 -118
- data/lib/xcodeproj/project/recursive_diff.rb +116 -0
- data/lib/xcodeproj/workspace.rb +56 -2
- metadata +38 -10
- data/lib/xcodeproj/project/association.rb +0 -54
- data/lib/xcodeproj/project/association/has_many.rb +0 -51
- data/lib/xcodeproj/project/association/has_one.rb +0 -39
- data/lib/xcodeproj/project/association/reflection.rb +0 -86
- data/lib/xcodeproj/project/object/configuration.rb +0 -97
@@ -0,0 +1,146 @@
|
|
1
|
+
module Xcodeproj
|
2
|
+
|
3
|
+
# This modules groups all the constants known to Xcodeproj.
|
4
|
+
#
|
5
|
+
module Constants
|
6
|
+
|
7
|
+
# The last known archive version to Xcodeproj.
|
8
|
+
#
|
9
|
+
LAST_KNOWN_ARCHIVE_VERSION = 1
|
10
|
+
|
11
|
+
# The last known object version to Xcodeproj.
|
12
|
+
#
|
13
|
+
LAST_KNOWN_OBJECT_VERSION = 46
|
14
|
+
|
15
|
+
# The all the known ISAs grouped by superclass.
|
16
|
+
#
|
17
|
+
KNOWN_ISAS = {
|
18
|
+
'AbstractObject' => %w[
|
19
|
+
PBXBuildFile
|
20
|
+
AbstractBuildPhase
|
21
|
+
PBXBuildRule
|
22
|
+
XCBuildConfiguration
|
23
|
+
XCConfigurationList
|
24
|
+
PBXContainerItemProxy
|
25
|
+
PBXFileReference
|
26
|
+
PBXGroup
|
27
|
+
PBXNativeTarget
|
28
|
+
PBXProject
|
29
|
+
PBXTargetDependency
|
30
|
+
],
|
31
|
+
|
32
|
+
'AbstractBuildPhase' => %w[
|
33
|
+
PBXCopyFilesBuildPhase
|
34
|
+
PBXResourcesBuildPhase
|
35
|
+
PBXSourcesBuildPhase
|
36
|
+
PBXFrameworksBuildPhase
|
37
|
+
PBXHeadersBuildPhase
|
38
|
+
PBXShellScriptBuildPhase
|
39
|
+
],
|
40
|
+
|
41
|
+
'PBXGroup' => %w[
|
42
|
+
XCVersionGroup
|
43
|
+
PBXVariantGroup
|
44
|
+
]
|
45
|
+
}.freeze
|
46
|
+
|
47
|
+
# The list of the super classes for each ISA.
|
48
|
+
#
|
49
|
+
ISAS_SUPER_CLASSES = %w[ AbstractObject AbstractBuildPhase PBXGroup ]
|
50
|
+
|
51
|
+
# The known file types corresponding to each extension.
|
52
|
+
#
|
53
|
+
FILE_TYPES_BY_EXTENSION = {
|
54
|
+
'a' => 'archive.ar',
|
55
|
+
'application' => 'wrapper.application',
|
56
|
+
'dylib' => 'compiled.mach-o.dylib',
|
57
|
+
'framework' => 'wrapper.framework',
|
58
|
+
'h' => 'sourcecode.c.h',
|
59
|
+
'm' => 'sourcecode.c.objc',
|
60
|
+
'xcconfig' => 'text.xcconfig',
|
61
|
+
'xcdatamodel' => 'wrapper.xcdatamodel',
|
62
|
+
}.freeze
|
63
|
+
|
64
|
+
# The uniform type identifier of various product types.
|
65
|
+
#
|
66
|
+
PRODUCT_TYPE_UTI = {
|
67
|
+
:application => 'com.apple.product-type.application',
|
68
|
+
:dynamic_library => 'com.apple.product-type.library.dynamic',
|
69
|
+
:static_library => 'com.apple.product-type.library.static',
|
70
|
+
}.freeze
|
71
|
+
|
72
|
+
# The common build settings grouped by platform, and build configuration
|
73
|
+
# name.
|
74
|
+
#
|
75
|
+
COMMON_BUILD_SETTINGS = {
|
76
|
+
:all => {
|
77
|
+
'GCC_VERSION' => 'com.apple.compilers.llvm.clang.1_0',
|
78
|
+
'GCC_PRECOMPILE_PREFIX_HEADER' => 'YES',
|
79
|
+
'PRODUCT_NAME' => '$(TARGET_NAME)',
|
80
|
+
'SKIP_INSTALL' => 'YES',
|
81
|
+
'DSTROOT' => '/tmp/xcodeproj.dst',
|
82
|
+
'ALWAYS_SEARCH_USER_PATHS' => 'NO',
|
83
|
+
'GCC_C_LANGUAGE_STANDARD' => 'gnu99',
|
84
|
+
'INSTALL_PATH' => "$(BUILT_PRODUCTS_DIR)",
|
85
|
+
'OTHER_LDFLAGS' => '',
|
86
|
+
'COPY_PHASE_STRIP' => 'YES',
|
87
|
+
}.freeze,
|
88
|
+
:debug => {
|
89
|
+
'GCC_DYNAMIC_NO_PIC' => 'NO',
|
90
|
+
'GCC_PREPROCESSOR_DEFINITIONS' => ["DEBUG=1", "$(inherited)"],
|
91
|
+
'GCC_SYMBOLS_PRIVATE_EXTERN' => 'NO',
|
92
|
+
'GCC_OPTIMIZATION_LEVEL' => '0',
|
93
|
+
'COPY_PHASE_STRIP' => 'NO',
|
94
|
+
}.freeze,
|
95
|
+
:release => {
|
96
|
+
# Empty?
|
97
|
+
}.freeze,
|
98
|
+
:ios => {
|
99
|
+
'ARCHS' => "$(ARCHS_STANDARD_32_BIT)",
|
100
|
+
'IPHONEOS_DEPLOYMENT_TARGET' => '4.3',
|
101
|
+
'PUBLIC_HEADERS_FOLDER_PATH' => "$(TARGET_NAME)",
|
102
|
+
'SDKROOT' => 'iphoneos',
|
103
|
+
}.freeze,
|
104
|
+
:osx => {
|
105
|
+
'ARCHS' => "$(ARCHS_STANDARD_64_BIT)",
|
106
|
+
'GCC_ENABLE_OBJC_EXCEPTIONS' => 'YES',
|
107
|
+
'GCC_VERSION' => 'com.apple.compilers.llvm.clang.1_0',
|
108
|
+
'MACOSX_DEPLOYMENT_TARGET' => '10.7',
|
109
|
+
'SDKROOT' => 'macosx',
|
110
|
+
'COMBINE_HIDPI_IMAGES' => 'YES',
|
111
|
+
}.freeze,
|
112
|
+
[:osx, :debug] => {
|
113
|
+
'ONLY_ACTIVE_ARCH' => 'YES',
|
114
|
+
}.freeze,
|
115
|
+
[:osx, :release] => {
|
116
|
+
'DEBUG_INFORMATION_FORMAT' => 'dwarf-with-dsym',
|
117
|
+
}.freeze,
|
118
|
+
[:ios, :debug] => {
|
119
|
+
# Empty?
|
120
|
+
}.freeze,
|
121
|
+
[:ios, :release] => {
|
122
|
+
'VALIDATE_PRODUCT' => 'YES',
|
123
|
+
}.freeze,
|
124
|
+
}.freeze
|
125
|
+
|
126
|
+
# The corresponding numeric value of each copy build phase destination.
|
127
|
+
#
|
128
|
+
COPY_FILES_BUILD_PHASE_DESTINATIONS = {
|
129
|
+
:absolute_path => '0',
|
130
|
+
:products_directory => '16',
|
131
|
+
:wrapper => '1',
|
132
|
+
:resources => '7', #default
|
133
|
+
:executables => '6',
|
134
|
+
:java_resources => '15',
|
135
|
+
:frameworks => '10',
|
136
|
+
:shared_frameworks => '11',
|
137
|
+
:shared_support => '12',
|
138
|
+
:plug_ins => '13'
|
139
|
+
}.freeze
|
140
|
+
|
141
|
+
# The extensions which are associated with header files
|
142
|
+
#
|
143
|
+
HEADER_FILES_EXTENSIONS = %w| .h .hh .hpp |.freeze
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Xcodeproj
|
2
|
+
module Helper
|
3
|
+
class TargetDiff
|
4
|
+
attr_reader :project, :target1, :target2
|
5
|
+
|
6
|
+
def initialize(project, target1_name, target2_name)
|
7
|
+
@project = project
|
8
|
+
unless @target1 = @project.targets.find { |target| target.name == target1_name }
|
9
|
+
raise ArgumentError, "Target 1 by name `#{target1_name}' not found in the project."
|
10
|
+
end
|
11
|
+
unless @target2 = @project.targets.find { |target| target.name == target2_name }
|
12
|
+
raise ArgumentError, "Target 1 by name `#{target2_name}' not found in the project."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [Array<PBXBuildFile>] A list of source files (that will be
|
17
|
+
# compiled) which are in ‘target 2’ but not in ‘target 1’. The list is
|
18
|
+
# sorted by file path.
|
19
|
+
def new_source_build_files
|
20
|
+
@target2.source_build_phase.files.reject do |target2_build_file|
|
21
|
+
@target1.source_build_phase.files.any? do |target1_build_file|
|
22
|
+
target1_build_file.file_ref.path == target2_build_file.file_ref.path
|
23
|
+
end
|
24
|
+
end.sort_by { |build_file| build_file.file_ref.path }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/xcodeproj/project.rb
CHANGED
@@ -3,185 +3,398 @@ require 'pathname'
|
|
3
3
|
require 'xcodeproj/xcodeproj_ext'
|
4
4
|
|
5
5
|
require 'xcodeproj/project/object'
|
6
|
+
require 'xcodeproj/project/recursive_diff'
|
6
7
|
|
7
8
|
module Xcodeproj
|
9
|
+
|
8
10
|
# This class represents a Xcode project document.
|
9
11
|
#
|
10
12
|
# It can be used to manipulate existing documents or even create new ones
|
11
13
|
# from scratch.
|
12
14
|
#
|
13
|
-
#
|
14
|
-
#
|
15
|
+
# An Xcode project document is a plist file where the root is a dictionary
|
16
|
+
# containing the following keys:
|
17
|
+
#
|
18
|
+
# - archiveVersion: the version of the document.
|
19
|
+
# - objectVersion: the version of the objects description.
|
20
|
+
# - classes: a key that apparently is always empty.
|
21
|
+
# - objects: a dictionary where the UUID of every object is associated to
|
22
|
+
# its attributes.
|
23
|
+
# - rootObject: the UUID identifier of the root object ({PBXProject}).
|
24
|
+
#
|
25
|
+
# Every object is in turn a dictionary that specifies an `isa` (the class of
|
26
|
+
# the object) and in accordance to it maintains a set attributes. Those
|
27
|
+
# attributes might reference one or more other objects by UUID. If the
|
28
|
+
# reference is a collection, it is ordered.
|
29
|
+
#
|
30
|
+
# The {Project} API returns instances of {AbstractObject} which wrap the
|
31
|
+
# objects described in the Xcode project document. All the attributes types
|
32
|
+
# are preserved from the plist, except for the relationships which are
|
33
|
+
# replaced with objects instead of UUIDs.
|
34
|
+
#
|
35
|
+
# An object might be referenced by multiple objects, an when no other object
|
36
|
+
# is references it, it becomes unreachable (the root object is referenced by
|
37
|
+
# the project itself). Xcodeproj takes care of adding and removing those
|
38
|
+
# objects from the `objects` dictionary so the project is always in a
|
39
|
+
# consistent state.
|
40
|
+
#
|
15
41
|
class Project
|
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
|
21
|
-
end
|
22
|
-
end
|
23
42
|
|
24
43
|
include Object
|
25
44
|
|
26
|
-
#
|
27
|
-
#
|
45
|
+
# @return [String] the archive version.
|
46
|
+
#
|
47
|
+
attr_reader :archive_version
|
48
|
+
|
49
|
+
# @return [Hash] an dictionary whose purpose is unknown.
|
50
|
+
#
|
51
|
+
attr_reader :classes
|
52
|
+
|
53
|
+
# @return [String] the objects version.
|
28
54
|
#
|
29
|
-
|
30
|
-
|
55
|
+
attr_reader :object_version
|
56
|
+
|
57
|
+
# @return [Hash{String => AbstractObject}] A hash containing all the
|
58
|
+
# objects of the project by UUID.
|
59
|
+
#
|
60
|
+
attr_reader :objects_by_uuid
|
61
|
+
|
62
|
+
# @return [PBXProject] the root object of the project.
|
63
|
+
#
|
64
|
+
attr_reader :root_object
|
65
|
+
|
66
|
+
# Creates a new Project instance or initializes one with the data of an
|
67
|
+
# existing Xcode document.
|
68
|
+
#
|
69
|
+
# @param [Pathname, String] xcodeproj
|
70
|
+
# The path to the Xcode project document (xcodeproj).
|
71
|
+
#
|
72
|
+
# @raise If the project versions are more recent than the ones know to
|
73
|
+
# Xcodeproj to prevent it from corrupting existing projects. Naturally,
|
74
|
+
# this would never happen with a project generated by xcodeproj itself.
|
75
|
+
#
|
76
|
+
# @raise If it can't find the root object. This means that the project is
|
77
|
+
# malformed.
|
78
|
+
#
|
79
|
+
# @example Opening a project
|
80
|
+
# Project.new("path/to/Project.xcodeproj")
|
31
81
|
#
|
32
|
-
# @return [Project] A new Project instance or one with
|
33
|
-
# the data of an existing Xcode
|
34
|
-
# document.
|
35
82
|
def initialize(xcodeproj = nil)
|
83
|
+
@objects_by_uuid = {}
|
84
|
+
@generated_uuids = []
|
85
|
+
@available_uuids = []
|
86
|
+
|
36
87
|
if xcodeproj
|
37
88
|
file = File.join(xcodeproj, 'project.pbxproj')
|
38
|
-
|
89
|
+
plist = Xcodeproj.read_plist(file.to_s)
|
90
|
+
|
91
|
+
@archive_version = plist['archiveVersion']
|
92
|
+
@object_version = plist['objectVersion']
|
93
|
+
@classes = plist['classes']
|
94
|
+
|
95
|
+
root_object_uuid = plist['rootObject']
|
96
|
+
@root_object = new_from_plist(root_object_uuid, plist['objects'], self)
|
97
|
+
|
98
|
+
if (@archive_version.to_i > Constants::LAST_KNOWN_ARCHIVE_VERSION || @object_version.to_i > Constants::LAST_KNOWN_OBJECT_VERSION)
|
99
|
+
raise '[Xcodeproj] Unknown archive or object version.'
|
100
|
+
end
|
101
|
+
|
102
|
+
unless @root_object
|
103
|
+
raise "[Xcodeproj] Unable to find a root object in #{file}."
|
104
|
+
end
|
39
105
|
else
|
40
|
-
@
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
'compatibilityVersion' => 'Xcode 3.2',
|
50
|
-
'developmentRegion' => 'English',
|
51
|
-
'hasScannedForEncodings' => '0',
|
52
|
-
'knownRegions' => ['en'],
|
53
|
-
'mainGroup' => main_group.uuid,
|
54
|
-
'productRefGroup' => main_group.groups.new('name' => 'Products').uuid,
|
55
|
-
'projectDirPath' => '',
|
56
|
-
'projectRoot' => '',
|
57
|
-
'targets' => []
|
58
|
-
})
|
59
|
-
|
60
|
-
config_list = objects.add(XCConfigurationList)
|
106
|
+
@archive_version = Constants::LAST_KNOWN_ARCHIVE_VERSION.to_s
|
107
|
+
@object_version = Constants::LAST_KNOWN_OBJECT_VERSION.to_s
|
108
|
+
@classes = {}
|
109
|
+
|
110
|
+
root_object = new(PBXProject)
|
111
|
+
root_object.main_group = new(PBXGroup)
|
112
|
+
root_object.product_ref_group = root_object.main_group.new_group('Products')
|
113
|
+
|
114
|
+
config_list = new(XCConfigurationList)
|
61
115
|
config_list.default_configuration_name = 'Release'
|
62
116
|
config_list.default_configuration_is_visible = '0'
|
63
|
-
|
64
|
-
|
65
|
-
|
117
|
+
root_object.build_configuration_list = config_list
|
118
|
+
|
119
|
+
%w| Release Debug |.each do |name|
|
120
|
+
build_configuration = new(XCBuildConfiguration)
|
121
|
+
build_configuration.name = name
|
122
|
+
build_configuration.build_settings = {}
|
123
|
+
config_list.build_configurations << build_configuration
|
124
|
+
end
|
66
125
|
|
67
|
-
|
68
|
-
|
126
|
+
@root_object = root_object
|
127
|
+
root_object.add_referrer(self)
|
128
|
+
new_group('Frameworks')
|
69
129
|
end
|
70
130
|
end
|
71
131
|
|
72
|
-
#
|
132
|
+
# Compares the project to another one, or to a plist representation.
|
133
|
+
#
|
134
|
+
# @param [#to_hash] other the object to compare.
|
135
|
+
#
|
136
|
+
# @return [Boolean] whether the project is equivalent to the given object.
|
137
|
+
#
|
138
|
+
def ==(other)
|
139
|
+
other.respond_to?(:to_hash) && to_hash == other.to_hash
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_s
|
143
|
+
"Project with root object UUID: #{root_object.uuid}"
|
144
|
+
end
|
145
|
+
|
146
|
+
alias :inspect :to_s
|
147
|
+
|
148
|
+
|
149
|
+
|
150
|
+
# @!group Plist serialization
|
151
|
+
|
152
|
+
# Creates a new object from the given UUID and `objects` hash (of a plist).
|
153
|
+
#
|
154
|
+
# The method sets up any relationship of the new object, generating the
|
155
|
+
# destination object(s) if not already present in the project.
|
156
|
+
#
|
157
|
+
# @note This method is used to generate the root object
|
158
|
+
# from a plist. Subsequent invocation are called by the
|
159
|
+
# {AbstractObject#configure_with_plist}. Clients of {Xcodeproj} are
|
160
|
+
# not expected to call this method.
|
161
|
+
#
|
162
|
+
# @visibility private.
|
163
|
+
#
|
164
|
+
# @param [String] uuid
|
165
|
+
# the UUID of the object that needs to be generated.
|
166
|
+
#
|
167
|
+
# @param [Hash {String => Hash}] objects_by_uuid_plist
|
168
|
+
# the `objects` hash of the plist representation of the project.
|
169
|
+
#
|
170
|
+
# @param [Boolean] root_object
|
171
|
+
# whether the requested object is the root object and needs to be
|
172
|
+
# retained by the project before configuration to add it to the `objects`
|
173
|
+
# hash and avoid infinite loops.
|
174
|
+
#
|
175
|
+
# @return [AbstractObject] the new object.
|
176
|
+
#
|
177
|
+
def new_from_plist(uuid, objects_by_uuid_plist, root_object = false)
|
178
|
+
attributes = objects_by_uuid_plist[uuid]
|
179
|
+
klass = Object.const_get(attributes['isa'])
|
180
|
+
object = klass.new(self, uuid)
|
181
|
+
object.add_referrer(self) if root_object
|
182
|
+
|
183
|
+
object.configure_with_plist(objects_by_uuid_plist)
|
184
|
+
object
|
185
|
+
end
|
186
|
+
|
187
|
+
# @return [Hash] The plist representation of the project.
|
188
|
+
#
|
73
189
|
def to_hash
|
74
|
-
|
190
|
+
plist = {}
|
191
|
+
objects_dictionary = {}
|
192
|
+
objects.each { |obj| objects_dictionary[obj.uuid] = obj.to_plist }
|
193
|
+
plist['objects'] = objects_dictionary
|
194
|
+
plist['archiveVersion'] = archive_version.to_s
|
195
|
+
plist['objectVersion'] = object_version.to_s
|
196
|
+
plist['classes'] = classes
|
197
|
+
plist['rootObject'] = root_object.uuid
|
198
|
+
plist
|
75
199
|
end
|
76
200
|
|
77
|
-
|
78
|
-
|
201
|
+
alias :to_plist :to_hash
|
202
|
+
|
203
|
+
# Converts the objects tree to a hash substituting the hash
|
204
|
+
# of the referenced to their uuid reference. As a consequene the hash of an
|
205
|
+
# object might appear multiple times and the information about their
|
206
|
+
# uniqueness is lost.
|
207
|
+
#
|
208
|
+
# This method is designed to work in conjuction with {Hash#recursive_diff}
|
209
|
+
# to provie a complete, yet redable, diff of two projects *not* affected by
|
210
|
+
# isa differences.
|
211
|
+
#
|
212
|
+
# @return [Hash] a hash reppresentation of the project different from the
|
213
|
+
# plist one.
|
214
|
+
#
|
215
|
+
def to_tree_hash
|
216
|
+
hash = {}
|
217
|
+
objects_dictionary = {}
|
218
|
+
hash['objects'] = objects_dictionary
|
219
|
+
hash['archiveVersion'] = archive_version.to_s
|
220
|
+
hash['objectVersion'] = object_version.to_s
|
221
|
+
hash['classes'] = classes
|
222
|
+
hash['rootObject'] = root_object.to_tree_hash
|
223
|
+
hash
|
79
224
|
end
|
80
225
|
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
226
|
+
# Serializes the internal data as a property list and stores it on disk at
|
227
|
+
# the given path (`xcodeproj` file).
|
228
|
+
#
|
229
|
+
# @example Saving a project
|
230
|
+
# project.save_as("path/to/Project.xcodeproj") #=> true
|
84
231
|
#
|
85
|
-
# @
|
86
|
-
|
87
|
-
|
232
|
+
# @param [String, Pathname] projpath The path where the data should be
|
233
|
+
# stored.
|
234
|
+
#
|
235
|
+
# @return [Boolean] Whether or not saving was successful.
|
236
|
+
#
|
237
|
+
def save_as(projpath)
|
238
|
+
projpath = projpath.to_s
|
239
|
+
FileUtils.mkdir_p(projpath)
|
240
|
+
Xcodeproj.write_plist(to_plist, File.join(projpath, 'project.pbxproj'))
|
88
241
|
end
|
89
242
|
|
90
|
-
|
91
|
-
|
243
|
+
|
244
|
+
|
245
|
+
# @!group Creating objects
|
246
|
+
|
247
|
+
# Creates a new object with a suitable UUID.
|
92
248
|
#
|
93
|
-
#
|
94
|
-
#
|
249
|
+
# The object is only configured with the default values of the `:simple`
|
250
|
+
# attributes, for this reason it is better to use the convenience methods
|
251
|
+
# offered by the {AbstractObject} subclasses or by this class.
|
95
252
|
#
|
96
|
-
# @
|
97
|
-
#
|
253
|
+
# @param [Class] klass The concrete subclass of AbstractObject for new
|
254
|
+
# object.
|
98
255
|
#
|
99
|
-
# @
|
100
|
-
#
|
101
|
-
def
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
objects_hash[uuid] = attributes
|
256
|
+
# @return [AbstractObject] the new object.
|
257
|
+
#
|
258
|
+
def new(klass)
|
259
|
+
object = klass.new(self, generate_uuid)
|
260
|
+
object.initialize_defaults
|
261
|
+
object
|
106
262
|
end
|
107
263
|
|
108
|
-
#
|
109
|
-
|
110
|
-
|
264
|
+
# Generates a UUID unique for the project.
|
265
|
+
#
|
266
|
+
# @note UUIDs are not guaranteed to be generated unique because we need to
|
267
|
+
# trim the ones generated in the xcodeproj extension.
|
268
|
+
#
|
269
|
+
# @note Implementation detail: as objects usually are created serially this
|
270
|
+
# method creates a batch of UUID and stores the not colliding ones,
|
271
|
+
# so the search for collisions with known UUIDS (a performance
|
272
|
+
# bottleneck) is performed is performed less often.
|
273
|
+
#
|
274
|
+
# @return [String] A UUID unique to the project.
|
275
|
+
#
|
276
|
+
def generate_uuid
|
277
|
+
while @available_uuids.empty?
|
278
|
+
generate_available_uuid_list
|
279
|
+
end
|
280
|
+
@available_uuids.shift
|
111
281
|
end
|
112
282
|
|
113
|
-
# @
|
114
|
-
|
115
|
-
|
283
|
+
# @return [Array<String>] the list of all the generated UUIDs.
|
284
|
+
#
|
285
|
+
# Used for checking new UUIDs for duplicates with UUIDs already generated
|
286
|
+
# but used for objects which are not yet part of the `objects` hash but
|
287
|
+
# which might be added at a later time.
|
288
|
+
#
|
289
|
+
attr_reader :generated_uuids
|
290
|
+
|
291
|
+
# Pre-generates the given number of UUIDs. Useful for optimizing
|
292
|
+
# performance when the rough number of objects that will be created is
|
293
|
+
# known in advance.
|
294
|
+
#
|
295
|
+
# @param [Integer] count
|
296
|
+
# the number of UUIDs that should be generated.
|
297
|
+
#
|
298
|
+
# @note This method might generated a minor number of uniques UUIDs than
|
299
|
+
# the given count, because some might be duplicated a thus will be
|
300
|
+
# discarded.
|
301
|
+
#
|
302
|
+
# @return [void]
|
303
|
+
#
|
304
|
+
def generate_available_uuid_list(count = 100)
|
305
|
+
new_uuids = (0..count).map { Xcodeproj.generate_uuid }
|
306
|
+
uniques = (new_uuids - (@generated_uuids + uuids))
|
307
|
+
@generated_uuids += uniques
|
308
|
+
@available_uuids += uniques
|
116
309
|
end
|
117
310
|
|
118
|
-
|
119
|
-
|
311
|
+
## CONVENIENCE METHODS #####################################################
|
312
|
+
|
313
|
+
# @!group Convenience accessors
|
314
|
+
|
315
|
+
# @return [Array<AbstractObject>] all the objects of the project.
|
316
|
+
#
|
120
317
|
def objects
|
121
|
-
|
122
|
-
list.let(:uuid_scope) { objects_hash.keys }
|
123
|
-
end
|
318
|
+
objects_by_uuid.values
|
124
319
|
end
|
125
320
|
|
126
|
-
# @return [
|
127
|
-
#
|
128
|
-
def
|
129
|
-
|
321
|
+
# @return [Array<String>] all the UUIDs of the project.
|
322
|
+
#
|
323
|
+
def uuids
|
324
|
+
objects_by_uuid.keys
|
130
325
|
end
|
131
326
|
|
132
|
-
#
|
327
|
+
# @return [Array<AbstractObject>] all the objects of the project with a
|
328
|
+
# given isa.
|
133
329
|
#
|
134
|
-
|
135
|
-
|
136
|
-
def group(name)
|
137
|
-
groups.object_named(name)
|
330
|
+
def list_by_class(klass)
|
331
|
+
objects.select { |o| o.class == klass }
|
138
332
|
end
|
139
333
|
|
140
|
-
# @return [PBXGroup]
|
334
|
+
# @return [PBXGroup] the main top-level group.
|
335
|
+
#
|
141
336
|
def main_group
|
142
|
-
|
337
|
+
root_object.main_group
|
143
338
|
end
|
144
339
|
|
145
|
-
# @return [
|
146
|
-
#
|
147
|
-
|
148
|
-
|
340
|
+
# @return [ObjectList<PBXGroup>] a list of all the groups in the
|
341
|
+
# project.
|
342
|
+
#
|
343
|
+
def groups
|
344
|
+
main_group.groups
|
149
345
|
end
|
150
346
|
|
151
|
-
#
|
152
|
-
#
|
153
|
-
# The file reference can then be added to the buildFiles of a
|
154
|
-
# PBXFrameworksBuildPhase.
|
347
|
+
# Returns a group at the given subpath relative to the main group.
|
155
348
|
#
|
156
349
|
# @example
|
350
|
+
# frameworks = project['Frameworks']
|
351
|
+
# frameworks.name #=> 'Frameworks'
|
352
|
+
# main_group.children.include? frameworks #=> True
|
157
353
|
#
|
158
|
-
#
|
354
|
+
# @param [String] group_path
|
159
355
|
#
|
160
|
-
#
|
161
|
-
# build_phase = target.frameworks_build_phases.first
|
162
|
-
# build_phase.files << framework.buildFiles.new
|
356
|
+
# @return [PBXGroup] the group at the given subpath.
|
163
357
|
#
|
164
|
-
|
358
|
+
def [](group_path)
|
359
|
+
main_group[group_path]
|
360
|
+
end
|
361
|
+
|
362
|
+
# @return [ObjectList<PBXFileReference>] a list of all the files in the
|
363
|
+
# project.
|
165
364
|
#
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
group.files.new({
|
176
|
-
'name' => "#{name}.framework",
|
177
|
-
'path' => path,
|
178
|
-
'sourceTree' => 'SDKROOT'
|
179
|
-
})
|
180
|
-
end
|
365
|
+
def files
|
366
|
+
objects.select { |obj| obj.class == PBXFileReference }
|
367
|
+
end
|
368
|
+
|
369
|
+
# @return [ObjectList<PBXNativeTarget>] A list of all the targets in the
|
370
|
+
# project.
|
371
|
+
#
|
372
|
+
def targets
|
373
|
+
root_object.targets
|
181
374
|
end
|
182
375
|
|
183
|
-
# @return [
|
184
|
-
#
|
376
|
+
# @return [PBXGroup] The group which holds the product file references.
|
377
|
+
#
|
378
|
+
def products_group
|
379
|
+
root_object.product_ref_group
|
380
|
+
end
|
381
|
+
|
382
|
+
# @return [ObjectList<PBXFileReference>] A list of the product file
|
383
|
+
# references.
|
384
|
+
#
|
385
|
+
def products
|
386
|
+
products_group.children
|
387
|
+
end
|
388
|
+
|
389
|
+
# @return [PBXGroup] the `Frameworks` group creating it if necessary.
|
390
|
+
#
|
391
|
+
def frameworks_group
|
392
|
+
main_group['Frameworks'] || new_group('Frameworks')
|
393
|
+
end
|
394
|
+
|
395
|
+
# @return [ObjectList<XCBuildConfiguration>] A list of project wide
|
396
|
+
# build configurations.
|
397
|
+
#
|
185
398
|
def build_configurations
|
186
399
|
root_object.build_configuration_list.build_configurations
|
187
400
|
end
|
@@ -190,63 +403,156 @@ module Xcodeproj
|
|
190
403
|
#
|
191
404
|
# @return [Hash] The build settings of the project wide build
|
192
405
|
# configuration with the given name.
|
406
|
+
#
|
193
407
|
def build_settings(name)
|
194
408
|
root_object.build_configuration_list.build_settings(name)
|
195
409
|
end
|
196
410
|
|
197
|
-
|
411
|
+
|
412
|
+
|
413
|
+
# @!group Convenience methods for generating objects
|
414
|
+
|
415
|
+
# Creates a new file reference at the given subpath of the main group.
|
198
416
|
#
|
199
|
-
# @
|
200
|
-
#
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
417
|
+
# @param (see PBXGroup#new_file)
|
418
|
+
#
|
419
|
+
# @return [PBXFileReference] the new file.
|
420
|
+
#
|
421
|
+
def new_file(path, sub_group_path = nil)
|
422
|
+
main_group.new_file(path, sub_group_path)
|
205
423
|
end
|
206
424
|
|
207
|
-
#
|
208
|
-
|
209
|
-
|
425
|
+
# Creates a new group at the given subpath of the main group.
|
426
|
+
#
|
427
|
+
# @param (see PBXGroup#new_group)
|
428
|
+
#
|
429
|
+
# @return [PBXGroup] the new group.
|
430
|
+
#
|
431
|
+
def new_group(name, path = nil)
|
432
|
+
main_group.new_group(name, path)
|
210
433
|
end
|
211
434
|
|
212
|
-
#
|
213
|
-
#
|
214
|
-
|
215
|
-
|
435
|
+
# Adds a file reference for a system framework to the project.
|
436
|
+
#
|
437
|
+
# The file reference can then be added to the build files of a
|
438
|
+
# {PBXFrameworksBuildPhase}.
|
439
|
+
#
|
440
|
+
# @example
|
441
|
+
#
|
442
|
+
# framework = project.add_system_framework('QuartzCore')
|
443
|
+
#
|
444
|
+
# target = project.targets.first
|
445
|
+
# build_phase = target.frameworks_build_phases.first
|
446
|
+
# build_phase.files << framework.buildFiles.new
|
447
|
+
#
|
448
|
+
# @param [String] name The name of a framework in the SDK System
|
449
|
+
# directory.
|
450
|
+
# @return [PBXFileReference] The file reference object.
|
451
|
+
#
|
452
|
+
def add_system_framework(name)
|
453
|
+
path = "System/Library/Frameworks/#{name}.framework"
|
454
|
+
if file = frameworks_group.files.first { |f| f.path == path }
|
455
|
+
file
|
456
|
+
else
|
457
|
+
framework_ref = frameworks_group.new_file(path)
|
458
|
+
framework_ref.name = "#{name}.framework"
|
459
|
+
framework_ref.source_tree = 'SDKROOT'
|
460
|
+
framework_ref
|
461
|
+
end
|
216
462
|
end
|
217
463
|
|
218
|
-
# @
|
219
|
-
|
464
|
+
# @return [PBXNativeTarget] Creates a new target and adds it to the
|
465
|
+
# project.
|
466
|
+
#
|
467
|
+
# The target is configured for the given platform and its file reference
|
468
|
+
# it is added to the {products_group}.
|
469
|
+
#
|
470
|
+
# The target is pre-populated with common build phases, and all the
|
471
|
+
# Frameworks of the project are added to to its Frameworks phase.
|
472
|
+
#
|
473
|
+
# @todo Adding all the Frameworks is required by CocoaPods and should be
|
474
|
+
# performed there.
|
475
|
+
#
|
476
|
+
# @param [Symbol] type
|
477
|
+
# the type of target.
|
478
|
+
# Can be `:application`, `:dynamic_library` or `:static_library`.
|
479
|
+
#
|
480
|
+
# @param [String] name
|
481
|
+
# the name of the static library product.
|
482
|
+
#
|
483
|
+
# @param [Symbol] platform
|
484
|
+
# the platform of the static library.
|
485
|
+
# Can be `:ios` or `:osx`.
|
486
|
+
#
|
487
|
+
def new_target(type, name, platform)
|
488
|
+
add_system_framework(platform == :ios ? 'Foundation' : 'Cocoa')
|
489
|
+
|
490
|
+
# Target
|
491
|
+
target = new(PBXNativeTarget)
|
492
|
+
targets << target
|
493
|
+
target.name = name
|
494
|
+
target.product_name = name
|
495
|
+
target.product_type = Constants::PRODUCT_TYPE_UTI[type]
|
496
|
+
target.build_configuration_list = configuration_list(platform)
|
497
|
+
|
498
|
+
# Product
|
499
|
+
product = products_group.new_static_library(name)
|
500
|
+
target.product_reference = product
|
220
501
|
|
221
|
-
|
222
|
-
|
502
|
+
# Build phases
|
503
|
+
target.build_phases << new(PBXSourcesBuildPhase)
|
504
|
+
frameworks_phase = new(PBXFrameworksBuildPhase)
|
505
|
+
frameworks_group.files.each { |framework| frameworks_phase.add_file_reference(framework) }
|
506
|
+
target.build_phases << frameworks_phase
|
507
|
+
|
508
|
+
target
|
509
|
+
end
|
510
|
+
|
511
|
+
# Returns a new configuration list, populated with release and debug
|
512
|
+
# configurations with common build settings for the given platform.
|
223
513
|
#
|
224
|
-
# @
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
514
|
+
# @param [Symbol] platform
|
515
|
+
# the platform for the configuration list, can be `:ios` or `:osx`.
|
516
|
+
#
|
517
|
+
# @return [XCConfigurationList] the generated configuration list.
|
518
|
+
#
|
519
|
+
def configuration_list(platform)
|
520
|
+
cl = new(XCConfigurationList)
|
521
|
+
cl.default_configuration_is_visible = '0'
|
522
|
+
cl.default_configuration_name = 'Release'
|
523
|
+
|
524
|
+
release_conf = new(XCBuildConfiguration)
|
525
|
+
release_conf.name = 'Release'
|
526
|
+
release_conf.build_settings = configuration_list_settings(platform, :release)
|
527
|
+
|
528
|
+
debug_conf = new(XCBuildConfiguration)
|
529
|
+
debug_conf.name = 'Debug'
|
530
|
+
debug_conf.build_settings = configuration_list_settings(platform, :debug)
|
531
|
+
|
532
|
+
cl.build_configurations << release_conf
|
533
|
+
cl.build_configurations << debug_conf
|
534
|
+
cl
|
232
535
|
end
|
233
536
|
|
234
|
-
#
|
235
|
-
#
|
537
|
+
# Returns the common build settings for a given platform and configuration
|
538
|
+
# name.
|
236
539
|
#
|
237
|
-
# @
|
540
|
+
# @param [Symbol] platform
|
541
|
+
# the platform for the build settings, can be `:ios` or `:osx`.
|
238
542
|
#
|
239
|
-
#
|
543
|
+
# @param [Symbol] name
|
544
|
+
# the name of the build configuration, can be `:release` or `:debug`.
|
240
545
|
#
|
241
|
-
# @
|
242
|
-
# stored.
|
546
|
+
# @return [Hash] The common build settings
|
243
547
|
#
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
548
|
+
def configuration_list_settings(platform, name)
|
549
|
+
common_settings = Constants::COMMON_BUILD_SETTINGS
|
550
|
+
bs = common_settings[:all].dup
|
551
|
+
bs = bs.merge(common_settings[name])
|
552
|
+
bs = bs.merge(common_settings[platform])
|
553
|
+
bs = bs.merge(common_settings[[platform, name]])
|
554
|
+
bs
|
250
555
|
end
|
556
|
+
|
251
557
|
end
|
252
558
|
end
|