xcoder 0.1.1 → 0.1.2

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.
@@ -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