xcoder 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,203 @@
1
+ require 'xcode/core_ext/string'
2
+
3
+ module Xcode
4
+
5
+ #
6
+ # Resources are not represented as a true entity within an Xcode project.
7
+ # When traversing through groups, targets, configurations, etc. you will find
8
+ # yourself interacting with these objects. As they represent a class that
9
+ # acts as a shim around the hash data is parsed from the project.
10
+ #
11
+ # A resource do some things that should be explained:
12
+ #
13
+ # When a resource is created it requires an identifier and an instance of
14
+ # of the Registry. It finds the properties hash of that given identifier
15
+ # within the registry and creates a bunch of read-only methods that allow for
16
+ # easy access to those elements. This is not unlike an OpenStruct.
17
+ #
18
+ # @example of accessing the contents of a file reference
19
+ #
20
+ # file_resource.properties # =>
21
+ #
22
+ # { 'isa' => 'PBXFileReference',
23
+ # 'lastKnownFileType' => 'sourcecode.c.h',
24
+ # 'path' => IOSAppDelegate.h',
25
+ # 'sourceTree' => "<group>" }
26
+ #
27
+ # file_resource.isa # => 'PBXFileReference'
28
+ # file_resource.sourceTree # => "<group>"
29
+ #
30
+ #
31
+ # To provide additional convenience when traversing through the
32
+ # various objects, the read-only method will check to see if the value
33
+ # being returned matches that of a unique identifier. If it does, instead of
34
+ # providing that identifier as a result and then having additional code to
35
+ # perform the look up, it does it automatically.
36
+ #
37
+ # @example of how this would have to been done without this indirection
38
+ #
39
+ # project = Xcode.project('MyProject')
40
+ # main_group = project.groups
41
+ # child_identifier = group.children.first
42
+ # subgroup = project.registry['objects'][child_identifier]
43
+ #
44
+ # @example of hot this works currently because of this indirection
45
+ #
46
+ # group = Xcode.project('MyProject.xcodeproj').mainGroup
47
+ # subgroup = group.group('Models')
48
+ #
49
+ #
50
+ # Next, as most every one of these objects is a Hash that contain the properties
51
+ # instead of objects it would likely be better to encapsulate these resources
52
+ # within specific classes that provide additional functionality. So that when
53
+ # a group resource or a file resource is returned you can perform unique
54
+ # functionality with it automatically.
55
+ #
56
+ # This is done by using the 'isa' property field which contains the type of
57
+ # content object. Instead of creating an object and encapsulating if a module
58
+ # that matches the name of the 'isa', that module of functionality is
59
+ # automatically mixed-in to provide that functionality.
60
+ #
61
+ class Resource
62
+
63
+ # The unique identifier for this resource
64
+ attr_accessor :identifier
65
+
66
+ # The properties hash that is known about the resource
67
+ attr_accessor :properties
68
+
69
+ # The registry of all objects within the project file which all resources
70
+ # have a reference to so that they can retrieve any items they may own that
71
+ # are simply referenced by identifiers.
72
+ attr_accessor :registry
73
+
74
+ #
75
+ # Definiing a property allows the creation of an alias to the actual value.
76
+ # This level of indirection allows for the replacement of values which are
77
+ # identifiers with a resource representation of it.
78
+ #
79
+ # @note This is used internally by the resource when it is created.
80
+ #
81
+ # @param [String] name of the property
82
+ # @param [String] value of the property
83
+ #
84
+ def define_property name, value
85
+
86
+ # Save the properties within the resource within a custom hash. This
87
+ # provides access to them without the indirection that we are about to
88
+ # set up.
89
+
90
+ @properties[name] = value
91
+
92
+ # Generate a getter method for this property based on the given name.
93
+
94
+ self.class.send :define_method, name.underscore do
95
+
96
+ # Retrieve the value that is current stored for this name.
97
+
98
+ raw_value = @properties[name]
99
+
100
+ # If the value is an array then we want to examine each element within
101
+ # the array to see if any of them are identifiers that we should replace
102
+ # finally returning all of the items as their resource representations
103
+ # or as their raw values.
104
+ #
105
+ # If the value is not an array then we want to examine that item and
106
+ # return the resource representation or the raw value.
107
+
108
+ if raw_value.is_a?(Array)
109
+
110
+ Array(raw_value).map do |sub_value|
111
+
112
+ if Registry.is_identifier? sub_value
113
+ Resource.new sub_value, @registry
114
+ else
115
+ sub_value
116
+ end
117
+ end
118
+
119
+ else
120
+
121
+ if Registry.is_identifier? raw_value
122
+ Resource.new raw_value, @registry
123
+ else
124
+ raw_value
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+
131
+ self.class.send :define_method, "#{name.underscore}=" do |new_value|
132
+ @properties[name] = new_value
133
+ end
134
+
135
+
136
+ end
137
+
138
+ #
139
+ # Within the code, a single resource is created and that is with the root
140
+ # projet object. All other resources are created through the indirecation of
141
+ # the above property methods.
142
+ #
143
+ # @param [String] identifier the unique identifier for this resource.
144
+ # @param [Types] details Description
145
+ #
146
+ def initialize identifier, details
147
+ @registry = details
148
+ @properties = {}
149
+ @identifier = identifier
150
+
151
+ # Create property methods for all of the key-value pairs found in the
152
+ # registry for specified identifier.
153
+
154
+ Array(details.properties(@identifier)).each do |key,value|
155
+ send :define_property, key, value
156
+ end
157
+
158
+ #
159
+ # Based on the `isa` property find if there is a constant within
160
+ # the Xcode module that matches and if it does, then we want to
161
+ # automatically include module into the Resource object.
162
+ #
163
+ constant = Registry.isa_to_module(isa)
164
+
165
+ self.extend(constant) if constant
166
+
167
+ end
168
+
169
+
170
+ #
171
+ # Saves the current resource back to the registry. This is necessary as
172
+ # any changes made are not automatically saved back into the registry.
173
+ #
174
+ def save!
175
+ @registry.set_object(self)
176
+ self
177
+ end
178
+
179
+ #
180
+ # @return [String] a representation with the identifier and the properties
181
+ # for this resource.
182
+ #
183
+ def to_s
184
+ "#{isa} #{@identifier} #{@properties}"
185
+ end
186
+
187
+ #
188
+ # This will generate the resource in the format that is supported by the
189
+ # Xcode project file. Which requires each key value pair to be represented.
190
+ #
191
+ # @return [String] a string representation of the object so that it can
192
+ # be persisted to an Xcode project file.
193
+ #
194
+ def to_xcplist
195
+ %{
196
+ #{@identifier} = { #{ @properties.map {|k,v| "#{k} = \"#{v.to_xcplist}\"" }.join("; ") } }
197
+
198
+ }
199
+ end
200
+
201
+ end
202
+
203
+ end
@@ -0,0 +1,36 @@
1
+ require 'nokogiri'
2
+
3
+ module Xcode
4
+
5
+ # Schemes are an XML file that describe build, test, launch and profile actions
6
+ # For the purposes of Xcoder, we want to be able to build and test
7
+ # The scheme's build action only describes a target, so we need to look at launch for the config
8
+ class Scheme
9
+ attr_reader :project, :path, :name, :launch, :test
10
+ def initialize(project, path)
11
+ @project = project
12
+ @path = File.expand_path(path)
13
+ @name = File.basename(path).gsub(/\.xcscheme$/,'')
14
+ doc = Nokogiri::XML(open(@path))
15
+
16
+ @launch = parse_action(doc, 'launch')
17
+ @test = parse_action(doc, 'test')
18
+ end
19
+
20
+ def builder
21
+ Xcode::Builder.new(self)
22
+ end
23
+
24
+ private
25
+
26
+ def parse_action(doc, action_name)
27
+ action = doc.xpath("//#{action_name.capitalize}Action").first
28
+ buildableReference = action.xpath('BuildableProductRunnable/BuildableReference').first
29
+ return nil if buildableReference.nil?
30
+
31
+ target_name = buildableReference['BlueprintName']
32
+ @project.target(target_name).config(action['buildConfiguration'])
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ module Xcode
2
+ module Shell
3
+
4
+ def self.execute(bits, show_output=true)
5
+ out = []
6
+ cmd = bits.is_a?(Array) ? bits.join(' ') : bits
7
+
8
+ puts "EXECUTE: #{cmd}"
9
+ IO.popen (cmd) do |f|
10
+ f.each do |line|
11
+ puts line if show_output
12
+ yield(line) if block_given?
13
+ out << line
14
+ end
15
+ end
16
+ #Process.wait
17
+ raise "Error (#{$?.exitstatus}) executing '#{cmd}'\n\n #{out.join(" ")}" if $?.exitstatus>0
18
+ #puts "RETURN: #{out.inspect}"
19
+ out
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module SimpleIdentifierGenerator
2
+ extend self
3
+
4
+ #
5
+ # Generate an identifier string
6
+ #
7
+ # @example identifier string
8
+ #
9
+ # "E21EB9EE14E359840058122A"
10
+ #
11
+ # @return [String] a 24-length string that contains only hexadecimal characters.
12
+ #
13
+ def generate
14
+ range = ('A'..'F').to_a + (0..9).to_a
15
+ 24.times.inject("") {|ident| "#{ident}#{range.sample}" }
16
+ end
17
+ end
@@ -0,0 +1,204 @@
1
+ require_relative 'build_file'
2
+
3
+ module Xcode
4
+
5
+ #
6
+ # Within a project a user may define a number of targets. These targets may
7
+ # be to generate the application, generate a universal framework, or execute
8
+ # tests.
9
+ #
10
+ #
11
+ # @example Target as Hash
12
+ #
13
+ # E21D8AA914E0F817002E56AA /* newtarget */ = {
14
+ # isa = PBXNativeTarget;
15
+ # buildConfigurationList = E21D8ABD14E0F817002E56AA /* Build configuration list for PBXNativeTarget "newtarget" */;
16
+ # buildPhases = (
17
+ # E21D8AA614E0F817002E56AA /* Sources */,
18
+ # E21D8AA714E0F817002E56AA /* Frameworks */,
19
+ # E21D8AA814E0F817002E56AA /* Resources */,
20
+ # );
21
+ # buildRules = (
22
+ # );
23
+ # dependencies = (
24
+ # );
25
+ # name = newtarget;
26
+ # productName = newtarget;
27
+ # productReference = E21D8AAA14E0F817002E56AA /* newtarget.app */;
28
+ # productType = "com.apple.product-type.application";
29
+ # };
30
+ #
31
+ # @todo provide more targets, based on the properties hash generated from Xcode
32
+ #
33
+ module Target
34
+
35
+ #
36
+ # This is a generic properties hash for an ios target
37
+ # @todo this target should create by default the sources, frameworks, and
38
+ # resources build phases.
39
+ #
40
+ def self.ios
41
+ { 'isa' => 'PBXNativeTarget',
42
+ 'buildConfigurationList' => nil,
43
+ 'buildPhases' => [],
44
+ 'buildRules' => [],
45
+ 'dependencies' => [],
46
+ 'name' => '',
47
+ 'productName' => '',
48
+ 'productReference' => '',
49
+ 'productType' => 'com.apple.product-type.application' }
50
+ end
51
+
52
+ # A reference to the project for which these targets reside.
53
+ attr_accessor :project
54
+
55
+ #
56
+ # @return [PBXBuildConfiguration] the configurations that this target supports.
57
+ # these are generally 'Debug' or 'Release' but may be custom designed
58
+ # configurations.
59
+ #
60
+ def configs
61
+ build_configuration_list.build_configurations.map do |config|
62
+ config.target = self
63
+ config
64
+ end
65
+ end
66
+
67
+ #
68
+ # Return a specific build configuration. When one is not found to match,
69
+ # an exception is raised.
70
+ #
71
+ # @param [String] name of a configuration to return
72
+ # @return [PBXBuildConfiguration] a specific build configuration that
73
+ # matches the specified name.
74
+ #
75
+ def config(name)
76
+ config = configs.select {|config| config.name == name.to_s }.first
77
+ raise "No such config #{name}, available configs are #{configs.map {|c| c.name}.join(', ')}" if config.nil?
78
+ yield config if block_given?
79
+ config
80
+ end
81
+
82
+ #
83
+ # Create a configuration for the target.
84
+ #
85
+ # @example debug configuration
86
+ #
87
+ # target.create_config 'Debug' do |config|
88
+ # # configuration the new debug config.
89
+ # end
90
+ #
91
+ # @param [String] name of the configuration to create
92
+ # @return [BuildConfiguration] that is created
93
+ #
94
+ def create_configuration(name)
95
+ # To create a configuration, we need to create or retrieve the configuration list
96
+
97
+ created_config = build_configuration_list.create_config(name) do |config|
98
+ yield config if block_given?
99
+ end
100
+
101
+ created_config
102
+ end
103
+
104
+ def create_configurations(*configuration_names)
105
+
106
+ configuration_names.compact.flatten.map do |config_name|
107
+ created_config = create_configuration config_name do |config|
108
+ yield config if block_given?
109
+ end
110
+
111
+ created_config.save!
112
+ end
113
+
114
+ end
115
+
116
+ #
117
+ # @return [BuildPhase] the framework specific build phase of the target.
118
+ #
119
+ def framework_build_phase
120
+ build_phase 'PBXFrameworksBuildPhase'
121
+ end
122
+
123
+ #
124
+ # @return [BuildPhase] the sources specific build phase of the target.
125
+ #
126
+ def sources_build_phase
127
+ build_phase 'PBXSourcesBuildPhase'
128
+ end
129
+
130
+ #
131
+ # @return [BuildPhase] the resources specific build phase of the target.
132
+ #
133
+ def resources_build_phase
134
+ build_phase 'PBXResourcesBuildPhase'
135
+ end
136
+
137
+ def build_phase(type,&block)
138
+ found_build_phase = build_phases.find {|phase| phase.isa == type }
139
+ found_build_phase.instance_eval(&block) if block_given?
140
+ found_build_phase
141
+ end
142
+ #
143
+ # @example building the three main phases for a target.
144
+ #
145
+ # target.create_build_phase :sources
146
+ #
147
+ # target.create_build_phase :resources do |phase|
148
+ # # each phase that is created.
149
+ # end
150
+ #
151
+ # @param [String] phase_name the name of the phase to add to the target
152
+ # @return [BuildPhase] the BuildPhase that is created
153
+ def create_build_phase(phase_name)
154
+
155
+ # Register a BuildPhase with the default properties specified by the name.
156
+ build_phase = @registry.add_object(BuildPhase.send("#{phase_name}"))
157
+
158
+ # Add the build phase to the list of build phases for this target.
159
+ # @todo this is being done commonly in the application in multiple places
160
+ # and it bugs me. Perhaps some special module could be mixed into the
161
+ # Array of results that are returned.
162
+ @properties['buildPhases'] << build_phase.identifier
163
+
164
+ yield build_phase if block_given?
165
+
166
+ build_phase.save!
167
+ end
168
+
169
+ #
170
+ # Create multiple build phases at the same time.
171
+ #
172
+ # @param [Array<String,Symbol>] base_phase_names are the names of the phases
173
+ # that you want to create for a target.
174
+ #
175
+ # @return [Array] the phases created.
176
+ #
177
+ def create_build_phases *base_phase_names
178
+
179
+ base_phase_names.compact.flatten.map do |phase_name|
180
+ build_phase = create_build_phase phase_name do |build_phase|
181
+ yield build_phase if block_given?
182
+ end
183
+
184
+ build_phase.save!
185
+ end
186
+
187
+ end
188
+
189
+ #
190
+ # Create a product reference file and add it to the product. This is by
191
+ # default added to the 'Products' group.
192
+ #
193
+ # @param [String] name of the product reference to add to the product
194
+ # @return [Resource] the product created
195
+ #
196
+ def create_product_reference(name)
197
+ product = project.products_group.create_product_reference(name)
198
+ product_reference = product.identifier
199
+ product
200
+ end
201
+
202
+ end
203
+
204
+ end