xcjobs 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +225 -0
- data/Rakefile +6 -0
- data/lib/xcjobs/certificate.rb +65 -0
- data/lib/xcjobs/distribute.rb +108 -0
- data/lib/xcjobs/infoplist.rb +114 -0
- data/lib/xcjobs/version.rb +3 -0
- data/lib/xcjobs/xcodebuild.rb +276 -0
- data/lib/xcjobs.rb +5 -0
- data/spec/certificate_spec.rb +52 -0
- data/spec/distribute_spec.rb +134 -0
- data/spec/profiles/adhoc.mobileprovision +0 -0
- data/spec/profiles/development.mobileprovision +0 -0
- data/spec/profiles/distribution.mobileprovision +0 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/xcjobs_spec.rb +7 -0
- data/spec/xcodebuild_spec.rb +422 -0
- data/xcjobs.gemspec +25 -0
- metadata +130 -0
@@ -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,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
|
Binary file
|
Binary file
|
data/spec/spec_helper.rb
ADDED