diff --git a/README.md b/README.md index 1f55b05..4f862d3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # BetaBuilder, a gem for managing iOS ad-hoc builds -BetaBuilder is a simple collection of Rake tasks and utilities for managing and publishing Adhoc builds of your iOS apps. +BetaBuilder is a simple collection of Rake tasks and utilities for managing and publishing Adhoc builds of your iOS apps. If you're looking for the OSX BetaBuilder app -- to which this gem owes most of the credit -- you can find it [here on Github](http://github.com/HunterHillegas/iOS-BetaBuilder). @@ -26,7 +26,7 @@ At the top of your Rakefile, you'll need to require `rubygems` and the `betabuil require 'rubygems' require 'betabuilder' - + Because BetaBuilder is a Rake task library, you do not need to define any tasks yourself. You simply need to configure BetaBuilder with some basic information about your project and it will generate the tasks for you. A sample configuration might look something like this: BetaBuilder::Tasks.new do |config| @@ -34,14 +34,14 @@ Because BetaBuilder is a Rake task library, you do not need to define any tasks config.target = "MyGreatApp" # the Xcode configuration profile - config.configuration = "Adhoc" + config.configuration = "Adhoc" end - + 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: rake beta:build # Build the beta release of the app rake beta:package # Package the beta release as an IPA file - + If you use a custom Xcode build directory, rather than the default `${SRCROOT}/build` location, you can configure that too: BetaBuilder::Tasks.new do |config| @@ -59,7 +59,7 @@ To use a namespace other than "beta" for the generated tasks, simply pass in you BetaBuilder::Tasks.new(:my_custom_namespace) do |config| end - + This lets you set up different sets of BetaBuilder tasks for different configurations in the same Rakefile (e.g. a production and staging build). ## Xcode 4 support @@ -69,19 +69,19 @@ Betabuilder works with Xcode 4, but you may need to tweak your task configuratio If you are using the Xcode derived data directory for your builds, then you will need to specify this. Betabuilder will then scan your build log to determine the path to the automatically generated build directory that Xcode is using for your project. config.build_dir = :derived - + This will become the default in 0.8. If you wish to generate archives for your Xcode 4 project, you will need to enable this. This will become the default in future once Xcode 3 support is dropped (deprecated in 0.7): config.xcode4_archive_mode = true - + If you are working with an Xcode 4 workspace instead of a project file, you will need to configure this too: config.workspace_path = "MyWorkspace.xcworkspace" config.scheme = "My App Scheme" config.app_name = "MyApp" - + If you are using a workspace, then you must specify the scheme. You can still specify the build configuration (e.g. Release). ## Automatic deployment with deployment strategies @@ -98,8 +98,8 @@ TestFlight provides an upload API and betabuilder uses that to provide a `:testf tf.api_token = "YOUR_API_TOKEN" tf.team_token = "YOUR_TEAM_TOKEN" end - -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. + +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. 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. @@ -109,7 +109,7 @@ You will be prompted to enter the release notes for the build; TestFlight requir # return release notes here end end - + Finally, you can also specify an array of distribution lists that you want to allow access to the build: config.deploy_using(:testflight) do |tf| @@ -117,6 +117,22 @@ Finally, you can also specify an array of distribution lists that you want to al tf.distribution_lists = %w{Testers Internal} end +### Deploying to HockeyApp + +Similar to TestFlight you can also use [HockeyApp](http://hockeyapp.net), you'll need your API token and your App ID. + +Example: + + config.deploy_using(:hockeyapp) do |puck| + puck.api_token = "YOUR_API_TOKEN" + puck.app_id = "YOUR_APP_ID" + puck.allow_download = true + + puck.generate_release_notes do + # return release notes here + end + end + ### Deploying to your own server 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. @@ -128,7 +144,7 @@ You will to configure betabuilder to use the `web` deployment strategy with some web.remote_host = "myserver.com" web.remote_directory = "/remote/path/to/deployment/directory" end - + 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. ## License diff --git a/lib/beta_builder/deployment_strategies.rb b/lib/beta_builder/deployment_strategies.rb index 24a89f7..45b1e4b 100644 --- a/lib/beta_builder/deployment_strategies.rb +++ b/lib/beta_builder/deployment_strategies.rb @@ -29,11 +29,11 @@ def prepare private def self.strategies - {:web => Web, :testflight => TestFlight} + {:web => Web, :testflight => TestFlight, :hockeyapp => HockeyApp} end end end require 'beta_builder/deployment_strategies/web' require 'beta_builder/deployment_strategies/testflight' - +require 'beta_builder/deployment_strategies/hockeyapp' diff --git a/lib/beta_builder/deployment_strategies/hockeyapp.rb b/lib/beta_builder/deployment_strategies/hockeyapp.rb new file mode 100644 index 0000000..e19d854 --- /dev/null +++ b/lib/beta_builder/deployment_strategies/hockeyapp.rb @@ -0,0 +1,118 @@ +require 'rest_client' +require 'json' +require 'tmpdir' +require 'fileutils' + +# # Deployment strategy for [HockeyApp](http://hockeyapp.net) +# +# Example API upload: +# +# curl \ +# -F "status=2" \ +# -F "notify=1" \ +# -F "notes=Some new features and fixed bugs." \ +# -F "notes_type=0" \ +# -F "ipa=@hockeyapp.ipa" \ +# -F "dsym=@hockeyapp.dSYM.zip" \ +# -H "X-HockeyAppToken: 4567abcd8901ef234567abcd8901ef23" \ +# https://rink.hockeyapp.net/api/2/apps/1234567890abcdef1234567890abcdef/app_versions +# +module BetaBuilder + module DeploymentStrategies + class HockeyApp < Strategy + + def endpoint(app_id) + "https://rink.hockeyapp.net/api/2/apps/#{app_id}/app_versions" + end + + def extended_configuration_for_strategy + proc do + def generate_release_notes(&block) + self.release_notes = block if block + end + end + end + + # 1: Don't allow users to download or install the version + # 2: Available for download or installation + def status_flag(allow_download = false) + allow_download ? 2 : 1 + end + + # 0 - Don't notify testers + # 1 - Notify all testers that can install this app + def notify_flag(notify = false) + notify ? 1 : 0 + end + + def deploy + release_notes = get_notes + payload = { + :status => status_flag(@configuration.allow_download) + :ipa => File.new(@configuration.ipa_path, 'rb'), + :notes => release_notes, + :notify => notify_flag(@configuration.notify), + } + api_token = @configuration.api_token + + puts "Uploading build to Hockey App..." + if @configuration.verbose + puts "ipa path: #{@configuration.ipa_path}" + puts "release notes: #{release_notes}" + puts payload.inspect + end + + if @configuration.dry_run + puts '** Dry Run - No action here! **' + puts payload.inspect + return + end + + begin + response = RestClient.post(endpoint(@configuration.app_id), payload, {:accept => :json, "X-HockeyAppToken" => api_token}) + rescue => e + response = e.response + end + + if (response.code == 201) || (response.code == 200) + puts "Upload complete." + else + puts "Upload failed. (#{response})" + end + end + + private + + def get_notes + notes = @configuration.release_notes_text + notes || get_notes_using_editor || get_notes_using_prompt + end + + def get_notes_using_editor + return unless (editor = ENV["EDITOR"]) + + dir = Dir.mktmpdir + begin + filepath = "#{dir}/release_notes" + system("#{editor} #{filepath}") + @configuration.release_notes = File.read(filepath) + ensure + rm_rf(dir) + end + end + + def get_notes_using_prompt + puts "Enter the release notes for this build (hit enter twice when done):\n" + @configuration.release_notes = gets_until_match(/\n{2}$/).strip + end + + def gets_until_match(pattern, string = "") + if (string += STDIN.gets) =~ pattern + string + else + gets_until_match(pattern, string) + end + end + end + end +end