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,77 @@
|
|
1
|
+
module Xcode
|
2
|
+
|
3
|
+
#
|
4
|
+
# Within the project file the PBXFileReference represents the object
|
5
|
+
# representation to the file on the file sytem. This is usually your source
|
6
|
+
# files within your project.
|
7
|
+
#
|
8
|
+
module FileReference
|
9
|
+
|
10
|
+
# This is the group for which this file is contained within.
|
11
|
+
attr_accessor :supergroup
|
12
|
+
|
13
|
+
def self.file(properties)
|
14
|
+
default_properties = { 'isa' => 'PBXFileReference',
|
15
|
+
'path' => nil,
|
16
|
+
'sourceTree' => '<group>' }
|
17
|
+
|
18
|
+
default_properties.merge(properties)
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# Generate the properties for a framework. A name and a path option need
|
23
|
+
# to be specified in the properties
|
24
|
+
#
|
25
|
+
# @note a 'name' and 'path' key need to be specified in the framework for
|
26
|
+
# the framework to be added correctly.
|
27
|
+
#
|
28
|
+
# @param [Hash] properties to override for the Framework
|
29
|
+
# @return [Hash] properties for a framework
|
30
|
+
#
|
31
|
+
def self.framework(properties)
|
32
|
+
default_properties = { 'isa' => "PBXFileReference",
|
33
|
+
'lastKnownFileType' => "wrapper.framework",
|
34
|
+
'name' => "FRAMEWORK.framework",
|
35
|
+
'path' => "FRAMEWORK.framework",
|
36
|
+
'sourceTree' => "<group>" }
|
37
|
+
|
38
|
+
default_properties.merge(properties)
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Generate the properties for a system framework.
|
43
|
+
#
|
44
|
+
# @param [String] name of the system framework
|
45
|
+
# @param [Hash] properties the parameters to override for the system framework
|
46
|
+
#
|
47
|
+
def self.system_framework(name,properties = {})
|
48
|
+
default_properties = { 'isa' => 'PBXFileReference',
|
49
|
+
'lastKnownFileType' => 'wrapper.framework',
|
50
|
+
'name' => "#{name}.framework",
|
51
|
+
'path' => "System/Library/Frameworks/#{name}.framework",
|
52
|
+
"sourceTree" => "SDKROOT" }
|
53
|
+
|
54
|
+
default_properties.merge(properties)
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# @example app product properties
|
59
|
+
#
|
60
|
+
# E21D8AAA14E0F817002E56AA /* newtarget.app */ = {
|
61
|
+
# isa = PBXFileReference;
|
62
|
+
# explicitFileType = wrapper.application;
|
63
|
+
# includeInIndex = 0;
|
64
|
+
# path = newtarget.app;
|
65
|
+
# sourceTree = BUILT_PRODUCTS_DIR; };
|
66
|
+
#
|
67
|
+
def self.app_product(name)
|
68
|
+
{ 'isa' => 'PBXFileReference',
|
69
|
+
'explicitFileType' => 'wrapper.application',
|
70
|
+
'includeInIndex' => 0,
|
71
|
+
'path' => "#{name}.app",
|
72
|
+
'sourceTree' => "BUILT_PRODUCTS_DIR" }
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
data/lib/xcode/group.rb
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
module Xcode
|
2
|
+
|
3
|
+
#
|
4
|
+
# Within the project file there are logical separation of resources into groups
|
5
|
+
# these groups may contain subgroups, files, or other objects. They have
|
6
|
+
# children.
|
7
|
+
#
|
8
|
+
# Group here provides the methods to traverse the groups to find these
|
9
|
+
# children resources as well as provide the ability to generate child
|
10
|
+
# resources.
|
11
|
+
#
|
12
|
+
# 7165D451146B4EA100DE2F0E /* Products */ = {
|
13
|
+
# isa = PBXGroup;
|
14
|
+
# children = (
|
15
|
+
# 7165D450146B4EA100DE2F0E /* TestProject.app */,
|
16
|
+
# 7165D46B146B4EA100DE2F0E /* TestProjectTests.octest */,
|
17
|
+
# E21EB9D614E357CF0058122A /* Specs.app */,
|
18
|
+
# );
|
19
|
+
# name = Products;
|
20
|
+
# sourceTree = "<group>";
|
21
|
+
# };
|
22
|
+
#
|
23
|
+
module Group
|
24
|
+
|
25
|
+
#
|
26
|
+
# Return the hash that maps to the properties for a logical group
|
27
|
+
#
|
28
|
+
# @param [String] name of the logical group
|
29
|
+
#
|
30
|
+
def self.logical_group(name)
|
31
|
+
{ 'isa' => 'PBXGroup',
|
32
|
+
'name' => name,
|
33
|
+
'sourceTree' => '<group>',
|
34
|
+
'children' => [] }
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# This is the group for which this file is contained within.
|
39
|
+
# @note this value is only set if the group has been discovered
|
40
|
+
# by traversing groups to this group.
|
41
|
+
attr_accessor :supergroup
|
42
|
+
|
43
|
+
#
|
44
|
+
# @example Return all the sub-groups of the main group
|
45
|
+
#
|
46
|
+
# main_group = Xcode.project('MyProject.xcodeproj').groups
|
47
|
+
# main_group.groups
|
48
|
+
#
|
49
|
+
# @return [Array] the sub-groups contained within this group.
|
50
|
+
#
|
51
|
+
def groups
|
52
|
+
children.find_all {|child| child.is_a?(Group) }.map do |group|
|
53
|
+
group.supergroup = self
|
54
|
+
group
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Find all the child groups that have a name that matches the specified name.
|
60
|
+
#
|
61
|
+
# @param [String] name of the group that you are looking to return.
|
62
|
+
# @return [Array<Group>] the groups with the same matching name. This
|
63
|
+
# could be no groups, one group, or multiple groups.
|
64
|
+
#
|
65
|
+
def group(name)
|
66
|
+
groups.find_all {|group| group.name == name or group.path == name }
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Find all the non-group objects within the group and return them
|
71
|
+
# @return [Array] the children of the group, excluding the groups
|
72
|
+
#
|
73
|
+
def files
|
74
|
+
children.reject {|child| child.is_a?(Group) }
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Find all the files that have have a name that matches the specified name.
|
79
|
+
#
|
80
|
+
# @param [String] name of the file that you are looking to return.
|
81
|
+
# @return [Array<FileReference>] the files with the same mathching
|
82
|
+
# name. This could be no files, one file, or multiple files.
|
83
|
+
#
|
84
|
+
def file(name)
|
85
|
+
files.find_all {|file| file.name == name or file.path == name }
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Check whether a file or group is contained within this group.
|
90
|
+
#
|
91
|
+
def exists?(name)
|
92
|
+
children.find_all {|child| child.name == name }
|
93
|
+
end
|
94
|
+
#
|
95
|
+
# Adds a group as a child to current group with the given name.
|
96
|
+
#
|
97
|
+
# @note A group may be added that has the same name as another group as they
|
98
|
+
# are distinguished by a unique identifier and not by name.
|
99
|
+
#
|
100
|
+
# @param [String] name of the group that you want to add as a child group of
|
101
|
+
# the specified group.
|
102
|
+
#
|
103
|
+
def create_group(name)
|
104
|
+
new_group = create_child_object Group.logical_group(name)
|
105
|
+
new_group.supergroup = self
|
106
|
+
new_group
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# Add a file to the specified group. Currently the file creation requires
|
111
|
+
# the path to the physical file.
|
112
|
+
#
|
113
|
+
# @param [String,Hash] path to the file that is being added or a hash that
|
114
|
+
# contains the values would be merged with the default values.
|
115
|
+
#
|
116
|
+
def create_file(file_properties)
|
117
|
+
# This allows both support for the string value or the hash as the parameter
|
118
|
+
file_properties = { 'path' => file_properties } if file_properties.is_a? String
|
119
|
+
|
120
|
+
# IF the file already exists then we will not create the file with the
|
121
|
+
# parameters that are being supplied, instead we will return what we
|
122
|
+
# found.
|
123
|
+
|
124
|
+
find_file_by = file_properties['name'] || file_properties['path']
|
125
|
+
found_or_created_file = exists?(find_file_by).first
|
126
|
+
|
127
|
+
unless found_or_created_file
|
128
|
+
found_or_created_file = create_child_object FileReference.file(file_properties)
|
129
|
+
end
|
130
|
+
|
131
|
+
found_or_created_file
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# Create a framework within this group.
|
136
|
+
#
|
137
|
+
# @param [Hash] framework_properties the properties to merge with the default
|
138
|
+
# properties.
|
139
|
+
#
|
140
|
+
def create_framework(framework_properties)
|
141
|
+
create_child_object FileReference.framework(framework_properties)
|
142
|
+
end
|
143
|
+
|
144
|
+
#
|
145
|
+
# Create a system framework reference within this group
|
146
|
+
#
|
147
|
+
# @param [String] name the name of the System framework to add to this group.
|
148
|
+
#
|
149
|
+
def create_system_framework(name)
|
150
|
+
create_child_object FileReference.system_framework(name)
|
151
|
+
end
|
152
|
+
|
153
|
+
#
|
154
|
+
# Create an infoplist within this group.
|
155
|
+
#
|
156
|
+
# @param [Hash] infoplist_properties the properties to merge with the default
|
157
|
+
# properties.
|
158
|
+
#
|
159
|
+
# @see VariantGroup#info_plist
|
160
|
+
#
|
161
|
+
def create_infoplist(infoplist_properties)
|
162
|
+
create_child_object VariantGroup.info_plist(infoplist_properties)
|
163
|
+
end
|
164
|
+
|
165
|
+
#
|
166
|
+
# Create a product reference witin this group.
|
167
|
+
#
|
168
|
+
# @note this is usually performed through the target as it is necessary within
|
169
|
+
# the target to specify what is the product reference.
|
170
|
+
#
|
171
|
+
# @see Target#create_product_reference
|
172
|
+
#
|
173
|
+
# @param [String] name the name of the product to generate
|
174
|
+
#
|
175
|
+
def create_product_reference(name)
|
176
|
+
create_child_object FileReference.app_product(name)
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
#
|
182
|
+
# This method is used internally to add objects to the registry and add the
|
183
|
+
# object as a child of this group.
|
184
|
+
#
|
185
|
+
# @param [Hash] child_as_properties the hash of resource to add as a child
|
186
|
+
# object of this group.
|
187
|
+
#
|
188
|
+
# @return [Resource] returns the resource that was added a child
|
189
|
+
def create_child_object(child_properties)
|
190
|
+
child_object = @registry.add_object child_properties
|
191
|
+
@properties['children'] << child_object.identifier
|
192
|
+
child_object
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'plist'
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
module Xcode
|
5
|
+
|
6
|
+
#
|
7
|
+
# @see https://developer.apple.com/library/ios/#documentation/general/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html
|
8
|
+
#
|
9
|
+
class InfoPlist
|
10
|
+
def initialize(config, plist_location)
|
11
|
+
@config = config
|
12
|
+
|
13
|
+
@plist_location = File.expand_path("#{File.dirname(@config.target.project.path)}/#{plist_location}")
|
14
|
+
unless File.exists?(@plist_location)
|
15
|
+
puts 'Plist not found ' + @plist_location
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
@plist = Plist::parse_xml(@plist_location)
|
19
|
+
end
|
20
|
+
|
21
|
+
def marketing_version
|
22
|
+
@plist['CFBundleShortVersionString']
|
23
|
+
end
|
24
|
+
|
25
|
+
def marketing_version=(version)
|
26
|
+
@plist['CFBundleShortVersionString'] = version
|
27
|
+
end
|
28
|
+
|
29
|
+
def version
|
30
|
+
@plist['CFBundleVersion']
|
31
|
+
end
|
32
|
+
|
33
|
+
def version=(version)
|
34
|
+
@plist['CFBundleVersion'] = version.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def save
|
38
|
+
File.open(@plist_location, 'w') {|f| f << @plist.to_plist}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Xcode
|
2
|
+
class Keychain
|
3
|
+
attr_accessor :name, :path
|
4
|
+
|
5
|
+
TEMP_PASSWORD = "build_keychain_password"
|
6
|
+
|
7
|
+
#
|
8
|
+
# Open the keychain with the specified name. It is assumed that keychains reside in the
|
9
|
+
# ~/Library/Keychains directory
|
10
|
+
#
|
11
|
+
# @param [String] the name of the keychain
|
12
|
+
#
|
13
|
+
def initialize(name)
|
14
|
+
@name = name
|
15
|
+
@path = File.expand_path "~/Library/Keychains/#{name}"
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Import the .p12 certificate file into the keychain using the provided password
|
20
|
+
#
|
21
|
+
# @param [String] the path to the .p12 certificate file
|
22
|
+
# @param [String] the password to open the certificate file
|
23
|
+
#
|
24
|
+
def import(cert, password)
|
25
|
+
cmd = []
|
26
|
+
cmd << "security"
|
27
|
+
cmd << "import '#{cert}'"
|
28
|
+
cmd << "-k '#{@path}'"
|
29
|
+
cmd << "-P #{password}"
|
30
|
+
cmd << "-T /usr/bin/codesign"
|
31
|
+
Xcode::Shell.execute(cmd)
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Returns a list of identities in the keychain.
|
36
|
+
#
|
37
|
+
# @return [Array<String>] a list of identity names
|
38
|
+
#
|
39
|
+
def identities
|
40
|
+
names = []
|
41
|
+
cmd = []
|
42
|
+
cmd << "security"
|
43
|
+
cmd << "find-certificate"
|
44
|
+
cmd << "-a"
|
45
|
+
cmd << "#{@name}"
|
46
|
+
data = Xcode::Shell.execute(cmd, false).join("")
|
47
|
+
data.scan /\s+"labl"<blob>="([^"]+)"/ do |m|
|
48
|
+
names << m[0]
|
49
|
+
end
|
50
|
+
names
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Unlock the keychain using the provided password
|
55
|
+
#
|
56
|
+
# @param [String] the password to open the keychain
|
57
|
+
#
|
58
|
+
def unlock(password)
|
59
|
+
cmd = []
|
60
|
+
cmd << "security"
|
61
|
+
cmd << "unlock-keychain"
|
62
|
+
cmd << "-p #{password}"
|
63
|
+
cmd << "#{@name}"
|
64
|
+
Xcode::Shell.execute(cmd)
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Create a new keychain with the given name and password
|
69
|
+
#
|
70
|
+
# @param [String] the name for the new keychain
|
71
|
+
# @param [String] the password for the new keychain
|
72
|
+
# @return [Xcode::Keychain] an object representing the new keychain
|
73
|
+
#
|
74
|
+
def self.create(name, password)
|
75
|
+
cmd = []
|
76
|
+
cmd << "security"
|
77
|
+
cmd << "create-keychain"
|
78
|
+
cmd << "-p #{password}"
|
79
|
+
cmd << "#{name}"
|
80
|
+
Xcode::Shell.execute(cmd)
|
81
|
+
|
82
|
+
Xcode::Keychain.new(name)
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Remove the keychain from the filesystem
|
87
|
+
#
|
88
|
+
# FIXME: dangerous
|
89
|
+
#
|
90
|
+
def delete
|
91
|
+
cmd = []
|
92
|
+
cmd << "security"
|
93
|
+
cmd << "delete-keychain #{name}"
|
94
|
+
Xcode::Shell.execute(cmd)
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# Creates a keychain with the given name that lasts for the duration of the provided block.
|
99
|
+
# The keychain is deleted even if the block throws an exception.
|
100
|
+
#
|
101
|
+
# @param [String] the name of the temporary keychain to create
|
102
|
+
#
|
103
|
+
def self.temp_keychain(name, &block)
|
104
|
+
kc = Xcode::Keychain.create(name, TEMP_PASSWORD)
|
105
|
+
begin
|
106
|
+
kc.unlock(TEMP_PASSWORD)
|
107
|
+
block.call(kc)
|
108
|
+
ensure
|
109
|
+
kc.delete
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Opens the default login.keychain for current user
|
115
|
+
#
|
116
|
+
# @return [Xcode::Keychain] the current user's login keychain
|
117
|
+
#
|
118
|
+
def self.login_keychain
|
119
|
+
Xcode::Keychain.new("login.keychain")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'plist'
|
3
|
+
|
4
|
+
module Xcode
|
5
|
+
|
6
|
+
module PLUTILProjectParser
|
7
|
+
extend self
|
8
|
+
|
9
|
+
#
|
10
|
+
# Using the sytem tool plutil, the specified project file is parsed and
|
11
|
+
# converted to XML, and then converted into a ruby hash object.
|
12
|
+
#
|
13
|
+
def parse path
|
14
|
+
xml = `plutil -convert xml1 -o - "#{path}"`
|
15
|
+
Plist::parse_xml(xml)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|