ruby_build_ios 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +110 -0
  3. data/example.rb +77 -0
  4. data/lib/ruby_build_ios.rb +361 -0
  5. metadata +49 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d86c0facbac4741b8325a9c178841d29c68ab964
4
+ data.tar.gz: 4034c6f566de14a9c1a09152418b91c738ef5551
5
+ SHA512:
6
+ metadata.gz: 3a6442765b90ce2bfb015140cf2027a9f1018c3d4317a8281f949de29bbc7a93161102381283abf4a544d112c1287f7cdb0145a875099c391555791b92f7c4ab
7
+ data.tar.gz: da154b9855cf4eee3d53f1af59c818e93c7b7b98dfccb8e98d5ba901be9c0ac7fccfaf199f0966c04f0e0aaf54abce80497e30a09df8d3195956a3f10677c12b
@@ -0,0 +1,110 @@
1
+ # README #
2
+
3
+ ruby_build_ios is a set of helper methods to assist in the development of build scripts for IOS mobile development.
4
+
5
+ These methods all reside in module RubyBuildIos and were developed as part of HS2's 2017 Hackathon.
6
+
7
+ ## Installation / Usage ##
8
+
9
+ Install it yourself as:
10
+
11
+ $ gem install ruby_build_ios
12
+
13
+ Then in your build script:
14
+
15
+ require 'ruby_build_ios'
16
+
17
+ ## Who do I talk to? ##
18
+
19
+ HS2 Solutions
20
+
21
+ https://www.hs2solutions.com
22
+
23
+ ## Documentation ##
24
+
25
+ Very sparse, but here it is:
26
+
27
+ build_project(project, target, configuration, build_number, output_dir)
28
+
29
+ build_workspace(workspace, scheme, configuration, build_number, archive_path)
30
+
31
+ clean(path)
32
+
33
+ copy_provision_file(file_name, uuid)
34
+
35
+ create_export_plist(file_name, key_string_pairs)
36
+
37
+ get_uuid_from_file(file_name)
38
+
39
+ install_provision_file(file_name)
40
+
41
+ install_provision_files(file_names_str)
42
+
43
+ latest_git_commit_comment(revision = '--all')
44
+
45
+ latest_git_commit_count(revision = '--all')
46
+
47
+ project_build_number(file_name = PROJECT_BUILD_NUMBER)
48
+
49
+ tag_latest_git_commit(tag, message = 'Sorry, no tag message provided.')
50
+
51
+ upload_to_hockey(app_id, api_token, note, file_to_upload)
52
+
53
+ upload_to_itunes(user, password, archive)
54
+
55
+ xcode_path()
56
+
57
+ ## Testing ##
58
+
59
+ We're hacking, so our testing is not what it would usually be. Here are some commands I used in irb to test each function:
60
+
61
+ Play this to test Module (Class) Functions
62
+
63
+ require './lib/ruby_build_ios.rb'
64
+
65
+ x=RubyBuildIos.latest_git_commit_count
66
+
67
+ x=RubyBuildIos.latest_git_commit_count('badbranch')
68
+
69
+
70
+ require './lib/ruby_build_ios.rb'
71
+
72
+ y=RubyBuildIos.latest_git_commit_comment
73
+
74
+
75
+ require './lib/ruby_build_ios.rb'
76
+
77
+ RubyBuildIos.tag_latest_git_commit('v1.0', "It is a fine day to commit!")
78
+
79
+
80
+ require './lib/ruby_build_ios.rb'
81
+
82
+ z=RubyBuildIos.project_build_number()
83
+
84
+
85
+ TODO: David, add a play here to test copy_provision_files
86
+
87
+ require './lib/ruby_build_ios.rb'
88
+
89
+ RubyBuildIos.install_provision_files("<file1>,<file2>,<file3>")
90
+
91
+
92
+ TODO: David, add a play script here to test upload_to_hockey
93
+
94
+ require './lib/ruby_build_ios.rb'
95
+
96
+ RubyBuildIos.upload_to_hockey(app_id, api_token, file_to_upload)
97
+
98
+
99
+ require './lib/ruby_build_ios.rb'
100
+
101
+ RubyBuildIos.create_export_plist('test_plist.xml', {
102
+ "ca.xxx.iphoneapp" => "XXXXX Prod App",
103
+ "ca.xxx.iphoneapp.watchkitapp" => "XXXXX Prod WKApp",
104
+ "ca.xxx.iphoneapp.watchkitapp.extension" => "XXXXX Prod WKExt" })
105
+
106
+
107
+ require './lib/ruby_build_ios.rb'
108
+
109
+ RubyBuildIos.xcode_path()
110
+
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # example.rb
4
+
5
+ require './lib/ruby_build_ios.rb'
6
+
7
+ # This code along with the build_project_workspace fills our current need for
8
+ # setting the build's version number. We have a TODO to allow for greater flexibility
9
+ # in setting build variables
10
+ def get_build_number_string()
11
+ build_number = RubyBuildIos.latest_git_commit_count()
12
+ build_number = build_number += 300000000
13
+ return build_number.to_s
14
+ end
15
+
16
+ def release_to_qa()
17
+ archive_path = './build/qa'
18
+ archive_file = archive_path + '/Example.xcarchive'
19
+
20
+ RubyBuildIos.clean(archive_path)
21
+
22
+ # Copies the provision file to the appropriately named file in ~/Library/MobileDevice/Provisioning Profiles
23
+ # where Xcode expects to find them.
24
+ RubyBuildIos.install_provision_file('./certs/EXAMPLE_QA_App.mobileprovision')
25
+ RubyBuildIos.install_provision_file('./certs/EXAMPLE_QA_WKApp.mobileprovision')
26
+ RubyBuildIos.install_provision_file('./certs/EXAMPLE_QA_WKExt.mobileprovision')
27
+
28
+ # build_workspace(workspace, scheme, configuration, build_number, archive_path)
29
+ RubyBuildIos.build_workspace('Example.xcworkspace', 'Example', 'QA', get_build_number_string(), archive_file)
30
+
31
+ #upload the app to hockey
32
+ api_token = '<YOUR HOCKEY API TOKEN>'
33
+ app_id = "<YOUR HOCKEY APP ID>"
34
+ hockey_note = RubyBuildIos.latest_git_commit_comment()
35
+
36
+ # upload_to_hockey(app_id, api_token, note, file_to_upload)
37
+ RubyBuildIos.upload_to_hockey(app_id, api_token, hockey_note, archive_file)
38
+ end
39
+
40
+ def release_to_prod()
41
+ archive_path = './build/prod'
42
+ archive_file = archive_path + '/Example.xcarchive'
43
+
44
+
45
+ RubyBuildIos.clean('./build')
46
+
47
+ # Copies the provision file to the appropriately named file in ~/Library/MobileDevice/Provisioning Profiles
48
+ # where Xcode expects to find them.
49
+ RubyBuildIos.install_provision_file('./certs/EXAMPLE_Prod_App.mobileprovision')
50
+ RubyBuildIos.install_provision_file('./certs/EXAMPLE_Prod_WKApp.mobileprovision')
51
+ RubyBuildIos.install_provision_file('./certs/EXAMPLE_Prod_WKExt.mobileprovision')
52
+
53
+ # build_workspace(workspace, scheme, configuration, build_number, archive_path)
54
+ RubyBuildIos.build_workspace('Example.xcworkspace', 'Example', 'Release', get_build_number_string(), archive_file)
55
+
56
+ # This builds the plist necessary to create the ipa from the xcarchive
57
+ # The keys are the bundle ids for the various parts of your app
58
+ # This particular app has a watch app associated
59
+ # The values are the Name baked into the provision file. The one you specified when you created the provision profile
60
+ # You need one entry per profile required for you app. Simple apps need just one. Apps with watch apps need three.
61
+ plist_file = './build/build.plist'
62
+ provision_keys = {
63
+ "com.example.iphoneapp" => "EXAMPLE Prod App",
64
+ "com.example.iphoneapp.watchkitapp" => "EXAMPLE Prod WKApp",
65
+ "com.example.iphoneapp.watchkitapp.extension" => "EXAMPLE Prod WKExt" }
66
+
67
+ RubyBuildIos.create_export_plist(plist_file, provision_keys)
68
+
69
+ RubyBuildIos.export_to_ipa(archive_file, plist_file, archive_path)
70
+
71
+ user = '<YOUR ITUNES UPLOAD USER>'
72
+ password = '<YOUR ITUNES UPLOAD PASS>'
73
+
74
+ RubyBuildIos.validate_with_itunes(user, password, './build/example.ipa')
75
+ end
76
+
77
+ release_to_qa()
@@ -0,0 +1,361 @@
1
+ # ruby-build-ios.rb
2
+
3
+ module RubyBuildIos
4
+ require 'open3'
5
+
6
+ PROJECT_BUILD_NUMBER = 'PROJECT_BUILD_NUMBER';
7
+
8
+ # https://ruby-doc.org/stdlib-2.0.0/libdoc/open3/rdoc/Open3.html#method-c-capture3
9
+ # https://ruby-doc.org/core-2.2.0/Process/Status.html
10
+ # http://ruby-doc.org/core-2.0.0/IO.html#method-i-puts
11
+
12
+ def self.latest_git_commit_count(revision = '--all')
13
+ command = "git rev-list --count #{revision}"
14
+ print "#{command}\n"
15
+ # status is a Process::Status
16
+ stdout_str, stderr_str, status = Open3.capture3(command)
17
+ if status.success?
18
+ print "#{stdout_str}\n"
19
+ else
20
+ raise stderr_str;
21
+ end
22
+ result = stdout_str.chomp.chomp
23
+ result.to_i
24
+ end
25
+
26
+ def self.latest_git_commit_comment(revision = '--all')
27
+ command = "git log -1 --oneline #{revision}"
28
+ print "#{command}\n"
29
+ # status is a Process::Status
30
+ stdout_str, stderr_str, status = Open3.capture3(command)
31
+ if status.success?
32
+ print "#{stdout_str}\n"
33
+ else
34
+ raise stderr_str;
35
+ end
36
+ result = stdout_str.chomp.chomp
37
+ end
38
+
39
+ def self.tag_latest_git_commit(tag, message = 'Sorry, no tag message provided.')
40
+ command = "git tag -a #{tag} -m \"#{message}\""
41
+ print "#{command}\n"
42
+ # status is a Process::Status
43
+ # -a is annotate
44
+ # -m is message
45
+ # ex: git tag -a v1.4 -m "my version 1.4"
46
+ stdout_str, stderr_str, status = Open3.capture3(command)
47
+ if status.success?
48
+ print "#{stdout_str}\n"
49
+ else
50
+ raise stderr_str;
51
+ end
52
+ result = stdout_str
53
+ command = "git push origin #{tag}"
54
+ print "#{command}\n"
55
+ # ex: git push origin v1.4
56
+ stdout_str, stderr_str, status = Open3.capture3(command)
57
+ if status.success?
58
+ print "#{stdout_str}\n"
59
+ else
60
+ raise stderr_str;
61
+ end
62
+
63
+ result = "#{result}\n#{stdout_str}"
64
+ end
65
+
66
+ def self.project_build_number(file_name = PROJECT_BUILD_NUMBER)
67
+ print "Reading from #{file_name} to get the last project build number\n"
68
+ file = File.open(file_name, 'r')
69
+ build_str = file.gets()
70
+ file.close()
71
+
72
+ build_int = build_str.chomp.to_i
73
+
74
+ build_int += 1
75
+
76
+ build_str = build_int.to_s
77
+
78
+ print "Writing into #{file_name} to set the next project build number\n"
79
+ file = File.open(file_name, 'w+', 0666)
80
+ file.puts(build_str)
81
+ file.close()
82
+
83
+ build_str
84
+ end
85
+
86
+ def self.get_uuid_from_file(file_name)
87
+ command = "/usr/libexec/PlistBuddy -c \"Print UUID\" /dev/stdin <<< $(security cms -D -i #{file_name} 2> /dev/null)"
88
+ print "#{command}\n"
89
+ # status is a Process::Status
90
+ stdout_str, stderr_str, status = Open3.capture3(command)
91
+ if status.success?
92
+ stdout_str = stdout_str.chomp
93
+ print "#{stdout_str}\n"
94
+ else
95
+ raise stderr_str;
96
+ end
97
+ result = stdout_str
98
+ end
99
+
100
+ def self.copy_provision_file(file_name, uuid)
101
+ command = "cp #{file_name} ~/Library/MobileDevice/Provisioning\\ Profiles/#{uuid}.mobileprovision"
102
+ print "#{command}\n"
103
+ # status is a Process::Status
104
+ stdout_str, stderr_str, status = Open3.capture3(command)
105
+ if status.success?
106
+ print "#{stdout_str}\n"
107
+ else
108
+ raise stderr_str;
109
+ end
110
+ result = stdout_str
111
+ end
112
+
113
+ def self.install_provision_file(file_name)
114
+ uuid = get_uuid_from_file(file_name)
115
+ copy_provision_file(file_name, uuid)
116
+ end
117
+
118
+ def self.install_provision_files(file_names_str)
119
+ file_names = file_name_str.gsub("\n",'').strip().split(',')
120
+ file_names.each do |file_name|
121
+ uuid = get_uuid_from_file(file_name)
122
+ copy_provision_file(file_name, uuid)
123
+ end
124
+ end
125
+
126
+ def self.upload_to_hockey(app_id, api_token, note, file_to_upload)
127
+ command = "puck -app_id=#{app_id} -api_token=#{api_token} -submit=auto -download=true -notify=true -notes=\"#{note}\" #{file_to_upload}"
128
+ print "#{command}\n"
129
+ # status is a Process::Status
130
+ stdout_str, stderr_str, status = Open3.capture3(command)
131
+ if status.success?
132
+ print "#{stdout_str}\n"
133
+ else
134
+ raise stderr_str;
135
+ end
136
+ result = stdout_str
137
+ end
138
+
139
+ def self.clean(path)
140
+ if path.chomp == '/'
141
+ raise "Are you out of your mind?"
142
+ end
143
+ command = "rm -fr #{path}/*"
144
+ print "#{command}\n"
145
+ # status is a Process::Status
146
+ stdout_str, stderr_str, status = Open3.capture3(command)
147
+ if status.success?
148
+ print "#{stdout_str}\n"
149
+ else
150
+ raise stderr_str;
151
+ end
152
+ result = stdout_str
153
+ end
154
+
155
+ # TODO allow flexibility in USER DEFINED build variables
156
+ def self.build_project(project, scheme, configuration, build_number, archive_path)
157
+ command = "xcodebuild -project #{project} -scheme #{scheme} -configuration #{configuration} -destination 'generic/platform=iOS' PROJECT_BUILD_NUMBER=#{build_number} -archivePath #{archive_path} archive"
158
+ print "#{command}\n"
159
+ # status is a Process::Status
160
+ result = 'OK'
161
+ status = nil
162
+ stderr_str = ''
163
+ Open3.popen3(command) do |stdin, stdout, stderr, wait|
164
+
165
+ t1 = Thread.new do
166
+ while line = stdout.gets
167
+ puts line
168
+ end
169
+ end
170
+
171
+ t2 = Thread.new do
172
+ while line = stderr.gets
173
+ stderr_str << line
174
+ end
175
+ end
176
+
177
+ status = wait.value
178
+ t2.value
179
+ t1.value
180
+ end
181
+
182
+ if status.success?
183
+ print "#{result}\n"
184
+ else
185
+ raise stderr_str;
186
+ end
187
+ result
188
+ end
189
+
190
+ # TODO allow flexibility in USER DEFINED build variables
191
+ def self.build_workspace(workspace, scheme, configuration, build_number, archive_path)
192
+ command = "xcodebuild -workspace #{workspace} -scheme #{scheme} -configuration #{configuration} -destination 'generic/platform=iOS' PROJECT_BUILD_NUMBER=#{build_number} -archivePath #{archive_path} archive"
193
+ print "#{command}\n"
194
+ # status is a Process::Status
195
+ result = 'OK'
196
+ status = nil
197
+ stderr_str = ''
198
+ Open3.popen3(command) do |stdin, stdout, stderr, wait|
199
+
200
+ t1 = Thread.new do
201
+ while line = stdout.gets
202
+ puts line
203
+ end
204
+ end
205
+
206
+ t2 = Thread.new do
207
+ while line = stderr.gets
208
+ stderr_str << line
209
+ end
210
+ end
211
+
212
+ status = wait.value
213
+ t2.value
214
+ t1.value
215
+ end
216
+
217
+ if status.success?
218
+ print "#{result}\n"
219
+ else
220
+ raise stderr_str;
221
+ end
222
+ result
223
+ end
224
+
225
+ def self.create_export_plist(file_name, key_string_pairs)
226
+ if !key_string_pairs.is_a? Hash
227
+ raise "create_export_plist() key_string_pairs must be a Hash\n"
228
+ end
229
+
230
+ xml_prelude = <<EO1
231
+ <?xml version="1.0" encoding="UTF-8"?>
232
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
233
+ <plist version="1.0">
234
+ <dict>
235
+ <key>provisioningProfiles</key>
236
+ <dict>
237
+ EO1
238
+ xml_postlude = <<EO2
239
+ </dict>
240
+ <key>method</key>
241
+ <string>app-store</string>
242
+ </dict>
243
+ </plist>
244
+ EO2
245
+ xml_key_string_pairs = ''
246
+ key_string_pairs.each_pair do |key, string|
247
+ xml_key_string_pairs << " <key>#{key}</key>\n"
248
+ xml_key_string_pairs << " <string>#{string}</string>\n"
249
+ end
250
+
251
+ print "Writing into #{file_name}\n"
252
+ file = File.open(file_name, 'w+', 0666)
253
+ file.puts("#{xml_prelude}#{xml_key_string_pairs}#{xml_postlude}")
254
+ file.close()
255
+
256
+ return true
257
+ end
258
+
259
+ def self.xcode_path()
260
+ command = "dirname \"$(xcode-select -p)\""
261
+ print "#{command}\n"
262
+ # status is a Process::Status
263
+ stdout_str, stderr_str, status = Open3.capture3(command)
264
+ if status.success?
265
+ print "#{stdout_str}\n"
266
+ else
267
+ raise stderr_str;
268
+ end
269
+ result = stdout_str.chomp.chomp
270
+ end
271
+
272
+ def self.export_to_ipa(archive, plist, ipa_path)
273
+ command = "xcodebuild -exportArchive -exportOptionsPlist #{plist} -archivePath #{archive} -exportPath #{ipa_path}"
274
+ print "#{command}\n"
275
+ # status is a Process::Status
276
+ stdout_str, stderr_str, status = Open3.capture3(command)
277
+ if status.success?
278
+ print "#{stdout_str}\n"
279
+ else
280
+ raise stderr_str;
281
+ end
282
+ result = stdout_str
283
+ end
284
+
285
+ # May want to stream stderr to console too, because altool uses stderr instead of stdout
286
+ def self.validate_with_itunes(user, password, ipa)
287
+ # get altools path
288
+ xcode_path = self.xcode_path
289
+ altool = "#{xcode_path}/Applications/Application\\ Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool"
290
+ command = "#{altool} --validate-app --type ios -u \"#{user}\" -p \"#{password}\" -f #{ipa}"
291
+ print "#{command}\n"
292
+ result = 'OK'
293
+ status = nil
294
+ stderr_str = ''
295
+ Open3.popen3(command) do |stdin, stdout, stderr, wait|
296
+
297
+ t1 = Thread.new do
298
+ while line = stdout.gets
299
+ puts line
300
+ end
301
+ end
302
+
303
+ t2 = Thread.new do
304
+ while line = stderr.gets
305
+ puts line
306
+ stderr_str << line
307
+ end
308
+ end
309
+
310
+ status = wait.value
311
+ t2.value
312
+ t1.value
313
+ end
314
+
315
+ if status.success?
316
+ print "#{result}\n"
317
+ else
318
+ raise stderr_str;
319
+ end
320
+ result
321
+ end
322
+
323
+ # May want to stream stderr to console too, because altool uses stderr instead of stdout
324
+ def self.upload_to_itunes(user, password, ipa)
325
+ # get altools path
326
+ xcode_path = self.xcode_path
327
+ altool = "#{xcode_path}/Applications/Application\\ Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool"
328
+ command = "#{altool} --upload-app --type ios -u \"#{user}\" -p \"#{password}\" -f #{ipa}"
329
+ print "#{command}\n"
330
+ result = 'OK'
331
+ status = nil
332
+ stderr_str = ''
333
+ Open3.popen3(command) do |stdin, stdout, stderr, wait|
334
+
335
+ t1 = Thread.new do
336
+ while line = stdout.gets
337
+ puts line
338
+ end
339
+ end
340
+
341
+ t2 = Thread.new do
342
+ while line = stderr.gets
343
+ puts line
344
+ stderr_str << line
345
+ end
346
+ end
347
+
348
+ status = wait.value
349
+ t2.value
350
+ t1.value
351
+ end
352
+
353
+ if status.success?
354
+ print "#{result}\n"
355
+ else
356
+ raise stderr_str;
357
+ end
358
+ result
359
+ end
360
+
361
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_build_ios
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - David Rogers of HS2 Solutions
8
+ - Don Bales of HS2 Solutions
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2017-09-15 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: A Ruby library to help write scripts to build IOS mobile apps written
15
+ during HS2's 2017 Hackathon. Please use RubyBuildIOS in the subject line when emailing
16
+ Dave.
17
+ email: david.rogers@hs2solutions.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - README.md
23
+ - example.rb
24
+ - lib/ruby_build_ios.rb
25
+ homepage: https://rubygems.org/gems/ruby_build_ios
26
+ licenses:
27
+ - MIT
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 2.6.11
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: Ruby Build IOS
49
+ test_files: []