xcoder 0.1.15 → 0.1.18
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rbenv-version +1 -0
- data/.rvmrc +1 -1
- data/Gemfile +10 -2
- data/README.md +110 -9
- data/Rakefile +2 -2
- data/bin/xcoder +74 -0
- data/lib/xcode/builder.rb +1 -2
- data/lib/xcode/builder/base_builder.rb +231 -102
- data/lib/xcode/builder/build_parser.rb +146 -0
- data/lib/xcode/builder/project_target_config_builder.rb +2 -2
- data/lib/xcode/builder/scheme_builder.rb +29 -12
- data/lib/xcode/buildspec.rb +286 -0
- data/lib/xcode/configuration_list.rb +24 -24
- data/lib/xcode/deploy/ftp.rb +56 -0
- data/lib/xcode/deploy/kickfolio.rb +18 -0
- data/lib/xcode/deploy/s3.rb +38 -0
- data/lib/xcode/deploy/ssh.rb +43 -0
- data/lib/xcode/deploy/templates/index.rhtml +22 -0
- data/lib/xcode/deploy/templates/manifest.rhtml +31 -0
- data/lib/xcode/deploy/testflight.rb +32 -27
- data/lib/xcode/deploy/web_assets.rb +39 -0
- data/lib/xcode/info_plist.rb +16 -0
- data/lib/xcode/keychain.rb +33 -10
- data/lib/xcode/platform.rb +65 -0
- data/lib/xcode/project.rb +7 -3
- data/lib/xcode/provisioning_profile.rb +38 -2
- data/lib/xcode/scheme.rb +44 -17
- data/lib/xcode/shell/command.rb +79 -5
- data/lib/xcode/terminal_output.rb +116 -0
- data/lib/xcode/test/formatters/junit_formatter.rb +7 -2
- data/lib/xcode/test/formatters/stdout_formatter.rb +34 -25
- data/lib/xcode/test/parsers/kif_parser.rb +87 -0
- data/lib/xcode/test/parsers/ocunit_parser.rb +3 -3
- data/lib/xcode/version.rb +1 -1
- data/lib/xcode/workspace.rb +13 -5
- data/lib/xcoder.rb +33 -31
- data/spec/TestProject/TestProject.xcodeproj/project.pbxproj +1627 -1015
- data/spec/TestWorkspace.xcworkspace/contents.xcworkspacedata +7 -0
- data/spec/builder_spec.rb +87 -71
- data/spec/deploy_spec.rb +63 -0
- data/spec/ocunit_parser_spec.rb +1 -1
- data/xcoder.gemspec +3 -1
- metadata +95 -19
- data/lib/xcode/buildfile.rb +0 -101
- data/lib/xcode/shell.rb +0 -26
- data/spec/deploy_testflight_spec.rb +0 -27
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'xcode/test/report'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Xcode
|
5
|
+
module Builder
|
6
|
+
class XcodebuildParser
|
7
|
+
include Xcode::TerminalOutput
|
8
|
+
attr_accessor :suppress_warnings
|
9
|
+
|
10
|
+
KNOWN_STEPS = [
|
11
|
+
'Clean.Remove',
|
12
|
+
'Build',
|
13
|
+
'Check',
|
14
|
+
'ProcessInfoPlistFile',
|
15
|
+
'CpResource',
|
16
|
+
'ProcessPCH',
|
17
|
+
'CompileC',
|
18
|
+
'Ld',
|
19
|
+
'CreateUniversalBinary',
|
20
|
+
'GenerateDSYMFile',
|
21
|
+
'CopyPNGFile',
|
22
|
+
'CompileXIB',
|
23
|
+
'CopyStringsFile',
|
24
|
+
'ProcessProductPackaging',
|
25
|
+
'Touch',
|
26
|
+
'CodeSign',
|
27
|
+
'Libtool',
|
28
|
+
'PhaseScriptExecution',
|
29
|
+
'Validate'
|
30
|
+
# 'SymLink'
|
31
|
+
]
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@last_good_index = 0
|
35
|
+
@last_step_name = nil
|
36
|
+
@last_step_params = []
|
37
|
+
@suppress_warnings = false
|
38
|
+
end
|
39
|
+
|
40
|
+
def close
|
41
|
+
end
|
42
|
+
|
43
|
+
def << piped_row
|
44
|
+
piped_row = piped_row.force_encoding("UTF-8").gsub(/\n$/,'')
|
45
|
+
|
46
|
+
if piped_row=~/^\s+/
|
47
|
+
@last_step_params << piped_row
|
48
|
+
else
|
49
|
+
if piped_row=~/\=\=\=\s/
|
50
|
+
# This is just an info
|
51
|
+
elsif piped_row=~/Build settings from command line/
|
52
|
+
# Ignore
|
53
|
+
elsif piped_row=~/Check dependencies/
|
54
|
+
# Ignore
|
55
|
+
elsif piped_row==''
|
56
|
+
# Empty line, ignore
|
57
|
+
elsif piped_row=~/[A-Z]+\s\=\s/
|
58
|
+
# some build env info
|
59
|
+
elsif piped_row=~/^warning:/i
|
60
|
+
log_xcode "#{piped_row.gsub(/^warning:\s/i,'')}", :warning
|
61
|
+
elsif piped_row=~/^\[BEROR\](.*)/
|
62
|
+
log_xcode "#{$1}", :error
|
63
|
+
elsif piped_row=~/^ld: warning:/i
|
64
|
+
log_xcode "#{piped_row.gsub(/^ld: warning:\s/i,'')}", :warning
|
65
|
+
elsif piped_row=~/Unable to validate your application/
|
66
|
+
log_xcode piped_row, :warning
|
67
|
+
# print "\n warning: ", :yellow
|
68
|
+
# print " #{piped_row}"
|
69
|
+
elsif piped_row=~/bin\/libtool\:\s(.*)/
|
70
|
+
log_xcode $1, :warning, :libtool
|
71
|
+
|
72
|
+
# Pick up success
|
73
|
+
elsif piped_row=~/\*\*\s.*SUCCEEDED\s\*\*/
|
74
|
+
# yay, all good
|
75
|
+
print "\n"
|
76
|
+
|
77
|
+
elsif piped_row=~/^cp\s\-R/
|
78
|
+
# Ignore
|
79
|
+
|
80
|
+
elsif piped_row=~/\sdeclared\shere$/
|
81
|
+
# Ignore
|
82
|
+
|
83
|
+
# Pick up warnings/notes/errors
|
84
|
+
elsif piped_row=~/^(.*:\d+:\d+): (\w+): (.*)$/
|
85
|
+
# This is a warning/note/error
|
86
|
+
type = $2.downcase
|
87
|
+
level = :info
|
88
|
+
if type=="warning"
|
89
|
+
level = :warning
|
90
|
+
elsif type=="error"
|
91
|
+
level = :error
|
92
|
+
end
|
93
|
+
|
94
|
+
if (level==:warning or level==:note) and @suppress_warnings
|
95
|
+
# ignore
|
96
|
+
else
|
97
|
+
log_xcode $3, level
|
98
|
+
log_xcode " at #{$1}", level
|
99
|
+
# print "\n#{level.rjust(8)}: ", color
|
100
|
+
# print $3
|
101
|
+
# print "\n at #{$1}"
|
102
|
+
end
|
103
|
+
|
104
|
+
# If there were warnings, this will be output
|
105
|
+
elsif piped_row=~/\d+\swarning(s?)\sgenerated\./
|
106
|
+
# TODO: is this safe to ignore?
|
107
|
+
|
108
|
+
|
109
|
+
# This might be a build step
|
110
|
+
else
|
111
|
+
step = piped_row.scan(/^(\S+)/).first.first
|
112
|
+
if KNOWN_STEPS.include? step
|
113
|
+
unless @last_step_name==step
|
114
|
+
# print "\n" unless @last_step_name.nil?
|
115
|
+
@last_step_name = step
|
116
|
+
@last_step_params = []
|
117
|
+
# print "#{"run".rjust(8)}: ", :green
|
118
|
+
# print "#{step} "
|
119
|
+
end
|
120
|
+
unless @need_cr
|
121
|
+
log_xcode step+" ", :info, false
|
122
|
+
end
|
123
|
+
print '.', :green
|
124
|
+
else
|
125
|
+
# Echo unknown output
|
126
|
+
unless @suppress_warnings
|
127
|
+
log_xcode piped_row, :info
|
128
|
+
# print "\n > ", :blue
|
129
|
+
# print "#{piped_row}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
rescue => e
|
135
|
+
puts "Failed to parse '#{piped_row}' because #{e}", :red
|
136
|
+
end # <<
|
137
|
+
|
138
|
+
def log_xcode message, level, cr=true, task = :xcode
|
139
|
+
print "\n" if @need_cr
|
140
|
+
print_task task, message, level, cr
|
141
|
+
@need_cr = !cr
|
142
|
+
end
|
143
|
+
|
144
|
+
end # XcodebuildParser
|
145
|
+
end # Builder
|
146
|
+
end # Xcode
|
@@ -2,8 +2,8 @@ module Xcode
|
|
2
2
|
module Builder
|
3
3
|
class ProjectTargetConfigBuilder < BaseBuilder
|
4
4
|
|
5
|
-
def
|
6
|
-
cmd = super
|
5
|
+
def prepare_xcodebuild sdk=nil
|
6
|
+
cmd = super sdk
|
7
7
|
cmd << "-project \"#{@target.project.path}\""
|
8
8
|
cmd << "-target \"#{@target.name}\""
|
9
9
|
cmd << "-config \"#{@config.name}\""
|
@@ -1,32 +1,49 @@
|
|
1
|
-
module Xcode
|
2
|
-
|
1
|
+
module Xcode
|
2
|
+
|
3
3
|
class Workspace
|
4
4
|
def to_xcodebuild_option
|
5
5
|
"-workspace \"#{self.path}\""
|
6
6
|
end
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
class Project
|
10
10
|
def to_xcodebuild_option
|
11
11
|
"-project \"#{self.path}\""
|
12
12
|
end
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
module Builder
|
16
16
|
class SchemeBuilder < BaseBuilder
|
17
|
-
|
17
|
+
|
18
18
|
def initialize(scheme)
|
19
|
-
@scheme = scheme
|
20
|
-
|
19
|
+
@scheme = scheme
|
20
|
+
@target = @scheme.build_targets.last
|
21
|
+
super @target, @target.config(@scheme.archive_config)
|
21
22
|
end
|
22
|
-
|
23
|
-
def
|
24
|
-
cmd = super
|
25
|
-
cmd << @scheme.parent.to_xcodebuild_option
|
23
|
+
|
24
|
+
def prepare_xcodebuild sdk=nil
|
25
|
+
cmd = super sdk
|
26
|
+
cmd << @scheme.parent.to_xcodebuild_option
|
26
27
|
cmd << "-scheme \"#{@scheme.name}\""
|
28
|
+
cmd << "-configuration \"#{@scheme.archive_config}\""
|
27
29
|
cmd
|
28
30
|
end
|
31
|
+
|
32
|
+
# def prepare_test_command sdk=@sdk
|
33
|
+
# cmd = super sdk
|
34
|
+
# cmd << "-scheme \"#{@scheme.name}\""
|
35
|
+
# cmd << "-configuration \"#{@scheme.test_config}\""
|
36
|
+
# cmd
|
37
|
+
# end
|
29
38
|
|
39
|
+
def test
|
40
|
+
unless @scheme.testable?
|
41
|
+
print_task :builder, "Nothing to test", :warning
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
30
47
|
end
|
31
48
|
end
|
32
|
-
end
|
49
|
+
end
|
@@ -0,0 +1,286 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
|
4
|
+
module Xcode
|
5
|
+
|
6
|
+
class Buildspec
|
7
|
+
|
8
|
+
#
|
9
|
+
# Given a path to a Buildspec, define rake tasks
|
10
|
+
#
|
11
|
+
# @param the path to the Buildspec file, or a directory containing one
|
12
|
+
#
|
13
|
+
def self.parse(path = '.')
|
14
|
+
b = GroupBuilder.new
|
15
|
+
path = File.join(path, 'Buildspec') if File.directory? path
|
16
|
+
b.instance_eval(File.read(path))
|
17
|
+
b.generate_rake_tasks
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Given a path to a buildspec, perform the given task
|
22
|
+
#
|
23
|
+
# @param the path to the buildspec, or the directory containing one
|
24
|
+
# @param the task to invoke
|
25
|
+
#
|
26
|
+
def self.run(path, task)
|
27
|
+
self.parse path
|
28
|
+
Rake::Task[task].invoke unless task.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
class GroupBuilder < ::Rake::TaskLib
|
32
|
+
include ::Rake::DSL if defined?(::Rake::DSL)
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@groups = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def group group, &block
|
39
|
+
@groups << group.downcase
|
40
|
+
namespace group.downcase do
|
41
|
+
t = TaskBuilder.new
|
42
|
+
t.instance_eval(&block)
|
43
|
+
t.generate_rake_tasks
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def generate_rake_tasks
|
48
|
+
|
49
|
+
# namespace :all do
|
50
|
+
# define top level tasks
|
51
|
+
desc "Build all"
|
52
|
+
task :build => @groups.map {|g| "#{g}:build"}
|
53
|
+
|
54
|
+
desc "Clean all"
|
55
|
+
task :clean => @groups.map {|g| "#{g}:clean"}
|
56
|
+
|
57
|
+
desc "Package all"
|
58
|
+
task :package => @groups.map {|g| "#{g}:package"}
|
59
|
+
|
60
|
+
desc "Deploy all"
|
61
|
+
task :deploy => @groups.map {|g| "#{g}:deploy:all"}
|
62
|
+
# end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class TaskBuilder < ::Rake::TaskLib
|
67
|
+
|
68
|
+
include ::Rake::DSL if defined?(::Rake::DSL)
|
69
|
+
|
70
|
+
def initialize
|
71
|
+
@before = lambda {|builder| return nil }
|
72
|
+
@deployments = []
|
73
|
+
@build_number = lambda do
|
74
|
+
timestamp = Time.now.strftime("%Y%m%d%H%M%S")
|
75
|
+
ENV['BUILD_NUMBER']||"SNAPSHOT-#{Socket.gethostname}-#{timestamp}"
|
76
|
+
end
|
77
|
+
|
78
|
+
# @profile = "Provisioning/#{name}.mobileprovision"
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# Use the given project/workspace file
|
83
|
+
#
|
84
|
+
# This must be provided.
|
85
|
+
#
|
86
|
+
# @param the path to the .xcodeproj or .xcworkspace
|
87
|
+
# @param a hash containing {:scheme => ''} or {:target => '', :config => ''}
|
88
|
+
#
|
89
|
+
def use filename, args={}
|
90
|
+
@filename = filename
|
91
|
+
@args = args
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# A block to run before each builder invocation
|
96
|
+
#
|
97
|
+
# If supplied, the block will be yielded the builder object just before the invocation
|
98
|
+
# of clean/build/package
|
99
|
+
#
|
100
|
+
# @param the block to call
|
101
|
+
#
|
102
|
+
def before &block
|
103
|
+
@before = block
|
104
|
+
end
|
105
|
+
|
106
|
+
#
|
107
|
+
# A block to generate the build number
|
108
|
+
#
|
109
|
+
# This is optional, by default it will use ENV['BUILD_NUMBER'] or create a snapshot
|
110
|
+
# filename based on the hostname and time
|
111
|
+
#
|
112
|
+
# @param the block to generate the build number
|
113
|
+
#
|
114
|
+
def build_number &block
|
115
|
+
@build_number = block
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Configure a keychain to use (optional)
|
120
|
+
#
|
121
|
+
# If specified, the keychain at the given path will be unlocked during the build and
|
122
|
+
# the first identity will be set on the builder
|
123
|
+
#
|
124
|
+
# @param the path to the keychain
|
125
|
+
# @param the password to unlock the keychain
|
126
|
+
#
|
127
|
+
def keychain path, password = nil
|
128
|
+
@keychain = {:path => path, :password => password}
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
#
|
133
|
+
# Specify the platform to build for
|
134
|
+
#
|
135
|
+
# @param the platform, can be 'iphone', 'iphonesimulator', 'macosx'
|
136
|
+
# @param the version, can be any valid, installed sdk version for the appropriate platform or nil
|
137
|
+
def platform name, version=nil
|
138
|
+
@platform = Xcode::Platforms.find name, version
|
139
|
+
end
|
140
|
+
|
141
|
+
#
|
142
|
+
# Set's the identity to use to sign the package
|
143
|
+
#
|
144
|
+
# This should be the name of the identity in your keychain, such as
|
145
|
+
# 'iPhone Distribution: My Name'
|
146
|
+
#
|
147
|
+
# @param the name of the identity
|
148
|
+
#
|
149
|
+
def identity identity
|
150
|
+
@identity = identity
|
151
|
+
end
|
152
|
+
|
153
|
+
#
|
154
|
+
# Set the profile (i.e. .mobileprovision) to use
|
155
|
+
#
|
156
|
+
# @param the name or path to the profile
|
157
|
+
def profile profile
|
158
|
+
@profile = profile
|
159
|
+
end
|
160
|
+
|
161
|
+
#
|
162
|
+
# Set a deployment target.
|
163
|
+
#
|
164
|
+
# This will configure a set of deploy: targets that
|
165
|
+
# will send the .ipa to various services (testflight, s3, ftp, sftp, etc)
|
166
|
+
#
|
167
|
+
# @param the deployment type (testflight, etc)
|
168
|
+
# @param arguments to pass to the deployment type
|
169
|
+
#
|
170
|
+
def deploy type, args = {}
|
171
|
+
@deployments << {:type => type, :args => args}
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
def notify type, args = {}
|
176
|
+
@notifications << {:type => type, :args => args}
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
#
|
181
|
+
# Internally used to lazily instantiate the builder given the properties that
|
182
|
+
# have been set.
|
183
|
+
#
|
184
|
+
# @return the appropriate builder
|
185
|
+
#
|
186
|
+
def builder
|
187
|
+
return @builder unless @builder.nil?
|
188
|
+
|
189
|
+
raise "profile must be defined" if @profile.nil?
|
190
|
+
raise "project/workspace must be defined" if @filename.nil?
|
191
|
+
|
192
|
+
begin
|
193
|
+
project = Xcode.project @filename
|
194
|
+
@builder = project.target(@args[:target]).config(@args[:config]).builder
|
195
|
+
rescue
|
196
|
+
workspace = Xcode.workspace @filename
|
197
|
+
@builder = workspace.scheme(@args[:scheme]).builder
|
198
|
+
rescue
|
199
|
+
raise "You must provide a project or workspace"
|
200
|
+
end
|
201
|
+
|
202
|
+
raise "Could not create a builder using #{@args}" if @builder.nil?
|
203
|
+
|
204
|
+
unless @platform.nil?
|
205
|
+
builder.sdk = @platform.sdk
|
206
|
+
end
|
207
|
+
|
208
|
+
unless @identity.nil?
|
209
|
+
builder.identity = @identity
|
210
|
+
end
|
211
|
+
|
212
|
+
unless @keychain.nil?
|
213
|
+
keychain = Xcode::Keychain.new @keychain[:path]
|
214
|
+
keychain.unlock @keychain[:password] unless @keychain[:password].nil?
|
215
|
+
|
216
|
+
builder.identity = keychain.identities.first
|
217
|
+
builder.keychain = keychain
|
218
|
+
end
|
219
|
+
|
220
|
+
builder.profile = @profile
|
221
|
+
|
222
|
+
@before.call builder
|
223
|
+
|
224
|
+
@builder
|
225
|
+
end
|
226
|
+
|
227
|
+
def project_name
|
228
|
+
builder.product_name
|
229
|
+
end
|
230
|
+
|
231
|
+
#
|
232
|
+
# Create a set of rake tasks for this buildspec
|
233
|
+
#
|
234
|
+
def generate_rake_tasks
|
235
|
+
require 'socket'
|
236
|
+
|
237
|
+
# namespace project_name.downcase do
|
238
|
+
|
239
|
+
desc "Clean #{project_name}"
|
240
|
+
task :clean do
|
241
|
+
builder.clean
|
242
|
+
end
|
243
|
+
|
244
|
+
desc "Fetch dependencies for #{project_name}"
|
245
|
+
task :deps do
|
246
|
+
builder.dependencies
|
247
|
+
end
|
248
|
+
|
249
|
+
desc "Build #{project_name}"
|
250
|
+
task :build => [:clean, :deps] do
|
251
|
+
builder.config.info_plist do |info|
|
252
|
+
info.version = @build_number.call
|
253
|
+
info.save
|
254
|
+
end
|
255
|
+
builder.build
|
256
|
+
end
|
257
|
+
|
258
|
+
desc "Test #{project_name}"
|
259
|
+
task :test => [:deps] do
|
260
|
+
builder.test
|
261
|
+
end
|
262
|
+
|
263
|
+
desc "Package (.ipa & .dSYM.zip) #{project_name}"
|
264
|
+
task :package => [:build, :test] do
|
265
|
+
builder.package
|
266
|
+
end
|
267
|
+
|
268
|
+
namespace :deploy do
|
269
|
+
@deployments.each do |deployment|
|
270
|
+
desc "Deploy #{project_name} to #{deployment[:type]}"
|
271
|
+
task deployment[:type] => [:package] do
|
272
|
+
builder.deploy deployment[:type], deployment[:args]
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
desc "Deploy #{project_name} to all"
|
277
|
+
task :all => [:package]+(@deployments.map{|k,v| k[:type]}) do
|
278
|
+
puts "Deployed to all"
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
# end
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
286
|
+
end
|