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