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