betabuilder 0.1.2 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES.md ADDED
@@ -0,0 +1,16 @@
1
+ ## 0.2.1
2
+ * Allow the namespace of generated tasks to be customised
3
+
4
+ ## 0.2
5
+ * Introduced deployment strategies, allowing custom deployment methods
6
+ * Added support for deploying beta releases to TestFlightApp.com
7
+
8
+ ## 0.1.2
9
+
10
+ * Allow custom hosts when using the SCP deployment task (simonjefford)
11
+
12
+ ## 0.1.1
13
+ * Fixed missing dependency
14
+
15
+ ## 0.1
16
+ * Initial Release
data/README.md CHANGED
@@ -33,30 +33,65 @@ Because BetaBuilder is a Rake task library, you do not need to define any tasks
33
33
 
34
34
  # the Xcode configuration profile
35
35
  config.configuration = "Adhoc"
36
-
37
- # where the distribution files will be uploaded to
38
- config.deploy_to = "http://yourwebsite.com/betas/"
39
36
  end
40
37
 
41
38
  Now, if you run `rake -T` in Terminal.app in the root of your project, the available tasks will be printed with a brief description of each one:
42
39
 
43
40
  rake beta:build # Build the beta release of the app
44
- rake beta:deploy # Deploy the beta to your server
45
41
  rake beta:package # Package the beta release as an IPA file
46
42
 
47
- To deploy a beta to your server, some additional configuration is needed (see the next section).
43
+ To deploy your beta to your testers, some additional configuration is needed (see the next section).
48
44
 
49
- Most of the time, you'll not need to run the `beta:build` task directly; it will be run automatically as a dependency of `beta:package`. Upon running this task, your ad-hoc build will be packaged into an IPA file and will be saved in ${PROJECT_ROOT}/pkg/dist, along with a HTML index file and the manifest file needed for over-the-air installation.
45
+ Most of the time, you'll not need to run the `beta:build` task directly; it will be run automatically as a dependency of `beta:package`. Upon running this task, your ad-hoc build will be packaged into an IPA file and will be saved in `${PROJECT_ROOT}/pkg/dist`, along with a HTML index file and the manifest file needed for over-the-air installation.
50
46
 
51
47
  If you are not using the automatic deployment task, you will need to upload the contents of the pkg/dist directory to your server.
52
48
 
53
- ## Automatic deployment
49
+ To use a namespace other than "beta" for the generated tasks, simply pass in your chosen namespace to BetaBuilder::Tasks.new:
54
50
 
55
- BetaBuilder also comes with a (rather rudimentary) automatic deployment task that uses SCP so you will need SSH access to your server and appropriate permissions to use it. You will also need one extra line of configuration in your Rakefile, specifying the path on the remote server where the files will be copied to:
51
+ BetaBuilder::Tasks.new(:my_custom_namespace) do |config|
52
+ end
53
+
54
+ This lets you set up different sets of BetaBuilder tasks for different configurations in the same Rakefile (e.g. a production and staging build).
55
+
56
+ ## Automatic deployment with deployment strategies
57
+
58
+ BetaBuilder allows you to deploy your built package using it's extensible deployment strategy system; the gem currently comes with support for simple web-based deployment or uploading to [TestFlightApp.com](http://www.testflightapp.com). Eventually, you will be able to write your own custom deployment strategies if neither of these are suitable for your needs.
59
+
60
+ ### Deploying your app with TestFlight
56
61
 
