xcoder 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/.rvmrc +4 -0
- data/Gemfile +14 -0
- data/Guardfile +14 -0
- data/README.md +142 -0
- data/Rakefile +28 -0
- data/lib/xcode/build_file.rb +22 -0
- data/lib/xcode/build_phase.rb +92 -0
- data/lib/xcode/builder.rb +190 -0
- data/lib/xcode/buildfile.rb +101 -0
- data/lib/xcode/configuration.rb +161 -0
- data/lib/xcode/configuration_list.rb +85 -0
- data/lib/xcode/core_ext/array.rb +23 -0
- data/lib/xcode/core_ext/boolean.rb +21 -0
- data/lib/xcode/core_ext/fixnum.rb +5 -0
- data/lib/xcode/core_ext/hash.rb +27 -0
- data/lib/xcode/core_ext/string.rb +26 -0
- data/lib/xcode/file_reference.rb +77 -0
- data/lib/xcode/group.rb +197 -0
- data/lib/xcode/info_plist.rb +41 -0
- data/lib/xcode/keychain.rb +122 -0
- data/lib/xcode/parsers/plutil_project_parser.rb +20 -0
- data/lib/xcode/project.rb +335 -0
- data/lib/xcode/provisioning_profile.rb +53 -0
- data/lib/xcode/registry.rb +168 -0
- data/lib/xcode/resource.rb +203 -0
- data/lib/xcode/scheme.rb +36 -0
- data/lib/xcode/shell.rb +22 -0
- data/lib/xcode/simple_identifier_generator.rb +17 -0
- data/lib/xcode/target.rb +204 -0
- data/lib/xcode/test/formatters/junit_formatter.rb +50 -0
- data/lib/xcode/test/ocunit_report_parser.rb +68 -0
- data/lib/xcode/test/suite_result.rb +31 -0
- data/lib/xcode/test/test_result.rb +38 -0
- data/lib/xcode/testflight.rb +56 -0
- data/lib/xcode/variant_group.rb +28 -0
- data/lib/xcode/version.rb +3 -0
- data/lib/xcode/workspace.rb +40 -0
- data/lib/xcoder.rb +105 -0
- data/xcoder.gemspec +26 -0
- metadata +52 -12
@@ -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
|
data/lib/xcode/scheme.rb
ADDED
@@ -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
|
data/lib/xcode/shell.rb
ADDED
@@ -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
|
data/lib/xcode/target.rb
ADDED
@@ -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
|