testflight 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +0 -0
  3. data/bin/takeoff +374 -0
  4. data/lib/testflight/version.rb +28 -0
  5. metadata +67 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Michael Berkovich
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
File without changes
data/bin/takeoff ADDED
@@ -0,0 +1,374 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+ require 'yaml'
5
+ require 'pp'
6
+ require 'plist'
7
+
8
+ XCODE_BUILDER = "/usr/bin/xcodebuild"
9
+ XCODE_PACKAGER = "/usr/bin/xcrun"
10
+ TESTFLIGHT_ENDPOINT = "http://testflightapp.com/api/builds.json"
11
+
12
+ # read project definition
13
+ # read app version and build number
14
+ # add all files to git and commit files
15
+ # tag git with the version and build
16
+ # build files
17
+ # package files
18
+ # deploy to testflight app
19
+
20
+
21
+ def project_dir
22
+ Dir.pwd
23
+ end
24
+
25
+ def project_files
26
+ @project_files ||= Dir.entries(project_dir)
27
+ end
28
+
29
+ def file_name_by_ext(ext)
30
+ project_files.select{|file| file.match(/#{ext}$/)}.first
31
+ end
32
+
33
+
34
+ def workspace_name
35
+ @workspace_name ||= file_name_by_ext('xcworkspace')
36
+ end
37
+
38
+ def project_name
39
+ @project_name ||= file_name_by_ext('xcodeproj')
40
+ end
41
+
42
+ def type
43
+ return "unknown" if workspace_name.nil? and project_name.nil?
44
+ @type ||= workspace_name ? 'workspace' : 'project'
45
+ end
46
+
47
+ def application_name
48
+ @application_name ||= begin
49
+ name = workspace_name || project_name
50
+ name = name.split(".")[0..-2].join(".") if name
51
+ name
52
+ end
53
+ end
54
+
55
+ def default_config
56
+ {
57
+ "build" => {
58
+ "developer_name" => "As it appears in your Apple certificate",
59
+ "increment_bundle" => true,
60
+ "commit_changes" => true
61
+ },
62
+ "testflight" => {
63
+ "api_token" => "Get it from https://testflightapp.com/account/#api",
64
+ "team_token" => "Get it from https://testflightapp.com/dashboard/team/edit/",
65
+ "distribution_lists" => [""]
66
+ }
67
+ }
68
+ end
69
+
70
+ def valid_config?
71
+ return false unless config
72
+ return false if config.empty?
73
+ return false if config["build"].nil?
74
+ return false if config["build"]["developer_name"].nil?
75
+ return false if config["testflight"].nil?
76
+ return false if config["testflight"]["api_token"].nil?
77
+ return false if config["testflight"]["team_token"].nil?
78
+ return false if config["testflight"]["distribution_lists"].nil?
79
+ true
80
+ end
81
+
82
+ def config_file
83
+ '.testflight'
84
+ end
85
+
86
+ def config
87
+ @config ||= begin
88
+ if File.exist?(config_file)
89
+ YAML::load(File.open(config_file))
90
+ else
91
+ File.open(config_file, "w") do |f|
92
+ f.write(default_config.to_yaml)
93
+ end
94
+ YAML::load(File.open(config_file))
95
+ end
96
+ end
97
+ end
98
+
99
+ def build_dir
100
+ "#{project_dir}/build"
101
+ end
102
+
103
+ def provisioning_dir
104
+ "#{project_dir}/Provisioning"
105
+ end
106
+
107
+ def distributions_dir
108
+ "#{project_dir}/Distributions"
109
+ end
110
+
111
+ def build_workspace
112
+ cmd = "#{XCODE_BUILDER} -workspace '#{workspace_name}' "
113
+ cmd << "-scheme '#{application_name}' "
114
+ cmd << "-sdk 'iphoneos6.0' "
115
+ cmd << "-configuration 'AdHoc' "
116
+ cmd << "-arch 'armv6 armv7' "
117
+ cmd << "CONFIGURATION_BUILD_DIR='#{build_dir}' "
118
+ pp "Building Workspace..."
119
+ pp cmd
120
+ unless system(cmd)
121
+ pp "Failed to package the workspace"
122
+ exit 1
123
+ end
124
+ end
125
+
126
+ def build_project
127
+ cmd = "#{XCODE_BUILDER} -target '#{application_name}' "
128
+ cmd << "-sdk 'iphoneos6.0' "
129
+ cmd << "-configuration 'AdHoc' "
130
+ pp "Building Project..."
131
+ pp cmd
132
+ unless system(cmd)
133
+ pp "Failed to build the project"
134
+ exit 1
135
+ end
136
+ end
137
+
138
+ def ad_hoc_provisioning_name
139
+ @ad_hoc_provisioning_name ||= begin
140
+ files = Dir.entries(provisioning_dir)
141
+ files.select{|file| file.match(/mobileprovision$/)}.first
142
+ end
143
+ end
144
+
145
+ def distribution_file
146
+ "#{distributions_dir}/#{application_name}.ipa"
147
+ end
148
+
149
+ def package_workspace
150
+ cmd = "#{XCODE_PACKAGER} -sdk iphoneos PackageApplication "
151
+ cmd << "-v '#{build_dir}/#{application_name}.app' "
152
+ cmd << "-o '#{distribution_file}' "
153
+ cmd << "--sign '#{config["build"]["developer_name"]}' "
154
+ cmd << "--embed '#{provisioning_dir}/#{ad_hoc_provisioning_name}'"
155
+
156
+ pp "Packaging Workspace..."
157
+ pp cmd
158
+ unless system(cmd)
159
+ pp "Failed to package the workspace"
160
+ exit 1
161
+ end
162
+ end
163
+
164
+ def package_project
165
+ cmd = "#{XCODE_PACKAGER} -sdk iphoneos PackageApplication "
166
+ cmd << "-v '#{build_dir}/AdHoc-iphoneos/#{application_name}.app' "
167
+ cmd << "-o '#{distribution_file}' "
168
+ cmd << "--sign '#{config["build"]["developer_name"]}' "
169
+ cmd << "--embed '#{provisioning_dir}/#{ad_hoc_provisioning_name}'"
170
+
171
+ pp "Packaging Project..."
172
+ pp cmd
173
+ unless system(cmd)
174
+ pp "Failed to package the project"
175
+ exit 1
176
+ end
177
+ end
178
+
179
+ def setup
180
+ unless application_name
181
+ pp "This folder does not contain an xCode project or a workspace."
182
+ exit 1
183
+ end
184
+
185
+ unless valid_config?
186
+ pp "Ensure that you have provided all of the information in the #{config_file} config file"
187
+ exit 1
188
+ end
189
+
190
+ unless project_files.include?("Distributions")
191
+ FileUtils.mkdir("Distributions")
192
+ @project_files = nil
193
+ end
194
+
195
+ unless project_files.include?("Provisioning")
196
+ FileUtils.mkdir("Provisioning")
197
+ @project_files = nil
198
+ end
199
+
200
+ unless ad_hoc_provisioning_name
201
+ pp "Please copy your Ad Hoc Provisioning Profile into the provisioning folder."
202
+ exit 1
203
+ end
204
+ end
205
+
206
+ def read_options(question, opts=["Yes", "No"], vals=["y", "yes", "n", "no"], joiner = "/")
207
+ prompt = "(#{opts.join(joiner)})? "
208
+ pp question
209
+
210
+ $stdout.print(prompt)
211
+
212
+ $stdin.each_line do |line|
213
+ value = line.strip.downcase
214
+ return value if vals.include?(value)
215
+ $stdout.print(prompt)
216
+ end
217
+ end
218
+
219
+ def read_info(question, prompt="> ", allow_blank=false)
220
+ pp(question) if question
221
+
222
+ $stdout.print(prompt)
223
+
224
+ $stdin.each_line do |line|
225
+ value = line.strip.downcase
226
+ return value if allow_blank
227
+ return value unless value.empty?
228
+ $stdout.print(prompt)
229
+ end
230
+ end
231
+
232
+ def read_distribution_lists
233
+ lists = []
234
+ vals = read_info(nil, prompt="? ")
235
+ vals.split(",").each do |index|
236
+ index = index.to_i - 1
237
+ return nil if index<0 or index>=config["testflight"]["distribution_lists"].size
238
+ lists << config["testflight"]["distribution_lists"][index]
239
+ end
240
+ lists
241
+ end
242
+
243
+ def select_distribution_lists
244
+ return [] unless config["testflight"]["distribution_lists"]
245
+
246
+ pp "Which distribution lists would you like to send this build to? (Select one or more lists, separated with comma.)"
247
+ config["testflight"]["distribution_lists"].each_with_index do |list, index|
248
+ pp " #{index+1}) #{list}"
249
+ end
250
+
251
+ lists = read_distribution_lists
252
+ while lists.nil?
253
+ pp "Invalid selection, please try again."
254
+ lists = read_distribution_lists
255
+ end
256
+
257
+ lists
258
+ end
259
+
260
+ def upload_to_testflightapp
261
+ pp "Uploading to TestFlightApp..."
262
+
263
+ notes = read_info("What has changed in this build?")
264
+
265
+ lists = select_distribution_lists
266
+
267
+ notify = read_options("Would you like to notify your team members by email about this build?")
268
+ ["y", "yes"].include?(notify) ? notify = "true" : notify = "false"
269
+
270
+ cmd = "curl #{TESTFLIGHT_ENDPOINT} "
271
+ cmd << "-F file=@#{distribution_file} "
272
+ cmd << "-F api_token=#{config["testflight"]["api_token"]} "
273
+ cmd << "-F team_token=#{config["testflight"]["team_token"]} "
274
+ cmd << "-F notify=#{notify} "
275
+ cmd << "-F distribution_lists=#{lists.join(",")} "
276
+ cmd << "-F notes='#{notes}'"
277
+
278
+ pp cmd
279
+ unless system(cmd)
280
+ pp "Failed to upload to testflight"
281
+ exit 1
282
+ end
283
+ end
284
+
285
+ ####################################################################################
286
+ ## Project Versioning
287
+ ####################################################################################
288
+
289
+ def project_info_path
290
+ files = Dir["**/#{application_name}-Info.plist"]
291
+ if files.empty?
292
+ pp "Cannot locate #{application_name}-Info.plist file. Please make sure such file exists in your project."
293
+ exit 1
294
+ end
295
+ files.first
296
+ end
297
+
298
+ def project_info
299
+ @project_info ||= Plist::parse_xml(project_info_path)
300
+ end
301
+
302
+ def project_version
303
+ "#{project_info["CFBundleShortVersionString"]} (#{project_info["CFBundleVersion"]})"
304
+ end
305
+
306
+ def project_version_short
307
+ "#{project_info["CFBundleShortVersionString"]}.#{project_info["CFBundleVersion"]}"
308
+ end
309
+
310
+ def increment_bundle_version
311
+ project_info["CFBundleVersion"] = (project_info["CFBundleVersion"].to_i + 1).to_s
312
+ pp "Incrementing bundle version to #{project_info["CFBundleVersion"]}..."
313
+
314
+ File.open(project_info_path, "w") do |f|
315
+ f.write(project_info.to_plist)
316
+ end
317
+ @project_info = nil
318
+ end
319
+
320
+ ####################################################################################
321
+ ## Git Support
322
+ ####################################################################################
323
+ def update_git_ignore
324
+ # build
325
+ # Distributions
326
+ # Provisioning
327
+ # .testflight
328
+ end
329
+
330
+ def commit_changes(msg)
331
+ system("git add .")
332
+ system("git add . --update")
333
+ system("git commit -m '#{msg}'")
334
+ system("git push")
335
+
336
+ system("git tag -a #{project_version_short} -m 'Release #{project_version}'")
337
+ end
338
+
339
+ ####################################################################################
340
+ ## Main
341
+ ####################################################################################
342
+
343
+ def deploy
344
+ setup
345
+
346
+ if config["build"]["commit_changes"]
347
+ commit_changes("Preparing build #{project_version}")
348
+ end
349
+
350
+ if workspace_name
351
+ build_workspace
352
+ package_workspace
353
+ else
354
+ build_project
355
+ package_project
356
+ end
357
+
358
+ upload_to_testflightapp
359
+
360
+ pp ""
361
+
362
+ if config["build"]["increment_bundle"]
363
+ increment_bundle_version
364
+ if config["build"]["commit_changes"]
365
+ commit_changes("Incrementing build number to #{project_version}")
366
+ end
367
+ end
368
+
369
+ pp "Congratulations! The app has been deployed!"
370
+ end
371
+
372
+ deploy
373
+
374
+
@@ -0,0 +1,28 @@
1
+ #--
2
+ # Copyright (c) 2012 Michael Berkovich
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module Testflight
25
+ VERSION = "0.1.1"
26
+ end
27
+
28
+
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: testflight
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michael Berkovich
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: plist
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 3.1.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 3.1.0
30
+ description: Mechanism for building, packaging, tagging and deploying XCode projects
31
+ to testflightapp.com
32
+ email:
33
+ - theiceberk@gmail.com
34
+ executables:
35
+ - takeoff
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - bin/takeoff
40
+ - lib/testflight/version.rb
41
+ - LICENSE
42
+ - README.md
43
+ homepage: https://github.com/berk/testflight
44
+ licenses: []
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 1.8.23
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: iOS application deployment automation
67
+ test_files: []