ruby_build_ios 0.0.0

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.
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: []