57
- config.remote_directory = "/var/www/yourwebsite.com/betas"
62
+ By far the easiest way to get your beta release into the hands of your testers is using the excellent [TestFlight service](http://testflightapp.com/), although at the time of writing it is still in private beta. You can use TestFlight to manage your beta testers and notify them of new releases instantly.
63
+
64
+ TestFlight provides an upload API and betabuilder uses that to provide a `:testflight` upload strategy. This strategy requires two pieces of information: your TestFlight API token and your team token:
65
+
66
+ config.deploy_using(:testflight) do |tf|
67
+ tf.api_token = "YOUR_API_TOKEN"
68
+ tf.team_token = "YOUR_TEAM_TOKEN"
69
+ end
70
+
71
+ Now, instead of using the `beta:package` task, you can run the `beta:deploy` task instead. This task will run the package task as a dependency and upload the generated IPA file to TestFlight.
72
+
73
+ You will be prompted to enter the release notes for the build; TestFlight requires these to inform your testers of what has changed in this build. Alternatively, if you have a way of generating the release notes automatically (for instance, using a CHANGELOG file or a git log command), you can specify a block that will be called at runtime - you can do whatever you want in this block, as long as you return a string which will be used as the release notes, e.g.
74
+
75
+ config.deploy_using(:testflight) do |tf|
76
+ ...
77
+ tf.generate_release_notes do
78
+ # return release notes here
79
+ end
80
+ end
81
+
82
+ ### Deploying to your own server
83
+
84
+ BetaBuilder also comes with a rather rudimentary web-based deployment task that uses SCP, so you will need SSH access to your server and appropriate permissions to use it. This works in the same way as the original iOS-BetaBuilder GUI app by generating a HTML template and manifest file that can be uploaded to a directly on your server. It includes links to install the app automatically on the device or download the IPA file.
85
+
86
+ You will to configure betabuilder to use the `web` deployment strategy with some additional configuration:
87
+
88
+ config.deploy_using(:web) do |web|
89
+ web.deploy_to = "http://beta.myserver.co.uk/myapp"
90
+ web.remote_host = "myserver.com"
91
+ web.remote_directory = "/remote/path/to/deployment/directory"
92
+ end
58
93
 
59
- Now, instead of using the `beta:package` task, you can run the `beta:deploy` task instead. This task will run the package task as a dependency and upload the pkg/dist directory to the remote directory you have configured (again, you will need to ensure that you have the correct permissions for this to work).
94
+ The `deploy_to` setting specifies the URL that your app will be published to. The `remote_host` setting is the SSH host that will be used to copy the files to your server using SCP. Finally, the `remote_directory` setting is the path to the location to your server that files will be uploaded to. You will need to configure any virtual hosts on your server to make this work.
60
95
 
61
96
  ## License
62
97
 
data/lib/beta_builder.rb CHANGED
@@ -3,16 +3,18 @@ require 'ostruct'
3
3
  require 'fileutils'
4
4
  require 'cfpropertylist'
5
5
  require 'beta_builder/archived_build'
6
+ require 'beta_builder/deployment_strategies'
6
7
 
7
8
  module BetaBuilder
8
9
  class Tasks < ::Rake::TaskLib
9
- def initialize(&block)
10
+ def initialize(namespace = :beta, &block)
10
11
  @configuration = Configuration.new(
11
12
  :configuration => "Adhoc",
12
13
  :build_dir => "build",
13
14
  :auto_archive => false,
14
15
  :archive_path => File.expand_path("~/Library/MobileDevice/Archived Applications/")
15
16
  )
17
+ @namespace = namespace
16
18
  yield @configuration if block_given?
17
19
  define
18
20
  end
@@ -38,23 +40,28 @@ module BetaBuilder
38
40
  "#{built_app_path}.dSYM"
39
41
  end
40
42
 
41
- def deployment_url
42
- File.join(deploy_to, target.downcase, ipa_name)
43
+ def dist_path
44
+ File.join("pkg/dist")
43
45
  end
44
46
 
45
- def manifest_url
46
- File.join(deploy_to, target.downcase, "manifest.plist")
47
+ def ipa_path
48
+ File.join(dist_path, ipa_name)
47
49
  end
48
50
 
49
- def remote_installation_path
50
- File.join(remote_directory, target.downcase)
51
+ def deploy_using(strategy_name, &block)
52
+ if DeploymentStrategies.valid_strategy?(strategy_name.to_sym)
53
+ self.deployment_strategy = DeploymentStrategies.build(strategy_name, self)
54
+ self.deployment_strategy.configure(&block)
55
+ else
56
+ raise "Unknown deployment strategy '#{strategy_name}'."
57
+ end
51
58
  end
52
59
  end
53
60
 
54
61
  private
55
62
 
56
63
  def define
57
- namespace :beta do
64
+ namespace(@namespace) do
58
65
  desc "Build the beta release of the app"
59
66
  task :build => :clean do
60
67
  system("xcodebuild #{@configuration.build_arguments} build")
@@ -78,74 +85,20 @@ module BetaBuilder
78
85
  end
79
86
  FileUtils.mkdir('pkg/dist')
80
87
  FileUtils.mv("pkg/#{@configuration.ipa_name}", "pkg/dist")
81
- plist = CFPropertyList::List.new(:file => "pkg/Payload/#{@configuration.app_name}/Info.plist")
82
- plist_data = CFPropertyList.native_types(plist.value)
83
- File.open("pkg/dist/manifest.plist", "w") do |io|
84
- io << %{
85
- <?xml version="1.0" encoding="UTF-8"?>
86
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
87
- <plist version="1.0">
88
- <dict>
89
- <key>items</key>
90
- <array>
91
- <dict>
92
- <key>assets</key>
93
- <array>
94
- <dict>
95
- <key>kind</key>
96
- <string>software-package</string>
97
- <key>url</key>
98
- <string>#{@configuration.deployment_url}</string>
99
- </dict>
100
- </array>
101
- <key>metadata</key>
102
- <dict>
103
- <key>bundle-identifier</key>
104
- <string>#{plist_data['CFBundleIdentifier']}</string>
105
- <key>bundle-version</key>
106
- <string>#{plist_data['CFBundleVersion']}</string>
107
- <key>kind</key>
108
- <string>software</string>
109
- <key>title</key>
110
- <string>#{plist_data['CFBundleDisplayName']}</string>
111
- </dict>
112
- </dict>
113
- </array>
114
- </dict>
115
- </plist>
116
- }
117
- end
118
- File.open("pkg/dist/index.html", "w") do |io|
119
- io << %{
120
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
121
- <html xmlns="http://www.w3.org/1999/xhtml">
122
- <head>
123
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
124
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
125
- <title>Beta Download</title>
126
- <style type="text/css">
127
- body {background:#fff;margin:0;padding:0;font-family:arial,helvetica,sans-serif;text-align:center;padding:10px;color:#333;font-size:16px;}
128
- #container {width:300px;margin:0 auto;}
129
- h1 {margin:0;padding:0;font-size:14px;}
130
- p {font-size:13px;}
131
- .link {background:#ecf5ff;border-top:1px solid #fff;border:1px solid #dfebf8;margin-top:.5em;padding:.3em;}
132
- .link a {text-decoration:none;font-size:15px;display:block;color:#069;}
133
- </style>
134
- </head>
135
- <body>
136
- <div id="container">
137
- <div class="link"><a href="itms-services://?action=download-manifest&url=#{@configuration.manifest_url}">Tap Here to Install<br />#{@configuration.target}<br />On Your Device</a></div>
138
- <p><strong>Link didn't work?</strong><br />
139
- Make sure you're visiting this page on your device, not your computer.</p>
140
- </body>
141
- </html>
142
- }
143
- end
144
88
  end
145
89
 
146
- desc "Deploy the beta to your server"
147
- task :deploy => :package do
148
- system("scp pkg/dist/* #{@configuration.remote_host}:#{@configuration.remote_installation_path}")
90
+ if @configuration.deployment_strategy
91
+ desc "Deploy the beta to your server"
92
+ task :deploy => :package do
93
+ @configuration.deployment_strategy.prepare
94
+ @configuration.deployment_strategy.deploy
95
+ end
96
+
97
+ desc "Deploy the last build"
98
+ task :redeploy do
99
+ @configuration.deployment_strategy.prepare
100
+ @configuration.deployment_strategy.deploy
101
+ end
149
102
  end
150
103
 
151
104
  desc "Build and archive the app"
@@ -0,0 +1,38 @@
1
+ module BetaBuilder
2
+ module DeploymentStrategies
3
+ def self.valid_strategy?(strategy_name)
4
+ strategies.keys.include?(strategy_name.to_sym)
5
+ end
6
+
7
+ def self.build(strategy_name, configuration)
8
+ strategies[strategy_name.to_sym].new(configuration)
9
+ end
10
+
11
+ class Strategy
12
+ def initialize(configuration)
13
+ @configuration = configuration
14
+
15
+ if respond_to?(:extended_configuration_for_strategy)
16
+ @configuration.instance_eval(&extended_configuration_for_strategy)
17
+ end
18
+ end
19
+
20
+ def configure(&block)
21
+ yield @configuration
22
+ end
23
+
24
+ def prepare
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def self.strategies
31
+ {:web => Web, :testflight => TestFlight}
32
+ end
33
+ end
34
+ end
35
+
36
+ require 'beta_builder/deployment_strategies/web'
37
+ require 'beta_builder/deployment_strategies/testflight'
38
+
@@ -0,0 +1,60 @@
1
+ require 'rest_client'
2
+ require 'json'
3
+
4
+ module BetaBuilder
5
+ module DeploymentStrategies
6
+ class TestFlight < Strategy
7
+ ENDPOINT = "http://testflightapp.com/api/builds.json"
8
+
9
+ def extended_configuration_for_strategy
10
+ proc do
11
+ def generate_release_notes(&block)
12
+ self.release_notes = yield if block_given?
13
+ end
14
+ end
15
+ end
16
+
17
+ def deploy
18
+ payload = {
19
+ :api_token => @configuration.api_token,
20
+ :team_token => @configuration.team_token,
21
+ :file => File.new(@configuration.ipa_path, 'rb'),
22
+ :notes => get_notes,
23
+ :notify => false
24
+ }
25
+ puts "Uploading build to TestFlight..."
26
+
27
+ begin
28
+ response = RestClient.post(ENDPOINT, payload, :accept => :json)
29
+ rescue => e
30
+ response = e.response
31
+ end
32
+
33
+ if response.code == 200
34
+ puts "Upload complete."
35
+ else
36
+ puts "Upload failed. (#{response})"
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def get_notes
43
+ @configuration.release_notes || get_notes_using_prompt
44
+ end
45
+
46
+ def get_notes_using_prompt
47
+ puts "Enter the release notes for this build (hit enter twice when done):\n"
48
+ gets_until_match(/\n{2}$/).strip
49
+ end
50
+
51
+ def gets_until_match(pattern, string = "")
52
+ if (string += STDIN.gets) =~ pattern
53
+ string
54
+ else
55
+ gets_until_match(pattern, string)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,91 @@
1
+ module BetaBuilder
2
+ module DeploymentStrategies
3
+ class Web < Strategy
4
+ def extended_configuration_for_strategy
5
+ proc do
6
+ def deployment_url
7
+ File.join(deploy_to, target.downcase, ipa_name)
8
+ end
9
+
10
+ def manifest_url
11
+ File.join(deploy_to, target.downcase, "manifest.plist")
12
+ end
13
+
14
+ def remote_installation_path
15
+ File.join(remote_directory, target.downcase)
16
+ end
17
+ end
18
+ end
19
+
20
+ def prepare
21
+ plist = CFPropertyList::List.new(:file => "pkg/Payload/#{@configuration.app_name}/Info.plist")
22
+ plist_data = CFPropertyList.native_types(plist.value)
23
+ File.open("pkg/dist/manifest.plist", "w") do |io|
24
+ io << %{
25
+ <?xml version="1.0" encoding="UTF-8"?>
26
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
27
+ <plist version="1.0">
28
+ <dict>
29
+ <key>items</key>
30
+ <array>
31
+ <dict>
32
+ <key>assets</key>
33
+ <array>
34
+ <dict>
35
+ <key>kind</key>
36
+ <string>software-package</string>
37
+ <key>url</key>
38
+ <string>#{@configuration.deployment_url}</string>
39
+ </dict>
40
+ </array>
41
+ <key>metadata</key>
42
+ <dict>
43
+ <key>bundle-identifier</key>
44
+ <string>#{plist_data['CFBundleIdentifier']}</string>
45
+ <key>bundle-version</key>
46
+ <string>#{plist_data['CFBundleVersion']}</string>
47
+ <key>kind</key>
48
+ <string>software</string>
49
+ <key>title</key>
50
+ <string>#{plist_data['CFBundleDisplayName']}</string>
51
+ </dict>
52
+ </dict>
53
+ </array>
54
+ </dict>
55
+ </plist>
56
+ }
57
+ end
58
+ File.open("pkg/dist/index.html", "w") do |io|
59
+ io << %{
60
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
61
+ <html xmlns="http://www.w3.org/1999/xhtml">
62
+ <head>
63
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
64
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
65
+ <title>Beta Download</title>
66
+ <style type="text/css">
67
+ body {background:#fff;margin:0;padding:0;font-family:arial,helvetica,sans-serif;text-align:center;padding:10px;color:#333;font-size:16px;}
68
+ #container {width:300px;margin:0 auto;}
69
+ h1 {margin:0;padding:0;font-size:14px;}
70
+ p {font-size:13px;}
71
+ .link {background:#ecf5ff;border-top:1px solid #fff;border:1px solid #dfebf8;margin-top:.5em;padding:.3em;}
72
+ .link a {text-decoration:none;font-size:15px;display:block;color:#069;}
73
+ </style>
74
+ </head>
75
+ <body>
76
+ <div id="container">
77
+ <div class="link"><a href="itms-services://?action=download-manifest&url=#{@configuration.manifest_url}">Tap Here to Install<br />#{@configuration.target}<br />On Your Device</a></div>
78
+ <p><strong>Link didn't work?</strong><br />
79
+ Make sure you're visiting this page on your device, not your computer.</p>
80
+ </body>
81
+ </html>
82
+ }
83
+ end
84
+ end
85
+
86
+ def deploy
87
+ system("scp pkg/dist/* #{@configuration.remote_host}:#{@configuration.remote_installation_path}")
88
+ end
89
+ end
90
+ end
91
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: betabuilder
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
5
- prerelease: false
4
+ hash: 21
5
+ prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
9
8
  - 2
10
- version: 0.1.2
9
+ - 1
10
+ version: 0.2.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Luke Redpath
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-11-05 00:00:00 +00:00
18
+ date: 2011-01-20 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -50,6 +50,38 @@ dependencies:
50
50
  version: 2.3.1
51
51
  type: :runtime
52
52
  version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: rest-client
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 13
62
+ segments:
63
+ - 1
64
+ - 6
65
+ - 1
66
+ version: 1.6.1
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: json
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ hash: 11
78
+ segments:
79
+ - 1
80
+ - 4
81
+ - 6
82
+ version: 1.4.6
83
+ type: :runtime
84
+ version_requirements: *id004
53
85
  description:
54
86
  email: luke@lukeredpath.co.uk
55
87
  executables: []
@@ -59,12 +91,16 @@ extensions: []
59
91
  extra_rdoc_files:
60
92
  - README.md
61
93
  files:
94
+ - CHANGES.md
62
95
  - LICENSE
63
96
  - README.md
64
97
  - lib/beta_builder/archived_build.rb
98
+ - lib/beta_builder/deployment_strategies/testflight.rb
99
+ - lib/beta_builder/deployment_strategies/web.rb
100
+ - lib/beta_builder/deployment_strategies.rb
65
101
  - lib/beta_builder.rb
66
102
  - lib/betabuilder.rb
67
- has_rdoc: false
103
+ has_rdoc: true
68
104
  homepage: http://github.com/lukeredpath/betabuilder
69
105
  licenses: []
70
106
 
@@ -95,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
131
  requirements: []
96
132
 
97
133
  rubyforge_project:
98
- rubygems_version: 1.3.7
134
+ rubygems_version: 1.4.1
99
135
  signing_key:
100
136
  specification_version: 3
101
137
  summary: A set of Rake tasks and utilities for managing iOS ad-hoc builds