diff --git a/CHANGES.md b/CHANGES.md
index c6c9b42..18a20b7 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,18 @@
+## 0.7.5
+* Allows automation of version/build number setting
+* Adds full paths to xcodebuild and xcrun; they are override-able in the config
+* Adds ability to add arguments to package command
+* Refactors commands and argument code
+* Makes build and signing output optional (use config.verbose to trigger)
+* Adds simplified output
+* Allows custom SCP ports (@smtlaissezfaire)
+* Fixes Archive task failure (@rennarda)
+* Uses Apple's Packager to produce valid IPAs (@dts)
+* Fixes cases where CFPropertyList could fail to load (@svelix)
+* Uses relative paths for requires (@smtlaissezfaire)
+* Raises an exception if the build fails (@epall)
+
+
## 0.7.4.1
* Allow auto-archiving from other Rake namespaces (@victor)
* Fixed bug with Xcode archive sharing (@victor)
diff --git a/README.md b/README.md
index 1f55b05..2a439ae 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,82 @@ To use a namespace other than "beta" for the generated tasks, simply pass in you
This lets you set up different sets of BetaBuilder tasks for different configurations in the same Rakefile (e.g. a production and staging build).
+## Configuration
+A full list of configuration options and their details
+
+`configuration` - (String) The Xcode Configuration to use (Defined on the Info tab of the Project)
+
+`build_dir` - (File Path) The directory the build output will be. (`:derived` for Xcode 4 for versions < 0.8)
+
+`auto_archive` - (true/**false**) Automatically archive when packaging
+
+`archive_path` - (File Path) Path to the Archives
+
+`xcodebuild_path` - (File Path) Path to xcodebuild, if its non-standard
+
+`xcodeargs` - (Arguments) Arguments passed to `xcodebuild` when it runs
+
+`project_file_path` - (File Path) Path to the `.xcodeproj` file
+
+`workspace_path` - (File Path) Path to the `.xcworkspace` file
+
+`ipa_destination_path` - (File Path) Path to output Packaged IPA to
+
+`scheme` - (String) What Scheme to use when building
+
+`app_name` - (String) The name of the app being built (should match the file name, less the `.app` extension)
+
+`arch` - (String) The architecture to build for, if different from project settings
+
+`xcode4_archive_mode` - (true/**false**) Use Xcode4's Archive mode
+
+`skip_clean` - (true/**false**) Skip the clean step when building
+
+`verbose` - (true/**false**) Increased output for debugging
+
+`dry_run` - (true/**false**) Don't upload to Distribution Strategy, just act like it
+
+`set_version_number` - (true/**false**) Attempts to set a version number, see below
+
+### Configuration (Testflight)
+Testflight presents its own set of options that can be configured
+
+`api_token` - (String) Can be your API key, but its recommended to use an `ENV[""]` variable
+
+`team_token` - (String) Your Team's Testflight API token
+
+`distribution_lists` - (Array) A Ruby array (`[1,2]` or `%w{1 2}`) of distribution list names for Testflight
+
+`notify` - (true/**false**) Notify the distribution list of this build
+
+`replace` - (true/**false**) Replace if an existing build exists with the same ID and version
+
+### Configuration (Web)
+Pushing to a web server has the following options.
+
+SSH keys will simplify authentication and make this process seamless
+
+`remote_host` - (String) Hostname for the server the build will be pushed to
+
+`remote_port` - (String) Port Number to use for SCP/SFTP
+
+`remote_installation_path` - (String) Remote Path
+
+### Configuration (RunTime)
+Certain configuration options are availabe at the command line, so that you can temporarily set them for a single run without modifying your configuration.
+
+Pass any of these in as environment variables:
+
+`DRY` - (true/**false**) Enable Dry Run
+`VERBOSE` - (true/**false**) Turn on all output; lets you see the clean, build, and signing output
+`SKIPCLEAN` - (true/**false**) Skips the clean step and goes right to Build.
+
+####Examples
+
+`rake staging:deploy DRY=true`
+
+`rake staging:redeploy VERBOSE=true SKIPCLEAN=true`
+
## Xcode 4 support
Betabuilder works with Xcode 4, but you may need to tweak your task configuration slightly. The most important change you will need to make is the build directory location, unless you have configured Xcode 4 to use the "build" directory relative to your project, as in Xcode 3.
@@ -81,13 +157,29 @@ If you are working with an Xcode 4 workspace instead of a project file, you will
config.workspace_path = "MyWorkspace.xcworkspace"
config.scheme = "My App Scheme"
config.app_name = "MyApp"
-
+ set_version_number
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
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.
+## Setting version numbers automatically
+
+You can use betabuilder to set a build number using Git's `describe` system. In order for that to work, at least one `tag` must exist somewhere in the git hierarchy for the branch being built from.
+
+Also, you are required to set your `CFBundleVersion` to `${VERSION_LONG}` inside your `Info.plist`. To accomodate manual builds, add a `VERSION_LONG` Build Setting to your app's Project, and treat it as you normally would your `Info.plist` version number.
+
+Once a tag is created and your App is configured, simply add this to your BetaBuilder config and it will use Git to generate the
+
+ config.set_version_number = true
+
+It will generate a version number like: `1.0-15-g6b3c1bb`.
+
+* *1.0* is the most recent tag
+* *15* is the number of commits since the tag was generated
+* *g6b3c1bb* is the beginning of the hash of the last commit.
+
### Deploying your app with TestFlight
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.
diff --git a/Rakefile b/Rakefile
index 16ec69d..85f6c29 100644
--- a/Rakefile
+++ b/Rakefile
@@ -15,11 +15,11 @@ spec = Gem::Specification.new do |s|
# Change these as appropriate
s.name = "betabuilder"
- s.version = "0.7.4.1"
+ s.version = "0.7.5"
s.summary = "A set of Rake tasks and utilities for managing iOS ad-hoc builds"
- s.author = "Luke Redpath"
- s.email = "luke@lukeredpath.co.uk"
- s.homepage = "http://github.com/lukeredpath/betabuilder"
+ s.authors = ["Luke Redpath", "Nick Peelman"]
+ s.email = ["luke@lukeredpath.co.uk", "nick@peelman.us"]
+ s.homepage = "http://github.com/peelman/betabuilder"
s.has_rdoc = false
s.extra_rdoc_files = %w(README.md LICENSE CHANGES.md)
diff --git a/betabuilder.gemspec b/betabuilder.gemspec
index 3a37189..c0e5585 100644
--- a/betabuilder.gemspec
+++ b/betabuilder.gemspec
@@ -1,9 +1,13 @@
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s|
- s.name = "betabuilder"
- s.version = "0.7.4.1"
-
+ s.name = "betabuilder"
+ s.version = "0.7.5"
+ s.summary = "A set of Rake tasks and utilities for managing iOS ad-hoc builds"
+ s.authors = ["Luke Redpath", "Nick Peelman"]
+ s.email = ["luke@lukeredpath.co.uk", "nick@peelman.us"]
+ s.homepage = "http://github.com/peelman/betabuilder"
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Luke Redpath"]
s.date = "2012-05-16"
@@ -14,7 +18,6 @@ Gem::Specification.new do |s|
s.rdoc_options = ["--main", "README.md"]
s.require_paths = ["lib"]
s.rubygems_version = "1.8.11"
- s.summary = "A set of Rake tasks and utilities for managing iOS ad-hoc builds"
if s.respond_to? :specification_version then
s.specification_version = 3
diff --git a/lib/beta_builder.rb b/lib/beta_builder.rb
index 878a6a7..f4be383 100644
--- a/lib/beta_builder.rb
+++ b/lib/beta_builder.rb
@@ -2,9 +2,9 @@
require 'ostruct'
require 'fileutils'
require 'cfpropertylist'
-require 'beta_builder/archived_build'
-require 'beta_builder/deployment_strategies'
-require 'beta_builder/build_output_parser'
+require File.dirname(__FILE__) + '/beta_builder/archived_build'
+require File.dirname(__FILE__) + '/beta_builder/deployment_strategies'
+require File.dirname(__FILE__) + '/beta_builder/build_output_parser'
module BetaBuilder
class Tasks < ::Rake::TaskLib
@@ -14,16 +14,21 @@ def initialize(namespace = :beta, &block)
:build_dir => "build",
:auto_archive => false,
:archive_path => File.expand_path("~/Library/Developer/Xcode/Archives"),
- :xcodebuild_path => "xcodebuild",
+ :xcodebuild_path => "/usr/bin/xcodebuild",
+ :xcrun_path => "/usr/bin/xcrun",
+ :xcodeargs => nil,
+ :packageargs => nil,
:project_file_path => nil,
:workspace_path => nil,
+ :ipa_destination_path => "./",
:scheme => nil,
:app_name => nil,
:arch => nil,
:xcode4_archive_mode => false,
- :skip_clean => false,
- :verbose => false,
- :dry_run => false
+ :skip_clean => ENV.fetch('SKIPCLEAN', false),
+ :verbose => ENV.fetch('VERBOSE', false),
+ :dry_run => ENV.fetch('DRY', false),
+ :set_version_number => false
)
@namespace = namespace
yield @configuration if block_given?
@@ -32,7 +37,13 @@ def initialize(namespace = :beta, &block)
def xcodebuild(*args)
# we're using tee as we still want to see our build output on screen
- system("#{@configuration.xcodebuild_path} #{args.join(" ")} | tee build.output")
+ cmd = []
+ cmd << @configuration.xcodebuild_path
+ cmd.concat args
+ puts "Running: #{cmd.join(" ")}" if @configuration.verbose
+ cmd << "2>&1 %s build.output" % (@configuration.verbose ? '| tee' : '>')
+ cmd = cmd.join(" ")
+ system(cmd)
end
class Configuration < OpenStruct
@@ -41,17 +52,26 @@ def release_notes_text
release_notes
end
def build_arguments
- args = ""
+ args = []
if workspace_path
raise "A scheme is required if building from a workspace" unless scheme
- args << "-workspace '#{workspace_path}' -scheme '#{scheme}' -configuration '#{configuration}'"
+ args << "-workspace '#{workspace_path}'"
+ args << "-scheme '#{scheme}'"
else
- args = "-target '#{target}' -configuration '#{configuration}' -sdk iphoneos"
- args << " -project #{project_file_path}" if project_file_path
+ args << "-target '#{target}'"
+ args << "-sdk iphoneos"
+ args << "-project '#{project_file_path}'" if project_file_path
end
-
- args << " -arch \"#{arch}\"" unless arch.nil?
-
+
+ args << "-configuration '#{configuration}'"
+ args << "-arch '#{arch}'" unless arch.nil?
+ args << "VERSION_LONG='#{build_number_git}'" if set_version_number
+
+ if xcodeargs
+ args.concat xcodeargs if xcodeargs.is_a? Array
+ args << "#{xcodeargs}" if xcodears.is_a? String
+ end
+
args
end
@@ -74,16 +94,24 @@ def ipa_name
else
"#{target}.ipa"
end
- end
+ end
def built_app_path
if build_dir == :derived
- "#{derived_build_dir_from_build_output}/#{configuration}-iphoneos/#{app_file_name}"
+ File.join("#{derived_build_dir}", "#{configuration}-iphoneos", "#{app_file_name}")
else
- "#{build_dir}/#{configuration}-iphoneos/#{app_file_name}"
+ File.join("#{build_dir}", "#{configuration}-iphoneos", "#{app_file_name}")
+ end
+ end
+
+
+ def derived_build_dir
+ for dir in Dir[File.join(File.expand_path("~/Library/Developer/Xcode/DerivedData"), "#{app_name}-*")]
+ return "#{dir}/Build/Products" if File.read( File.join(dir, "info.plist") ).match workspace_path
end
end
+
def derived_build_dir_from_build_output
output = BuildOutputParser.new(File.read("build.output"))
output.build_output_dir
@@ -93,12 +121,12 @@ def built_app_dsym_path
"#{built_app_path}.dSYM"
end
- def dist_path
- File.join("pkg/dist")
+ def ipa_path
+ File.join(File.expand_path(ipa_destination_path), ipa_name)
end
- def ipa_path
- File.join(dist_path, ipa_name)
+ def build_number_git
+ `git describe --tags --abbrev=1`.chop
end
def deploy_using(strategy_name, &block)
@@ -114,34 +142,64 @@ def deploy_using(strategy_name, &block)
private
def define
- namespace(@namespace) do
- desc "Build the beta release of the app"
- task :build => :clean do
- xcodebuild @configuration.build_arguments, "build"
- end
-
+ namespace(@namespace) do
+ desc "Clean the Build"
task :clean do
unless @configuration.skip_clean
+ print "Cleaning Project..."
xcodebuild @configuration.build_arguments, "clean"
+ puts "Done"
end
end
+ desc "Build the beta release of the app"
+ task :build => :clean do
+ print "Building Project..."
+ xcodebuild @configuration.build_arguments, "build"
+ raise "** BUILD FAILED **" if BuildOutputParser.new(File.read("build.output")).failed?
+ puts "Done"
+ end
+
desc "Package the beta release as an IPA file"
task :package => :build do
if @configuration.auto_archive
Rake::Task["#{@namespace}:archive"].invoke
end
-
- FileUtils.rm_rf('pkg') && FileUtils.mkdir_p('pkg')
- FileUtils.mkdir_p("pkg/Payload")
- FileUtils.mv(@configuration.built_app_path, "pkg/Payload/#{@configuration.app_file_name}")
- Dir.chdir("pkg") do
- system("zip -r '#{@configuration.ipa_name}' Payload")
+ print "Packaging and Signing..."
+ raise "** PACKAGE FAILED ** No Signing Identity Found" unless @configuration.signing_identity
+ raise "** PACKAGE FAILED ** No Provisioning Profile Found" unless @configuration.provisioning_profile
+
+ # Construct the IPA and Sign it
+ cmd = []
+ cmd << @configuration.xcrun_path
+ cmd << "-sdk iphoneos"
+ cmd << "PackageApplication"
+ cmd << "-v '#{@configuration.built_app_path}'"
+ cmd << "-o '#{@configuration.ipa_path}'"
+ cmd << "--sign '#{@configuration.signing_identity}'"
+ cmd << "--embed '#{@configuration.provisioning_profile}'"
+ if @configuration.packageargs
+ cmd.concat @configuration.packageargs if @configuration.packageargs.is_a? Array
+ cmd << @configuration.packageargs if @configuration.packageargs.is_a? String
end
- FileUtils.mkdir('pkg/dist')
- FileUtils.mv("pkg/#{@configuration.ipa_name}", "pkg/dist")
+ puts "Running #{cmd.join(" ")}" if @configuration.verbose
+ cmd << "2>&1 %s build.output" % (@configuration.verbose ? '| tee' : '>')
+ cmd = cmd.join(" ")
+ system(cmd)
+
+ puts "Done"
+
+ puts "IPA File: #{@configuration.ipa_path}" if @configuration.verbose
end
-
+
+ desc "Build and archive the app"
+ task :archive => :build do
+ puts "Archiving build..."
+ archive = BetaBuilder.archive(@configuration)
+ output_path = archive.save_to(@configuration.archive_path)
+ puts "Archive saved to #{output_path}."
+ end
+
if @configuration.deployment_strategy
desc "Prepare your app for deployment"
task :prepare => :package do
@@ -159,14 +217,6 @@ def define
@configuration.deployment_strategy.deploy
end
end
-
- desc "Build and archive the app"
- task :archive => :build do
- puts "Archiving build..."
- archive = BetaBuilder.archive(@configuration)
- output_path = archive.save_to(@configuration.archive_path)
- puts "Archive saved to #{output_path}."
- end
end
end
end
diff --git a/lib/beta_builder/archived_build.rb b/lib/beta_builder/archived_build.rb
index d9639e0..a79ac1c 100644
--- a/lib/beta_builder/archived_build.rb
+++ b/lib/beta_builder/archived_build.rb
@@ -1,6 +1,6 @@
require 'uuid'
require 'fileutils'
-require 'CFPropertyList'
+require 'cfpropertylist'
module BetaBuilder
def self.archive(configuration)
@@ -76,7 +76,7 @@ def write_plist_to(path)
"ApplicationPath" => File.join("Applications", @configuration.app_file_name),
"CFBundleIdentifier" => metadata["CFBundleIdentifier"],
"CFBundleShortVersionString" => version,
- "IconPaths" => metadata["CFBundleIconFiles"].map { |file| File.join("Applications", @configuration.app_file_name, file) }
+ "IconPaths" => metadata["CFBundleIcons"]["CFBundlePrimaryIcon"]["CFBundleIconFiles"].map { |file| File.join("Applications", @configuration.app_file_name, file) }
},
"ArchiveVersion" => 1.0,
"Comment" => @configuration.release_notes_text,
diff --git a/lib/beta_builder/build_output_parser.rb b/lib/beta_builder/build_output_parser.rb
index ec8ff97..f0fe1bc 100644
--- a/lib/beta_builder/build_output_parser.rb
+++ b/lib/beta_builder/build_output_parser.rb
@@ -16,6 +16,10 @@ def build_output_dir
derived_data_directory = reference.split("/Build/Products/").first
"#{derived_data_directory}/Build/Products/"
end
+
+ def failed?
+ @output.split("\n").any? {|line| line.include? "** BUILD FAILED **"}
+ end
end
end
diff --git a/lib/beta_builder/deployment_strategies.rb b/lib/beta_builder/deployment_strategies.rb
index 24a89f7..4520ebd 100644
--- a/lib/beta_builder/deployment_strategies.rb
+++ b/lib/beta_builder/deployment_strategies.rb
@@ -22,7 +22,7 @@ def configure(&block)
end
def prepare
- puts "Nothing to prepare!"
+ puts "Nothing to prepare!" if @configuration.verbose
end
end
@@ -34,6 +34,6 @@ def self.strategies
end
end
-require 'beta_builder/deployment_strategies/web'
-require 'beta_builder/deployment_strategies/testflight'
+require File.dirname(__FILE__) + '/deployment_strategies/web'
+require File.dirname(__FILE__) + '/deployment_strategies/testflight'
diff --git a/lib/beta_builder/deployment_strategies/testflight.rb b/lib/beta_builder/deployment_strategies/testflight.rb
index 41250aa..3afbaa1 100644
--- a/lib/beta_builder/deployment_strategies/testflight.rb
+++ b/lib/beta_builder/deployment_strategies/testflight.rb
@@ -6,6 +6,8 @@
module BetaBuilder
module DeploymentStrategies
class TestFlight < Strategy
+ include Rake::DSL
+ include FileUtils
ENDPOINT = "https://testflightapp.com/api/builds.json"
def extended_configuration_for_strategy
@@ -27,7 +29,6 @@ def deploy
:notify => @configuration.notify || false,
:replace => @configuration.replace || false
}
- puts "Uploading build to TestFlight..."
if @configuration.verbose
puts "ipa path: #{@configuration.ipa_path}"
puts "release notes: #{release_notes}"
@@ -38,6 +39,8 @@ def deploy
return
end
+ print "Uploading build to TestFlight..."
+
begin
response = RestClient.post(ENDPOINT, payload, :accept => :json)
rescue => e
@@ -45,9 +48,10 @@ def deploy
end
if (response.code == 201) || (response.code == 200)
- puts "Upload complete."
+ puts "Done."
else
- puts "Upload failed. (#{response})"
+ puts "Failed."
+ puts "#{response}"
end
end
diff --git a/lib/beta_builder/deployment_strategies/web.rb b/lib/beta_builder/deployment_strategies/web.rb
index 7510d2b..62293e1 100644
--- a/lib/beta_builder/deployment_strategies/web.rb
+++ b/lib/beta_builder/deployment_strategies/web.rb
@@ -4,87 +4,100 @@ class Web < Strategy
def extended_configuration_for_strategy
proc do
def deployment_url
- File.join(deploy_to, target.downcase, ipa_name)
+ File.join(deploy_to, ipa_name)
end
def manifest_url
- File.join(deploy_to, target.downcase, "manifest.plist")
+ File.join(deploy_to, "manifest.plist")
end
def remote_installation_path
- File.join(remote_directory, target.downcase)
+ File.join(remote_directory)
end
end
end
-
+
def prepare
- plist = CFPropertyList::List.new(:file => "pkg/Payload/#{@configuration.app_name}/Info.plist")
+ plist = CFPropertyList::List.new(:file => "#{@configuration.built_app_path}/Info.plist")
plist_data = CFPropertyList.native_types(plist.value)
File.open("pkg/dist/manifest.plist", "w") do |io|
- io << %{
-
-
-
Link didn't work?
- Make sure you're visiting this page on your device, not your computer.
Link didn't work?
+ Make sure you're visiting this page on your device, not your computer.