xcjobs 0.0.1

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.
@@ -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