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