xcoder 0.1.15 → 0.1.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +6 -0
  2. data/.rbenv-version +1 -0
  3. data/.rvmrc +1 -1
  4. data/Gemfile +10 -2
  5. data/README.md +110 -9
  6. data/Rakefile +2 -2
  7. data/bin/xcoder +74 -0
  8. data/lib/xcode/builder.rb +1 -2
  9. data/lib/xcode/builder/base_builder.rb +231 -102
  10. data/lib/xcode/builder/build_parser.rb +146 -0
  11. data/lib/xcode/builder/project_target_config_builder.rb +2 -2
  12. data/lib/xcode/builder/scheme_builder.rb +29 -12
  13. data/lib/xcode/buildspec.rb +286 -0
  14. data/lib/xcode/configuration_list.rb +24 -24
  15. data/lib/xcode/deploy/ftp.rb +56 -0
  16. data/lib/xcode/deploy/kickfolio.rb +18 -0
  17. data/lib/xcode/deploy/s3.rb +38 -0
  18. data/lib/xcode/deploy/ssh.rb +43 -0
  19. data/lib/xcode/deploy/templates/index.rhtml +22 -0
  20. data/lib/xcode/deploy/templates/manifest.rhtml +31 -0
  21. data/lib/xcode/deploy/testflight.rb +32 -27
  22. data/lib/xcode/deploy/web_assets.rb +39 -0
  23. data/lib/xcode/info_plist.rb +16 -0
  24. data/lib/xcode/keychain.rb +33 -10
  25. data/lib/xcode/platform.rb +65 -0
  26. data/lib/xcode/project.rb +7 -3
  27. data/lib/xcode/provisioning_profile.rb +38 -2
  28. data/lib/xcode/scheme.rb +44 -17
  29. data/lib/xcode/shell/command.rb +79 -5
  30. data/lib/xcode/terminal_output.rb +116 -0
  31. data/lib/xcode/test/formatters/junit_formatter.rb +7 -2
  32. data/lib/xcode/test/formatters/stdout_formatter.rb +34 -25
  33. data/lib/xcode/test/parsers/kif_parser.rb +87 -0
  34. data/lib/xcode/test/parsers/ocunit_parser.rb +3 -3
  35. data/lib/xcode/version.rb +1 -1
  36. data/lib/xcode/workspace.rb +13 -5
  37. data/lib/xcoder.rb +33 -31
  38. data/spec/TestProject/TestProject.xcodeproj/project.pbxproj +1627 -1015
  39. data/spec/TestWorkspace.xcworkspace/contents.xcworkspacedata +7 -0
  40. data/spec/builder_spec.rb +87 -71
  41. data/spec/deploy_spec.rb +63 -0
  42. data/spec/ocunit_parser_spec.rb +1 -1
  43. data/xcoder.gemspec +3 -1
  44. metadata +95 -19
  45. data/lib/xcode/buildfile.rb +0 -101
  46. data/lib/xcode/shell.rb +0 -26
  47. 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 xcodebuild
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
- super @scheme.build_targets.last, @scheme.build_config
19
+ @scheme = scheme
20
+ @target = @scheme.build_targets.last
21
+ super @target, @target.config(@scheme.archive_config)
21
22
  end
22
-
23
- def xcodebuild
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