xcjobs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,276 @@
1
+ require 'rake/tasklib'
2
+ require 'rake/clean'
3
+
4
+ module XCJobs
5
+ class Xcodebuild < Rake::TaskLib
6
+ include Rake::DSL if defined?(Rake::DSL)
7
+
8
+ attr_accessor :name
9
+ attr_accessor :project
10
+ attr_accessor :target
11
+ attr_accessor :workspace
12
+ attr_accessor :scheme
13
+ attr_accessor :sdk
14
+ attr_accessor :configuration
15
+ attr_accessor :signing_identity
16
+ attr_accessor :provisioning_profile
17
+ attr_accessor :build_dir
18
+ attr_accessor :formatter
19
+
20
+ attr_reader :destinations
21
+ attr_reader :provisioning_profile_name
22
+ attr_reader :provisioning_profile_uuid
23
+
24
+ def initialize(name)
25
+ @name = name
26
+ @destinations = []
27
+ @build_settings = {}
28
+ end
29
+
30
+ def before_action(&block)
31
+ @before_action = block
32
+ end
33
+
34
+ def after_action(&block)
35
+ @after_action = block
36
+ end
37
+
38
+ def provisioning_profile=(provisioning_profile)
39
+ @provisioning_profile = provisioning_profile
40
+
41
+ if File.file?(provisioning_profile)
42
+ @provisioning_profile_path = provisioning_profile
43
+ else
44
+ path = File.join("#{Dir.home}/Library/MobileDevice/Provisioning Profiles/", provisioning_profile)
45
+ if File.file?(path)
46
+ @provisioning_profile_path = path
47
+ end
48
+ end
49
+ if @provisioning_profile_path
50
+ out, status = Open3.capture2 %[/usr/libexec/PlistBuddy -c Print:UUID /dev/stdin <<< $(security cms -D -i "#{@provisioning_profile_path}")]
51
+ @provisioning_profile_uuid = out.strip if status.success?
52
+
53
+ out, status = Open3.capture2 %[/usr/libexec/PlistBuddy -c Print:Name /dev/stdin <<< $(security cms -D -i "#{@provisioning_profile_path}")]
54
+ @provisioning_profile_name = out.strip if status.success?
55
+ else
56
+ @provisioning_profile_name = provisioning_profile
57
+ end
58
+ end
59
+
60
+ def add_destination(destination)
61
+ @destinations << destination
62
+ end
63
+
64
+ def add_build_setting(setting, value)
65
+ @build_settings[setting] = value
66
+ end
67
+
68
+ private
69
+
70
+ def run(cmd)
71
+ @before_action.call if @before_action
72
+
73
+ puts cmd.join(" ")
74
+
75
+ if @formatter
76
+ Open3.pipeline_r(cmd, [@formatter]) do |stdout, wait_thrs|
77
+ output = []
78
+ while line = stdout.gets
79
+ puts line
80
+ output << line
81
+ end
82
+
83
+ status = wait_thrs.first.value
84
+ if status.success?
85
+ @after_action.call(output, status) if @after_action
86
+ else
87
+ fail "xcodebuild failed (exited with status: #{status.exitstatus})"
88
+ end
89
+ end
90
+ else
91
+ Open3.popen2e(*cmd) do |stdin, stdout_err, wait_thr|
92
+ output = []
93
+ while line = stdout_err.gets
94
+ puts line
95
+ output << line
96
+ end
97
+
98
+ status = wait_thr.value
99
+ if status.success?
100
+ @after_action.call(output, status) if @after_action
101
+ else
102
+ fail "xcodebuild failed (exited with status: #{status.exitstatus})"
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ def options
109
+ [].tap do |opts|
110
+ opts.concat(['-project', project]) if project
111
+ opts.concat(['-target', target]) if target
112
+ opts.concat(['-workspace', workspace]) if workspace
113
+ opts.concat(['-scheme', scheme]) if scheme
114
+ opts.concat(['-sdk', sdk]) if sdk
115
+ opts.concat(['-configuration', configuration]) if configuration
116
+ opts.concat(['-derivedDataPath', build_dir]) if build_dir
117
+
118
+ @destinations.each do |destination|
119
+ opts.concat(['-destination', destination])
120
+ end
121
+
122
+ @build_settings.each do |setting, value|
123
+ opts << "#{setting}=#{value}"
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ class Test < Xcodebuild
130
+ def initialize(name = :test)
131
+ super
132
+ yield self if block_given?
133
+ define
134
+ end
135
+
136
+ def sdk
137
+ @sdk || 'iphonesimulator'
138
+ end
139
+
140
+ private
141
+
142
+ def define
143
+ raise 'test action requires specifying a scheme' unless scheme
144
+ raise 'cannot specify both a scheme and targets' if scheme && target
145
+
146
+ desc 'test application'
147
+ task @name do
148
+ if sdk == 'iphonesimulator'
149
+ add_build_setting('CODE_SIGN_IDENTITY', '""')
150
+ add_build_setting('CODE_SIGNING_REQUIRED', 'NO')
151
+ add_build_setting('GCC_SYMBOLS_PRIVATE_EXTERN', 'NO')
152
+ end
153
+
154
+ run(['xcodebuild', 'test'] + options)
155
+ end
156
+ end
157
+ end
158
+
159
+ class Build < Xcodebuild
160
+ def initialize(name = :build)
161
+ super
162
+ yield self if block_given?
163
+ define
164
+ end
165
+
166
+ private
167
+
168
+ def define
169
+ raise 'the scheme is required when specifying build_dir' if build_dir && !scheme
170
+ raise 'cannot specify both a scheme and targets' if scheme && target
171
+
172
+ CLEAN.include(build_dir)
173
+ CLOBBER.include(build_dir)
174
+
175
+ desc 'build application'
176
+ task @name do
177
+ add_build_setting('CONFIGURATION_TEMP_DIR', File.join(build_dir, 'temp')) if build_dir
178
+ add_build_setting('CODE_SIGN_IDENTITY', signing_identity) if signing_identity
179
+ add_build_setting('PROVISIONING_PROFILE', provisioning_profile_uuid) if provisioning_profile_uuid
180
+
181
+ run(['xcodebuild', 'build'] + options)
182
+ end
183
+ end
184
+ end
185
+
186
+ class Archive < Xcodebuild
187
+ attr_accessor :archivePath
188
+
189
+ def initialize(name = :archive)
190
+ super
191
+ yield self if block_given?
192
+ define
193
+ end
194
+
195
+ private
196
+
197
+ def define
198
+ raise 'archive action requires specifying a scheme' unless scheme
199
+ raise 'cannot specify both a scheme and targets' if scheme && target
200
+
201
+ CLEAN.include(build_dir)
202
+ CLOBBER.include(build_dir)
203
+
204
+ desc 'make xcarchive'
205
+ namespace :build do
206
+ task @name do
207
+ add_build_setting('CONFIGURATION_TEMP_DIR', File.join(build_dir, 'temp')) if build_dir
208
+ add_build_setting('CODE_SIGN_IDENTITY', signing_identity) if signing_identity
209
+ add_build_setting('PROVISIONING_PROFILE', provisioning_profile_uuid) if provisioning_profile_uuid
210
+
211
+ run(['xcodebuild', 'archive'] + options)
212
+
213
+ sh %[(cd "#{build_dir}"; zip -ryq "dSYMs.zip" #{File.join("#{scheme}.xcarchive", "dSYMs")})] if build_dir && scheme
214
+ sh %[(cd "#{build_dir}"; zip -ryq #{scheme}.xcarchive.zip #{scheme}.xcarchive)] if build_dir && scheme
215
+ end
216
+ end
217
+ end
218
+
219
+ def archivePath
220
+ @archivePath || (build_dir && scheme ? File.join(build_dir, scheme) : nil)
221
+ end
222
+
223
+ def options
224
+ super.tap do |opts|
225
+ opts.concat(['-archivePath', archivePath]) if archivePath
226
+ end
227
+ end
228
+ end
229
+
230
+ class Export < Xcodebuild
231
+ attr_accessor :archivePath
232
+ attr_accessor :exportFormat
233
+ attr_accessor :exportPath
234
+ attr_accessor :exportProvisioningProfile
235
+ attr_accessor :exportSigningIdentity
236
+ attr_accessor :exportInstallerIdentity
237
+ attr_accessor :exportWithOriginalSigningIdentity
238
+
239
+ def initialize(name = :export)
240
+ super
241
+ yield self if block_given?
242
+ define
243
+ end
244
+
245
+ private
246
+
247
+ def define
248
+ desc 'export from an archive'
249
+ namespace :build do
250
+ task name do
251
+ run(['xcodebuild', '-exportArchive'] + options)
252
+ end
253
+ end
254
+ end
255
+
256
+ def archivePath
257
+ @archivePath || (build_dir && scheme ? File.join(build_dir, scheme) : nil)
258
+ end
259
+
260
+ def exportFormat
261
+ @exportFormat || 'IPA'
262
+ end
263
+
264
+ def options
265
+ [].tap do |opts|
266
+ opts.concat(['-archivePath', archivePath]) if archivePath
267
+ opts.concat(['-exportFormat', exportFormat]) if exportFormat
268
+ opts.concat(['-exportPath', exportPath]) if exportPath
269
+ opts.concat(['-exportProvisioningProfile', exportProvisioningProfile]) if exportProvisioningProfile
270
+ opts.concat(['-exportSigningIdentity', exportSigningIdentity]) if exportSigningIdentity
271
+ opts.concat(['-exportInstallerIdentity', exportInstallerIdentity]) if exportInstallerIdentity
272
+ opts.concat(['-exportWithOriginalSigningIdentity']) if exportWithOriginalSigningIdentity
273
+ end
274
+ end
275
+ end
276
+ end
data/lib/xcjobs.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "xcjobs/version"
2
+ require "xcjobs/xcodebuild"
3
+ require "xcjobs/certificate"
4
+ require "xcjobs/distribute"
5
+ require "xcjobs/infoplist"
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe XCJobs::Certificate do
4
+ before(:each) do
5
+ Rake.application = rake
6
+ end
7
+
8
+ let(:rake) { Rake::Application.new }
9
+
10
+ describe XCJobs::Certificate do
11
+ describe 'simple task' do
12
+ let!(:task) do
13
+ XCJobs::Certificate.new do |t|
14
+ passphrase = "password1234"
15
+
16
+ t.add_certificate('./certificates/apple.cer')
17
+ t.add_certificate('./certificates/appstore.cer')
18
+ t.add_certificate('./certificates/appstore.p12', passphrase)
19
+
20
+ t.add_profile('./spec/profiles/adhoc.mobileprovision')
21
+ t.add_profile('./spec/profiles/distribution.mobileprovision')
22
+ end
23
+ end
24
+
25
+ describe 'tasks' do
26
+ describe 'certificates:install' do
27
+ subject { Rake.application['certificates:install'] }
28
+
29
+ it 'defines the appropriate task' do
30
+ expect(subject.name).to eq('certificates:install')
31
+ end
32
+ end
33
+
34
+ describe 'certificates:remove' do
35
+ subject { Rake.application['certificates:remove'] }
36
+
37
+ it 'defines the appropriate task' do
38
+ expect(subject.name).to eq('certificates:remove')
39
+ end
40
+ end
41
+
42
+ describe 'profiles:install' do
43
+ subject { Rake.application['profiles:install'] }
44
+
45
+ it 'defines the appropriate task' do
46
+ expect(subject.name).to eq('profiles:install')
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,134 @@
1
+ require 'spec_helper'
2
+
3
+ describe XCJobs::Distribute do
4
+ before(:each) do
5
+ allow_any_instance_of(XCJobs::Distribute).to receive(:upload) do |object, url, form_data|
6
+ @url = url
7
+ @form_data = form_data
8
+ end
9
+
10
+ Rake.application = rake
11
+ end
12
+
13
+ let(:rake) { Rake::Application.new }
14
+
15
+ describe XCJobs::Distribute::TestFlight do
16
+ describe 'define upload ipa task' do
17
+ let(:credentials) do
18
+ { api_token: 'abcde12345efghi67890d543957972cd_MTE3NjUyMjBxMS0wOE0wNiAwMzwzMjoyNy41MTA3MzE',
19
+ team_token:'12345ab2692bd1c3093408a3399ee947_NDIzMDYyPDExLGExLTIwIDIxOjM9OjS2LjQxOTgzOA',
20
+ }
21
+ end
22
+
23
+ let(:file) do
24
+ File.join('build', 'Example.ipa')
25
+ end
26
+
27
+ let(:notes) { "Uploaded: #{DateTime.now.strftime("%Y/%m/%d %H:%M:%S")}" }
28
+
29
+ let!(:task) do
30
+ XCJobs::Distribute::TestFlight.new do |t|
31
+ t.file = file
32
+ t.api_token = credentials['api_token']
33
+ t.team_token = credentials['team_token']
34
+ t.notify = true
35
+ t.replace = true
36
+ t.distribution_lists = 'Dev'
37
+ t.notes = notes
38
+ end
39
+ end
40
+
41
+ it 'configures the ipa file path' do
42
+ expect(task.file).to eq file
43
+ end
44
+
45
+ it 'configures the api_token' do
46
+ expect(task.api_token).to eq credentials['api_token']
47
+ end
48
+
49
+ it 'configures the team_token' do
50
+ expect(task.team_token).to eq credentials['team_token']
51
+ end
52
+
53
+ it 'configures the notify' do
54
+ expect(task.notify).to eq true
55
+ end
56
+
57
+ it 'configures the replace' do
58
+ expect(task.replace).to eq true
59
+ end
60
+
61
+ it 'configures the distribution_lists' do
62
+ expect(task.distribution_lists).to eq 'Dev'
63
+ end
64
+
65
+ it 'configures the notes' do
66
+ expect(task.notes).to eq notes
67
+ end
68
+
69
+ describe 'tasks' do
70
+ describe 'distribute:testflight' do
71
+ subject { Rake.application['distribute:testflight'] }
72
+
73
+ it 'executes the appropriate commands' do
74
+ subject.invoke
75
+ expect(@url).to eq 'http://testflightapp.com/api/builds.json'
76
+ expect(@form_data).to eq({
77
+ file: "@#{file}",
78
+ notify: true,
79
+ replace: true,
80
+ distribution_lists: 'Dev',
81
+ notes: notes,
82
+ })
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ describe XCJobs::Distribute::Crittercism do
90
+ describe 'define upload dSYMs task' do
91
+ let(:credentials) do
92
+ { app_id: '123456789abcdefg12345678',
93
+ key:'abcdefghijklmnopqrstuvwxyz123456',
94
+ }
95
+ end
96
+
97
+ let(:dsym_file) do
98
+ File.join('build', 'dSYMs.zip')
99
+ end
100
+
101
+ let!(:task) do
102
+ XCJobs::Distribute::Crittercism.new do |t|
103
+ t.app_id = credentials['app_id']
104
+ t.key = credentials['key']
105
+ t.dsym = dsym_file
106
+ end
107
+ end
108
+
109
+ it 'configures the app_id' do
110
+ expect(task.app_id).to eq credentials['app_id']
111
+ end
112
+
113
+ it 'configures the key' do
114
+ expect(task.app_id).to eq credentials['key']
115
+ end
116
+
117
+ it 'configures the dsym file path' do
118
+ expect(task.dsym).to eq dsym_file
119
+ end
120
+
121
+ describe 'tasks' do
122
+ describe 'distribute:crittercism' do
123
+ subject { Rake.application['distribute:crittercism'] }
124
+
125
+ it 'executes the appropriate commands' do
126
+ subject.invoke
127
+ expect(@url).to eq "https://api.crittercism.com/api_beta/dsym/#{credentials['app_id']}"
128
+ expect(@form_data).to eq({ dsym: "@#{dsym_file}" })
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
Binary file
@@ -0,0 +1,7 @@
1
+ if ENV['CI']
2
+ require 'coveralls'
3
+ Coveralls.wear!
4
+ end
5
+
6
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
7
+ require 'xcjobs'
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe XCJobs do
4
+ it 'has a version number' do
5
+ expect(XCJobs::VERSION).not_to be nil
6
+ end
7
+ end